Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions contrib/sandbox/packages/jira/README.md
Original file line number Diff line number Diff line change
@@ -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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add Header, a sentence of "what is this" - a JIRA pack with one action and one sensor - at the beginning.
Add some details of how to configure sensor and action.

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.
101 changes: 101 additions & 0 deletions contrib/sandbox/packages/jira/actions/create_issue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env python

# Requirements
# pip install jira
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we handle dependencies for now? Capture in pack README? Add to requirements.txt? Create extra pack-level requirement.txt? Create pack setup.py which will take care of all requirements?

README is fine by me for alpha. Keep on capturing for post-alpha.


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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

authed!

(No suggestion)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Picked up from previous work :).

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)
6 changes: 6 additions & 0 deletions contrib/sandbox/packages/jira/actions/jira_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"rsa_cert_file": "/home/vagrant/jira.pem",
"oauth_token": "",
"oauth_secret": "",
"consumer_key": ""
}
33 changes: 33 additions & 0 deletions contrib/sandbox/packages/jira/actions/jira_create_issue.json
Original file line number Diff line number Diff line change
@@ -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"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@m4dcoder Do we recognize jsonschema type that this is a host name so it can be string or ip and the validation happens?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's whatever jsonschema supports. So i believe you can do a oneof multiple type string with different regex patterns.

},
"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"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@m4dcoder : How do I specify an enum?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
enum: ['fe', 'fi', 'fo', 'fum']
}

}
}
}
6 changes: 6 additions & 0 deletions contrib/sandbox/packages/jira/sensors/jira_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"rsa_cert_file": "/home/vagrant/jira.pem",
"oauth_token": "",
"oauth_secret": "",
"consumer_key": ""
}
93 changes: 93 additions & 0 deletions contrib/sandbox/packages/jira/sensors/jira_sensor.py
Original file line number Diff line number Diff line change
@@ -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'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another hardcoded configuration. Need a temp solution for alpha, and longer term solution for later.
Temp may be just keep it in the script and mention in README where to find and update them. Better ideas very welcome. Longer term will require a concept of pack that comprises both actions and sensors. We can discuss, capture, review after alpha.

# 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():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can also track removal of a project. Perhaps a new trigger or a delete property on the trigger. I prefer a new trigger to identify a deleted project.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just some random sensor so people can look at it and build their own. I can add a sensor that looks for deleted triggers.

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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@enykeev: Let's about this later tonight.