xref: /qemu/scripts/ci/gitlab-pipeline-status (revision 6dfcbff8bf7ca8c52a5ee2d351ec9c291e22fd5e)
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