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 & {