From 040d5edf582b7991458fd16ae768f2159bcc388f Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Fri, 9 Oct 2020 15:33:33 +0200 Subject: [PATCH] KEP-35 Impl Part1: New parameter types and attributes (#1705) * Added additional types (integer, number, boolean) * Added enum type * Added verification of parameter types in admission webhook * Added descriptive parameters on package level Signed-off-by: Andreas Neumann --- config/crds/kudo.dev_operatorversions.yaml | 5 + .../kudo/v1beta1/operatorversion_types.go | 11 +- pkg/apis/kudo/v1beta1/parameter_types.go | 3 + .../kudo/v1beta1/parameter_types_helpers.go | 171 ++++++++++++++++++ .../v1beta1/parameter_types_helpers_test.go | 89 +++++++++ .../kudo/v1beta1/zz_generated.deepcopy.go | 9 + .../cmd/testdata/deploy-kudo-ns.yaml.golden | 5 + .../cmd/testdata/deploy-kudo-sa.yaml.golden | 5 + .../cmd/testdata/deploy-kudo.json.golden | 7 + .../cmd/testdata/deploy-kudo.yaml.golden | 5 + pkg/kudoctl/kudoinit/crd/bindata.go | 2 +- pkg/kudoctl/packages/convert/parameters.go | 14 ++ pkg/kudoctl/packages/types.go | 50 +++++ .../verifier/template/verify_parameters.go | 86 ++++++++- .../template/verify_parameters_test.go | 59 ++++++ pkg/kudoctl/verifier/result.go | 4 + pkg/webhook/instance_admission.go | 20 ++ .../operator/params.yaml | 10 + .../operator/templates/deployment.yaml | 3 +- 19 files changed, 551 insertions(+), 7 deletions(-) diff --git a/config/crds/kudo.dev_operatorversions.yaml b/config/crds/kudo.dev_operatorversions.yaml index 6b00c32b6..a7327fb5e 100644 --- a/config/crds/kudo.dev_operatorversions.yaml +++ b/config/crds/kudo.dev_operatorversions.yaml @@ -73,6 +73,11 @@ spec: displayName: description: DisplayName can be used by UIs. type: string + enum: + description: Defines a list of allowed values. If Default is set and Enum is not nil, the value must be in this list as well + items: + type: string + type: array immutable: description: Specifies if the parameter can be changed after the initial installation of the operator type: boolean diff --git a/pkg/apis/kudo/v1beta1/operatorversion_types.go b/pkg/apis/kudo/v1beta1/operatorversion_types.go index e093e3dfe..29e2d9772 100644 --- a/pkg/apis/kudo/v1beta1/operatorversion_types.go +++ b/pkg/apis/kudo/v1beta1/operatorversion_types.go @@ -75,7 +75,16 @@ const ( // StringValueType is used for parameter values that are provided as a string. StringValueType ParameterType = "string" - // ArrayValueType is used for parameter values that described an array of values. + // IntegerValueType is used for parameter values that describe an integral number without any fractional part + IntegerValueType ParameterType = "integer" + + // NumberValueType is used for parameter values that describe any numeric value, with or without a fractional part + NumberValueType ParameterType = "number" + + // BooleanValueType is used for parameter values that are "true" or "false" + BooleanValueType ParameterType = "boolean" + + // ArrayValueType is used for parameter values that describe an array of values. ArrayValueType ParameterType = "array" // MapValueType is used for parameter values that describe a mapping type. diff --git a/pkg/apis/kudo/v1beta1/parameter_types.go b/pkg/apis/kudo/v1beta1/parameter_types.go index 478d9dd1a..b1de4ddbc 100644 --- a/pkg/apis/kudo/v1beta1/parameter_types.go +++ b/pkg/apis/kudo/v1beta1/parameter_types.go @@ -30,4 +30,7 @@ type Parameter struct { // Specifies if the parameter can be changed after the initial installation of the operator Immutable *bool `json:"immutable,omitempty"` + + // Defines a list of allowed values. If Default is set and Enum is not nil, the value must be in this list as well + Enum *[]string `json:"enum,omitempty"` } diff --git a/pkg/apis/kudo/v1beta1/parameter_types_helpers.go b/pkg/apis/kudo/v1beta1/parameter_types_helpers.go index 5ae5ca527..79a45e876 100644 --- a/pkg/apis/kudo/v1beta1/parameter_types_helpers.go +++ b/pkg/apis/kudo/v1beta1/parameter_types_helpers.go @@ -1,5 +1,13 @@ package v1beta1 +import ( + "fmt" + "reflect" + "strconv" + + "sigs.k8s.io/yaml" +) + func (p *Parameter) IsImmutable() bool { return p.Immutable != nil && *p.Immutable } @@ -8,10 +16,173 @@ func (p *Parameter) IsRequired() bool { return p.Required != nil && *p.Required } +func (p *Parameter) IsEnum() bool { + return p.Enum != nil +} + func (p *Parameter) HasDefault() bool { return p.Default != nil } +func (p *Parameter) EnumValues() []string { + if p.IsEnum() { + return *p.Enum + } + return []string{} +} + +func (p *Parameter) ValidateDefault() error { + if err := ValidateParameterValueForType(p.Type, p.Default); err != nil { + return fmt.Errorf("parameter %q has an invalid default value: %v", p.Name, err) + } + if p.IsEnum() { + if err := ValidateParameterValueForEnum(p.EnumValues(), p.Default); err != nil { + return fmt.Errorf("parameter %q has an invalid default value: %v", p.Name, err) + } + } + return nil +} + +// ValidateValue ensures that a the given value is valid for this parameter +func (p *Parameter) ValidateValue(pValue string) error { + if p.IsRequired() && !p.HasDefault() && pValue == "" { + return fmt.Errorf("parameter %q is required but has no value set", p.Name) + } + + if pValue == "" { + return nil + } + + if err := ValidateParameterValueForType(p.Type, pValue); err != nil { + return fmt.Errorf("parameter %q has an invalid value %q: %v", p.Name, pValue, err) + } + if p.IsEnum() { + if err := ValidateParameterValueForEnum(p.EnumValues(), pValue); err != nil { + return fmt.Errorf("parameter %q has an invalid value %q: %v", p.Name, pValue, err) + } + } + return nil +} + +func ValidateParameterValueForType(pType ParameterType, pValue interface{}) error { + switch pType { + case StringValueType: + _, ok := pValue.(string) + if !ok { + return fmt.Errorf("type is %q but format is invalid: %s", pType, pValue) + } + case IntegerValueType: + return validateIntegerType(pValue) + case NumberValueType: + return validateNumberType(pValue) + case BooleanValueType: + return validateBooleanType(pValue) + case ArrayValueType: + return validateArrayType(pValue) + case MapValueType: + return validateMapType(pValue) + } + return nil +} + +func validateIntegerType(pValue interface{}) error { + switch v := pValue.(type) { + case int, int8, int16, int32, int64: + return nil + case uint, uint8, uint16, uint32, uint64: + return nil + case string: + _, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return fmt.Errorf("type is %q but format of %q is invalid: %v", IntegerValueType, pValue, err) + } + default: + return fmt.Errorf("type is %q but format of %s is invalid", IntegerValueType, pValue) + } + return nil +} + +func validateNumberType(pValue interface{}) error { + switch v := pValue.(type) { + case int, int8, int16, int32, int64: + return nil + case uint, uint8, uint16, uint32, uint64: + return nil + case float32, float64: + return nil + case string: + _, err := strconv.ParseFloat(v, 64) + if err != nil { + return fmt.Errorf("type is %q but format of %q is invalid: %v", NumberValueType, pValue, err) + } + default: + return fmt.Errorf("type is %q but format of %s is invalid", NumberValueType, v) + } + return nil +} + +func validateBooleanType(pValue interface{}) error { + switch v := pValue.(type) { + case bool: + return nil + case string: + _, err := strconv.ParseBool(v) + if err != nil { + return fmt.Errorf("type is %q but format of %q is invalid: %v", BooleanValueType, pValue, err) + } + default: + return fmt.Errorf("type is %q but format of %s is invalid", BooleanValueType, pValue) + } + return nil +} + +func validateArrayType(pValue interface{}) error { + t := reflect.TypeOf(pValue) + switch t.Kind() { + case reflect.Slice, reflect.Array: + return nil + case reflect.String: + str, _ := pValue.(string) // We know here this is a string + + var result []interface{} + if err := yaml.Unmarshal([]byte(str), &result); err != nil { + return fmt.Errorf("type is %q, but format of %s is invalid", ArrayValueType, pValue) + } + + return nil + default: + return fmt.Errorf("type is %q but value %s is not an array", ArrayValueType, pValue) + } +} + +func validateMapType(pValue interface{}) error { + t := reflect.TypeOf(pValue) + switch t.Kind() { + case reflect.Map, reflect.Struct: + return nil + case reflect.String: + str, _ := pValue.(string) // We know here this is a string + + var result map[string]interface{} + if err := yaml.Unmarshal([]byte(str), &result); err != nil { + return fmt.Errorf("type is %q, but format of %s is invalid", MapValueType, pValue) + } + + return nil + default: + return fmt.Errorf("type is %q but value %s is not a map", MapValueType, pValue) + } +} + +func ValidateParameterValueForEnum(enumValues []string, pValue interface{}) error { + for _, eValue := range enumValues { + if pValue == eValue { + return nil + } + } + return fmt.Errorf("value is %q, but only allowed values are %v", pValue, enumValues) +} + // GetChangedParamDefs returns a list of parameters from ov2 that changed based on the given compare function between ov1 and ov2 func GetChangedParamDefs(p1, p2 []Parameter, isEqual func(p1, p2 Parameter) bool) []Parameter { changedParams := []Parameter{} diff --git a/pkg/apis/kudo/v1beta1/parameter_types_helpers_test.go b/pkg/apis/kudo/v1beta1/parameter_types_helpers_test.go index 1303d6142..8a79e2c04 100644 --- a/pkg/apis/kudo/v1beta1/parameter_types_helpers_test.go +++ b/pkg/apis/kudo/v1beta1/parameter_types_helpers_test.go @@ -137,3 +137,92 @@ func TestGetAddedRemovedParamDefs(t *testing.T) { }) } } + +func TestValidateType(t *testing.T) { + + tests := []struct { + name string + pValue interface{} + pType ParameterType + expectedErr bool + }{ + { + name: "simple int", + pValue: 23, + pType: IntegerValueType, + }, + { + name: "int8", + pValue: int8(23), + pType: IntegerValueType, + }, + { + name: "int64", + pValue: int64(23), + pType: IntegerValueType, + }, + { + name: "intAsString", + pValue: "42", + pType: IntegerValueType, + }, + { + name: "float32", + pValue: float32(3.14), + pType: NumberValueType, + }, + { + name: "float64", + pValue: float64(3.1415), + pType: NumberValueType, + }, + { + name: "floatAsString", + pValue: "3.1415", + pType: NumberValueType, + }, + { + name: "bool", + pValue: true, + pType: BooleanValueType, + }, + { + name: "boolAsString", + pValue: "true", + pType: BooleanValueType, + }, + { + name: "array", + pValue: []string{"oneString", "twoString"}, + pType: ArrayValueType, + }, + { + name: "arrayAsString", + pValue: `[ "oneString", "twoString" ]`, + pType: ArrayValueType, + }, + { + name: "map", + pValue: map[string]string{"oneString": "oneValue", "twoString": "twoValue"}, + pType: MapValueType, + }, + { + name: "mapAsString", + pValue: `{ "oneString": "oneValue", "twoString": "twoValue" }`, + pType: MapValueType, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + err := ValidateParameterValueForType(tt.pType, tt.pValue) + + if tt.expectedErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/pkg/apis/kudo/v1beta1/zz_generated.deepcopy.go b/pkg/apis/kudo/v1beta1/zz_generated.deepcopy.go index aa95b991a..0e2b9b921 100644 --- a/pkg/apis/kudo/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/kudo/v1beta1/zz_generated.deepcopy.go @@ -437,6 +437,15 @@ func (in *Parameter) DeepCopyInto(out *Parameter) { *out = new(bool) **out = **in } + if in.Enum != nil { + in, out := &in.Enum, &out.Enum + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } return } diff --git a/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden index 2f6d4c047..b9282d20b 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden @@ -142,6 +142,11 @@ spec: displayName: description: DisplayName can be used by UIs. type: string + enum: + description: Defines a list of allowed values. If Default is set and Enum is not nil, the value must be in this list as well + items: + type: string + type: array immutable: description: Specifies if the parameter can be changed after the initial installation of the operator type: boolean diff --git a/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden index 16fb4d6b9..3841e6775 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden @@ -142,6 +142,11 @@ spec: displayName: description: DisplayName can be used by UIs. type: string + enum: + description: Defines a list of allowed values. If Default is set and Enum is not nil, the value must be in this list as well + items: + type: string + type: array immutable: description: Specifies if the parameter can be changed after the initial installation of the operator type: boolean diff --git a/pkg/kudoctl/cmd/testdata/deploy-kudo.json.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo.json.golden index 945674242..5fbf6eeaa 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo.json.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo.json.golden @@ -196,6 +196,13 @@ "description": "DisplayName can be used by UIs.", "type": "string" }, + "enum": { + "description": "Defines a list of allowed values. If Default is set and Enum is not nil, the value must be in this list as well", + "type": "array", + "items": { + "type": "string" + } + }, "immutable": { "description": "Specifies if the parameter can be changed after the initial installation of the operator", "type": "boolean" diff --git a/pkg/kudoctl/cmd/testdata/deploy-kudo.yaml.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo.yaml.golden index 33bf9809f..5364ad650 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo.yaml.golden @@ -142,6 +142,11 @@ spec: displayName: description: DisplayName can be used by UIs. type: string + enum: + description: Defines a list of allowed values. If Default is set and Enum is not nil, the value must be in this list as well + items: + type: string + type: array immutable: description: Specifies if the parameter can be changed after the initial installation of the operator type: boolean diff --git a/pkg/kudoctl/kudoinit/crd/bindata.go b/pkg/kudoctl/kudoinit/crd/bindata.go index 1e8f45d78..5e5f64f8a 100644 --- a/pkg/kudoctl/kudoinit/crd/bindata.go +++ b/pkg/kudoctl/kudoinit/crd/bindata.go @@ -120,7 +120,7 @@ func configCrdsKudoDev_operatorsYaml() (*asset, error) { return a, nil } -var _configCrdsKudoDev_operatorversionsYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x3b\x6b\x8f\x1b\x37\x92\xdf\xe7\x57\x14\x26\x1f\x9c\x04\xa3\xd6\x3a\xd9\xc5\x1d\xe6\x5b\xd6\xde\x1c\xe6\xe2\xb5\x0d\x3f\xf6\x70\xc8\x05\x18\xaa\x59\x52\x73\x87\x4d\xf2\xf8\x90\xac\x0b\xf2\xdf\x0f\x55\x64\x3f\xa4\x51\x4b\x9a\x71\x36\x97\xc3\x4e\x7f\xb1\xa7\x9b\x64\xbd\x9f\x2c\x5d\xcc\x66\xb3\x0b\xe1\xd4\xdf\xd0\x07\x65\xcd\x35\x08\xa7\xf0\x53\x44\x43\x7f\x85\xea\xee\x5f\x43\xa5\xec\x7c\xfd\x7c\x81\x51\x3c\xbf\xb8\x53\x46\x5e\xc3\x8b\x14\xa2\x6d\xdf\x61\xb0\xc9\xd7\xf8\x12\x97\xca\xa8\xa8\xac\xb9\x68\x31\x0a\x29\xa2\xb8\xbe\x00\x10\xc6\xd8\x28\xe8\x75\xa0\x3f\x01\x6a\x6b\xa2\xb7\x5a\xa3\x9f\xad\xd0\x54\x77\x69\x81\x8b\xa4\xb4\x44\xcf\x10\x3a\xf8\xeb\x3f\x54\x7f\xac\xfe\x70\x01\x50\x7b\xe4\xed\x1f\x54\x8b\x21\x8a\xd6\x5d\x83\x49\x5a\x5f\x00\x18\xd1\xe2\x35\x58\x87\x5e\x44\xeb\xcb\xce\x50\xdd\x25\x69\x2b\x89\xeb\x8b\xe0\xb0\x26\x98\x2b\x6f\x93\xbb\x86\xfe\x7d\xde\x59\xd0\xc9\xa4\xbc\x29\x87\x14\xf2\xf9\x8b\x56\x21\xfe\x70\xe8\xeb\x2b\x15\x22\xaf\x70\x3a\x79\xa1\xef\xa3\xc0\x1f\x83\x32\xab\xa4\x85\xbf\xf7\xf9\x02\x20\xd4\xd6\xe1\x35\xbc\x26\x34\x9c\xa8\x51\x5e\x00\xac\x85\x56\x92\x29\xcd\x88\x59\x87\xe6\xbb\xb7\x37\x7f\xfb\xf6\x7d\xdd\x60\x2b\xf2\x4b\x00\x89\xa1\xf6\xca\xf1\xba\x7d\xc4\x40\x05\x88\x0d\x42\xde\x01\x4b\xeb\xf9\xcf\x7d\xf4\xe0\xbb\xb7\x37\x55\x39\xce\x79\xfa\x1a\x55\xc7\x0e\x7a\x46\x6a\xd0\xbf\xdb\x03\xfc\x8c\x30\x2b\x40\x25\x09\x1e\x33\xe4\x02\x02\x25\x84\x8c\x83\x5d\x42\x6c\x54\x00\x8f\xce\x63\x40\x93\x55\x81\x5e\x0b\x03\x76\xf1\x77\xac\x63\x05\xef\x91\x71\x83\xd0\xd8\xa4\x25\x69\xc8\x1a\x7d\x04\x8f\xb5\x5d\x19\xf5\x3f\xfd\x69\x01\xa2\x65\x30\x5a\x44\x0c\x11\x94\x89\xe8\x8d\xd0\xc4\xbb\x84\x57\x20\x8c\x84\x56\x6c\xc1\x23\x9d\x0b\xc9\x8c\x4e\xe0\x25\xa1\x82\xbf\x5a\x8f\xa0\xcc\xd2\x5e\x43\x13\xa3\x0b\xd7\xf3\xf9\x4a\xc5\x4e\xc1\x6b\xdb\xb6\xc9\xa8\xb8\x9d\xb3\x9a\xaa\x45\x8a\xd6\x87\xb9\xc4\x35\xea\x79\x50\xab\x99\xf0\x75\xa3\x22\xd6\x31\x79\x9c\x0b\xa7\x66\x8c\xac\x61\xfd\xae\x5a\xf9\x85\x2f\xd6\x10\x9e\x8d\x58\x17\xb7\x24\xed\x10\xbd\x32\xab\xfe\x35\x2b\xde\x24\x7f\x49\xf1\x48\x9c\xa2\x6c\xcb\xf8\x0f\x6c\xa4\x57\xc4\x89\x77\x7f\x79\xff\x01\x3a\xa0\x99\xd5\x99\xab\xc3\xd2\x30\x30\x98\x98\xa3\xcc\x12\x7d\x5e\xb9\xf4\xb6\xe5\x53\xd0\x48\x67\x95\x89\xfc\x47\xad\x15\x9a\x08\x21\x2d\x5a\x15\x49\x72\xff\x9d\x30\x44\xe2\x7d\x05\x2f\xd8\x9c\x61\x81\x90\x9c\x14\x11\x65\x05\x37\x06\x5e\x88\x16\xf5\x0b\x11\xf0\x1f\xce\x5e\xe2\x64\x98\x11\xeb\x4e\x33\x78\xec\x85\x76\x17\x66\x0e\xf5\xaf\x3b\x3f\x71\x50\x12\x7b\x26\xf6\xde\x61\xbd\xa3\xf1\x12\x83\xf2\xa4\xa1\x51\x44\x24\xbd\xde\xdb\x50\x8d\x0e\x3e\x64\x6c\xd9\xe0\xdc\x01\x83\x9b\x24\xac\x78\x51\x83\x35\xa1\xf8\x9e\x3f\xee\x6f\xdc\xa1\xe1\xc5\xde\xe2\x9e\x00\x01\x11\x5b\x47\xd6\x24\x3b\x3d\x8b\x8d\x88\x50\x0b\xc3\x32\x0e\x28\xc9\xe4\x0a\x30\xfa\xaf\x30\xa0\x4c\x88\xc2\xd4\x98\x6d\x1b\x7b\x82\xab\x73\x71\xef\xfc\xd1\x51\x9c\x9f\xbd\x61\x21\xbd\xc3\x25\x7a\x24\x68\xa4\x31\x42\x99\x00\x68\x6c\x5a\x35\xac\x64\xbe\xcd\xee\x24\x5a\xd0\x18\x61\x6b\x13\x61\xe7\x08\x57\xeb\xa1\xb5\x52\x2d\xb7\x8c\xa3\xa7\x63\x48\x4c\x9d\xcb\x99\xcd\x66\xf0\x1a\x37\x44\x62\xe8\x9d\x14\x61\x0c\xc2\x23\x48\x15\x6a\x9b\xbc\x58\xa1\x84\x05\xd6\x22\x05\xa6\x56\xaa\xe5\x52\xd5\x49\xc7\x6d\xc1\x75\x41\x1c\x23\x23\x49\x41\xac\x10\x36\x0d\x1a\xc0\x76\x81\x52\xa2\x04\x65\xc8\xd1\x86\x0a\xe0\x79\x05\x37\x2b\x63\x09\xfe\x52\xa1\x96\xf4\xee\x86\xdc\x57\xad\x93\x44\x32\x4b\xb3\x2d\x5f\x60\xd3\xa8\xba\x61\x24\xc8\xd0\x56\x68\xd0\x0b\xad\xb7\xd0\x58\x3e\xa0\x02\xf8\xde\xfa\x5e\x06\x57\xd0\x85\xdf\xce\x1b\x93\x0f\xfc\x9e\x8e\x7a\x2b\x62\x3e\x67\x61\x63\x43\x8e\x79\x0b\x5e\x78\xd4\xdb\x1c\x69\x08\x3d\x51\xc7\x24\x74\x46\xbe\x02\xf8\x86\x8c\x39\x7f\xcc\xf4\x34\xa8\x5d\x41\x35\x80\x6a\x9d\x0d\x41\x2d\x34\xb2\x1e\x48\xc9\x76\xa3\x96\xaa\xe6\x75\x1c\x6d\x94\x91\x6a\xad\xe4\xf8\xd0\x1b\x03\xad\x0d\x71\x60\x0b\x7f\x08\x57\x24\x16\x9f\xb9\xed\x84\x8f\xc4\x56\xe1\x59\x0d\x3c\x92\xce\xb0\xba\x06\xd0\xea\x0e\xaf\xe0\xb2\x4d\x21\x66\x21\x82\x35\x7a\xcb\x71\x80\x5c\x01\x7c\xc7\x04\xff\xf9\x92\xe4\x7d\xf9\xf1\xe6\x25\x73\xad\xf0\x2a\xbf\xa4\x58\x0f\xbc\x7f\x81\xfd\xd9\x28\x2f\x2b\x06\xf6\xa1\xb1\x01\x49\xdf\x8b\x5b\xdb\xa0\xd6\x9d\x70\x51\xee\x4a\xb4\x02\xf8\x96\x58\x54\x5b\x13\x54\x88\xe4\x24\x87\xa0\x5d\x01\xfc\xb9\x68\x0a\x29\x5c\xa6\xb2\x28\xd3\x92\x75\x38\x5e\xe5\x10\xd9\x6f\x01\x9f\xf4\xfe\x1a\x58\x6c\xf3\xde\xab\xa2\x09\xad\xb8\xc3\x00\x2a\x42\x23\xbc\x64\x26\xa7\x40\xae\x3c\x5a\x70\x1e\xa5\xaa\x23\x6c\xc8\x64\x37\x4a\x6b\x68\x84\x73\x48\xa8\xfc\xb1\x82\x0f\x0d\x76\x3a\xd5\x6b\x81\x6a\x9d\xc7\x5a\x05\x64\xae\xd9\x35\x7a\xbd\x85\xf2\xaa\x02\xe8\x82\x0e\xf1\x42\x74\xef\xa1\x15\xce\xb1\x67\xb0\x20\xe0\xe3\xbb\x57\x74\xb4\x0a\xec\x23\x9c\xb7\x32\xd5\x08\xa2\x5d\xa8\x55\x52\x71\x9b\xed\x38\xb1\x27\xe1\xe8\xec\x3c\x96\x90\x4f\x10\x29\x96\x28\x92\x7a\x8e\x5b\xe5\xe4\x91\x96\xd4\x22\x14\xdd\x00\x89\x0e\x8d\x44\x53\x6f\x09\x25\x32\xf2\x06\x73\x2a\x77\x35\xc4\xbb\xe4\x34\x66\x17\x6a\xe4\x38\x01\xe9\x7c\x53\xd1\xf0\x10\x7d\xaa\xb3\x16\x7b\x8f\x1a\xd7\xc2\xc4\x0a\xe0\x4f\x15\xfc\x47\x2f\x7c\x14\x41\xe9\x2d\xd4\x8d\x30\x2b\x04\x15\x77\x04\xda\x39\x07\x15\x76\xec\x9b\x0d\x57\xdb\x3a\xe7\xb7\x57\x25\x28\x96\x34\xa5\xdb\x43\x0f\x4b\x47\x2c\x97\xe4\x99\x4c\x6a\xd1\xdb\x14\xba\xa4\xa6\x02\x78\x69\xcd\xb3\x67\x91\x65\x0d\x06\x37\xec\x37\x32\x20\x72\xb8\xc9\x48\xf4\xc5\xd8\x50\xd2\xc7\x7c\x70\x6c\x70\x0b\xd2\xb2\xb8\x4a\x56\x4d\xea\x19\x22\x0a\x49\x0c\x48\x21\x3b\xf4\x82\xc8\x55\x4e\xa5\x89\xfb\x84\xb2\x66\xd1\xdb\xb5\x92\x0c\x45\x16\x6f\x9f\x0f\x16\xcc\x2c\x32\x86\xd9\xd2\xd6\xfc\xc5\x1a\xf2\xaf\x3e\x5b\x21\x79\xe4\x8a\x3d\x11\x7e\x12\xad\xd3\x78\xc5\x39\x86\xaa\xb1\x77\xd8\x81\x95\x55\xc8\x56\x05\x96\x88\xc7\x95\x0a\xd1\xe7\x0c\x77\x9c\x1c\x34\x69\x51\xd5\xb6\x9d\x53\x25\xe0\x0d\x46\x0c\x14\xf9\xe7\x0b\x6d\x17\x73\x12\x96\x08\x38\x7b\x5e\x3d\xff\x97\x79\x7f\xd6\xf8\xa8\xf9\xfa\xf9\x9c\x5d\x41\xb5\xb2\x5f\xbc\xfa\xd3\xb7\xdf\x42\xf5\x6c\x2f\xae\x4c\x85\x5d\x98\xcc\x75\x0f\x46\x24\xe2\xfb\x9e\x7a\x15\x5e\xc4\xfd\xd0\x77\x34\xfc\xd1\xb3\xec\x3c\xf4\x49\xa8\xcf\x6e\x96\x25\x7a\xf5\x36\xe8\x14\xe6\xe8\xdb\xa7\xd0\x1c\x0f\x8a\xd4\x85\x01\x4a\x98\x3c\x96\x6f\x57\x59\x03\x4a\x78\x1f\x52\x6c\x0a\xa6\x20\x4a\x30\xf8\xf7\xf7\x6f\x5e\xcf\xff\xcd\x66\xbc\x40\xd4\x35\x86\x90\x53\x9a\x96\x1d\x57\x48\x14\x94\x42\x97\xed\xbc\xa7\x2f\x55\x2b\x8c\x5a\x62\x88\x55\x39\x0d\x7d\xf8\xf1\x9b\x9f\xf6\xd4\x42\x65\x4e\xf5\x49\x69\x17\xce\x55\xc8\xc4\xf4\x7b\x61\xa3\x62\xc3\x28\x39\x2b\x0b\xd2\x1b\x46\x36\x92\x59\xd8\x82\x6c\x42\x8e\x09\xd7\x70\x49\x16\x31\x02\xfd\x33\x39\xfa\x5f\x2e\xe1\xcb\x0d\x07\x16\xf6\xfb\x97\x19\x60\x5f\x37\x70\x2c\x28\xb2\x1b\x00\xb3\xba\x47\xaf\x56\x2b\xa4\x10\xcd\x09\x31\x25\x9d\x5f\x51\xfc\x50\x4b\x30\x76\xb4\x98\x8f\x20\x7e\xf6\xf6\xb8\x8f\xc8\x8f\xdf\xfc\x74\x09\x5f\xee\xd2\x45\x91\x11\x3f\xc1\x37\xe4\x34\x98\x32\x67\xe5\x57\xc5\x91\x86\xad\x89\xe2\x13\x9d\x59\x53\x30\x32\x7d\x84\x6b\xc4\x1a\x21\xd8\x36\x47\xa5\x59\x4e\xda\x24\x6c\xc4\x96\x68\xe8\x58\x49\x52\x15\x1c\x43\xf7\xaa\xaa\x0f\x6f\x5e\xbe\xb9\xce\xd0\x48\x6c\x2b\xd3\xb9\xf6\xa5\xa2\x9a\x29\x7b\x4c\xaa\x02\x58\xe6\x84\x48\xca\x42\xa2\x8c\xaf\x78\xc1\xec\x75\x97\x89\xf2\xf1\x7b\x76\x75\x52\xcb\xf7\x4b\x9c\xc3\x0a\xce\x51\x67\xdf\xa0\xfe\x8f\x0a\x89\xb3\xc8\xe2\xbe\xc3\x49\xb2\x5e\x8f\x74\xed\x28\x59\x83\xdf\x23\xca\xa4\xad\x03\x11\x55\xa3\x8b\x61\x4e\x21\x7a\xad\x70\x33\xdf\x58\x7f\xa7\xcc\x6a\x46\xca\x34\xcb\x12\x0e\x73\x6e\x63\xcc\xbf\xe0\x7f\x1e\x45\x05\x37\x1f\xce\x23\x85\x97\xfe\x16\xf4\x10\x9c\x30\x7f\x30\x39\x7e\x37\x0f\x3e\x4d\xd4\xfb\x2e\x7b\xdd\xdb\x49\xea\x9f\x53\xaf\xd2\xb8\x18\x79\xac\x56\xc8\xec\xd2\x84\xd9\xfe\xc3\x55\x94\x98\x96\x3c\xc1\xde\xce\x4a\x78\x9f\x09\x23\x67\x7d\xfa\x59\x6f\x1f\xcc\xa5\xa4\xce\x30\x48\x4a\xa3\x7f\x13\xc5\x4d\xea\xc1\xd6\x77\xb0\x80\xa7\xc7\x09\x2f\x5a\x8c\xe8\xef\x05\x79\x15\xb1\x3d\x10\xf9\x77\x68\x7e\xdb\xed\x86\x5a\x38\x12\x48\x69\x67\x09\xaf\xc4\x42\x69\xca\x6c\xb3\x73\xdd\xef\xb8\x2d\x30\xa7\xba\x54\x8e\x45\xc5\x85\x34\xc5\xb0\xa1\x4a\xbe\x9f\x1a\x1c\x4b\x47\x08\xad\xa5\x48\x3a\x1e\xfa\xb4\x87\xf3\xcb\xbc\x32\x77\x89\xca\xb6\x12\x21\x73\xd0\xea\x59\x42\x4b\xfa\x54\x6f\x91\x2b\xe2\x69\xfc\x4e\x48\x60\x1f\x8f\x73\x10\xed\xff\x18\xd8\x4b\x49\xa8\x59\xa1\x1f\x2f\x25\x1e\x37\x76\xc3\xf8\x0d\xc8\x73\xee\x5c\xba\x11\x8f\xc3\x56\x05\xa7\xc5\xf6\xf5\x84\xdb\xde\xc7\x76\x58\xbd\xd3\x07\x59\x6c\xe1\xe3\x4d\x78\x14\x02\xaa\x6d\x53\x14\x0b\x7d\x0e\xf8\xe2\x96\xa8\xe6\x5b\xee\x31\xa2\x60\x93\x63\xb3\x04\xb1\x8c\xe8\x8b\x2c\x55\x54\x42\x67\x99\x6a\xdd\xb7\x58\xc7\xcd\xdf\x23\x78\x2f\xac\xd5\x28\xcc\x81\x15\x53\x91\x6e\x0f\xe7\xcb\xd7\x25\x29\x22\x80\xe3\x3e\x52\xc9\x36\x3b\x16\x96\x74\xa2\xeb\x39\xc1\x52\x69\xe4\x2a\x61\x9c\x2d\xde\xe6\xb6\xfe\x8b\x37\x1f\x5f\x7f\xb8\xa5\xf5\xa6\x2f\x64\x3a\x83\xd4\x9c\x99\x08\xce\xc1\x4a\x36\xf8\x5f\x26\xb7\xf1\x38\x12\x38\xad\x6a\x11\xae\x01\x7e\xfe\x19\x2a\x36\xed\x50\xf1\x79\xf0\xcb\x2f\x97\x8f\x11\x60\xa9\x5b\x0f\xfa\xce\x3d\x5e\xbc\x2b\x4b\xfb\x04\xf1\x80\x20\x55\xe8\x4f\xa4\x68\xb3\xc0\x1d\xeb\x14\x5a\xf7\xd6\x19\xae\x28\x07\xdd\x34\x18\x1b\xf4\x23\x33\x27\x55\x08\x69\xb9\x54\xc7\x0d\x78\x5a\xb2\x25\xdd\x3d\x83\xa0\x0f\x79\x25\x28\x49\xb1\x89\x09\x62\x6a\xb4\x30\x59\xc8\x2b\x8c\x01\xf0\x13\xd6\x29\x76\x1d\x93\x9c\xe2\x0e\x8a\xcb\x1a\x1b\x3a\xf9\xdf\xf4\x0d\xc4\x92\xa9\x8e\x3c\xd9\x6d\x2e\xa1\x6f\x39\xc8\x66\x20\x9c\x3f\x33\x24\xce\xbd\xf1\x93\x0a\x91\xf8\x42\x2c\xd9\xa8\x40\xb5\xfa\xb3\x00\xb7\x12\x9d\xb6\xdb\xdb\x47\x19\x28\x7b\xcd\x19\x2f\x3a\x83\x21\x54\x22\x0f\xd2\x2d\x3d\x9d\x84\x0c\xa4\x27\x86\x6b\x8e\xdb\x0c\xf1\x31\x48\x4d\x06\xba\xee\x93\xf0\x5e\x6c\x77\x43\xa0\x16\xe6\x5e\x4c\x11\x52\xf2\xa5\x9c\xd0\x6f\x8f\xc4\x9d\xdd\x60\x48\x5c\x1f\x08\x14\x10\xd0\xab\xdc\x26\x7d\xdb\x88\xc0\x34\x93\x34\xb0\x57\xdf\xda\x92\xf9\xc6\x43\xfe\xf9\x78\xb4\x73\x7c\xde\x19\x4c\x2f\x80\x5b\xe1\x08\x21\xde\x96\xd5\x81\x8b\x48\xfe\xda\xa9\xd3\xc1\xc3\x26\x92\x80\x29\x48\x3b\xe4\x6b\x15\xb8\xb6\x0a\x11\x5d\xa1\xbd\xab\x9f\x7f\xe8\x53\xa0\x02\x7d\x22\x40\x9c\xe2\x43\x7e\xa6\xdd\x6d\x7e\x4e\x68\x71\x7e\x18\xcb\x63\xa7\xec\x86\x1b\xa6\xa9\xb0\x95\xb6\x8e\xb8\xda\xd1\x3d\xdc\x11\xdc\x27\x17\x42\xe4\xbe\xb6\x18\xae\xaa\xa6\x38\x00\xa7\xc4\x30\x81\xde\xe8\xb6\xa2\xef\x38\x07\x64\xcc\x72\x70\xe3\x46\x31\x8b\xc5\xd6\x75\xba\x77\x0f\xb1\xfb\x9c\x23\x87\xfc\x9c\x92\x46\x7e\xce\x92\x49\x59\x2a\xc2\xdd\x49\xa8\x67\xf1\xe8\xc1\xa0\xa7\x9d\xc6\xe1\x75\x07\xfd\xce\x43\x8f\xe3\x16\x1d\xae\xb6\x67\x2b\xe3\x1b\x2f\x31\x77\xa9\x7a\xeb\xeb\xd2\xc1\x90\x16\xcc\x97\xa1\x81\xa2\x85\x99\x67\x3f\x30\x64\x19\x3c\x57\x20\xc1\xa6\x09\x2f\x30\xc6\xfd\x28\xdf\x4e\x72\xc1\x24\xad\x39\xa3\x83\xe8\x13\x1e\x71\xef\x53\x2c\x3a\xce\x9c\xdf\x96\x2d\x8f\x0d\x44\xf7\xa2\xc6\xe0\xa0\x29\x84\x0c\x9e\x84\xfe\xdc\x07\x7d\x94\x83\x93\x40\x0f\xda\xd0\x0e\x1e\xaf\x8a\xd7\xa2\x34\x8a\x57\x83\x58\x0b\xa5\xbb\xac\x91\xb9\x74\xe4\x8e\x16\xce\xac\x18\x3f\x88\x70\x97\x4b\xaf\x95\xb6\x0b\xa1\xaf\xc0\x59\xbd\x6d\xad\x77\x8d\xaa\x41\x51\x3c\x6c\x77\xc6\x1d\xb4\x06\x97\x16\x5a\xd5\x7a\x3b\xc2\x88\x31\x7c\x60\xd0\x9c\x6a\xab\x9d\xd4\xec\x69\x8f\x76\x62\xe3\xfe\x0d\xf9\x11\x9e\xf0\x05\x39\x5f\x5d\x84\x4c\x73\x7f\x69\x48\x0c\xa3\x83\x42\xe9\x7b\x0a\xad\xed\x26\x40\xca\x8d\xe0\xb5\x55\x12\x36\x5e\xf1\x64\x43\xcd\x93\x45\x90\xcc\xbc\x15\x3e\x34\x42\x6b\x6e\xe2\xf2\x3d\x17\x2b\x35\xb7\x48\x9d\xf0\x01\xa1\x46\xcf\xa1\xb8\xdc\x75\xe5\x6b\x23\x3a\xa4\x5c\x19\x31\xdc\x1f\x94\x91\xf9\x4a\x4c\xda\x8d\x09\x4a\x62\x7f\xe9\x2b\x9c\xf3\x56\xd4\x0d\x28\xbe\x78\x12\xa3\xab\xca\x7c\xc5\x48\xb9\x36\xdf\x2a\x8a\x75\x7f\xa3\x56\x72\x52\x84\x40\x3a\xfe\xf7\x60\xb3\xb6\x07\x8a\x83\xaa\x43\x72\x81\xb5\x6d\xbb\xcb\x31\x9b\x42\x3f\x90\xd3\x65\xf1\x4c\x80\xe7\x4b\xa8\x56\xad\x9a\x08\x1e\xd7\x2a\xa8\xb8\x8f\xd8\xb8\x0b\xdb\x99\x35\x2f\xe9\x20\x18\x50\x21\xa4\x89\x52\xe0\x74\xc4\x9b\x9e\x3f\x98\x10\xf4\x28\x12\x0b\xe7\xfa\x5b\x91\x82\xa8\xa5\xba\x84\xea\x51\x8f\xce\x5e\x75\xd4\xf6\x4d\x78\xbe\xe8\xf3\x58\xa3\x99\x0e\x30\x67\xb8\x68\x69\xcd\x91\xf0\x7c\xaa\xfe\xa1\x67\x29\xa2\xd0\x9f\x77\x44\x57\xa7\x4d\xf7\x16\xce\xa4\xc6\xee\x7a\xa4\x87\xcb\xa1\x3b\xe0\xf1\xc2\x00\x6b\x0e\x07\xb2\x33\x49\x70\xa2\xbe\x13\xab\x23\x6c\xd8\x41\x1d\x15\x97\xb4\x84\x55\xb7\x93\x4d\xe8\x2a\x5f\x4e\xf6\xef\x96\x56\x4b\xf4\x54\x05\x0b\x03\x1f\xdf\xbd\xe2\x8b\xef\xf2\x2d\x0a\xbf\x10\x5a\x57\xdd\x8d\x73\xcf\x83\x71\x23\xe4\x8a\x07\xff\xea\xa8\x73\x23\xc9\x63\xb0\x7a\x8d\xa5\x22\xcf\xe7\x74\x97\xe1\x9e\xfc\xc2\xe8\x4e\xa4\xb7\xf2\xb2\x49\x0e\x10\x08\xd5\xe9\x1c\xe3\x2c\x6e\x95\xea\xf8\xb3\xd4\xa6\x3f\xe5\x7b\x35\xd5\x5d\xba\xc7\xf9\xf1\x25\xd8\x6e\x2b\xe6\x4b\xbe\xca\x1e\x1a\x35\xb7\xdd\xe7\x70\x5b\xe4\xf0\x55\x9e\x19\xeb\xfa\xe3\x08\x5f\x3b\xe1\xd1\xc4\xaf\x87\xa9\xa0\x3c\xb2\x12\x39\x5f\x1f\x5a\x00\x7c\x7e\x37\x4f\xe4\xac\x4b\x0c\x95\x4f\xa8\x1b\xa5\xe5\xd7\x7d\x57\xa0\xa2\x08\x52\xf5\x0d\xd9\xfb\xc1\xf1\x21\xec\x51\x53\x15\x3d\x9c\x93\x68\xef\xe6\x37\xca\x61\x99\xfe\xca\xd3\x21\x39\x0b\x13\x99\xb4\x8e\xea\xdc\xc1\xe1\xc5\x5d\x86\xd0\x55\x48\x3c\xff\x50\x2e\x13\xe4\x11\xa8\xe7\x56\x28\x68\xd6\xc7\xa5\x9e\x9f\xb3\xab\x84\xe5\xaf\x79\xd8\x1d\x1e\x4d\xfb\x1f\x76\xd6\x64\xbe\xf3\xe0\xc3\xce\xaa\x6e\xce\xc8\xed\xe1\xac\x12\xc8\xd9\x23\x78\x9f\x81\x71\x3f\x4c\xfa\x19\x3a\x7c\x16\x63\x7e\x35\x8a\x37\xc2\xc4\xbf\xf8\x93\x3e\xed\x58\x50\x3d\x2a\xa2\x47\x74\xc5\x7a\x1f\xf6\xb8\xce\xd8\x11\xfe\xed\xe6\xbe\x1d\x98\x5c\x14\x74\xbd\x13\x3f\x8c\xc3\x44\x0b\xff\xf9\xdd\x5f\x5f\x0d\x08\xc1\x9e\xbb\x1d\x3e\x94\xa0\x47\x2e\x83\x5e\x8c\x86\x6e\x64\x19\xda\xa5\xb2\xe1\xf0\xc0\xe5\x01\xe6\x24\xb7\xf2\x42\x92\x80\xbf\xf7\xb6\x3d\x5a\x3d\x7d\xdc\x59\xca\x64\xe4\x3c\x7e\xaf\x64\x0a\xc3\xa0\x68\x3e\x1d\xfb\xa1\xa7\x5f\xa1\xb8\x7a\x1a\xfe\x7c\x1a\xfe\x7c\x1a\xfe\x7c\x1a\xfe\x7c\x1a\xfe\x7c\x1a\xfe\xfc\xcc\xe1\xcf\x53\xf9\xf4\xf1\x01\xd0\xcf\x1b\x01\x3d\x99\x7d\x1d\x1d\x03\x7d\x1a\x04\x7d\x1a\x04\xfd\xff\x33\x08\x7a\x52\xd7\xa7\xab\xb8\xdf\xff\x38\xe8\x67\x74\xd6\x7f\x77\x43\xa1\x67\xd1\x32\x39\x18\xfa\x3b\x1d\x0d\x3d\x63\x82\xe7\xe4\x78\xe8\x3f\xcb\x80\xe8\x49\x5e\x4d\x0c\x89\xfe\xee\xc6\x44\x7f\xfd\x09\x9a\xf5\x83\x7e\x85\x79\xf8\xc7\xa4\x51\xc4\x14\xce\xfe\x39\x29\xaf\xde\xf9\x41\xa9\x5d\x04\xf4\xeb\x33\x7f\x51\x7a\x00\x85\xbd\x57\xc3\x0f\xea\xcb\x6f\xf7\xfb\x57\x8c\xe4\xac\xfc\x8a\x7e\xf8\x0a\x90\xe1\x8f\x7a\x41\x21\x5a\x2a\x87\xcb\x9b\x81\x42\x4a\x0f\x5c\x44\xf9\x7a\xff\xe7\xf4\x97\x79\xca\xae\xfb\x7d\x3c\xff\x59\x5b\x93\xfb\x2d\xe1\x1a\x7e\xfc\xe9\x02\x4a\x5f\xb4\xeb\x27\xf0\xcb\xff\x0d\x00\x00\xff\xff\x92\x02\x26\xc3\x86\x40\x00\x00") +var _configCrdsKudoDev_operatorversionsYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x3c\x6b\x8f\x1c\x37\x72\xdf\xf7\x57\x14\xd6\x1f\x64\x1b\x3b\x3d\x27\xfb\x0e\x09\xf6\x9b\x4f\xb2\x83\x8d\x75\x92\xa0\xc7\x05\x81\x63\x60\x39\xcd\xea\x69\xde\xb2\x49\x86\x8f\x19\x4d\x0c\xff\xf7\xa0\x8a\xec\xc7\xcc\xce\x6b\x57\xb6\xa3\xe0\xd4\x5f\xec\xe9\x26\xeb\xcd\x7a\xb1\xb4\x17\xb3\xd9\xec\x42\x38\xf5\x77\xf4\x41\x59\x73\x0d\xc2\x29\xfc\x10\xd1\xd0\xaf\x50\xdd\xfd\x6b\xa8\x94\x9d\xaf\x9e\x2e\x30\x8a\xa7\x17\x77\xca\xc8\x6b\x78\x96\x42\xb4\xdd\x1b\x0c\x36\xf9\x1a\x9f\x63\xa3\x8c\x8a\xca\x9a\x8b\x0e\xa3\x90\x22\x8a\xeb\x0b\x00\x61\x8c\x8d\x82\x5e\x07\xfa\x09\x50\x5b\x13\xbd\xd5\x1a\xfd\x6c\x89\xa6\xba\x4b\x0b\x5c\x24\xa5\x25\x7a\xc6\xd0\xe3\x5f\xfd\xa9\xfa\x73\xf5\xa7\x0b\x80\xda\x23\x6f\x7f\xa7\x3a\x0c\x51\x74\xee\x1a\x4c\xd2\xfa\x02\xc0\x88\x0e\xaf\xc1\x3a\xf4\x22\x5a\x5f\x76\x86\xea\x2e\x49\x5b\x49\x5c\x5d\x04\x87\x35\xe1\x5c\x7a\x9b\xdc\x35\x0c\xef\xf3\xce\x42\x4e\x66\xe5\x55\x01\x52\xd8\xe7\x2f\x5a\x85\xf8\xe3\xbe\xaf\x2f\x54\x88\xbc\xc2\xe9\xe4\x85\xbe\x4f\x02\x7f\x0c\xca\x2c\x93\x16\xfe\xde\xe7\x0b\x80\x50\x5b\x87\xd7\xf0\x92\xc8\x70\xa2\x46\x79\x01\xb0\x12\x5a\x49\xe6\x34\x13\x66\x1d\x9a\xef\x5e\xdf\xfc\xfd\xdb\xb7\x75\x8b\x9d\xc8\x2f\x01\x24\x86\xda\x2b\xc7\xeb\x76\x09\x03\x15\x20\xb6\x08\x79\x07\x34\xd6\xf3\xcf\x5d\xf2\xe0\xbb\xd7\x37\x55\x01\xe7\x3c\x7d\x8d\xaa\x17\x07\x3d\x13\x33\x18\xde\xed\x20\x7e\x42\x94\x15\xa4\x92\x14\x8f\x19\x73\x41\x81\x12\x42\xa6\xc1\x36\x10\x5b\x15\xc0\xa3\xf3\x18\xd0\x64\x53\xa0\xd7\xc2\x80\x5d\xfc\x03\xeb\x58\xc1\x5b\x64\xda\x20\xb4\x36\x69\x49\x16\xb2\x42\x1f\xc1\x63\x6d\x97\x46\xfd\xcf\x00\x2d\x40\xb4\x8c\x46\x8b\x88\x21\x82\x32\x11\xbd\x11\x9a\x64\x97\xf0\x0a\x84\x91\xd0\x89\x0d\x78\x24\xb8\x90\xcc\x04\x02\x2f\x09\x15\xfc\xcd\x7a\x04\x65\x1a\x7b\x0d\x6d\x8c\x2e\x5c\xcf\xe7\x4b\x15\x7b\x03\xaf\x6d\xd7\x25\xa3\xe2\x66\xce\x66\xaa\x16\x29\x5a\x1f\xe6\x12\x57\xa8\xe7\x41\x2d\x67\xc2\xd7\xad\x8a\x58\xc7\xe4\x71\x2e\x9c\x9a\x31\xb1\x86\xed\xbb\xea\xe4\x17\xbe\x9c\x86\xf0\x64\x22\xba\xb8\x21\x6d\x87\xe8\x95\x59\x0e\xaf\xd9\xf0\x0e\xca\x97\x0c\x8f\xd4\x29\xca\xb6\x4c\xff\x28\x46\x7a\x45\x92\x78\xf3\xfd\xdb\x77\xd0\x23\xcd\xa2\xce\x52\x1d\x97\x86\x51\xc0\x24\x1c\x65\x1a\xf4\x79\x65\xe3\x6d\xc7\x50\xd0\x48\x67\x95\x89\xfc\xa3\xd6\x0a\x4d\x84\x90\x16\x9d\x8a\xa4\xb9\xff\x4e\x18\x22\xc9\xbe\x82\x67\x7c\x9c\x61\x81\x90\x9c\x14\x11\x65\x05\x37\x06\x9e\x89\x0e\xf5\x33\x11\xf0\x77\x17\x2f\x49\x32\xcc\x48\x74\xa7\x05\x3c\xf5\x42\xdb\x0b\xb3\x84\x86\xd7\xbd\x9f\xd8\xab\x89\x9d\x23\xf6\xd6\x61\xbd\x65\xf1\x12\x83\xf2\x64\xa1\x51\x44\x24\xbb\xde\xd9\x50\x4d\x00\xef\x3b\x6c\xf9\xc0\xb9\x3d\x07\xee\x20\x63\xc5\x8b\x1a\xac\x89\xc4\xb7\xfc\x71\x77\xe3\x16\x0f\xcf\x76\x16\x0f\x0c\x08\x88\xd8\x39\x3a\x4d\xb2\xb7\xb3\xd8\x8a\x08\xb5\x30\xac\xe3\x80\x92\x8e\x5c\x41\x46\xff\x2b\x0c\x28\x13\xa2\x30\x35\xe6\xb3\x8d\x03\xc3\xd5\xb9\xb4\xf7\xfe\xe8\x28\xcd\x4f\x5e\xb1\x92\xde\x60\x83\x1e\x09\x1b\x59\x8c\x50\x26\x00\x1a\x9b\x96\x2d\x1b\x99\xef\xb2\x3b\x89\x16\x34\x46\xd8\xd8\x44\xd4\x39\xa2\xd5\x7a\xe8\xac\x54\xcd\x86\x69\xf4\x04\x86\xd4\xd4\xbb\x9c\xd9\x6c\x06\x2f\x71\x4d\x2c\x86\xc1\x49\x11\xc5\x20\x3c\x82\x54\xa1\xb6\xc9\x8b\x25\x4a\x58\x60\x2d\x52\x60\x6e\xa5\x6a\x1a\x55\x27\x1d\x37\x85\xd6\x05\x49\x8c\x0e\x49\x0a\x62\x89\xb0\x6e\xd1\x00\x76\x0b\x94\x12\x25\x28\x43\x8e\x36\x54\x00\x4f\x2b\xb8\x59\x1a\x4b\xf8\x1b\x85\x5a\xd2\xbb\x1b\x72\x5f\xb5\x4e\x12\xe9\x58\x9a\x4d\xf9\x02\xeb\x56\xd5\x2d\x13\x41\x07\x6d\x89\x06\xbd\xd0\x7a\x03\xad\x65\x00\x15\xc0\x0f\xd6\x0f\x3a\xb8\x82\x3e\xfc\xf6\xde\x98\x7c\xe0\x0f\x04\xea\xb5\x88\x19\xce\xc2\xc6\x96\x1c\xf3\x06\xbc\xf0\xa8\x37\x39\xd2\x10\x79\xa2\x8e\x49\xe8\x4c\x7c\x05\xf0\x0d\x1d\xe6\xfc\x31\xf3\xd3\xa2\x76\x85\xd4\x00\xaa\x73\x36\x04\xb5\xd0\xc8\x76\x20\x25\x9f\x1b\xd5\xa8\x9a\xd7\x71\xb4\x51\x46\xaa\x95\x92\x53\xa0\x37\x06\x3a\x1b\xe2\x28\x16\xfe\x10\xae\x48\x2d\x3e\x4b\xdb\x09\x1f\x49\xac\xc2\xb3\x19\x78\x24\x9b\x61\x73\x0d\xa0\xd5\x1d\x5e\xc1\x65\x97\x42\xcc\x4a\x04\x6b\xf4\x86\xe3\x00\xb9\x02\xf8\x8e\x19\xfe\xeb\x25\xe9\xfb\xf2\xfd\xcd\x73\x96\x5a\x91\x55\x7e\x49\xb1\x1e\x78\xff\x02\x07\xd8\x28\x2f\x2b\x46\xf6\xae\xb5\x01\xc9\xde\x8b\x5b\x5b\xa3\xd6\xbd\x72\x51\x6e\x6b\xb4\x02\xf8\x96\x44\x54\x5b\x13\x54\x88\xe4\x24\xc7\xa0\x5d\x01\xfc\xb5\x58\x0a\x19\x5c\xe6\xb2\x18\x53\xc3\x36\x1c\xaf\x72\x88\x1c\xb6\x80\x4f\x7a\x77\x0d\x2c\x36\x79\xef\x55\xb1\x84\x4e\xdc\x61\x00\x15\xa1\x15\x5e\xb2\x90\x53\x20\x57\x1e\x2d\x38\x8f\x52\xd5\x11\xd6\x74\x64\xd7\x4a\x6b\x68\x85\x73\x48\xa4\xfc\xb9\x82\x77\x2d\xf6\x36\x35\x58\x81\xea\x9c\xc7\x5a\x05\x64\xa9\xd9\x15\x7a\xbd\x81\xf2\xaa\x02\xe8\x83\x0e\xc9\x42\xf4\xef\xa1\x13\xce\xb1\x67\xb0\x20\xe0\xfd\x9b\x17\x04\x5a\x05\xf6\x11\xce\x5b\x99\x6a\x04\xd1\x2d\xd4\x32\xa9\xb8\xc9\xe7\x38\xb1\x27\xe1\xe8\xec\x3c\x96\x90\x4f\x18\x29\x96\x28\xd2\x7a\x8e\x5b\x05\xf2\xc4\x4a\x6a\x11\x8a\x6d\x80\x44\x87\x46\xa2\xa9\x37\x44\x12\x1d\xf2\x16\x73\x2a\x77\x35\xc6\xbb\xe4\x34\x66\x17\x6a\xe4\x34\x01\xe9\x7d\x53\xb1\xf0\x10\x7d\xaa\xb3\x15\x7b\x8f\x1a\x57\xc2\xc4\x0a\xe0\x2f\x15\xfc\xc7\xa0\x7c\x14\x41\xe9\x0d\xd4\xad\x30\x4b\x04\x15\xb7\x14\xda\x3b\x07\x15\xb6\xce\x37\x1f\x5c\x6d\xeb\x9c\xdf\x5e\x95\xa0\x58\xd2\x94\x7e\x0f\x3d\xac\x1d\xd1\x34\xe4\x99\x4c\xea\xd0\xdb\x14\xfa\xa4\xa6\x02\x78\x6e\xcd\x93\x27\x91\x75\x0d\x06\xd7\xec\x37\x32\x22\x72\xb8\xc9\x48\xf4\xe5\xb0\xa1\xa4\x8f\x19\x70\x6c\x71\x03\xd2\xb2\xba\x4a\x56\x4d\xe6\x19\x22\x0a\x49\x02\x48\x21\x3b\xf4\x42\xc8\x55\x4e\xa5\x49\xfa\x44\xb2\x66\xd5\xdb\x95\x92\x8c\x45\x16\x6f\x9f\x01\x0b\x16\x16\x1d\x86\x59\x63\x6b\xfe\x62\x0d\xf9\x57\x9f\x4f\x21\x79\xe4\x8a\x3d\x11\x7e\x10\x9d\xd3\x78\xc5\x39\x86\xaa\x71\x70\xd8\x81\x8d\x55\xc8\x4e\x05\xd6\x88\xc7\xa5\x0a\xd1\xe7\x0c\x77\x9a\x1c\xb4\x69\x51\xd5\xb6\x9b\x53\x25\xe0\x0d\x46\x0c\x14\xf9\xe7\x0b\x6d\x17\x73\x52\x96\x08\x38\x7b\x5a\x3d\xfd\x97\xf9\x00\x6b\x0a\x6a\xbe\x7a\x3a\x67\x57\x50\x2d\xed\x17\x2f\xfe\xf2\xed\xb7\x50\x3d\xd9\x89\x2b\x87\xc2\x2e\x1c\xcc\x75\xf7\x46\x24\x92\xfb\x8e\x79\x15\x59\xc4\xdd\xd0\x77\x34\xfc\xd1\xd3\xf4\x1e\xfa\x24\xd6\x27\x37\x4d\x89\x5e\xc3\x19\x74\x0a\x73\xf4\x1d\x52\x68\x8e\x07\x45\xeb\xc2\x00\x25\x4c\x1e\xcb\xb7\xab\x6c\x01\x25\xbc\x8f\x29\x36\x05\x53\x10\x25\x18\xfc\xfb\xdb\x57\x2f\xe7\xff\x66\x33\x5d\x20\xea\x1a\x43\xc8\x29\x4d\xc7\x8e\x2b\x24\x0a\x4a\xa1\xcf\x76\xde\xd2\x97\xaa\x13\x46\x35\x18\x62\x55\xa0\xa1\x0f\x3f\x7d\xf3\xf3\x8e\x59\xa8\x2c\xa9\x21\x29\xed\xc3\xb9\x0a\x99\x99\x61\x2f\xac\x55\x6c\x99\x24\x67\x65\x21\x7a\xcd\xc4\x46\x3a\x16\xb6\x10\x9b\x90\x63\xc2\x35\x5c\xd2\x89\x98\xa0\xfe\x85\x1c\xfd\xaf\x97\xf0\xe5\x9a\x03\x0b\xfb\xfd\xcb\x8c\x70\xa8\x1b\x38\x16\x14\xdd\x8d\x88\xd9\xdc\xa3\x57\xcb\x25\x52\x88\xe6\x84\x98\x92\xce\xaf\x28\x7e\xa8\x06\x8c\x9d\x2c\x66\x10\x24\xcf\xe1\x3c\xee\x12\xf2\xd3\x37\x3f\x5f\xc2\x97\xdb\x7c\x51\x64\xc4\x0f\xf0\x0d\x39\x0d\xe6\xcc\x59\xf9\x55\x71\xa4\x61\x63\xa2\xf8\x40\x30\x6b\x0a\x46\x66\x88\x70\xad\x58\x21\x04\xdb\xe5\xa8\x34\xcb\x49\x9b\x84\xb5\xd8\x10\x0f\xbd\x28\x49\xab\x82\x63\xe8\x4e\x55\xf5\xee\xd5\xf3\x57\xd7\x19\x1b\xa9\x6d\x69\x7a\xd7\xde\x28\xaa\x99\xb2\xc7\xa4\x2a\x80\x75\x4e\x84\xa4\xac\x24\xca\xf8\x8a\x17\xcc\x5e\xb7\x49\x94\x8f\xdf\x3b\x57\x27\xad\x7c\xb7\xc4\xd9\x6f\xe0\x1c\x75\x76\x0f\xd4\xff\x51\x21\x71\x16\x5b\xdc\x77\x38\xc9\xd6\xcb\x89\xad\x1d\x65\x6b\xf4\x7b\xc4\x99\xb4\x75\x20\xa6\x6a\x74\x31\xcc\x29\x44\xaf\x14\xae\xe7\x6b\xeb\xef\x94\x59\xce\xc8\x98\x66\x59\xc3\x61\xce\x6d\x8c\xf9\x17\xfc\x9f\x47\x71\xc1\xcd\x87\xf3\x58\xe1\xa5\x7f\x04\x3f\x84\x27\xcc\x1f\xcc\x8e\xdf\xce\x83\x4f\x33\xf5\xb6\xcf\x5e\x77\x76\x92\xf9\xe7\xd4\xab\x34\x2e\x26\x1e\xab\x13\x32\xbb\x34\x61\x36\xbf\xbb\x89\x92\xd0\x92\x27\xdc\x9b\x59\x09\xef\x33\x61\xe4\x6c\x48\x3f\xeb\xcd\x83\xa5\x94\xd4\x19\x07\x92\xd2\xe8\x3f\xc4\x70\x93\x7a\xf0\xe9\xdb\x5b\xc0\xd3\xe3\x84\x17\x1d\x46\xf4\xf7\x82\xbc\x8a\xd8\xed\x89\xfc\x5b\x3c\xbf\xee\x77\x43\x2d\x1c\x29\xa4\xb4\xb3\x84\x57\x62\xa1\x34\x65\xb6\xd9\xb9\xee\x76\xdc\x16\x98\x53\x5d\x2a\xc7\xa2\xe2\x42\x9a\x62\xd8\x58\x25\xdf\x4f\x0d\x8e\xa5\x23\x44\x56\x23\x92\x8e\xfb\x3e\xed\xd0\xfc\x3c\xaf\xcc\x5d\xa2\xb2\xad\x44\xc8\x1c\xb4\x06\x91\xd0\x92\x21\xd5\x5b\xe4\x8a\xf8\x30\x7d\x27\x34\xb0\x4b\xc7\x39\x84\x0e\x3f\x46\xf1\x52\x12\x6a\x96\xe8\xa7\x4b\x49\xc6\xad\x5d\x33\x7d\x23\xf1\x9c\x3b\x97\x6e\xc4\xe3\xa8\x55\xc1\x69\xb1\x79\x79\xc0\x6d\xef\x52\x3b\xae\xde\xea\x83\x2c\x36\xf0\xfe\x26\x3c\x8a\x00\x34\xa9\x3b\x4f\xa1\xa5\x2b\xa3\x55\xc8\xd1\x5c\x6b\xbb\x9e\x34\x2f\x6f\x9a\xa9\xd6\x03\x46\x8e\xe2\xdf\x9b\xd4\xf5\xb1\xdd\x28\x3d\x94\x99\x69\xac\x7b\xfb\xb4\x83\x01\x8b\x9c\xd9\xef\x25\xe8\xc0\x61\x39\x8b\xcd\x7e\x81\xf0\x5e\x6c\xf6\x7c\x57\x5d\x97\xa2\x58\xe8\x73\xb4\x50\xbc\x33\x95\xbe\xcd\x8e\x3d\x14\xa5\xe4\x14\x45\x82\x68\x22\xfa\x62\xd2\x2a\x2a\xa1\xb3\x69\x6b\x3d\x74\x9a\xa7\x3d\xf0\x23\x64\x2f\xac\xd5\x28\xcc\x9e\x15\x87\x02\xfe\x0e\xcd\x97\x2f\x4b\x6e\x48\x08\xa7\xed\xb4\x92\x74\xf7\x96\x54\xb2\xaa\xbe\xf5\x06\x8d\xd2\xc8\xc5\xd2\x34\x69\xbe\xcd\xb7\x1b\xcf\x5e\xbd\x7f\xf9\xee\x96\xd6\x9b\xa1\x9e\xeb\xfd\x92\x66\xbd\x0a\x4e\x45\x4b\x52\xfc\x5f\x26\x77\x33\x39\x20\x3a\xad\x6a\x11\xae\x01\x7e\xf9\x05\x2a\xf6\x70\xa1\x62\x78\xf0\xeb\xaf\x97\x8f\xb1\xe3\x52\xbe\xef\x0d\x21\x3b\xb2\x78\x53\x96\x0e\x79\xf2\x1e\x45\xaa\x30\x40\xa4\xa0\xbb\xc0\x2d\x27\x25\xb4\x1e\x9c\x54\xb8\xa2\x54\x7c\xdd\x62\x6c\xd1\x4f\xbc\x1d\x99\x42\x48\x4d\xa3\x8e\xfb\xb1\xc3\x9a\x2d\x59\xff\x19\x0c\xbd\xcb\x2b\x41\x49\x0a\xd1\xcc\x10\x73\xa3\x85\xc9\x4a\x5e\x62\x0c\x80\x1f\xb0\x4e\xb1\x6f\x1c\xe5\x4c\x7f\x34\x5c\xb6\xd8\xd0\xeb\xff\x66\xe8\xa3\x96\x84\x7d\x72\xb4\x6f\x73\x27\xe1\x96\x73\x8d\x8c\x84\xcb\x08\xc6\xc4\x25\x08\x7e\x50\x21\x92\x5c\x48\x24\x6b\x15\x10\x54\x7c\x12\xe0\x56\xa2\xd3\x76\x73\xfb\x28\x3f\xc5\x3e\x63\xc6\x8b\xce\x10\xc8\xc6\xe1\x44\xbb\xa3\xcf\xa1\xfd\x03\x33\x5c\x7a\xdd\x66\x8c\x8f\x21\xea\x60\xbc\x3f\xec\x6c\x48\x5a\xf7\x5c\x98\x90\x92\xef\x26\x85\x7e\x7d\x24\xfc\x6e\xe7\x04\x24\xf5\x91\x41\x01\x01\xbd\xca\xdd\xe2\xd7\xad\x08\xcc\x33\x69\x03\x07\xf3\xad\x2d\x1d\xdf\xb8\x2f\x4c\x1d\x0f\xfa\x8e\xe1\x9d\x21\xf4\x82\xb8\x13\x8e\x08\xe2\x6d\xd9\x1c\xb8\x96\xe6\xaf\xbd\x39\x3d\xdc\xbd\xdf\xc7\xb4\xc5\x7e\x1f\x94\x42\x44\x57\x78\xef\xdb\x08\x3f\x0e\x99\x60\xc1\x7e\x20\x4e\x9e\x92\x43\x7e\x0e\xbb\xdb\xfc\x9c\x0c\x43\xf4\x30\x95\xc7\xa0\x6c\x87\x1b\xe6\xa9\x88\x95\xb6\x4e\xa4\xda\xf3\x3d\x5e\x95\xdc\x67\x17\x42\xe4\xf6\xbe\x18\x6f\xec\x0e\x49\x00\x4e\xa9\xe1\x00\x79\x93\x4b\x9b\xa1\xf1\x4e\x19\x80\x6d\x4a\x70\xe3\x7e\x39\xab\xc5\xd6\x75\xba\x77\x1d\xb3\xfd\x9c\xa3\x87\xfc\x9c\xd2\x46\x7e\xce\xd2\x49\x59\x2a\xc2\xdd\x49\xac\x67\xc9\xe8\xc1\xa8\x4f\x65\x28\xbb\xeb\xf6\xfa\x9d\x87\x82\xe3\x4e\x25\x2e\x37\x67\x1b\xe3\x2b\x2f\x31\x37\xeb\x86\xd3\xd7\x67\xc5\x21\x2d\x58\x2e\x63\x1f\x49\x0b\x33\xcf\x7e\x60\xcc\x32\x78\xbc\x42\x82\x4d\x07\xbc\xc0\x94\xf6\xa3\x72\x3b\x29\x05\x93\xb4\xe6\x8c\x0e\xa2\x4f\xf8\x88\x9c\xf0\xb8\x70\xfe\x58\xb1\x3c\x36\x10\xdd\x8b\x1a\xa3\x83\xa6\x10\x32\x7a\x12\xfa\xb9\x8b\xfa\xa8\x04\x0f\x22\xdd\x7b\x86\xb6\xe8\x78\x31\x96\x10\x79\x35\x88\x95\x50\xba\xcf\x1a\x59\x4a\x47\xae\xaa\xe1\xcc\xc2\xf9\x9d\x08\x77\xb9\x02\x5d\x6a\xbb\x10\xfa\x0a\x9c\xd5\x9b\xce\x7a\xd7\xaa\x1a\x14\xc5\xc3\x6e\x6b\xea\x43\x6b\x70\x69\xa1\x55\xad\x37\x13\x8a\x98\xc2\x07\x06\xcd\x43\xdd\xc5\x93\x96\x7d\xd8\xa3\x9d\xd8\xb8\x3b\x28\x70\x44\x26\x3c\x27\xc0\x37\x38\x21\xf3\x3c\xdc\x9d\x92\xc0\x08\x50\x28\xed\x5f\xae\xf1\x02\xa4\xdc\x0f\x5f\x59\x25\x61\xed\x15\x0f\x78\xd4\x3c\x60\x05\xc9\xcc\x3b\xe1\x43\x2b\xb4\xe6\x5e\x36\x5f\xf7\xb1\x51\x73\xa7\xd8\x09\x1f\x10\x6a\xf4\x1c\x8a\xcb\x95\x5f\xbe\x3d\x23\x20\xe5\xe6\x8c\xf1\xfe\xa8\x8c\xcc\x37\x83\xd2\xae\x4d\x50\x12\x87\xbb\x6f\xe1\x9c\xb7\xa2\x6e\x41\xf1\xfd\x9b\x98\xdc\xd8\xe6\x9b\x56\xca\xb5\xf9\x72\x55\xac\x86\x8b\xc5\x92\x93\x22\x04\xb2\xf1\x7f\x04\x9b\xad\x3d\x50\x1c\x54\x3d\x91\x0b\xac\x6d\xd7\xdf\x11\xda\x14\x86\xb9\xa4\x3e\x8b\x67\x06\x3c\xdf\xc5\x75\x6a\xd9\x46\xf0\xb8\x52\x41\xc5\x5d\xc2\xa6\xcd\xe8\xfe\x58\xf3\x92\x1e\x83\x01\x15\x42\x3a\x50\x0a\x9c\x8e\x78\x87\xc7\x30\x0e\x28\x7a\x12\x89\x85\x73\xc3\xe5\x50\x21\xd4\x52\x5d\x42\xf5\xa8\x47\x67\xaf\x7a\x6e\x87\xbb\x08\xbe\xef\xf4\x58\xa3\x39\x1c\x60\xce\x70\xd1\xd2\x9a\x23\xe1\xf9\x54\xfd\x43\x4f\x23\xa2\xd0\x1f\x07\xa2\xaf\xd3\x0e\xb7\x58\xce\xe4\xc6\x6e\x7b\xa4\x87\xeb\xa1\x07\xf0\x78\x65\x80\x35\xfb\x03\xd9\x99\x2c\x38\x51\xdf\x89\xe5\x11\x31\x6c\x91\x8e\x8a\x4b\x5a\xa2\xaa\xdf\xc9\x47\xe8\x2a\xdf\xd1\x0e\xef\x1a\xab\x25\x7a\xaa\x82\x85\x81\xf7\x6f\x5e\xf0\xfd\x7f\xf9\x16\x85\x5f\x08\xad\xab\xfe\xe2\x7d\x90\xc1\xb4\x11\x72\xc5\xf3\x8f\x75\xd4\xb9\x9f\xe6\x31\x58\xbd\xc2\x52\x91\x67\x38\xfd\x4c\x80\x27\xbf\x30\xb9\x1a\x1a\x4e\x79\xd9\x24\x47\x0c\x44\xea\xe1\x1c\xe3\x2c\x69\x95\xea\xf8\xa3\xcc\x66\x80\xf2\x83\x3a\xd4\x5d\xba\x27\xf9\xe9\x5d\xe0\x76\x2b\xe6\x4b\xbe\xd1\x1f\x1b\x35\xb7\xfd\xe7\x70\x5b\xf4\xf0\x55\x1e\x9d\xeb\xaf\x09\x10\xbe\x76\xc2\xa3\x89\x5f\x8f\xc3\x51\x79\x72\x27\x72\xbe\x3e\xb6\x00\x18\x7e\x3f\x56\xe5\xac\x4b\x8c\x95\x21\xd4\xad\xd2\xf2\xeb\xa1\x2b\x50\x51\x04\xa9\x86\xbe\xf4\xfd\xe0\xf8\x10\xf1\xa8\x43\x15\x3d\x9c\x93\x68\x6f\xe7\x37\xca\x61\x19\x82\xcb\x43\x32\x39\x0b\x13\x99\xb5\x9e\xeb\xdc\xc1\xe1\xc5\x7d\x86\xd0\x57\x48\x3c\x06\x52\xee\x54\xe4\x11\xac\xe7\x56\x28\x68\x56\xc7\xb5\x9e\x9f\xb3\xab\x84\xe6\xb7\x04\x76\x87\x47\xd3\xfe\x87\xc1\x3a\x98\xef\x3c\x18\xd8\x59\xd5\xcd\x19\xb9\x3d\x9c\x55\x02\x39\x7b\x84\xee\x33\x28\x1e\x66\x6a\x3f\xc2\x86\xcf\x12\xcc\x6f\xc6\xf1\x5a\x98\xf8\xbd\x3f\xe9\xd3\x8e\x05\xd5\xa3\x2a\x7a\x44\x57\x6c\xf0\x61\x8f\xeb\x8c\x1d\x91\xdf\x76\xee\xdb\xa3\xc9\x45\x41\xdf\x3b\xf1\xe3\x54\x50\xb4\xf0\x9f\xdf\xfd\xed\xc5\x48\x10\xec\xb8\xdb\xf1\x43\x09\x7a\xe4\x32\xe8\xc5\x64\xf6\x48\x96\xd9\x65\x2a\x1b\xf6\xcf\x9d\xee\x11\x4e\x72\x4b\x2f\x24\x29\xf8\x07\x6f\xef\x5d\xc8\x6c\xb1\xf1\x7e\x6b\x29\xb3\x91\xf3\xf8\x9d\x92\x29\x8c\xf3\xb2\x19\x3a\x0e\xb3\x5f\xbf\x41\x71\xf5\x79\x06\xf6\xf3\x0c\xec\xe7\x19\xd8\xcf\x33\xb0\x9f\x67\x60\x3f\xcf\xc0\x7e\xe4\x0c\xec\xa9\x7c\xfa\xf8\x1c\xec\xc7\x4d\xc2\x9e\xcc\xbe\x8e\x4e\xc3\x7e\x9e\x87\xfd\x3c\x0f\xfb\xff\x67\x1e\xf6\xa4\xad\x1f\xae\xe2\x3e\xfd\xa9\xd8\x8f\xe8\xac\x7f\x72\xb3\xb1\x67\xf1\x72\x70\x3e\xf6\x13\x9d\x90\x3d\x63\x82\xe7\xe4\x94\xec\x3f\xcb\x9c\xec\x49\x59\x1d\x98\x95\xfd\xe4\xa6\x65\x7f\xfb\x09\x9a\xd5\x83\xfe\x31\xea\xfe\x7f\x53\x1b\x45\x4c\xe1\xec\x7f\x55\xcb\xab\xb7\xfe\x5d\xad\x5d\x04\xf4\xab\x33\xff\x61\xed\x1e\x12\x76\x5e\x8d\x7f\x57\xa0\xfc\x09\x83\xe1\x15\x13\x39\x2b\x7f\x4c\x60\xfc\x0a\x90\xf1\x4f\x7a\x41\x21\x5a\x2a\x87\xcb\x9b\x91\x43\x4a\x0f\x5c\x44\xf9\x72\xf7\xaf\x0a\x5c\xe6\x29\xbb\xfe\xcf\x04\xf0\xcf\xda\x9a\xdc\x6f\x09\xd7\xf0\xd3\xcf\x17\x50\xfa\xa2\x7d\x3f\x81\x5f\xfe\x6f\x00\x00\x00\xff\xff\x87\xa5\x14\x8f\x8d\x41\x00\x00") func configCrdsKudoDev_operatorversionsYamlBytes() ([]byte, error) { return bindataRead( diff --git a/pkg/kudoctl/packages/convert/parameters.go b/pkg/kudoctl/packages/convert/parameters.go index 58cb6194f..a715d5218 100644 --- a/pkg/kudoctl/packages/convert/parameters.go +++ b/pkg/kudoctl/packages/convert/parameters.go @@ -18,6 +18,19 @@ func ParametersToCRDType(parameters packages.Parameters) ([]kudoapi.Parameter, e return nil, fmt.Errorf("failed to convert %s default for parameter '%s': %w", parameter.Type, parameter.Name, err) } + var enumValues *[]string + if parameter.IsEnum() { + var ev []string + for _, v := range parameter.EnumValues() { + vWrapped, err := utilconvert.WrapParamValue(v, parameter.Type) + if err != nil { + return nil, fmt.Errorf("failed to convert %s enum value '%s' for parameter '%s': %w", parameter.Type, v, parameter.Name, err) + } + ev = append(ev, *vWrapped) + } + enumValues = &ev + } + result = append(result, kudoapi.Parameter{ DisplayName: parameter.DisplayName, Name: parameter.Name, @@ -27,6 +40,7 @@ func ParametersToCRDType(parameters packages.Parameters) ([]kudoapi.Parameter, e Trigger: parameter.Trigger, Type: parameter.Type, Immutable: parameter.Immutable, + Enum: enumValues, }) } diff --git a/pkg/kudoctl/packages/types.go b/pkg/kudoctl/packages/types.go index 517b39e2e..5a41fb250 100644 --- a/pkg/kudoctl/packages/types.go +++ b/pkg/kudoctl/packages/types.go @@ -1,6 +1,8 @@ package packages import ( + "fmt" + kudoapi "github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1" ) @@ -58,6 +60,14 @@ type Parameter struct { Trigger string `json:"trigger,omitempty"` Type kudoapi.ParameterType `json:"type,omitempty"` Immutable *bool `json:"immutable,omitempty"` + Enum *[]interface{} `json:"enum,omitempty"` + + // The following fields are descriptive only and are not used in the OperatorVersion. They are only used on the + // package level and are not converted to the CRDs, as they are only used during installation of an operator and + // are not necessary server-side. + Group string `json:"group,omitempty"` + Advanced *bool `json:"advanced,omitempty"` + Hint string `json:"hint,omitempty"` } func (p Parameter) IsImmutable() bool { @@ -68,10 +78,40 @@ func (p Parameter) IsRequired() bool { return p.Required != nil && *p.Required } +func (p Parameter) IsAdvanced() bool { + return p.Advanced != nil && *p.Advanced +} + +func (p Parameter) IsEnum() bool { + return p.Enum != nil +} + func (p *Parameter) HasDefault() bool { return p.Default != nil } +func (p *Parameter) ValidateDefault() error { + if err := kudoapi.ValidateParameterValueForType(p.Type, p.Default); err != nil { + return fmt.Errorf("parameter \"%s\" has an invalid default value: %v", p.Name, err) + } + if p.IsEnum() { + for _, eValue := range p.EnumValues() { + if p.Default == eValue { + return nil + } + } + return fmt.Errorf("parameter \"%s\" has an invalid default value: value is %q, but only allowed values are %v", p.Name, p.Default, p.EnumValues()) + } + return nil +} + +func (p *Parameter) EnumValues() []interface{} { + if p.IsEnum() { + return *p.Enum + } + return []interface{}{} +} + type Parameters []Parameter // Len returns the number of params. @@ -88,6 +128,15 @@ func (p Parameters) Less(x, y int) bool { return p[x].Name < p[y].Name } +type Groups []Group + +type Group struct { + Name string `json:"name,omitempty"` + DisplayName string `json:"displayName,omitempty"` + Description string `json:"description,omitempty"` + Priority int `json:"prio,omitempty"` +} + // Templates is a map of file names and stringified files in the template folder of an operator type Templates map[string]string @@ -101,6 +150,7 @@ type Files struct { // ParamsFile is a representation of the package params.yaml type ParamsFile struct { APIVersion string `json:"apiVersion,omitempty"` + Groups Groups `json:"groups,omitempty"` Parameters Parameters `json:"parameters"` } diff --git a/pkg/kudoctl/packages/verifier/template/verify_parameters.go b/pkg/kudoctl/packages/verifier/template/verify_parameters.go index 83b6198f9..3c05c368d 100644 --- a/pkg/kudoctl/packages/verifier/template/verify_parameters.go +++ b/pkg/kudoctl/packages/verifier/template/verify_parameters.go @@ -2,7 +2,9 @@ package template import ( "fmt" + "strings" + kudoapi "github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1" "github.com/kudobuilder/kudo/pkg/engine/renderer" "github.com/kudobuilder/kudo/pkg/engine/task" "github.com/kudobuilder/kudo/pkg/kudoctl/packages" @@ -21,6 +23,10 @@ func (ParametersVerifier) Verify(pf *packages.Files) verifier.Result { res.Merge(paramsNotDefined(pf)) res.Merge(paramsDefinedNotUsed(pf)) res.Merge(immutableParams(pf)) + res.Merge(enumParams(pf)) + res.Merge(paramDefaults(pf)) + res.Merge(metadata(pf)) + res.Merge(paramGroups(pf)) implicits := renderer.NewVariableMap().WithDefaults() @@ -43,13 +49,85 @@ func (ParametersVerifier) Verify(pf *packages.Files) verifier.Result { func immutableParams(pf *packages.Files) verifier.Result { res := verifier.NewResult() - for _, value := range pf.Params.Parameters { - if value.IsImmutable() { - if !value.HasDefault() && !value.IsRequired() { - res.AddParamError(value.Name, "is immutable but is not marked as required or has a default value") + for _, p := range pf.Params.Parameters { + if p.IsImmutable() { + if !p.HasDefault() && !p.IsRequired() { + res.AddParamError(p.Name, "is immutable but is not marked as required or has a default value") + } + } + } + return res +} + +func paramDefaults(pf *packages.Files) verifier.Result { + res := verifier.NewResult() + for _, p := range pf.Params.Parameters { + if p.HasDefault() { + if err := p.ValidateDefault(); err != nil { + res.AddErrors(err.Error()) + } + } + } + return res +} + +func enumParams(pf *packages.Files) verifier.Result { + res := verifier.NewResult() + for _, p := range pf.Params.Parameters { + + if p.IsEnum() { + if len(p.EnumValues()) == 0 { + res.AddParamError(p.Name, "is an enum but has no allowed values") + continue + } + for _, enumVal := range p.EnumValues() { + + if err := kudoapi.ValidateParameterValueForType(p.Type, enumVal); err != nil { + res.AddParamError(p.Name, fmt.Sprintf("has an invalid enum value: %v", err)) + } + } + } + } + return res +} + +func metadata(pf *packages.Files) verifier.Result { + res := verifier.NewResult() + for _, p := range pf.Params.Parameters { + if p.Group != "" { + if strings.Contains(p.Group, "/") { + res.AddParamError(p.Name, "has a group with invalid character '/'") + } + } + if p.IsAdvanced() && !p.HasDefault() && p.IsRequired() { + res.AddParamError(p.Name, "is marked as advanced, but also as required and has no default. An advanced parameter must either be optional or have a default value") + } + } + return res +} + +func paramGroups(pf *packages.Files) verifier.Result { + res := verifier.NewResult() + + groups := map[string]packages.Group{} + for _, g := range pf.Params.Groups { + if strings.Contains(g.Name, "/") { + res.AddGroupError(g.Name, "contains invalid character '/'") + } + if _, ok := groups[g.Name]; ok { + res.AddGroupError(g.Name, "is duplicated") + } + groups[g.Name] = g + } + + for _, p := range pf.Params.Parameters { + if p.Group != "" { + if _, ok := groups[p.Group]; !ok { + res.AddParamError(p.Name, "has a group that is not defined in the group section") } } } + return res } diff --git a/pkg/kudoctl/packages/verifier/template/verify_parameters_test.go b/pkg/kudoctl/packages/verifier/template/verify_parameters_test.go index 988f4dd3c..df2b5527b 100644 --- a/pkg/kudoctl/packages/verifier/template/verify_parameters_test.go +++ b/pkg/kudoctl/packages/verifier/template/verify_parameters_test.go @@ -18,6 +18,7 @@ func TestTemplateParametersVerifier(t *testing.T) { {Name: "BROKER_COUNT"}, {Name: "EXTERNAL_NODE_PORT"}, {Name: "TRIGGER_ONLY", Trigger: "foo"}, + {Name: "ValidDefault", Type: kudoapi.IntegerValueType, Default: 42}, } paramFile := packages.ParamsFile{Parameters: params} templates := make(map[string]string) @@ -52,6 +53,8 @@ func TestTemplateParametersVerifier(t *testing.T) { - containerPort: {{ add (int $.Params.EXTERNAL_NODE_PORT) $v}} name: node-port-{{ $v }} {{ end }} + +{{.Params.ValidDefault}} ` operator := packages.OperatorFile{ Tasks: []kudoapi.Task{ @@ -110,3 +113,59 @@ func TestImmutableParams(t *testing.T) { assert.Equal(t, 1, len(res.Errors)) assert.Equal(t, `parameter "NoDefaultOrRequired" is immutable but is not marked as required or has a default value`, res.Errors[0]) } + +func TestEnumParams(t *testing.T) { + + params := []packages.Parameter{ + {Name: "NoDefaultNoEnum"}, + {Name: "EnumNoDefault", Enum: &[]interface{}{"someVal"}}, + {Name: "EnumWithDefault", Enum: &[]interface{}{"someVal"}, Default: "someOtherVal"}, + {Name: "EnumNoValues", Enum: &[]interface{}{}}, + {Name: "EnumWrongValues", Enum: &[]interface{}{"noint", "23", "42", "1.23"}, Type: kudoapi.IntegerValueType}, + } + paramFile := packages.ParamsFile{Parameters: params} + templates := make(map[string]string) + + operator := packages.OperatorFile{} + pf := packages.Files{ + Templates: templates, + Operator: &operator, + Params: ¶mFile, + } + verifier := ParametersVerifier{} + res := verifier.Verify(&pf) + + assert.Equal(t, 5, len(res.Warnings)) // NotUsed Warnings + assert.Equal(t, 4, len(res.Errors)) + assert.Equal(t, `parameter "EnumNoValues" is an enum but has no allowed values`, res.Errors[0]) + assert.Equal(t, `parameter "EnumWrongValues" has an invalid enum value: type is "integer" but format of "noint" is invalid: strconv.ParseInt: parsing "noint": invalid syntax`, res.Errors[1]) + assert.Equal(t, `parameter "EnumWrongValues" has an invalid enum value: type is "integer" but format of "1.23" is invalid: strconv.ParseInt: parsing "1.23": invalid syntax`, res.Errors[2]) + assert.Equal(t, `parameter "EnumWithDefault" has an invalid default value: value is "someOtherVal", but only allowed values are [someVal]`, res.Errors[3]) +} + +func TestMetadata(t *testing.T) { + trueFlag := true + params := []packages.Parameter{ + {Name: "InvalidGroup", Group: "some/group"}, + {Name: "InvalidAdvanced", Advanced: &trueFlag, Required: &trueFlag}, + {Name: "ValidAdvanced", Advanced: &trueFlag, Required: &trueFlag, Default: "someValue"}, + {Name: "AnotherValidAdvanced", Advanced: &trueFlag}, + } + paramFile := packages.ParamsFile{Parameters: params} + templates := make(map[string]string) + + operator := packages.OperatorFile{} + pf := packages.Files{ + Templates: templates, + Operator: &operator, + Params: ¶mFile, + } + verifier := ParametersVerifier{} + res := verifier.Verify(&pf) + + assert.Equal(t, 4, len(res.Warnings)) // NotUsed Warnings + assert.Equal(t, 3, len(res.Errors)) + assert.Equal(t, `parameter "InvalidGroup" has a group with invalid character '/'`, res.Errors[0]) + assert.Equal(t, `parameter "InvalidAdvanced" is marked as advanced, but also as required and has no default. An advanced parameter must either be optional or have a default value`, res.Errors[1]) + assert.Equal(t, `parameter "InvalidGroup" has a group that is not defined in the group section`, res.Errors[2]) +} diff --git a/pkg/kudoctl/verifier/result.go b/pkg/kudoctl/verifier/result.go index 7710e53bf..5a5a57021 100644 --- a/pkg/kudoctl/verifier/result.go +++ b/pkg/kudoctl/verifier/result.go @@ -60,6 +60,10 @@ func (vr *Result) AddParamError(paramName string, reason string) { vr.AddErrors(fmt.Sprintf("parameter %q %s", paramName, reason)) } +func (vr *Result) AddGroupError(groupName string, reason string) { + vr.AddErrors(fmt.Sprintf("parameter group %q %s", groupName, reason)) +} + // AddParamWarning adds a formatted warning string for a package parameter error func (vr *Result) AddParamWarning(paramName string, reason string) { vr.AddWarnings(fmt.Sprintf("parameter %q %s", paramName, reason)) diff --git a/pkg/webhook/instance_admission.go b/pkg/webhook/instance_admission.go index 95c4e630d..9d8f42e3b 100644 --- a/pkg/webhook/instance_admission.go +++ b/pkg/webhook/instance_admission.go @@ -91,6 +91,9 @@ func handleCreate(ia *InstanceAdmission, req admission.Request) admission.Respon new.Spec.PlanExecution.UID = uuid.NewUUID() setImmutableParameterDefaults(ov, new) + if err := validateParameters(ov, new); err != nil { + return admission.Denied(fmt.Sprintf("failed to create an Instance %s/%s: parameters are not valid: %v", new.Namespace, new.Name, err)) + } marshaled, err := json.Marshal(new) if err != nil { @@ -222,6 +225,9 @@ func admitUpdate(old, new *kudoapi.Instance, ov, oldOv *kudoapi.OperatorVersion) if err = checkImmutableParameters(old.Spec.Parameters, new.Spec.Parameters, ov, oldOv, changedDefs, isUpgrade); err != nil { return nil, fmt.Errorf("failed to check immutable parameters for Instance %s/%s: %v", old.Namespace, old.Name, err) } + if err := validateParameters(ov, new); err != nil { + return nil, fmt.Errorf("failed to validate parameters for Instance %s/%s: %v", new.Namespace, new.Name, err) + } parameterDefs := append(changedDefs, removedDefs...) triggeredPlan, err := triggeredByParameterUpdate(parameterDefs, ov) @@ -440,6 +446,20 @@ func validateOVUpgrade(new map[string]string, newOv, oldOv *kudoapi.OperatorVers return nil } +// validateParameters ensures that all parameters have correct values +func validateParameters(ov *kudoapi.OperatorVersion, instance *kudoapi.Instance) error { + for _, p := range ov.Spec.Parameters { + p := p + pValue := instance.Spec.Parameters[p.Name] + + if err := p.ValidateValue(pValue); err != nil { + return err + } + + } + return nil +} + // setImmutableParameterDefaults sets the default values for immutable parameters into the instances parameter map func setImmutableParameterDefaults(ov *kudoapi.OperatorVersion, instance *kudoapi.Instance) { for _, p := range ov.Spec.Parameters { diff --git a/test/integration/parameter-types-operator/operator/params.yaml b/test/integration/parameter-types-operator/operator/params.yaml index 110d4cb49..758729d01 100644 --- a/test/integration/parameter-types-operator/operator/params.yaml +++ b/test/integration/parameter-types-operator/operator/params.yaml @@ -12,3 +12,13 @@ parameters: - 80 - 443 type: array + - name: PULL_POLICY + type: string + enum: + - IfNotPresent + - Always + - Never + default: IfNotPresent + - name: REPLICAS + type: integer + default: 1 \ No newline at end of file diff --git a/test/integration/parameter-types-operator/operator/templates/deployment.yaml b/test/integration/parameter-types-operator/operator/templates/deployment.yaml index d39492c32..29ee68f57 100644 --- a/test/integration/parameter-types-operator/operator/templates/deployment.yaml +++ b/test/integration/parameter-types-operator/operator/templates/deployment.yaml @@ -10,7 +10,7 @@ spec: selector: matchLabels: app: nginx - replicas: 1 + replicas: {{ .Params.REPLICAS }} template: metadata: labels: @@ -19,6 +19,7 @@ spec: containers: - name: nginx image: nginx:1.7.9 + imagePullPolicy: {{ .Params.PULL_POLICY }} ports: {{ range .Params.PORTS }} - containerPort: {{ . }}