diff options
Diffstat (limited to 'scripts/ci/gitlab-pipeline-status')
-rwxr-xr-x | scripts/ci/gitlab-pipeline-status | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/scripts/ci/gitlab-pipeline-status b/scripts/ci/gitlab-pipeline-status new file mode 100755 index 000000000..924db327f --- /dev/null +++ b/scripts/ci/gitlab-pipeline-status @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2019-2020 Red Hat, Inc. +# +# Author: +# Cleber Rosa <crosa@redhat.com> +# +# This work is licensed under the terms of the GNU GPL, version 2 or +# later. See the COPYING file in the top-level directory. + +""" +Checks the GitLab pipeline status for a given commit ID +""" + +# pylint: disable=C0103 + +import argparse +import http.client +import json +import os +import subprocess +import time +import sys + + +class CommunicationFailure(Exception): + """Failed to communicate to gitlab.com APIs.""" + + +class NoPipelineFound(Exception): + """Communication is successfull but pipeline is not found.""" + + +def get_local_branch_commit(branch): + """ + Returns the commit sha1 for the *local* branch named "staging" + """ + result = subprocess.run(['git', 'rev-parse', branch], + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + cwd=os.path.dirname(__file__), + universal_newlines=True).stdout.strip() + if result == branch: + raise ValueError("There's no local branch named '%s'" % branch) + if len(result) != 40: + raise ValueError("Branch '%s' HEAD doesn't look like a sha1" % branch) + return result + + +def get_json_http_response(url): + """ + Returns the JSON content of an HTTP GET request to gitlab.com + """ + connection = http.client.HTTPSConnection('gitlab.com') + connection.request('GET', url=url) + response = connection.getresponse() + if response.code != http.HTTPStatus.OK: + msg = "Received unsuccessful response: %s (%s)" % (response.code, + response.reason) + raise CommunicationFailure(msg) + return json.loads(response.read()) + + +def get_pipeline_status(project_id, commit_sha1): + """ + Returns the JSON content of the pipeline status API response + """ + url = '/api/v4/projects/{}/pipelines?sha={}'.format(project_id, + commit_sha1) + json_response = get_json_http_response(url) + + # As far as I can tell, there should be only one pipeline for the same + # project + commit. If this assumption is false, we can add further + # filters to the url, such as username, and order_by. + if not json_response: + msg = "No pipeline found for project %s and commit %s" % (project_id, + commit_sha1) + raise NoPipelineFound(msg) + return json_response[0] + + +def wait_on_pipeline_success(timeout, interval, + project_id, commit_sha): + """ + Waits for the pipeline to finish within the given timeout + """ + start = time.time() + while True: + if time.time() >= (start + timeout): + msg = ("Timeout (-t/--timeout) of %i seconds reached, " + "won't wait any longer for the pipeline to complete") + msg %= timeout + print(msg) + return False + + try: + status = get_pipeline_status(project_id, commit_sha) + except NoPipelineFound: + print('Pipeline has not been found, it may not have been created yet.') + time.sleep(1) + continue + + pipeline_status = status['status'] + status_to_wait = ('created', 'waiting_for_resource', 'preparing', + 'pending', 'running') + if pipeline_status in status_to_wait: + print('%s...' % pipeline_status) + time.sleep(interval) + continue + + if pipeline_status == 'success': + return True + + msg = "Pipeline failed, check: %s" % status['web_url'] + print(msg) + return False + + +def create_parser(): + parser = argparse.ArgumentParser( + prog='pipeline-status', + description='check or wait on a pipeline status') + + parser.add_argument('-t', '--timeout', type=int, default=7200, + help=('Amount of time (in seconds) to wait for the ' + 'pipeline to complete. Defaults to ' + '%(default)s')) + parser.add_argument('-i', '--interval', type=int, default=60, + help=('Amount of time (in seconds) to wait between ' + 'checks of the pipeline status. Defaults ' + 'to %(default)s')) + parser.add_argument('-w', '--wait', action='store_true', default=False, + help=('Wether to wait, instead of checking only once ' + 'the status of a pipeline')) + parser.add_argument('-p', '--project-id', type=int, default=11167699, + help=('The GitLab project ID. Defaults to the project ' + 'for https://gitlab.com/qemu-project/qemu, that ' + 'is, "%(default)s"')) + parser.add_argument('-b', '--branch', type=str, default="staging", + help=('Specify the branch to check. ' + 'Use HEAD for your current branch. ' + 'Otherwise looks at "%(default)s"')) + parser.add_argument('-c', '--commit', + default=None, + help=('Look for a pipeline associated with the given ' + 'commit. If one is not explicitly given, the ' + 'commit associated with the default branch ' + 'is used.')) + parser.add_argument('--verbose', action='store_true', default=False, + help=('A minimal verbosity level that prints the ' + 'overall result of the check/wait')) + return parser + +def main(): + """ + Script entry point + """ + parser = create_parser() + args = parser.parse_args() + + if not args.commit: + args.commit = get_local_branch_commit(args.branch) + + success = False + try: + if args.wait: + success = wait_on_pipeline_success( + args.timeout, + args.interval, + args.project_id, + args.commit) + else: + status = get_pipeline_status(args.project_id, + args.commit) + success = status['status'] == 'success' + except Exception as error: # pylint: disable=W0703 + if args.verbose: + print("ERROR: %s" % error.args[0]) + except KeyboardInterrupt: + if args.verbose: + print("Exiting on user's request") + + if success: + if args.verbose: + print('success') + sys.exit(0) + else: + if args.verbose: + print('failure') + sys.exit(1) + + +if __name__ == '__main__': + main() |