blob: d50daabf86031339c5491c0f3b3a930d908f6697 [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.
"""Create an integration.git release branch.
Branching Modes
- Revision: Create a branch off a specific revision.
- LKGR: Create a branch using the LKGR of a set of builders.
Options
- Experimental: Create an experimental branch using the "add track" feature.
See release/api.py for more details.
- Dryrun: Run recipe without modifying anything remotely.
"""
from PB.recipes.fuchsia.release.create_branch import InputProperties
DEPS = [
"fuchsia/git",
"fuchsia/lkg",
"fuchsia/release",
"fuchsia/sso",
"fuchsia/status_check",
"recipe_engine/context",
"recipe_engine/path",
"recipe_engine/properties",
"recipe_engine/python",
"recipe_engine/raw_io",
"recipe_engine/step",
"recipe_engine/time",
]
PROPERTIES = InputProperties
def RunSteps(api, props):
with api.step.nest("check inputs"):
if (props.revision and props.lkg_builders) or (
not props.revision and not props.lkg_builders
):
api.python.failing_step(
"invalid revision input",
"must specify exactly one of `revision` or `lkg_builders`",
)
if not api.release.validate_branch(props.target_branch):
api.python.failing_step(
"invalid target branch input",
'`target_branch` must start with "releases"',
)
# Assert branch does not yet exist remotely.
https_remote = api.sso.sso_to_https(props.remote)
if api.git.get_remote_branch_head(
step_name="get remote branch", url=https_remote, branch=props.target_branch,
):
api.python.failing_step(
"target branch already exists",
"target branch %s already exists" % props.target_branch,
)
# If `revision` was specified, use it. Otherwise, set `revision` to LKGR.
revision = props.revision
if props.lkg_builders:
revision = api.lkg.revision(
step_name="get last-known-good revision",
builders=props.lkg_builders,
test_data="lkgrevision",
)
tmpdir = api.path.mkdtemp()
with api.step.nest("checkout base revision"), api.context(infra_steps=True):
api.git.checkout(ref=revision, url=https_remote, path=tmpdir, cache=False)
with api.step.nest("resolve release version"):
# If there are existing candidates on `revision`, get the next candidate
# version.
release_version = api.release.get_next_candidate_version(
ref=revision, repo_path=tmpdir, add_track=props.add_track
)
# Otherwise, `revision` has no release history yet, so initialize a
# release version with the current date.
if not release_version:
release_version = api.release.get_initial_release_version(
date=api.time.utcnow().date().strftime("%Y%m%d")
)
# Assert release version does not yet exist remotely.
if api.git.get_remote_tag(url=https_remote, tag=str(release_version)):
api.python.failing_step(
"release version conflict",
"attempting creation would conflict with existing version %s, "
"indicating source revision %s has already moved forward"
% (release_version, revision),
)
with api.context(cwd=tmpdir), api.step.nest("create release branch"):
# Construct commit message.
create_message = "[release] Initialize branch %s to revision %s" % (
props.target_branch,
revision,
)
# If the source revision is a release version, make a note in the commit
# message.
source_version = api.release.ref_to_release_version(
ref=revision, repo_path=tmpdir
)
if source_version:
create_message = "%s (equal to %s)" % (
create_message,
str(source_version).replace("releases/", ""),
)
# Create empty commit to mark the creation of the branch.
api.git.commit(
step_name="create release commit", message=create_message, allow_empty=True,
)
api.git.tag(step_name="tag release", name=str(release_version))
release_revision = api.git.get_hash()
api.git.push(
step_name="push release",
refs=[
"HEAD:refs/heads/%s" % props.target_branch,
"refs/tags/%s" % release_version,
],
atomic=True,
dryrun=props.dryrun,
)
with api.step.nest("set output properties") as output:
output.presentation.properties["release_version"] = str(release_version)
output.presentation.properties["release_revision"] = release_revision
if not props.dryrun and props.downstream_builders:
api.release.lookup_builds(
builders=props.downstream_builders,
revision=release_revision,
remote=https_remote,
)
def GenTests(api):
default_revision = "foo"
default_remote = "sso://fuchsia/integration"
default_branch = "releases/newbranch"
empty_output = api.raw_io.stream_output("")
branch_does_not_exist = api.step_data(
"check inputs.get remote branch", empty_output
)
revision_has_no_release_history = api.step_data(
"resolve release version.git describe", retcode=1,
)
source_version = api.step_data(
"resolve release version.git describe",
api.raw_io.stream_output("releases/0.20200102.0.1"),
)
version_does_not_exist = api.step_data(
"resolve release version.get remote tag", empty_output
)
revision_is_release_version = api.step_data(
"create release branch.git describe",
api.raw_io.stream_output("releases/0.20200101.0.1"),
)
no_matching_version = api.step_data(
"create release branch.git describe", retcode=1,
)
yield (
api.status_check.test("mutually_exclusive_rev_input", status="failure")
+ api.properties(
revision=default_revision, lkg_builders=["project/bucket/builder"]
)
)
yield (api.status_check.test("missing_rev_input", status="failure"))
yield (
api.status_check.test("invalid_branch_input", status="failure")
+ api.properties(revision=default_revision, target_branch="invalidbranch")
)
yield (
api.status_check.test("branch_already_exists", status="failure")
+ api.properties(revision=default_revision, target_branch=default_branch)
)
yield (
api.status_check.test("release_version_conflict", status="failure")
+ api.properties(revision=default_revision, target_branch=default_branch)
+ branch_does_not_exist
+ revision_has_no_release_history
)
yield (
api.status_check.test("revision_from_main")
+ api.properties(
revision=default_revision,
target_branch=default_branch,
remote=default_remote,
downstream_builders=["a/b/c"],
)
+ branch_does_not_exist
+ revision_has_no_release_history
+ version_does_not_exist
+ no_matching_version
)
yield (
api.status_check.test("lkgr_from_main")
+ api.properties(
lkg_builders=["project/bucket/builder"],
target_branch=default_branch,
remote=default_remote,
)
+ branch_does_not_exist
+ revision_has_no_release_history
+ version_does_not_exist
+ no_matching_version
)
yield (
api.status_check.test("branch_from_release_version")
+ api.properties(
revision=default_revision,
target_branch=default_branch,
remote=default_remote,
)
+ branch_does_not_exist
+ source_version
+ version_does_not_exist
+ revision_is_release_version
)