blob: 22746672cfddb3b87bd6b5969e023ae6d6324f55 [file] [log] [blame]
# Copyright 2019 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from google.protobuf import text_format as textpb
from recipe_engine import recipe_api
DEFAULT_SPEC_DIR = "infra/config/generated"
class SpecApi(recipe_api.RecipeApi):
"""API for working with textproto files."""
class ParseError(recipe_api.StepFailure):
pass
def _get_spec_revision(self, spec_remote, Type, spec_dir, spec_revision):
build = self.m.buildbucket.build
builder_name = build.builder.builder
# Have subbuilders use the same specs as the parent builders.
if builder_name.endswith("-subbuild"):
builder_name = builder_name[: builder_name.index("-subbuild")]
spec_checkout_path = self.m.path["cleanup"].join("spec_workspace")
spec_path = spec_checkout_path.join(
spec_dir,
build.builder.project,
"specs",
build.builder.bucket,
builder_name + ".textproto",
)
if not spec_revision:
assert build.input.gitiles_commit
commit_remote = "https://%s/%s" % (
build.input.gitiles_commit.host,
build.input.gitiles_commit.project,
)
if commit_remote == spec_remote:
# The spec_revision will be ignored in this case and the
# revision of the gitiles_commit associated with the build
# will be used instead. This is just for consistency to
# return the correct spec revision being used.
spec_revision = build.input.gitiles_commit.id
else:
# If the commit is associated with a repo other than the one
# that contains the specs, then we still want to check out the
# spec repo at the same ref because we'll go on to do the
# primary checkout at this ref and we want to get the spec at
# the same revision as the primary checkout.
spec_revision = build.input.gitiles_commit.ref or "refs/heads/main"
spec_revision = self.m.git.checkout(
url=spec_remote,
path=spec_checkout_path,
ref=spec_revision,
submodules=False,
)
if build.input.gerrit_changes:
change = build.input.gerrit_changes[0]
gerrit_remote = "https://%s/%s" % (
change.host.replace("-review", ""),
change.project,
)
if gerrit_remote == spec_remote:
self.m.git.checkout_cl(
cl=change,
path=spec_checkout_path,
onto=spec_revision,
submodules=False,
cache=False,
)
spec = self.m.file.read_raw(name="read spec", source=spec_path).strip()
if not spec:
# Return parse error if the file is empty. google.protobuf considers
# that a valid proto so this results in an empty spec otherwise.
raise self.ParseError("file is empty")
try:
return textpb.Merge(spec, Type()), spec_revision
except Exception as e:
raise self.ParseError(e)
def get_spec_revision(
self,
spec_remote,
Type,
spec_dir=DEFAULT_SPEC_DIR,
spec_revision=None,
):
"""Loads a spec message from the given Buildbucket build input.
If the build input is for a gitiles commit, the Gitiles host, project,
and ref are inferred from the build input and the spec is fetched from
Gitiles.
If the build input is for a gerrit change, the project is rebased on top
of the parent gitiles_commit in build.
Args:
spec_remote (string): URL of the specs git repository.
spec_dir (string): The directory within spec_remote containing specs.
Specifically we'll look for the spec in
"<spec_dir>/<project>/specs/<bucket>/<builder>.textproto".
spec_revision (string or None): The git revision to fetch the spec from.
Returns:
spec (Type), rev (string): The parsed proto API spec and its revision.
Raises:
file.Error if the spec file could not be located.
git.RebaseError if the spec file could not be rebased.
spec.ParseError if the proto fails to parse into the given Type.
"""
try:
return self._get_spec_revision(
spec_remote=spec_remote,
Type=Type,
spec_dir=spec_dir,
spec_revision=spec_revision,
)
except self.m.file.Error:
# File not found or user does not have read permissions. This may
# be encountered in integration CQ if a user makes a bad config
# change. It is not a general infrastructure issue.
raise
except self.m.git.RebaseError:
# A failure to rebase is closer to user error than an infra failure.
# It can be fixed by the user by rebasing their change and then
# retrying the patch. The infrastructure is working properly.
raise
except self.ParseError:
# File is not parsable as a valid proto. This may be encounter in
# integration CQ if a bad config change is made, it is however not
# a general infrastructure issue, and would not make it thru CQ.
raise
except recipe_api.StepFailure as e:
# All other failures are the infra's fault.
raise recipe_api.InfraFailure(e.name or e.reason, result=e.result)