From 55696811e317a51767e09acab3d4d4e2abc6e982 Mon Sep 17 00:00:00 2001 From: Zbynek Roubalik <726523+zroubalik@users.noreply.github.com> Date: Wed, 11 Aug 2021 19:12:00 +0200 Subject: [PATCH] feat!: use key&value for Labels (#472) * feat!: use key/value for labels Signed-off-by: Zbynek Roubalik * fix typo Signed-off-by: Zbynek Roubalik --- cmd/config_envs.go | 4 +- cmd/config_labels.go | 24 +-- cmd/root.go | 6 +- cmd/root_test.go | 32 ++-- config.go | 92 ++++++---- config_test.go | 329 ++++++++++++++++++----------------- docs/guides/func_yaml.md | 4 +- function.go | 4 +- knative/deployer.go | 14 +- schema/func_yaml-schema.json | 53 ++++-- utils/names.go | 6 +- utils/names_test.go | 2 +- 12 files changed, 313 insertions(+), 257 deletions(-) diff --git a/cmd/config_envs.go b/cmd/config_envs.go index 29bd71d3..6dbc2155 100644 --- a/cmd/config_envs.go +++ b/cmd/config_envs.go @@ -175,7 +175,7 @@ func runAddEnvsPrompt(ctx context.Context, f fn.Function) (err error) { return } - newEnv := fn.Pair{} + newEnv := fn.Env{} switch selectedOption { // SECTION - add new Environment variable with the specified value @@ -403,7 +403,7 @@ func runRemoveEnvsPrompt(f fn.Function) (err error) { return } - var newEnvs fn.Pairs + var newEnvs fn.Envs removed := false for i, e := range f.Envs { if e.String() == selectedEnv { diff --git a/cmd/config_labels.go b/cmd/config_labels.go index 598ccc4c..a4a784b1 100644 --- a/cmd/config_labels.go +++ b/cmd/config_labels.go @@ -152,17 +152,17 @@ func runAddLabelsPrompt(ctx context.Context, f fn.Function) (err error) { return } - newPair := fn.Pair{} + newPair := fn.Label{} switch selectedOption { // SECTION - add new label with the specified value case optionLabelValue: qs := []*survey.Question{ { - Name: "name", - Prompt: &survey.Input{Message: "Please specify the label name:"}, + Name: "key", + Prompt: &survey.Input{Message: "Please specify the label key:"}, Validate: func(val interface{}) error { - return utils.ValidateLabelName(val.(string)) + return utils.ValidateLabelKey(val.(string)) }, }, { @@ -173,7 +173,7 @@ func runAddLabelsPrompt(ctx context.Context, f fn.Function) (err error) { }}, } answers := struct { - Name string + Key string Value string }{} @@ -185,17 +185,17 @@ func runAddLabelsPrompt(ctx context.Context, f fn.Function) (err error) { return } - newPair.Name = &answers.Name + newPair.Key = &answers.Key newPair.Value = &answers.Value // SECTION - add new label with value from a local environment variable case optionLabelLocal: qs := []*survey.Question{ { - Name: "name", - Prompt: &survey.Input{Message: "Please specify the label name:"}, + Name: "key", + Prompt: &survey.Input{Message: "Please specify the label key:"}, Validate: func(val interface{}) error { - return utils.ValidateLabelName(val.(string)) + return utils.ValidateLabelKey(val.(string)) }, }, { @@ -207,7 +207,7 @@ func runAddLabelsPrompt(ctx context.Context, f fn.Function) (err error) { }, } answers := struct { - Name string + Key string Value string }{} @@ -224,7 +224,7 @@ func runAddLabelsPrompt(ctx context.Context, f fn.Function) (err error) { } value := fmt.Sprintf("{{ env:%s }}", answers.Value) - newPair.Name = &answers.Name + newPair.Key = &answers.Key newPair.Value = &value } @@ -268,7 +268,7 @@ func runRemoveLabelsPrompt(f fn.Function) (err error) { return } - var newLabels fn.Pairs + var newLabels fn.Labels removed := false for i, e := range f.Labels { if e.String() == selectedLabel { diff --git a/cmd/root.go b/cmd/root.go index 228e8268..23c1fe51 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -276,7 +276,7 @@ func envFromCmd(cmd *cobra.Command) (*util.OrderedMap, []string, error) { return util.NewOrderedMap(), []string{}, nil } -func mergeEnvs(envs fn.Pairs, envToUpdate *util.OrderedMap, envToRemove []string) (fn.Pairs, error) { +func mergeEnvs(envs fn.Envs, envToUpdate *util.OrderedMap, envToRemove []string) (fn.Envs, error) { updated := sets.NewString() for i := range envs { @@ -294,7 +294,7 @@ func mergeEnvs(envs fn.Pairs, envToUpdate *util.OrderedMap, envToRemove []string if !updated.Has(name) { n := name v := value - envs = append(envs, fn.Pair{Name: &n, Value: &v}) + envs = append(envs, fn.Env{Name: &n, Value: &v}) } } @@ -309,7 +309,7 @@ func mergeEnvs(envs fn.Pairs, envToUpdate *util.OrderedMap, envToRemove []string errMsg := fn.ValidateEnvs(envs) if len(errMsg) > 0 { - return fn.Pairs{}, fmt.Errorf(strings.Join(errMsg, "\n")) + return fn.Envs{}, fmt.Errorf(strings.Join(errMsg, "\n")) } return envs, nil diff --git a/cmd/root_test.go b/cmd/root_test.go index 6625db46..84b84d28 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -18,77 +18,77 @@ func Test_mergeEnvMaps(t *testing.T) { v2 := "y" type args struct { - envs fn.Pairs + envs fn.Envs toUpdate *util.OrderedMap toRemove []string } tests := []struct { name string args args - want fn.Pairs + want fn.Envs }{ { "add new var to empty list", args{ - fn.Pairs{}, + fn.Envs{}, util.NewOrderedMapWithKVStrings([][]string{{a, v1}}), []string{}, }, - fn.Pairs{fn.Pair{Name: &a, Value: &v1}}, + fn.Envs{fn.Env{Name: &a, Value: &v1}}, }, { "add new var", args{ - fn.Pairs{fn.Pair{Name: &b, Value: &v2}}, + fn.Envs{fn.Env{Name: &b, Value: &v2}}, util.NewOrderedMapWithKVStrings([][]string{{a, v1}}), []string{}, }, - fn.Pairs{fn.Pair{Name: &b, Value: &v2}, fn.Pair{Name: &a, Value: &v1}}, + fn.Envs{fn.Env{Name: &b, Value: &v2}, fn.Env{Name: &a, Value: &v1}}, }, { "update var", args{ - fn.Pairs{fn.Pair{Name: &a, Value: &v1}}, + fn.Envs{fn.Env{Name: &a, Value: &v1}}, util.NewOrderedMapWithKVStrings([][]string{{a, v2}}), []string{}, }, - fn.Pairs{fn.Pair{Name: &a, Value: &v2}}, + fn.Envs{fn.Env{Name: &a, Value: &v2}}, }, { "update multiple vars", args{ - fn.Pairs{fn.Pair{Name: &a, Value: &v1}, fn.Pair{Name: &b, Value: &v2}}, + fn.Envs{fn.Env{Name: &a, Value: &v1}, fn.Env{Name: &b, Value: &v2}}, util.NewOrderedMapWithKVStrings([][]string{{a, v2}, {b, v1}}), []string{}, }, - fn.Pairs{fn.Pair{Name: &a, Value: &v2}, fn.Pair{Name: &b, Value: &v1}}, + fn.Envs{fn.Env{Name: &a, Value: &v2}, fn.Env{Name: &b, Value: &v1}}, }, { "remove var", args{ - fn.Pairs{fn.Pair{Name: &a, Value: &v1}}, + fn.Envs{fn.Env{Name: &a, Value: &v1}}, util.NewOrderedMap(), []string{a}, }, - fn.Pairs{}, + fn.Envs{}, }, { "remove multiple vars", args{ - fn.Pairs{fn.Pair{Name: &a, Value: &v1}, fn.Pair{Name: &b, Value: &v2}}, + fn.Envs{fn.Env{Name: &a, Value: &v1}, fn.Env{Name: &b, Value: &v2}}, util.NewOrderedMap(), []string{a, b}, }, - fn.Pairs{}, + fn.Envs{}, }, { "update and remove vars", args{ - fn.Pairs{fn.Pair{Name: &a, Value: &v1}, fn.Pair{Name: &b, Value: &v2}}, + fn.Envs{fn.Env{Name: &a, Value: &v1}, fn.Env{Name: &b, Value: &v2}}, util.NewOrderedMapWithKVStrings([][]string{{a, v2}}), []string{b}, }, - fn.Pairs{fn.Pair{Name: &a, Value: &v2}}, + fn.Envs{fn.Env{Name: &a, Value: &v2}}, }, } for _, tt := range tests { diff --git a/config.go b/config.go index be0b1b2e..718fbe94 100644 --- a/config.go +++ b/config.go @@ -42,13 +42,13 @@ func (v Volume) String() string { return "" } -type Pairs []Pair -type Pair struct { - Name *string `yaml:"name,omitempty"` +type Envs []Env +type Env struct { + Name *string `yaml:"name,omitempty" jsonschema:"pattern=^[-._a-zA-Z][-._a-zA-Z0-9]*$"` Value *string `yaml:"value"` } -func (e Pair) String() string { +func (e Env) String() string { if e.Name == nil && e.Value != nil { match := regWholeSecret.FindStringSubmatch(*e.Value) if len(match) == 2 { @@ -61,18 +61,40 @@ func (e Pair) String() string { } else if e.Name != nil && e.Value != nil { match := regKeyFromSecret.FindStringSubmatch(*e.Value) if len(match) == 3 { - return fmt.Sprintf("Entry \"%s\" with value set from key \"%s\" from Secret \"%s\"", *e.Name, match[2], match[1]) + return fmt.Sprintf("Env \"%s\" with value set from key \"%s\" from Secret \"%s\"", *e.Name, match[2], match[1]) } match = regKeyFromConfigMap.FindStringSubmatch(*e.Value) if len(match) == 3 { - return fmt.Sprintf("Entry \"%s\" with value set from key \"%s\" from ConfigMap \"%s\"", *e.Name, match[2], match[1]) + return fmt.Sprintf("Env \"%s\" with value set from key \"%s\" from ConfigMap \"%s\"", *e.Name, match[2], match[1]) } match = regLocalEnv.FindStringSubmatch(*e.Value) if len(match) == 2 { - return fmt.Sprintf("Entry \"%s\" with value set from local env variable \"%s\"", *e.Name, match[1]) + return fmt.Sprintf("Env \"%s\" with value set from local env variable \"%s\"", *e.Name, match[1]) } - return fmt.Sprintf("Entry \"%s\" with value \"%s\"", *e.Name, *e.Value) + return fmt.Sprintf("Env \"%s\" with value \"%s\"", *e.Name, *e.Value) + } + return "" +} + +type Labels []Label +type Label struct { + // Key consist of optional prefix part (ended by '/') and name part + // Prefix part validation pattern: [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* + // Name part validation pattern: ([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9] + Key *string `yaml:"key" jsonschema:"pattern=^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/)?([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$"` + Value *string `yaml:"value,omitempty" jsonschema:"pattern=^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$"` +} + +func (l Label) String() string { + if l.Key != nil && l.Value == nil { + return fmt.Sprintf("Label with key \"%s\"", *l.Key) + } else if l.Key != nil && l.Value != nil { + match := regLocalEnv.FindStringSubmatch(*l.Value) + if len(match) == 2 { + return fmt.Sprintf("Label with key \"%s\" and value set from local env variable \"%s\"", *l.Key, match[1]) + } + return fmt.Sprintf("Label with key \"%s\" and value \"%s\"", *l.Key, *l.Value) } return "" } @@ -117,10 +139,10 @@ type Config struct { Builder string `yaml:"builder"` BuilderMap map[string]string `yaml:"builderMap"` Volumes Volumes `yaml:"volumes"` - Envs Pairs `yaml:"envs"` + Envs Envs `yaml:"envs"` Annotations map[string]string `yaml:"annotations"` Options Options `yaml:"options"` - Labels Pairs `yaml:"labels"` + Labels Labels `yaml:"labels"` // Add new values to the toConfig/fromConfig functions. } @@ -307,7 +329,7 @@ func validateVolumes(volumes Volumes) (errors []string) { // - 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 -func ValidateEnvs(envs Pairs) (errors []string) { +func ValidateEnvs(envs Envs) (errors []string) { for i, env := range envs { if env.Name == nil && env.Value == nil { @@ -347,36 +369,38 @@ func ValidateEnvs(envs Pairs) (errors []string) { // Returns array of error messages, empty if no errors are found // // Allowed settings: -// - name: EXAMPLE1 # label directly from a value +// - key: EXAMPLE1 # label directly from a value // value: value1 -// - name: EXAMPLE2 # label from the local ENV var +// - key: EXAMPLE2 # label from the local ENV var // value: {{ env:MY_ENV }} -func ValidateLabels(labels Pairs) (errors []string) { +func ValidateLabels(labels Labels) (errors []string) { for i, label := range labels { - if label.Name == nil && label.Value == nil { + if label.Key == nil && label.Value == nil { errors = append(errors, fmt.Sprintf("label entry #%d is not properly set", i)) - } else if label.Value == nil { - errors = append(errors, fmt.Sprintf("label entry #%d is missing value field, only name '%s' is set", i, *label.Name)) + } else if label.Key == nil && label.Value != nil { + errors = append(errors, fmt.Sprintf("label entry #%d is missing key field, only value '%s' is set", i, *label.Value)) } else { - - if err := utils.ValidateLabelName(*label.Name); err != nil { - errors = append(errors, fmt.Sprintf("label entry #%d has invalid name or value set: %q %q; %s", i, *label.Name, *label.Value, err.Error())) - } else if err := utils.ValidateLabelValue(*label.Value); err != nil { - errors = append(errors, fmt.Sprintf("label entry #%d has invalid name or value set: %q %q; %s", i, *label.Name, *label.Value, err.Error())) + if err := utils.ValidateLabelKey(*label.Key); err != nil { + errors = append(errors, fmt.Sprintf("label entry #%d has invalid key set: %q; %s", i, *label.Key, err.Error())) } + if label.Value != nil { + if err := utils.ValidateLabelValue(*label.Value); err != nil { + errors = append(errors, fmt.Sprintf("label entry #%d has invalid value set: %q; %s", i, *label.Value, err.Error())) + } - if strings.HasPrefix(*label.Value, "{{") { - // ENV from the local ENV var; {{ env:MY_ENV }} - if !regLocalEnv.MatchString(*label.Value) { - errors = append(errors, - fmt.Sprintf( - "label entry #%d with name '%s' has invalid value field set, it has '%s', but allowed is only '{{ env:MY_ENV }}'", - i, *label.Name, *label.Value)) - } else { - match := regLocalEnv.FindStringSubmatch(*label.Value) - value := os.Getenv(match[1]) - if err := utils.ValidateLabelValue(value); err != nil { - errors = append(errors, fmt.Sprintf("label entry #%d with name '%s' has invalid value when the environment is evaluated: '%s': %s", i, *label.Name, value, err.Error())) + if strings.HasPrefix(*label.Value, "{{") { + // ENV from the local ENV var; {{ env:MY_ENV }} + if !regLocalEnv.MatchString(*label.Value) { + errors = append(errors, + fmt.Sprintf( + "label entry #%d with key '%s' has invalid value field set, it has '%s', but allowed is only '{{ env:MY_ENV }}'", + i, *label.Key, *label.Value)) + } else { + match := regLocalEnv.FindStringSubmatch(*label.Value) + value := os.Getenv(match[1]) + if err := utils.ValidateLabelValue(value); err != nil { + errors = append(errors, fmt.Sprintf("label entry #%d with key '%s' has invalid value when the environment is evaluated: '%s': %s", i, *label.Key, value, err.Error())) + } } } } diff --git a/config_test.go b/config_test.go index 3fa3d92e..994adb33 100644 --- a/config_test.go +++ b/config_test.go @@ -180,13 +180,13 @@ func Test_validateEnvs(t *testing.T) { tests := []struct { name string - envs Pairs + envs Envs errs int }{ { "correct entry - single env with value", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, Value: &value, }, @@ -195,8 +195,8 @@ func Test_validateEnvs(t *testing.T) { }, { "incorrect entry - missing value", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, }, }, @@ -204,8 +204,8 @@ func Test_validateEnvs(t *testing.T) { }, { "incorrect entry - invalid name", - Pairs{ - Pair{ + Envs{ + Env{ Name: &incorrectName, Value: &value, }, @@ -214,8 +214,8 @@ func Test_validateEnvs(t *testing.T) { }, { "incorrect entry - invalid name2", - Pairs{ - Pair{ + Envs{ + Env{ Name: &incorrectName2, Value: &value, }, @@ -224,12 +224,12 @@ func Test_validateEnvs(t *testing.T) { }, { "correct entry - multiple envs with value", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, Value: &value, }, - Pair{ + Env{ Name: &name2, Value: &value2, }, @@ -238,11 +238,11 @@ func Test_validateEnvs(t *testing.T) { }, { "incorrect entry - mmissing value - multiple envs", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, }, - Pair{ + Env{ Name: &name2, }, }, @@ -250,8 +250,8 @@ func Test_validateEnvs(t *testing.T) { }, { "correct entry - single env with value Local env", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, Value: &valueLocalEnv, }, @@ -260,16 +260,16 @@ func Test_validateEnvs(t *testing.T) { }, { "correct entry - multiple envs with value Local env", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, Value: &valueLocalEnv, }, - Pair{ + Env{ Name: &name, Value: &valueLocalEnv2, }, - Pair{ + Env{ Name: &name, Value: &valueLocalEnv3, }, @@ -278,20 +278,20 @@ func Test_validateEnvs(t *testing.T) { }, { "incorrect entry - multiple envs with value Local env", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, Value: &valueLocalEnv, }, - Pair{ + Env{ Name: &name, Value: &valueLocalEnvIncorrect, }, - Pair{ + Env{ Name: &name, Value: &valueLocalEnvIncorrect2, }, - Pair{ + Env{ Name: &name, Value: &valueLocalEnvIncorrect3, }, @@ -300,8 +300,8 @@ func Test_validateEnvs(t *testing.T) { }, { "correct entry - single secret with key", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, Value: &valueSecretKey, }, @@ -310,8 +310,8 @@ func Test_validateEnvs(t *testing.T) { }, { "correct entry - single configMap with key", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, Value: &valueConfigMapKey, }, @@ -320,16 +320,16 @@ func Test_validateEnvs(t *testing.T) { }, { "correct entry - multiple secrets with key", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, Value: &valueSecretKey, }, - Pair{ + Env{ Name: &name, Value: &valueSecretKey2, }, - Pair{ + Env{ Name: &name, Value: &valueSecretKey3, }, @@ -338,12 +338,12 @@ func Test_validateEnvs(t *testing.T) { }, { "correct entry - both secret and configmap with key", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, Value: &valueSecretKey, }, - Pair{ + Env{ Name: &name, Value: &valueConfigMapKey, }, @@ -352,8 +352,8 @@ func Test_validateEnvs(t *testing.T) { }, { "incorrect entry - single secret with key", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, Value: &valueSecretKeyIncorrect, }, @@ -362,20 +362,20 @@ func Test_validateEnvs(t *testing.T) { }, { "incorrect entry - mutliple secrets with key", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, Value: &valueSecretKey, }, - Pair{ + Env{ Name: &name, Value: &valueSecretKeyIncorrect, }, - Pair{ + Env{ Name: &name, Value: &valueSecretKeyIncorrect2, }, - Pair{ + Env{ Name: &name, Value: &valueSecretKeyIncorrect3, }, @@ -384,8 +384,8 @@ func Test_validateEnvs(t *testing.T) { }, { "correct entry - single whole secret", - Pairs{ - Pair{ + Envs{ + Env{ Value: &valueSecret, }, }, @@ -393,8 +393,8 @@ func Test_validateEnvs(t *testing.T) { }, { "correct entry - single whole configMap", - Pairs{ - Pair{ + Envs{ + Env{ Value: &valueConfigMap, }, }, @@ -402,14 +402,14 @@ func Test_validateEnvs(t *testing.T) { }, { "correct entry - multiple whole secret", - Pairs{ - Pair{ + Envs{ + Env{ Value: &valueSecret, }, - Pair{ + Env{ Value: &valueSecret2, }, - Pair{ + Env{ Value: &valueSecret3, }, }, @@ -417,11 +417,11 @@ func Test_validateEnvs(t *testing.T) { }, { "correct entry - both whole secret and configMap", - Pairs{ - Pair{ + Envs{ + Env{ Value: &valueSecret, }, - Pair{ + Env{ Value: &valueConfigMap, }, }, @@ -429,8 +429,8 @@ func Test_validateEnvs(t *testing.T) { }, { "incorrect entry - single whole secret", - Pairs{ - Pair{ + Envs{ + Env{ Value: &value, }, }, @@ -438,29 +438,29 @@ func Test_validateEnvs(t *testing.T) { }, { "incorrect entry - multiple whole secret", - Pairs{ - Pair{ + Envs{ + Env{ Value: &valueSecretIncorrect, }, - Pair{ + Env{ Value: &valueSecretIncorrect2, }, - Pair{ + Env{ Value: &valueSecretIncorrect3, }, - Pair{ + Env{ Value: &value, }, - Pair{ + Env{ Value: &valueLocalEnv, }, - Pair{ + Env{ Value: &valueLocalEnv2, }, - Pair{ + Env{ Value: &valueLocalEnv3, }, - Pair{ + Env{ Value: &valueSecret, }, }, @@ -468,52 +468,52 @@ func Test_validateEnvs(t *testing.T) { }, { "correct entry - all combinations", - Pairs{ - Pair{ + Envs{ + Env{ Name: &name, Value: &value, }, - Pair{ + Env{ Name: &name2, Value: &value2, }, - Pair{ + Env{ Name: &name, Value: &valueLocalEnv, }, - Pair{ + Env{ Name: &name, Value: &valueLocalEnv2, }, - Pair{ + Env{ Name: &name, Value: &valueLocalEnv3, }, - Pair{ + Env{ Value: &valueSecret, }, - Pair{ + Env{ Value: &valueSecret2, }, - Pair{ + Env{ Value: &valueSecret3, }, - Pair{ + Env{ Value: &valueConfigMap, }, - Pair{ + Env{ Name: &name, Value: &valueSecretKey, }, - Pair{ + Env{ Name: &name, Value: &valueSecretKey2, }, - Pair{ + Env{ Name: &name, Value: &valueSecretKey3, }, - Pair{ + Env{ Name: &name, Value: &valueConfigMapKey, }, @@ -534,15 +534,15 @@ func Test_validateEnvs(t *testing.T) { func Test_validateLabels(t *testing.T) { - name := "name" - name2 := "name-two" - name3 := "prefix.io/name3" + key := "name" + key2 := "name-two" + key3 := "prefix.io/name3" value := "value" value2 := "value2" value3 := "value3" - incorrectName := ",foo" - incorrectName2 := ":foo" + incorrectKey := ",foo" + incorrectKey2 := ":foo" incorrectValue := ":foo" valueLocalEnv := "{{ env:MY_ENV }}" @@ -559,15 +559,15 @@ func Test_validateLabels(t *testing.T) { valueLocalEnv4 := "{{env:GOOD_EXAMPLE}}" tests := []struct { - name string - labels Pairs + key string + labels Labels errs int }{ { "correct entry - single label with value", - Pairs{ - Pair{ - Name: &name, + Labels{ + Label{ + Key: &key, Value: &value, }, }, @@ -575,37 +575,48 @@ func Test_validateLabels(t *testing.T) { }, { "correct entry - prefixed label with value", - Pairs{ - Pair{ - Name: &name3, + Labels{ + Label{ + Key: &key3, Value: &value3, }, }, 0, }, { - "incorrect entry - missing value", - Pairs{ - Pair{ - Name: &name, + "incorrect entry - missing key", + Labels{ + Label{ + Value: &value, }, }, 1, + },{ + "incorrect entry - missing multiple keys", + Labels{ + Label{ + Value: &value, + }, + Label{ + Value: &value2, + }, + }, + 2, }, { - "incorrect entry - invalid name", - Pairs{ - Pair{ - Name: &incorrectName, + "incorrect entry - invalid key", + Labels{ + Label{ + Key: &incorrectKey, Value: &value, }, }, 1, }, { - "incorrect entry - invalid name2", - Pairs{ - Pair{ - Name: &incorrectName2, + "incorrect entry - invalid key2", + Labels{ + Label{ + Key: &incorrectKey2, Value: &value, }, }, @@ -613,9 +624,9 @@ func Test_validateLabels(t *testing.T) { }, { "incorrect entry - invalid value", - Pairs{ - Pair{ - Name: &name, + Labels{ + Label{ + Key: &key, Value: &incorrectValue, }, }, @@ -623,35 +634,35 @@ func Test_validateLabels(t *testing.T) { }, { "correct entry - multiple labels with value", - Pairs{ - Pair{ - Name: &name, + Labels{ + Label{ + Key: &key, Value: &value, }, - Pair{ - Name: &name2, + Label{ + Key: &key2, Value: &value2, }, }, 0, }, { - "incorrect entry - missing value - multiple labels", - Pairs{ - Pair{ - Name: &name, + "correct entry - missing value - multiple labels", + Labels{ + Label{ + Key: &key, }, - Pair{ - Name: &name2, + Label{ + Key: &key2, }, }, - 2, + 0, }, { "correct entry - single label with value from local env", - Pairs{ - Pair{ - Name: &name, + Labels{ + Label{ + Key: &key, Value: &valueLocalEnv, }, }, @@ -659,17 +670,17 @@ func Test_validateLabels(t *testing.T) { }, { "correct entry - multiple labels with values from Local env", - Pairs{ - Pair{ - Name: &name, + Labels{ + Label{ + Key: &key, Value: &valueLocalEnv, }, - Pair{ - Name: &name, + Label{ + Key: &key, Value: &valueLocalEnv2, }, - Pair{ - Name: &name, + Label{ + Key: &key, Value: &valueLocalEnv3, }, }, @@ -677,21 +688,21 @@ func Test_validateLabels(t *testing.T) { }, { "incorrect entry - multiple labels with values from Local env", - Pairs{ - Pair{ - Name: &name, + Labels{ + Label{ + Key: &key, Value: &valueLocalEnv, }, - Pair{ - Name: &name, + Label{ + Key: &key, Value: &valueLocalEnvIncorrect, }, - Pair{ - Name: &name, + Label{ + Key: &key, Value: &valueLocalEnvIncorrect2, }, - Pair{ - Name: &name, + Label{ + Key: &key, Value: &valueLocalEnvIncorrect3, }, }, @@ -699,18 +710,18 @@ func Test_validateLabels(t *testing.T) { }, { "correct entry - good environment variable value", - Pairs{ - Pair{ - Name: &name, + Labels{ + Label{ + Key: &key, Value: &valueLocalEnv4, }, }, 0, }, { "incorrect entry - bad environment variable value", - Pairs{ - Pair{ - Name: &name, + Labels{ + Label{ + Key: &key, Value: &valueLocalEnvIncorrect4, }, }, @@ -718,29 +729,29 @@ func Test_validateLabels(t *testing.T) { }, { "correct entry - all combinations", - Pairs{ - Pair{ - Name: &name, + Labels{ + Label{ + Key: &key, Value: &value, }, - Pair{ - Name: &name2, + Label{ + Key: &key2, Value: &value2, }, - Pair{ - Name: &name3, + Label{ + Key: &key3, Value: &value3, }, - Pair{ - Name: &name, + Label{ + Key: &key, Value: &valueLocalEnv, }, - Pair{ - Name: &name, + Label{ + Key: &key, Value: &valueLocalEnv2, }, - Pair{ - Name: &name, + Label{ + Key: &key, Value: &valueLocalEnv3, }, }, @@ -749,7 +760,7 @@ func Test_validateLabels(t *testing.T) { } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.key, func(t *testing.T) { if got := ValidateLabels(tt.labels); len(got) != tt.errs { t.Errorf("validateLabels() = %v\n got %d errors but want %d", got, len(got), tt.errs) } diff --git a/docs/guides/func_yaml.md b/docs/guides/func_yaml.md index 97c34ec5..a027e424 100644 --- a/docs/guides/func_yaml.md +++ b/docs/guides/func_yaml.md @@ -69,9 +69,9 @@ directly from a value or from a local environment value. Eg. `'{{ env:USER }}'`, ```yaml labels: -- name: role # (1) label directly from a value +- key: role # (1) label directly from a value value: backend -- name: author # (2) label from a local environment value +- key: author # (2) label from a local environment value value: '{{ env:USER }}' ``` diff --git a/function.go b/function.go index 2de6fdbb..01959080 100644 --- a/function.go +++ b/function.go @@ -56,7 +56,7 @@ type Function struct { Volumes Volumes // Env variables to be set - Envs Pairs + Envs Envs // Map containing user-supplied annotations // Example: { "division": "finance" } @@ -66,7 +66,7 @@ type Function struct { Options Options // Map of user-supplied labels - Labels Pairs + Labels Labels } // NewFunction loads a Function from a path on disk. use .Initialized() to determine if diff --git a/knative/deployer.go b/knative/deployer.go index d08a9039..8d77a3ba 100644 --- a/knative/deployer.go +++ b/knative/deployer.go @@ -250,9 +250,9 @@ func updateService(f fn.Function, newEnv []corev1.EnvVar, newEnvFrom []corev1.En // processLabels generates a map of labels as key/value pairs from a function config // labels: -// - name: EXAMPLE1 # Label directly from a value +// - key: EXAMPLE1 # Label directly from a value // value: value1 -// - name: EXAMPLE2 # Label from the local ENV var +// - key: EXAMPLE2 # Label from the local ENV var // value: {{ env:MY_ENV }} func processLabels(f fn.Function) (map[string]string, error) { labels := map[string]string{ @@ -260,7 +260,7 @@ func processLabels(f fn.Function) (map[string]string, error) { "boson.dev/runtime": f.Runtime, } for _, label := range f.Labels { - if label.Name != nil && label.Value != nil { + if label.Key != nil && label.Value != nil { if strings.HasPrefix(*label.Value, "{{") { slices := strings.Split(strings.Trim(*label.Value, "{} "), ":") if len(slices) == 2 { @@ -269,14 +269,16 @@ func processLabels(f fn.Function) (map[string]string, error) { if err != nil { return nil, err } - labels[*label.Name] = localValue + labels[*label.Key] = localValue continue } } else { // a standard label with key and value, eg. author=alice@example.com - labels[*label.Name] = *label.Value + labels[*label.Key] = *label.Value continue } + } else if label.Key != nil && label.Value == nil { + labels[*label.Key] = "" } } @@ -295,7 +297,7 @@ func processLabels(f fn.Function) (map[string]string, error) { // - 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 -func processEnvs(envs fn.Pairs, referencedSecrets, referencedConfigMaps *sets.String) ([]corev1.EnvVar, []corev1.EnvFromSource, error) { +func processEnvs(envs fn.Envs, referencedSecrets, referencedConfigMaps *sets.String) ([]corev1.EnvVar, []corev1.EnvFromSource, error) { envVars := []corev1.EnvVar{{Name: "BUILT", Value: time.Now().Format("20060102T150405")}} envFrom := []corev1.EnvFromSource{} diff --git a/schema/func_yaml-schema.json b/schema/func_yaml-schema.json index 22d27ae6..795542c9 100644 --- a/schema/func_yaml-schema.json +++ b/schema/func_yaml-schema.json @@ -55,7 +55,7 @@ "envs": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/Pair" + "$ref": "#/definitions/Env" }, "type": "array" }, @@ -73,7 +73,8 @@ }, "labels": { "items": { - "$ref": "#/definitions/Pair" + "$schema": "http://json-schema.org/draft-04/schema#", + "$ref": "#/definitions/Label" }, "type": "array" } @@ -81,6 +82,39 @@ "additionalProperties": false, "type": "object" }, + "Env": { + "required": [ + "value" + ], + "properties": { + "name": { + "pattern": "^[-._a-zA-Z][-._a-zA-Z0-9]*$", + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Label": { + "required": [ + "key" + ], + "properties": { + "key": { + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/)?([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$", + "type": "string" + }, + "value": { + "pattern": "^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$", + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, "Options": { "properties": { "scale": { @@ -95,21 +129,6 @@ "additionalProperties": false, "type": "object" }, - "Pair": { - "required": [ - "value" - ], - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - }, "ResourcesLimitsOptions": { "properties": { "cpu": { diff --git a/utils/names.go b/utils/names.go index ef3a14c8..bf158c45 100644 --- a/utils/names.go +++ b/utils/names.go @@ -44,15 +44,15 @@ func ValidateEnvVarName(name string) error { return nil } -// ValidateLabelName validates that the input name is a valid Kubernetes key. +// ValidateLabelKey validates that the input name is a valid Kubernetes key. // Valid label names have two segments: an optional prefix and name, separated by a slash (/). // The name segment is required and must be 63 characters or less, beginning and ending with // an alphanumeric character ([a-z0-9A-Z]) with dashes (-), underscores (_), dots (.), and // alphanumerics between. The prefix is optional. If specified, the prefix must be a DNS subdomain: // a series of DNS labels separated by dots (.), not longer than 253 characters in total, followed // by a slash (/). -func ValidateLabelName(name string) error { - errs := validation.IsQualifiedName(name) +func ValidateLabelKey(key string) error { + errs := validation.IsQualifiedName(key) if len(errs) > 0 { return ErrInvalidLabel(errors.New(strings.Join(errs, ""))) } diff --git a/utils/names_test.go b/utils/names_test.go index f8b52807..58282609 100644 --- a/utils/names_test.go +++ b/utils/names_test.go @@ -86,7 +86,7 @@ func TestValidateLabelName(t *testing.T) { } for _, c := range cases { - err := ValidateLabelName(c.In) + err := ValidateLabelKey(c.In) if err != nil && c.Valid { t.Fatalf("Unexpected error: %v, for '%v'", err, c.In) }