mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-26 11:51:02 +00:00
Get secrets in settings (#604)
* Get secrets in settings Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add error test Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add docs Signed-off-by: jolheiser <john.olheiser@gmail.com>
This commit is contained in:
parent
3bee9044f1
commit
9e8d1a9294
4 changed files with 80 additions and 6 deletions
|
@ -14,6 +14,21 @@ pipeline:
|
||||||
+ secrets: [ docker_username, docker_password ]
|
+ 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.
|
Please note parameter expressions are subject to pre-processing. When using secrets in parameter expressions they should be escaped.
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
|
|
|
@ -72,7 +72,7 @@ func (c *Compiler) createProcess(name string, container *yaml.Container, section
|
||||||
}
|
}
|
||||||
|
|
||||||
if !detached {
|
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")
|
log.Error().Err(err).Msg("paramsToEnv")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
// paramsToEnv uses reflection to convert a map[string]interface to a list
|
// paramsToEnv uses reflection to convert a map[string]interface to a list
|
||||||
// of environment variables.
|
// 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 {
|
if to == nil {
|
||||||
return fmt.Errorf("no map to write to")
|
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 {
|
if v == nil || len(k) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
to[sanitizeParamKey(k)], err = sanitizeParamValue(v)
|
to[sanitizeParamKey(k)], err = sanitizeParamValue(v, secrets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
t := reflect.TypeOf(v)
|
||||||
vv := reflect.ValueOf(v)
|
vv := reflect.ValueOf(v)
|
||||||
|
|
||||||
|
@ -66,6 +66,16 @@ func sanitizeParamValue(v interface{}) (string, error) {
|
||||||
return fmt.Sprintf("%v", vv.Float()), nil
|
return fmt.Sprintf("%v", vv.Float()), nil
|
||||||
|
|
||||||
case reflect.Map:
|
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())
|
ymlOut, _ := yaml.Marshal(vv.Interface())
|
||||||
out, _ := yml.ToJSON(ymlOut)
|
out, _ := yml.ToJSON(ymlOut)
|
||||||
return string(out), nil
|
return string(out), nil
|
||||||
|
@ -78,7 +88,7 @@ func sanitizeParamValue(v interface{}) (string, error) {
|
||||||
in := make([]string, vv.Len())
|
in := make([]string, vv.Len())
|
||||||
for i := 0; i < vv.Len(); i++ {
|
for i := 0; i < vv.Len(); i++ {
|
||||||
var err error
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParamsToEnv(t *testing.T) {
|
func TestParamsToEnv(t *testing.T) {
|
||||||
|
@ -20,6 +21,7 @@ func TestParamsToEnv(t *testing.T) {
|
||||||
"from.address": "noreply@example.com",
|
"from.address": "noreply@example.com",
|
||||||
"tags": stringsToInterface("next", "latest"),
|
"tags": stringsToInterface("next", "latest"),
|
||||||
"tag": stringsToInterface("next"),
|
"tag": stringsToInterface("next"),
|
||||||
|
"my_secret": map[string]interface{}{"from_secret": "secret_token"},
|
||||||
}
|
}
|
||||||
want := map[string]string{
|
want := map[string]string{
|
||||||
"PLUGIN_STRING": "stringz",
|
"PLUGIN_STRING": "stringz",
|
||||||
|
@ -33,12 +35,59 @@ func TestParamsToEnv(t *testing.T) {
|
||||||
"PLUGIN_FROM_ADDRESS": "noreply@example.com",
|
"PLUGIN_FROM_ADDRESS": "noreply@example.com",
|
||||||
"PLUGIN_TAG": "next",
|
"PLUGIN_TAG": "next",
|
||||||
"PLUGIN_TAGS": "next,latest",
|
"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{}
|
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")
|
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{} {
|
func stringsToInterface(val ...string) []interface{} {
|
||||||
res := make([]interface{}, len(val))
|
res := make([]interface{}, len(val))
|
||||||
for i := range val {
|
for i := range val {
|
||||||
|
|
Loading…
Reference in a new issue