blob: d026c44305e6169dc70529d2f6fb86fd0a9b6c7c [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 recipe_engine import recipe_api
from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb2
class DisplayUtilApi(recipe_api.RecipeApi):
"""Module to display buildbucket or swarming tasks as steps."""
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:
step_name (str): Name of build group to display in step name.
builds (seq(buildbucket.v2.Build)): buildbucket Build objects. See
recipe_engine/buildbucket recipe module for more info.
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.
"""
self._display(
step_name=step_name,
builds=builds,
raise_on_failure=raise_on_failure,
process_func=self._process_build,
)
def display_tasks(self, step_name, results, metadata, raise_on_failure=False):
"""Display task links and status for each input task.
Optionally raise on build failure(s).
Args:
step_name (str): Name of build group to display in step name.
results (seq(swarming.TaskResult)): swarming TaskResult objects. See
recipe_engine/swarming recipe module for more info.
metadata (seq(swarming.TaskMetadata)): swarming TaskMetadata objects. See
recipe_engine/swarming recipe module for more info.
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.
"""
self._display(
step_name=step_name,
builds=results,
raise_on_failure=raise_on_failure,
process_func=self._process_task,
metadata=metadata,
)
def _process_build(self, result, infra_failed_builders, failed_builders):
"""Process a single buildbucket.v2.Build.
Args:
result (buildbucket.v2.Build): A buildbucket Build object.
infra_failed_builders (List(str)): A list of the builder names with infra failures.
failed_builders (List(str)): A list of the builder names with failures.
"""
with self.m.step.nest(result.builder.builder) as display_step:
step_links = display_step.presentation.links
step_links[str(
result.id)] = self.m.buildbucket.build_url(build_id=result.id)
if result.status == common_pb2.Status.Value('SUCCESS'):
display_step.presentation.status = self.m.step.SUCCESS
elif result.status == common_pb2.Status.Value('INFRA_FAILURE'):
display_step.presentation.status = self.m.step.EXCEPTION
infra_failed_builders.append(result.builder.builder)
elif result.status == common_pb2.Status.Value('FAILURE'):
display_step.presentation.status = self.m.step.FAILURE
failed_builders.append(result.builder.builder)
# For any other status, use warning color.
else:
display_step.presentation.status = self.m.step.WARNING
def _process_task(self, result, infra_failed_builders, failed_builders,
links):
"""Process a single swarming.TaskResult.
Args:
result (swarming.TaskResult): A swarming TaskResult object.
infra_failed_builders (List(str)): A list of the builder names with infra failures.
failed_builders (List(str)): A list of the builder names with failures.
links (Dict): A dictionary with the task links as values and the task id as keys.
"""
with self.m.step.nest(result.name) as display_step:
step_links = display_step.presentation.links
step_links[str(result.id)] = links[result.id]
if (result.state is None or
result.state != self.m.swarming.TaskState.COMPLETED):
display_step.status = self.m.step.EXCEPTION
infra_failed_builders.append(result.name)
elif not result.success:
display_step.status = self.m.step.FAILURE
failed_builders.append(result.name)
else:
display_step.presentation.status = self.m.step.WARNING
def _display(self,
step_name,
builds,
process_func,
raise_on_failure=False,
metadata=None):
"""Display build links and status for each input build.
Optionally raise on build failure(s).
Args:
step_name (str): Name of build group to display in step name.
builds (seq(buildbucket.v2.Build) or seq(swarming.TaskResult)): buildbucket Build or swarming TaskResult objects. See
recipe_engine/buildbucket or recipe_engine/swarming recipe module for more info.
process_func (Runnable): A function to process a build or task result object.
raise_on_failure (bool): Raise InfraFailure or StepFailure on failure.
metadata (seq(swarming.TaskMetadata)): swarming TaskMetadata objects. See
recipe_engine/swarming recipe module for more info.
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 k in builds:
build = builds[k] if isinstance(k, long) or isinstance(k, int) else k
args = {
"result": build,
"infra_failed_builders": infra_failed_builders,
"failed_builders": failed_builders,
}
if metadata:
args["links"] = {m.id: m.task_ui_link for m in metadata}
process_func(**args)
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,
)