123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- #! /usr/bin/env python
- # -*- coding: utf8 -*-
- #
- # gitlab irc sender
- # Copyright (C) 2016-2017 Andrei Karas (4144)
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 3 of the License, or
- # any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- import json
- import re
- from code.codeshipirchandler import CodeshipIrcHandler
- from code.core import Core
- from code.githubirchandler import GithubIrcHandler
- from code.irchandler import IrcHandler
- from code.logger import Logger
- eolSplit = re.compile("\n")
- class ActionHandler():
- def printInfo(self, text):
- if Core.config.show_info == True:
- Logger.addLog(text)
- def saveJson(self, data):
- Core.log.info(json.dumps(data, indent = 2))
- def findProject(self, path):
- if path in Core.config.projects:
- self.project = Core.config.projects[path]
- return self.project
- self.project = None
- Logger.addError("Wrong url")
- return None
- def checkProject(self, data):
- if "project" in data:
- # gitlab project name
- prj = data["project"]["name"]
- elif "repository" in data:
- # gitlab or github project name
- prj = data["repository"]["name"]
- elif "build" in data:
- # codeship project name
- prj = data["build"]["project_name"]
- else:
- prj = "Unknown"
- if prj != self.project.name:
- Logger.addError("Wrong project. Asked for project {0} but we found {1}".format(prj, self.project.name))
- return False
- return True
- def isEnabled(self, object_kind):
- if object_kind in self.project.actions:
- return True
- return False
- def validate(self, path, data):
- self.project = self.findProject(path)
- if self.project is None:
- return False
- if self.checkProject(data) == False:
- return False
- return True
- def shortenBranch(self, branch):
- if len(branch) > 11 and branch[:11] == "refs/heads/":
- branch = branch[11:]
- return branch
- def isAllowBranch(self, branch, allow_list, deny_list):
- if branch in allow_list:
- return True
- if branch in deny_list:
- return False
- if "*" in allow_list:
- return True
- if "*" in deny_list:
- return False
- def processAction(self, path, data, headers):
- Logger.addLog("Request: " + path)
- Logger.addLog("Accesing project: " + self.project.name)
- service = self.project.options.service
- if service == "gitlab":
- self.processGitlabAction(path, data)
- elif service == "github":
- self.processGithubAction(path, data, headers["X-GitHub-Event"])
- elif service == "codeship":
- self.processCodeshipAction(path, data)
- else:
- Logger.addLog("Unknown service: " + service)
- def processCodeshipAction(self, path, data):
- if "build" not in data:
- Logger.addLog("Unknown json for CodeShip")
- return
- build = data["build"]
- branch = self.shortenBranch(build["branch"])
- channels = self.project.channels
- allow_branches = self.project.allow_branches
- deny_brancges = self.project.deny_branches
- if self.isAllowBranch(branch, allow_branches, deny_brancges) == False:
- return
- self.printInfo(build["build_url"])
- commit_id = build["commit_id"]
- commit_url = build["commit_url"]
- url = build["build_url"]
- build_id = build["build_id"]
- build_status = build["status"]
- ircHandler = CodeshipIrcHandler()
- if build_status == "error":
- for channel in channels:
- options = self.project.channels[channel]
- ircHandler.printBuildFailed(channel, self.project.name, branch, data, commit_id, commit_url, url, build_id)
- if len(options.build_fail_custom_message) > 0:
- ircHandler.printCustomMessage(channel, options.build_fail_custom_message)
- return
- elif build_status == "success":
- found = False
- for channel in channels:
- ircHandler.printBuildSuccess(channel, self.project.name, branch, data, commit_id, commit_url, url, build_id)
- found = True
- if found == True:
- return
- elif build_status == "waiting":
- pass
- else:
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- Logger.addLog("Skipping unsupported build status {0}.".format(build_status))
- return
- Logger.addLog("Unused build status {0}.".format(build_status))
- def processGitlabAction(self, path, data):
- object_kind = data["object_kind"]
- if self.isEnabled(object_kind) == False:
- Logger.addLog("Ignoring action: " + object_kind)
- return
- Logger.addLog("Accepting action: " + object_kind)
- channels = self.project.actions[object_kind]
- allow_branches = self.project.allow_branches
- deny_brancges = self.project.deny_branches
- if len(channels) == 0:
- Logger.addLog("no channels in action")
- return
- if object_kind == "push":
- branch = self.shortenBranch(data["ref"])
- if self.isAllowBranch(branch, allow_branches, deny_brancges) == True:
- self.push(data, channels, branch)
- elif object_kind == "tag_push":
- self.tag_push(data, channels)
- elif object_kind == "note":
- self.note(data, channels)
- elif object_kind == "build":
- branch = self.shortenBranch(data["ref"])
- if self.isAllowBranch(branch, allow_branches, deny_brancges) == True:
- self.build(data, channels, branch)
- elif object_kind == "pipeline":
- branch = self.shortenBranch(data["object_attributes"]["ref"])
- if self.isAllowBranch(branch, allow_branches, deny_brancges) == True:
- self.pipeline(data, channels, branch)
- elif object_kind == "merge_request":
- self.merge_request(data, channels)
- elif object_kind == "issue":
- self.issue(data, channels)
- else:
- Logger.addLog("Ignoring unsupported action: " + object_kind)
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- def processGithubAction(self, path, data, object_kind):
- if self.isEnabled(object_kind) == False:
- Logger.addLog("Ignoring github action: " + object_kind)
- return
- Logger.addLog("Accepting github action: " + object_kind)
- channels = self.project.actions[object_kind]
- allow_branches = self.project.allow_branches
- deny_brancges = self.project.deny_branches
- if len(channels) == 0:
- Logger.addLog("no channels in action")
- return
- if object_kind == "status":
- branches = data["branches"]
- for br in branches:
- branch = self.shortenBranch(br["name"])
- if self.isAllowBranch(branch, allow_branches, deny_brancges) == True:
- self.github_status(data, channels, branch)
- elif object_kind == "issues":
- self.github_issues(data, channels)
- elif object_kind == "issue_comment":
- self.github_issue_comment(data, channels)
- elif object_kind == "check_suite":
- branch = data["check_suite"]["head_branch"]
- if self.isAllowBranch(branch, allow_branches, deny_brancges) == True:
- self.github_check_suite(data, channels, branch)
- else:
- Logger.addLog("Ignoring unsupported github action: " + object_kind)
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- def splitMessageToParts(self, data):
- parts = eolSplit.split(data)
- lines = []
- for part in parts:
- if len(part) == 0:
- continue
- lines.append(part)
- return lines
- def push(self, data, channels, branch):
- self.printInfo(data["project"]["homepage"])
- commitsCount = data["total_commits_count"]
- commits = data["commits"]
- url = data["project"]["homepage"]
- ircHandler = IrcHandler()
- if len(commits) > 0:
- for channel in channels:
- options = self.project.channels[channel]
- ircHandler.printPushHeader(channel, self.project.name, branch, url, data, commits, commitsCount)
- if options.show_commits == False:
- continue
- # if first push and fix enabled
- if options.push_first_push_fix == True and data["before"] == "0000000000000000000000000000000000000000":
- commits_list = reversed(commits)
- else:
- commits_list = commits
- for commit in commits_list:
- messages = self.splitMessageToParts(commit["message"])
- if len(messages) == 0:
- continue
- ircHandler.printCommitAuthor(channel, self.project.name, data, commit, messages[0])
- if options.show_commit_messages == True:
- del messages[0]
- if options.max_message_lines_per_commit != 0:
- while len(messages) > options.max_message_lines_per_commit:
- del messages[len(messages) - 1]
- ircHandler.printCommitMessage(channel, messages)
- else:
- for channel in channels:
- ircHandler.printCreateBranch(channel, self.project.name, data, branch, url)
- def tag_push(self, data, channels):
- self.printInfo(data["project"]["homepage"])
- commits = data["commits"]
- tag = data["ref"]
- if len(tag) > 10 and tag[:10] == "refs/tags/":
- tag = tag[10:]
- url = data["project"]["homepage"]
- ircHandler = IrcHandler()
- for channel in channels:
- ircHandler.printPushTag(channel, self.project.name, data, commits, tag, url)
- def note(self, data, channels):
- self.printInfo(data["project"]["homepage"])
- attrs = data["object_attributes"]
- url = attrs["url"]
- if "commit" in data:
- commit = data["commit"]
- url = data["project"]["homepage"]
- iid = attrs["id"]
- ircHandler = IrcHandler()
- for channel in channels:
- ircHandler.printAddCommitNote(channel, self.project.name, data, commit, url, iid)
- elif "merge_request" in data:
- ircHandler = IrcHandler()
- merge_request = data["merge_request"]
- iid = merge_request["iid"]
- branch = merge_request["target_branch"]
- source = merge_request["source"]
- title = merge_request["title"]
- source_branch = merge_request["source_branch"]
- source_namespace = source["namespace"]
- for channel in channels:
- ircHandler.printAddMRNote(channel, self.project.name, branch, source_namespace, source_branch, data, url, iid)
- elif "issue" in data:
- ircHandler = IrcHandler()
- issue = data["issue"]
- iid = issue["iid"]
- title = issue["title"]
- for channel in channels:
- ircHandler.printAddIssueNote(channel, self.project.name, data, url, iid, title)
- elif "snippet" in data:
- ircHandler = IrcHandler()
- snippet = data["snippet"]
- iid = snippet["id"]
- title = snippet["title"]
- for channel in channels:
- ircHandler.printAddSnippetNote(channel, self.project.name, data, url, iid, title)
- else:
- Logger.addLog("Skipping unsupported note type");
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- def build(self, data, channels, branch):
- self.printInfo(data["repository"]["homepage"])
- commit = data["commit"]
- url = data["repository"]["homepage"]
- build_id = data["build_id"]
- build_status = data["build_status"]
- ircHandler = IrcHandler()
- build_stage = data["build_stage"]
- build_name = data["build_name"]
- build_allow_failure = data["build_allow_failure"]
- if build_status == "failed":
- options = self.project.options
- build_retry = options.build_retry
- if options.gitlab_api_token != "" and build_retry != "never" and build_retry != "":
- Core.apiHandler.retry_build(data["project_id"], build_id, data["sha"], options)
- for channel in channels:
- options = self.project.channels[channel]
- if len(options.last_build_fail_stage) > 0 and data["build_stage"] == options.last_build_fail_stage:
- ircHandler.printStageFailed(channel, self.project.name, branch, data, commit, url, build_id)
- if options.show_build_failures_for_allowed_to_fail == True and data["build_allow_failure"] == True:
- ircHandler.printBuildFailed(channel, self.project.name, branch, data, commit, url, build_id, build_stage, build_name)
- if len(options.build_fail_custom_message) > 0:
- ircHandler.printCustomMessage(channel, options.build_fail_custom_message)
- continue # for prevent double messages
- if options.show_build_failures == False:
- continue
- if options.build_accept_allow_failure == False or build_allow_failure == False:
- ircHandler.printBuildFailed(channel, self.project.name, branch, data, commit, url, build_id, build_stage, build_name)
- if len(options.build_fail_custom_message) > 0:
- ircHandler.printCustomMessage(channel, options.build_fail_custom_message)
- return
- elif build_status == "success":
- found = False
- for channel in channels:
- options = self.project.channels[channel]
- if len(options.last_build_fail_stage) > 0 and data["build_stage"] == options.last_build_fail_stage:
- ircHandler.printStageFailed(channel, self.project.name, branch, data, commit, url, build_id)
- found = True
- if len(options.last_build_stage) > 0 and data["build_stage"] == options.last_build_stage:
- ircHandler.printBuildSuccess(channel, self.project.name, branch, data, commit, url, build_id)
- found = True
- if options.show_build_success_for_allowed_to_fail == True and data["build_allow_failure"] == True:
- ircHandler.printBuildSuccess(channel, self.project.name, branch, data, commit, url, build_id)
- found = True
- if found == True:
- return
- elif build_status == "pending":
- pass
- elif build_status == "running":
- pass
- elif build_status == "canceled":
- pass
- else:
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- Logger.addLog("Skipping unsupported build status {0}.".format(build_status))
- return
- Logger.addLog("Unused build status {0}.".format(build_status))
- def pipeline(self, data, channels, branch):
- self.printInfo(data["project"]["web_url"])
- commit = data["commit"]
- url = data["project"]["web_url"]
- pipeline_status = data["object_attributes"]["status"]
- ircHandler = IrcHandler()
- pipeline_id = data["object_attributes"]["id"]
- if pipeline_status == "failed":
- if self.isPipelineComplete(data) == True:
- for channel in channels:
- options = self.project.channels[channel]
- if options.show_pipeline_failures == False:
- continue
- ircHandler.printPipelineFailed(channel, self.project.name, branch, data, commit, url, pipeline_id)
- if len(options.pipeline_fail_custom_message) > 0:
- ircHandler.printCustomMessage(channel, options.pipeline_fail_custom_message)
- return
- elif pipeline_status == "success":
- if self.isPipelineComplete(data) == True:
- for channel in channels:
- options = self.project.channels[channel]
- if options.show_pipeline_success == False:
- continue
- ircHandler.printPipelineSuccess(channel, self.project.name, branch, data, commit, url, pipeline_id)
- return
- elif pipeline_status == "pending":
- pass
- elif pipeline_status == "running":
- pass
- elif pipeline_status == "canceled":
- pass
- else:
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- Logger.addLog("Skipping unsupported pipeline status {0}.".format(pipeline_status))
- return
- Logger.addLog("Unused pipeline status {0}.".format(pipeline_status))
- def merge_request(self, data, channels):
- self.printInfo(data["project"]["homepage"])
- ircHandler = IrcHandler()
- attrs = data["object_attributes"]
- url = attrs["url"]
- action = attrs["action"]
- iid = attrs["iid"]
- branch = attrs["target_branch"]
- source = attrs["source"]
- title = attrs["title"]
- source_branch = attrs["source_branch"]
- source_namespace = source["namespace"]
- if action == "open":
- for channel in channels:
- ircHandler.printMROpened(channel, self.project.name, branch, source_branch, source_namespace, data, url, iid, title)
- elif action == "close":
- for channel in channels:
- ircHandler.printMRClosed(channel, self.project.name, branch, source_branch, source_namespace, data, url, iid, title)
- elif action == "reopen":
- for channel in channels:
- ircHandler.printMRReopened(channel, self.project.name, branch, source_branch, source_namespace, data, url, iid, title)
- elif action == "update":
- for channel in channels:
- ircHandler.printMRUpdated(channel, self.project.name, branch, source_branch, source_namespace, data, url, iid, title)
- elif action == "merge":
- for channel in channels:
- ircHandler.printMRMerged(channel, self.project.name, branch, source_branch, source_namespace, data, url, iid, title)
- else:
- Logger.addLog("Skipping unsupported action {0}.".format(action))
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- def issue(self, data, channels):
- self.printInfo(data["project"]["homepage"])
- ircHandler = IrcHandler()
- attrs = data["object_attributes"]
- url = attrs["url"]
- action = attrs["action"]
- iid = attrs["iid"]
- title = attrs["title"]
- if action == "open":
- for channel in channels:
- ircHandler.printIssueOpened(channel, self.project.name, data, url, iid, title)
- elif action == "close":
- for channel in channels:
- ircHandler.printIssueClosed(channel, self.project.name, data, url, iid, title)
- elif action == "reopen":
- for channel in channels:
- ircHandler.printIssueReopened(channel, self.project.name, data, url, iid, title)
- elif action == "update":
- for channel in channels:
- ircHandler.printIssueUpdated(channel, self.project.name, data, url, iid, title)
- else:
- Logger.addLog("Skipping unsupported action {0}.".format(action))
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- def github_status(self, data, channels, branch):
- self.printInfo(data["repository"]["html_url"])
- ircHandler = GithubIrcHandler()
- action = data["state"]
- description = data["description"]
- commit = data["commit"]["sha"]
- url = data["target_url"]
- if action == "pending":
- pass
- elif action == "success":
- for channel in channels:
- ircHandler.printBuildSuccess(channel, self.project.name, branch, data, commit, url, description)
- elif action == "failure" or action == "error":
- for channel in channels:
- options = self.project.channels[channel]
- if options.show_build_failures == False:
- continue
- ircHandler.printBuildFailed(channel, self.project.name, branch, data, commit, url, description)
- if len(options.build_fail_custom_message) > 0:
- ircHandler.printCustomMessage(channel, options.build_fail_custom_message)
- else:
- Logger.addLog("Skipping unsupported github status action {0}.".format(action))
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- def github_check_suite(self, data, channels, branch):
- self.printInfo(data["repository"]["html_url"])
- ircHandler = GithubIrcHandler()
- action = data["action"]
- conclusion = data["check_suite"]["conclusion"]
- status = data["check_suite"]["status"]
- commit = data["check_suite"]["head_sha"]
- # tested only on azure pipelines, and here actual url missing
- url = data["repository"]["html_url"] + "/commits/" + commit
- # tested only on azure pipelines, and here actual description missing
- description = data["check_suite"]["app"]["name"]
- if action == "completed":
- if conclusion == "failure" or conclusion == "cancelled" or conclusion == "timed_out":
- description = description + " build " + conclusion.replace("_", " ")
- for channel in channels:
- options = self.project.channels[channel]
- if options.show_build_failures == False:
- continue
- ircHandler.printBuildFailed(channel, self.project.name, branch, data, commit, url, description)
- if len(options.build_fail_custom_message) > 0:
- ircHandler.printCustomMessage(channel, options.build_fail_custom_message)
- elif conclusion == "success" or conclusion == "action_required":
- description = description + " build " + conclusion.replace("_", " ")
- for channel in channels:
- ircHandler.printBuildSuccess(channel, self.project.name, branch, data, commit, url, description)
- else:
- Logger.addLog("Skipping unsupported github check_suite action {0} / {1} / {2}.".format(action, status, conclusion))
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- else:
- Logger.addLog("Skipping unsupported github check_suite action {0} / {1} / {2}.".format(action, status, conclusion))
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- def github_issues(self, data, channels):
- self.printInfo(data["repository"]["html_url"])
- ircHandler = GithubIrcHandler()
- action = data["action"]
- url = data["issue"]["html_url"]
- description = data["issue"]["title"]
- iid = data["issue"]["number"]
- if action == "opened":
- for channel in channels:
- ircHandler.printIssuesOpened(channel, self.project.name, data, url, iid, description)
- elif action == "edited":
- for channel in channels:
- ircHandler.printIssuesEdited(channel, self.project.name, data, url, iid, description)
- elif action == "closed":
- for channel in channels:
- ircHandler.printIssuesClosed(channel, self.project.name, data, url, iid, description)
- elif action == "reopened":
- for channel in channels:
- ircHandler.printIssuesReopened(channel, self.project.name, data, url, iid, description)
- else:
- Logger.addLog("Skipping unsupported github issues action {0}.".format(action))
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- def github_issue_comment(self, data, channels):
- self.printInfo(data["repository"]["html_url"])
- ircHandler = GithubIrcHandler()
- action = data["action"]
- url = data["issue"]["html_url"]
- description = data["issue"]["title"]
- iid = data["issue"]["number"]
- if action == "created":
- for channel in channels:
- ircHandler.printIssueCommentCreated(channel, self.project.name, data, url, iid, description)
- elif action == "edited":
- for channel in channels:
- ircHandler.printIssueCommentEdited(channel, self.project.name, data, url, iid, description)
- elif action == "deleted":
- for channel in channels:
- ircHandler.printIssueCommentDeleted(channel, self.project.name, data, url, iid, description)
- else:
- Logger.addLog("Skipping unsupported github issue_comment action {0}.".format(action))
- if Core.config.global_options.http_save_json == "unsupported":
- self.saveJson(data)
- def isPipelineComplete(self, data):
- for build in data["builds"]:
- status = build["status"]
- if status != "success" and status != "failed" and status != "canceled" and status != "skipped":
- return False
- return True
|