diff --git a/pkg/dagger.io/dagger/engine/image.cue b/pkg/dagger.io/dagger/engine/image.cue index ae346903..46bc7832 100644 --- a/pkg/dagger.io/dagger/engine/image.cue +++ b/pkg/dagger.io/dagger/engine/image.cue @@ -1,5 +1,9 @@ package engine +import ( + "list" +) + // Upload a container image to a remote repository #Push: { $dagger: task: _name: "Push" @@ -34,7 +38,6 @@ package engine #Ref: string // Container image config. See [OCI](https://www.opencontainers.org/). -// Spec left open on purpose to account for additional fields. #ImageConfig: { user?: string expose?: [string]: {} @@ -116,3 +119,75 @@ package engine // Container image config produced config: #ImageConfig } + +// Change image config +#Set: { + // The source image config + input: #ImageConfig + + // The config to merge + config: #ImageConfig + + // Resulting config + output: #ImageConfig & { + let structs = ["env", "label", "volume", "expose"] + let lists = ["onbuild"] + + // doesn't exist in config, copy away + for field, value in input if config[field] == _|_ { + "\(field)": value + } + + // only exists in config, just copy as is + for field, value in config if input[field] == _|_ { + "\(field)": value + } + + // these should exist in both places + for field, value in config if input[field] != _|_ { + "\(field)": { + // handle structs that need merging + if list.Contains(structs, field) { + _#mergeStructs & { + #a: input[field] + #b: config[field] + } + } + + // handle lists that need concatenation + if list.Contains(lists, field) { + list.Concat([ + input[field], + config[field], + ]) + } + + // replace anything else + if !list.Contains(structs+lists, field) { + value + } + } + } + } +} + +// Merge two structs by overwriting or adding values +_#mergeStructs: { + // Struct with defaults + #a: [string]: _ + + // Struct with overrides + #b: [string]: _ + { + // FIXME: we need exists() in if because this matches any kind of error (cue-lang/cue#943) + // add anything not in b + for field, value in #a if #b[field] == _|_ { + "\(field)": value + } + + // safely add all of b + for field, value in #b { + "\(field)": value + } + } +} diff --git a/pkg/universe.dagger.io/docker/set.cue b/pkg/universe.dagger.io/docker/set.cue new file mode 100644 index 00000000..3902dbd7 --- /dev/null +++ b/pkg/universe.dagger.io/docker/set.cue @@ -0,0 +1,25 @@ +package docker + +import ( + "dagger.io/dagger/engine" +) + +// Change image config +#Set: { + // The source image + input: #Image + + // The image config to change + config: engine.#ImageConfig + + _set: engine.#Set & { + "input": input.config + "config": config + } + + // Resulting image with the config changes + output: #Image & { + rootfs: input.rootfs + config: _set.output + } +} diff --git a/pkg/universe.dagger.io/docker/test/docker.bats b/pkg/universe.dagger.io/docker/test/docker.bats index d8414a1d..8198c44d 100644 --- a/pkg/universe.dagger.io/docker/test/docker.bats +++ b/pkg/universe.dagger.io/docker/test/docker.bats @@ -22,3 +22,7 @@ setup() { dagger up ./run-export-directory-test.cue dagger up ./image-config-test.cue } + +@test "docker.#Set" { + dagger up ./set.cue +} diff --git a/pkg/universe.dagger.io/docker/test/set.cue b/pkg/universe.dagger.io/docker/test/set.cue new file mode 100644 index 00000000..b19ff93d --- /dev/null +++ b/pkg/universe.dagger.io/docker/test/set.cue @@ -0,0 +1,40 @@ +package docker + +import ( + "dagger.io/dagger" + "dagger.io/dagger/engine" + "universe.dagger.io/docker" +) + +dagger.#Plan & { + actions: { + image: output: docker.#Image & { + rootfs: engine.#Scratch + config: { + cmd: ["/bin/sh"] + env: PATH: "/sbin:/bin" + onbuild: ["COPY . /app"] + } + } + set: docker.#Set & { + input: image.output + config: { + env: FOO: "bar" + workdir: "/root" + onbuild: ["RUN /app/build.sh"] + } + } + verify: set.output.config & { + env: { + PATH: "/sbin:/bin" + FOO: "bar" + } + cmd: ["/bin/sh"] + workdir: "/root" + onbuild: [ + "COPY . /app", + "RUN /app/build.sh", + ] + } + } +}