diff --git a/contrib/sandbox/packages/jira/README.md b/contrib/sandbox/packages/jira/README.md new file mode 100644 index 0000000000..81c49b2c42 --- /dev/null +++ b/contrib/sandbox/packages/jira/README.md @@ -0,0 +1,47 @@ +# JIRA integration +This pack consists of a sample JIRA sensor and a JIRA action. + +## JIRA sensor +The sensor monitors for new projects and sends a trigger into the system whenever there is a new project. + +## JIRA action +The action script allows you to create a JIRA issue. + +## Requirements +To use either of the sensor or action, following are the dependencies: + +1. Python 2.7 or later. (Might work with 2.6. Not tested.) +2. pip install jira # installs python JIRA client + +## Configuration +Sensor and action come with a json configuration file (jira_config.json). You'll need to configure the following: + +1. JIRA server +2. OAuth token +3. OAuth secret +4. Consumer key + +To get these OAuth credentials, take a look at OAuth section. + +## OAuth +## Disclaimer +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. + +## Steps +1. Generate RSA public/private key pair + ``` + # This will create a 2048 length RSA private key + $openssl genrsa -out mykey.pem 2048 + ``` + + ``` + # Now, create the public key associated with that private key + openssl rsa -in mykey.pem -pubout + ``` +2. Generate a consumer key. You can use python uuid.uuid4() to do this. +3. Configure JIRA for external access: + * Go to AppLinks section of your JIRA - https://JIRA_SERVER/plugins/servlet/applinks/listApplicationLinks + * Create a Generic Application with some fake URL + * Click Edit, hit IncomingAuthentication. Plug in the consumer key and RSA public key you generated. +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. +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. diff --git a/contrib/sandbox/packages/jira/actions/create_issue.py b/contrib/sandbox/packages/jira/actions/create_issue.py new file mode 100755 index 0000000000..16e8e5a043 --- /dev/null +++ b/contrib/sandbox/packages/jira/actions/create_issue.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python + +# Requirements +# pip install jira + +try: + import simplejson as json +except ImportError: + import json +import os +import sys + +from jira.client import JIRA + +CONFIG_FILE = './jira_config.json' + + +class AuthedJiraClient(object): + def __init__(self, jira_server, oauth_creds): + self._client = JIRA(options={'server': jira_server}, + oauth=oauth_creds) + + def is_project_exists(self, project): + projs = self._client.projects() + project_names = [proj.key for proj in projs] + if project not in project_names: + return False + return True + + def create_issue(self, project=None, summary=None, desc=None, issuetype=None): + issue_dict = { + 'project': {'key': project}, + 'summary': summary, + 'description': desc, + 'issuetype': {'name': issuetype}, + } + new_issue = self._client.create_issue(fields=issue_dict) + return new_issue + + +def _read_cert(file_path): + with open(file_path) as f: + return f.read() + + +def _parse_args(args): + params = {} + params['project_name'] = args[1] + params['issue_summary'] = args[2] + params['issue_description'] = args[3] + params['issue_type'] = args[4] + return params + + +def _get_jira_client(config): + rsa_cert_file = config['rsa_cert_file'] + if not os.path.exists(rsa_cert_file): + raise Exception('Cert file for JIRA OAuth not found at %s.' % rsa_cert_file) + rsa_key = _read_cert(rsa_cert_file) + oauth_creds = { + 'access_token': config['oauth_token'], + 'access_token_secret': config['oauth_secret'], + 'consumer_key': config['consumer_key'], + 'key_cert': rsa_key + } + jira_client = AuthedJiraClient(config['jira_server'], oauth_creds) + return jira_client + + +def _get_config(): + global CONFIG_FILE + if not os.path.exists(CONFIG_FILE): + raise Exception('Config file not found at %s.' % CONFIG_FILE) + with open(CONFIG_FILE) as f: + return json.load(f) + + +def main(args): + try: + client = _get_jira_client(_get_config()) + except Exception as e: + sys.stderr.write('Failed to create JIRA client: %s\n' % str(e)) + sys.exit(1) + + params = _parse_args(args) + proj = params['project_name'] + try: + if not client.is_project_exists(proj): + raise Exception('Project ' + proj + ' does not exist.') + issue = client.create_issue(project=params['project_name'], + summary=params['issue_summary'], + desc=params['issue_description'], + issuetype=params['issue_type']) + except Exception as e: + sys.stderr.write(str(e) + '\n') + sys.exit(2) + else: + sys.stdout.write('Issue ' + issue + ' created.\n') + +if __name__ == '__main__': + main(sys.argv) diff --git a/contrib/sandbox/packages/jira/actions/jira_config.json b/contrib/sandbox/packages/jira/actions/jira_config.json new file mode 100644 index 0000000000..b38f9a732d --- /dev/null +++ b/contrib/sandbox/packages/jira/actions/jira_config.json @@ -0,0 +1,6 @@ +{ + "rsa_cert_file": "/home/vagrant/jira.pem", + "oauth_token": "", + "oauth_secret": "", + "consumer_key": "" +} diff --git a/contrib/sandbox/packages/jira/actions/jira_create_issue.json b/contrib/sandbox/packages/jira/actions/jira_create_issue.json new file mode 100644 index 0000000000..f71ac31396 --- /dev/null +++ b/contrib/sandbox/packages/jira/actions/jira_create_issue.json @@ -0,0 +1,33 @@ +{ + "name": "jira-create-issue", + "runner_type": "remote-exec-sysuser", + "description": "Create JIRA issue action.", + "enabled": true, + "entry_point": "jira/create_issue.py", + "parameters": { + "jira_server": { + "type": "string" + }, + "oauth_token": { + "type": "string" + }, + "oauth_token_secret": { + "type": "string" + }, + "consumer_key": { + "type": "string" + }, + "project_name": { + "type": "string" + }, + "issue_summary": { + "type": "string" + }, + "issue_description": { + "type": "string" + }, + "issue_type": { + "type": "string" + } + } +} diff --git a/contrib/sandbox/packages/jira/sensors/jira_config.json b/contrib/sandbox/packages/jira/sensors/jira_config.json new file mode 100644 index 0000000000..b38f9a732d --- /dev/null +++ b/contrib/sandbox/packages/jira/sensors/jira_config.json @@ -0,0 +1,6 @@ +{ + "rsa_cert_file": "/home/vagrant/jira.pem", + "oauth_token": "", + "oauth_secret": "", + "consumer_key": "" +} diff --git a/contrib/sandbox/packages/jira/sensors/jira_sensor.py b/contrib/sandbox/packages/jira/sensors/jira_sensor.py new file mode 100644 index 0000000000..00635b87b2 --- /dev/null +++ b/contrib/sandbox/packages/jira/sensors/jira_sensor.py @@ -0,0 +1,93 @@ +# Requirements +# pip install jira + +try: + import simplejson as json +except ImportError: + import json +import os +import time + +from jira.client import JIRA + +CONFIG_FILE = './jira_config.json' + + +class JIRASensor(object): + ''' + Sensor will monitor for any new projects created in JIRA and + emit trigger instance when one is created. + ''' + def __init__(self, container_service): + self._container_service = container_service + self._jira_server = 'https://stackstorm.atlassian.net' + # The Consumer Key created while setting up the "Incoming Authentication" in + # JIRA for the Application Link. + self._consumer_key = u'' + self._rsa_key = None + self._jira_client = None + self._access_token = u'' + self._access_secret = u'' + self._projects_available = None + self._sleep_time = 30 + self._config = None + + def _read_cert(self, file_path): + with open(file_path) as f: + return f.read() + + def _parse_config(self): + global CONFIG_FILE + if not os.path.exists(CONFIG_FILE): + raise Exception('Config file %s not found.' % CONFIG_FILE) + with open(CONFIG_FILE) as f: + self._config = json.load(f) + rsa_cert_file = self._config['rsa_cert_file'] + if not os.path.exists(rsa_cert_file): + raise Exception('Cert file for JIRA OAuth not found at %s.' % rsa_cert_file) + self._rsa_key = self._read_cert(rsa_cert_file) + + def setup(self): + self._parse_config() + oauth_creds = { + 'access_token': self._config['oauth_token'], + 'access_token_secret': self._config['oauth_secret'], + 'consumer_key': self._config['consumer_key'], + 'key_cert': self._rsa_key + } + + self._jira_client = JIRA(options={'server': self._jira_server}, + oauth=oauth_creds) + if self._projects_available is None: + self._projects_available = set() + for proj in self._jira_client.projects(): + self._projects_available.add(proj.key) + + def start(self): + while True: + for proj in self._jira_client.projects(): + if proj.key not in self._projects_available: + self._dispatch_trigger(proj) + self._projects_available.add(proj.key) + time.sleep(self._sleep_time) + + def stop(self): + pass + + def get_trigger_types(self): + return [ + { + 'name': 'st2.jira.project_tracker', + 'description': 'Stackstorm JIRA projects tracker', + 'payload_info': ['project_name', 'project_url'] + } + ] + + def _dispatch_trigger(self, proj): + trigger = {} + trigger['name'] = 'st2.jira.projects-tracker' + payload = {} + payload['project_name'] = proj.key + payload['project_url'] = proj.self + trigger['payload'] = payload + self._container_service.dispatch(trigger)