in services/lambda-mxnet-ci-bot/CIBot.py [0:0]
def parse_webhook_data(self, event):
"""
This method triggers the CI bot when the appropriate
GitHub event is recognized by use of a webhook
:param event: The event data that is received whenever a PR comment is made
:return: Log statements which we can track in lambda
"""
try:
github_event = ast.literal_eval(event["Records"][0]['body'])['headers']["X-GitHub-Event"]
logging.info(f"github event {github_event}")
except KeyError:
raise Exception("Not a GitHub Event")
if not self._secure_webhook(event):
raise Exception("Failed to validate WebHook security")
try:
payload = json.loads(ast.literal_eval(event["Records"][0]['body'])['body'])
except ValueError:
raise Exception("Decoding JSON for payload failed")
# find all jobs currently run in CI
self._find_all_jobs()
if not self.all_jobs:
raise Exception("Unable to gather jobs from the CI")
if(github_event == "pull_request"):
pr_num = payload['number']
if(payload['action'] == 'opened'):
logging.info('New PR create event detected. Send help guide.')
pr_author = payload['pull_request']['user']['login']
if(self.auto_trigger):
intro_mesg = "All tests are already queued to run once. If tests fail, you can trigger one or more tests again with the following commands:"
else:
intro_mesg = "Once your PR is ready for CI checks, invoke the following commands:"
message = "Hey @" + pr_author + " , Thanks for submitting the PR \n" \
+ intro_mesg + " \n" \
"- To trigger all jobs: @" + self.bot_user + " run ci [all] \n" \
"- To trigger specific jobs: @" + self.bot_user + " run ci [job1, job2] \n" \
"*** \n" \
"**CI supported jobs**: " + str(list(self.all_jobs)).translate(self.translation) + "\n" \
"*** \n" \
"_Note_: \n Only following 3 categories can trigger CI :" \
"PR Author, MXNet Committer, Jenkins Admin. \n" \
"All CI tests must pass before the PR can be merged. \n"
self.create_comment(pr_num, message)
elif(payload['action'] == 'closed' and payload['pull_request']['merged'] is True and self.run_after_merge):
if(payload['pull_request']['base']['ref'] != 'master'):
# PR not merged into master
# no need to run CI
logging.info('PR merged into' + payload['pull_request']['base']['ref'] + '.Hence ignore.')
return
# PR has been merged into master
# trigger a final CI run on master
successful_jobs = self._trigger_ci(self.all_jobs, "master")
message = "PR #" + str(pr_num) + " merged. Congrats! \n"
if successful_jobs:
message += "Jenkins CI successfully triggered : " + str(successful_jobs).translate(self.translation)
else:
message += "However, the bot is unable to trigger CI."
self.create_comment(pr_num, message)
else:
# other actions : reopened, deleted
logging.info('Irrelevant PR related event. Ignore.')
return
if github_event in ["check_suite", "check_run", "status"]:
# if payload["check_suite"]["app"]["slug"] == "github-actions":
logging.info('Irrelevant event. Ignore.')
return
if "action" in payload:
if(payload["action"] == 'deleted'):
logging.info('comment deleted. Ignore.')
return
# fetch comment author
comment_author = payload["comment"]["user"]["login"]
# if comment author is label bot itself, ignore
if comment_author == self.bot_user:
logging.info('Ignore comments made by bot')
return
logging.info(f"payload loaded {payload}")
issue_num = payload["issue"]["number"]
# Grab actual payload data of the appropriate GitHub event needed for
# triggering CI
if github_event == "issue_comment":
# Check if the bot is invoked from a PR or an issue
if "@" + str(self.bot_user) in payload["comment"]["body"].lower() and "pull_request" not in payload["issue"]:
# This means the bot didn't get invoked from a PR
message = "Hey @" + comment_author + " \n @" + self.bot_user + " can only be invoked on a PR."
self.create_comment(issue_num, message)
logging.error("Bot invoked on an Issue instead of PR")
return
# Look for phrase referencing @<bot_user>
if "@" + str(self.bot_user) in payload["comment"]["body"].lower():
# if(payload["comment"]["body"].find("]")==-1):
# # ] not found in the phrase; capture everything bot's name onwards
# phrase = payload["comment"]["body"][payload["comment"]["body"].find("@"+str(self.bot_user)):]
# else:
# # ] found in the phrase; capture everything between bot's name and end of list token ]
phrase = payload["comment"]["body"][payload["comment"]["body"].find("@" + str(self.bot_user)):payload["comment"]["body"].find("]") + 1]
# remove @<bot_user from the phrase
phrase = phrase.replace('@' + self.bot_user, '')
logging.info(phrase)
# remove whitespace characters
phrase = ' '.join(phrase.split())
# Handles both cases : ( run ci[job1] ) and ( run ci [job1] ) are treated the same way
action = phrase[0:phrase.find('[')].strip()
logging.info(f'action {action}')
# only looking for the word run in PR Comment
if action not in ['run ci']:
message = "Undefined action detected. \n" \
"Permissible actions are : run ci [all], run ci [job1, job2] \n" \
"Example : @" + self.bot_user + " run ci [all] \n" \
"Example : @" + self.bot_user + " run ci [centos-cpu, clang]"
self.create_comment(issue_num, message)
logging.error(f'Undefined action by user: {action}')
raise Exception("Undefined action by user")
# parse jobs from the comment
user_jobs = self._parse_jobs_from_comment(phrase)
if not user_jobs:
logging.error(f'Message typed by user: {phrase}')
raise Exception("No jobs found from PR comment")
# check if any of the jobs requested by user are supported by CI
# intersection of user request jobs and CI supported jobs
if user_jobs == ['all']:
valid_jobs = self.all_jobs
else:
valid_jobs = list(set(user_jobs).intersection(set(self.all_jobs)))
if not valid_jobs:
logging.error(f'Jobs entered by user: {set(user_jobs)}')
logging.error(f'CI supported Jobs: {set(self.all_jobs)}')
message = "None of the jobs entered are supported. \n" \
"Jobs entered by user: " + str(user_jobs).translate(self.translation) + "\n" \
"CI supported Jobs: " + str(list(self.all_jobs)).translate(self.translation) + "\n"
self.create_comment(issue_num, message)
raise Exception("Provided jobs don't match the ones supported by CI")
# check if the comment author is authorized
pr_author = payload["issue"]["user"]["login"]
if self._is_authorized(comment_author, pr_author):
logging.info(f'Authorized user: {comment_author}')
# since authorized user commented, go ahead trigger CI
# branch to be triggered is PR-N where N is the PR number
successful_jobs = self._trigger_ci(valid_jobs, "PR-" + str(issue_num))
if successful_jobs:
message = "Jenkins CI successfully triggered : " + str(successful_jobs).translate(self.translation)
else:
message = "Authorized user recognized. However, the bot is unable to trigger CI."
self.create_comment(issue_num, message)
else:
# since unauthorized user tried to trigger CI
logging.info(f'Unauthorized user: {comment_author}')
message = "Unauthorized access detected. \n" \
"Only following 3 categories can trigger CI : \n" \
"PR Author, MXNet Committer, Jenkins Admin."
self.create_comment(issue_num, message)
else:
logging.error("CI Bot is not called")
return
else:
logging.info(f'GitHub Event unsupported by CI Bot: {github_event}') # {payload["action"]}