diff --git a/pkg/dagger.io/dagger/engine/secret.cue b/pkg/dagger.io/dagger/engine/secret.cue index 6c6d87f8..08e1c893 100644 --- a/pkg/dagger.io/dagger/engine/secret.cue +++ b/pkg/dagger.io/dagger/engine/secret.cue @@ -15,18 +15,31 @@ package engine } // Securely apply a CUE transformation on the contents of a secret -#TransformSecret: { - $dagger: task: _name: "TransformSecret" - // The original secret +// FIXME: disabled due to data race associated with filling #function.input +// #TransformSecret: { +// $dagger: task: _name: "TransformSecret" +// // The original secret +// input: #Secret +// // A new secret or (map of secrets) with the transformation applied +// output: #Secret | {[string]: output} +// // Transformation function +// #function: { +// // Full contents of the input secret (only available to the function) +// input: string +// _functionOutput: string | {[string]: _functionOutput} +// // New contents of the output secret (must provided by the caller) +// output: _functionOutput +// } +// } + +#DecodeSecret: { + $dagger: task: _name: "DecodeSecret" + + // A #Secret whose plain text is a JSON or YAML string input: #Secret - // A new secret or (map of secrets) with the transformation applied + + format: "json" | "yaml" + + // A new secret or (map of secrets) derived from unmarshaling the input secret's plain text output: #Secret | {[string]: output} - // Transformation function - #function: { - // Full contents of the input secret (only available to the function) - input: string - _functionOutput: string | {[string]: _functionOutput} - // New contents of the output secret (must provided by the caller) - output: _functionOutput - } } diff --git a/pkg/dagger.io/dagger/utils.cue b/pkg/dagger.io/dagger/utils.cue index 2888b4ff..b4aa8a76 100644 --- a/pkg/dagger.io/dagger/utils.cue +++ b/pkg/dagger.io/dagger/utils.cue @@ -1,8 +1,8 @@ package dagger import ( - "encoding/yaml" - "encoding/json" + // "encoding/json" + // "encoding/yaml" "dagger.io/dagger/engine" ) @@ -38,23 +38,24 @@ import ( } // DecodeSecret is a convenience wrapper around #TransformSecret. The plain text contents of input is expected to match the format -#DecodeSecret: { - { - format: "json" - engine.#TransformSecret & { - #function: { - input: _ - output: json.Unmarshal(input) - } - } - } | { - format: "yaml" - engine.#TransformSecret & { - #function: { - input: _ - output: yaml.Unmarshal(input) - } - } - } +// #DecodeSecret: { +// { +// format: "json" +// engine.#TransformSecret & { +// #function: { +// input: _ +// output: json.Unmarshal(input) +// } +// } +// } | { +// format: "yaml" +// engine.#TransformSecret & { +// #function: { +// input: _ +// output: yaml.Unmarshal(input) +// } +// } +// } +// } -} +#DecodeSecret: engine.#DecodeSecret diff --git a/pkg/universe.dagger.io/netlify/test/netlify-test.cue b/pkg/universe.dagger.io/netlify/test/netlify-test.cue index 0e7a95f0..a032cb35 100644 --- a/pkg/universe.dagger.io/netlify/test/netlify-test.cue +++ b/pkg/universe.dagger.io/netlify/test/netlify-test.cue @@ -1,8 +1,6 @@ package yarn import ( - "encoding/yaml" - "dagger.io/dagger" "dagger.io/dagger/engine" @@ -18,12 +16,9 @@ dagger.#Plan & { } actions: { - testSecrets: engine.#TransformSecret & { - input: inputs.secrets.test.contents - #function: { - input: _ - output: yaml.Unmarshal(input) - } + testSecrets: dagger.#DecodeSecret & { + input: inputs.secrets.test.contents + format: "yaml" } marker: "hello world" diff --git a/plan/task/decodesecret.go b/plan/task/decodesecret.go new file mode 100644 index 00000000..8dbcf633 --- /dev/null +++ b/plan/task/decodesecret.go @@ -0,0 +1,80 @@ +package task + +import ( + "context" + "encoding/json" + "errors" + + "cuelang.org/go/cue" + "github.com/rs/zerolog/log" + "go.dagger.io/dagger/compiler" + "go.dagger.io/dagger/plancontext" + "go.dagger.io/dagger/solver" + "gopkg.in/yaml.v3" +) + +func init() { + Register("DecodeSecret", func() Task { return &decodeSecretTask{} }) +} + +type decodeSecretTask struct { +} + +func (c *decodeSecretTask) Run(ctx context.Context, pctx *plancontext.Context, _ solver.Solver, v *compiler.Value) (*compiler.Value, error) { + lg := log.Ctx(ctx) + lg.Debug().Msg("decoding secret") + + input := v.Lookup("input") + + inputSecret, err := pctx.Secrets.FromValue(input) + if err != nil { + return nil, err + } + + format, err := v.Lookup("format").String() + if err != nil { + return nil, err + } + + lg.Debug().Str("format", format).Msg("unmarshaling secret") + + inputSecretPlaintext := inputSecret.PlainText() + + var unmarshaled map[string]interface{} + + switch format { + case "json": + err = json.Unmarshal([]byte(inputSecretPlaintext), &unmarshaled) + case "yaml": + err = yaml.Unmarshal([]byte(inputSecretPlaintext), &unmarshaled) + } + + if err != nil { + // returning err here could expose secret plaintext! + return nil, errors.New("could not unmarshal secret") + } + + output := compiler.NewValue() + + // recurse over unmarshaled to convert string values to secrets + var convert func(p []cue.Selector, i interface{}) + convert = func(p []cue.Selector, i interface{}) { + switch entry := i.(type) { + case string: + secret := pctx.Secrets.New(entry) + path := cue.MakePath(append(p, cue.ParsePath("contents").Selectors()...)...) + pathSelectors := path.Selectors() + logPath := cue.MakePath(pathSelectors[1 : len(pathSelectors)-1]...) + lg.Debug().Str("path", logPath.String()).Str("type", "string").Msg("found secret") + output.FillPath(path, secret.MarshalCUE()) + case map[string]interface{}: + for k, v := range entry { + convert(append(p, cue.ParsePath(k).Selectors()...), v) + } + } + } + + convert(cue.ParsePath("output").Selectors(), unmarshaled) + + return output, nil +}