blob: 5e5df1e61cf119f05c8c7b89a2d1c886d99d6a2a [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.
"""Recipe for rolling CIPD prebuilts which depend on other prebuilts.
Version dependencies are defined in a file called versions.json on the
root folder of the CIPD package. This recipe reads the dependencies and
validate the correct versions are already in the tree. If all the
dependencies are satisfied then the new CIPD package is rolled if not it
will just present a message notifying is waiting for a given version.
"""
import re
from PB.recipes.fuchsia.contrib.cipd_with_dependencies_roller import InputProperties
PYTHON_VERSION_COMPATIBILITY = "PY3"
DEPS = [
"fuchsia/auto_roller",
"fuchsia/buildbucket_util",
"fuchsia/cipd_dependencies",
"fuchsia/gerrit",
"fuchsia/jiri",
"recipe_engine/buildbucket",
"recipe_engine/cipd",
"recipe_engine/context",
"recipe_engine/file",
"recipe_engine/path",
"recipe_engine/properties",
"recipe_engine/step",
]
PROPERTIES = InputProperties
COMMIT_MESSAGE_TITLE = """{prepend}[{type}] {type_descr} {roller} CIPD packages:"""
COMMIT_MESSAGE_DO_NOT_SUBMIT = "DO NOT SUBMIT "
COMMIT_MESSAGE = """
{packages}
From: {old_version}
To: {version}
Test: CQ
"""
CIPD_URL = "https://chrome-infra-packages.appspot.com/p/{package}/+/{version}"
def RunSteps(api, props): # pylint: disable=inconsistent-return-statements
ref = props.ref or "latest"
with api.context(infra_steps=True):
gerrit_host = api.gerrit.host_from_remote_url(props.manifests[0]["remote"])
if props.owners:
with api.step.nest("owners") as owners_presentation:
owners_presentation.step_summary_text = ", ".join(props.owners)
api.jiri.init(use_lock_file=True)
for manifest_config in props.manifests:
api.jiri.import_manifest(
manifest=manifest_config["manifest"],
remote=manifest_config["remote"],
name=manifest_config["project"],
)
api.jiri.update(run_hooks=False)
with api.context(cwd=api.path["start_dir"]):
api.jiri.run_hooks()
deps_info = api.jiri.package([props.package])
cipd_description = api.cipd.describe(props.package, ref)
# Is there a new version to roll?
if cipd_description.tags[0].tag == deps_info.json.output[0]["version"]:
api.step.empty("manifest up-to-date; nothing to roll")
return
version = cipd_description.tags[0].tag
# We need to roll the new version.
versions_json = api.cipd_dependencies.get_dependencies(
"read versions",
props.package,
cipd_description.pin.instance_id,
props.version_file,
)
summary = api.cipd_dependencies.validate_against_tree(
package=props.package,
dependencies=props.package_dependencies,
versions_dict=versions_json,
)
if summary:
api.step.empty("package dependencies not satisfied: {}".format(summary))
return
# Verify source dependencies are satisfied.
summary = api.cipd_dependencies.validate_against_tree(
props.package, props.project_dependencies, versions_json, is_package=False
)
if summary:
api.step.empty("project dependencies not satisfied: {}".format(summary))
return
changes = api.jiri.edit_manifest(
props.import_in, packages=[(props.package, version)]
)
old_version = changes["packages"][0]["old_version"]
exact_packages = set()
# Update the lockfiles.
for lock_entry in props.lockfiles:
fields = lock_entry.split("=")
manifest = fields[0]
lock = fields[1]
resolve_output = api.jiri.resolve(
local_manifest=True, output=lock, manifests=[manifest]
).stdout
platform_pkgs = get_platform_specific_packages(
props.package, resolve_output
)
exact_packages = exact_packages.union(platform_pkgs)
exact_packages.add(props.package)
exact_packages = sorted(exact_packages)
packages_with_urls = append_urls(exact_packages, old_version, version)
message = generate_message(
builder_name=api.buildbucket.builder_name,
packages="\n".join(packages_with_urls),
old_version=old_version,
version=version,
build_id=api.buildbucket_util.id,
dry_run=props.dry_run,
)
# Land the changes.
project_dir = api.path["start_dir"].join(*props.project.split("/"))
with api.context(cwd=project_dir):
change = api.auto_roller.attempt_roll(
gerrit_host,
gerrit_project=props.project,
repo_dir=project_dir,
commit_message=message,
dry_run=props.dry_run,
roller_owners=props.owners,
)
return api.auto_roller.raw_result(change)
def get_platform_specific_packages(package, output):
platform_regex = "(?<=" + package.replace("${platform}", r"\${platform=).*(?=})")
if not package.endswith("${platform}"):
return [package]
pattern = re.compile(platform_regex)
match = pattern.search(output)
if match:
platforms = match.group(0).split(",")
return [package.replace("${platform}", platform) for platform in platforms]
def append_urls(packages, old_version, new_version):
package_line = "{package} old:{old} new:{new}"
packages_with_urls = []
for package in packages:
if "${platform}" in package:
packages_with_urls.append(package)
else:
packages_with_urls.append(
package_line.format(
old=CIPD_URL.format(package=package, version=old_version),
new=CIPD_URL.format(package=package, version=new_version),
package=package,
)
)
return packages_with_urls
def generate_message(builder_name, packages, old_version, version, build_id, dry_run):
roller_string = builder_name.replace("-roller", "").replace("-dryrun", "")
if dry_run:
message_title = COMMIT_MESSAGE_TITLE.format(
prepend=COMMIT_MESSAGE_DO_NOT_SUBMIT,
type="dryrun",
type_descr="Dry run",
roller=roller_string,
)
else:
message_title = COMMIT_MESSAGE_TITLE.format(
prepend="",
type="roll",
type_descr="Roll",
roller=roller_string,
)
message_body = COMMIT_MESSAGE.format(
roller=roller_string,
packages=packages,
old_version=old_version,
version=version,
builder=builder_name,
build_id=build_id,
)
return "".join([message_title, message_body])
def GenTests(api):
def properties(**kwargs):
props = {
"project": "integration",
"manifests": [
{
"project": "integration",
"manifest": "other/dependency",
"remote": "sso://fuchsia/integration",
},
{
"project": "integration",
"manifest": "fuchsia/flower",
"remote": "sso://fuchsia/integration",
},
],
"import_in": "fuchsia/prebuilts",
"package": "flutter/dependent/${platform}",
"lockfiles": ["integration/flower=integration/jiri.lock"],
"version_file": "flutter/versions.json",
"package_dependencies": ["flutter/fuchsia=engine_version"],
"project_dependencies": ["dart/sdk=dart_version"],
"owners": ["abc@google.com"],
"dry_run": False,
}
props.update(kwargs)
return api.properties(**props)
flutter_dependent_no_match_test_data = api.step_data(
"cipd describe flutter/dependent/${platform}",
api.cipd.example_describe(
package_name="flutter/dependent",
version="dependent_version_abc",
test_data_tags=["git_revision:dependent_revision_jkl"],
),
)
package_test_data = [
{
"name": "flutter/dependent",
"path": "path/flutter/dependent",
"version": "git_revision:dependent_revision_jkl",
"manifest": "manifest1",
},
]
yield (
api.buildbucket_util.test("nothing_to_roll")
+ properties()
+ flutter_dependent_no_match_test_data
+ api.step_data("jiri package", api.jiri.package(package_test_data))
)
yield (
api.buildbucket_util.test("nothing_to_roll_dryrun")
+ properties(dry_run=True)
+ flutter_dependent_no_match_test_data
+ api.step_data("jiri package", api.jiri.package(package_test_data))
)
flutter_dependent_match_test_data = api.step_data(
"cipd describe flutter/dependent/${platform}",
api.cipd.example_describe(
package_name="flutter/dependent",
version="dependent_version_def",
test_data_tags=["git_revision:dependent_revision_lmn"],
),
)
versions_json_deps_not_satisfied = {
"engine_version": "engine_version_xyz",
}
dependencies_package_test_data = [
{
"name": "flutter/fuchsia",
"path": "path/flutter/fuchsia",
"version": "git_revision:engine_revision_abc",
"manifest": "manifest1",
},
]
yield (
api.buildbucket_util.test("package_dependencies_not_satisfied")
+ properties()
+ flutter_dependent_match_test_data
+ api.step_data("jiri package", api.jiri.package(package_test_data))
+ api.step_data(
"jiri package (2)", api.jiri.package(dependencies_package_test_data)
)
+ api.step_data(
"read versions",
api.file.read_json(json_content=versions_json_deps_not_satisfied),
)
)
yield (
api.buildbucket_util.test("package_dependencies_not_satisfied_dryrun")
+ properties(dry_run=True)
+ flutter_dependent_match_test_data
+ api.step_data("jiri package", api.jiri.package(package_test_data))
+ api.step_data(
"jiri package (2)", api.jiri.package(dependencies_package_test_data)
)
+ api.step_data(
"read versions",
api.file.read_json(json_content=versions_json_deps_not_satisfied),
)
)
deps_satisfied = {
"engine_version": "engine_revision_abc",
"dart_version": "dart_version_abc",
}
project_test_data = [
{
"name": "dart/sdk",
"path": "path/fuchsia/dart/sdk",
"revision": "dart_version_abc",
"manifest": "manifest1",
}
]
flutter_dependent_match_test_data_no_platform = api.step_data(
"cipd describe flutter/dependent",
api.cipd.example_describe(
package_name="flutter/dependent",
version="dependent_version_def",
test_data_tags=["git_revision:dependent_revision_lmn"],
),
)
yield (
api.buildbucket_util.test(
"dependencies_satisfied_package_with_platform",
builder="flutter-dependent-roller",
)
+ properties(package="flutter/dependent")
+ flutter_dependent_match_test_data_no_platform
+ api.step_data("jiri package", api.jiri.package(package_test_data))
+ api.step_data(
"jiri resolve", stdout=api.jiri.example_resolve_data("flutter/dependent")
)
+ api.step_data(
"jiri package (2)", api.jiri.package(dependencies_package_test_data)
)
+ api.step_data("jiri project", api.jiri.project(project_test_data))
+ api.step_data(
"read versions", api.file.read_json(json_content=deps_satisfied)
)
+ api.auto_roller.success()
)
yield (
api.buildbucket_util.test(
"dependencies_satisfied", builder="flutter-dependent-roller"
)
+ properties(dry_run=True)
+ flutter_dependent_match_test_data
+ api.step_data("jiri package", api.jiri.package(package_test_data))
+ api.step_data(
"jiri resolve",
stdout=api.jiri.example_resolve_data("flutter/dependent/${platform}"),
)
+ api.step_data(
"jiri package (2)", api.jiri.package(dependencies_package_test_data)
)
+ api.step_data("jiri project", api.jiri.project(project_test_data))
+ api.step_data(
"read versions", api.file.read_json(json_content=deps_satisfied)
)
+ api.auto_roller.dry_run_success()
)
yield (
api.buildbucket_util.test(
"dependencies_satisfied_dryrun", builder="flutter-dependent-roller"
)
+ properties(dry_run=True)
+ flutter_dependent_match_test_data
+ api.step_data("jiri package", api.jiri.package(package_test_data))
+ api.step_data(
"jiri resolve",
stdout=api.jiri.example_resolve_data("flutter/dependent/${platform}"),
)
+ api.step_data(
"jiri package (2)", api.jiri.package(dependencies_package_test_data)
)
+ api.step_data("jiri project", api.jiri.project(project_test_data))
+ api.step_data(
"read versions", api.file.read_json(json_content=deps_satisfied)
)
+ api.auto_roller.dry_run_success()
)
project_test_data_not_satisfied = [
{
"name": "dart/sdk",
"path": "path/fuchsia/dart/sdk",
"revision": "dart_version_xyz",
"manifest": "manifest1",
}
]
yield (
api.buildbucket_util.test("project_dependencies_not_satisfied")
+ properties()
+ flutter_dependent_match_test_data
+ api.step_data("jiri package", api.jiri.package(package_test_data))
+ api.step_data(
"jiri package (2)", api.jiri.package(dependencies_package_test_data)
)
+ api.step_data(
"jiri project", api.jiri.project(project_test_data_not_satisfied)
)
+ api.step_data(
"read versions", api.file.read_json(json_content=deps_satisfied)
)
)
yield (
api.buildbucket_util.test("project_dependencies_not_satisfied_dryrun")
+ properties(dry_run=True)
+ flutter_dependent_match_test_data
+ api.step_data("jiri package", api.jiri.package(package_test_data))
+ api.step_data(
"jiri package (2)", api.jiri.package(dependencies_package_test_data)
)
+ api.step_data(
"jiri project", api.jiri.project(project_test_data_not_satisfied)
)
+ api.step_data(
"read versions", api.file.read_json(json_content=deps_satisfied)
)
)