blob: a4bb48cbd348ad00ccccd8cae347f19e6a744195 [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 recipe_engine import recipe_api
from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb2
from PB.go.chromium.org.luci.buildbucket.proto import rpc as rpc_pb2
class BuildbucketUtilApi(recipe_api.RecipeApi):
"""Module for high-level buildbucket utilities specific to Fuchsia infra."""
@property
def id(self):
"""A unique string identifier for the current build."""
if self.m.led.launched_by_led:
return self.m.led.run_id
else:
return str(self.m.buildbucket.build.id)
@property
def is_tryjob(self):
"""Whether the current build is running as a tryjob."""
bucket_name = self.m.buildbucket.build.builder.bucket
return bucket_name.endswith('try')
def display_builds(self, step_name, builds, raise_on_failure=False):
"""
Display build links and status for each input build.
Optionally raise on build failure(s).
Args:
builds (seq[buildbucket.v2.Build]): buildbucket Build objects. See
recipe_engine/buildbucket recipe module for more info.
build_group_name (str): Name of build group to display in step name.
raise_on_failure (bool): Raise InfraFailure or StepFailure on failure.
Raises:
InfraFailure: One or more input builds had infra failure. Takes priority
over step failures.
StepFailure: One or more of input builds failed.
"""
infra_failed_builders = []
failed_builders = []
# Create per-build display steps.
with self.m.step.nest(step_name):
for build in builds:
with self.m.step.nest(build.builder.builder) as display_step:
step_links = display_step.presentation.links
step_links[str(build.id)] = self.m.buildbucket.build_url(
build_id=build.id) # yapf: disable
if build.status == common_pb2.SUCCESS:
display_step.presentation.status = self.m.step.SUCCESS
elif build.status == common_pb2.INFRA_FAILURE:
display_step.presentation.status = self.m.step.EXCEPTION
infra_failed_builders.append(build.builder.builder)
elif build.status == common_pb2.FAILURE:
display_step.presentation.status = self.m.step.FAILURE
failed_builders.append(build.builder.builder)
# For any other status, use warning color.
else:
display_step.presentation.status = self.m.step.WARNING
if raise_on_failure:
# Construct failure header and message. Include both types of failures,
# regardless of whether we raise purple or red.
failure_header = 'build(s) failed'
failure_message = []
if infra_failed_builders:
failure_message.append(
'infra failures: {infra_failed_builders}'.format(
infra_failed_builders=', '.join(infra_failed_builders)))
if failed_builders:
failure_message.append('step failures: {failed_builders}'.format(
failed_builders=', '.join(failed_builders)))
failure_message = ', '.join(failure_message)
# If there were any infra failures, raise purple.
if infra_failed_builders:
self.m.python.infra_failing_step(
failure_header,
failure_message,
)
# Otherwise if there were any step failures, raise red.
if failed_builders:
self.m.python.failing_step(
failure_header,
failure_message,
)
def last_build(self, project, bucket, builder, fields=None, status=None):
"""Returns the build proto for a builder's most recent successful build.
Returns None if no successful builds could be found.
Args:
project (str): LUCI project.
bucket (str): BuildBucket bucket.
builder (str): BuildBucket builder.
fields (seq of str): BuildBucket Build proto message fields to include in
the result. If None, defaults to api.buildbucket.DEFAULT_FIELDS.
status (common_pb2.Status enum member): If set, will return the
last build with this status.
"""
predicate = rpc_pb2.BuildPredicate()
predicate.builder.project = project
predicate.builder.bucket = bucket
predicate.builder.builder = builder
if status is not None:
predicate.status = status
fields = fields or self.m.buildbucket.DEFAULT_FIELDS
builds = self.m.buildbucket.search(predicate, limit=1, fields=fields)
if not builds:
return None
assert len(builds) == 1
return builds[0]