| #!/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 successful 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=('Whether 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() |