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 26d9143750SCleber Rosadef get_local_branch_commit(branch='staging'): 27c02b2eacSCleber Rosa """ 28c02b2eacSCleber Rosa Returns the commit sha1 for the *local* branch named "staging" 29c02b2eacSCleber Rosa """ 30d9143750SCleber 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() 36d9143750SCleber Rosa if result == branch: 37d9143750SCleber Rosa raise ValueError("There's no local branch named '%s'" % branch) 38c02b2eacSCleber Rosa if len(result) != 40: 39d9143750SCleber 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): 72*6dfcbff8SCleber Rosa msg = ("Timeout (-t/--timeout) of %i seconds reached, " 73*6dfcbff8SCleber Rosa "won't wait any longer for the pipeline to complete") 74*6dfcbff8SCleber Rosa msg %= timeout 75*6dfcbff8SCleber Rosa print(msg) 76c02b2eacSCleber Rosa return False 77c02b2eacSCleber Rosa 78c02b2eacSCleber Rosa status = get_pipeline_status(project_id, commit_sha) 79c02b2eacSCleber Rosa if status['status'] == 'running': 80c02b2eacSCleber Rosa time.sleep(interval) 81c02b2eacSCleber Rosa print('running...') 82c02b2eacSCleber Rosa continue 83c02b2eacSCleber Rosa 84c02b2eacSCleber Rosa if status['status'] == 'success': 85c02b2eacSCleber Rosa return True 86c02b2eacSCleber Rosa 87c02b2eacSCleber Rosa msg = "Pipeline failed, check: %s" % status['web_url'] 88c02b2eacSCleber Rosa print(msg) 89c02b2eacSCleber Rosa return False 90c02b2eacSCleber Rosa 91c02b2eacSCleber Rosa 92c02b2eacSCleber Rosadef main(): 93c02b2eacSCleber Rosa """ 94c02b2eacSCleber Rosa Script entry point 95c02b2eacSCleber Rosa """ 96c02b2eacSCleber Rosa parser = argparse.ArgumentParser( 97c02b2eacSCleber Rosa prog='pipeline-status', 98c02b2eacSCleber Rosa description='check or wait on a pipeline status') 99c02b2eacSCleber Rosa 100c02b2eacSCleber Rosa parser.add_argument('-t', '--timeout', type=int, default=7200, 101c02b2eacSCleber Rosa help=('Amount of time (in seconds) to wait for the ' 102c02b2eacSCleber Rosa 'pipeline to complete. Defaults to ' 103c02b2eacSCleber Rosa '%(default)s')) 104c02b2eacSCleber Rosa parser.add_argument('-i', '--interval', type=int, default=60, 105c02b2eacSCleber Rosa help=('Amount of time (in seconds) to wait between ' 106c02b2eacSCleber Rosa 'checks of the pipeline status. Defaults ' 107c02b2eacSCleber Rosa 'to %(default)s')) 108c02b2eacSCleber Rosa parser.add_argument('-w', '--wait', action='store_true', default=False, 109c02b2eacSCleber Rosa help=('Wether to wait, instead of checking only once ' 110c02b2eacSCleber Rosa 'the status of a pipeline')) 111c02b2eacSCleber Rosa parser.add_argument('-p', '--project-id', type=int, default=11167699, 112c02b2eacSCleber Rosa help=('The GitLab project ID. Defaults to the project ' 113c02b2eacSCleber Rosa 'for https://gitlab.com/qemu-project/qemu, that ' 114c02b2eacSCleber Rosa 'is, "%(default)s"')) 115c02b2eacSCleber Rosa try: 116d9143750SCleber Rosa default_commit = get_local_branch_commit() 117c02b2eacSCleber Rosa commit_required = False 118c02b2eacSCleber Rosa except ValueError: 119c02b2eacSCleber Rosa default_commit = '' 120c02b2eacSCleber Rosa commit_required = True 121c02b2eacSCleber Rosa parser.add_argument('-c', '--commit', required=commit_required, 122c02b2eacSCleber Rosa default=default_commit, 123c02b2eacSCleber Rosa help=('Look for a pipeline associated with the given ' 124c02b2eacSCleber Rosa 'commit. If one is not explicitly given, the ' 125c02b2eacSCleber Rosa 'commit associated with the local branch named ' 126c02b2eacSCleber Rosa '"staging" is used. Default: %(default)s')) 127c02b2eacSCleber Rosa parser.add_argument('--verbose', action='store_true', default=False, 128c02b2eacSCleber Rosa help=('A minimal verbosity level that prints the ' 129c02b2eacSCleber Rosa 'overall result of the check/wait')) 130c02b2eacSCleber Rosa 131c02b2eacSCleber Rosa args = parser.parse_args() 132c02b2eacSCleber Rosa 133c02b2eacSCleber Rosa try: 134c02b2eacSCleber Rosa if args.wait: 135c02b2eacSCleber Rosa success = wait_on_pipeline_success( 136c02b2eacSCleber Rosa args.timeout, 137c02b2eacSCleber Rosa args.interval, 138c02b2eacSCleber Rosa args.project_id, 139c02b2eacSCleber Rosa args.commit) 140c02b2eacSCleber Rosa else: 141c02b2eacSCleber Rosa status = get_pipeline_status(args.project_id, 142c02b2eacSCleber Rosa args.commit) 143c02b2eacSCleber Rosa success = status['status'] == 'success' 144c02b2eacSCleber Rosa except Exception as error: # pylint: disable=W0703 145c02b2eacSCleber Rosa success = False 146c02b2eacSCleber Rosa if args.verbose: 147c02b2eacSCleber Rosa print("ERROR: %s" % error.args[0]) 148c02b2eacSCleber Rosa 149c02b2eacSCleber Rosa if success: 150c02b2eacSCleber Rosa if args.verbose: 151c02b2eacSCleber Rosa print('success') 152c02b2eacSCleber Rosa sys.exit(0) 153c02b2eacSCleber Rosa else: 154c02b2eacSCleber Rosa if args.verbose: 155c02b2eacSCleber Rosa print('failure') 156c02b2eacSCleber Rosa sys.exit(1) 157c02b2eacSCleber Rosa 158c02b2eacSCleber Rosa 159c02b2eacSCleber Rosaif __name__ == '__main__': 160c02b2eacSCleber Rosa main() 161