diff --git a/Makefile b/Makefile index 760d9fff..8c9eb4b3 100644 --- a/Makefile +++ b/Makefile @@ -44,12 +44,12 @@ integration: core-integration universe-test doc-test .PHONY: core-integration core-integration: dagger-debug yarn --cwd "./tests" install - DAGGER_BINARY="../cmd/dagger/dagger-debug" yarn --cwd "./tests" test + DAGGER_BINARY="$(shell pwd)/cmd/dagger/dagger-debug" yarn --cwd "./tests" test .PHONY: universe-test universe-test: dagger-debug yarn --cwd "./universe" install - DAGGER_BINARY="../cmd/dagger/dagger-debug" yarn --cwd "./universe" test + DAGGER_BINARY="$(shell pwd)/cmd/dagger/dagger-debug" yarn --cwd "./universe" test .PHONY: doc-test doc-test: dagger-debug diff --git a/compiler/value.go b/compiler/value.go index 30fdac18..50aed583 100644 --- a/compiler/value.go +++ b/compiler/value.go @@ -31,6 +31,17 @@ func (v *Value) FillPath(p cue.Path, x interface{}) error { return v.val.Err() } +// FillFields fills multiple fields, in place +func (v *Value) FillFields(values map[string]interface{}) (*Value, error) { + for p, x := range values { + if err := v.FillPath(cue.ParsePath(p), x); err != nil { + return nil, err + } + } + + return v, nil +} + // LookupPath is a concurrency safe wrapper around cue.Value.LookupPath func (v *Value) LookupPath(p cue.Path) *Value { v.cc.rlock() diff --git a/plan/task/import.go b/plan/task/import.go index a567a223..30d09cd9 100644 --- a/plan/task/import.go +++ b/plan/task/import.go @@ -3,7 +3,6 @@ package task import ( "context" - "cuelang.org/go/cue" "github.com/moby/buildkit/client/llb" "go.dagger.io/dagger/compiler" "go.dagger.io/dagger/plancontext" @@ -69,9 +68,7 @@ func (c importTask) Run(ctx context.Context, pctx *plancontext.Context, s solver } fs := pctx.FS.New(result) - out := compiler.NewValue() - if err := out.FillPath(cue.ParsePath("fs"), fs.MarshalCUE()); err != nil { - return nil, err - } - return out, nil + return compiler.NewValue().FillFields(map[string]interface{}{ + "fs": fs.MarshalCUE(), + }) } diff --git a/plan/task/pull.go b/plan/task/pull.go new file mode 100644 index 00000000..2ff149b1 --- /dev/null +++ b/plan/task/pull.go @@ -0,0 +1,71 @@ +package task + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/docker/distribution/reference" + "github.com/moby/buildkit/client/llb" + "go.dagger.io/dagger/compiler" + "go.dagger.io/dagger/plancontext" + "go.dagger.io/dagger/solver" +) + +func init() { + Register("Pull", func() Task { return &pullTask{} }) +} + +type pullTask struct { +} + +func (c *pullTask) Run(ctx context.Context, pctx *plancontext.Context, s solver.Solver, v *compiler.Value) (*compiler.Value, error) { + // FIXME: handle auth + rawRef, err := v.Lookup("source").String() + if err != nil { + return nil, err + } + + ref, err := reference.ParseNormalizedNamed(rawRef) + if err != nil { + return nil, fmt.Errorf("failed to parse ref %s: %w", rawRef, err) + } + // Add the default tag "latest" to a reference if it only has a repo name. + ref = reference.TagNameOnly(ref) + + st := llb.Image( + ref.String(), + withCustomName(v, "FetchContainer %s", rawRef), + ) + + // Load image metadata and convert to to LLB. + platform := pctx.Platform.Get() + image, digest, err := s.ResolveImageConfig(ctx, ref.String(), llb.ResolveImageConfigOpt{ + LogName: vertexNamef(v, "load metadata for %s", ref.String()), + Platform: &platform, + }) + if err != nil { + return nil, err + } + imageJSON, err := json.Marshal(image) + if err != nil { + return nil, err + } + // Apply Image Config on top of LLB instructions + st, err = st.WithImageConfig(imageJSON) + if err != nil { + return nil, err + } + + result, err := s.Solve(ctx, st, pctx.Platform.Get()) + if err != nil { + return nil, err + } + fs := pctx.FS.New(result) + + return compiler.NewValue().FillFields(map[string]interface{}{ + "output": fs.MarshalCUE(), + "digest": digest, + "config": image.Config, + }) +} diff --git a/plan/task/secretenv.go b/plan/task/secretenv.go index f3db6923..1a67fa58 100644 --- a/plan/task/secretenv.go +++ b/plan/task/secretenv.go @@ -5,7 +5,6 @@ import ( "fmt" "os" - "cuelang.org/go/cue" "github.com/rs/zerolog/log" "go.dagger.io/dagger/compiler" "go.dagger.io/dagger/plancontext" @@ -37,9 +36,7 @@ func (c secretEnvTask) Run(ctx context.Context, pctx *plancontext.Context, _ sol return nil, fmt.Errorf("environment variable %q not set", secretEnv.Envvar) } secret := pctx.Secrets.New(env) - out := compiler.NewValue() - if err := out.FillPath(cue.ParsePath("contents"), secret.MarshalCUE()); err != nil { - return nil, err - } - return out, nil + return compiler.NewValue().FillFields(map[string]interface{}{ + "contents": secret.MarshalCUE(), + }) } diff --git a/plan/task/secretfile.go b/plan/task/secretfile.go index 3cb5a335..c41f25b3 100644 --- a/plan/task/secretfile.go +++ b/plan/task/secretfile.go @@ -4,7 +4,6 @@ import ( "context" "os" - "cuelang.org/go/cue" "github.com/rs/zerolog/log" "go.dagger.io/dagger/compiler" "go.dagger.io/dagger/plancontext" @@ -37,9 +36,7 @@ func (c secretFileTask) Run(ctx context.Context, pctx *plancontext.Context, _ so } secret := pctx.Secrets.New(string(plaintext)) - out := compiler.NewValue() - if err := out.FillPath(cue.ParsePath("contents"), secret.MarshalCUE()); err != nil { - return nil, err - } - return out, nil + return compiler.NewValue().FillFields(map[string]interface{}{ + "contents": secret.MarshalCUE(), + }) } diff --git a/plan/task/util.go b/plan/task/util.go index 8c8b66ab..19d1f312 100644 --- a/plan/task/util.go +++ b/plan/task/util.go @@ -7,8 +7,12 @@ import ( "go.dagger.io/dagger/compiler" ) -func withCustomName(v *compiler.Value, format string, a ...interface{}) llb.ConstraintsOpt { +func vertexNamef(v *compiler.Value, format string, a ...interface{}) string { prefix := fmt.Sprintf("@%s@", v.Path().String()) name := fmt.Sprintf(format, a...) - return llb.WithCustomName(prefix + " " + name) + return prefix + " " + name +} + +func withCustomName(v *compiler.Value, format string, a ...interface{}) llb.ConstraintsOpt { + return llb.WithCustomName(vertexNamef(v, format, a...)) } diff --git a/stdlib/dagger/engine/image.cue b/stdlib/dagger/engine/image.cue new file mode 100644 index 00000000..5f22bd02 --- /dev/null +++ b/stdlib/dagger/engine/image.cue @@ -0,0 +1,45 @@ +package engine + +// A ref is an address for a remote container image +// +// Examples: +// - "index.docker.io/dagger" +// - "dagger" +// - "index.docker.io/dagger:latest" +// - "index.docker.io/dagger:latest@sha256:a89cb097693dd354de598d279c304a1c73ee550fbfff6d9ee515568e0c749cfe" +#Ref: string + +// Container image config. See [OCI](https://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] + ... +} + +// Download a container image from a remote repository +#Pull: { + _type: "Pull" + + // Repository source ref + source: #Ref + + // Authentication + auth: [...{ + target: string + username: string + secret: string | #Secret + }] + + // Root filesystem of downloaded image + output: #FS + + // Image digest + digest: string + + // Downloaded container image config + config: #ImageConfig +} diff --git a/tests/tasks.bats b/tests/tasks.bats new file mode 100644 index 00000000..2eed09a8 --- /dev/null +++ b/tests/tasks.bats @@ -0,0 +1,10 @@ +setup() { + load 'helpers' + + common_setup +} + +@test "task: #Pull" { + cd "$TESTDIR"/tasks/pull + dagger --europa up +} \ No newline at end of file diff --git a/tests/tasks/pull/cue.mod/module.cue b/tests/tasks/pull/cue.mod/module.cue new file mode 100644 index 00000000..f8af9cef --- /dev/null +++ b/tests/tasks/pull/cue.mod/module.cue @@ -0,0 +1 @@ +module: "" diff --git a/tests/tasks/pull/cue.mod/pkg/.gitignore b/tests/tasks/pull/cue.mod/pkg/.gitignore new file mode 100644 index 00000000..2d4dc1ae --- /dev/null +++ b/tests/tasks/pull/cue.mod/pkg/.gitignore @@ -0,0 +1,3 @@ +# generated by dagger +alpha.dagger.io +dagger.lock diff --git a/tests/tasks/pull/pull.cue b/tests/tasks/pull/pull.cue new file mode 100644 index 00000000..d6a58c7b --- /dev/null +++ b/tests/tasks/pull/pull.cue @@ -0,0 +1,18 @@ +package main + +import ( + "alpha.dagger.io/dagger/engine" +) + +engine.#Plan & { + actions: pull: engine.#Pull & { + source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3" + } & { + // assert result + digest: "sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3" + config: { + Env: ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"] + Cmd: ["/bin/sh"] + } + } +}