blob: d6ff7c02f4ecda38bdeaa5d15447c36ed4016ed4 [file] [log] [blame]
# Copyright 2021 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.
"""
Recipe to build GCE images with Packer.
"""
from recipe_engine.recipe_api import Property
import re
DEPS = [
"fuchsia/buildbucket_util",
"fuchsia/git",
"fuchsia/git_checkout",
"recipe_engine/context",
"recipe_engine/file",
"recipe_engine/futures",
"recipe_engine/path",
"recipe_engine/properties",
"recipe_engine/raw_io",
"recipe_engine/step",
]
PROPERTIES = {
"repo": Property(kind=str, help="Salt repository to checkout."),
"dry_run": Property(
kind=bool, help="Exit early instead of creating a disk image.", default=True
),
}
TEMPLATE_SUFFIX = ".packer.generated.json"
def RunSteps(api, repo, dry_run):
salt_path, revision = api.git_checkout(repo, rebase_merges=True)
# Get a short revision, image names must be < 64 characters
with api.context(cwd=salt_path):
revision = api.git(
"git rev-parse",
"rev-parse",
"--short",
revision,
stdout=api.raw_io.output_text(),
).stdout.rstrip()
packer_dir = salt_path.join("packer")
template_paths = api.file.glob_paths(
"find packer templates", packer_dir, "*" + TEMPLATE_SUFFIX
)
env = {
# Disable update checks.
"CHECKPOINT_DISABLE": "1",
# Enable verbose logging.
"PACKER_LOG": "1",
# Disable color in logging.
"PACKER_NO_COLOR": "1",
}
with api.context(env=env, cwd=salt_path):
builds = []
for template_path in template_paths:
api.step(
"packer validate",
[
"packer",
"validate",
"-var",
"revision={}".format(revision),
template_path,
],
)
builds.append(
api.futures.spawn(
_do_packer_builder,
api,
revision,
dry_run,
template_path,
)
)
api.futures.wait(builds)
if any(not build.result() for build in builds):
raise api.step.StepFailure("BUILDS FAILED")
def _do_packer_builder(api, revision, dry_run, template_path):
build = api.path.basename(template_path).replace(TEMPLATE_SUFFIX, "")
with api.step.nest(build):
result = api.step(
"packer build",
[
"packer",
"build",
"-var",
"revision={}".format(revision),
"-var",
"dry_run={}".format(str(dry_run).lower()),
"-var",
"use_internal_ip=true",
"-only={}".format(build),
template_path,
],
stdout=api.raw_io.output_text(),
ok_ret="any",
# By default recipe_engine assigns a `cost` of 500 mCPU per step,
# this limits our parallelism to 2*NUM_CORES but these steps are
# simply waiting 99% of the time we can run far more in parallel.
cost=None,
)
output = result.stdout
result.presentation.logs["output"] = output.splitlines()
if result.retcode != 0:
if dry_run:
for line in output.splitlines():
if "DRYRUN SUCCEEDED" in line:
result.presentation.step_text = "DRYRUN SUCCEEDED"
return True
result.presentation.step_text = "DRYRUN FAILED"
result.presentation.status = api.step.FAILURE
result.presentation.status = api.step.FAILURE
else:
return True
if result.presentation.status == api.step.FAILURE:
failures_regex = re.compile(
"(\s*ID:\s(.*?\n).*?Result:\sFalse\n.*?Changes:.*?)\n\s*{}:\s-{{10}}".format(
build
),
re.DOTALL | re.MULTILINE,
)
for f in re.findall(failures_regex, output):
result.presentation.logs[f[1]] = f[0].splitlines()
return False
def GenTests(api):
state_failures = """
fail: ----------
fail: ID: /etc/systemd/system/gce-provider-start-agent.service
fail: Function: file.managed
fail: Result: False
fail: Comment: The following requisites were not found:
fail: require:
fail: user: swarming_use
fail: Started: 01:13:41.861499
fail: Duration: 0.028 ms
fail: Changes:
fail: ----------
fail: ID: gce-provider-start-agent
fail: Function: service.enabled
fail: Result: False
fail: Comment: One or more requisite failed: luci.gce_provider./etc/systemd/system/gce-provider-start-agent.service
fail: Started: 01:13:41.862762
fail: Duration: 0.017 ms
fail: Changes:
fail: ----------
fail: ID: curl
fail: Function: pkg.installed
fail: Result: True
fail: Comment: All specified packages are already installed
fail: Started: 01:13:41.868646
fail: Duration: 688.753 ms
fail: Changes:
fail: ----------
"""
repo = "https://dash-internal.googlesource.com/salt"
yield (
api.buildbucket_util.test("ci_failure", status="failure", git_repo=repo)
+ api.properties(repo=repo, dry_run=False)
+ api.step_data(
"find packer templates",
api.file.glob_paths(
[
"pass.packer.generated.json",
"fail.packer.generated.json",
]
),
)
+ api.step_data("pass.packer build", retcode=0)
+ api.step_data(
"fail.packer build",
api.raw_io.stream_output_text(state_failures),
retcode=1,
)
)
yield (
api.buildbucket_util.test("ci_success", git_repo=repo)
+ api.properties(repo=repo, dry_run=False)
+ api.step_data(
"find packer templates",
api.file.glob_paths(
[
"pass.packer.generated.json",
]
),
)
+ api.step_data("pass.packer build", retcode=0)
)
yield (
api.buildbucket_util.test("try_failure", status="failure", git_repo=repo)
+ api.properties(repo=repo, dry_run=True)
+ api.step_data(
"find packer templates",
api.file.glob_paths(
[
"pass.packer.generated.json",
"fail.packer.generated.json",
]
),
)
+ api.step_data("pass.packer build", retcode=0)
+ api.step_data(
"fail.packer build",
api.raw_io.stream_output_text(state_failures),
retcode=1,
)
)
yield (
api.buildbucket_util.test("try_success", tryjob=True, git_repo=repo)
+ api.properties(repo=repo, dry_run=True)
+ api.step_data(
"find packer templates",
api.file.glob_paths(
[
"pass.packer.generated.json",
]
),
)
+ api.step_data(
"pass.packer build",
api.raw_io.stream_output_text("DRYRUN SUCCEEDED"),
retcode=1,
)
)