smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | """Tool for interacting with Buildbucket. |
| 7 | |
| 8 | Usage: |
| 9 | $ depot-tools-auth login https://cr-buildbucket.appspot.com |
| 10 | $ buildbucket.py \ |
| 11 | put \ |
| 12 | --bucket master.tryserver.chromium.linux \ |
| 13 | --builder my-builder \ |
| 14 | |
| 15 | Puts a build into buildbucket for my-builder on tryserver.chromium.linux. |
| 16 | """ |
| 17 | |
Raul Tambre | 80ee78e | 2019-05-06 22:41:05 +0000 | [diff] [blame^] | 18 | from __future__ import print_function |
| 19 | |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 20 | import argparse |
| 21 | import json |
| 22 | import urlparse |
| 23 | import os |
| 24 | import sys |
Sergiy Belozorov | 64a001e | 2018-12-03 23:19:51 +0000 | [diff] [blame] | 25 | import time |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 26 | |
| 27 | from third_party import httplib2 |
| 28 | |
| 29 | import auth |
| 30 | |
| 31 | |
| 32 | BUILDBUCKET_URL = 'https://cr-buildbucket.appspot.com' |
smut@google.com | 8e6e1e6 | 2015-08-07 21:52:18 +0000 | [diff] [blame] | 33 | BUILDBUCKET_API_URL = urlparse.urljoin( |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 34 | BUILDBUCKET_URL, |
| 35 | '_ah/api/buildbucket/v1/builds', |
| 36 | ) |
| 37 | |
| 38 | |
John Budorick | ec40d02 | 2018-12-10 17:59:09 +0000 | [diff] [blame] | 39 | def add_common_arguments(parser): |
| 40 | parser.add_argument( |
| 41 | '--response-json', |
| 42 | help=( |
| 43 | 'A path to which the response JSON will be written. ' |
| 44 | 'If no valid JSON is received, nothing will be written.' |
| 45 | ) |
| 46 | ) |
| 47 | |
| 48 | |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 49 | def main(argv): |
| 50 | parser = argparse.ArgumentParser() |
| 51 | parser.add_argument( |
| 52 | '-v', |
| 53 | '--verbose', |
| 54 | action='store_true', |
| 55 | ) |
| 56 | subparsers = parser.add_subparsers(dest='command') |
John Budorick | ec40d02 | 2018-12-10 17:59:09 +0000 | [diff] [blame] | 57 | |
smut@google.com | 8e6e1e6 | 2015-08-07 21:52:18 +0000 | [diff] [blame] | 58 | get_parser = subparsers.add_parser('get') |
John Budorick | ec40d02 | 2018-12-10 17:59:09 +0000 | [diff] [blame] | 59 | add_common_arguments(get_parser) |
smut@google.com | 8e6e1e6 | 2015-08-07 21:52:18 +0000 | [diff] [blame] | 60 | get_parser.add_argument( |
| 61 | '--id', |
| 62 | help='The ID of the build to get the status of.', |
| 63 | required=True, |
| 64 | ) |
John Budorick | ec40d02 | 2018-12-10 17:59:09 +0000 | [diff] [blame] | 65 | |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 66 | put_parser = subparsers.add_parser('put') |
John Budorick | ec40d02 | 2018-12-10 17:59:09 +0000 | [diff] [blame] | 67 | add_common_arguments(put_parser) |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 68 | put_parser.add_argument( |
smut@google.com | 1da6b03 | 2015-06-09 00:16:03 +0000 | [diff] [blame] | 69 | '-b', |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 70 | '--bucket', |
| 71 | help=( |
| 72 | 'The bucket to schedule the build on. Typically the master name, e.g.' |
| 73 | ' master.tryserver.chromium.linux.' |
| 74 | ), |
| 75 | required=True, |
| 76 | ) |
| 77 | put_parser.add_argument( |
smut@google.com | c1ae89e | 2015-06-22 23:06:44 +0000 | [diff] [blame] | 78 | '-c', |
| 79 | '--changes', |
| 80 | help='A flie to load a JSON list of changes dicts from.', |
| 81 | ) |
| 82 | put_parser.add_argument( |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 83 | '-n', |
| 84 | '--builder-name', |
| 85 | help='The builder to schedule the build on.', |
| 86 | required=True, |
| 87 | ) |
| 88 | put_parser.add_argument( |
| 89 | '-p', |
| 90 | '--properties', |
Sergiy Byelozyorov | c3975e5 | 2018-07-09 22:30:09 +0000 | [diff] [blame] | 91 | help=( |
| 92 | 'A file to load a JSON dict of properties from. Use "-" to pipe JSON ' |
| 93 | 'from another command.' |
| 94 | ), |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 95 | ) |
John Budorick | ec40d02 | 2018-12-10 17:59:09 +0000 | [diff] [blame] | 96 | |
Sergiy Belozorov | 64a001e | 2018-12-03 23:19:51 +0000 | [diff] [blame] | 97 | retry_parser = subparsers.add_parser('retry') |
John Budorick | ec40d02 | 2018-12-10 17:59:09 +0000 | [diff] [blame] | 98 | add_common_arguments(retry_parser) |
Sergiy Belozorov | 64a001e | 2018-12-03 23:19:51 +0000 | [diff] [blame] | 99 | retry_parser.add_argument( |
| 100 | '--id', |
| 101 | help='The ID of the build to retry.', |
| 102 | required=True, |
| 103 | ) |
| 104 | |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 105 | args = parser.parse_args() |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 106 | |
smut@google.com | 8e6e1e6 | 2015-08-07 21:52:18 +0000 | [diff] [blame] | 107 | body = None |
smut@google.com | c1ae89e | 2015-06-22 23:06:44 +0000 | [diff] [blame] | 108 | |
smut@google.com | 8e6e1e6 | 2015-08-07 21:52:18 +0000 | [diff] [blame] | 109 | if args.command == 'get': |
| 110 | method = 'GET' |
| 111 | url = '%s/%s' % (BUILDBUCKET_API_URL, args.id) |
| 112 | elif args.command == 'put': |
| 113 | changes = [] |
| 114 | if args.changes: |
| 115 | try: |
| 116 | with open(args.changes) as fp: |
| 117 | changes.extend(json.load(fp)) |
| 118 | except (TypeError, ValueError): |
| 119 | sys.stderr.write('%s contained invalid JSON list.\n' % args.changes) |
| 120 | raise |
| 121 | |
| 122 | properties = {} |
| 123 | if args.properties: |
| 124 | try: |
Sergiy Byelozyorov | c3975e5 | 2018-07-09 22:30:09 +0000 | [diff] [blame] | 125 | # Allow using pipes to stream properties from another command, e.g. |
| 126 | # echo '{"foo": "bar", "baz": 42}' | buildbucket.py -p - |
| 127 | if args.properties == '-': |
| 128 | properties.update(json.load(sys.stdin)) |
| 129 | else: |
| 130 | with open(args.properties) as fp: |
| 131 | properties.update(json.load(fp)) |
smut@google.com | 8e6e1e6 | 2015-08-07 21:52:18 +0000 | [diff] [blame] | 132 | except (TypeError, ValueError): |
| 133 | sys.stderr.write('%s contained invalid JSON dict.\n' % args.properties) |
| 134 | raise |
| 135 | |
| 136 | body = json.dumps({ |
| 137 | 'bucket': args.bucket, |
| 138 | 'parameters_json': json.dumps({ |
| 139 | 'builder_name': args.builder_name, |
| 140 | 'changes': changes, |
| 141 | 'properties': properties, |
| 142 | }), |
| 143 | }) |
| 144 | method = 'PUT' |
| 145 | url = BUILDBUCKET_API_URL |
Sergiy Belozorov | 64a001e | 2018-12-03 23:19:51 +0000 | [diff] [blame] | 146 | elif args.command == 'retry': |
| 147 | method = 'PUT' |
| 148 | url = '%s/%s/retry' % (BUILDBUCKET_API_URL, args.id) |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 149 | |
| 150 | authenticator = auth.get_authenticator_for_host( |
| 151 | BUILDBUCKET_URL, |
| 152 | auth.make_auth_config(use_oauth2=True), |
| 153 | ) |
| 154 | http = authenticator.authorize(httplib2.Http()) |
| 155 | http.force_exception_to_status_code = True |
Sergiy Belozorov | 64a001e | 2018-12-03 23:19:51 +0000 | [diff] [blame] | 156 | |
| 157 | if args.verbose: |
Raul Tambre | 80ee78e | 2019-05-06 22:41:05 +0000 | [diff] [blame^] | 158 | print('Request URL:', url) |
| 159 | print('Request method:', method) |
| 160 | print('Request body:', body) |
Sergiy Belozorov | 64a001e | 2018-12-03 23:19:51 +0000 | [diff] [blame] | 161 | |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 162 | response, content = http.request( |
smut@google.com | 8e6e1e6 | 2015-08-07 21:52:18 +0000 | [diff] [blame] | 163 | url, |
| 164 | method, |
| 165 | body=body, |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 166 | headers={'Content-Type': 'application/json'}, |
| 167 | ) |
| 168 | |
| 169 | if args.verbose: |
Raul Tambre | 80ee78e | 2019-05-06 22:41:05 +0000 | [diff] [blame^] | 170 | print('Response:', response) |
| 171 | print('Content:', content) |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 172 | |
Sergiy Belozorov | 64a001e | 2018-12-03 23:19:51 +0000 | [diff] [blame] | 173 | try: |
John Budorick | ec40d02 | 2018-12-10 17:59:09 +0000 | [diff] [blame] | 174 | content_json = json.loads(content) |
| 175 | if args.response_json: |
| 176 | with open(args.response_json, 'w') as response_json_file: |
| 177 | response_json_file.write(content) |
| 178 | build_url = content_json['build']['url'] |
Sergiy Belozorov | 64a001e | 2018-12-03 23:19:51 +0000 | [diff] [blame] | 179 | except (ValueError, TypeError, KeyError): |
| 180 | pass |
| 181 | else: |
Raul Tambre | 80ee78e | 2019-05-06 22:41:05 +0000 | [diff] [blame^] | 182 | print('Build: %s' % build_url) |
Michael Achenbach | 0639cbc | 2018-09-18 11:33:14 +0000 | [diff] [blame] | 183 | |
smut@google.com | 5f0788b | 2015-06-09 00:04:51 +0000 | [diff] [blame] | 184 | return response.status != 200 |
| 185 | |
| 186 | |
| 187 | if __name__ == '__main__': |
| 188 | sys.exit(main(sys.argv)) |