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