From 75d9ea54e9364397c80dd31631eb3e8906aa5e5d Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Sat, 28 Dec 2019 12:35:59 +0530 Subject: [PATCH 01/10] Add client cert configuration to the pack Signed-off-by: Vivek Singh --- actions/lib/base.py | 45 +++++++++------ config.schema.yaml | 138 ++++++++++++++++++++++++-------------------- 2 files changed, 101 insertions(+), 82 deletions(-) diff --git a/actions/lib/base.py b/actions/lib/base.py index 9ca50e3..4aa9686 100755 --- a/actions/lib/base.py +++ b/actions/lib/base.py @@ -1,9 +1,7 @@ from jira import JIRA # from st2common.runners.base_action import Action -__all__ = [ - 'BaseJiraAction' -] +__all__ = ["BaseJiraAction"] class Action(object): @@ -19,37 +17,48 @@ def __init__(self, config): def _get_client(self): config = self.config - options = {'server': config['url'], 'verify': config['verify']} + options = {"server": config["url"], "verify": config["verify"]} - auth_method = config['auth_method'] + # Getting client cert configuration + cert_file_path = config["cert_file_path"] + key_file_path = config["key_file_path"] + if cert_file_path and key_file_path: + options["client_cert"] = (cert_file_path, key_file_path) - if auth_method == 'oauth': - rsa_cert_file = config['rsa_cert_file'] + auth_method = config["auth_method"] + + if auth_method == "oauth": + rsa_cert_file = config["rsa_cert_file"] rsa_key_content = self._get_file_content(file_path=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_content + "access_token": config["oauth_token"], + "access_token_secret": config["oauth_secret"], + "consumer_key": config["consumer_key"], + "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)) + elif auth_method == "basic": + basic_creds = (config["username"], config["password"]) + 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 def _get_file_content(self, file_path): - with open(file_path, 'r') as fp: + with open(file_path, "r") as fp: content = fp.read() return content diff --git a/config.schema.yaml b/config.schema.yaml index 4ad712f..56a13bd 100755 --- a/config.schema.yaml +++ b/config.schema.yaml @@ -1,65 +1,75 @@ --- - url: - description: "URL of the JIRA instance (e.g. ``https://myproject.atlassian.net``)" - type: "string" - secret: false - required: true - verify: - description: "Verify SSL certificate. Set to False to disable verification. Default True" - type: boolean - default: True - auth_method: - description: "Authentication method to use - oauth or basic" - type: "string" - secret: false - required: true - default: "oauth" - enum: - - oauth - - basic - username: - description: "Username if using the basic auth_method" - type: "string" - secret: false - required: false - password: - description: "Password if using the basic auth_method" - type: "string" - secret: true - required: false - rsa_cert_file: - description: "Path to a private key file, e.g. /home/vagrant/jira.pem" - type: "string" - secret: false - required: false - oauth_token: - description: "OAuth token" - type: "string" - secret: true - required: false - oauth_secret: - description: "OAuth secret" - type: "string" - secret: true - required: false - consumer_key: - description: "Consumer key" - type: "string" - secret: true - required: false - poll_interval: - description: "Polling interval - default 30s" - type: "integer" - secret: false - required: false - default: 30 - project: - description: "Project to be used as default for actions that don't require or allow a project" - type: "string" - secret: false - required: true - validate: - description: "If true it will validate your credentials first." - type: boolean - default: false - required: false +url: + description: "URL of the JIRA instance (e.g. ``https://myproject.atlassian.net``)" + type: "string" + secret: false + required: true +verify: + description: "Verify SSL certificate. Set to False to disable verification. Default True" + type: boolean + default: True +auth_method: + description: "Authentication method to use - oauth or basic" + type: "string" + secret: false + required: true + default: "oauth" + enum: + - oauth + - basic +username: + description: "Username if using the basic auth_method" + type: "string" + secret: false + required: false +password: + description: "Password if using the basic auth_method" + type: "string" + secret: true + required: false +rsa_cert_file: + description: "Path to a private key file, e.g. /home/vagrant/jira.pem" + type: "string" + secret: false + required: false +oauth_token: + description: "OAuth token" + type: "string" + secret: true + required: false +oauth_secret: + description: "OAuth secret" + type: "string" + secret: true + required: false +consumer_key: + description: "Consumer key" + type: "string" + secret: true + required: false +poll_interval: + description: "Polling interval - default 30s" + type: "integer" + secret: false + required: false + default: 30 +project: + description: "Project to be used as default for actions that don't require or allow a project" + type: "string" + secret: false + required: true +validate: + description: "If true it will validate your credentials first." + 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 From 49cb1c3a385fd67bf62350145c70aa5b6ea670be Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Sat, 28 Dec 2019 12:49:34 +0530 Subject: [PATCH 02/10] Update config variable name Signed-off-by: Vivek Singh --- actions/lib/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/lib/base.py b/actions/lib/base.py index 4aa9686..899d8ab 100755 --- a/actions/lib/base.py +++ b/actions/lib/base.py @@ -20,8 +20,8 @@ def _get_client(self): options = {"server": config["url"], "verify": config["verify"]} # Getting client cert configuration - cert_file_path = config["cert_file_path"] - key_file_path = config["key_file_path"] + 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) From 73514987a574d6f9acb9522f3ca6a200e75ab5c6 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 2 Jan 2020 15:47:30 +0530 Subject: [PATCH 03/10] Add cert conig for JIRA sensor --- sensors/jira_sensor.py | 96 ++++++++++++++++++-------------- sensors/jira_sensor_for_apiv2.py | 94 +++++++++++++++++-------------- 2 files changed, 107 insertions(+), 83 deletions(-) diff --git a/sensors/jira_sensor.py b/sensors/jira_sensor.py index c168d40..1119d81 100755 --- a/sensors/jira_sensor.py +++ b/sensors/jira_sensor.py @@ -7,73 +7,83 @@ 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 # JIRA for the Application Link. - self._consumer_key = u'' + self._consumer_key = u"" self._rsa_key = None self._jira_client = None - self._access_token = u'' - self._access_secret = u'' + self._access_token = u"" + self._access_secret = u"" self._projects_available = None self._poll_interval = 30 self._project = None self._issues_in_project = None self._jql_query = None - self._trigger_name = 'issues_tracker' - self._trigger_pack = 'jira' - self._trigger_ref = '.'.join([self._trigger_pack, self._trigger_name]) + self._trigger_name = "issues_tracker" + self._trigger_pack = "jira" + self._trigger_ref = ".".join([self._trigger_pack, self._trigger_name]) def _read_cert(self, file_path): with open(file_path) as f: return f.read() def setup(self): - self._jira_url = self._config['url'] - auth_method = self._config['auth_method'] - - if auth_method == 'oauth': - rsa_cert_file = self._config['rsa_cert_file'] + 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 + "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_url}, - 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, oauth=oauth_creds) + elif auth_method == "basic": + basic_creds = (self._config["username"], self._config["password"]) + 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: self._projects_available = set() for proj in self._jira_client.projects(): self._projects_available.add(proj.key) - self._project = self._config.get('project', None) + self._project = self._config.get("project", None) 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 + 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) self._issues_in_project = {issue.key: issue for issue in all_issues} @@ -93,7 +103,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: @@ -103,12 +115,12 @@ def _detect_new_issues(self): def _dispatch_issues_trigger(self, issue): trigger = self._trigger_ref payload = {} - payload['issue_name'] = issue.key - payload['issue_url'] = issue.self - payload['issue_browse_url'] = self._jira_url + '/browse/' + issue.key - payload['project'] = self._project - payload['created'] = issue.raw['fields']['created'] - payload['assignee'] = issue.raw['fields']['assignee'] - payload['fix_versions'] = issue.raw['fields']['fixVersions'] - payload['issue_type'] = issue.raw['fields']['issuetype']['name'] + payload["issue_name"] = issue.key + payload["issue_url"] = issue.self + payload["issue_browse_url"] = self._jira_url + "/browse/" + issue.key + payload["project"] = self._project + payload["created"] = issue.raw["fields"]["created"] + payload["assignee"] = issue.raw["fields"]["assignee"] + payload["fix_versions"] = issue.raw["fields"]["fixVersions"] + payload["issue_type"] = issue.raw["fields"]["issuetype"]["name"] self._sensor_service.dispatch(trigger, payload) diff --git a/sensors/jira_sensor_for_apiv2.py b/sensors/jira_sensor_for_apiv2.py index c676225..e897fbb 100755 --- a/sensors/jira_sensor_for_apiv2.py +++ b/sensors/jira_sensor_for_apiv2.py @@ -7,73 +7,83 @@ 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 # JIRA for the Application Link. - self._consumer_key = u'' + self._consumer_key = u"" self._rsa_key = None self._jira_client = None - self._access_token = u'' - self._access_secret = u'' + self._access_token = u"" + self._access_secret = u"" self._projects_available = None self._poll_interval = 30 self._project = None self._issues_in_project = None self._jql_query = None - self._trigger_name = 'issues_tracker_for_apiv2' - self._trigger_pack = 'jira' - self._trigger_ref = '.'.join([self._trigger_pack, self._trigger_name]) + self._trigger_name = "issues_tracker_for_apiv2" + self._trigger_pack = "jira" + self._trigger_ref = ".".join([self._trigger_pack, self._trigger_name]) def _read_cert(self, file_path): with open(file_path) as f: return f.read() def setup(self): - self._jira_url = self._config['url'] - auth_method = self._config['auth_method'] - - if auth_method == 'oauth': - rsa_cert_file = self._config['rsa_cert_file'] + 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 + "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_url}, - 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, oauth=oauth_creds) + elif auth_method == "basic": + basic_creds = (self._config["username"], self._config["password"]) + 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: self._projects_available = set() for proj in self._jira_client.projects(): self._projects_available.add(proj.key) - self._project = self._config.get('project', None) + self._project = self._config.get("project", None) 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 + 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) self._issues_in_project = {issue.key: issue for issue in all_issues} @@ -93,7 +103,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: @@ -103,11 +115,11 @@ def _detect_new_issues(self): def _dispatch_issues_trigger(self, issue): trigger = self._trigger_ref payload = {} - payload['project'] = self._project - payload['id'] = issue.id - payload['expand'] = issue.raw.get('expand', '') - payload['issue_key'] = issue.key - payload['issue_url'] = issue.self - payload['issue_browse_url'] = self._jira_url + '/browse/' + issue.key - payload['fields'] = issue.raw.get('fields', {}) + payload["project"] = self._project + payload["id"] = issue.id + payload["expand"] = issue.raw.get("expand", "") + payload["issue_key"] = issue.key + payload["issue_url"] = issue.self + payload["issue_browse_url"] = self._jira_url + "/browse/" + issue.key + payload["fields"] = issue.raw.get("fields", {}) self._sensor_service.dispatch(trigger, payload) From e39c968b50f6391e7e4599befc8b31115f132ecc Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 2 Jan 2020 15:59:45 +0530 Subject: [PATCH 04/10] Disable get cert server info call --- sensors/jira_sensor.py | 8 ++++++-- sensors/jira_sensor_for_apiv2.py | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/sensors/jira_sensor.py b/sensors/jira_sensor.py index 1119d81..e3ec90a 100755 --- a/sensors/jira_sensor.py +++ b/sensors/jira_sensor.py @@ -64,10 +64,14 @@ def setup(self): "key_cert": self._rsa_key, } - self._jira_client = JIRA(options=options, oauth=oauth_creds) + self._jira_client = JIRA( + options=options, oauth=oauth_creds, get_server_info=False + ) elif auth_method == "basic": basic_creds = (self._config["username"], self._config["password"]) - self._jira_client = JIRA(options=options, basic_auth=basic_creds) + self._jira_client = JIRA( + options=options, basic_auth=basic_creds, get_server_info=False + ) else: msg = ( diff --git a/sensors/jira_sensor_for_apiv2.py b/sensors/jira_sensor_for_apiv2.py index e897fbb..812b044 100755 --- a/sensors/jira_sensor_for_apiv2.py +++ b/sensors/jira_sensor_for_apiv2.py @@ -64,10 +64,14 @@ def setup(self): "key_cert": self._rsa_key, } - self._jira_client = JIRA(options=options, oauth=oauth_creds) + self._jira_client = JIRA( + options=options, oauth=oauth_creds, get_server_info=False + ) elif auth_method == "basic": basic_creds = (self._config["username"], self._config["password"]) - self._jira_client = JIRA(options=options, basic_auth=basic_creds) + self._jira_client = JIRA( + options=options, basic_auth=basic_creds, get_server_info=False + ) else: msg = ( From 6059581b9a64294aa0c9cb2cd6b522ece100db67 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 2 Jan 2020 17:41:08 +0530 Subject: [PATCH 05/10] Update trigger payload structure --- sensors/jira_sensor.py | 8 ++---- sensors/jira_sensor.yaml | 49 ++++++++++++++++---------------- sensors/jira_sensor_for_apiv2.py | 8 ++---- 3 files changed, 28 insertions(+), 37 deletions(-) diff --git a/sensors/jira_sensor.py b/sensors/jira_sensor.py index e3ec90a..1119d81 100755 --- a/sensors/jira_sensor.py +++ b/sensors/jira_sensor.py @@ -64,14 +64,10 @@ def setup(self): "key_cert": self._rsa_key, } - self._jira_client = JIRA( - options=options, oauth=oauth_creds, get_server_info=False - ) + 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=options, basic_auth=basic_creds, get_server_info=False - ) + self._jira_client = JIRA(options=options, basic_auth=basic_creds) else: msg = ( diff --git a/sensors/jira_sensor.yaml b/sensors/jira_sensor.yaml index 8d06e46..ee73173 100755 --- a/sensors/jira_sensor.yaml +++ b/sensors/jira_sensor.yaml @@ -1,26 +1,25 @@ --- - class_name: "JIRASensor" - entry_point: "jira_sensor.py" - description: "Sensor which monitors JIRA for new tickets" - poll_interval: 30 - trigger_types: - - - name: "issues_tracker" - description: "Trigger which indicates that a new issue has been created" - payload_schema: - type: "object" - properties: - project: - type: "string" - issue_name: - type: "string" - issue_url: - type: "string" - created: - type: "string" - assignee: - type: "string" - fix_versions: - type: "string" - issue_type: - type: "string" +class_name: "JIRASensor" +entry_point: "jira_sensor.py" +description: "Sensor which monitors JIRA for new tickets" +poll_interval: 30 +trigger_types: + - name: "issues_tracker" + description: "Trigger which indicates that a new issue has been created" + payload_schema: + type: "object" + properties: + project: + type: "string" + issue_name: + type: "string" + issue_url: + type: "string" + created: + type: "string" + assignee: + type: "string" + fix_versions: + type: "list" + issue_type: + type: "string" diff --git a/sensors/jira_sensor_for_apiv2.py b/sensors/jira_sensor_for_apiv2.py index 812b044..e897fbb 100755 --- a/sensors/jira_sensor_for_apiv2.py +++ b/sensors/jira_sensor_for_apiv2.py @@ -64,14 +64,10 @@ def setup(self): "key_cert": self._rsa_key, } - self._jira_client = JIRA( - options=options, oauth=oauth_creds, get_server_info=False - ) + 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=options, basic_auth=basic_creds, get_server_info=False - ) + self._jira_client = JIRA(options=options, basic_auth=basic_creds) else: msg = ( From 17e3cc4906f863c0c089714df5b6b96b852f2af3 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Mon, 20 Jan 2020 23:12:10 +0530 Subject: [PATCH 06/10] Add add watcher to issue command Signed-off-by: Vivek Singh --- actions/add_watcher.py | 9 +++++++++ actions/add_watcher.yaml | 15 +++++++++++++++ 2 files changed, 24 insertions(+) create mode 100755 actions/add_watcher.py create mode 100755 actions/add_watcher.yaml diff --git a/actions/add_watcher.py b/actions/add_watcher.py new file mode 100755 index 0000000..f5bd3f0 --- /dev/null +++ b/actions/add_watcher.py @@ -0,0 +1,9 @@ +from lib.base import BaseJiraAction + +__all__ = ["JiraIssueAddWatcherAction"] + + +class JiraIssueAddWatcherAction(BaseJiraAction): + def run(self, issue_key, username): + result = self._client.add_watcher(issue_key, username) + return result diff --git a/actions/add_watcher.yaml b/actions/add_watcher.yaml new file mode 100755 index 0000000..74a0ff8 --- /dev/null +++ b/actions/add_watcher.yaml @@ -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 From cbb9cf362eb49570f1967f03ee396a89e7f9e88b Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Mon, 20 Jan 2020 23:47:31 +0530 Subject: [PATCH 07/10] Replace double quotes with single quotes Signed-off-by: Vivek Singh --- .circleci/config.yml | 2 +- actions/lib/base.py | 36 +++++++------- sensors/jira_sensor.py | 84 ++++++++++++++++---------------- sensors/jira_sensor_for_apiv2.py | 75 ++++++++++++++-------------- 4 files changed, 101 insertions(+), 96 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 528cfa0..1944d83 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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 diff --git a/actions/lib/base.py b/actions/lib/base.py index 899d8ab..15dbe8c 100755 --- a/actions/lib/base.py +++ b/actions/lib/base.py @@ -1,7 +1,7 @@ from jira import JIRA # from st2common.runners.base_action import Action -__all__ = ["BaseJiraAction"] +__all__ = ['BaseJiraAction'] class Action(object): @@ -17,48 +17,48 @@ def __init__(self, config): def _get_client(self): config = self.config - options = {"server": config["url"], "verify": config["verify"]} + 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"] + 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) + options['client_cert'] = (cert_file_path, key_file_path) - auth_method = config["auth_method"] + auth_method = config['auth_method'] - if auth_method == "oauth": - rsa_cert_file = config["rsa_cert_file"] + if auth_method == 'oauth': + rsa_cert_file = config['rsa_cert_file'] rsa_key_content = self._get_file_content(file_path=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_content, + 'access_token': config['oauth_token'], + 'access_token_secret': config['oauth_secret'], + 'consumer_key': config['consumer_key'], + 'key_cert': rsa_key_content, } client = JIRA(options=options, oauth=oauth_creds) - elif auth_method == "basic": - basic_creds = (config["username"], config["password"]) + elif auth_method == 'basic': + basic_creds = (config['username'], config['password']) client = JIRA( options=options, basic_auth=basic_creds, - validate=config.get("validate", False), + validate=config.get('validate', False), ) else: msg = ( - 'You must set auth_method to either "oauth"', - 'or "basic" your jira.yaml config file.', + "You must set auth_method to either 'oauth'", + "or 'basic' your jira.yaml config file.", ) raise Exception(msg) return client def _get_file_content(self, file_path): - with open(file_path, "r") as fp: + with open(file_path, 'r') as fp: content = fp.read() return content diff --git a/sensors/jira_sensor.py b/sensors/jira_sensor.py index 1119d81..8220a87 100755 --- a/sensors/jira_sensor.py +++ b/sensors/jira_sensor.py @@ -7,10 +7,10 @@ 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__( @@ -18,73 +18,75 @@ def __init__(self, sensor_service, config=None, poll_interval=5): ) 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._consumer_key = u'' self._rsa_key = None self._jira_client = None - self._access_token = u"" - self._access_secret = u"" + self._access_token = u'' + self._access_secret = u'' self._projects_available = None self._poll_interval = 30 self._project = None self._issues_in_project = None self._jql_query = None - self._trigger_name = "issues_tracker" - self._trigger_pack = "jira" - self._trigger_ref = ".".join([self._trigger_pack, self._trigger_name]) + self._trigger_name = 'issues_tracker' + self._trigger_pack = 'jira' + self._trigger_ref = '.'.join([self._trigger_pack, self._trigger_name]) def _read_cert(self, file_path): with open(file_path) as f: return f.read() def setup(self): - self._jira_url = self._config["url"] - auth_method = self._config["auth_method"] + self._jira_url = self._config['url'] + auth_method = self._config['auth_method'] - options = {"server": self._config["url"], "verify": self._config["verify"]} + 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"] + 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) + options['client_cert'] = (cert_file_path, key_file_path) - if auth_method == "oauth": - rsa_cert_file = self._config["rsa_cert_file"] + 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 + '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, + '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=options, oauth=oauth_creds) - elif auth_method == "basic": - basic_creds = (self._config["username"], self._config["password"]) + elif auth_method == 'basic': + basic_creds = (self._config['username'], self._config['password']) 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: self._projects_available = set() for proj in self._jira_client.projects(): self._projects_available.add(proj.key) - self._project = self._config.get("project", None) + self._project = self._config.get('project', None) 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) + 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) self._issues_in_project = {issue.key: issue for issue in all_issues} def poll(self): @@ -115,12 +117,12 @@ def _detect_new_issues(self): def _dispatch_issues_trigger(self, issue): trigger = self._trigger_ref payload = {} - payload["issue_name"] = issue.key - payload["issue_url"] = issue.self - payload["issue_browse_url"] = self._jira_url + "/browse/" + issue.key - payload["project"] = self._project - payload["created"] = issue.raw["fields"]["created"] - payload["assignee"] = issue.raw["fields"]["assignee"] - payload["fix_versions"] = issue.raw["fields"]["fixVersions"] - payload["issue_type"] = issue.raw["fields"]["issuetype"]["name"] + payload['issue_name'] = issue.key + payload['issue_url'] = issue.self + payload['issue_browse_url'] = self._jira_url + '/browse/' + issue.key + payload['project'] = self._project + payload['created'] = issue.raw['fields']['created'] + payload['assignee'] = issue.raw['fields']['assignee'] + payload['fix_versions'] = issue.raw['fields']['fixVersions'] + payload['issue_type'] = issue.raw['fields']['issuetype']['name'] self._sensor_service.dispatch(trigger, payload) diff --git a/sensors/jira_sensor_for_apiv2.py b/sensors/jira_sensor_for_apiv2.py index e897fbb..cca38e7 100755 --- a/sensors/jira_sensor_for_apiv2.py +++ b/sensors/jira_sensor_for_apiv2.py @@ -7,10 +7,10 @@ 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__( @@ -18,55 +18,57 @@ def __init__(self, sensor_service, config=None, poll_interval=5): ) 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._consumer_key = u'' self._rsa_key = None self._jira_client = None - self._access_token = u"" - self._access_secret = u"" + self._access_token = u'' + self._access_secret = u'' self._projects_available = None self._poll_interval = 30 self._project = None self._issues_in_project = None self._jql_query = None - self._trigger_name = "issues_tracker_for_apiv2" - self._trigger_pack = "jira" - self._trigger_ref = ".".join([self._trigger_pack, self._trigger_name]) + self._trigger_name = 'issues_tracker_for_apiv2' + self._trigger_pack = 'jira' + self._trigger_ref = '.'.join([self._trigger_pack, self._trigger_name]) def _read_cert(self, file_path): with open(file_path) as f: return f.read() def setup(self): - self._jira_url = self._config["url"] - auth_method = self._config["auth_method"] + self._jira_url = self._config['url'] + auth_method = self._config['auth_method'] - options = {"server": self._config["url"], "verify": self._config["verify"]} + 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"] + 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) + options['client_cert'] = (cert_file_path, key_file_path) - if auth_method == "oauth": - rsa_cert_file = self._config["rsa_cert_file"] + 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 + '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, + '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=options, oauth=oauth_creds) - elif auth_method == "basic": - basic_creds = (self._config["username"], self._config["password"]) + elif auth_method == 'basic': + basic_creds = (self._config['username'], self._config['password']) self._jira_client = JIRA(options=options, basic_auth=basic_creds) else: @@ -80,11 +82,12 @@ def setup(self): self._projects_available = set() for proj in self._jira_client.projects(): self._projects_available.add(proj.key) - self._project = self._config.get("project", None) + self._project = self._config.get('project', None) 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) + 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) self._issues_in_project = {issue.key: issue for issue in all_issues} def poll(self): @@ -115,11 +118,11 @@ def _detect_new_issues(self): def _dispatch_issues_trigger(self, issue): trigger = self._trigger_ref payload = {} - payload["project"] = self._project - payload["id"] = issue.id - payload["expand"] = issue.raw.get("expand", "") - payload["issue_key"] = issue.key - payload["issue_url"] = issue.self - payload["issue_browse_url"] = self._jira_url + "/browse/" + issue.key - payload["fields"] = issue.raw.get("fields", {}) + payload['project'] = self._project + payload['id'] = issue.id + payload['expand'] = issue.raw.get('expand', '') + payload['issue_key'] = issue.key + payload['issue_url'] = issue.self + payload['issue_browse_url'] = self._jira_url + '/browse/' + issue.key + payload['fields'] = issue.raw.get('fields', {}) self._sensor_service.dispatch(trigger, payload) From 1f84b0035db5c3ab428bbf876ae1e758ee6d74dc Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Mon, 20 Jan 2020 23:56:42 +0530 Subject: [PATCH 08/10] Add client cert and key to fixture Signed-off-by: Vivek Singh --- tests/fixtures/full_auth_passwd.yaml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/fixtures/full_auth_passwd.yaml b/tests/fixtures/full_auth_passwd.yaml index d387c7a..6cb7204 100644 --- a/tests/fixtures/full_auth_passwd.yaml +++ b/tests/fixtures/full_auth_passwd.yaml @@ -1,9 +1,10 @@ --- - url: "https://company.atlassian.net" - auth_method: "basic" - username: "user" - password: "passwd" - poll_interval: 30 - project: "MY_PROJECT" - verify: True - +url: "https://company.atlassian.net" +auth_method: "basic" +username: "user" +password: "passwd" +poll_interval: 30 +project: "MY_PROJECT" +verify: True +client_cert_file: "" +client_key_file: "" From ccba2ec26e04308306ea248b6c02f9db0e82024c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Georges=20M=C3=A9nie?= Date: Wed, 22 Jan 2020 17:39:05 +0100 Subject: [PATCH 09/10] added update_reporter action --- actions/update_reporter.py | 13 +++++++++++++ actions/update_reporter.yaml | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 actions/update_reporter.py create mode 100644 actions/update_reporter.yaml diff --git a/actions/update_reporter.py b/actions/update_reporter.py new file mode 100644 index 0000000..5f23b10 --- /dev/null +++ b/actions/update_reporter.py @@ -0,0 +1,13 @@ +from lib.base import BaseJiraAction + +__all__ = [ + 'UpdateReporterValue' +] + + +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 diff --git a/actions/update_reporter.yaml b/actions/update_reporter.yaml new file mode 100644 index 0000000..cd0f668 --- /dev/null +++ b/actions/update_reporter.yaml @@ -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 From d08bc9b78bb5a5cce1c6e84c1f947f1ba3088d26 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Mon, 20 Jul 2020 12:38:00 +0530 Subject: [PATCH 10/10] Update changelog and readme Signed-off-by: Vivek Singh --- CHANGES.md | 5 +++++ README.md | 16 ++++++++++++++++ pack.yaml | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 30d97f7..735babc 100755 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/README.md b/README.md index 41c4fd0..f504a17 100755 --- a/README.md +++ b/README.md @@ -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 @@ -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. diff --git a/pack.yaml b/pack.yaml index 9c36d76..f157124 100755 --- a/pack.yaml +++ b/pack.yaml @@ -6,7 +6,7 @@ keywords: - issues - ticket management - project management -version: 0.13.0 +version: 0.14.0 python_versions: - "2" - "3"