Because image_builder_arm64 is cross-built by the amd64 image_builder, it ended up with its architecture metadata wrongly set to amd64. There is no kaniko or skopeo flag to override only the resulting image metadata: kaniko's --custom-platform also switches the platform used to pull base images and run the build steps, which breaks the cross-build on the amd64 worker. Instead, set a TARGET_ARCH environment variable for image_builder_arm64 and have build-image rewrite the architecture field in the image config after kaniko builds it, before repacking with skopeo. Differential Revision: https://phabricator.services.mozilla.com/D306420
214 lines
7.9 KiB
Python
214 lines
7.9 KiB
Python
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
|
import logging
|
|
import os
|
|
import re
|
|
from typing import Optional
|
|
|
|
import mozpack.path as mozpath
|
|
import taskgraph
|
|
from taskgraph.transforms.base import TransformSequence
|
|
from taskgraph.util import json
|
|
from taskgraph.util.docker import create_context_tar, generate_context_hash
|
|
from taskgraph.util.schema import Schema
|
|
|
|
from gecko_taskgraph.util.docker import (
|
|
image_path,
|
|
)
|
|
|
|
from .. import GECKO
|
|
from .task import TaskDescriptionSchema
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
CONTEXTS_DIR = "docker-contexts"
|
|
|
|
DIGEST_RE = re.compile("^[0-9a-f]{64}$")
|
|
|
|
IMAGE_BUILDER_IMAGE = (
|
|
"mozillareleases/image_builder:6.0.0"
|
|
"@sha256:"
|
|
"734c03809c83c716c1460ed3e00519d79b14d117343d3c556cbd9218a2e7f094"
|
|
)
|
|
|
|
transforms = TransformSequence()
|
|
|
|
|
|
class DockerImageSchema(Schema, kw_only=True):
|
|
# Name of the docker image.
|
|
name: str
|
|
# Name of the parent docker image.
|
|
parent: Optional[str] = None
|
|
# Treeherder symbol.
|
|
symbol: str
|
|
# relative path (from config.path) to the file the docker image was defined
|
|
# in.
|
|
task_from: Optional[str] = None
|
|
# Arguments to use for the Dockerfile.
|
|
args: Optional[dict[str, str]] = None
|
|
# Name of the docker image definition under taskcluster/docker, when
|
|
# different from the docker image name.
|
|
definition: Optional[str] = None
|
|
# List of package tasks this docker image depends on.
|
|
packages: Optional[list[str]] = None
|
|
arch: Optional[str] = None
|
|
# information for indexing this build so its artifacts can be discovered
|
|
index: TaskDescriptionSchema.__annotations__["index"] = None
|
|
# Whether this image should be cached based on inputs.
|
|
cache: Optional[bool] = None
|
|
run_on_repo_type: TaskDescriptionSchema.__annotations__["run_on_repo_type"] = None
|
|
|
|
|
|
transforms.add_validate(DockerImageSchema)
|
|
|
|
|
|
@transforms.add
|
|
def fill_template(config, tasks):
|
|
if not taskgraph.fast and config.write_artifacts:
|
|
if not os.path.isdir(CONTEXTS_DIR):
|
|
os.makedirs(CONTEXTS_DIR)
|
|
|
|
for task in tasks:
|
|
image_name = task.pop("name")
|
|
job_symbol = task.pop("symbol")
|
|
args = task.pop("args", {})
|
|
packages = task.pop("packages", [])
|
|
parent = task.pop("parent", None)
|
|
|
|
for p in packages:
|
|
if f"packages-{p}" not in config.kind_dependencies_tasks:
|
|
raise Exception(
|
|
f"Missing package job for {config.kind}-{image_name}: {p}"
|
|
)
|
|
|
|
if not taskgraph.fast:
|
|
context_path = mozpath.relpath(image_path(image_name), GECKO)
|
|
if config.write_artifacts:
|
|
context_file = os.path.join(CONTEXTS_DIR, f"{image_name}.tar.gz")
|
|
logger.info(f"Writing {context_file} for docker image {image_name}")
|
|
context_hash = create_context_tar(
|
|
GECKO, context_path, context_file, args
|
|
)
|
|
else:
|
|
context_hash = generate_context_hash(GECKO, context_path, args)
|
|
else:
|
|
if config.write_artifacts:
|
|
raise Exception("Can't write artifacts if `taskgraph.fast` is set.")
|
|
context_hash = "0" * 40
|
|
digest_data = [context_hash]
|
|
digest_data += [json.dumps(args, sort_keys=True)]
|
|
|
|
description = f"Build the docker image {image_name} for use by dependent tasks"
|
|
|
|
args["DOCKER_IMAGE_PACKAGES"] = " ".join(f"<{p}>" for p in packages)
|
|
|
|
# Adjust the zstandard compression level based on the execution level.
|
|
# We use faster compression for level 1 because we care more about
|
|
# end-to-end times. We use slower/better compression for other levels
|
|
# because images are read more often and it is worth the trade-off to
|
|
# burn more CPU once to reduce image size.
|
|
zstd_level = "3" if int(config.params["level"]) == 1 else "10"
|
|
|
|
if task.get("arch", "") == "arm64":
|
|
worker_type = "images-aarch64"
|
|
else:
|
|
worker_type = "images"
|
|
|
|
# include some information that is useful in reconstructing this task
|
|
# from JSON
|
|
taskdesc = {
|
|
"label": f"{config.kind}-{image_name}",
|
|
"description": description,
|
|
"attributes": {
|
|
"image_name": image_name,
|
|
"artifact_prefix": "public",
|
|
},
|
|
"always-target": True,
|
|
"expiration-policy": "long",
|
|
"scopes": [],
|
|
"treeherder": {
|
|
"symbol": job_symbol,
|
|
"platform": "taskcluster-images/opt",
|
|
"kind": "other",
|
|
"tier": 1,
|
|
},
|
|
"run-on-projects": [],
|
|
"run-on-repo-type": task.get("run-on-repo-type", ["git", "hg"]),
|
|
"worker-type": worker_type,
|
|
"worker": {
|
|
"implementation": "docker-worker",
|
|
"os": "linux",
|
|
"artifacts": [
|
|
{
|
|
"type": "file",
|
|
"path": "/workspace/out/image.tar.zst",
|
|
"name": "public/image.tar.zst",
|
|
}
|
|
],
|
|
"env": {
|
|
"CONTEXT_TASK_ID": {"task-reference": "<decision>"},
|
|
"CONTEXT_PATH": f"public/docker-contexts/{image_name}.tar.gz",
|
|
"HASH": context_hash,
|
|
"PROJECT": config.params["project"],
|
|
"IMAGE_NAME": image_name,
|
|
"DOCKER_IMAGE_ZSTD_LEVEL": zstd_level,
|
|
"DOCKER_BUILD_ARGS": {"task-reference": json.dumps(args)},
|
|
"GECKO_BASE_REPOSITORY": config.params["base_repository"],
|
|
"GECKO_HEAD_REPOSITORY": config.params["head_repository"],
|
|
"GECKO_HEAD_REV": config.params["head_rev"],
|
|
},
|
|
"chain-of-trust": True,
|
|
"max-run-time": 7200,
|
|
# FIXME: We aren't currently propagating the exit code
|
|
},
|
|
}
|
|
|
|
worker = taskdesc["worker"]
|
|
|
|
# image_builder_arm64 is built `FROM scratch` on an amd64 worker (only
|
|
# its binaries are cross-compiled via the ARCH build arg), so kaniko has
|
|
# no base image to infer the architecture from and defaults it to amd64.
|
|
# Tell kaniko the real target architecture so the image metadata is
|
|
# correct. Other arm64 images build natively from arm64 base images and
|
|
# don't need this.
|
|
if image_name == "image_builder_arm64":
|
|
worker["env"]["TARGET_ARCH"] = "arm64"
|
|
|
|
if image_name == "image_builder":
|
|
worker["docker-image"] = IMAGE_BUILDER_IMAGE
|
|
digest_data.append(f"image-builder-image:{IMAGE_BUILDER_IMAGE}")
|
|
else:
|
|
if task.get("arch", "") == "arm64":
|
|
image_builder = "image_builder_arm64"
|
|
else:
|
|
image_builder = "image_builder"
|
|
worker["docker-image"] = {"in-tree": image_builder}
|
|
deps = taskdesc.setdefault("dependencies", {})
|
|
deps["docker-image"] = f"{config.kind}-{image_builder}"
|
|
|
|
if packages:
|
|
deps = taskdesc.setdefault("dependencies", {})
|
|
for p in sorted(packages):
|
|
deps[p] = f"packages-{p}"
|
|
|
|
if parent:
|
|
deps = taskdesc.setdefault("dependencies", {})
|
|
deps["parent"] = f"{config.kind}-{parent}"
|
|
worker["env"]["PARENT_TASK_ID"] = {
|
|
"task-reference": "<parent>",
|
|
}
|
|
if "index" in task:
|
|
taskdesc["index"] = task["index"]
|
|
|
|
if task.get("cache", True) and not taskgraph.fast:
|
|
taskdesc["cache"] = {
|
|
"type": "docker-images.v2",
|
|
"name": image_name,
|
|
"digest-data": digest_data,
|
|
}
|
|
|
|
yield taskdesc
|