From a61c616d549f99afb099230870b3bbb173926655 Mon Sep 17 00:00:00 2001 From: ignormies Date: Tue, 23 Sep 2025 10:13:24 -0700 Subject: [PATCH] Add support for CMD in addition to ENTRYPOINT OCI supports both. We should support both. This is especially useful for _removing_ `CMD` if it is set in base images. Previously there was no way to do this and it would always be appended to whatever `ENTRYPOINT` the image had (per OCI spec) --- docs/docs.md | 5 +-- go/cmd/ocitool/appendlayer_cmd.go | 13 ++++++++ go/cmd/ocitool/main.go | 3 ++ go/pkg/layer/append.go | 4 +++ oci/image.bzl | 54 ++++++++++++++++++++++--------- 5 files changed, 61 insertions(+), 18 deletions(-) diff --git a/docs/docs.md b/docs/docs.md index 8291cd7..5c40b6a 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -142,7 +142,7 @@ The config file named after the rule, os, and arch
 load("@rules_oci//oci:defs.bzl", "oci_image")
 
-oci_image(name, base, annotations, arch, entrypoint, env, labels, layers, os, tars, kwargs)
+oci_image(name, base, annotations, arch, cmd, entrypoint, env, labels, layers, os, tars, kwargs)
 
oci_image @@ -161,7 +161,8 @@ index, then `os` and `arch` will be used to extract the image manifest. | base | A base image, as defined by oci_pull or oci_image. | none | | annotations | OCI Annotations to add to the manifest. | `None` | | arch | Used to extract a manifest from base if base is an index. | `None` | -| entrypoint | A list of entrypoints for the image; these will be inserted into the generated container configuration. | `None` | +| cmd | Default arguments to the entrypoint of the container. If an Entrypoint value is not specified, then the first entry of the Cmd array will be interpreted as the executable to run | `None` | +| entrypoint | A list of arguments to use as the command to execute when the container starts; these will be inserted into the generated OCI image config | `None` | | env | Entries are in the format of `VARNAME=VARVALUE`. These values act as defaults and are merged with any specified when creating a container. | `None` | | labels | Labels that will be applied to the image configuration, as defined in the OCI config. These behave the same way as docker LABEL. In particular, labels from the base image are inherited. An empty value for a label will cause that label to be deleted. For backwards compatibility, if this is not set, then the value of annotations will be used instead. | `None` | | layers | A list of layers defined by oci_image_layer. | `None` | diff --git a/go/cmd/ocitool/appendlayer_cmd.go b/go/cmd/ocitool/appendlayer_cmd.go index e475d75..3d07120 100644 --- a/go/cmd/ocitool/appendlayer_cmd.go +++ b/go/cmd/ocitool/appendlayer_cmd.go @@ -207,6 +207,18 @@ func AppendLayersCmd(c *cli.Context) error { layerDescs = append(layerDescs, tarDesc) } + var cmd *[]string + if cmdPath := c.String("cmd"); cmdPath != "" { + var cmdStruct struct { + Cmd []string `json:"cmd"` + } + err := jsonutil.DecodeFromFile(cmdPath, &cmdStruct) + if err != nil { + return fmt.Errorf("failed to read cmd config file: %w", err) + } + cmd = &cmdStruct.Cmd + } + var entrypoint *[]string if entrypointPath := c.String("entrypoint"); entrypointPath != "" { var entrypointStruct struct { @@ -230,6 +242,7 @@ func AppendLayersCmd(c *cli.Context) error { c.Generic("labels").(*flagutil.KeyValueFlag).Map, c.StringSlice("env"), createdTimestamp, + cmd, entrypoint, targetPlatform, ) diff --git a/go/cmd/ocitool/main.go b/go/cmd/ocitool/main.go index 1aa90ae..3365119 100644 --- a/go/cmd/ocitool/main.go +++ b/go/cmd/ocitool/main.go @@ -130,6 +130,9 @@ var app = &cli.App{ &cli.StringFlag{ Name: "out-layout", }, + &cli.StringFlag{ + Name: "cmd", + }, &cli.StringFlag{ Name: "entrypoint", }, diff --git a/go/pkg/layer/append.go b/go/pkg/layer/append.go index 03e355d..5c89860 100644 --- a/go/pkg/layer/append.go +++ b/go/pkg/layer/append.go @@ -28,6 +28,7 @@ func AppendLayers( labels map[string]string, env []string, created time.Time, + cmd *[]string, entrypoint *[]string, platform ocispec.Platform, ) (ocispec.Descriptor, ocispec.Descriptor, error) { @@ -136,6 +137,9 @@ func AppendLayers( imageConfig.History = append(imageConfig.History, history...) imageConfig.Author = "rules_oci" + if cmd != nil { + imageConfig.Config.Cmd = *cmd + } if entrypoint != nil { imageConfig.Config.Entrypoint = *entrypoint } diff --git a/oci/image.bzl b/oci/image.bzl index fd32f86..5f14bc5 100755 --- a/oci/image.bzl +++ b/oci/image.bzl @@ -4,16 +4,17 @@ load("@aspect_bazel_lib//lib:stamping.bzl", "STAMP_ATTRS", "maybe_stamp") load("@com_github_datadog_rules_oci//oci:providers.bzl", "OCIDescriptor", "OCILayout") def oci_image( - name, - base, - annotations = None, - arch = None, - entrypoint = None, - env = None, - labels = None, - layers = None, - os = None, - tars = None, + name, # type: string + base, # type: Label + annotations = None, # type: list[string] | None + arch = None, # type: string | None + cmd = None, # type: string | list[string] | None + entrypoint = None, # type: string | list[string] | None + env = None, # type: list[string] | None + labels = None, # type: dict[string, string] | None + layers = None, # type: list[Label] | None + os = None, # type: string | None + tars = None, # type: list[Label] **kwargs): """ oci_image @@ -26,8 +27,12 @@ def oci_image( base: A base image, as defined by oci_pull or oci_image. annotations: OCI Annotations to add to the manifest. arch: Used to extract a manifest from base if base is an index. - entrypoint: A list of entrypoints for the image; these will be inserted - into the generated container configuration. + cmd: Default arguments to the entrypoint of the container. If an + Entrypoint value is not specified, then the first entry of the Cmd + array will be interpreted as the executable to run + entrypoint: A list of arguments to use as the command to execute when + the container starts; these will be inserted into the generated OCI + image config env: Entries are in the format of `VARNAME=VARVALUE`. These values act as defaults and are merged with any specified when creating a container. @@ -42,16 +47,18 @@ def oci_image( tars: A list of tars to add as layers. **kwargs: Additional keyword arguments, e.g. tags or visibility """ - if entrypoint == None: - entrypoint_override = False - else: - entrypoint_override = True + + # Override if non-None + cmd_override = (cmd != None) + entrypoint_override = (entrypoint != None) _oci_image( name = name, base = base, annotations = annotations, arch = arch, + cmd = cmd, + cmd_override = cmd_override, entrypoint = entrypoint, entrypoint_override = entrypoint_override, env = env, @@ -221,6 +228,19 @@ def _oci_image_impl(ctx): base_layout.blob_index, ] + ctx.files.layers + layer_descriptor_files + base_layout.files.to_list() + tars + if ctx.attr.cmd_override: + cmd_config_file = ctx.actions.declare_file("{}.cmd.config.json".format(ctx.label.name)) + cmd_config = struct( + cmd = ctx.attr.cmd, + ) + ctx.actions.write( + output = cmd_config_file, + content = json.encode(cmd_config), + ) + arguments.append("--cmd={}".format(cmd_config_file.path)) + default_info_files.append(cmd_config_file) + inputs.append(cmd_config_file) + if ctx.attr.entrypoint_override: entrypoint_config_file = ctx.actions.declare_file("{}.entrypoint.config.json".format(ctx.label.name)) entrypoint_config = struct( @@ -276,6 +296,8 @@ _oci_image = rule( mandatory = True, providers = [OCIDescriptor, OCILayout], ), + "cmd": attr.string_list(), + "cmd_override": attr.bool(), "entrypoint": attr.string_list(), "entrypoint_override": attr.bool(), "env": attr.string_list(),