def validate_tags(string_pairs, mode): """Validates a list of common.StringPair tags. For mode, see buildtags.validate_tags docstring. """ for p in string_pairs: if ':' in p.key: _err('tag key "%s" cannot have a colon', p.key) with _handle_invalid_input_error(): tags = ['%s:%s' % (p.key, p.value) for p in string_pairs] buildtags.validate_tags(tags, mode)
def test_two_gitiles_commits(self): err_pattern = r'More than one commits/gitiles buildset' with self.assertRaisesRegexp(errors.InvalidInputError, err_pattern): tags = [ ( 'buildset:commit/gitiles/chromium.googlesource.com/chromium/src' '/+/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ), ( 'buildset:commit/gitiles/chromium.googlesource.com/chromium/src' '/+/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' ), ] buildtags.validate_tags(tags, 'new')
def _complete(build_id, lease_key, status, result_details, url=None, new_tags=None): """Marks a build as completed. Used by succeed and fail methods.""" validate_lease_key(lease_key) validate_url(url) buildtags.validate_tags(new_tags, 'append') assert model.is_terminal_status(status), status @ndb.transactional def txn(): build = _get_leasable_build(build_id) if build.is_ended: if (build.proto.status == status and build.result_details == result_details and build.url == url): return False, build raise errors.BuildIsCompletedError( 'Build %s has already completed' % build_id) _check_lease(build, lease_key) now = utils.utcnow() build.proto.status = status build.status_changed_time = now build.proto.end_time.FromDatetime(now) if url is not None: # pragma: no branch build.url = url build.result_details = result_details if new_tags: build.tags.extend(new_tags) build.tags = sorted(set(build.tags)) build.clear_lease() _fut_results( build.put_async(), events.on_build_completing_async(build), _put_output_properties_async(build.key, result_details), ) return True, build updated, build = txn() if updated: events.on_build_completed(build) return build
def validate(self): """Raises errors.InvalidInputError if self is invalid.""" assert isinstance(self.status, (type(None), StatusFilter, int)), self.status assert isinstance(self.bucket_ids, (type(None), list)), self.bucket_ids if self.bucket_ids and self.project: raise errors.InvalidInputError( 'project and bucket_ids are mutually exclusive') if self.builder and not self.bucket_ids: raise errors.InvalidInputError( 'builder requires non-empty bucket_ids') buildtags.validate_tags(self.tags, 'search') create_time_range = (self.create_time_low is not None or self.create_time_high is not None) build_range = self.build_low is not None or self.build_high is not None if create_time_range and build_range: raise errors.InvalidInputError( 'create_time_low and create_time_high are mutually exclusive with ' 'build_low and build_high')
def put_request_message_to_build_request(put_request): """Converts PutRequest to BuildRequest. Raises errors.InvalidInputError if the put_request is invalid. """ lease_expiration_date = parse_datetime(put_request.lease_expiration_ts) errors.validate_lease_expiration_date(lease_expiration_date) # Read parameters. parameters = parse_json_object(put_request.parameters_json, 'parameters_json') parameters = parameters or {} validate_known_build_parameters(parameters) builder = parameters.get(model.BUILDER_PARAMETER) or '' # Validate tags. buildtags.validate_tags(put_request.tags, 'new', builder=builder) # Read properties. Remove them from parameters. props = parameters.pop(model.PROPERTIES_PARAMETER, None) if props is not None and not isinstance(props, dict): raise errors.InvalidInputError( '"properties" parameter must be a JSON object or null') props = props or {} changes = parameters.get(_PARAM_CHANGES) if changes: # pragma: no branch # Buildbucket-Buildbot integration passes repo_url of the first change in # build parameter "changes" as "repository" attribute of SourceStamp. # https://chromium.googlesource.com/chromium/tools/build/+/2c6023d # /scripts/master/buildbucket/changestore.py#140 # Buildbot passes repository of the build source stamp as "repository" # build property. Recipes, in partiular bot_update recipe module, rely on # "repository" property and it is an almost sane property to support in # swarmbucket. repo_url = changes[0].get('repo_url') if repo_url: # pragma: no branch props['repository'] = repo_url # Buildbot-Buildbucket integration converts emails in changes to blamelist # property. emails = [c.get('author', {}).get('email') for c in changes] props['blamelist'] = filter(None, emails) # Create a v2 request. sbr = rpc_pb2.ScheduleBuildRequest( builder=build_pb2.BuilderID(builder=builder), properties=bbutil.dict_to_struct(props), request_id=put_request.client_operation_id, experimental=bbutil.BOOLISH_TO_TRINARY[put_request.experimental], canary=api_common.CANARY_PREFERENCE_TO_TRINARY.get( put_request.canary_preference, common_pb2.UNSET), ) sbr.builder.project, sbr.builder.bucket = config.parse_bucket_id( put_request.bucket) # Parse tags. Extract gitiles commit and gerrit changes. tags, gitiles_commit, gerrit_changes = parse_v1_tags(put_request.tags) sbr.tags.extend(tags) if gitiles_commit: sbr.gitiles_commit.CopyFrom(gitiles_commit) # Gerrit changes explicitly passed via "gerrit_changes" parameter win. gerrit_change_list = parameters.pop('gerrit_changes', None) if gerrit_change_list is not None: if not isinstance(gerrit_change_list, list): # pragma: no cover raise errors.InvalidInputError('gerrit_changes must be a list') try: gerrit_changes = [ json_format.ParseDict(c, common_pb2.GerritChange()) for c in gerrit_change_list ] except json_format.ParseError as ex: # pragma: no cover raise errors.InvalidInputError('Invalid gerrit_changes: %s' % ex) sbr.gerrit_changes.extend(gerrit_changes) if (not gerrit_changes and not sbr.builder.bucket.startswith('master.')): # pragma: no cover changes = parameters.get('changes') if isinstance(changes, list) and changes and not gitiles_commit: legacy_revision = changes[0].get('revision') if legacy_revision: raise errors.InvalidInputError( 'legacy revision without gitiles buildset tag') # Populate Gerrit project from patch_project property. # V2 API users will have to provide this. patch_project = props.get('patch_project') if len(sbr.gerrit_changes) == 1 and isinstance(patch_project, basestring): sbr.gerrit_changes[0].project = patch_project # Read PubSub callback. pubsub_callback_auth_token = None if put_request.pubsub_callback: pubsub_callback_auth_token = put_request.pubsub_callback.auth_token pubsub_callback_to_notification_config(put_request.pubsub_callback, sbr.notify) # Validate the resulting v2 request before continuing. with _wrap_validation_error(): validation.validate_schedule_build_request(sbr, legacy=True) return creation.BuildRequest( schedule_build_request=sbr, parameters=parameters, lease_expiration_date=lease_expiration_date, pubsub_callback_auth_token=pubsub_callback_auth_token, override_builder_cfg=_override_builder_cfg_func(parameters), )
def test_invalid_buildset(self): with self.assertRaises(errors.InvalidInputError): buildtags.validate_tags(['buildset:patch/gerrit/foo'], 'search')
def test_append_builder(self): with self.assertRaises(errors.InvalidInputError): buildtags.validate_tags(['builder:1'], 'append')
def test_no_key(self): with self.assertRaises(errors.InvalidInputError): buildtags.validate_tags([':'], 'search')
def test_builder_inconsistent(self): err_pattern = r'conflicts with tag' with self.assertRaisesRegexp(errors.InvalidInputError, err_pattern): buildtags.validate_tags(['builder:a', 'builder:b'], 'new')
def test_builder_inconsistent_with_param(self): with self.assertRaises(errors.InvalidInputError): buildtags.validate_tags(['builder:a'], 'new', 'b')
def test_build_address(self): with self.assertRaises(errors.InvalidInputError): buildtags.validate_tags(['build_address:1'], 'new') with self.assertRaises(errors.InvalidInputError): buildtags.validate_tags(['build_address:1'], 'append')
def test_no_colon(self): with self.assertRaises(errors.InvalidInputError): buildtags.validate_tags(['tag,value'], 'search')
def test_nonstring(self): with self.assertRaises(errors.InvalidInputError): buildtags.validate_tags(['tag:value', 123456], 'search')
def test_nonlist(self): with self.assertRaises(errors.InvalidInputError): buildtags.validate_tags('tag:value', 'search')
def test_tags_none(self): self.assertIsNone(buildtags.validate_tags(None, 'search'))