From 461b15bd1e984dbe0a95ec383f599e39476c4284 Mon Sep 17 00:00:00 2001 From: Richard Nguyen Date: Mon, 3 Jun 2024 09:24:48 -0400 Subject: [PATCH] feat: stamp the "tag" attribute for oci_push (#59) --- docs/docs.md | 5 +-- oci/BUILD.bazel | 1 + oci/providers.bzl | 1 + oci/push.bzl | 86 ++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 83 insertions(+), 10 deletions(-) diff --git a/docs/docs.md b/docs/docs.md index 959693b..3a177b2 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -102,7 +102,7 @@ oci_pull(name, digest -oci_push(name, headers, manifest, registry, repository, tag, x_meta_headers) +oci_push(name, headers, manifest, registry, repository, stamp, tag, x_meta_headers) @@ -119,7 +119,8 @@ oci_push(name, headers< | manifest | A manifest to push to a registry. If an OCILayout index, then push all artifacts with a 'org.opencontainers.image.ref.name' annotation. | Label | optional | None | | registry | A registry host to push to, if not present consult the toolchain. | String | optional | "" | | repository | A repository to push to, if not present consult the toolchain. | String | optional | "" | -| tag | (optional) A tag to include in the target reference. This will not be included on child images." | String | optional | "" | +| stamp | Whether to encode build information into the output. Possible values:

- stamp = 1: Always stamp the build information into the output, even in [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. This setting should be avoided, since it is non-deterministic. It potentially causes remote cache misses for the target and any downstream actions that depend on the result. - stamp = 0: Never stamp, instead replace build information by constant values. This gives good build result caching. - stamp = -1: Embedding of build information is controlled by the [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag. Stamped targets are not rebuilt unless their dependencies change. | Integer | optional | -1 | +| tag | (optional) A tag to include in the target reference. This will not be included on child images.

Subject to [$(location)](https://bazel.build/reference/be/make-variables#predefined_label_variables) and ["Make variable"](https://bazel.build/reference/be/make-variabmes) substitution.

**Stamping**

You can use values produced by the workspace status command in your tag. To do this write a script that prints key-value pairs separated by spaces, e.g.

sh                 #!/usr/bin/env bash                 echo "STABLE_KEY1 VALUE1"                 echo "STABLE_KEY2 VALUE2"                 


You can reference these keys in tag using curly braces,

python                 oci_push(                     name = "push",                     tag = "v1.0-{STABLE_KEY1}",                 )                 
| String | optional | "" | | x_meta_headers | (optional) A list of key/values to to be sent to the registry as headers with an X-Meta- prefix. | Dictionary: String -> String | optional | {} | diff --git a/oci/BUILD.bazel b/oci/BUILD.bazel index 4268ab9..6443160 100755 --- a/oci/BUILD.bazel +++ b/oci/BUILD.bazel @@ -69,6 +69,7 @@ bzl_library( srcs = ["push.bzl"], visibility = ["//visibility:public"], deps = [ + "@aspect_bazel_lib//lib:stamping", "@com_github_datadog_rules_oci//oci:debug_flag", "@com_github_datadog_rules_oci//oci:providers", ], diff --git a/oci/providers.bzl b/oci/providers.bzl index 4854416..8057d9a 100755 --- a/oci/providers.bzl +++ b/oci/providers.bzl @@ -4,6 +4,7 @@ OCIReferenceInfo = provider( "registry": "the URI where the artifact is stored", "repository": "a namespace for an artifact", "tag": "a organizational reference within a repository", + "tag_file": "a file containing the organizational reference within a repository", "digest": "a file containing the digest of the artifact", }, ) diff --git a/oci/push.bzl b/oci/push.bzl index 9bd933b..78844e5 100755 --- a/oci/push.bzl +++ b/oci/push.bzl @@ -1,3 +1,4 @@ +load("@aspect_bazel_lib//lib:stamping.bzl", "STAMP_ATTRS", "maybe_stamp") load("@com_github_datadog_rules_oci//oci:providers.bzl", "OCIDescriptor", "OCILayout", "OCIReferenceInfo") load("@com_github_datadog_rules_oci//oci:debug_flag.bzl", "DebugInfo") @@ -11,7 +12,54 @@ def _oci_push_impl(ctx): repository = ctx.attr.repository, ) - tag = ctx.expand_make_variables("tag", ctx.attr.tag, {}) + tag_file = ctx.actions.declare_file("{name}.tag".format(name = ctx.label.name)) + ctx.actions.write( + output = tag_file, + content = ctx.expand_make_variables("tag", ctx.attr.tag, {}), + ) + + stamp = maybe_stamp(ctx) + if stamp: + unstamped_tag_file = tag_file + tag_file = ctx.actions.declare_file("{name}.stamped.tag".format(name = ctx.label.name)) + + args = ctx.actions.args() + args.add_all([ + unstamped_tag_file, + tag_file, + stamp.stable_status_file, + stamp.volatile_status_file, + ]) + + ctx.actions.run_shell( + inputs = [ + unstamped_tag_file, + stamp.stable_status_file, + stamp.volatile_status_file, + ], + outputs = [ + tag_file, + ], + arguments = [args], + command = """#!/usr/bin/env bash +scratch=$(cat $1) +shift + +out=$1 +shift + +for file in $@ +do + while read -r key value + do + # Replace the keys with their corresponding values in the scratch output + scratch=${scratch//\\{$key\\}/$value} + done <$file +done + +>$out echo -n "$scratch" +""", + ) digest_file = ctx.actions.declare_file("{name}.digest".format(name = ctx.label.name)) ctx.actions.run( @@ -47,7 +95,7 @@ def _oci_push_impl(ctx): --layout-relative {root} \\ --desc {desc} \\ --target-ref {ref} \\ - --parent-tag \"{tag}\" \\ + --parent-tag \"$(cat {tag})\" \\ {headers} \\ {xheaders} \\ @@ -59,12 +107,12 @@ def _oci_push_impl(ctx): layout = layout.blob_index.short_path, desc = ctx.attr.manifest[OCIDescriptor].descriptor_file.short_path, ref = ref, - tag = tag, debug = str(ctx.attr._debug[DebugInfo].debug), headers = headers, xheaders = xheaders, post_scripts = "\n".join(["./" + hook.short_path for hook in toolchain.post_push_hooks]), digest = digest_file.short_path, + tag = tag_file.short_path, ), output = ctx.outputs.executable, is_executable = True, @@ -73,14 +121,15 @@ def _oci_push_impl(ctx): return [ DefaultInfo( runfiles = ctx.runfiles( - files = layout.files.to_list() + - [toolchain.sdk.ocitool, ctx.attr.manifest[OCIDescriptor].descriptor_file, layout.blob_index, digest_file] + toolchain.post_push_hooks, + files = layout.files.to_list() + toolchain.post_push_hooks + + [toolchain.sdk.ocitool, ctx.attr.manifest[OCIDescriptor].descriptor_file, layout.blob_index, digest_file, tag_file], ), ), OCIReferenceInfo( registry = ctx.attr.registry, repository = ctx.attr.repository, digest = digest_file, + tag_file = tag_file, ), ] @@ -90,7 +139,7 @@ oci_push = rule( """, implementation = _oci_push_impl, executable = True, - attrs = { + attrs = dict({ "manifest": attr.label( doc = """ A manifest to push to a registry. If an OCILayout index, then @@ -111,7 +160,28 @@ oci_push = rule( ), "tag": attr.string( doc = """ - (optional) A tag to include in the target reference. This will not be included on child images." + (optional) A tag to include in the target reference. This will not be included on child images. + + Subject to [$(location)](https://bazel.build/reference/be/make-variables#predefined_label_variables) and ["Make variable"](https://bazel.build/reference/be/make-variabmes) substitution. + + **Stamping** + + You can use values produced by the workspace status command in your tag. To do this write a script that prints key-value pairs separated by spaces, e.g. + + ```sh + #!/usr/bin/env bash + echo "STABLE_KEY1 VALUE1" + echo "STABLE_KEY2 VALUE2" + ``` + + You can reference these keys in `tag` using curly braces, + + ```python + oci_push( + name = "push", + tag = "v1.0-{STABLE_KEY1}", + ) + ``` """, ), "headers": attr.string_dict( @@ -128,7 +198,7 @@ oci_push = rule( default = "//oci:debug", providers = [DebugInfo], ), - }, + }, **STAMP_ATTRS), provides = [ OCIReferenceInfo, ],