diff --git a/docs/image.md b/docs/image.md index 98522cb8..9fcc7166 100644 --- a/docs/image.md +++ b/docs/image.md @@ -11,8 +11,8 @@ load("@rules_oci//oci:defs.bzl", ...) ## oci_image_rule
-oci_image_rule(name, annotations, architecture, base, cmd, entrypoint, env, exposed_ports, labels, - os, resource_set, tars, user, variant, volumes, workdir) +oci_image_rule(name, annotations, architecture, base, cmd, created, entrypoint, env, exposed_ports, + labels, os, resource_set, tars, user, variant, volumes, workdir)Build an OCI compatible container image. @@ -72,6 +72,7 @@ oci_image( | architecture | The CPU architecture which the binaries in this image are built to run on. eg: `arm64`, `arm`, `amd64`, `s390x`. See $GOARCH documentation for possible values: https://go.dev/doc/install/source#environment | String | optional | `""` | | base | Label to an oci_image target to use as the base. | Label | optional | `None` | | cmd | A file containing a newline separated list to be used as the `command & args` of the container. These values act as defaults and may be replaced by any specified when creating a container. | Label | optional | `None` | +| created | The datetime when the image was created. This can be a file containing a string in the format `YYYY-MM-DDTHH:MM:SS.sssZ` Typically, you'd provide a file containing a stamp variable replaced by the datetime of the build when executed with `--stamp`. | Label | optional | `None` | | entrypoint | A file containing a newline separated list to be used as the `entrypoint` to execute when the container starts. These values act as defaults and may be replaced by an entrypoint specified when creating a container. NOTE: Setting this attribute will reset the `cmd` attribute | Label | optional | `None` | | env | A file containing the default values for the environment variables of the container. These values act as defaults and are merged with any specified when creating a container. Entries replace the base environment variables if any of the entries has conflicting keys. To merge entries with keys specified in the base, `${KEY}` or `$KEY` syntax may be used. | Label | optional | `None` | | exposed_ports | A file containing a comma separated list of exposed ports. (e.g. 2000/tcp, 3000/udp or 4000. No protocol defaults to tcp). | Label | optional | `None` | @@ -90,7 +91,7 @@ oci_image( ## oci_image
-oci_image(name, labels, annotations, env, cmd, entrypoint, exposed_ports, volumes, kwargs) +oci_image(name, created, labels, annotations, env, cmd, entrypoint, exposed_ports, volumes, kwargs)Macro wrapper around [oci_image_rule](#oci_image_rule). @@ -113,6 +114,7 @@ This is similar to the same-named target created by rules_docker's `container_im | Name | Description | Default Value | | :------------- | :------------- | :------------- | | name | name of resulting oci_image_rule | none | +| created | Label to a file containing a single datetime string. The content of that file is used as the value of the `created` field in the image config. | `None` | | labels | Labels for the image config. May either be specified as a file, as with the documentation above, or a dict of strings to specify values inline. | `None` | | annotations | Annotations for the image config. May either be specified as a file, as with the documentation above, or a dict of strings to specify values inline. | `None` | | env | Environment variables provisioned by default to the running container. May either be specified as a file, as with the documentation above, or a dict of strings to specify values inline. | `None` | diff --git a/e2e/smoke/BUILD.bazel b/e2e/smoke/BUILD.bazel index c4918705..3079c557 100644 --- a/e2e/smoke/BUILD.bazel +++ b/e2e/smoke/BUILD.bazel @@ -1,8 +1,17 @@ +load("@aspect_bazel_lib//lib:diff_test.bzl", "diff_test") load("@aspect_bazel_lib//lib:testing.bzl", "assert_json_matches") load("@bazel_skylib//rules:build_test.bzl", "build_test") load("@bazel_skylib//rules:write_file.bzl", "write_file") load("@container_structure_test//:defs.bzl", "container_structure_test") load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load") +load("@aspect_bazel_lib//lib:expand_template.bzl", "expand_template") + +expand_template( + name = "created", + out = "created.txt", + stamp_substitutions = {"2000-01-01T01:02:03Z": "{{BUILD_ISO8601}}"}, # BUILD_ISO8601 is an imaginary stamp var + template = ["2000-01-01T01:02:03Z"], +) # SMOKE TEST: oci_image oci_image( @@ -12,6 +21,7 @@ oci_image( "--arg1", "--arg2", ], + created = ":created", entrypoint = ["/custom_bin"], env = { "ENV": "/test", @@ -62,6 +72,19 @@ assert_json_matches( filter1 = ".[0].RepoTags", ) +genrule( + name = "docker_created", + srcs = [":tarball.tar"], + outs = ["docker_created.txt"], + cmd = "docker load -i $(location :tarball.tar) && docker inspect --format='{{{{.Created}}}}' {} > $@".format(tags[0]), +) + +diff_test( + name = "test_created", + file1 = ":docker_created", + file2 = "expected_created.txt", +) + # SMOKE TEST: oci_image from an external repo build_test( name = "test_external", diff --git a/e2e/smoke/WORKSPACE.bazel b/e2e/smoke/WORKSPACE.bazel index ce239399..29f83b89 100644 --- a/e2e/smoke/WORKSPACE.bazel +++ b/e2e/smoke/WORKSPACE.bazel @@ -14,6 +14,12 @@ http_archive( url = "https://github.com/aspect-build/bazel-lib/releases/download/v2.7.2/bazel-lib-v2.7.2.tar.gz", ) +load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies", "aspect_bazel_lib_register_toolchains") + +aspect_bazel_lib_dependencies() + +aspect_bazel_lib_register_toolchains() + http_archive( name = "container_structure_test", sha256 = "4fd1e0d4974fb95e06d0e94e6ceaae126382bf958524062db4e582232590b863", diff --git a/e2e/smoke/expected_created.txt b/e2e/smoke/expected_created.txt new file mode 100644 index 00000000..6acd0303 --- /dev/null +++ b/e2e/smoke/expected_created.txt @@ -0,0 +1 @@ +2000-01-01T01:02:03Z diff --git a/oci/defs.bzl b/oci/defs.bzl index fab48105..845e9804 100644 --- a/oci/defs.bzl +++ b/oci/defs.bzl @@ -33,7 +33,17 @@ def _write_nl_seperated_file(name, kind, elems, forwarded_kwargs): ) return label -def oci_image(name, labels = None, annotations = None, env = None, cmd = None, entrypoint = None, exposed_ports = None, volumes = None, **kwargs): +def oci_image( + name, + created = None, + labels = None, + annotations = None, + env = None, + cmd = None, + entrypoint = None, + exposed_ports = None, + volumes = None, + **kwargs): """Macro wrapper around [oci_image_rule](#oci_image_rule). Allows labels and annotations to be provided as a dictionary, in addition to a text file. @@ -49,6 +59,8 @@ def oci_image(name, labels = None, annotations = None, env = None, cmd = None, e Args: name: name of resulting oci_image_rule + created: Label to a file containing a single datetime string. + The content of that file is used as the value of the `created` field in the image config. labels: Labels for the image config. May either be specified as a file, as with the documentation above, or a dict of strings to specify values inline. annotations: Annotations for the image config. @@ -136,6 +148,7 @@ def oci_image(name, labels = None, annotations = None, env = None, cmd = None, e oci_image_rule( name = name, + created = created, annotations = annotations, labels = labels, env = env, diff --git a/oci/private/image.bzl b/oci/private/image.bzl index 99558e91..73a0d573 100644 --- a/oci/private/image.bzl +++ b/oci/private/image.bzl @@ -61,6 +61,11 @@ oci_image( """ _attrs = { "base": attr.label(allow_single_file = True, doc = "Label to an oci_image target to use as the base."), + "created": attr.label(allow_single_file = True, doc = """\ + The datetime when the image was created. This can be a file containing a string in the format `YYYY-MM-DDTHH:MM:SS.sssZ` + Typically, you'd provide a file containing a stamp variable replaced by the datetime of the build + when executed with `--stamp`. + """), "tars": attr.label_list(allow_files = _ACCEPTED_TAR_EXTENSIONS, doc = """\ List of tar files to add to the image as layers. Do not sort this list; the order is preserved in the resulting image. @@ -192,6 +197,10 @@ def _oci_image_impl(ctx): # tars are already added as input above. args.add_joined([layer, descriptor], join_with = "=", format_joined = "--layer=%s") + if ctx.attr.created: + args.add(ctx.file.created.path, format = "--created=%s") + inputs.append(ctx.file.created) + # WARNING: entrypoint should always be added before the cmd argument. # This due to implicit behavior which setting entrypoint deletes `cmd`. # See: https://github.com/bazel-contrib/rules_oci/issues/649 diff --git a/oci/private/image.sh b/oci/private/image.sh index 13f1a480..32cb6ee2 100644 --- a/oci/private/image.sh +++ b/oci/private/image.sh @@ -169,6 +169,9 @@ for ARG in "$@"; do --labels=*) CONFIG=$(jq --rawfile labels "${ARG#--labels=}" '.config.Labels += ($labels | split("\n") | map(select(. | length > 0)) | map(. | split("=")) | map({key: .[0], value: .[1:] | join("=")}) | from_entries)' <<<"$CONFIG") ;; + --created=*) + CONFIG=$(jq --rawfile created "${ARG#--created=}" '.created = $created' <<<"$CONFIG") + ;; --annotations=*) get_manifest | jq --rawfile annotations "${ARG#--annotations=}" \