diff --git a/docs/docs/20-usage/40-secrets.md b/docs/docs/20-usage/40-secrets.md index 342907a89..195f3999a 100644 --- a/docs/docs/20-usage/40-secrets.md +++ b/docs/docs/20-usage/40-secrets.md @@ -14,6 +14,21 @@ pipeline: + secrets: [ docker_username, docker_password ] ``` +Alternatively, you can get a `setting` from secrets using the `from_secret` syntax. +In this example, the secret named `secret_token` would be passed to the pipeline as `PLUGIN_TOKEN`. + +**NOTE:** the `from_secret` syntax only works with the newer `settings` block. + +```diff +pipeline: + docker: + image: my-plugin + settings: ++ token: ++ from_secret: secret_token +``` + + Please note parameter expressions are subject to pre-processing. When using secrets in parameter expressions they should be escaped. ```diff diff --git a/pipeline/frontend/yaml/compiler/convert.go b/pipeline/frontend/yaml/compiler/convert.go index be9df7efa..cb913da94 100644 --- a/pipeline/frontend/yaml/compiler/convert.go +++ b/pipeline/frontend/yaml/compiler/convert.go @@ -72,7 +72,7 @@ func (c *Compiler) createProcess(name string, container *yaml.Container, section } if !detached { - if err := paramsToEnv(container.Settings, environment); err != nil { + if err := paramsToEnv(container.Settings, environment, c.secrets); err != nil { log.Error().Err(err).Msg("paramsToEnv") } } diff --git a/pipeline/frontend/yaml/compiler/params.go b/pipeline/frontend/yaml/compiler/params.go index db4e0c15a..263412d91 100644 --- a/pipeline/frontend/yaml/compiler/params.go +++ b/pipeline/frontend/yaml/compiler/params.go @@ -13,7 +13,7 @@ import ( // paramsToEnv uses reflection to convert a map[string]interface to a list // of environment variables. -func paramsToEnv(from map[string]interface{}, to map[string]string) (err error) { +func paramsToEnv(from map[string]interface{}, to map[string]string, secrets map[string]Secret) (err error) { if to == nil { return fmt.Errorf("no map to write to") } @@ -21,7 +21,7 @@ func paramsToEnv(from map[string]interface{}, to map[string]string) (err error) if v == nil || len(k) == 0 { continue } - to[sanitizeParamKey(k)], err = sanitizeParamValue(v) + to[sanitizeParamKey(k)], err = sanitizeParamValue(v, secrets) if err != nil { return err } @@ -48,7 +48,7 @@ func isComplex(t reflect.Kind) bool { } } -func sanitizeParamValue(v interface{}) (string, error) { +func sanitizeParamValue(v interface{}, secrets map[string]Secret) (string, error) { t := reflect.TypeOf(v) vv := reflect.ValueOf(v) @@ -66,6 +66,16 @@ func sanitizeParamValue(v interface{}) (string, error) { return fmt.Sprintf("%v", vv.Float()), nil case reflect.Map: + if fromSecret, ok := v.(map[string]interface{}); ok { + if secretNameI, ok := fromSecret["from_secret"]; ok { + if secretName, ok := secretNameI.(string); ok { + if secret, ok := secrets[secretName]; ok { + return secret.Value, nil + } + return "", fmt.Errorf("no secret found for %q", secretName) + } + } + } ymlOut, _ := yaml.Marshal(vv.Interface()) out, _ := yml.ToJSON(ymlOut) return string(out), nil @@ -78,7 +88,7 @@ func sanitizeParamValue(v interface{}) (string, error) { in := make([]string, vv.Len()) for i := 0; i < vv.Len(); i++ { var err error - if in[i], err = sanitizeParamValue(vv.Index(i).Interface()); err != nil { + if in[i], err = sanitizeParamValue(vv.Index(i).Interface(), secrets); err != nil { return "", err } } diff --git a/pipeline/frontend/yaml/compiler/params_test.go b/pipeline/frontend/yaml/compiler/params_test.go index 9d8046d28..a17d60554 100644 --- a/pipeline/frontend/yaml/compiler/params_test.go +++ b/pipeline/frontend/yaml/compiler/params_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" ) func TestParamsToEnv(t *testing.T) { @@ -20,6 +21,7 @@ func TestParamsToEnv(t *testing.T) { "from.address": "noreply@example.com", "tags": stringsToInterface("next", "latest"), "tag": stringsToInterface("next"), + "my_secret": map[string]interface{}{"from_secret": "secret_token"}, } want := map[string]string{ "PLUGIN_STRING": "stringz", @@ -33,12 +35,59 @@ func TestParamsToEnv(t *testing.T) { "PLUGIN_FROM_ADDRESS": "noreply@example.com", "PLUGIN_TAG": "next", "PLUGIN_TAGS": "next,latest", + "PLUGIN_MY_SECRET": "FooBar", + } + secrets := map[string]Secret{ + "secret_token": {Name: "secret_token", Value: "FooBar", Match: nil}, } got := map[string]string{} - assert.NoError(t, paramsToEnv(from, got)) + assert.NoError(t, paramsToEnv(from, got, secrets)) assert.EqualValues(t, want, got, "Problem converting plugin parameters to environment variables") } +func TestYAMLToParamsToEnv(t *testing.T) { + fromYAML := []byte(`skip: ~ +string: stringz +int: 1 +float: 1.2 +bool: true +slice: [1, 2, 3] +my_secret: + from_secret: secret_token +`) + var from map[string]interface{} + err := yaml.Unmarshal(fromYAML, &from) + assert.NoError(t, err) + + want := map[string]string{ + "PLUGIN_STRING": "stringz", + "PLUGIN_INT": "1", + "PLUGIN_FLOAT": "1.2", + "PLUGIN_BOOL": "true", + "PLUGIN_SLICE": "1,2,3", + "PLUGIN_MY_SECRET": "FooBar", + } + secrets := map[string]Secret{ + "secret_token": {Name: "secret_token", Value: "FooBar", Match: nil}, + } + got := map[string]string{} + assert.NoError(t, paramsToEnv(from, got, secrets)) + assert.EqualValues(t, want, got, "Problem converting plugin parameters to environment variables") +} + +func TestYAMLToParamsToEnvError(t *testing.T) { + fromYAML := []byte(`my_secret: + from_secret: not_a_secret +`) + var from map[string]interface{} + err := yaml.Unmarshal(fromYAML, &from) + assert.NoError(t, err) + secrets := map[string]Secret{ + "secret_token": {Name: "secret_token", Value: "FooBar", Match: nil}, + } + assert.Error(t, paramsToEnv(from, make(map[string]string), secrets)) +} + func stringsToInterface(val ...string) []interface{} { res := make([]interface{}, len(val)) for i := range val {