implemented #DecodeSecret as engine task

Signed-off-by: Richard Jones <richard@dagger.io>
This commit is contained in:
Richard Jones 2022-02-03 12:08:11 -07:00
parent 5dfdf5149f
commit a087161fbb
4 changed files with 130 additions and 41 deletions

View File

@ -15,18 +15,31 @@ package engine
} }
// Securely apply a CUE transformation on the contents of a secret // Securely apply a CUE transformation on the contents of a secret
#TransformSecret: { // FIXME: disabled due to data race associated with filling #function.input
$dagger: task: _name: "TransformSecret" // #TransformSecret: {
// The original secret // $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 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} 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
}
} }

View File

@ -1,8 +1,8 @@
package dagger package dagger
import ( import (
"encoding/yaml" // "encoding/json"
"encoding/json" // "encoding/yaml"
"dagger.io/dagger/engine" "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 is a convenience wrapper around #TransformSecret. The plain text contents of input is expected to match the format
#DecodeSecret: { // #DecodeSecret: {
{ // {
format: "json" // format: "json"
engine.#TransformSecret & { // engine.#TransformSecret & {
#function: { // #function: {
input: _ // input: _
output: json.Unmarshal(input) // output: json.Unmarshal(input)
} // }
} // }
} | { // } | {
format: "yaml" // format: "yaml"
engine.#TransformSecret & { // engine.#TransformSecret & {
#function: { // #function: {
input: _ // input: _
output: yaml.Unmarshal(input) // output: yaml.Unmarshal(input)
} // }
} // }
} // }
// }
} #DecodeSecret: engine.#DecodeSecret

View File

@ -1,8 +1,6 @@
package yarn package yarn
import ( import (
"encoding/yaml"
"dagger.io/dagger" "dagger.io/dagger"
"dagger.io/dagger/engine" "dagger.io/dagger/engine"
@ -18,12 +16,9 @@ dagger.#Plan & {
} }
actions: { actions: {
testSecrets: engine.#TransformSecret & { testSecrets: dagger.#DecodeSecret & {
input: inputs.secrets.test.contents input: inputs.secrets.test.contents
#function: { format: "yaml"
input: _
output: yaml.Unmarshal(input)
}
} }
marker: "hello world" marker: "hello world"

80
plan/task/decodesecret.go Normal file
View File

@ -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
}