From 47c049aa58632c4f9a18ba2babfa444d6b3a66e8 Mon Sep 17 00:00:00 2001 From: Sebastian Bauersfeld Date: Wed, 28 Apr 2021 14:35:19 +0200 Subject: [PATCH 1/3] Fetch the right field for the alert description. --- server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.py b/server.py index 1aab01b..d3c3013 100644 --- a/server.py +++ b/server.py @@ -107,7 +107,7 @@ def github_webhook(): alert_url = alert.get("html_url") alert_num = alert.get("number") rule_id = alert.get("rule").get("id") - rule_desc = alert.get("rule").get("id") + rule_desc = alert.get("rule").get("description") # TODO: We might want to do the following asynchronously, as it could # take time to do a full sync on a repo with many alerts / issues From 1e12ce2b4e13d99a196e56eb3b85761e0da56e49 Mon Sep 17 00:00:00 2001 From: Sebastian Bauersfeld Date: Mon, 10 May 2021 16:55:20 +0700 Subject: [PATCH 2/3] Also synchronize secret scanning alerts --- ghlib.py | 148 ++++++++++++++++++++++++++++++++++++++++++++--------- jiralib.py | 54 +++++++++++-------- server.py | 2 +- sync.py | 76 ++++++++++++++++----------- util.py | 19 ++++--- 5 files changed, 211 insertions(+), 88 deletions(-) diff --git a/ghlib.py b/ghlib.py index 0b2edff..65dc315 100644 --- a/ghlib.py +++ b/ghlib.py @@ -153,7 +153,11 @@ class GHRepository: ) - def get_alerts(self, state = None): + def get_key(self): + return util.make_key(self.repo_id) + + + def alerts_helper(self, api_segment, state=None): if state: state = '&state=' + state else: @@ -161,9 +165,10 @@ class GHRepository: try: resp = requests.get( - '{api_url}/repos/{repo_id}/code-scanning/alerts?per_page={results_per_page}{state}'.format( + '{api_url}/repos/{repo_id}/{api_segment}/alerts?per_page={results_per_page}{state}'.format( api_url=self.gh.url, repo_id=self.repo_id, + api_segment=api_segment, state=state, results_per_page=RESULTS_PER_PAGE ), @@ -175,7 +180,7 @@ class GHRepository: resp.raise_for_status() for a in resp.json(): - yield GHAlert(self, a) + yield a nextpage = resp.links.get('next', {}).get('url', None) if not nextpage: @@ -197,6 +202,37 @@ class GHRepository: raise + def get_info(self): + resp = requests.get( + '{api_url}/repos/{repo_id}'.format( + api_url=self.gh.url, + repo_id=self.repo_id + ), + headers=self.gh.default_headers(), + timeout=util.REQUEST_TIMEOUT + ) + resp.raise_for_status() + return resp.json() + + + def isprivate(self): + return self.get_info()['private'] + + + def get_alerts(self, state=None): + for a in self.alerts_helper('code-scanning', state): + yield Alert(self, a) + + + def get_secrets(self, state=None): + # secret scanning alerts are only accessible on private repositories, so + # we return an empty list on public ones + if not self.isprivate(): + return + for a in self.alerts_helper('secret-scanning', state): + yield Secret(self, a) + + def get_alert(self, alert_num): resp = requests.get( '{api_url}/repos/{repo_id}/code-scanning/alerts/{alert_num}'.format( @@ -209,7 +245,7 @@ class GHRepository: ) try: resp.raise_for_status() - return GHAlert(self, resp.json()) + return Alert(self, resp.json()) except HTTPError as httpe: if httpe.response.status_code == 404: # A 404 suggests that the alert doesn't exist @@ -219,48 +255,76 @@ class GHRepository: raise -class GHAlert: +class AlertBase: def __init__(self, github_repo, json): self.github_repo = github_repo self.gh = github_repo.gh self.json = json + def get_state(self): + return self.json['state'] == 'open' + + def get_type(self): + return type(self).__name__ def number(self): return int(self.json['number']) + def short_desc(self): + raise NotImplementedError - def get_state(self): - return parse_alert_state(self.json['state']) + def long_desc(self): + raise NotImplementedError + def hyperlink(self): + return self.json['html_url'] - def adjust_state(self, state): - if state: - self.update('open') - else: - self.update('dismissed') + def can_transition(self): + return True + def get_key(self): + raise NotImplementedError - def is_fixed(self): - return self.json['state'] == 'fixed' - - - def update(self, alert_state): - if self.json['state'] == alert_state: + def adjust_state(self, target_state): + if self.get_state() == target_state: return - action = 'Reopening' if parse_alert_state(alert_state) else 'Closing' logger.info( - '{action} alert {alert_num} of repository "{repo_id}".'.format( - action=action, + '{action} {atype} {alert_num} of repository "{repo_id}".'.format( + atype=self.get_type(), + action='Reopening' if target_state else 'Closing', alert_num=self.number(), repo_id=self.github_repo.repo_id ) ) + self.do_adjust_state(target_state) + + +class Alert(AlertBase): + def __init__(self, github_repo, json): + AlertBase.__init__(self, github_repo, json) + + def can_transition(self): + return self.json['state'] != 'fixed' + + def long_desc(self): + return self.json['rule']['description'] + + def short_desc(self): + return self.json['rule']['id'] + + def get_key(self): + return util.make_key( + self.github_repo.repo_id + '/' + str(self.number()) + ) + + def do_adjust_state(self, target_state): + state = 'open' reason = '' - if alert_state == 'dismissed': + if not target_state: + state = 'dismissed' reason = ', "dismissed_reason": "won\'t fix"' - data = '{{"state": "{state}"{reason}}}'.format(state=alert_state, reason=reason) + data = '{{"state": "{state}"{reason}}}'.format(state=state, reason=reason) resp = requests.patch( '{api_url}/repos/{repo_id}/code-scanning/alerts/{alert_num}'.format( api_url=self.gh.url, @@ -274,5 +338,39 @@ class GHAlert: resp.raise_for_status() -def parse_alert_state(state_string): - return state_string not in ['dismissed', 'fixed'] +class Secret(AlertBase): + def __init__(self, github_repo, json): + AlertBase.__init__(self, github_repo, json) + + def can_transition(self): + return True + + def long_desc(self): + return self.json['secret_type'] + + def short_desc(self): + return self.long_desc() + + def get_key(self): + return util.make_key( + self.github_repo.repo_id + '/' + self.get_type() + '/' + str(self.number()) + ) + + def do_adjust_state(self, target_state): + state = 'open' + resolution = '' + if not target_state: + state = 'resolved' + resolution = ', "resolution": "wont_fix"' + data = '{{"state": "{state}"{resolution}}}'.format(state=state, resolution=resolution) + resp = requests.patch( + '{api_url}/repos/{repo_id}/secret-scanning/alerts/{alert_num}'.format( + api_url=self.gh.url, + repo_id=self.github_repo.repo_id, + alert_num=self.number() + ), + data=data, + headers=self.gh.default_headers(), + timeout=util.REQUEST_TIMEOUT + ) + resp.raise_for_status() diff --git a/jiralib.py b/jiralib.py index 372b30c..143bfb2 100644 --- a/jiralib.py +++ b/jiralib.py @@ -15,10 +15,13 @@ UPDATE_EVENT = 'jira:issue_updated' CREATE_EVENT = 'jira:issue_created' DELETE_EVENT = 'jira:issue_deleted' -TITLE_PREFIX = '[Code Scanning Alert]:' +TITLE_PREFIXES = { + 'Alert': '[Code Scanning Alert]:', + 'Secret': '[Secret Scanning Alert]:' +} DESC_TEMPLATE=""" -{rule_desc} +{long_desc} {alert_url} @@ -26,6 +29,7 @@ DESC_TEMPLATE=""" This issue was automatically generated from a GitHub alert, and will be automatically resolved once the underlying problem is fixed. DO NOT MODIFY DESCRIPTION BELOW LINE. REPOSITORY_NAME={repo_id} +ALERT_TYPE={alert_type} ALERT_NUMBER={alert_num} REPOSITORY_KEY={repo_key} ALERT_KEY={alert_key} @@ -190,26 +194,38 @@ class JiraProject: ) - def create_issue(self, repo_id, rule_id, rule_desc, alert_url, alert_num): + def create_issue( + self, + repo_id, + short_desc, + long_desc, + alert_url, + alert_type, + alert_num, + repo_key, + alert_key + ): raw = self.j.create_issue( project=self.projectkey, - summary='{prefix} {rule} in {repo}'.format( - prefix=TITLE_PREFIX, - rule=rule_id, + summary='{prefix} {short_desc} in {repo}'.format( + prefix=TITLE_PREFIXES[alert_type], + short_desc=short_desc, repo=repo_id ), description=DESC_TEMPLATE.format( - rule_desc=rule_desc, + long_desc=long_desc, alert_url=alert_url, repo_id=repo_id, + alert_type=alert_type, alert_num=alert_num, - repo_key=util.make_key(repo_id), - alert_key=util.make_alert_key(repo_id, alert_num) + repo_key=repo_key, + alert_key=alert_key ), issuetype={'name': 'Bug'} ) - logger.info('Created issue {issue_key} for alert {alert_num} in {repo_id}.'.format( + logger.info('Created issue {issue_key} for {alert_type} {alert_num} in {repo_id}.'.format( issue_key=raw.key, + alert_type=alert_type, alert_num=alert_num, repo_id=repo_id )) @@ -217,11 +233,7 @@ class JiraProject: return JiraIssue(self, raw) - def fetch_issues(self, repo_id, alert_num=None): - if alert_num is None: - key = util.make_key(repo_id) - else: - key = util.make_alert_key(repo_id, alert_num) + def fetch_issues(self, key): issue_search = 'project={jira_project} and description ~ "{key}"'.format( jira_project='\"{}\"'.format(self.projectkey), key=key @@ -314,6 +326,11 @@ def parse_alert_info(desc): if m is None: return failed repo_id = m.group(1) + m = re.search('ALERT_TYPE=(.*)$', desc, re.MULTILINE) + if m is None: + alert_type = None + else: + alert_type = m.group(1) m = re.search('ALERT_NUMBER=(.*)$', desc, re.MULTILINE) if m is None: return failed @@ -327,12 +344,7 @@ def parse_alert_info(desc): return failed alert_key = m.group(1) - # consistency checks: - if repo_key != util.make_key(repo_id) \ - or alert_key != util.make_alert_key(repo_id, alert_num): - return failed - - return repo_id, alert_num, repo_key, alert_key + return repo_id, alert_num, repo_key, alert_key, alert_type def parse_state(raw_state): diff --git a/server.py b/server.py index d3c3013..ea1f079 100644 --- a/server.py +++ b/server.py @@ -49,7 +49,7 @@ def jira_webhook(): payload = json.loads(request.data.decode('utf-8')) event = payload['webhookEvent'] desc = payload['issue']['fields']['description'] - repo_id, alert_id, _, _ = jiralib.parse_alert_info(desc) + repo_id, _, _, _, _ = jiralib.parse_alert_info(desc) app.logger.debug('Received JIRA webhook for event "{event}"'.format(event=event)) diff --git a/sync.py b/sync.py index 08567ec..754ffc6 100644 --- a/sync.py +++ b/sync.py @@ -1,6 +1,7 @@ import jiralib import ghlib import logging +import itertools logger = logging.getLogger(__name__) @@ -22,52 +23,58 @@ class Sync: def alert_created(self, repo_id, alert_num): + a = self.github.getRepository(repo_id).get_alert(alert_num) self.sync( - self.github.getRepository(repo_id).get_alert(alert_num), - self.jira.fetch_issues(repo_id, alert_num), + a, + self.jira.fetch_issues(a.get_key()), DIRECTION_G2J ) def alert_changed(self, repo_id, alert_num): + a = self.github.getRepository(repo_id).get_alert(alert_num) self.sync( - self.github.getRepository(repo_id).get_alert(alert_num), - self.jira.fetch_issues(repo_id, alert_num), + a, + self.jira.fetch_issues(a.get_key()), DIRECTION_G2J ) def alert_fixed(self, repo_id, alert_num): + a = self.github.getRepository(repo_id).get_alert(alert_num) self.sync( - self.github.getRepository(repo_id).get_alert(alert_num), - self.jira.fetch_issues(repo_id, alert_num), + a, + self.jira.fetch_issues(a.get_key()), DIRECTION_G2J ) def issue_created(self, desc): - repo_id, alert_num, _, _ = jiralib.parse_alert_info(desc) + repo_id, alert_num, _, _, _ = jiralib.parse_alert_info(desc) + a = self.github.getRepository(repo_id).get_alert(alert_num) self.sync( - self.github.getRepository(repo_id).get_alert(alert_num), - self.jira.fetch_issues(repo_id, alert_num), + a, + self.jira.fetch_issues(a.get_key()), DIRECTION_J2G ) def issue_changed(self, desc): - repo_id, alert_num, _, _ = jiralib.parse_alert_info(desc) + repo_id, alert_num, _, _, _ = jiralib.parse_alert_info(desc) + a = self.github.getRepository(repo_id).get_alert(alert_num) self.sync( - self.github.getRepository(repo_id).get_alert(alert_num), - self.jira.fetch_issues(repo_id, alert_num), + a, + self.jira.fetch_issues(a.get_key()), DIRECTION_J2G ) def issue_deleted(self, desc): - repo_id, alert_num, _, _ = jiralib.parse_alert_info(desc) + repo_id, alert_num, _, _, _ = jiralib.parse_alert_info(desc) + a = self.github.getRepository(repo_id).get_alert(alert_num) self.sync( - self.github.getRepository(repo_id).get_alert(alert_num), - self.jira.fetch_issues(repo_id, alert_num), + a, + self.jira.fetch_issues(a.get_key()), DIRECTION_J2G ) @@ -85,10 +92,13 @@ class Sync: if len(issues) == 0: newissue = self.jira.create_issue( alert.github_repo.repo_id, - alert.json['rule']['id'], - alert.json['rule']['description'], - alert.json['html_url'], - alert.number() + alert.short_desc(), + alert.long_desc(), + alert.hyperlink(), + alert.get_type(), + alert.number(), + alert.github_repo.get_key(), + alert.get_key() ) newissue.adjust_state(alert.get_state()) return alert.get_state() @@ -108,7 +118,7 @@ class Sync: else: d = self.direction - if d & DIRECTION_G2J or alert.is_fixed(): + if d & DIRECTION_G2J or not alert.can_transition(): # The user treats GitHub as the source of truth. # Also, if the alert to be synchronized is already "fixed" # then even if the user treats JIRA as the source of truth, @@ -127,19 +137,23 @@ class Sync: repo_id=repo_id )) + repo = self.github.getRepository(repo_id) states = {} if states is None else states pairs = {} # gather alerts - for a in self.github.getRepository(repo_id).get_alerts(): - pairs[a.number()] = (a, []) + for a in itertools.chain( + repo.get_secrets(), + repo.get_alerts() + ): + pairs[a.get_key()] = (a, []) # gather issues - for i in self.jira.fetch_issues(repo_id): - _, anum, _, _ = i.get_alert_info() - if not anum in pairs: - pairs[anum] = (None, []) - pairs[anum][1].append(i) + for i in self.jira.fetch_issues(repo.get_key()): + _, _, _, alert_key, _ = i.get_alert_info() + if not alert_key in pairs: + pairs[alert_key] = (None, []) + pairs[alert_key][1].append(i) # remove unused states for k in list(states.keys()): @@ -147,8 +161,8 @@ class Sync: del states[k] # perform sync - for anum, (alert, issues) in pairs.items(): - past_state = states.get(anum, None) + for akey, (alert, issues) in pairs.items(): + past_state = states.get(akey, None) if alert is None or alert.get_state() != past_state: d = DIRECTION_G2J else: @@ -157,6 +171,6 @@ class Sync: new_state = self.sync(alert, issues, d) if new_state is None: - states.pop(anum, None) + states.pop(akey, None) else: - states[anum] = new_state + states[akey] = new_state diff --git a/util.py b/util.py index 82711b9..c9fc852 100644 --- a/util.py +++ b/util.py @@ -6,16 +6,19 @@ REQUEST_TIMEOUT = 10 def state_from_json(s): - # convert string keys into int keys - # this is necessary because JSON doesn't allow - # int keys and json.dump() automatically converts - # int keys into string keys. - return {int(k): v for k, v in json.loads(s).items()} + j = json.loads(s) + if not "version" in j: + return {} + return j['states'] def state_to_json(state): + final = { + 'version': 2, + 'states': state + } return json.dumps( - state, + final, indent=2, sort_keys=True ) @@ -39,9 +42,5 @@ def make_key(s): return sha_1.hexdigest() -def make_alert_key(repo_id, alert_num): - return make_key(repo_id + '/' + str(alert_num)) - - def json_accept_header(): return {'Accept': 'application/vnd.github.v3+json'} From e9a9a3c65f88f563ccd1d942fdabbe7e2e4c1fd2 Mon Sep 17 00:00:00 2001 From: Chelsea Boling Date: Tue, 2 Nov 2021 22:24:36 -0700 Subject: [PATCH 3/3] Lint files --- ghlib.py | 64 +++++++++++++++++++++++++----------------------------- jiralib.py | 40 +++++++++++++++------------------- server.py | 6 ++--- sync.py | 43 +++++++----------------------------- util.py | 13 +++-------- 5 files changed, 62 insertions(+), 104 deletions(-) diff --git a/ghlib.py b/ghlib.py index 7f0a039..2133fa2 100644 --- a/ghlib.py +++ b/ghlib.py @@ -156,7 +156,7 @@ class GHRepository: try: resp = requests.get( - '{api_url}/repos/{repo_id}/{api_segment}/alerts?per_page={results_per_page}{state}'.format( + "{api_url}/repos/{repo_id}/{api_segment}/alerts?per_page={results_per_page}{state}".format( api_url=self.gh.url, repo_id=self.repo_id, api_segment=api_segment, @@ -194,32 +194,28 @@ class GHRepository: def get_info(self): resp = requests.get( - '{api_url}/repos/{repo_id}'.format( - api_url=self.gh.url, - repo_id=self.repo_id + "{api_url}/repos/{repo_id}".format( + api_url=self.gh.url, repo_id=self.repo_id ), headers=self.gh.default_headers(), - timeout=util.REQUEST_TIMEOUT + timeout=util.REQUEST_TIMEOUT, ) resp.raise_for_status() return resp.json() - def isprivate(self): - return self.get_info()['private'] - + return self.get_info()["private"] def get_alerts(self, state=None): - for a in self.alerts_helper('code-scanning', state): + for a in self.alerts_helper("code-scanning", state): yield Alert(self, a) - def get_secrets(self, state=None): # secret scanning alerts are only accessible on private repositories, so # we return an empty list on public ones if not self.isprivate(): return - for a in self.alerts_helper('secret-scanning', state): + for a in self.alerts_helper("secret-scanning", state): yield Secret(self, a) def get_alert(self, alert_num): @@ -249,13 +245,13 @@ class AlertBase: self.json = json def get_state(self): - return self.json['state'] == 'open' + return self.json["state"] == "open" def get_type(self): return type(self).__name__ def number(self): - return int(self.json['number']) + return int(self.json["number"]) def short_desc(self): raise NotImplementedError @@ -264,7 +260,7 @@ class AlertBase: raise NotImplementedError def hyperlink(self): - return self.json['html_url'] + return self.json["html_url"] def can_transition(self): return True @@ -279,9 +275,9 @@ class AlertBase: logger.info( '{action} {atype} {alert_num} of repository "{repo_id}".'.format( atype=self.get_type(), - action='Reopening' if target_state else 'Closing', + action="Reopening" if target_state else "Closing", alert_num=self.number(), - repo_id=self.github_repo.repo_id + repo_id=self.github_repo.repo_id, ) ) self.do_adjust_state(target_state) @@ -292,24 +288,22 @@ class Alert(AlertBase): AlertBase.__init__(self, github_repo, json) def can_transition(self): - return self.json['state'] != 'fixed' + return self.json["state"] != "fixed" def long_desc(self): - return self.json['rule']['description'] + return self.json["rule"]["description"] def short_desc(self): - return self.json['rule']['id'] + return self.json["rule"]["id"] def get_key(self): - return util.make_key( - self.github_repo.repo_id + '/' + str(self.number()) - ) + return util.make_key(self.github_repo.repo_id + "/" + str(self.number())) def do_adjust_state(self, target_state): - state = 'open' - reason = '' + state = "open" + reason = "" if not target_state: - state = 'dismissed' + state = "dismissed" reason = ', "dismissed_reason": "won\'t fix"' data = '{{"state": "{state}"{reason}}}'.format(state=state, reason=reason) resp = requests.patch( @@ -333,31 +327,33 @@ class Secret(AlertBase): return True def long_desc(self): - return self.json['secret_type'] + return self.json["secret_type"] def short_desc(self): return self.long_desc() def get_key(self): return util.make_key( - self.github_repo.repo_id + '/' + self.get_type() + '/' + str(self.number()) + self.github_repo.repo_id + "/" + self.get_type() + "/" + str(self.number()) ) def do_adjust_state(self, target_state): - state = 'open' - resolution = '' + state = "open" + resolution = "" if not target_state: - state = 'resolved' + state = "resolved" resolution = ', "resolution": "wont_fix"' - data = '{{"state": "{state}"{resolution}}}'.format(state=state, resolution=resolution) + data = '{{"state": "{state}"{resolution}}}'.format( + state=state, resolution=resolution + ) resp = requests.patch( - '{api_url}/repos/{repo_id}/secret-scanning/alerts/{alert_num}'.format( + "{api_url}/repos/{repo_id}/secret-scanning/alerts/{alert_num}".format( api_url=self.gh.url, repo_id=self.github_repo.repo_id, - alert_num=self.number() + alert_num=self.number(), ), data=data, headers=self.gh.default_headers(), - timeout=util.REQUEST_TIMEOUT + timeout=util.REQUEST_TIMEOUT, ) resp.raise_for_status() diff --git a/jiralib.py b/jiralib.py index 7cd71ad..cadb4d1 100644 --- a/jiralib.py +++ b/jiralib.py @@ -12,11 +12,11 @@ DELETE_EVENT = "jira:issue_deleted" TITLE_PREFIXES = { - 'Alert': '[Code Scanning Alert]:', - 'Secret': '[Secret Scanning Alert]:' + "Alert": "[Code Scanning Alert]:", + "Secret": "[Secret Scanning Alert]:", } -DESC_TEMPLATE=""" +DESC_TEMPLATE = """ {long_desc} {alert_url} @@ -162,12 +162,9 @@ class JiraProject: # attach the new state file self.jira.attach_file( - i.key, - repo_id_to_fname(repo_id), - util.state_to_json(state) + i.key, repo_id_to_fname(repo_id), util.state_to_json(state) ) - def create_issue( self, repo_id, @@ -177,14 +174,12 @@ class JiraProject: alert_type, alert_num, repo_key, - alert_key + alert_key, ): raw = self.j.create_issue( project=self.projectkey, - summary='{prefix} {short_desc} in {repo}'.format( - prefix=TITLE_PREFIXES[alert_type], - short_desc=short_desc, - repo=repo_id + summary="{prefix} {short_desc} in {repo}".format( + prefix=TITLE_PREFIXES[alert_type], short_desc=short_desc, repo=repo_id ), description=DESC_TEMPLATE.format( long_desc=long_desc, @@ -193,7 +188,7 @@ class JiraProject: alert_type=alert_type, alert_num=alert_num, repo_key=repo_key, - alert_key=alert_key + alert_key=alert_key, ), issuetype={"name": "Bug"}, labels=self.labels, @@ -203,16 +198,17 @@ class JiraProject: issue_key=raw.key, alert_num=alert_num, repo_id=repo_id ) ) - logger.info('Created issue {issue_key} for {alert_type} {alert_num} in {repo_id}.'.format( - issue_key=raw.key, - alert_type=alert_type, - alert_num=alert_num, - repo_id=repo_id - )) + logger.info( + "Created issue {issue_key} for {alert_type} {alert_num} in {repo_id}.".format( + issue_key=raw.key, + alert_type=alert_type, + alert_num=alert_num, + repo_id=repo_id, + ) + ) return JiraIssue(self, raw) - def fetch_issues(self, key): issue_search = 'project={jira_project} and description ~ "{key}"'.format( jira_project='"{}"'.format(self.projectkey), key=key @@ -325,12 +321,12 @@ def parse_alert_info(desc): return failed repo_id = m.group(1) - m = re.search('ALERT_TYPE=(.*)$', desc, re.MULTILINE) + m = re.search("ALERT_TYPE=(.*)$", desc, re.MULTILINE) if m is None: alert_type = None else: alert_type = m.group(1) - m = re.search('ALERT_NUMBER=(.*)$', desc, re.MULTILINE) + m = re.search("ALERT_NUMBER=(.*)$", desc, re.MULTILINE) if m is None: return failed diff --git a/server.py b/server.py index 5d633cd..42729c1 100644 --- a/server.py +++ b/server.py @@ -54,9 +54,9 @@ def jira_webhook(): ): return jsonify({"code": 403, "error": "Unauthorized"}), 403 - payload = json.loads(request.data.decode('utf-8')) - event = payload['webhookEvent'] - desc = payload['issue']['fields']['description'] + payload = json.loads(request.data.decode("utf-8")) + event = payload["webhookEvent"] + desc = payload["issue"]["fields"]["description"] repo_id, _, _, _, _ = jiralib.parse_alert_info(desc) app.logger.debug('Received JIRA webhook for event "{event}"'.format(event=event)) diff --git a/sync.py b/sync.py index 2ff24dc..af1f109 100644 --- a/sync.py +++ b/sync.py @@ -19,54 +19,30 @@ class Sync: def alert_created(self, repo_id, alert_num): a = self.github.getRepository(repo_id).get_alert(alert_num) - self.sync( - a, - self.jira.fetch_issues(a.get_key()), - DIRECTION_G2J - ) + self.sync(a, self.jira.fetch_issues(a.get_key()), DIRECTION_G2J) def alert_changed(self, repo_id, alert_num): a = self.github.getRepository(repo_id).get_alert(alert_num) - self.sync( - a, - self.jira.fetch_issues(a.get_key()), - DIRECTION_G2J - ) + self.sync(a, self.jira.fetch_issues(a.get_key()), DIRECTION_G2J) def alert_fixed(self, repo_id, alert_num): a = self.github.getRepository(repo_id).get_alert(alert_num) - self.sync( - a, - self.jira.fetch_issues(a.get_key()), - DIRECTION_G2J - ) + self.sync(a, self.jira.fetch_issues(a.get_key()), DIRECTION_G2J) def issue_created(self, desc): repo_id, alert_num, _, _, _ = jiralib.parse_alert_info(desc) a = self.github.getRepository(repo_id).get_alert(alert_num) - self.sync( - a, - self.jira.fetch_issues(a.get_key()), - DIRECTION_J2G - ) + self.sync(a, self.jira.fetch_issues(a.get_key()), DIRECTION_J2G) def issue_changed(self, desc): repo_id, alert_num, _, _, _ = jiralib.parse_alert_info(desc) a = self.github.getRepository(repo_id).get_alert(alert_num) - self.sync( - a, - self.jira.fetch_issues(a.get_key()), - DIRECTION_J2G - ) + self.sync(a, self.jira.fetch_issues(a.get_key()), DIRECTION_J2G) def issue_deleted(self, desc): repo_id, alert_num, _, _, _ = jiralib.parse_alert_info(desc) a = self.github.getRepository(repo_id).get_alert(alert_num) - self.sync( - a, - self.jira.fetch_issues(a.get_key()), - DIRECTION_J2G - ) + self.sync(a, self.jira.fetch_issues(a.get_key()), DIRECTION_J2G) def sync(self, alert, issues, in_direction): if alert is None: @@ -87,7 +63,7 @@ class Sync: alert.get_type(), alert.number(), alert.github_repo.get_key(), - alert.get_key() + alert.get_key(), ) newissue.adjust_state(alert.get_state()) return alert.get_state() @@ -132,10 +108,7 @@ class Sync: pairs = {} # gather alerts - for a in itertools.chain( - repo.get_secrets(), - repo.get_alerts() - ): + for a in itertools.chain(repo.get_secrets(), repo.get_alerts()): pairs[a.get_key()] = (a, []) # gather issues diff --git a/util.py b/util.py index 35f25df..860af3e 100644 --- a/util.py +++ b/util.py @@ -9,19 +9,12 @@ def state_from_json(s): j = json.loads(s) if not "version" in j: return {} - return j['states'] + return j["states"] def state_to_json(state): - final = { - 'version': 2, - 'states': state - } - return json.dumps( - final, - indent=2, - sort_keys=True - ) + final = {"version": 2, "states": state} + return json.dumps(final, indent=2, sort_keys=True) def state_from_file(fpath):