From c276a8b8ba790ba40cd2f208b4c05a336670c786 Mon Sep 17 00:00:00 2001 From: Helder Correia <174525+helderco@users.noreply.github.com> Date: Wed, 26 Jan 2022 09:22:09 -0100 Subject: [PATCH] Make env in ImageConfig a map Fields in CUE were renamed to the lowercase version of Dockerfile instructions. There's now opportunity to make other fields simpler to use (e.g., healthcheck), this commit is focused on env. Signed-off-by: Helder Correia <174525+helderco@users.noreply.github.com> --- pkg/dagger.io/dagger/engine/image.cue | 23 +++- plan/task/dockerfile.go | 2 +- plan/task/imageconfig.go | 154 ++++++++++++++++++++++++ plan/task/pull.go | 2 +- plan/task/push.go | 4 +- tests/tasks/dockerfile/image_config.cue | 4 +- tests/tasks/pull/pull.cue | 4 +- tests/tasks/pull/pull_auth.cue | 4 +- tests/tasks/push/push.cue | 6 +- 9 files changed, 184 insertions(+), 19 deletions(-) create mode 100644 plan/task/imageconfig.go diff --git a/pkg/dagger.io/dagger/engine/image.cue b/pkg/dagger.io/dagger/engine/image.cue index 39e2fbb0..acd0cf30 100644 --- a/pkg/dagger.io/dagger/engine/image.cue +++ b/pkg/dagger.io/dagger/engine/image.cue @@ -35,15 +35,28 @@ package engine // Container image config. See [OCI](https://www.opencontainers.org/). // Spec left open on purpose to account for additional fields. -// [Image Spec](https://github.com/opencontainers/image-spec/blob/main/specs-go/v1/config.go) -// [Docker Superset](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/dockerfile2llb/image.go) #ImageConfig: { - Env?: [...string] - User?: string - Cmd?: [...string] + user?: string + expose?: [string]: {} + env?: [string]: string + entrypoint?: [...string] + cmd?: [...string] + volume?: [string]: {} + workdir?: string + label?: [string]: string + healthcheck?: #HealthCheck + shell?: [...string] ... } +#HealthCheck: { + test?: [...string] + interval?: int + timeout?: int + startPeriod?: int + retries?: int +} + // Download a container image from a remote repository #Pull: { $dagger: task: _name: "Pull" diff --git a/plan/task/dockerfile.go b/plan/task/dockerfile.go index c9c4e5d0..397da66e 100644 --- a/plan/task/dockerfile.go +++ b/plan/task/dockerfile.go @@ -126,7 +126,7 @@ func (t *dockerfileTask) Run(ctx context.Context, pctx *plancontext.Context, s s return compiler.NewValue().FillFields(map[string]interface{}{ "output": pctx.FS.New(solvedRef).MarshalCUE(), - "config": image.Config, + "config": ConvertImageConfig(image.Config), }) } diff --git a/plan/task/imageconfig.go b/plan/task/imageconfig.go new file mode 100644 index 00000000..6a0101dc --- /dev/null +++ b/plan/task/imageconfig.go @@ -0,0 +1,154 @@ +package task + +import ( + "time" + + "github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb" + "github.com/moby/buildkit/frontend/dockerfile/shell" +) + +// ImageConfig defines the execution parameters which should be used as a base when running a container using an image. +type ImageConfig struct { + // [Image Spec](https://github.com/opencontainers/image-spec/blob/main/specs-go/v1/config.go) + + // User defines the username or UID which the process in the container should run as. + User string `json:"user,omitempty"` + + // ExposedPorts a set of ports to expose from a container running this image. + ExposedPorts map[string]struct{} `json:"expose,omitempty"` + + // Env is a list of environment variables to be used in a container. + Env map[string]string `json:"env,omitempty"` + + // Entrypoint defines a list of arguments to use as the command to execute when the container starts. + Entrypoint []string `json:"entrypoint,omitempty"` + + // Cmd defines the default arguments to the entrypoint of the container. + Cmd []string `json:"cmd,omitempty"` + + // Volumes is a set of directories describing where the process is likely write data specific to a container instance. + Volumes map[string]struct{} `json:"volume,omitempty"` + + // WorkingDir sets the current working directory of the entrypoint process in the container. + WorkingDir string `json:"workdir,omitempty"` + + // Labels contains arbitrary metadata for the container. + Labels map[string]string `json:"label,omitempty"` + + // StopSignal contains the system call signal that will be sent to the container to exit. + StopSignal string `json:"stopsignal,omitempty"` + + // [Docker Superset](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/dockerfile2llb/image.go) + + Healthcheck *HealthConfig `json:"healthcheck,omitempty"` // Healthcheck describes how to check the container is healthy + ArgsEscaped bool `json:"argsescaped,omitempty"` // True if command is already escaped (Windows specific) + + OnBuild []string `json:"onbuild,omitempty"` // ONBUILD metadata that were defined on the image Dockerfile + StopTimeout *int `json:"stoptimeout,omitempty"` // Timeout (in seconds) to stop a container + Shell []string `json:"shell,omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT +} + +// HealthConfig holds configuration settings for the HEALTHCHECK feature. +type HealthConfig struct { + // Test is the test to perform to check that the container is healthy. + // An empty slice means to inherit the default. + // The options are: + // {} : inherit healthcheck + // {"NONE"} : disable healthcheck + // {"CMD", args...} : exec arguments directly + // {"CMD-SHELL", command} : run command with system's default shell + Test []string `json:"test,omitempty"` + + // Zero means to inherit. Durations are expressed as integer nanoseconds. + Interval time.Duration `json:"interval,omitempty"` // Interval is the time to wait between checks. + Timeout time.Duration `json:"timeout,omitempty"` // Timeout is the time to wait before considering the check to have hung. + StartPeriod time.Duration `json:"startPeriod,omitempty"` // The start period for the container to initialize before the retries starts to count down. + + // Retries is the number of consecutive failures needed to consider a container as unhealthy. + // Zero means inherit. + Retries int `json:"retries,omitempty"` +} + +func (ic ImageConfig) ToSpec() dockerfile2llb.ImageConfig { + cfg := dockerfile2llb.ImageConfig{} + + cfg.User = ic.User + cfg.ExposedPorts = ic.ExposedPorts + cfg.Env = envToSpec(ic.Env) + cfg.Entrypoint = ic.Entrypoint + cfg.Cmd = ic.Cmd + cfg.Volumes = ic.Volumes + cfg.WorkingDir = ic.WorkingDir + cfg.Labels = ic.Labels + cfg.StopSignal = ic.StopSignal + + cfg.Healthcheck = ic.Healthcheck.ToSpec() + cfg.ArgsEscaped = ic.ArgsEscaped + cfg.OnBuild = ic.OnBuild + cfg.StopTimeout = ic.StopTimeout + cfg.Shell = ic.Shell + + return cfg +} + +func ConvertImageConfig(spec dockerfile2llb.ImageConfig) ImageConfig { + cfg := ImageConfig{} + + cfg.User = spec.User + cfg.ExposedPorts = spec.ExposedPorts + cfg.Env = shell.BuildEnvs(spec.Env) + cfg.Entrypoint = spec.Entrypoint + cfg.Cmd = spec.Cmd + cfg.Volumes = spec.Volumes + cfg.WorkingDir = spec.WorkingDir + cfg.Labels = spec.Labels + cfg.StopSignal = spec.StopSignal + + cfg.Healthcheck = ConvertHealthConfig(spec.Healthcheck) + cfg.ArgsEscaped = spec.ArgsEscaped + cfg.OnBuild = spec.OnBuild + cfg.StopTimeout = spec.StopTimeout + cfg.Shell = spec.Shell + + return cfg +} + +func envToSpec(env map[string]string) []string { + envs := []string{} + for k, v := range env { + envs = append(envs, k+"="+v) + } + return envs +} + +func (hc *HealthConfig) ToSpec() *dockerfile2llb.HealthConfig { + if hc == nil { + return nil + } + + cfg := dockerfile2llb.HealthConfig{} + + cfg.Test = hc.Test + cfg.Interval = hc.Interval + cfg.Timeout = hc.Timeout + cfg.StartPeriod = hc.StartPeriod + cfg.Retries = hc.Retries + + return &cfg +} + +func ConvertHealthConfig(spec *dockerfile2llb.HealthConfig) *HealthConfig { + if spec == nil { + return nil + } + + cfg := HealthConfig{} + + cfg.Test = spec.Test + cfg.Interval = spec.Interval + cfg.Timeout = spec.Timeout + cfg.StartPeriod = spec.StartPeriod + cfg.Retries = spec.Retries + + return &cfg +} diff --git a/plan/task/pull.go b/plan/task/pull.go index dc207ed2..d4dede0f 100644 --- a/plan/task/pull.go +++ b/plan/task/pull.go @@ -68,6 +68,6 @@ func (c *pullTask) Run(ctx context.Context, pctx *plancontext.Context, s solver. return compiler.NewValue().FillFields(map[string]interface{}{ "output": fs.MarshalCUE(), "digest": digest, - "config": image.Config, + "config": ConvertImageConfig(image.Config), }) } diff --git a/plan/task/push.go b/plan/task/push.go index 0cce7cb0..300b21a8 100644 --- a/plan/task/push.go +++ b/plan/task/push.go @@ -56,14 +56,14 @@ func (c *pushTask) Run(ctx context.Context, pctx *plancontext.Context, s solver. } // Decode the image config - imageConfig := dockerfile2llb.ImageConfig{} + imageConfig := ImageConfig{} if err := v.Lookup("config").Decode(&imageConfig); err != nil { return nil, err } // Export image lg.Debug().Str("dest", dest.String()).Msg("export image") - resp, err := s.Export(ctx, st, &dockerfile2llb.Image{Config: imageConfig}, bk.ExportEntry{ + resp, err := s.Export(ctx, st, &dockerfile2llb.Image{Config: imageConfig.ToSpec()}, bk.ExportEntry{ Type: bk.ExporterImage, Attrs: map[string]string{ "name": dest.String(), diff --git a/tests/tasks/dockerfile/image_config.cue b/tests/tasks/dockerfile/image_config.cue index a9d399ad..65519d51 100644 --- a/tests/tasks/dockerfile/image_config.cue +++ b/tests/tasks/dockerfile/image_config.cue @@ -18,8 +18,8 @@ engine.#Plan & { """ } & { config: { - Env: ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "test=foobar"] - Cmd: ["/bin/sh", "-c", "/test-cmd"] + env: test: "foobar" + cmd: ["/bin/sh", "-c", "/test-cmd"] } } } diff --git a/tests/tasks/pull/pull.cue b/tests/tasks/pull/pull.cue index d7f2c05a..d53080e4 100644 --- a/tests/tasks/pull/pull.cue +++ b/tests/tasks/pull/pull.cue @@ -11,8 +11,8 @@ engine.#Plan & { // assert result digest: "sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3" config: { - Env: ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"] - Cmd: ["/bin/sh"] + env: PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + cmd: ["/bin/sh"] } } } diff --git a/tests/tasks/pull/pull_auth.cue b/tests/tasks/pull/pull_auth.cue index 91b944f5..0d851f77 100644 --- a/tests/tasks/pull/pull_auth.cue +++ b/tests/tasks/pull/pull_auth.cue @@ -20,8 +20,8 @@ engine.#Plan & { // assert result digest: "sha256:c74f1b1166784193ea6c8f9440263b9be6cae07dfe35e32a5df7a31358ac2060" config: { - Env: ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"] - Cmd: ["/bin/sh"] + env: PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + cmd: ["/bin/sh"] } } } diff --git a/tests/tasks/push/push.cue b/tests/tasks/push/push.cue index 0a43842c..e910d840 100644 --- a/tests/tasks/push/push.cue +++ b/tests/tasks/push/push.cue @@ -42,7 +42,7 @@ engine.#Plan & { push: engine.#Push & { dest: "daggerio/ci-test:\(randomString.output)" input: randomString.image.output - config: Env: ["FOO=\(randomString.output)"] + config: env: FOO: randomString.output auth: #auth } @@ -54,9 +54,7 @@ engine.#Plan & { // check digest digest: strings.Split(push.result, "@")[1] // check image config - config: { - Env: ["FOO=\(randomString.output)"] - } + config: env: FOO: randomString.output } pullOutputFile: engine.#ReadFile & {