diff --git a/docs/reference/europa/dagger/engine.md b/docs/reference/europa/dagger/engine.md index c7f64d24..34ee1528 100644 --- a/docs/reference/europa/dagger/engine.md +++ b/docs/reference/europa/dagger/engine.md @@ -138,6 +138,18 @@ _No input._ _No output._ +## engine.#LoadSecret + +Load a secret from a filesystem tree + +### engine.#LoadSecret Inputs + +_No input._ + +### engine.#LoadSecret Outputs + +_No output._ + ## engine.#Merge Merge multiple FS trees into one diff --git a/plan/task/loadsecret.go b/plan/task/loadsecret.go new file mode 100644 index 00000000..794e83b3 --- /dev/null +++ b/plan/task/loadsecret.go @@ -0,0 +1,54 @@ +package task + +import ( + "context" + "fmt" + "io/fs" + "strings" + + "go.dagger.io/dagger/compiler" + "go.dagger.io/dagger/plancontext" + "go.dagger.io/dagger/solver" +) + +func init() { + Register("LoadSecret", func() Task { return &loadSecretTask{} }) +} + +type loadSecretTask struct { +} + +func (t *loadSecretTask) Run(ctx context.Context, pctx *plancontext.Context, s solver.Solver, v *compiler.Value) (*compiler.Value, error) { + path, err := v.Lookup("path").String() + if err != nil { + return nil, err + } + + input, err := pctx.FS.FromValue(v.Lookup("input")) + if err != nil { + return nil, err + } + inputFS := solver.NewBuildkitFS(input.Result()) + + // FIXME: we should create an intermediate image containing only `path`. + // That way, on cache misses, we'll only download the layer with the file contents rather than the entire FS. + contents, err := fs.ReadFile(inputFS, path) + if err != nil { + return nil, fmt.Errorf("ReadFile %s: %w", path, err) + } + plaintext := string(contents) + + trimSpace, err := v.Lookup("trimSpace").Bool() + if err != nil { + return nil, err + } + if trimSpace { + plaintext = strings.TrimSpace(plaintext) + } + + secret := pctx.Secrets.New(plaintext) + + return compiler.NewValue().FillFields(map[string]interface{}{ + "contents": secret.MarshalCUE(), + }) +} diff --git a/stdlib/europa/dagger/engine/secret.cue b/stdlib/europa/dagger/engine/secret.cue new file mode 100644 index 00000000..a4cbe811 --- /dev/null +++ b/stdlib/europa/dagger/engine/secret.cue @@ -0,0 +1,15 @@ +package engine + +// Load a secret from a filesystem tree +#LoadSecret: { + $dagger: task: _name: "LoadSecret" + + // Filesystem tree holding the secret + input: #FS + // Path of the secret to read + path: string + // Whether to trim leading and trailing space characters from secret value + trimSpace: *true | false + // Contents of the secret + contents: #Secret +} diff --git a/tests/tasks.bats b/tests/tasks.bats index c93a408e..765aa61d 100644 --- a/tests/tasks.bats +++ b/tests/tasks.bats @@ -125,3 +125,9 @@ setup() { run "$DAGGER" --europa up ./tasks/httpfetch/not_exist.cue assert_failure } + +@test "task: #LoadSecret" { + cd "$TESTDIR"/tasks/loadsecret + + "$DAGGER" --europa up ./loadsecret.cue +} diff --git a/tests/tasks/loadsecret/loadsecret.cue b/tests/tasks/loadsecret/loadsecret.cue new file mode 100644 index 00000000..8ca558a5 --- /dev/null +++ b/tests/tasks/loadsecret/loadsecret.cue @@ -0,0 +1,37 @@ +package main + +import ( + "alpha.dagger.io/europa/dagger/engine" +) + +engine.#Plan & { + actions: { + image: engine.#Pull & { + source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3" + } + + generate: engine.#Exec & { + input: image.output + args: ["sh", "-c", "echo test > /secret"] + } + + load: engine.#LoadSecret & { + input: generate.output + path: "/secret" + } + + verify: engine.#Exec & { + input: image.output + mounts: secret: { + dest: "/run/secrets/test" + contents: load.contents + } + args: [ + "sh", "-c", + #""" + test "$(cat /run/secrets/test)" = "test" + """#, + ] + } + } +}