| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """This module integrates buildbucket with swarming. | 5 """This module integrates buildbucket with swarming. |
| 6 | 6 |
| 7 A bucket config may have "swarming" field that specifies how a builder | 7 A bucket config may have "swarming" field that specifies how a builder |
| 8 is mapped to a recipe. If build is scheduled for a bucket/builder | 8 is mapped to a recipe. If build is scheduled for a bucket/builder |
| 9 with swarming configuration, the integration overrides the default behavior. | 9 with swarming configuration, the integration overrides the default behavior. |
| 10 | 10 |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 93 task_template = yield get_task_template_async() | 93 task_template = yield get_task_template_async() |
| 94 if task_template and isinstance(build.parameters, dict): # pragma: no branch | 94 if task_template and isinstance(build.parameters, dict): # pragma: no branch |
| 95 builder = build.parameters.get(BUILDER_PARAMETER) | 95 builder = build.parameters.get(BUILDER_PARAMETER) |
| 96 if builder: # pragma: no branch | 96 if builder: # pragma: no branch |
| 97 result = yield _is_for_swarming_async(build.bucket, builder) | 97 result = yield _is_for_swarming_async(build.bucket, builder) |
| 98 raise ndb.Return(result) | 98 raise ndb.Return(result) |
| 99 | 99 |
| 100 | 100 |
| 101 def validate_build_parameters(builder_name, params): | 101 def validate_build_parameters(builder_name, params): |
| 102 """Raises errors.InvalidInputError if build parameters are invalid.""" | 102 """Raises errors.InvalidInputError if build parameters are invalid.""" |
| 103 |
| 104 def bad(fmt, *args): |
| 105 raise errors.InvalidInputError(fmt % args) |
| 106 |
| 103 properties = params.get(PARAM_PROPERTIES) | 107 properties = params.get(PARAM_PROPERTIES) |
| 104 if properties is not None: | 108 if properties is not None: |
| 105 if not isinstance(properties, dict): | 109 if not isinstance(properties, dict): |
| 106 raise errors.InvalidInputError('properties param must be an object') | 110 bad('properties parameter must be an object') |
| 107 if properties.get('buildername', builder_name) != builder_name: | 111 if properties.get('buildername', builder_name) != builder_name: |
| 108 raise errors.InvalidInputError('inconsistent builder name') | 112 bad('inconsistent builder name') |
| 109 | 113 |
| 110 swarming = params.get(PARAM_SWARMING) | 114 swarming = params.get(PARAM_SWARMING) |
| 111 if swarming is not None: | 115 if swarming is not None: |
| 112 if not isinstance(swarming, dict): | 116 if not isinstance(swarming, dict): |
| 113 raise errors.InvalidInputError('swarming param must be an object') | 117 bad('swarming parameter must be an object') |
| 114 swarming = copy.deepcopy(swarming) | 118 swarming = copy.deepcopy(swarming) |
| 115 if 'recipe' in swarming: | 119 if 'recipe' in swarming: |
| 116 recipe = swarming.pop('recipe') | 120 recipe = swarming.pop('recipe') |
| 117 if not isinstance(recipe, dict): | 121 if not isinstance(recipe, dict): |
| 118 raise errors.InvalidInputError( | 122 bad('swarming.recipe parameter must be an object') |
| 119 'swarming.recipe param must be an object') | |
| 120 if 'revision' in recipe: | 123 if 'revision' in recipe: |
| 121 revision = recipe.pop('revision') | 124 revision = recipe.pop('revision') |
| 122 if not isinstance(revision, basestring): | 125 if not isinstance(revision, basestring): |
| 123 raise errors.InvalidInputError( | 126 bad('swarming.recipe.revision parameter must be a string') |
| 124 'swarming.recipe.revision must be a string') | |
| 125 if recipe: | 127 if recipe: |
| 126 raise errors.InvalidInputError( | 128 bad('unrecognized keys in swarming.recipe: %r', recipe) |
| 127 'Unrecognized keys in swarming.recipe: %r' % recipe) | |
| 128 | 129 |
| 129 if swarming: | 130 if swarming: |
| 130 raise errors.InvalidInputError('Unrecognized keys: %r', swarming) | 131 bad('unrecognized keys: %r', swarming) |
| 132 |
| 133 changes = params.get(PARAM_CHANGES) |
| 134 if changes is not None: |
| 135 if not isinstance(changes, list): |
| 136 bad('changes param must be an array') |
| 137 for c in changes: # pragma: no branch |
| 138 if not isinstance(c, dict): |
| 139 bad('changes param must contain only objects') |
| 140 repo_url = c.get('repo_url') |
| 141 if repo_url is not None and not isinstance(repo_url, basestring): |
| 142 bad('change repo_url must be a string') |
| 143 author = c.get('author') |
| 144 if not isinstance(author, dict): |
| 145 bad('change author must be an object') |
| 146 email = author.get('email') |
| 147 if not isinstance(email, basestring): |
| 148 bad('change author email must be a string') |
| 149 if not email: |
| 150 bad('change author email not specified') |
| 131 | 151 |
| 132 | 152 |
| 133 def merge_recipe(r1, r2): | 153 def merge_recipe(r1, r2): |
| 134 """Merges two Recipe messages. Values in r2 overwrite values in r1.""" | 154 """Merges two Recipe messages. Values in r2 overwrite values in r1.""" |
| 135 if not r1: # pragma: no branch | 155 if not r1: # pragma: no branch |
| 136 return r2 # pragma: no cover | 156 return r2 # pragma: no cover |
| 137 r1_props = dict(p.split(':', 1) for p in r1.properties) | 157 r1_props = dict(p.split(':', 1) for p in r1.properties) |
| 138 r2_props = dict(p.split(':', 1) for p in r2.properties) | 158 r2_props = dict(p.split(':', 1) for p in r2.properties) |
| 139 r1_props.update(r2_props) | 159 r1_props.update(r2_props) |
| 140 return project_config_pb2.Swarming.Recipe( | 160 return project_config_pb2.Swarming.Recipe( |
| (...skipping 26 matching lines...) Expand all Loading... |
| 167 } | 187 } |
| 168 | 188 |
| 169 is_recipe = ( | 189 is_recipe = ( |
| 170 builder_cfg.HasField('recipe') or swarming_cfg.HasField('common_recipe')) | 190 builder_cfg.HasField('recipe') or swarming_cfg.HasField('common_recipe')) |
| 171 if is_recipe: # pragma: no branch | 191 if is_recipe: # pragma: no branch |
| 172 recipe = merge_recipe(swarming_cfg.common_recipe, builder_cfg.recipe) | 192 recipe = merge_recipe(swarming_cfg.common_recipe, builder_cfg.recipe) |
| 173 revision = swarming_param.get('recipe', {}).get('revision') or '' | 193 revision = swarming_param.get('recipe', {}).get('revision') or '' |
| 174 | 194 |
| 175 build_properties = dict( | 195 build_properties = dict( |
| 176 p.split(':', 1) for p in recipe.properties or []) | 196 p.split(':', 1) for p in recipe.properties or []) |
| 177 build_properties.update(build.parameters.get(PARAM_PROPERTIES) or {}) | |
| 178 build_properties['buildername'] = builder_cfg.name | 197 build_properties['buildername'] = builder_cfg.name |
| 179 | 198 |
| 180 # Convert changes in build to blamelist property, like Buildbot-Buildbucket | |
| 181 # integration. In Buildbot the property value is a list of emails. | |
| 182 changes = params.get(PARAM_CHANGES) | 199 changes = params.get(PARAM_CHANGES) |
| 183 if changes: # pragma: no branch | 200 if changes: # pragma: no branch |
| 201 # Buildbucket-Buildbot integration passes repo_url of the first change in |
| 202 # build parameter "changes" as "repository" attribute of SourceStamp. |
| 203 # https://chromium.googlesource.com/chromium/tools/build/+/2c6023d/scripts
/master/buildbucket/changestore.py#140 |
| 204 # Buildbot passes repository of the build source stamp as "repository" |
| 205 # build property. Recipes, in partiular bot_update recipe module, rely on |
| 206 # "repository" property and it is an almost sane property to support in |
| 207 # swarmbucket. |
| 208 repo_url = changes[0].get('repo_url') |
| 209 if repo_url: # pragma: no branch |
| 210 build_properties['repository'] = repo_url |
| 211 |
| 212 # Buildbot-Buildbucket integration converts emails in changes to blamelist |
| 213 # property. |
| 184 emails = [c.get('author', {}).get('email') for c in changes] | 214 emails = [c.get('author', {}).get('email') for c in changes] |
| 185 build_properties['blamelist'] = filter(None, emails) | 215 build_properties['blamelist'] = filter(None, emails) |
| 186 | 216 |
| 217 # Properties specified in build parameters must override any values derived |
| 218 # by swarmbucket. |
| 219 build_properties.update(build.parameters.get(PARAM_PROPERTIES) or {}) |
| 220 |
| 187 task_template_params.update({ | 221 task_template_params.update({ |
| 188 'repository': recipe.repository, | 222 'repository': recipe.repository, |
| 189 'revision': revision, | 223 'revision': revision, |
| 190 'recipe': recipe.name, | 224 'recipe': recipe.name, |
| 191 'properties_json': json.dumps(build_properties, sort_keys=True), | 225 'properties_json': json.dumps(build_properties, sort_keys=True), |
| 192 }) | 226 }) |
| 193 | 227 |
| 194 task_template_params = { | 228 task_template_params = { |
| 195 k: v or '' for k, v in task_template_params.iteritems()} | 229 k: v or '' for k, v in task_template_params.iteritems()} |
| 196 task = format_obj(task_template, task_template_params) | 230 task = format_obj(task_template, task_template_params) |
| (...skipping 436 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 633 def _extend_unique(target, items): | 667 def _extend_unique(target, items): |
| 634 for x in items: | 668 for x in items: |
| 635 if x not in target: # pragma: no branch | 669 if x not in target: # pragma: no branch |
| 636 target.append(x) | 670 target.append(x) |
| 637 | 671 |
| 638 | 672 |
| 639 class TaskToken(tokens.TokenKind): | 673 class TaskToken(tokens.TokenKind): |
| 640 expiration_sec = 60 * 60 * 24 # 24 hours. | 674 expiration_sec = 60 * 60 * 24 # 24 hours. |
| 641 secret_key = auth.SecretKey('swarming_task_token', scope='local') | 675 secret_key = auth.SecretKey('swarming_task_token', scope='local') |
| 642 version = 1 | 676 version = 1 |
| OLD | NEW |