diff --git a/pkg/scalers/authentication/authentication_types.go b/pkg/scalers/authentication/authentication_types.go index be272fc3290..908010e2483 100644 --- a/pkg/scalers/authentication/authentication_types.go +++ b/pkg/scalers/authentication/authentication_types.go @@ -66,37 +66,37 @@ type AuthMeta struct { // BasicAuth is a basic authentication type type BasicAuth struct { - Username string `keda:"name=username, parsingOrder=authParams"` - Password string `keda:"name=password, parsingOrder=authParams"` + Username string `keda:"name=username, order=authParams"` + Password string `keda:"name=password, order=authParams"` } // CertAuth is a client certificate authentication type type CertAuth struct { - Cert string `keda:"name=cert, parsingOrder=authParams"` - Key string `keda:"name=key, parsingOrder=authParams"` - CA string `keda:"name=ca, parsingOrder=authParams"` + Cert string `keda:"name=cert, order=authParams"` + Key string `keda:"name=key, order=authParams"` + CA string `keda:"name=ca, order=authParams"` } // OAuth is an oAuth2 authentication type type OAuth struct { - OauthTokenURI string `keda:"name=oauthTokenURI, parsingOrder=authParams"` - Scopes []string `keda:"name=scopes, parsingOrder=authParams"` - ClientID string `keda:"name=clientID, parsingOrder=authParams"` - ClientSecret string `keda:"name=clientSecret, parsingOrder=authParams"` - EndpointParams url.Values `keda:"name=endpointParams, parsingOrder=authParams"` + OauthTokenURI string `keda:"name=oauthTokenURI, order=authParams"` + Scopes []string `keda:"name=scopes, order=authParams"` + ClientID string `keda:"name=clientID, order=authParams"` + ClientSecret string `keda:"name=clientSecret, order=authParams"` + EndpointParams url.Values `keda:"name=endpointParams, order=authParams"` } // CustomAuth is a custom header authentication type type CustomAuth struct { - CustomAuthHeader string `keda:"name=customAuthHeader, parsingOrder=authParams"` - CustomAuthValue string `keda:"name=customAuthValue, parsingOrder=authParams"` + CustomAuthHeader string `keda:"name=customAuthHeader, order=authParams"` + CustomAuthValue string `keda:"name=customAuthValue, order=authParams"` } // Config is the configuration for the authentication types type Config struct { - Modes []Type `keda:"name=authModes, parsingOrder=triggerMetadata, enum=apiKey;basic;tls;bearer;custom;oauth, exclusive=bearer;basic;oauth, optional"` + Modes []Type `keda:"name=authModes, order=triggerMetadata, enum=apiKey;basic;tls;bearer;custom;oauth, exclusiveSet=bearer;basic;oauth, optional"` - BearerToken string `keda:"name=bearerToken, parsingOrder=authParams, optional"` + BearerToken string `keda:"name=bearerToken, order=authParams, optional"` BasicAuth `keda:"optional"` CertAuth `keda:"optional"` OAuth `keda:"optional"` diff --git a/pkg/scalers/prometheus_scaler.go b/pkg/scalers/prometheus_scaler.go index 2bd519e2118..99fc6f9f5ea 100644 --- a/pkg/scalers/prometheus_scaler.go +++ b/pkg/scalers/prometheus_scaler.go @@ -45,7 +45,7 @@ type prometheusScaler struct { logger logr.Logger } -// sometimes should consider there is an error we can accept +// IgnoreNullValues - sometimes should consider there is an error we can accept // default value is true/t, to ignore the null value return from prometheus // change to false/f if can not accept prometheus return null values // https://github.com/kedacore/keda/issues/3065 @@ -53,18 +53,18 @@ type prometheusMetadata struct { triggerIndex int PrometheusAuth *authentication.Config `keda:"optional"` - ServerAddress string `keda:"name=serverAddress, parsingOrder=triggerMetadata"` - Query string `keda:"name=query, parsingOrder=triggerMetadata"` - QueryParameters map[string]string `keda:"name=queryParameters, parsingOrder=triggerMetadata, optional"` - Threshold float64 `keda:"name=threshold, parsingOrder=triggerMetadata"` - ActivationThreshold float64 `keda:"name=activationThreshold, parsingOrder=triggerMetadata, optional"` - Namespace string `keda:"name=namespace, parsingOrder=triggerMetadata, optional"` - CustomHeaders map[string]string `keda:"name=customHeaders, parsingOrder=triggerMetadata, optional"` - IgnoreNullValues bool `keda:"name=ignoreNullValues, parsingOrder=triggerMetadata, optional, default=true"` - UnsafeSSL bool `keda:"name=unsafeSsl, parsingOrder=triggerMetadata, optional"` + ServerAddress string `keda:"name=serverAddress, order=triggerMetadata"` + Query string `keda:"name=query, order=triggerMetadata"` + QueryParameters map[string]string `keda:"name=queryParameters, order=triggerMetadata, optional"` + Threshold float64 `keda:"name=threshold, order=triggerMetadata"` + ActivationThreshold float64 `keda:"name=activationThreshold, order=triggerMetadata, optional"` + Namespace string `keda:"name=namespace, order=triggerMetadata, optional"` + CustomHeaders map[string]string `keda:"name=customHeaders, order=triggerMetadata, optional"` + IgnoreNullValues bool `keda:"name=ignoreNullValues, order=triggerMetadata, optional, default=true"` + UnsafeSSL bool `keda:"name=unsafeSsl, order=triggerMetadata, optional"` // deprecated - CortexOrgID string `keda:"name=cortexOrgID, parsingOrder=triggerMetadata, optional, deprecated=use customHeaders instead"` + CortexOrgID string `keda:"name=cortexOrgID, order=triggerMetadata, optional, deprecated=use customHeaders instead"` } type promQueryResult struct { diff --git a/pkg/scalers/scalersconfig/typed_config.go b/pkg/scalers/scalersconfig/typed_config.go index 23694edcdcb..125cf9a5ad6 100644 --- a/pkg/scalers/scalersconfig/typed_config.go +++ b/pkg/scalers/scalersconfig/typed_config.go @@ -25,6 +25,8 @@ import ( "runtime/debug" "strconv" "strings" + + "golang.org/x/exp/maps" ) // CustomValidator is an interface that can be implemented to validate the configuration of the typed config @@ -42,8 +44,15 @@ const ( AuthParams ParsingOrder = "authParams" ) +// mapParsingOrder is a map with set of valid parsing orders +var mapParsingOrder = map[ParsingOrder]bool{ + TriggerMetadata: true, + ResolvedEnv: true, + AuthParams: true, +} + // separators for field tag structure -// e.g. name=stringVal,parsingOrder=triggerMetadata;resolvedEnv;authParams,optional +// e.g. name=stringVal,order=triggerMetadata;resolvedEnv;authParams,optional const ( tagSeparator = "," tagKeySeparator = "=" @@ -61,10 +70,10 @@ const ( optionalTag = "optional" deprecatedTag = "deprecated" defaultTag = "default" - parsingOrderTag = "parsingOrder" + orderTag = "order" nameTag = "name" enumTag = "enum" - exclusiveTag = "exclusive" + exclusiveSetTag = "exclusiveSet" ) // Params is a struct that represents the parameter list that can be used in the keda tag @@ -78,9 +87,9 @@ type Params struct { // Optional is the 'optional' tag parameter defining if the parameter is optional Optional bool - // ParsingOrder is the 'parsingOrder' tag parameter defining the order in which the parameter is looked up + // Order is the 'order' tag parameter defining the parsing order in which the parameter is looked up // in the triggerMetadata, resolvedEnv or authParams maps - ParsingOrder []ParsingOrder + Order []ParsingOrder // Default is the 'default' tag parameter defining the default value of the parameter if it's not found // in any of the maps from ParsingOrder @@ -93,8 +102,8 @@ type Params struct { // Enum is the 'enum' tag parameter defining the list of possible values for the parameter Enum []string - // Exclusive is the 'exclusive' tag parameter defining the list of values that are mutually exclusive - Exclusive []string + // ExclusiveSet is the 'exclusiveSet' tag parameter defining the list of values that are mutually exclusive + ExclusiveSet []string } // IsNested is a function that returns true if the parameter is nested @@ -181,7 +190,11 @@ func (sc *ScalerConfig) setValue(field reflect.Value, params Params) error { return nil } if !exists && !(params.Optional || params.IsDeprecated()) { - return fmt.Errorf("missing required parameter %q in %v", params.Name, params.ParsingOrder) + if len(params.Order) == 0 { + orderSlice := maps.Keys(mapParsingOrder) + return fmt.Errorf("missing required parameter %q, no 'order' tag, provide any from %v", params.Name, orderSlice) + } + return fmt.Errorf("missing required parameter %q in %v", params.Name, params.Order) } if params.Enum != nil { enumMap := make(map[string]bool) @@ -200,9 +213,9 @@ func (sc *ScalerConfig) setValue(field reflect.Value, params Params) error { return fmt.Errorf("parameter %q value %q must be one of %v", params.Name, valFromConfig, params.Enum) } } - if params.Exclusive != nil { + if params.ExclusiveSet != nil { exclusiveMap := make(map[string]bool) - for _, e := range params.Exclusive { + for _, e := range params.ExclusiveSet { exclusiveMap[e] = true } split := strings.Split(valFromConfig, elemSeparator) @@ -214,7 +227,7 @@ func (sc *ScalerConfig) setValue(field reflect.Value, params Params) error { } } if exclusiveCount > 1 { - return fmt.Errorf("parameter %q value %q must contain only one of %v", params.Name, valFromConfig, params.Exclusive) + return fmt.Errorf("parameter %q value %q must contain only one of %v", params.Name, valFromConfig, params.ExclusiveSet) } } if params.IsNested() { @@ -326,7 +339,7 @@ func setConfigValueHelper(valFromConfig string, field reflect.Value) error { // configParamValue is a function that returns the value of the parameter based on the parsing order func (sc *ScalerConfig) configParamValue(params Params) (string, bool) { - for _, po := range params.ParsingOrder { + for _, po := range params.Order { var m map[string]string key := params.Name switch po { @@ -338,7 +351,8 @@ func (sc *ScalerConfig) configParamValue(params Params) (string, bool) { m = sc.ResolvedEnv key = sc.TriggerMetadata[fmt.Sprintf("%sFromEnv", params.Name)] default: - m = sc.TriggerMetadata + // this is checked when parsing the tags but adding as default case to avoid any potential future problems + return "", false } if param, ok := m[key]; ok && param != "" { return strings.TrimSpace(param), true @@ -362,12 +376,16 @@ func paramsFromTag(tag string, field reflect.StructField) (Params, error) { if len(tsplit) > 1 { params.Optional, _ = strconv.ParseBool(strings.TrimSpace(tsplit[1])) } - case parsingOrderTag: + case orderTag: if len(tsplit) > 1 { - parsingOrder := strings.Split(tsplit[1], tagValueSeparator) - for _, po := range parsingOrder { + order := strings.Split(tsplit[1], tagValueSeparator) + for _, po := range order { poTyped := ParsingOrder(strings.TrimSpace(po)) - params.ParsingOrder = append(params.ParsingOrder, poTyped) + if !mapParsingOrder[poTyped] { + poSlice := maps.Keys(mapParsingOrder) + return params, fmt.Errorf("unknown parsing order value %s, has to be one of %s", po, poSlice) + } + params.Order = append(params.Order, poTyped) } } case nameTag: @@ -388,9 +406,9 @@ func paramsFromTag(tag string, field reflect.StructField) (Params, error) { if len(tsplit) > 1 { params.Enum = strings.Split(tsplit[1], tagValueSeparator) } - case exclusiveTag: + case exclusiveSetTag: if len(tsplit) > 1 { - params.Exclusive = strings.Split(tsplit[1], tagValueSeparator) + params.ExclusiveSet = strings.Split(tsplit[1], tagValueSeparator) } case "": continue diff --git a/pkg/scalers/scalersconfig/typed_config_test.go b/pkg/scalers/scalersconfig/typed_config_test.go index c9f1e4f75e1..26855b1d0d0 100644 --- a/pkg/scalers/scalersconfig/typed_config_test.go +++ b/pkg/scalers/scalersconfig/typed_config_test.go @@ -44,11 +44,11 @@ func TestBasicTypedConfig(t *testing.T) { } type testStruct struct { - StringVal string `keda:"name=stringVal, parsingOrder=triggerMetadata"` - IntVal int `keda:"name=intVal, parsingOrder=triggerMetadata"` - BoolVal bool `keda:"name=boolVal, parsingOrder=resolvedEnv"` - FloatVal float64 `keda:"name=floatVal, parsingOrder=resolvedEnv"` - AuthVal string `keda:"name=auth, parsingOrder=authParams"` + StringVal string `keda:"name=stringVal, order=triggerMetadata"` + IntVal int `keda:"name=intVal, order=triggerMetadata"` + BoolVal bool `keda:"name=boolVal, order=resolvedEnv"` + FloatVal float64 `keda:"name=floatVal, order=resolvedEnv"` + AuthVal string `keda:"name=auth, order=authParams"` } ts := testStruct{} @@ -82,9 +82,9 @@ func TestParsingOrder(t *testing.T) { } type testStruct struct { - StringVal string `keda:"name=stringVal, parsingOrder=resolvedEnv;triggerMetadata"` - IntVal int `keda:"name=intVal, parsingOrder=triggerMetadata;resolvedEnv"` - FloatVal float64 `keda:"name=floatVal, parsingOrder=resolvedEnv;triggerMetadata"` + StringVal string `keda:"name=stringVal, order=resolvedEnv;triggerMetadata"` + IntVal int `keda:"name=intVal, order=triggerMetadata;resolvedEnv"` + FloatVal float64 `keda:"name=floatVal, order=resolvedEnv;triggerMetadata"` } ts := testStruct{} @@ -107,9 +107,9 @@ func TestOptional(t *testing.T) { } type testStruct struct { - StringVal string `keda:"name=stringVal, parsingOrder=triggerMetadata"` - IntValOptional int `keda:"name=intVal, parsingOrder=triggerMetadata, optional"` - IntValAlsoOptional int `keda:"name=intVal, parsingOrder=triggerMetadata, optional=true"` + StringVal string `keda:"name=stringVal, order=triggerMetadata"` + IntValOptional int `keda:"name=intVal, order=triggerMetadata, optional"` + IntValAlsoOptional int `keda:"name=intVal, order=triggerMetadata, optional=true"` } ts := testStruct{} @@ -128,7 +128,7 @@ func TestMissing(t *testing.T) { sc := &ScalerConfig{} type testStruct struct { - StringVal string `keda:"name=stringVal, parsingOrder=triggerMetadata"` + StringVal string `keda:"name=stringVal, order=triggerMetadata"` } ts := testStruct{} @@ -147,7 +147,7 @@ func TestDeprecated(t *testing.T) { } type testStruct struct { - StringVal string `keda:"name=stringVal, parsingOrder=triggerMetadata, deprecated=deprecated"` + StringVal string `keda:"name=stringVal, order=triggerMetadata, deprecated=deprecated"` } ts := testStruct{} @@ -174,9 +174,9 @@ func TestDefaultValue(t *testing.T) { } type testStruct struct { - BoolVal bool `keda:"name=boolVal, parsingOrder=triggerMetadata, optional, default=true"` - StringVal string `keda:"name=stringVal, parsingOrder=triggerMetadata, optional, default=d"` - StringVal2 string `keda:"name=stringVal2, parsingOrder=triggerMetadata, optional, default=d"` + BoolVal bool `keda:"name=boolVal, order=triggerMetadata, optional, default=true"` + StringVal string `keda:"name=stringVal, order=triggerMetadata, optional, default=d"` + StringVal2 string `keda:"name=stringVal2, order=triggerMetadata, optional, default=d"` } ts := testStruct{} @@ -199,7 +199,7 @@ func TestMap(t *testing.T) { } type testStruct struct { - MapVal map[string]int `keda:"name=mapVal, parsingOrder=triggerMetadata"` + MapVal map[string]int `keda:"name=mapVal, order=triggerMetadata"` } ts := testStruct{} @@ -222,8 +222,8 @@ func TestSlice(t *testing.T) { } type testStruct struct { - SliceVal []int `keda:"name=sliceVal, parsingOrder=triggerMetadata"` - SliceValWithSpaces []int `keda:"name=sliceValWithSpaces, parsingOrder=triggerMetadata"` + SliceVal []int `keda:"name=sliceVal, order=triggerMetadata"` + SliceValWithSpaces []int `keda:"name=sliceValWithSpaces, order=triggerMetadata"` } ts := testStruct{} @@ -251,8 +251,8 @@ func TestEnum(t *testing.T) { } type testStruct struct { - EnumVal string `keda:"name=enumVal, parsingOrder=triggerMetadata, enum=value1;value2"` - EnumSlice []string `keda:"name=enumSlice, parsingOrder=triggerMetadata, enum=value1;value2, optional"` + EnumVal string `keda:"name=enumVal, order=triggerMetadata, enum=value1;value2"` + EnumSlice []string `keda:"name=enumSlice, order=triggerMetadata, enum=value1;value2, optional"` } ts := testStruct{} @@ -273,12 +273,12 @@ func TestEnum(t *testing.T) { Expect(err).To(MatchError(`parameter "enumVal" value "value3" must be one of [value1 value2]`)) } -// TestExclusive tests the exclusive type +// TestExclusive tests the exclusiveSet type func TestExclusive(t *testing.T) { RegisterTestingT(t) type testStruct struct { - IntVal []int `keda:"name=intVal, parsingOrder=triggerMetadata, exclusive=1;4;5"` + IntVal []int `keda:"name=intVal, order=triggerMetadata, exclusiveSet=1;4;5"` } sc := &ScalerConfig{ @@ -313,7 +313,7 @@ func TestURLValues(t *testing.T) { } type testStruct struct { - EndpointParams url.Values `keda:"name=endpointParams, parsingOrder=authParams"` + EndpointParams url.Values `keda:"name=endpointParams, order=authParams"` } ts := testStruct{} @@ -338,7 +338,7 @@ func TestGenericMap(t *testing.T) { // structurally similar to url.Values but should behave as generic map type testStruct struct { - EndpointParams map[string][]string `keda:"name=endpointParams, parsingOrder=authParams"` + EndpointParams map[string][]string `keda:"name=endpointParams, order=authParams"` } ts := testStruct{} @@ -365,8 +365,8 @@ func TestNestedStruct(t *testing.T) { } type basicAuth struct { - Username string `keda:"name=username, parsingOrder=authParams"` - Password string `keda:"name=password, parsingOrder=authParams"` + Username string `keda:"name=username, order=authParams"` + Password string `keda:"name=password, order=authParams"` } type testStruct struct { @@ -393,8 +393,8 @@ func TestEmbeddedStruct(t *testing.T) { type testStruct struct { BasicAuth struct { - Username string `keda:"name=username, parsingOrder=authParams"` - Password string `keda:"name=password, parsingOrder=authParams"` + Username string `keda:"name=username, order=authParams"` + Password string `keda:"name=password, order=authParams"` } `keda:""` } @@ -436,9 +436,9 @@ func TestNestedOptional(t *testing.T) { } type basicAuth struct { - Username string `keda:"name=username, parsingOrder=authParams"` - Password string `keda:"name=password, parsingOrder=authParams, optional"` - AlsoOptionalThanksToParent string `keda:"name=optional, parsingOrder=authParams"` + Username string `keda:"name=username, order=authParams"` + Password string `keda:"name=password, order=authParams, optional"` + AlsoOptionalThanksToParent string `keda:"name=optional, order=authParams"` } type testStruct struct { @@ -465,8 +465,8 @@ func TestNestedPointer(t *testing.T) { } type basicAuth struct { - Username string `keda:"name=username, parsingOrder=authParams"` - Password string `keda:"name=password, parsingOrder=authParams"` + Username string `keda:"name=username, order=authParams"` + Password string `keda:"name=password, order=authParams"` } type testStruct struct { @@ -480,3 +480,38 @@ func TestNestedPointer(t *testing.T) { Expect(ts.BA.Username).To(Equal("user")) Expect(ts.BA.Password).To(Equal("pass")) } + +// TestNoParsingOrder tests when no parsing order is provided +func TestNoParsingOrder(t *testing.T) { + RegisterTestingT(t) + + sc := &ScalerConfig{ + TriggerMetadata: map[string]string{ + "strVal": "value1", + "defaultVal": "value2", + }, + } + + type testStructMissing struct { + StrVal string `keda:"name=strVal, enum=value1;value2"` + } + tsm := testStructMissing{} + err := sc.TypedConfig(&tsm) + Expect(err).To(MatchError(`missing required parameter "strVal", no 'order' tag, provide any from [triggerMetadata resolvedEnv authParams]`)) + + type testStructDefault struct { + DefaultVal string `keda:"name=defaultVal, default=dv"` + } + tsd := testStructDefault{} + err = sc.TypedConfig(&tsd) + Expect(err).To(BeNil()) + Expect(tsd.DefaultVal).To(Equal("dv")) + + type testStructDefaultMissing struct { + DefaultVal2 string `keda:"name=defaultVal2, default=dv"` + } + tsdm := testStructDefaultMissing{} + err = sc.TypedConfig(&tsdm) + Expect(err).To(BeNil()) + Expect(tsdm.DefaultVal2).To(Equal("dv")) +}