From daf18052f9d89405102efb79d7923b0051597207 Mon Sep 17 00:00:00 2001 From: Malte Poll Date: Mon, 3 Apr 2023 18:13:17 +0200 Subject: [PATCH] bazel: rules to handle container images --- BUILD.bazel | 5 + bazel/devbuild/BUILD.bazel | 2 + .../prepare_developer_workspace.sh.in | 3 + bazel/oci/BUILD.bazel | 0 bazel/oci/containers.bzl | 135 ++++++++++ bazel/oci/pin.bzl | 230 ++++++++++++++++++ bazel/release/BUILD.bazel | 60 +++++ bazel/settings/BUILD.bazel | 8 + bazel/toolchains/container_images.bzl | 16 ++ 9 files changed, 459 insertions(+) create mode 100644 bazel/oci/BUILD.bazel create mode 100644 bazel/oci/containers.bzl create mode 100644 bazel/oci/pin.bzl create mode 100644 bazel/release/BUILD.bazel create mode 100644 bazel/toolchains/container_images.bzl diff --git a/BUILD.bazel b/BUILD.bazel index c50c54e54..a98d73583 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -26,6 +26,11 @@ alias( actual = "//bazel/devbuild:devbuild", ) +alias( + name = "push", + actual = "//bazel/release:push", +) + # These magic Gazelle commands need to be in the top-level BUILD file. # gazelle:map_kind go_test go_test //bazel/go:go_test.bzl # gazelle:prefix github.com/edgelesssys/constellation/v2 diff --git a/bazel/devbuild/BUILD.bazel b/bazel/devbuild/BUILD.bazel index f6fa68537..1746a5328 100644 --- a/bazel/devbuild/BUILD.bazel +++ b/bazel/devbuild/BUILD.bazel @@ -3,6 +3,7 @@ load("//bazel/sh:def.bzl", "sh_template") sh_template( name = "devbuild", data = [ + "//bazel/release:container_sums", "//bootstrapper/cmd/bootstrapper:bootstrapper_linux_amd64", "//cli:cli_oss_host", "//debugd/cmd/cdbg:cdbg_host", @@ -12,6 +13,7 @@ sh_template( "@@BOOTSTRAPPER@@": "$(rootpath //bootstrapper/cmd/bootstrapper:bootstrapper_linux_amd64)", "@@CDBG@@": "$(rootpath //debugd/cmd/cdbg:cdbg_host)", "@@CLI@@": "$(rootpath //cli:cli_oss_host)", + "@@CONTAINER_SUMS@@": "$(rootpath //bazel/release:container_sums)", "@@UPGRADE_AGENT@@": "$(rootpath //upgrade-agent/cmd:upgrade_agent_linux_amd64)", }, template = "prepare_developer_workspace.sh.in", diff --git a/bazel/devbuild/prepare_developer_workspace.sh.in b/bazel/devbuild/prepare_developer_workspace.sh.in index 186ac33b6..0710eadc3 100755 --- a/bazel/devbuild/prepare_developer_workspace.sh.in +++ b/bazel/devbuild/prepare_developer_workspace.sh.in @@ -22,6 +22,8 @@ cli=$(realpath @@CLI@@) stat "${cli}" >> /dev/null cdbg=$(realpath @@CDBG@@) stat "${cdbg}" >> /dev/null +container_sums=$(realpath @@CONTAINER_SUMS@@) +stat "${container_sums}" >> /dev/null cd "${BUILD_WORKING_DIRECTORY}" @@ -53,3 +55,4 @@ ln -sf "$(replace_prefix "${host_cache}" "${builder_cache}" "${bootstrapper}")" ln -sf "$(replace_prefix "${host_cache}" "${builder_cache}" "${upgrade_agent}")" "${workdir}/upgrade-agent" ln -sf "$(replace_prefix "${host_cache}" "${builder_cache}" "${cli}")" "${workdir}/constellation" ln -sf "$(replace_prefix "${host_cache}" "${builder_cache}" "${cdbg}")" "${workdir}/cdbg" +ln -sf "$(replace_prefix "${host_cache}" "${builder_cache}" "${container_sums}")" "${workdir}/container_sums.sha256" diff --git a/bazel/oci/BUILD.bazel b/bazel/oci/BUILD.bazel new file mode 100644 index 000000000..e69de29bb diff --git a/bazel/oci/containers.bzl b/bazel/oci/containers.bzl new file mode 100644 index 000000000..aba550a93 --- /dev/null +++ b/bazel/oci/containers.bzl @@ -0,0 +1,135 @@ +""" +This module holds the definitions of the containers that are built. +""" + +load("@rules_oci//oci:defs.bzl", _oci_push = "oci_push", _oci_tarball = "oci_tarball") +load("//bazel/oci:pin.bzl", "oci_sum") + +_default_registry = "ghcr.io" +_default_prefix = "edgelesssys/constellation" + +def containers(): + return [ + { + "identifier": "joinService", + "image_name": "join-service", + "name": "joinservice", + "oci": "//joinservice/cmd:joinservice", + "prefix": _default_prefix, + "registry": _default_registry, + "tag_file": "//bazel/settings:tag", + "used_by": ["helm"], + }, + { + "identifier": "keyService", + "image_name": "key-service", + "name": "keyservice", + "oci": "//keyservice/cmd:keyservice", + "prefix": _default_prefix, + "registry": _default_registry, + "tag_file": "//bazel/settings:tag", + "used_by": ["helm"], + }, + { + "identifier": "verificationService", + "image_name": "verification-service", + "name": "verificationservice", + "oci": "//verify/cmd:verificationservice", + "prefix": _default_prefix, + "registry": _default_registry, + "tag_file": "//bazel/settings:tag", + "used_by": ["helm"], + }, + { + "identifier": "constellationNodeOperator", + "image_name": "node-operator", + "name": "nodeoperator", + "oci": "//operators/constellation-node-operator:node_operator", + "prefix": _default_prefix, + "registry": _default_registry, + "tag_file": "//bazel/settings:tag", + "used_by": ["helm"], + }, + { + "identifier": "qemuMetadata", + "image_name": "qemu-metadata-api", + "name": "qemumetadata", + "oci": "//hack/qemu-metadata-api:qemumetadata", + "prefix": _default_prefix, + "registry": _default_registry, + "tag_file": "//bazel/settings:tag", + "used_by": ["config"], + }, + { + "identifier": "libvirt", + "image_name": "libvirt", + "name": "libvirt", + "oci": "//cli/internal/libvirt:constellation_libvirt", + "prefix": _default_prefix, + "registry": _default_registry, + "tag_file": "//bazel/settings:tag", + "used_by": ["config"], + }, + ] + +def helm_containers(): + return [container for container in containers() if "helm" in container["used_by"]] + +def config_containers(): + return [container for container in containers() if "config" in container["used_by"]] + +def container_sum(name, oci, registry, prefix, image_name, **kwargs): + tag = kwargs.get("tag", None) + tag_file = kwargs.get("tag_file", None) + oci_sum( + name = name + "_sum", + oci = oci, + registry = registry, + prefix = prefix, + image_name = image_name, + tag = tag, + tag_file = tag_file, + visibility = ["//visibility:public"], + ) + +def oci_push(name, image, registry, image_name, **kwargs): + """oci_push pushes an OCI image to a registry. + + Args: + name: The name of the target. + image: The OCI image to push. + registry: The registry to push to. + image_name: The name of the image. + **kwargs: Additional arguments to pass to oci_push. + """ + prefix = kwargs.pop("prefix", None) + tag = kwargs.pop("tag", None) + tag_file = kwargs.pop("tag_file", None) + if prefix == None: + repository = registry + "/" + image_name + else: + repository = registry + "/" + prefix + "/" + image_name + _oci_push( + name = name, + image = image, + repository = repository, + tag = tag, + tag_file = tag_file, + visibility = ["//visibility:public"], + **kwargs + ) + +# TODO(malt3): allow repotags (registry + tag) to be read from a file. +def oci_tarball(name, image): + """oci_tarball creates a tarball of an OCI image. + + Args: + name: The name of the target. + image: The OCI image to create a tarball of. + """ + _oci_tarball( + name = name, + image = image, + repotags = [], + visibility = ["//visibility:public"], + ) diff --git a/bazel/oci/pin.bzl b/bazel/oci/pin.bzl new file mode 100644 index 000000000..01e7ae591 --- /dev/null +++ b/bazel/oci/pin.bzl @@ -0,0 +1,230 @@ +""" +This module contains rules and macros for pinning oci images. +""" + +load("@aspect_bazel_lib//lib:jq.bzl", "jq") +load("@bazel_skylib//lib:types.bzl", "types") + +def stamp_tags(name, repotags, **kwargs): + if not types.is_list(repotags): + fail("repotags should be a list") + _maybe_quote = lambda x: x if "\"" in x else "\"{}\"".format(x) + jq( + name = name, + srcs = [], + out = "_{}.tags.txt".format(name), + args = ["--raw-output"], + filter = "|".join([ + "$ARGS.named.STAMP as $stamp", + ",".join([_maybe_quote(t) for t in repotags]), + ]), + **kwargs + ) + +def _oci_go_source_impl(ctx): + oci = ctx.file.oci + inputs = [oci] + if ctx.attr.tag_file: + inputs.append(ctx.file.tag_file) + output = ctx.actions.declare_file(ctx.label.name + ".go") + args = [ + "codegen", + "--image-registry", + ctx.attr.registry, + "--image-prefix", + ctx.attr.prefix, + "--image-name", + ctx.attr.image_name, + "--oci-path", + oci.path, + "--package", + ctx.attr.package, + "--identifier", + ctx.attr.identifier, + "--output", + output.path, + ] + if ctx.attr.tag: + args.append("--image-tag") + args.append(ctx.attr.tag) + if ctx.attr.tag_file: + args.append("--image-tag-file") + args.append(ctx.file.tag_file.path) + + ctx.actions.run( + inputs = inputs, + arguments = args, + outputs = [output], + executable = ctx.executable._oci_pin, + mnemonic = "OCIPin", + progress_message = "Generating OCI pin Go source %{label}", + ) + + return [DefaultInfo( + files = depset([output]), + )] + +_go_source_attrs = { + "identifier": attr.string( + mandatory = True, + doc = "Identifier to use for the generated Go source.", + ), + "image_name": attr.string( + mandatory = True, + doc = "Image name to use for the generated Go source.", + ), + "oci": attr.label( + mandatory = True, + allow_single_file = True, + doc = "OCI image to extract the digest from.", + ), + "package": attr.string( + mandatory = True, + doc = "Package to use for the generated Go source.", + ), + "prefix": attr.string( + doc = "Prefix to use for the generated Go source.", + ), + "registry": attr.string( + mandatory = True, + doc = "Registry to use for the generated Go source.", + ), + "tag": attr.string( + doc = "OCI image tag to use for the generated Go source.", + ), + "tag_file": attr.label( + allow_single_file = True, + doc = "OCI image tag file to use for the generated Go source.", + ), + "_oci_pin": attr.label( + allow_single_file = True, + executable = True, + cfg = "exec", + default = Label("//hack/oci-pin"), + ), +} + +oci_go_source = rule( + implementation = _oci_go_source_impl, + attrs = _go_source_attrs, +) + +def _oci_sum_impl(ctx): + oci = ctx.file.oci + inputs = [oci] + if ctx.attr.tag_file: + inputs.append(ctx.file.tag_file) + output = ctx.actions.declare_file(ctx.label.name + ".sha256") + args = [ + "sum", + "--image-name", + ctx.attr.image_name, + "--oci-path", + oci.path, + "--output", + output.path, + "--registry", + ctx.attr.registry, + ] + if ctx.attr.prefix: + args.append("--prefix") + args.append(ctx.attr.prefix) + if ctx.attr.tag: + args.append("--image-tag") + args.append(ctx.attr.tag) + if ctx.attr.tag_file: + args.append("--image-tag-file") + args.append(ctx.file.tag_file.path) + + ctx.actions.run( + inputs = inputs, + arguments = args, + outputs = [output], + executable = ctx.executable._oci_pin, + mnemonic = "OCISum", + progress_message = "Generating OCI sum file %{label}", + ) + + return [DefaultInfo( + files = depset([output]), + )] + +_sum_attrs = { + "image_name": attr.string( + mandatory = True, + doc = "Image name to use for the sum entry.", + ), + "oci": attr.label( + mandatory = True, + allow_single_file = True, + doc = "OCI image to extract the digest from.", + ), + "prefix": attr.string( + doc = "Prefix to use for the sum entry.", + ), + "registry": attr.string( + mandatory = True, + doc = "Registry to use for the sum entry.", + ), + "tag": attr.string( + doc = "OCI image tag to use for the sum entry.", + ), + "tag_file": attr.label( + allow_single_file = True, + doc = "OCI image tag file to use for the sum entry.", + ), + "_oci_pin": attr.label( + allow_single_file = True, + executable = True, + cfg = "exec", + default = Label("//hack/oci-pin"), + ), +} + +oci_sum = rule( + implementation = _oci_sum_impl, + attrs = _sum_attrs, +) + +def _oci_sum_merge_impl(ctx): + # TODO: select list of labels + inputs = ctx.files.sums + output = ctx.actions.declare_file(ctx.label.name + ".sha256") + args = [ + "merge", + "--output", + output.path, + ] + for sum in ctx.files.sums: + args.append("--input") + args.append(sum.path) + + ctx.actions.run( + inputs = inputs, + arguments = args, + outputs = [output], + executable = ctx.executable._oci_pin, + mnemonic = "OCISumMerge", + progress_message = "Merging OCI sum files %{label}", + ) + + return [DefaultInfo( + files = depset([output]), + )] + +_sum_merge_attrs = { + "sums": attr.label_list( + doc = "Sum files to merge for the combined sum entry.", + ), + "_oci_pin": attr.label( + allow_single_file = True, + executable = True, + cfg = "exec", + default = Label("//hack/oci-pin"), + ), +} + +oci_sum_merge = rule( + implementation = _oci_sum_merge_impl, + attrs = _sum_merge_attrs, +) diff --git a/bazel/release/BUILD.bazel b/bazel/release/BUILD.bazel new file mode 100644 index 000000000..a35e10bab --- /dev/null +++ b/bazel/release/BUILD.bazel @@ -0,0 +1,60 @@ +""" +This folder contains labels used to collect release artifacts. +""" + +load("@com_github_ash2k_bazel_tools//multirun:def.bzl", "multirun") +load("//bazel/oci:containers.bzl", "container_sum", "containers", "oci_push", "oci_tarball") +load("//bazel/oci:pin.bzl", "oci_sum_merge") + +[ + oci_tarball( + name = container["name"] + "_tar", + image = container["oci"], + ) + for container in containers() +] + +[ + container_sum( + name = container["name"], + image_name = container["image_name"], + oci = container["oci"], + prefix = container["prefix"], + registry = container["registry"], + tag_file = container["tag_file"], + ) + for container in containers() +] + +oci_sum_merge( + name = "container_sums", + sums = [ + ":%s_sum" % container["name"] + for container in containers() + ], + visibility = ["//visibility:public"], +) + +# TODO(malt3): use config setting to allow devs the use of custom registries +# https://www.grahambrooks.com/software-development/2021/08/30/user-defined-bazel-arguments.html +[ + oci_push( + name = container["name"] + "_push", + image = container["oci"], + image_name = container["image_name"], + prefix = container["prefix"], + registry = container["registry"], + repotags = container["tag_file"], + ) + for container in containers() +] + +multirun( + name = "push", + commands = [ + ":" + container["name"] + "_push" + for container in containers() + ], + jobs = 0, # execute in parallel + visibility = ["//visibility:public"], +) diff --git a/bazel/settings/BUILD.bazel b/bazel/settings/BUILD.bazel index e2f05fe93..f3f7d3ead 100644 --- a/bazel/settings/BUILD.bazel +++ b/bazel/settings/BUILD.bazel @@ -1,4 +1,5 @@ load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_flag") +load("//bazel/oci:pin.bzl", "stamp_tags") bool_flag( # tpm_simulator is used to decide if the TPM simulator should be enabled @@ -44,3 +45,10 @@ config_setting( flag_values = {":select_never": "True"}, visibility = ["//visibility:public"], ) + +stamp_tags( + # generates a container image version tag based on the version stamp + name = "tag", + repotags = [""""v"+($stamp.STABLE_STAMP_VERSION // "0.0.0")"""], + visibility = ["//visibility:public"], +) diff --git a/bazel/toolchains/container_images.bzl b/bazel/toolchains/container_images.bzl new file mode 100644 index 000000000..f85da56fe --- /dev/null +++ b/bazel/toolchains/container_images.bzl @@ -0,0 +1,16 @@ +""" +This file contains external container images used by the project. +""" + +load("@rules_oci//oci:pull.bzl", "oci_pull") + +def containter_image_deps(): + oci_pull( + name = "distroless_static", + digest = "sha256:c3c3d0230d487c0ad3a0d87ad03ee02ea2ff0b3dcce91ca06a1019e07de05f12", + image = "gcr.io/distroless/static", + platforms = [ + "linux/amd64", + "linux/arm64", + ], + )