diff --git a/client/client.go b/client/client.go index 4e411743..bd8cc25b 100644 --- a/client/client.go +++ b/client/client.go @@ -213,8 +213,10 @@ func (c *Client) logSolveStatus(ctx context.Context, pctx *plancontext.Context, } // Just like sprintf, but redacts secrets automatically - secrets := pctx.Secrets.List() secureSprintf := func(format string, a ...interface{}) string { + // Load a fresh copy of secrets (since they can be dynamically added). + secrets := pctx.Secrets.List() + s := fmt.Sprintf(format, a...) for _, secret := range secrets { s = strings.ReplaceAll(s, secret.PlainText(), "***") diff --git a/docs/reference/europa/dagger/engine.md b/docs/reference/europa/dagger/engine.md index c7f64d24..b591dd29 100644 --- a/docs/reference/europa/dagger/engine.md +++ b/docs/reference/europa/dagger/engine.md @@ -174,6 +174,18 @@ _No input._ _No output._ +## engine.#NewSecret + +Create a new a secret from a filesystem tree + +### engine.#NewSecret Inputs + +_No input._ + +### engine.#NewSecret Outputs + +_No output._ + ## engine.#Plan A deployment plan executed by `dagger up` diff --git a/plan/task/newsecret.go b/plan/task/newsecret.go new file mode 100644 index 00000000..0fbaf8ec --- /dev/null +++ b/plan/task/newsecret.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("NewSecret", func() Task { return &newSecretTask{} }) +} + +type newSecretTask struct { +} + +func (t *newSecretTask) 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{}{ + "output": secret.MarshalCUE(), + }) +} diff --git a/stdlib/europa/dagger/engine/secret.cue b/stdlib/europa/dagger/engine/secret.cue new file mode 100644 index 00000000..9a202622 --- /dev/null +++ b/stdlib/europa/dagger/engine/secret.cue @@ -0,0 +1,15 @@ +package engine + +// Create a new a secret from a filesystem tree +#NewSecret: { + $dagger: task: _name: "NewSecret" + + // 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 + output: #Secret +} diff --git a/tests/tasks.bats b/tests/tasks.bats index 79b98601..b7d942e9 100644 --- a/tests/tasks.bats +++ b/tests/tasks.bats @@ -127,3 +127,9 @@ setup() { run "$DAGGER" --europa up ./tasks/httpfetch/not_exist.cue assert_failure } + +@test "task: #NewSecret" { + cd "$TESTDIR"/tasks/newsecret + + "$DAGGER" --europa up ./newsecret.cue +} diff --git a/tests/tasks/newsecret/newsecret.cue b/tests/tasks/newsecret/newsecret.cue new file mode 100644 index 00000000..5bebcbb2 --- /dev/null +++ b/tests/tasks/newsecret/newsecret.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.#NewSecret & { + input: generate.output + path: "/secret" + } + + verify: engine.#Exec & { + input: image.output + mounts: secret: { + dest: "/run/secrets/test" + contents: load.output + } + args: [ + "sh", "-c", + #""" + test "$(cat /run/secrets/test)" = "test" + """#, + ] + } + } +}