Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add client cert support and new action #39

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ jobs:
command: sudo apt -y install gmic optipng
- run:
name: Update exchange.stackstorm.org
command: ~/ci/.circle/deployment
command: ~/ci/.circle/deployment

workflows:
version: 2
Expand Down
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log

## 0.14.0

- Add `add_watcher` and `update_reporter` action
- Add `client_cert_file` and `client_key_file` for certificate based authentication with JIRA server

## 0.13.0

- Add ``validate`` option to pack config to enable validating credentials
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ to `/opt/stackstorm/configs/jira.yaml` and edit as required.
* ``verify`` - Verify SSL certificates. Default True. Set to False to disable verification
* ``auth_method`` - Specify either `basic` or `oauth` authentication

Include following settings for certificate based authentication with JIRA server

* ``client_cert_file`` Path to a client cert file, e.g. /home/jiracerts/username.cer
* ``client_key_file`` Path to a client key file, e.g. /home/jiracerts/username.key

Include the following settings when using the `oauth` auth_method:
* ``rsa_cert_file`` - Path to the file with a private key
* ``oauth_token`` - OAuth token
Expand Down Expand Up @@ -90,3 +95,14 @@ The sensor monitors for new tickets and sends a trigger into the system whenever

* ``create_issue`` - Action which creates a new JIRA issue.
* ``get_issue`` - Action which retrieves details for a particular issue.
* ``add_field_value`` - Add a field to a particular JIRA issue.
* ``add_watcher`` - Add a watcher on a JIRA issue.
* ``attach_file_to_issue`` - Attach a file to JIRA issue / ticket.
* ``attach_files_to_issue`` - Attach multiple files to JIRA issue / ticket.
* ``comment_issue`` - Comment on a JIRA issue / ticket.
* ``create_issue`` - Create a new JIRA issue / ticket.
* ``get_issue_attachments`` - Retrieve attachments for a particular JIRA issue.
* ``search_issues`` - Search JIRA issues with a JQL query
* ``transition_issue`` - Do a transition on a JIRA issue / ticket.
* ``update_field_value`` - Update a field in a particular JIRA issue.
* ``update_reporter`` - Update the reporter of a particular JIRA issue.
8 changes: 8 additions & 0 deletions actions/add_watcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from lib.base import BaseJiraAction

__all__ = ["JiraIssueAddWatcherAction"]


class JiraIssueAddWatcherAction(BaseJiraAction):
def run(self, issue_key, username):
return self._client.add_watcher(issue_key, username)
15 changes: 15 additions & 0 deletions actions/add_watcher.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
name: add_watcher
runner_type: python-script
description: Add a watcher on a JIRA issue / ticket.
enabled: true
entry_point: add_watcher.py
parameters:
username:
type: string
description: User to add as a watcher.
required: true
issue_key:
type: string
description: Issue key (e.g. PROJECT-1000).
required: true
25 changes: 17 additions & 8 deletions actions/lib/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from jira import JIRA

# from st2common.runners.base_action import Action
__all__ = [
'BaseJiraAction'
]
__all__ = ['BaseJiraAction']


class Action(object):
Expand All @@ -21,6 +19,12 @@ def _get_client(self):

options = {'server': config['url'], 'verify': config['verify']}

# Getting client cert configuration
cert_file_path = config['client_cert_file']
key_file_path = config['client_key_file']
if cert_file_path and key_file_path:
options['client_cert'] = (cert_file_path, key_file_path)

auth_method = config['auth_method']

if auth_method == 'oauth':
Expand All @@ -31,19 +35,24 @@ def _get_client(self):
'access_token': config['oauth_token'],
'access_token_secret': config['oauth_secret'],
'consumer_key': config['consumer_key'],
'key_cert': rsa_key_content
'key_cert': rsa_key_content,
}

client = JIRA(options=options, oauth=oauth_creds)

elif auth_method == 'basic':
basic_creds = (config['username'], config['password'])
client = JIRA(options=options, basic_auth=basic_creds,
validate=config.get('validate', False))
client = JIRA(
options=options,
basic_auth=basic_creds,
validate=config.get('validate', False),
)

else:
msg = ('You must set auth_method to either "oauth"',
'or "basic" your jira.yaml config file.')
msg = (
"You must set auth_method to either 'oauth'",
"or 'basic' your jira.yaml config file.",
)
raise Exception(msg)

return client
Expand Down
13 changes: 13 additions & 0 deletions actions/update_reporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from lib.base import BaseJiraAction

__all__ = [
'UpdateReporterValue'
]


Copy link
Contributor

Choose a reason for hiding this comment

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

Would like to see unit tests for these new actions.

Copy link
Author

Choose a reason for hiding this comment

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

I don't see unit tests for individual action that are already existing. could you please help me understanding, what are you expecting from those unit tests, since most of these actions are connecting to JIRA API and returning result ?

class UpdateReporterValue(BaseJiraAction):
def run(self, issue_key, username, notify):
issue = self._client.issue(issue_key)
issue.update(reporter={'name': username}, notify=notify)
result = issue.fields.labels
return result
20 changes: 20 additions & 0 deletions actions/update_reporter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: update_reporter
runner_type: python-script
description: Update the reporter of a particular JIRA issue.
enabled: true
entry_point: update_reporter.py
parameters:
issue_key:
type: string
description: Issue key (e.g. PROJECT-1000).
required: true
username:
type: string
description: the reporter name.
required: true
notify:
type: boolean
description: jira will send notifications (default is true)
default: true
required: false
10 changes: 10 additions & 0 deletions config.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,13 @@
type: boolean
default: false
required: false
client_cert_file:
description: "Path to a client cert file, e.g. /home/jiracerts/username.cer"
type: "string"
secret: false
required: false
client_key_file:
description: "Path to a client key file, e.g. /home/jiracerts/username.key"
type: "string"
secret: false
required: false
2 changes: 1 addition & 1 deletion pack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ keywords:
- issues
- ticket management
- project management
version: 0.13.0
version: 0.14.0
python_versions:
- "2"
- "3"
Expand Down
42 changes: 28 additions & 14 deletions sensors/jira_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ class JIRASensor(PollingSensor):
Sensor will monitor for any new projects created in JIRA and
emit trigger instance when one is created.
'''

def __init__(self, sensor_service, config=None, poll_interval=5):
super(JIRASensor, self).__init__(sensor_service=sensor_service,
config=config,
poll_interval=poll_interval)
super(JIRASensor, self).__init__(sensor_service=sensor_service,
config=config,
poll_interval=poll_interval)

self._jira_url = None
# The Consumer Key created while setting up the "Incoming Authentication" in
# The Consumer Key created while setting up the 'Incoming Authentication' in
# JIRA for the Application Link.
self._consumer_key = u''
self._rsa_key = None
Expand All @@ -41,29 +42,39 @@ def setup(self):
self._jira_url = self._config['url']
auth_method = self._config['auth_method']

options = {'server': self._config['url'],
Copy link
Contributor

Choose a reason for hiding this comment

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

Would like to see unit tests for these new options.

Copy link
Author

Choose a reason for hiding this comment

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

I have already added these configurations in test fixtures which is being used my tests to run the test.

'verify': self._config['verify']}
# Getting client cert configuration
cert_file_path = self._config['client_cert_file']
key_file_path = self._config['client_key_file']
if cert_file_path and key_file_path:
options['client_cert'] = (cert_file_path, key_file_path)

if auth_method == 'oauth':
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)
raise Exception(
'Cert file for JIRA OAuth not found at %s.' % rsa_cert_file
)
self._rsa_key = self._read_cert(rsa_cert_file)
self._poll_interval = self._config.get('poll_interval', self._poll_interval)
self._poll_interval = self._config.get(
Copy link
Contributor

Choose a reason for hiding this comment

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

More cosmetic changes

'poll_interval', self._poll_interval)
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
'key_cert': self._rsa_key,
}

self._jira_client = JIRA(options={'server': self._jira_url},
oauth=oauth_creds)
self._jira_client = JIRA(options=options, oauth=oauth_creds)
elif auth_method == 'basic':
basic_creds = (self._config['username'], self._config['password'])
self._jira_client = JIRA(options={'server': self._jira_url},
basic_auth=basic_creds)
self._jira_client = JIRA(options=options, basic_auth=basic_creds)

else:
msg = ('You must set auth_method to either "oauth"',
'or "basic" your jira.yaml config file.')
'or "basic" your jira.yaml config file.',
)
raise Exception(msg)

if self._projects_available is None:
Expand All @@ -74,7 +85,8 @@ def setup(self):
if not self._project or self._project not in self._projects_available:
raise Exception('Invalid project (%s) to track.' % self._project)
self._jql_query = 'project=%s' % self._project
all_issues = self._jira_client.search_issues(self._jql_query, maxResults=None)
all_issues = self._jira_client.search_issues(
self._jql_query, maxResults=None)
self._issues_in_project = {issue.key: issue for issue in all_issues}

def poll(self):
Expand All @@ -93,7 +105,9 @@ def remove_trigger(self, trigger):
pass

def _detect_new_issues(self):
new_issues = self._jira_client.search_issues(self._jql_query, maxResults=50, startAt=0)
new_issues = self._jira_client.search_issues(
self._jql_query, maxResults=50, startAt=0
)

for issue in new_issues:
if issue.key not in self._issues_in_project:
Expand Down
45 changes: 30 additions & 15 deletions sensors/jira_sensor_for_apiv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ class JIRASensorForAPIv2(PollingSensor):
Sensor will monitor for any new projects created in JIRA and
emit trigger instance when one is created.
'''

def __init__(self, sensor_service, config=None, poll_interval=5):
super(JIRASensorForAPIv2, self).__init__(sensor_service=sensor_service,
config=config,
poll_interval=poll_interval)
super(JIRASensorForAPIv2, self).__init__(
sensor_service=sensor_service, config=config, poll_interval=poll_interval
)

self._jira_url = None
# The Consumer Key created while setting up the "Incoming Authentication" in
# The Consumer Key created while setting up the 'Incoming Authentication' in
# JIRA for the Application Link.
self._consumer_key = u''
self._rsa_key = None
Expand All @@ -41,29 +42,40 @@ def setup(self):
self._jira_url = self._config['url']
auth_method = self._config['auth_method']

options = {'server': self._config['url'],
'verify': self._config['verify']}
# Getting client cert configuration
cert_file_path = self._config['client_cert_file']
key_file_path = self._config['client_key_file']
if cert_file_path and key_file_path:
options['client_cert'] = (cert_file_path, key_file_path)

if auth_method == 'oauth':
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)
raise Exception(
'Cert file for JIRA OAuth not found at %s.' % rsa_cert_file
)
self._rsa_key = self._read_cert(rsa_cert_file)
self._poll_interval = self._config.get('poll_interval', self._poll_interval)
self._poll_interval = self._config.get(
'poll_interval', self._poll_interval)
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
'key_cert': self._rsa_key,
}

self._jira_client = JIRA(options={'server': self._jira_url},
oauth=oauth_creds)
self._jira_client = JIRA(options=options, oauth=oauth_creds)
elif auth_method == 'basic':
basic_creds = (self._config['username'], self._config['password'])
self._jira_client = JIRA(options={'server': self._jira_url},
basic_auth=basic_creds)
self._jira_client = JIRA(options=options, basic_auth=basic_creds)

else:
msg = ('You must set auth_method to either "oauth"',
'or "basic" your jira.yaml config file.')
msg = (
'You must set auth_method to either "oauth"',
'or "basic" your jira.yaml config file.',
)
raise Exception(msg)

if self._projects_available is None:
Expand All @@ -74,7 +86,8 @@ def setup(self):
if not self._project or self._project not in self._projects_available:
raise Exception('Invalid project (%s) to track.' % self._project)
self._jql_query = 'project=%s' % self._project
all_issues = self._jira_client.search_issues(self._jql_query, maxResults=None)
all_issues = self._jira_client.search_issues(
self._jql_query, maxResults=None)
self._issues_in_project = {issue.key: issue for issue in all_issues}

def poll(self):
Expand All @@ -93,7 +106,9 @@ def remove_trigger(self, trigger):
pass

def _detect_new_issues(self):
new_issues = self._jira_client.search_issues(self._jql_query, maxResults=50, startAt=0)
new_issues = self._jira_client.search_issues(
self._jql_query, maxResults=50, startAt=0
)

for issue in new_issues:
if issue.key not in self._issues_in_project:
Expand Down
3 changes: 2 additions & 1 deletion tests/fixtures/full_auth_passwd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
poll_interval: 30
project: "MY_PROJECT"
verify: True

client_cert_file: ""
client_key_file: ""