211 lines
7.5 KiB
Python
211 lines
7.5 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 json
|
|
import logging
|
|
import os
|
|
import re
|
|
|
|
import mozpack.path as mozpath
|
|
import taskgraph
|
|
from taskgraph.transforms.base import TransformSequence
|
|
from taskgraph.util.docker import create_context_tar, generate_context_hash
|
|
from taskgraph.util.schema import Schema
|
|
from voluptuous import Optional, Required
|
|
|
|
from gecko_taskgraph.util.docker import (
|
|
image_path,
|
|
)
|
|
|
|
from .. import GECKO
|
|
from .task import task_description_schema
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
CONTEXTS_DIR = "docker-contexts"
|
|
|
|
DIGEST_RE = re.compile("^[0-9a-f]{64}$")
|
|
|
|
IMAGE_BUILDER_IMAGE = (
|
|
"mozillareleases/image_builder:5.0.0"
|
|
"@sha256:"
|
|
"e510a9a9b80385f71c112d61b2f2053da625aff2b6d430411ac42e424c58953f"
|
|
)
|
|
|
|
transforms = TransformSequence()
|
|
|
|
docker_image_schema = Schema(
|
|
{
|
|
# Name of the docker image.
|
|
Required("name"): str,
|
|
# Name of the parent docker image.
|
|
Optional("parent"): str,
|
|
# Treeherder symbol.
|
|
Required("symbol"): str,
|
|
# relative path (from config.path) to the file the docker image was defined
|
|
# in.
|
|
Optional("task-from"): str,
|
|
# Arguments to use for the Dockerfile.
|
|
Optional("args"): {str: str},
|
|
# Name of the docker image definition under taskcluster/docker, when
|
|
# different from the docker image name.
|
|
Optional("definition"): str,
|
|
# List of package tasks this docker image depends on.
|
|
Optional("packages"): [str],
|
|
Optional("arch"): str,
|
|
Optional(
|
|
"index",
|
|
description="information for indexing this build so its artifacts can be discovered",
|
|
): task_description_schema["index"],
|
|
Optional(
|
|
"cache",
|
|
description="Whether this image should be cached based on inputs.",
|
|
): bool,
|
|
}
|
|
)
|
|
|
|
|
|
transforms.add_validate(docker_image_schema)
|
|
|
|
|
|
@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-gcp-aarch64"
|
|
else:
|
|
worker_type = "images-gcp"
|
|
|
|
# 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": [],
|
|
"worker-type": worker_type,
|
|
"worker": {
|
|
"implementation": "docker-worker",
|
|
"os": "linux",
|
|
"artifacts": [
|
|
{
|
|
"type": "file",
|
|
"path": "/workspace/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
|
|
},
|
|
}
|
|
# Retry for 'funsize-update-generator' if exit status code is -1
|
|
if image_name in ["funsize-update-generator"]:
|
|
taskdesc["worker"]["retry-exit-status"] = [-1]
|
|
|
|
worker = taskdesc["worker"]
|
|
|
|
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
|