1c02b2eacSCleber Rosa#!/usr/bin/env python3 2c02b2eacSCleber Rosa# 3c02b2eacSCleber Rosa# Copyright (c) 2019-2020 Red Hat, Inc. 4c02b2eacSCleber Rosa# 5c02b2eacSCleber Rosa# Author: 6c02b2eacSCleber Rosa# Cleber Rosa <crosa@redhat.com> 7c02b2eacSCleber Rosa# 8c02b2eacSCleber Rosa# This work is licensed under the terms of the GNU GPL, version 2 or 9c02b2eacSCleber Rosa# later. See the COPYING file in the top-level directory. 10c02b2eacSCleber Rosa 11c02b2eacSCleber Rosa""" 12c02b2eacSCleber RosaChecks the GitLab pipeline status for a given commit ID 13c02b2eacSCleber Rosa""" 14c02b2eacSCleber Rosa 15c02b2eacSCleber Rosa# pylint: disable=C0103 16c02b2eacSCleber Rosa 17c02b2eacSCleber Rosaimport argparse 18c02b2eacSCleber Rosaimport http.client 19c02b2eacSCleber Rosaimport json 20c02b2eacSCleber Rosaimport os 21c02b2eacSCleber Rosaimport subprocess 22c02b2eacSCleber Rosaimport time 23c02b2eacSCleber Rosaimport sys 24c02b2eacSCleber Rosa 25c02b2eacSCleber Rosa 26*d9143750SCleber Rosadef get_local_branch_commit(branch='staging'): 27c02b2eacSCleber Rosa """ 28c02b2eacSCleber Rosa Returns the commit sha1 for the *local* branch named "staging" 29c02b2eacSCleber Rosa """ 30*d9143750SCleber Rosa result = subprocess.run(['git', 'rev-parse', branch], 31c02b2eacSCleber Rosa stdin=subprocess.DEVNULL, 32c02b2eacSCleber Rosa stdout=subprocess.PIPE, 33c02b2eacSCleber Rosa stderr=subprocess.DEVNULL, 34c02b2eacSCleber Rosa cwd=os.path.dirname(__file__), 35c02b2eacSCleber Rosa universal_newlines=True).stdout.strip() 36*d9143750SCleber Rosa if result == branch: 37*d9143750SCleber Rosa raise ValueError("There's no local branch named '%s'" % branch) 38c02b2eacSCleber Rosa if len(result) != 40: 39*d9143750SCleber Rosa raise ValueError("Branch '%s' HEAD doesn't look like a sha1" % branch) 40c02b2eacSCleber Rosa return result 41c02b2eacSCleber Rosa 42c02b2eacSCleber Rosa 43c02b2eacSCleber Rosadef get_pipeline_status(project_id, commit_sha1): 44c02b2eacSCleber Rosa """ 45c02b2eacSCleber Rosa Returns the JSON content of the pipeline status API response 46c02b2eacSCleber Rosa """ 47c02b2eacSCleber Rosa url = '/api/v4/projects/{}/pipelines?sha={}'.format(project_id, 48c02b2eacSCleber Rosa commit_sha1) 49c02b2eacSCleber Rosa connection = http.client.HTTPSConnection('gitlab.com') 50c02b2eacSCleber Rosa connection.request('GET', url=url) 51c02b2eacSCleber Rosa response = connection.getresponse() 52c02b2eacSCleber Rosa if response.code != http.HTTPStatus.OK: 53c02b2eacSCleber Rosa raise ValueError("Failed to receive a successful response") 54c02b2eacSCleber Rosa json_response = json.loads(response.read()) 55c02b2eacSCleber Rosa 56c02b2eacSCleber Rosa # As far as I can tell, there should be only one pipeline for the same 57c02b2eacSCleber Rosa # project + commit. If this assumption is false, we can add further 58c02b2eacSCleber Rosa # filters to the url, such as username, and order_by. 59c02b2eacSCleber Rosa if not json_response: 60c02b2eacSCleber Rosa raise ValueError("No pipeline found") 61c02b2eacSCleber Rosa return json_response[0] 62c02b2eacSCleber Rosa 63c02b2eacSCleber Rosa 64c02b2eacSCleber Rosadef wait_on_pipeline_success(timeout, interval, 65c02b2eacSCleber Rosa project_id, commit_sha): 66c02b2eacSCleber Rosa """ 67c02b2eacSCleber Rosa Waits for the pipeline to finish within the given timeout 68c02b2eacSCleber Rosa """ 69c02b2eacSCleber Rosa start = time.time() 70c02b2eacSCleber Rosa while True: 71c02b2eacSCleber Rosa if time.time() >= (start + timeout): 72c02b2eacSCleber Rosa print("Waiting on the pipeline timed out") 73c02b2eacSCleber Rosa return False 74c02b2eacSCleber Rosa 75c02b2eacSCleber Rosa status = get_pipeline_status(project_id, commit_sha) 76c02b2eacSCleber Rosa if status['status'] == 'running': 77c02b2eacSCleber Rosa time.sleep(interval) 78c02b2eacSCleber Rosa print('running...') 79c02b2eacSCleber Rosa continue 80c02b2eacSCleber Rosa 81c02b2eacSCleber Rosa if status['status'] == 'success': 82c02b2eacSCleber Rosa return True 83c02b2eacSCleber Rosa 84c02b2eacSCleber Rosa msg = "Pipeline failed, check: %s" % status['web_url'] 85c02b2eacSCleber Rosa print(msg) 86c02b2eacSCleber Rosa return False 87c02b2eacSCleber Rosa 88c02b2eacSCleber Rosa 89c02b2eacSCleber Rosadef main(): 90c02b2eacSCleber Rosa """ 91c02b2eacSCleber Rosa Script entry point 92c02b2eacSCleber Rosa """ 93c02b2eacSCleber Rosa parser = argparse.ArgumentParser( 94c02b2eacSCleber Rosa prog='pipeline-status', 95c02b2eacSCleber Rosa description='check or wait on a pipeline status') 96c02b2eacSCleber Rosa 97c02b2eacSCleber Rosa parser.add_argument('-t', '--timeout', type=int, default=7200, 98c02b2eacSCleber Rosa help=('Amount of time (in seconds) to wait for the ' 99c02b2eacSCleber Rosa 'pipeline to complete. Defaults to ' 100c02b2eacSCleber Rosa '%(default)s')) 101c02b2eacSCleber Rosa parser.add_argument('-i', '--interval', type=int, default=60, 102c02b2eacSCleber Rosa help=('Amount of time (in seconds) to wait between ' 103c02b2eacSCleber Rosa 'checks of the pipeline status. Defaults ' 104c02b2eacSCleber Rosa 'to %(default)s')) 105c02b2eacSCleber Rosa parser.add_argument('-w', '--wait', action='store_true', default=False, 106c02b2eacSCleber Rosa help=('Wether to wait, instead of checking only once ' 107c02b2eacSCleber Rosa 'the status of a pipeline')) 108c02b2eacSCleber Rosa parser.add_argument('-p', '--project-id', type=int, default=11167699, 109c02b2eacSCleber Rosa help=('The GitLab project ID. Defaults to the project ' 110c02b2eacSCleber Rosa 'for https://gitlab.com/qemu-project/qemu, that ' 111c02b2eacSCleber Rosa 'is, "%(default)s"')) 112c02b2eacSCleber Rosa try: 113*d9143750SCleber Rosa default_commit = get_local_branch_commit() 114c02b2eacSCleber Rosa commit_required = False 115c02b2eacSCleber Rosa except ValueError: 116c02b2eacSCleber Rosa default_commit = '' 117c02b2eacSCleber Rosa commit_required = True 118c02b2eacSCleber Rosa parser.add_argument('-c', '--commit', required=commit_required, 119c02b2eacSCleber Rosa default=default_commit, 120c02b2eacSCleber Rosa help=('Look for a pipeline associated with the given ' 121c02b2eacSCleber Rosa 'commit. If one is not explicitly given, the ' 122c02b2eacSCleber Rosa 'commit associated with the local branch named ' 123c02b2eacSCleber Rosa '"staging" is used. Default: %(default)s')) 124c02b2eacSCleber Rosa parser.add_argument('--verbose', action='store_true', default=False, 125c02b2eacSCleber Rosa help=('A minimal verbosity level that prints the ' 126c02b2eacSCleber Rosa 'overall result of the check/wait')) 127c02b2eacSCleber Rosa 128c02b2eacSCleber Rosa args = parser.parse_args() 129c02b2eacSCleber Rosa 130c02b2eacSCleber Rosa try: 131c02b2eacSCleber Rosa if args.wait: 132c02b2eacSCleber Rosa success = wait_on_pipeline_success( 133c02b2eacSCleber Rosa args.timeout, 134c02b2eacSCleber Rosa args.interval, 135c02b2eacSCleber Rosa args.project_id, 136c02b2eacSCleber Rosa args.commit) 137c02b2eacSCleber Rosa else: 138c02b2eacSCleber Rosa status = get_pipeline_status(args.project_id, 139c02b2eacSCleber Rosa args.commit) 140c02b2eacSCleber Rosa success = status['status'] == 'success' 141c02b2eacSCleber Rosa except Exception as error: # pylint: disable=W0703 142c02b2eacSCleber Rosa success = False 143c02b2eacSCleber Rosa if args.verbose: 144c02b2eacSCleber Rosa print("ERROR: %s" % error.args[0]) 145c02b2eacSCleber Rosa 146c02b2eacSCleber Rosa if success: 147c02b2eacSCleber Rosa if args.verbose: 148c02b2eacSCleber Rosa print('success') 149c02b2eacSCleber Rosa sys.exit(0) 150c02b2eacSCleber Rosa else: 151c02b2eacSCleber Rosa if args.verbose: 152c02b2eacSCleber Rosa print('failure') 153c02b2eacSCleber Rosa sys.exit(1) 154c02b2eacSCleber Rosa 155c02b2eacSCleber Rosa 156c02b2eacSCleber Rosaif __name__ == '__main__': 157c02b2eacSCleber Rosa main() 158