src: envs: use `:` instead of `.` as separators (#394)

* src: envs: use `:` instead of `.` as separators

Signed-off-by: Zbynek Roubalik <zroubali@redhat.com>

* leftovers

Signed-off-by: Zbynek Roubalik <zroubali@redhat.com>
This commit is contained in:
Zbynek Roubalik 2021-06-18 09:50:07 +02:00 committed by GitHub
parent 54f60a2552
commit 76b5800c62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 68 deletions

View File

@ -16,6 +16,14 @@ import (
// ConfigFile is the name of the config's serialized form.
const ConfigFile = "func.yaml"
var (
regWholeSecret = regexp.MustCompile(`^{{\s*secret:((?:\w|['-]\w)+)\s*}}$`)
regKeyFromSecret = regexp.MustCompile(`^{{\s*secret:((?:\w|['-]\w)+):(\w+)\s*}}$`)
regWholeConfigMap = regexp.MustCompile(`^{{\s*configMap:((?:\w|['-]\w)+)\s*}}$`)
regKeyFromConfigMap = regexp.MustCompile(`^{{\s*configMap:((?:\w|['-]\w)+):(\w+)\s*}}$`)
regLocalEnv = regexp.MustCompile(`^{{\s*env:(\w+)\s*}}$`)
)
type Volumes []Volume
type Volume struct {
Secret *string `yaml:"secret,omitempty"`
@ -234,31 +242,24 @@ func validateVolumes(volumes Volumes) (errors []string) {
// - name: EXAMPLE1 # ENV directly from a value
// value: value1
// - name: EXAMPLE2 # ENV from the local ENV var
// value: {{ env.MY_ENV }}
// value: {{ env:MY_ENV }}
// - name: EXAMPLE3
// value: {{ secret.secretName.key }} # ENV from a key in secret
// - value: {{ secret.secretName }} # all key-pair values from secret are set as ENV
// value: {{ secret:secretName:key }} # ENV from a key in secret
// - value: {{ secret:secretName }} # all key-pair values from secret are set as ENV
// - name: EXAMPLE4
// value: {{ configMap.configMapName.key }} # ENV from a key in configMap
// - value: {{ configMap.configMapName }} # all key-pair values from configMap are set as ENV
// value: {{ configMap:configMapName:key }} # ENV from a key in configMap
// - value: {{ configMap:configMapName }} # all key-pair values from configMap are set as ENV
func ValidateEnvs(envs Envs) (errors []string) {
// there could be '-' char in the secret/configMap name, but not in the key
regWholeSecret := regexp.MustCompile(`^{{\s*secret\.(?:\w|['-]\w)+\s*}}$`)
regKeyFromSecret := regexp.MustCompile(`^{{\s*secret\.(?:\w|['-]\w)+\.\w+\s*}}$`)
regWholeConfigMap := regexp.MustCompile(`^{{\s*configMap\.(?:\w|['-]\w)+\s*}}$`)
regKeyFromConfigMap := regexp.MustCompile(`^{{\s*configMap\.(?:\w|['-]\w)+\.\w+\s*}}$`)
regLocalEnv := regexp.MustCompile(`^{{\s*env\.(\w+)\s*}}$`)
for i, env := range envs {
if env.Name == nil && env.Value == nil {
errors = append(errors, fmt.Sprintf("env entry #%d is not properly set", i))
} else if env.Value == nil {
errors = append(errors, fmt.Sprintf("env entry #%d is missing value field, only name '%s' is set", i, *env.Name))
} else if env.Name == nil {
// all key-pair values from secret are set as ENV; {{ secret.secretName }} or {{ configMap.configMapName }}
// all key-pair values from secret are set as ENV; {{ secret:secretName }} or {{ configMap:configMapName }}
if !regWholeSecret.MatchString(*env.Value) && !regWholeConfigMap.MatchString(*env.Value) {
errors = append(errors, fmt.Sprintf("env entry #%d has invalid value field set, it has '%s', but allowed is only '{{ secret.secretName }}' or '{{ configMap.configMapName }}'",
errors = append(errors, fmt.Sprintf("env entry #%d has invalid value field set, it has '%s', but allowed is only '{{ secret:secretName }}' or '{{ configMap:configMapName }}'",
i, *env.Value))
}
} else {
@ -268,13 +269,13 @@ func ValidateEnvs(envs Envs) (errors []string) {
}
if strings.HasPrefix(*env.Value, "{{") {
// ENV from the local ENV var; {{ env.MY_ENV }}
// ENV from the local ENV var; {{ env:MY_ENV }}
// or
// ENV from a key in secret/configMap; {{ secret.secretName.key }} or {{ configMap.configMapName.key }}
// ENV from a key in secret/configMap; {{ secret:secretName:key }} or {{ configMap:configMapName:key }}
if !regLocalEnv.MatchString(*env.Value) && !regKeyFromSecret.MatchString(*env.Value) && !regKeyFromConfigMap.MatchString(*env.Value) {
errors = append(errors,
fmt.Sprintf(
"env entry #%d with name '%s' has invalid value field set, it has '%s', but allowed is only '{{ env.MY_ENV }}', '{{ secret.secretName.key }}' or '{{ configMap.configMapName.key }}'",
"env entry #%d with name '%s' has invalid value field set, it has '%s', but allowed is only '{{ env:MY_ENV }}', '{{ secret:secretName:key }}' or '{{ configMap:configMapName:key }}'",
i, *env.Name, *env.Value))
}
}

View File

@ -154,28 +154,28 @@ func Test_validateEnvs(t *testing.T) {
incorrectName := ",foo"
incorrectName2 := ":foo"
valueLocalEnv := "{{ env.MY_ENV }}"
valueLocalEnv2 := "{{ env.MY_ENV2 }}"
valueLocalEnv3 := "{{env.MY_ENV3}}"
valueLocalEnvIncorrect := "{{ envs.MY_ENV }}"
valueLocalEnv := "{{ env:MY_ENV }}"
valueLocalEnv2 := "{{ env:MY_ENV2 }}"
valueLocalEnv3 := "{{env:MY_ENV3}}"
valueLocalEnvIncorrect := "{{ envs:MY_ENV }}"
valueLocalEnvIncorrect2 := "{{ MY_ENV }}"
valueLocalEnvIncorrect3 := "{{env.MY_ENV}}foo"
valueLocalEnvIncorrect3 := "{{env:MY_ENV}}foo"
valueSecretKey := "{{ secret.mysecret.key }}"
valueSecretKey2 := "{{secret.my-secret.key }}"
valueSecretKey3 := "{{secret.my-secret.key2}}"
valueSecretKeyIncorrect := "{{ secret.my-secret.key.key }}"
valueSecretKeyIncorrect2 := "{{ my-secret.key }}"
valueSecretKeyIncorrect3 := "{{ secret.my-secret.key }}foo"
valueConfigMapKey := "{{ configMap.myconfigmap.key }}"
valueSecretKey := "{{ secret:mysecret:key }}"
valueSecretKey2 := "{{secret:my-secret:key }}"
valueSecretKey3 := "{{secret:my-secret:key2}}"
valueSecretKeyIncorrect := "{{ secret:my-secret:key.key }}"
valueSecretKeyIncorrect2 := "{{ my-secret:key }}"
valueSecretKeyIncorrect3 := "{{ secret:my-secret:key }}foo"
valueConfigMapKey := "{{ configMap:myconfigmap:key }}"
valueSecret := "{{ secret.my-secret }}"
valueSecret2 := "{{ secret.mysecret }}"
valueSecret3 := "{{ secret.mysecret}}"
valueSecret := "{{ secret:my-secret }}"
valueSecret2 := "{{ secret:mysecret }}"
valueSecret3 := "{{ secret:mysecret}}"
valueSecretIncorrect := "{{ my-secret }}"
valueSecretIncorrect2 := "my-secret"
valueSecretIncorrect3 := "{{ secret.my-secret }}foo"
valueConfigMap := "{{ configMap.myconfigmap }}"
valueSecretIncorrect3 := "{{ secret:my-secret }}foo"
valueConfigMap := "{{ configMap:myconfigmap }}"
tests := []struct {
name string

View File

@ -144,15 +144,15 @@ func (n *Runner) Run(ctx context.Context, f bosonFunc.Function) error {
return nil
}
// run command supports only ENV values in from FOO=bar or FOO={{ env.LOCAL_VALUE }}
var evRegex = regexp.MustCompile(`^{{\s*(\w+)\s*.(\w+)\s*}}$`)
// run command supports only ENV values in from FOO=bar or FOO={{ env:LOCAL_VALUE }}
var evRegex = regexp.MustCompile(`^{{\s*(\w+)\s*:(\w+)\s*}}$`)
const (
ctxIdx = 1
valIdx = 2
)
// processEnvValue returns only value for ENV variable, that is defined in form FOO=bar or FOO={{ env.LOCAL_VALUE }}
// processEnvValue returns only value for ENV variable, that is defined in form FOO=bar or FOO={{ env:LOCAL_VALUE }}
// if the value is correct, it is returned and the second return parameter is set to `true`
// otherwise it is set to `false`
// if the specified value is correct, but the required local variable is not set, error is returned as well

View File

@ -33,22 +33,22 @@ it's typically unnecessary to modify the `builder` field, using values from
The `envs` field allows you to set environment variables that will be
available to your function at runtime.
1. Environment variable can be set directly from a value
2. Environment variable can be set from a local environment value. Eg. `'{{ env.LOCAL_ENV_VALUE }}'`, for more details see [Local Environment Variables section](#local-environment-variables).
3. Environment variable can be set from a key in a Kubernetes Secret or ConfigMap. This Secret/ConfigMap needs to be created before it is referenced in a function. Eg. `'{{ secret.mysecret.key }}'` where `mysecret` is the name of the Secret and `key` is the referenced key; or `{{ configMap.myconfigmap.key }}` where `myconfigmap` is the name of the ConfigMap and `key` is the referenced key.
4. All key-value pairs from a Kubernetes Secret or ConfigMap will be set as environment variables. This Secret/ConfigMap needs to be created before it is referenced in a function. Eg. `'{{ secret.mysecret2 }}'` where `mysecret2` is the name of the Secret; or `{{ configMap.myconfigmap }}` where `myconfigmap` is the name of the ConfigMap.
2. Environment variable can be set from a local environment value. Eg. `'{{ env:LOCAL_ENV_VALUE }}'`, for more details see [Local Environment Variables section](#local-environment-variables).
3. Environment variable can be set from a key in a Kubernetes Secret or ConfigMap. This Secret/ConfigMap needs to be created before it is referenced in a function. Eg. `'{{ secret:mysecret:key }}'` where `mysecret` is the name of the Secret and `key` is the referenced key; or `{{ configMap:myconfigmap:key }}` where `myconfigmap` is the name of the ConfigMap and `key` is the referenced key.
4. All key-value pairs from a Kubernetes Secret or ConfigMap will be set as environment variables. This Secret/ConfigMap needs to be created before it is referenced in a function. Eg. `'{{ secret:mysecret2 }}'` where `mysecret2` is the name of the Secret: or `{{ configMap:myconfigmap }}` where `myconfigmap` is the name of the ConfigMap.
```yaml
envs:
- name: EXAMPLE1 # (1) env variable directly from a value
value: value
- name: EXAMPLE2 # (2) env variable from a local environment value
value: '{{ env.LOCAL_ENV_VALUE }}'
value: '{{ env:LOCAL_ENV_VALUE }}'
- name: EXAMPLE3 # (3) env variable from a key in Secret
value: '{{ secret.mysecret.key }}'
value: '{{ secret:mysecret:key }}'
- name: EXAMPLE4 # (3) env variable from a key in ConfigMap
value: '{{ configMap.myconfigmap.key }}'
- value: '{{ secret.mysecret2 }}' # (4) all key-value pairs in Secret as env variables
- value: '{{ configMap.myconfigmap2 }}' # (4) all key-value pairs in ConfigMap as env variables
value: '{{ configMap:myconfigmap:key }}'
- value: '{{ secret:mysecret2 }}' # (4) all key-value pairs in Secret as env variables
- value: '{{ configMap:myconfigmap2 }}' # (4) all key-value pairs in ConfigMap as env variables
```
### `volumes`
@ -120,10 +120,10 @@ variable available in the local environment. For example, if I would like
to avoid storing sensitive information such as an API key in my function
configuration, I may have this value set from the local environment. To do
this, prefix the local environment variable with `{{` and `}}` and prefix
the name with `env.`. For example:
the name with `env:`. For example:
```yaml
envs:
- name: API_KEY
value: '{{ env.API_KEY }}'
value: '{{ env:API_KEY }}'
```

View File

@ -245,13 +245,13 @@ func updateService(image string, newEnv []corev1.EnvVar, newEnvFrom []corev1.Env
// - name: EXAMPLE1 # ENV directly from a value
// value: value1
// - name: EXAMPLE2 # ENV from the local ENV var
// value: {{ env.MY_ENV }}
// value: {{ env:MY_ENV }}
// - name: EXAMPLE3
// value: {{ secret.example-secret.key }} # ENV from a key in Secret
// - value: {{ secret.example-secret }} # all ENVs from Secret
// value: {{ secret:example-secret:key }} # ENV from a key in Secret
// - value: {{ secret:example-secret }} # all ENVs from Secret
// - name: EXAMPLE4
// value: {{ configMap.configMapName.key }} # ENV from a key in ConfigMap
// - value: {{ configMap.configMapName }} # all key-pair values from ConfigMap are set as ENV
// value: {{ configMap:configMapName:key }} # ENV from a key in ConfigMap
// - value: {{ configMap:configMapName }} # all key-pair values from ConfigMap are set as ENV
func processEnvs(envs fn.Envs, referencedSecrets, referencedConfigMaps *sets.String) ([]corev1.EnvVar, []corev1.EnvFromSource, error) {
envVars := []corev1.EnvVar{{Name: "BUILT", Value: time.Now().Format("20060102T150405")}}
@ -259,7 +259,7 @@ func processEnvs(envs fn.Envs, referencedSecrets, referencedConfigMaps *sets.Str
for _, env := range envs {
if env.Name == nil && env.Value != nil {
// all key-pair values from secret/configMap are set as ENV, eg. {{ secret.secretName }} or {{ configMap.configMapName }}
// all key-pair values from secret/configMap are set as ENV, eg. {{ secret:secretName }} or {{ configMap:configMapName }}
if strings.HasPrefix(*env.Value, "{{") {
envFromSource, err := createEnvFromSource(*env.Value, referencedSecrets, referencedConfigMaps)
if err != nil {
@ -270,9 +270,9 @@ func processEnvs(envs fn.Envs, referencedSecrets, referencedConfigMaps *sets.Str
}
} else if env.Name != nil && env.Value != nil {
if strings.HasPrefix(*env.Value, "{{") {
slices := strings.Split(strings.Trim(*env.Value, "{} "), ".")
slices := strings.Split(strings.Trim(*env.Value, "{} "), ":")
if len(slices) == 3 {
// ENV from a key in secret/configMap, eg. FOO={{ secret.secretName.key }} FOO={{ configMap.configMapName.key }}
// ENV from a key in secret/configMap, eg. FOO={{ secret:secretName:key }} FOO={{ configMap:configMapName.key }}
valueFrom, err := createEnvVarSource(slices, referencedSecrets, referencedConfigMaps)
envVars = append(envVars, corev1.EnvVar{Name: *env.Name, ValueFrom: valueFrom})
if err != nil {
@ -280,7 +280,7 @@ func processEnvs(envs fn.Envs, referencedSecrets, referencedConfigMaps *sets.Str
}
continue
} else if len(slices) == 2 {
// ENV from the local ENV var, eg. FOO={{ env.LOCAL_ENV }}
// ENV from the local ENV var, eg. FOO={{ env:LOCAL_ENV }}
localValue, err := processLocalEnvValue(*env.Value)
if err != nil {
return nil, nil, err
@ -301,9 +301,9 @@ func processEnvs(envs fn.Envs, referencedSecrets, referencedConfigMaps *sets.Str
}
func createEnvFromSource(value string, referencedSecrets, referencedConfigMaps *sets.String) (*corev1.EnvFromSource, error) {
slices := strings.Split(strings.Trim(value, "{} "), ".")
slices := strings.Split(strings.Trim(value, "{} "), ":")
if len(slices) != 2 {
return nil, fmt.Errorf("env requires a value in form \"resourceType.name\" where \"resourceType\" can be one of \"configMap\" or \"secret\"; got %q", slices)
return nil, fmt.Errorf("env requires a value in form \"resourceType:name\" where \"resourceType\" can be one of \"configMap\" or \"secret\"; got %q", slices)
}
envVarSource := corev1.EnvFromSource{}
@ -334,7 +334,7 @@ func createEnvFromSource(value string, referencedSecrets, referencedConfigMaps *
referencedSecrets.Insert(sourceName)
}
default:
return nil, fmt.Errorf("unsupported env source type \"%q\"; supported source types are \"configMap\" or \"secret\"", slices[0])
return nil, fmt.Errorf("unsupported env source type %q; supported source types are \"configMap\" or \"secret\"", slices[0])
}
if len(sourceName) == 0 {
@ -347,7 +347,7 @@ func createEnvFromSource(value string, referencedSecrets, referencedConfigMaps *
func createEnvVarSource(slices []string, referencedSecrets, referencedConfigMaps *sets.String) (*corev1.EnvVarSource, error) {
if len(slices) != 3 {
return nil, fmt.Errorf("env requires a value in form \"resourceType.name.key\" where \"resourceType\" can be one of \"configMap\" or \"secret\"; got %q", slices)
return nil, fmt.Errorf("env requires a value in form \"resourceType:name:key\" where \"resourceType\" can be one of \"configMap\" or \"secret\"; got %q", slices)
}
envVarSource := corev1.EnvVarSource{}
@ -382,7 +382,7 @@ func createEnvVarSource(slices []string, referencedSecrets, referencedConfigMaps
referencedSecrets.Insert(sourceName)
}
default:
return nil, fmt.Errorf("unsupported env source type \"%q\"; supported source types are \"configMap\" or \"secret\"", slices[0])
return nil, fmt.Errorf("unsupported env source type %q; supported source types are \"configMap\" or \"secret\"", slices[0])
}
if len(sourceName) == 0 {
@ -390,13 +390,13 @@ func createEnvVarSource(slices []string, referencedSecrets, referencedConfigMaps
}
if len(sourceKey) == 0 {
return nil, fmt.Errorf("the key referenced by resource %s \"%s\" cannot be an empty string", sourceType, sourceName)
return nil, fmt.Errorf("the key referenced by resource %s %q cannot be an empty string", sourceType, sourceName)
}
return &envVarSource, nil
}
var evRegex = regexp.MustCompile(`^{{\s*(\w+)\s*.(\w+)\s*}}$`)
var evRegex = regexp.MustCompile(`^{{\s*(\w+)\s*:(\w+)\s*}}$`)
const (
ctxIdx = 1
@ -407,12 +407,12 @@ func processLocalEnvValue(val string) (string, error) {
match := evRegex.FindStringSubmatch(val)
if len(match) > valIdx {
if match[ctxIdx] != "env" {
return "", fmt.Errorf("allowed env value entry is \"{{ env.LOCAL_VALUE }}\"1; got: %q", match[ctxIdx])
return "", fmt.Errorf("allowed env value entry is \"{{ env:LOCAL_VALUE }}\"; got: %q", match[ctxIdx])
}
if v, ok := os.LookupEnv(match[valIdx]); ok {
return v, nil
} else {
return "", fmt.Errorf("required local environment variable \"%q\" is not set", match[valIdx])
return "", fmt.Errorf("required local environment variable %q is not set", match[valIdx])
}
} else {
return val, nil

View File

@ -31,9 +31,9 @@ func Test_processValue(t *testing.T) {
wantErr bool
}{
{name: "simple value", arg: "A_VALUE", want: "A_VALUE", wantErr: false},
{name: "using envvar value", arg: "{{ env.TEST_KNATIVE_DEPLOYER }}", want: "VALUE_FOR_TEST_KNATIVE_DEPLOYER", wantErr: false},
{name: "bad context", arg: "{{secret.S}}", want: "", wantErr: true},
{name: "unset envvar", arg: "{{env.SOME_UNSET_VAR}}", want: "", wantErr: true},
{name: "using envvar value", arg: "{{ env:TEST_KNATIVE_DEPLOYER }}", want: "VALUE_FOR_TEST_KNATIVE_DEPLOYER", wantErr: false},
{name: "bad context", arg: "{{secret:S}}", want: "", wantErr: true},
{name: "unset envvar", arg: "{{env:SOME_UNSET_VAR}}", want: "", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {