xref: /openbmc/qemu/scripts/ci/gitlab-pipeline-status (revision 91641d555ae06726169f6ec8b57caeebcb0b71d0)
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):
726dfcbff8SCleber Rosa            msg = ("Timeout (-t/--timeout) of %i seconds reached, "
736dfcbff8SCleber Rosa                   "won't wait any longer for the pipeline to complete")
746dfcbff8SCleber Rosa            msg %= timeout
756dfcbff8SCleber 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            print('running...')
81db5424dfSCleber Rosa            time.sleep(interval)
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
92*91641d55SCleber Rosadef create_parser():
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:
113d9143750SCleber 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'))
127*91641d55SCleber Rosa    return parser
128c02b2eacSCleber Rosa
129*91641d55SCleber Rosadef main():
130*91641d55SCleber Rosa    """
131*91641d55SCleber Rosa    Script entry point
132*91641d55SCleber Rosa    """
133*91641d55SCleber Rosa    parser = create_parser()
134c02b2eacSCleber Rosa    args = parser.parse_args()
135c02b2eacSCleber Rosa
136c02b2eacSCleber Rosa    try:
137c02b2eacSCleber Rosa        if args.wait:
138c02b2eacSCleber Rosa            success = wait_on_pipeline_success(
139c02b2eacSCleber Rosa                args.timeout,
140c02b2eacSCleber Rosa                args.interval,
141c02b2eacSCleber Rosa                args.project_id,
142c02b2eacSCleber Rosa                args.commit)
143c02b2eacSCleber Rosa        else:
144c02b2eacSCleber Rosa            status = get_pipeline_status(args.project_id,
145c02b2eacSCleber Rosa                                         args.commit)
146c02b2eacSCleber Rosa            success = status['status'] == 'success'
147c02b2eacSCleber Rosa    except Exception as error:      # pylint: disable=W0703
148c02b2eacSCleber Rosa        success = False
149c02b2eacSCleber Rosa        if args.verbose:
150c02b2eacSCleber Rosa            print("ERROR: %s" % error.args[0])
151c02b2eacSCleber Rosa
152c02b2eacSCleber Rosa    if success:
153c02b2eacSCleber Rosa        if args.verbose:
154c02b2eacSCleber Rosa            print('success')
155c02b2eacSCleber Rosa        sys.exit(0)
156c02b2eacSCleber Rosa    else:
157c02b2eacSCleber Rosa        if args.verbose:
158c02b2eacSCleber Rosa            print('failure')
159c02b2eacSCleber Rosa        sys.exit(1)
160c02b2eacSCleber Rosa
161c02b2eacSCleber Rosa
162c02b2eacSCleber Rosaif __name__ == '__main__':
163c02b2eacSCleber Rosa    main()
164