blob: 84798481e844f5ce9f200f0f125e37e848cdae92 [file] [log] [blame]
# Copyright 2020 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 PB.go.chromium.org.luci.common.proto.gerrit import gerrit as gerrit_pb2
from recipe_engine import recipe_api
from RECIPE_MODULES.fuchsia.utils import pluralize
from PB.go.chromium.org.luci.buildbucket.proto import builder as builder_pb2
from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb2
from PB.go.chromium.org.luci.buildbucket.proto import (
builds_service as builds_service_pb2,
)
CL_UTIL_PATH = "fuchsia/infra/cl-util/${platform}"
CL_UTIL_VERSION = "git_revision:72959a38896fc76d7fb2074f917ca2aa4b676776"
class CLUtilApi(recipe_api.RecipeApi):
"""APIs for manipulating CLs."""
def __init__(self, *args, **kwargs):
super(CLUtilApi, self).__init__(*args, **kwargs)
self.gerrit_host = None
self.gerrit_project = None
def create_cl(self, step_name, subject, file_edits, presentation=None):
"""Create a CL.
Args:
step_name (str): Name of the step.
subject (str): Commit message subject of CL.
file_edits (seq((str, str))): A sequence of file edits, where each
file edit is a pair of strings: (filepath, contents).
presentation (Step): Add links to this step, if specified. Otherwise,
add links to the step generated by this call.
Returns:
gerritpb.ChangeInfo: The CL's change info.
"""
args = [
"-subject",
subject,
"-json-output",
self.m.proto.output(gerrit_pb2.ChangeInfo, "JSONPB"),
]
for filepath, contents in file_edits:
args += ["-file-edit", "%s:%s" % (filepath, contents)]
step = self(step_name, "create-cl", args, infra_step=True)
change_info = step.proto.output
presentation = presentation or step.presentation
presentation.links["gerrit_link"] = "https://%s/c/%s/+/%d" % (
self.gerrit_host,
self.gerrit_project,
change_info.number,
)
return change_info
def trigger_cq(
self, step_name, change_num, wait=False, timeout_secs=3600, dry_run=False
):
"""Trigger CQ on a CL and optionally wait for completion.
Args:
step_name (str): Name of the step.
change_num (int): Gerrit change number.
wait (bool): If set, wait for completion.
timeout (str): How long to wait for completion in seconds.
dry_run (bool): If set, apply CQ+1 instead of CQ+2.
"""
args = [
"-change-num",
change_num,
]
if wait:
args += [
"-wait",
"-timeout",
"%ds" % timeout_secs,
]
if dry_run:
args.append("-dryrun")
self(step_name, "trigger-cq", args)
def abandon_cl(self, step_name, change_num):
"""Abandon a CL.
Args:
step_name (str): Name of the step.
change_num (int): Gerrit change number.
"""
args = [
"-change-num",
change_num,
]
self(step_name, "abandon-cl", args, infra_step=True)
def trigger_tryjobs(
self, step_name, change_num, patchset_num, builders, gerrit_host=None
):
"""Trigger tryjobs on a change's patchset.
Args:
step_name (str): Name of the step.
change_num (int): Gerrit change number.
patchset_num (int): Gerrit change's patchset number.
builders (seq(str)): A sequence of builder names to trigger.
gerrit_host (str): If specified, trigger tryjobs under this Gerrit
host. Otherwise, use module's Gerrit host.
"""
reqs = []
for builder in builders:
project, bucket, builder_name = builder.split("/")
req = self.m.buildbucket.schedule_request(
builder=builder_name,
project=project,
bucket=bucket,
# Emulate "Choose Tryjobs" plugin.
gerrit_changes=[
common_pb2.GerritChange(
host=gerrit_host or self.gerrit_host,
project=self.gerrit_project,
change=change_num,
patchset=patchset_num,
),
],
# Use the server-defined settings rather than inheriting the
# parent build's settings. The parent build's settings should
# not necessarily match the tryjobs' settings, e.g. when
# crossing infrastructures.
exe_cipd_version=None,
experimental=None,
inherit_buildsets=False,
gitiles_commit=None,
priority=None,
)
reqs.append(req)
with self.m.step.nest(step_name):
return self.m.buildbucket.schedule(reqs, step_name="schedule")
def collect_tryjobs(
self,
step_name,
change_num,
patchset_num,
builders=None,
build_ids=None,
gerrit_host=None,
timeout_secs=3600,
raise_on_failure=True,
):
"""Collect tryjobs for a patchset.
Args:
step_name (str): Name of the step.
change_num (int): Gerrit change number.
patchset_num (int): Gerrit change's patchset number.
builders (seq(str)): A sequence of builder names to collect. Cannot
be combined with `build_ids`.
build_ids (seq(str)): A sequence of build ids to collect. Cannot be
be combined with `builders`.
gerrit_host (str): If specified, search tryjobs registered under this
Gerrit host. Otherwise, use module's Gerrit host.
timeout_secs (int): How long to attempt collection in seconds.
raise_on_failure (bool): Whether to raise on tryjob failure(s).
Raises:
InfraFailure: One or more tryjobs completed with INFRA_FAILURE status.
StepFailure: One or more tryjobs completed with FAILURE status.
"""
with self.m.step.nest(step_name):
assert (
builders or build_ids
), "only one of builders or build_ids may be specified"
if builders:
predicates = []
for builder in builders:
project, bucket, builder_name = builder.split("/")
predicate = builds_service_pb2.BuildPredicate(
builder=builder_pb2.BuilderID(
project=project, bucket=bucket, builder=builder_name,
),
gerrit_changes=[
common_pb2.GerritChange(
host=gerrit_host or self.gerrit_host,
project=self.gerrit_project,
change=change_num,
patchset=patchset_num,
),
],
)
predicates.append(predicate)
build_ids = [
b.id
for b in self.m.buildbucket.search(
step_name="search", predicate=predicates,
)
]
builds = self.m.buildbucket.collect_builds(
step_name="collect", build_ids=build_ids, timeout=timeout_secs,
)
try:
self.m.display_util.display_builds(
step_name="display",
builds=builds.itervalues(),
raise_on_failure=raise_on_failure,
)
except self.m.step.StepFailure:
gerrit_link = "https://%s/c/%s/+/%d" % (
self.gerrit_host,
self.gerrit_project,
change_num,
)
failed_tryjobs = [
b.builder.builder
for b in builds.itervalues()
if b.status != common_pb2.SUCCESS
]
raise self.m.step.StepFailure(
"%s failed: see [gerrit UI](%s):\n\n%s"
% (
pluralize("external tryjob", len(failed_tryjobs)),
gerrit_link,
"\n".join(["- %s" % t for t in failed_tryjobs]),
)
)
return builds
def __call__(self, step_name, subcmd_name, args, infra_step=False):
assert self.gerrit_host
assert self.gerrit_project
cmd = [
self._cl_util_tool,
subcmd_name,
"-gerrit-host",
self.gerrit_host,
"-gerrit-project",
self.gerrit_project,
] + args
return self.m.step(step_name, cmd, infra_step=infra_step)
@property
def _cl_util_tool(self):
return self.m.cipd.ensure_tool(CL_UTIL_PATH, CL_UTIL_VERSION)