Skip to content

Commit f1ec0aa

Browse files
Merge pull request #379 from StackStorm/jira_sensor_action
RFR: Add sample JIRA sensor and action (Pass #1)
2 parents 736f3ac + 2835828 commit f1ec0aa

File tree

6 files changed

+286
-0
lines changed

6 files changed

+286
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# JIRA integration
2+
This pack consists of a sample JIRA sensor and a JIRA action.
3+
4+
## JIRA sensor
5+
The sensor monitors for new projects and sends a trigger into the system whenever there is a new project.
6+
7+
## JIRA action
8+
The action script allows you to create a JIRA issue.
9+
10+
## Requirements
11+
To use either of the sensor or action, following are the dependencies:
12+
13+
1. Python 2.7 or later. (Might work with 2.6. Not tested.)
14+
2. pip install jira # installs python JIRA client
15+
16+
## Configuration
17+
Sensor and action come with a json configuration file (jira_config.json). You'll need to configure the following:
18+
19+
1. JIRA server
20+
2. OAuth token
21+
3. OAuth secret
22+
4. Consumer key
23+
24+
To get these OAuth credentials, take a look at OAuth section.
25+
26+
## OAuth
27+
## Disclaimer
28+
This documentation is written as of 06/17/2014. JIRA 6.3 implements OAuth1. Most of this doc would need to be revised when JIRA switches to OAuth2.
29+
30+
## Steps
31+
1. Generate RSA public/private key pair
32+
```
33+
# This will create a 2048 length RSA private key
34+
$openssl genrsa -out mykey.pem 2048
35+
```
36+
37+
```
38+
# Now, create the public key associated with that private key
39+
openssl rsa -in mykey.pem -pubout
40+
```
41+
2. Generate a consumer key. You can use python uuid.uuid4() to do this.
42+
3. Configure JIRA for external access:
43+
* Go to AppLinks section of your JIRA - https://JIRA_SERVER/plugins/servlet/applinks/listApplicationLinks
44+
* Create a Generic Application with some fake URL
45+
* Click Edit, hit IncomingAuthentication. Plug in the consumer key and RSA public key you generated.
46+
4. Get access token using this [script](https://github.com/lakshmi-kannan/jira-oauth-access-token-generator/blob/master/generate_access_token.py). These are the ones that are printed at the last. Save these keys somewhere safe.
47+
5. Plug in the access token and access secret into the sensor or action. You are good to make JIRA calls. Note: OAuth token expires. You'll have to repeat the process based on the expiry date.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env python
2+
3+
# Requirements
4+
# pip install jira
5+
6+
try:
7+
import simplejson as json
8+
except ImportError:
9+
import json
10+
import os
11+
import sys
12+
13+
from jira.client import JIRA
14+
15+
CONFIG_FILE = './jira_config.json'
16+
17+
18+
class AuthedJiraClient(object):
19+
def __init__(self, jira_server, oauth_creds):
20+
self._client = JIRA(options={'server': jira_server},
21+
oauth=oauth_creds)
22+
23+
def is_project_exists(self, project):
24+
projs = self._client.projects()
25+
project_names = [proj.key for proj in projs]
26+
if project not in project_names:
27+
return False
28+
return True
29+
30+
def create_issue(self, project=None, summary=None, desc=None, issuetype=None):
31+
issue_dict = {
32+
'project': {'key': project},
33+
'summary': summary,
34+
'description': desc,
35+
'issuetype': {'name': issuetype},
36+
}
37+
new_issue = self._client.create_issue(fields=issue_dict)
38+
return new_issue
39+
40+
41+
def _read_cert(file_path):
42+
with open(file_path) as f:
43+
return f.read()
44+
45+
46+
def _parse_args(args):
47+
params = {}
48+
params['project_name'] = args[1]
49+
params['issue_summary'] = args[2]
50+
params['issue_description'] = args[3]
51+
params['issue_type'] = args[4]
52+
return params
53+
54+
55+
def _get_jira_client(config):
56+
rsa_cert_file = config['rsa_cert_file']
57+
if not os.path.exists(rsa_cert_file):
58+
raise Exception('Cert file for JIRA OAuth not found at %s.' % rsa_cert_file)
59+
rsa_key = _read_cert(rsa_cert_file)
60+
oauth_creds = {
61+
'access_token': config['oauth_token'],
62+
'access_token_secret': config['oauth_secret'],
63+
'consumer_key': config['consumer_key'],
64+
'key_cert': rsa_key
65+
}
66+
jira_client = AuthedJiraClient(config['jira_server'], oauth_creds)
67+
return jira_client
68+
69+
70+
def _get_config():
71+
global CONFIG_FILE
72+
if not os.path.exists(CONFIG_FILE):
73+
raise Exception('Config file not found at %s.' % CONFIG_FILE)
74+
with open(CONFIG_FILE) as f:
75+
return json.load(f)
76+
77+
78+
def main(args):
79+
try:
80+
client = _get_jira_client(_get_config())
81+
except Exception as e:
82+
sys.stderr.write('Failed to create JIRA client: %s\n' % str(e))
83+
sys.exit(1)
84+
85+
params = _parse_args(args)
86+
proj = params['project_name']
87+
try:
88+
if not client.is_project_exists(proj):
89+
raise Exception('Project ' + proj + ' does not exist.')
90+
issue = client.create_issue(project=params['project_name'],
91+
summary=params['issue_summary'],
92+
desc=params['issue_description'],
93+
issuetype=params['issue_type'])
94+
except Exception as e:
95+
sys.stderr.write(str(e) + '\n')
96+
sys.exit(2)
97+
else:
98+
sys.stdout.write('Issue ' + issue + ' created.\n')
99+
100+
if __name__ == '__main__':
101+
main(sys.argv)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"rsa_cert_file": "/home/vagrant/jira.pem",
3+
"oauth_token": "",
4+
"oauth_secret": "",
5+
"consumer_key": ""
6+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "jira-create-issue",
3+
"runner_type": "remote-exec-sysuser",
4+
"description": "Create JIRA issue action.",
5+
"enabled": true,
6+
"entry_point": "jira/create_issue.py",
7+
"parameters": {
8+
"jira_server": {
9+
"type": "string"
10+
},
11+
"oauth_token": {
12+
"type": "string"
13+
},
14+
"oauth_token_secret": {
15+
"type": "string"
16+
},
17+
"consumer_key": {
18+
"type": "string"
19+
},
20+
"project_name": {
21+
"type": "string"
22+
},
23+
"issue_summary": {
24+
"type": "string"
25+
},
26+
"issue_description": {
27+
"type": "string"
28+
},
29+
"issue_type": {
30+
"type": "string"
31+
}
32+
}
33+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"rsa_cert_file": "/home/vagrant/jira.pem",
3+
"oauth_token": "",
4+
"oauth_secret": "",
5+
"consumer_key": ""
6+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Requirements
2+
# pip install jira
3+
4+
try:
5+
import simplejson as json
6+
except ImportError:
7+
import json
8+
import os
9+
import time
10+
11+
from jira.client import JIRA
12+
13+
CONFIG_FILE = './jira_config.json'
14+
15+
16+
class JIRASensor(object):
17+
'''
18+
Sensor will monitor for any new projects created in JIRA and
19+
emit trigger instance when one is created.
20+
'''
21+
def __init__(self, container_service):
22+
self._container_service = container_service
23+
self._jira_server = 'https://stackstorm.atlassian.net'
24+
# The Consumer Key created while setting up the "Incoming Authentication" in
25+
# JIRA for the Application Link.
26+
self._consumer_key = u''
27+
self._rsa_key = None
28+
self._jira_client = None
29+
self._access_token = u''
30+
self._access_secret = u''
31+
self._projects_available = None
32+
self._sleep_time = 30
33+
self._config = None
34+
35+
def _read_cert(self, file_path):
36+
with open(file_path) as f:
37+
return f.read()
38+
39+
def _parse_config(self):
40+
global CONFIG_FILE
41+
if not os.path.exists(CONFIG_FILE):
42+
raise Exception('Config file %s not found.' % CONFIG_FILE)
43+
with open(CONFIG_FILE) as f:
44+
self._config = json.load(f)
45+
rsa_cert_file = self._config['rsa_cert_file']
46+
if not os.path.exists(rsa_cert_file):
47+
raise Exception('Cert file for JIRA OAuth not found at %s.' % rsa_cert_file)
48+
self._rsa_key = self._read_cert(rsa_cert_file)
49+
50+
def setup(self):
51+
self._parse_config()
52+
oauth_creds = {
53+
'access_token': self._config['oauth_token'],
54+
'access_token_secret': self._config['oauth_secret'],
55+
'consumer_key': self._config['consumer_key'],
56+
'key_cert': self._rsa_key
57+
}
58+
59+
self._jira_client = JIRA(options={'server': self._jira_server},
60+
oauth=oauth_creds)
61+
if self._projects_available is None:
62+
self._projects_available = set()
63+
for proj in self._jira_client.projects():
64+
self._projects_available.add(proj.key)
65+
66+
def start(self):
67+
while True:
68+
for proj in self._jira_client.projects():
69+
if proj.key not in self._projects_available:
70+
self._dispatch_trigger(proj)
71+
self._projects_available.add(proj.key)
72+
time.sleep(self._sleep_time)
73+
74+
def stop(self):
75+
pass
76+
77+
def get_trigger_types(self):
78+
return [
79+
{
80+
'name': 'st2.jira.project_tracker',
81+
'description': 'Stackstorm JIRA projects tracker',
82+
'payload_info': ['project_name', 'project_url']
83+
}
84+
]
85+
86+
def _dispatch_trigger(self, proj):
87+
trigger = {}
88+
trigger['name'] = 'st2.jira.projects-tracker'
89+
payload = {}
90+
payload['project_name'] = proj.key
91+
payload['project_url'] = proj.self
92+
trigger['payload'] = payload
93+
self._container_service.dispatch(trigger)

0 commit comments

Comments
 (0)