def validate_tech_approval(self): """ notify in channel if not tech approved :return: relevant response dict """ if self.pr.is_merged: # and self.head_branch in PushPayload.PROTECTED_BRANCH_LIST: TO ENABLE is_bad_pr = self.is_bad_pr() bad_name_str = MSG_BAD_START + "@" + self.created_by if is_bad_pr: msg = MSG_NO_TECH_REVIEW.format( name=bad_name_str, pr=self.pr.link_pretty, title=self.pr.title, branch=self.pr.base_branch, team=self.pr.config.cc_tech_team) LOG.debug(msg) self.slack.postToSlack(self.pr.config.alertChannelName, msg) LOG.info("Bad PR={msg} repo:{repo}".format(repo=self.pr.repo, msg=is_bad_pr)) return { "msg": "Bad PR={msg} repo:{repo}".format(repo=self.pr.repo, msg=is_bad_pr) } return {"msg": "PR is approved so No Alerts"} return {"msg": "Skipped review because its not PR merge event"}
def send_to_slack(self): final_text = str(self.parsed_data.comment) for item in self.js_map_dict: final_text = final_text.replace( '[~{0}]'.format(item), '<@{0}>'.format(self.js_map_dict.get(item))) LOG.info('************ final comment ************ %s' % final_text) first_attach = JIRA_COMMENT.copy() # shallow copy first_attach["pretext"] = first_attach.get("pretext").format( commenter=self.parsed_data.commenter, issue_key=self.parsed_data.issue_key) first_attach["title"] = first_attach.get("title").format( issue_key=self.parsed_data.issue_key, issue_title=self.parsed_data.issue_title) first_attach["title_link"] = first_attach.get("title_link").format( issue_url=self.parsed_data.issue_url) first_attach["text"] = first_attach.get("text").format( final_text=final_text) attachment = list() attachment.append(first_attach) slack = Slacker(self.slack_token) # resp = slack.chat.post_message(channel="@Nagaraj", text="", username="******", as_user=False, attachments=attachment) for item in self.js_map_dict: slack.chat.post_message(channel=self.js_map_dict.get(item), text="", username="******", as_user=True, attachments=attachment)
def notify_channel_on_merge(self): """ pass entry in fixed channel for all code merge details (merged to any of sensitive branch) :return: relevant response dict """ if self.pr.is_merged: LOG.debug( "**** Repo=%s, new merge came to=%s, setting trace to=%s channel" % (self.pr.repo, self.pr.base_branch, self.pr.config.codeChannelName)) msg = MSG_CODE_CHANNEL.format(title=self.pr.title, desc=self.pr.description, pr=self.pr.link, head_branch=self.pr.head_branch, base_branch=self.pr.base_branch, pr_by=self.created_by, merge_by=self.merged_by) self.slack.postToSlack(self.pr.config.codeChannelName, msg) LOG.info( "informed %s because pr=%s is merged into sensitive branch=%s" % (self.pr.config.codeChannelName, self.pr.link_pretty, self.pr.base_branch)) return { "msg": "informed %s because pr=%s is merged into sensitive branch=%s" % (self.pr.config.codeChannelName, self.pr.link_pretty, self.pr.base_branch) } return { "msg", "Skipped posting to code channel because '%s' is not merge event" % self.pr.action }
def is_bad_pr(self): """ parse approval's content to identify if PR is actually approved or just random approval click :return: bad_pr (boolean) """ reviews = self.github.get_reviews() if 200 != reviews.status_code: raise Exception(reviews.content) bad_pr = True LOG.info("***** Reading Reviews *****") for item in json.loads(reviews.content): if "APPROVED" == item["state"]: review_comment = item["body"] LOG.debug("review body= %s" + review_comment) thumbsUpIcon = THUMBS_UP_ICON in json.dumps(review_comment) LOG.debug("unicode thumbsUp icon present=%s" % (thumbsUpIcon)) if self.pr.opened_by in self.pr.config.superMembers: # FEW FOLKS TO ALLOW TO HAVE SUPER POWER LOG.debug("PR is opened by %s who is the super user of repo %s, so NO alert'" % (self.pr.opened_by_slack, self.pr.repo)) bad_pr = False break print "***** review_comment", review_comment created_by = self.pr.config.getSlackName(self.pr.opened_by) if item["user"]["login"] != created_by and (review_comment.find("+1") != -1 or thumbsUpIcon): LOG.debug("+1 is found from reviewer=%s marking No Alert " % item["user"]["login"]) bad_pr = False break return bad_pr
def notify_if_sensitive_modified(self): """ check & notify devOps team if any sensitive files are touched :return: relevant response dict """ if self.pr.is_merged: if self.sensitive_file_touched.get("is_found"): msg = MSG_SENSITIVE_FILE_TOUCHED.format( notify_folks=self.pr.config.devOpsTeamToBeNotified, file=self.sensitive_file_touched["file_name"], pr=self.pr.link_pretty, pr_by=self.created_by, pr_number=self.pr.number) self.slack.postToSlack(self.pr.config.alertChannelName, msg) LOG.info( "informed %s because sensitive files are touched in pr=%s" % (self.pr.config.devOpsTeamToBeNotified, self.pr.link_pretty)) return { "msg": "informed %s because sensitive files are touched" % self.pr.config.devOpsTeamToBeNotified } return { "msg": "Skipped sensitive files alerts because no sensitive file being touched" } return { "msg": "Skipped sensitive files alerts because its not PR merge event %s" % self.pr.config.devOpsTeamToBeNotified }
def validate_product_approval(self): """ notify in channel if not product approved (applicable only to files/dir which require product approval) :return: relevant response dict """ if self.pr.is_merged: if self.pr.opened_by in self.pr.config.superMembers: LOG.debug('pr_opened_by is super user of {repo} so NO alert, super_members={super_members}' .format(repo=self.pr.repo, super_members=self.pr.config.superMembers)) return {"msg": "Skipped product review because the PR=%s is by a Super User" % self.pr.link_pretty} if self.change_requires_product_plus1: comments = self.github.get_comments() is_product_plus1 = self.is_plus_1_in_comments(comments, self.pr.config.productTeamGithub) if not is_product_plus1: bad_name_str = MSG_BAD_START + "@" + self.created_by msg = MSG_NO_PRODUCT_REVIEW.format(name=bad_name_str, pr=self.pr.link_pretty, title=self.pr.title, branch=self.pr.base_branch, team="".join(self.pr.config.productTeamToBeNotified)) LOG.debug(msg) self.slack.postToSlack(self.pr.config.alertChannelName, msg) LOG.info("Bad PR={msg} repo:{repo}".format(repo=self.pr.repo, msg=self.is_bad_pr)) return {"msg": "Bad PR={msg} repo:{repo}".format(repo=self.pr.repo, msg=self.is_bad_pr)} return {"msg":"Product approved so no alert, pr=%s" %self.pr.link_pretty} return {"msg":"Skipped product review because no changes found which requires product +1 as well, PR=%s" %self.pr.link_pretty} return {"msg": "Skipped product review because the PR=%s is not merged" %self.pr.link_pretty}
def __init__(self, repo=None): config_file = os.environ["config"] #LOG.info("********** config file=" + config_file) LOG.info("********** config file={config_file}".format( config_file=config_file)) # absolute path to keep file anywhere self.config = get_dict_from_config_file(config_file) self.repo_name = repo
def remind_direct_release_guideline_on_merge(self): """ remind individually to follow guidelines for QA process :return: relevant response dict """ if self.pr.is_merged: if self.base_branch in self.pr.config.sensitiveBranches: msg = MSG_GUIDELINE_ON_MERGE.format(person=self.created_by, pr=self.pr.link_pretty, base_branch=self.pr.base_branch, title=self.pr.title, release_notes_link=self.pr.config.release_notes_link) self.slack.directSlack('@' + self.created_by, msg) LOG.info("slacked personally to %s" % self.created_by) return {"msg": "slacked personally to %s" % self.created_by} return {"msg": "skipped slack personally because not sensitive branch"} return {"msg": "skipped slack personally to %s because its not merge event" % self.created_by}
def directSlack(self, person, msg=None, as_user=False, *args, **kwargs): if self.config.is_debug: person = self.config.debug_folks LOG.info("\n************** NOTIFYING ******************\n" "************** %s *************\n" "Message= %s\n" "******************************************* " % (person, msg)) self.slack.chat.post_message(channel=person, text=msg, icon_url=self.icon, username="******", as_user=as_user, *args, **kwargs)
def close_dangerous_pr(self): """ close a Pull Request which is not supposed to be opened Ex. base=master head=feature :return: relevant response dict """ if self.pr.is_opened or self.pr.is_reopened: master_branch = self.pr.config.mainBranch qa_branch = self.pr.config.testBranch if self.base_branch == master_branch and self.head_branch != qa_branch: msg = MSG_AUTO_CLOSE.format(tested_branch=qa_branch, main_branch=master_branch) self.github.modify_pr(msg, "closed") self.slack.postToSlack(self.pr.config.alertChannelName, "@" + self.created_by + ": " + msg) LOG.info("closed dangerous PR %s" % self.pr.link_pretty) return {"msg": "closed dangerous PR %s" % self.pr.link_pretty} return {"msg": "skipped closing PR=%s because not raised to mainBranch %s" %(self.pr.link_pretty, master_branch)} return {"msg": "skipped closing PR because not a opened PR"}
def notify_on_action(self): """ notify respective folk(s) on particular action. Ex. on open notify to lead (or on merged, can configure that) :return: relevant response dict """ desired_action = self.pr.config.actionToBeNotifiedFor if self.pr.action == desired_action: if self.pr.base_branch == self.pr.config.mainBranch: msg = MSG_OPENED_TO_MAIN_BRANCH.format( repo=self.pr.repo, pr_by=self.created_by, main_branch=self.pr.config.mainBranch, title=self.pr.title, pr=self.pr.link_pretty, action=self.pr.action) for person in self.pr.config.techLeadsToBeNotified: self.slack.postToSlack(person, msg + MSG_RELEASE_PREPARATION) LOG.info( "Notified to %s on action %s" % (self.pr.config.techLeadsToBeNotified, self.pr.action)) return { "msg": "Notified to %s on action %s" % (self.pr.config.techLeadsToBeNotified, self.pr.action) } else: msg = MSG_OPENED_TO_PREVENTED_BRANCH.format( repo=self.pr.repo, pr_by=self.created_by, base_branch=self.pr.base_branch, title=self.pr.title, pr=self.pr.link_pretty, action=self.pr.action) self.slack.postToSlack(self.pr.config.personToBeNotified, msg) LOG.info("Notified to %s on action %s" % (self.pr.config.personToBeNotified, self.pr.action)) return { "msg": "Notified to %s on action %s" % (self.pr.config.personToBeNotified, self.pr.action) } return { "msg": "Skipped notify because its (%s) not desired event '%s'" % (self.pr.action, desired_action) }
def comment_on_pr(self): """ comment on pr :return: relevant response dict """ if self.pr.is_opened: if not self.pr.config.is_debug: if self.pr.base_branch == self.pr.config.mainBranch: guideline_comment = SPECIAL_COMMENT self.slack.postToSlack(self.pr.opened_by) else: guideline_comment = GENERAL_COMMENT self.github.comment_pr(self.pr.comments_section, guideline_comment) LOG.info("**** Added Comment of dev guidelines ***") return {"msg": "Added Comment of dev guidelines"} return {"msg": "Skipped commenting because DEBUG is on "} return {"msg": "Skipped commenting because its not PR opened"}
def postToSlack(self, channel, msg=None, as_user=True, *args, **kwargs): LOG.info("\n************** NOTIFYING ******************\n" "************** %s *************\n" "Message= %s\n" "******************************************* " % (channel, msg)) try: self.slack.chat.post_message(channel=channel, text=msg, icon_url=self.icon, as_user=as_user, *args, **kwargs) except Exception as ex: LOG.error("Error while posting alert to slack, please check if: \n1. The provided slack/hubot token for alice is correct " \ "and it has access to the channel=%s" \ "\n2.The channel name is correct\n" %(channel)) raise ex
def parse_files_and_set_flags(self): """ Parse payload and keep important flags ready to be used in checks :return: sensitive_file_touched (dict of file name with boolean value) :return: change_requires_product_plus1 (boolean) """ change_requires_product_plus1 = False sensitive_file_touched = {} try: files_contents = self.github.get_files() LOG.info("**** Reading files ****") for item in files_contents: file_path = item["filename"] if any(x in str(file_path) for x in self.pr.config.sensitiveFiles): sensitive_file_touched["is_found"] = True sensitive_file_touched["file_name"] = str(file_path) if item["filename"].find(self.pr.config.productPlusRequiredDirPattern) != -1: LOG.info("product change found marking ui_change to True") change_requires_product_plus1 = True # break except PRFilesNotFoundException, e: LOG.exception(e)
def handle_issue_create(self): assignee_slack_channel_id = self.slack_dict.get( self.parsed_data.assignee_email) LOG.info( '*********** assigne slack channel id create handler ************ %s' % assignee_slack_channel_id) attachment = list() fields = list() for change in self.parsed_data.change_log: field_item = dict() field_list = ['assignee', 'description', 'priority', 'Status'] if str(change.get("field")) in field_list: field_item["title"] = str(change.get("field")) field_item["value"] = str(change.get("toString")) fields.append(field_item) attach = JIRA_ISSUE_UPDATE.copy() attach[ 'pretext'] = '*{issue_reporter}* reported issue *{issue_key}*'.format( issue_reporter=self.parsed_data.issue_reporter, issue_key=self.parsed_data.issue_key) attach["title"] = attach.get("title").format( issue_key=self.parsed_data.issue_key, issue_title=self.parsed_data.issue_title) attach["title_link"] = attach.get("title_link").format( issue_url=self.parsed_data.issue_url) attach["text"] = '' attach["fields"] = fields attachment.append(attach) slack = Slacker(self.slack_token) # resp = slack.chat.post_message(channel="@Nagaraj", text="", username="******", as_user=True, attachments=attachment) slack.chat.post_message(channel=str(assignee_slack_channel_id), text="", username="******", as_user=True, attachments=attachment)
class RunChecks(object): def execute_check(self, ci, check): LOG.debug("************* Starting check=%s *****************" % check) response = getattr(ci, check)() LOG.debug("for check= %s, response= %s" % (check, response)) def run_checks(self, request, data): ci = CheckImpl(PushPayloadParser(request, payload=data)) response = {} checks = ci.pr.config.checks if ci.pr.is_sensitive_branch: if len(checks) == 0: import inspect method_names = [ attr for attr in dir(ci) if inspect.ismethod(getattr(ci, attr)) ] for check in method_names: if check != "__init__": self.execute_check(ci, check) else: try: for check in checks: try: self.execute_check(ci, check) except AttributeError: LOG.debug("Exception in Run Checks", exc_info=traceback) raise CheckNotFoundException(check) except Exception, e: LOG.debug("Exception in Run Checks", exc_info=traceback) if 'invalid_auth' not in e: raise Exception( str(e) + ISSUE_FOUND.format(issue_link=ISSUE_LINK)) return response LOG.info("skipped because '%s' is not sensitive branch" % ci.base_branch) return { "msg": "skipped because '%s' is not sensitive branch" % ci.base_branch }
def __init__(self, repo): config_file = os.environ["config"] LOG.info("********** config file=" + config_file) # absolute path to keep file anywhere self.config = get_dict_from_config_file(config_file) self.repo_name = repo
def handle_issue_update(self): assignee_slack_channel_id = self.slack_dict.get( self.parsed_data.assignee_email) reporter_slack_channel_id = self.slack_dict.get( self.parsed_data.issue_reporter_email) issue_reporter_email_id = self.parsed_data.issue_reporter_email issue_updated_email_id = self.parsed_data.issue_updated_by_email assignee_email_id = self.parsed_data.assignee_email LOG.info( '*********** assigne slack channel id update handler ************ %s' % assignee_slack_channel_id) LOG.info( '*********** reporter slack channel id update handler ************ %s' % reporter_slack_channel_id) attachment = list() for change in self.parsed_data.change_log: attach = JIRA_ISSUE_UPDATE.copy() if change.get("field") == "description": attach[ 'pretext'] = "*{issue_updated_by}* updated {issue_key} `{field}`".format( issue_updated_by=self.parsed_data.issue_updated_by, issue_key=self.parsed_data.issue_key, field=change.get('field'), ) else: if change.get('fromString'): attach['pretext'] = attach.get("pretext").format( issue_updated_by=self.parsed_data.issue_updated_by, issue_key=self.parsed_data.issue_key, field=change.get('field'), from_string=change.get('fromString'), to_string=change.get('toString')) else: attach[ 'pretext'] = "*{issue_updated_by}* updated {issue_key} `{field}` to `{to_string}`".format( issue_updated_by=self.parsed_data.issue_updated_by, issue_key=self.parsed_data.issue_key, field=change.get('field'), to_string=change.get('toString')) attach["title"] = attach.get("title").format( issue_key=self.parsed_data.issue_key, issue_title=self.parsed_data.issue_title) attach["title_link"] = attach.get("title_link").format( issue_url=self.parsed_data.issue_url) attach["text"] = attach.get("text").format( issue_desc=self.description) attachment.append(attach) slack = Slacker(self.slack_token) # resp = slack.chat.post_message(channel="@Nagaraj", text="", username="******", as_user=True, attachments=attachment) if issue_updated_email_id == issue_reporter_email_id and issue_updated_email_id != assignee_email_id: slack.chat.post_message(channel=str(assignee_slack_channel_id), text="", username="******", as_user=True, attachments=attachment) elif issue_updated_email_id == assignee_email_id and issue_updated_email_id != issue_reporter_email_id: slack.chat.post_message(channel=str(reporter_slack_channel_id), text="", username="******", as_user=True, attachments=attachment)