xref: /qemu/scripts/ci/gitlab-pipeline-status (revision ea8bf1e514d2f442dd1a008794eb1563e2ee1c48)
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
26176498abSCleber Rosaclass CommunicationFailure(Exception):
27176498abSCleber Rosa    """Failed to communicate to gitlab.com APIs."""
28176498abSCleber Rosa
29176498abSCleber Rosa
30176498abSCleber Rosaclass NoPipelineFound(Exception):
31176498abSCleber Rosa    """Communication is successfull but pipeline is not found."""
32176498abSCleber Rosa
33176498abSCleber Rosa
34d9143750SCleber Rosadef get_local_branch_commit(branch='staging'):
35c02b2eacSCleber Rosa    """
36c02b2eacSCleber Rosa    Returns the commit sha1 for the *local* branch named "staging"
37c02b2eacSCleber Rosa    """
38d9143750SCleber Rosa    result = subprocess.run(['git', 'rev-parse', branch],
39c02b2eacSCleber Rosa                            stdin=subprocess.DEVNULL,
40c02b2eacSCleber Rosa                            stdout=subprocess.PIPE,
41c02b2eacSCleber Rosa                            stderr=subprocess.DEVNULL,
42c02b2eacSCleber Rosa                            cwd=os.path.dirname(__file__),
43c02b2eacSCleber Rosa                            universal_newlines=True).stdout.strip()
44d9143750SCleber Rosa    if result == branch:
45d9143750SCleber Rosa        raise ValueError("There's no local branch named '%s'" % branch)
46c02b2eacSCleber Rosa    if len(result) != 40:
47d9143750SCleber Rosa        raise ValueError("Branch '%s' HEAD doesn't look like a sha1" % branch)
48c02b2eacSCleber Rosa    return result
49c02b2eacSCleber Rosa
50c02b2eacSCleber Rosa
51c02b2eacSCleber Rosadef get_pipeline_status(project_id, commit_sha1):
52c02b2eacSCleber Rosa    """
53c02b2eacSCleber Rosa    Returns the JSON content of the pipeline status API response
54c02b2eacSCleber Rosa    """
55c02b2eacSCleber Rosa    url = '/api/v4/projects/{}/pipelines?sha={}'.format(project_id,
56c02b2eacSCleber Rosa                                                        commit_sha1)
57c02b2eacSCleber Rosa    connection = http.client.HTTPSConnection('gitlab.com')
58c02b2eacSCleber Rosa    connection.request('GET', url=url)
59c02b2eacSCleber Rosa    response = connection.getresponse()
60c02b2eacSCleber Rosa    if response.code != http.HTTPStatus.OK:
61176498abSCleber Rosa        raise CommunicationFailure("Failed to receive a successful response")
62c02b2eacSCleber Rosa    json_response = json.loads(response.read())
63c02b2eacSCleber Rosa
64c02b2eacSCleber Rosa    # As far as I can tell, there should be only one pipeline for the same
65c02b2eacSCleber Rosa    # project + commit. If this assumption is false, we can add further
66c02b2eacSCleber Rosa    # filters to the url, such as username, and order_by.
67c02b2eacSCleber Rosa    if not json_response:
68176498abSCleber Rosa        raise NoPipelineFound("No pipeline found")
69c02b2eacSCleber Rosa    return json_response[0]
70c02b2eacSCleber Rosa
71c02b2eacSCleber Rosa
72c02b2eacSCleber Rosadef wait_on_pipeline_success(timeout, interval,
73c02b2eacSCleber Rosa                             project_id, commit_sha):
74c02b2eacSCleber Rosa    """
75c02b2eacSCleber Rosa    Waits for the pipeline to finish within the given timeout
76c02b2eacSCleber Rosa    """
77c02b2eacSCleber Rosa    start = time.time()
78c02b2eacSCleber Rosa    while True:
79c02b2eacSCleber Rosa        if time.time() >= (start + timeout):
806dfcbff8SCleber Rosa            msg = ("Timeout (-t/--timeout) of %i seconds reached, "
816dfcbff8SCleber Rosa                   "won't wait any longer for the pipeline to complete")
826dfcbff8SCleber Rosa            msg %= timeout
836dfcbff8SCleber Rosa            print(msg)
84c02b2eacSCleber Rosa            return False
85c02b2eacSCleber Rosa
86*ea8bf1e5SCleber Rosa        try:
87c02b2eacSCleber Rosa            status = get_pipeline_status(project_id, commit_sha)
88*ea8bf1e5SCleber Rosa        except NoPipelineFound:
89*ea8bf1e5SCleber Rosa            print('Pipeline has not been found, it may not have been created yet.')
90*ea8bf1e5SCleber Rosa            time.sleep(1)
91*ea8bf1e5SCleber Rosa            continue
92*ea8bf1e5SCleber Rosa
93*ea8bf1e5SCleber Rosa        pipeline_status = status['status']
94*ea8bf1e5SCleber Rosa        status_to_wait = ('created', 'waiting_for_resource', 'preparing',
95*ea8bf1e5SCleber Rosa                          'pending', 'running')
96*ea8bf1e5SCleber Rosa        if pipeline_status in status_to_wait:
97*ea8bf1e5SCleber Rosa            print('%s...' % pipeline_status)
98db5424dfSCleber Rosa            time.sleep(interval)
99c02b2eacSCleber Rosa            continue
100c02b2eacSCleber Rosa
101*ea8bf1e5SCleber Rosa        if pipeline_status == 'success':
102c02b2eacSCleber Rosa            return True
103c02b2eacSCleber Rosa
104c02b2eacSCleber Rosa        msg = "Pipeline failed, check: %s" % status['web_url']
105c02b2eacSCleber Rosa        print(msg)
106c02b2eacSCleber Rosa        return False
107c02b2eacSCleber Rosa
108c02b2eacSCleber Rosa
10991641d55SCleber Rosadef create_parser():
110c02b2eacSCleber Rosa    parser = argparse.ArgumentParser(
111c02b2eacSCleber Rosa        prog='pipeline-status',
112c02b2eacSCleber Rosa        description='check or wait on a pipeline status')
113c02b2eacSCleber Rosa
114c02b2eacSCleber Rosa    parser.add_argument('-t', '--timeout', type=int, default=7200,
115c02b2eacSCleber Rosa                        help=('Amount of time (in seconds) to wait for the '
116c02b2eacSCleber Rosa                              'pipeline to complete.  Defaults to '
117c02b2eacSCleber Rosa                              '%(default)s'))
118c02b2eacSCleber Rosa    parser.add_argument('-i', '--interval', type=int, default=60,
119c02b2eacSCleber Rosa                        help=('Amount of time (in seconds) to wait between '
120c02b2eacSCleber Rosa                              'checks of the pipeline status.  Defaults '
121c02b2eacSCleber Rosa                              'to %(default)s'))
122c02b2eacSCleber Rosa    parser.add_argument('-w', '--wait', action='store_true', default=False,
123c02b2eacSCleber Rosa                        help=('Wether to wait, instead of checking only once '
124c02b2eacSCleber Rosa                              'the status of a pipeline'))
125c02b2eacSCleber Rosa    parser.add_argument('-p', '--project-id', type=int, default=11167699,
126c02b2eacSCleber Rosa                        help=('The GitLab project ID. Defaults to the project '
127c02b2eacSCleber Rosa                              'for https://gitlab.com/qemu-project/qemu, that '
128c02b2eacSCleber Rosa                              'is, "%(default)s"'))
129c02b2eacSCleber Rosa    try:
130d9143750SCleber Rosa        default_commit = get_local_branch_commit()
131c02b2eacSCleber Rosa        commit_required = False
132c02b2eacSCleber Rosa    except ValueError:
133c02b2eacSCleber Rosa        default_commit = ''
134c02b2eacSCleber Rosa        commit_required = True
135c02b2eacSCleber Rosa    parser.add_argument('-c', '--commit', required=commit_required,
136c02b2eacSCleber Rosa                        default=default_commit,
137c02b2eacSCleber Rosa                        help=('Look for a pipeline associated with the given '
138c02b2eacSCleber Rosa                              'commit.  If one is not explicitly given, the '
139c02b2eacSCleber Rosa                              'commit associated with the local branch named '
140c02b2eacSCleber Rosa                              '"staging" is used.  Default: %(default)s'))
141c02b2eacSCleber Rosa    parser.add_argument('--verbose', action='store_true', default=False,
142c02b2eacSCleber Rosa                        help=('A minimal verbosity level that prints the '
143c02b2eacSCleber Rosa                              'overall result of the check/wait'))
14491641d55SCleber Rosa    return parser
145c02b2eacSCleber Rosa
14691641d55SCleber Rosadef main():
14791641d55SCleber Rosa    """
14891641d55SCleber Rosa    Script entry point
14991641d55SCleber Rosa    """
15091641d55SCleber Rosa    parser = create_parser()
151c02b2eacSCleber Rosa    args = parser.parse_args()
15279df438eSCleber Rosa    success = False
153c02b2eacSCleber Rosa    try:
154c02b2eacSCleber Rosa        if args.wait:
155c02b2eacSCleber Rosa            success = wait_on_pipeline_success(
156c02b2eacSCleber Rosa                args.timeout,
157c02b2eacSCleber Rosa                args.interval,
158c02b2eacSCleber Rosa                args.project_id,
159c02b2eacSCleber Rosa                args.commit)
160c02b2eacSCleber Rosa        else:
161c02b2eacSCleber Rosa            status = get_pipeline_status(args.project_id,
162c02b2eacSCleber Rosa                                         args.commit)
163c02b2eacSCleber Rosa            success = status['status'] == 'success'
164c02b2eacSCleber Rosa    except Exception as error:      # pylint: disable=W0703
165c02b2eacSCleber Rosa        if args.verbose:
166c02b2eacSCleber Rosa            print("ERROR: %s" % error.args[0])
16779df438eSCleber Rosa    except KeyboardInterrupt:
16879df438eSCleber Rosa        if args.verbose:
16979df438eSCleber Rosa            print("Exiting on user's request")
170c02b2eacSCleber Rosa
171c02b2eacSCleber Rosa    if success:
172c02b2eacSCleber Rosa        if args.verbose:
173c02b2eacSCleber Rosa            print('success')
174c02b2eacSCleber Rosa        sys.exit(0)
175c02b2eacSCleber Rosa    else:
176c02b2eacSCleber Rosa        if args.verbose:
177c02b2eacSCleber Rosa            print('failure')
178c02b2eacSCleber Rosa        sys.exit(1)
179c02b2eacSCleber Rosa
180c02b2eacSCleber Rosa
181c02b2eacSCleber Rosaif __name__ == '__main__':
182c02b2eacSCleber Rosa    main()
183