blob: c2d2922ff51dd8118181ce62c36182c45ee2655c [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.go.chromium.org.luci.buildbucket.proto import build as build_pb2
from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb2
from PB.recipes.fuchsia.release.create_branch import InputProperties
DEPS = [
"fuchsia/checkout",
"fuchsia/gerrit",
"fuchsia/git",
"fuchsia/jiri",
"fuchsia/lkg",
"fuchsia/release",
"fuchsia/sso",
"fuchsia/status_check",
"recipe_engine/context",
"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",
)
checkout = api.checkout.fuchsia_with_options(
build_input=build_pb2.Build.Input(
gitiles_commit=common_pb2.GitilesCommit(
id=revision,
project=props.project,
),
),
project=props.project,
manifest=props.manifest,
remote=props.remote,
# Skip for faster checkout.
fetch_packages=False,
)
integration_root = checkout.root_dir.join(props.project)
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=integration_root, 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"),
repo_path=integration_root,
)
# 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=integration_root), api.step.nest(
"create release branch"
) as presentation:
# 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=integration_root
)
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,
)
api.release.set_output_properties(
presentation, release_revision, release_version
)
if props.projects_to_initialize:
with api.step.nest("initialize project branches"), api.context(
cwd=integration_root
):
initialize_project_branches(
api,
props.target_branch,
props.projects_to_initialize,
props.dryrun,
)
if not props.dryrun and props.downstream_builders:
api.release.lookup_builds(
builders=props.downstream_builders,
revision=release_revision,
remote=https_remote,
)
def initialize_project_branches(api, target_branch, projects, dryrun):
"""Initialize a branch for each project.
The base revision for each branch is the revision pinned by its integration
manifest.
"""
for project in projects:
with api.step.nest(project):
project_info = api.jiri.project(
projects=[project],
list_remote=True,
).json.output[0]
gerrit_host = api.jiri.read_manifest_element(
project_info["manifest"], "project", project
)["gerrithost"]
if not dryrun:
api.gerrit.create_branch(
name="create branch",
host=gerrit_host,
project=project,
ref=target_branch,
revision=project_info["revision"],
)
def GenTests(api):
default_revision = "foo"
default_project = "integration"
default_manifest = "integration"
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.release.ref_to_release_version(
"",
nesting="resolve release version",
retcode=1,
)
source_version = api.release.ref_to_release_version(
"releases/0.20200102.0.1",
nesting="resolve release version",
)
version_does_not_exist = api.step_data(
"resolve release version.get remote tag", empty_output
)
revision_is_release_version = api.release.ref_to_release_version(
"releases/0.20200101.0.1",
nesting="create release branch",
)
no_matching_version = api.release.ref_to_release_version(
"",
nesting="create release branch",
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,
project=default_project,
manifest=default_manifest,
remote=default_remote,
)
+ 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,
project=default_project,
manifest=default_manifest,
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,
project=default_project,
manifest=default_manifest,
remote=default_remote,
projects_to_initialize=[
"test-project",
],
)
+ branch_does_not_exist
+ revision_has_no_release_history
+ version_does_not_exist
+ no_matching_version
+ api.step_data(
"initialize project branches.test-project.jiri project",
api.jiri.project(
projects=[
{
"name": "test-project",
"revision": "abcdef",
"manifest": "flower",
}
]
),
)
+ api.jiri.read_manifest_element(
"flower",
"project",
"test-project",
test_output={
"gerrithost": "https://fuchsia-review.googlesource.com",
},
nesting="initialize project branches.test-project",
)
)
yield (
api.status_check.test("branch_from_release_version")
+ api.properties(
revision=default_revision,
target_branch=default_branch,
project=default_project,
manifest=default_manifest,
remote=default_remote,
)
+ branch_does_not_exist
+ source_version
+ version_does_not_exist
+ revision_is_release_version
)