From 2e8a3b7739a5d87fedbd437d0a9229d7f1147aa0 Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Fri, 9 Oct 2020 17:48:36 +0200 Subject: [PATCH 1/9] Started json-schema export Signed-off-by: Andreas Neumann --- go.mod | 2 + go.sum | 5 + pkg/kudoctl/cmd/package_list_params.go | 40 +++- pkg/kudoctl/packages/convert/fullschema.json | 197 ++++++++++++++++++ .../convert/json-schema-extensions.go | 39 ++++ pkg/kudoctl/packages/convert/json-schema.go | 188 +++++++++++++++++ 6 files changed, 467 insertions(+), 4 deletions(-) create mode 100644 pkg/kudoctl/packages/convert/fullschema.json create mode 100644 pkg/kudoctl/packages/convert/json-schema-extensions.go create mode 100644 pkg/kudoctl/packages/convert/json-schema.go diff --git a/go.mod b/go.mod index a7c7772f3..0749f6f02 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,8 @@ require ( github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2 // indirect github.com/onsi/ginkgo v1.14.1 github.com/onsi/gomega v1.10.2 + github.com/qri-io/jsonpointer v0.1.1 + github.com/qri-io/jsonschema v0.2.0 github.com/spf13/afero v1.4.0 github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index 7df6e3ee6..043529c8e 100644 --- a/go.sum +++ b/go.sum @@ -449,11 +449,16 @@ github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4 github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA= +github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64= +github.com/qri-io/jsonschema v0.2.0 h1:is8lirh3HYwTkC0e+4jL/vWEHwzPLojnl4FWkUoeEPU= +github.com/qri-io/jsonschema v0.2.0/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= diff --git a/pkg/kudoctl/cmd/package_list_params.go b/pkg/kudoctl/cmd/package_list_params.go index 600736282..81770c74e 100644 --- a/pkg/kudoctl/cmd/package_list_params.go +++ b/pkg/kudoctl/cmd/package_list_params.go @@ -11,8 +11,10 @@ import ( "github.com/kudobuilder/kudo/pkg/kudoctl/clog" "github.com/kudobuilder/kudo/pkg/kudoctl/cmd/generate" + "github.com/kudobuilder/kudo/pkg/kudoctl/cmd/output" "github.com/kudobuilder/kudo/pkg/kudoctl/env" "github.com/kudobuilder/kudo/pkg/kudoctl/packages" + packageconvert "github.com/kudobuilder/kudo/pkg/kudoctl/packages/convert" "github.com/kudobuilder/kudo/pkg/util/convert" ) @@ -26,6 +28,8 @@ type packageListParamsCmd struct { RepoName string AppVersion string OperatorVersion string + Output output.Type + Format string } const ( @@ -34,11 +38,18 @@ const ( # show parameters from zookeeper (where zookeeper is name of package in KUDO repository) kubectl kudo package list parameters zookeeper` + + outputFormatList = "list" + outputFormatJSONSchema = "json-schema" + + TypeJSONSchema output.Type = "json-schema" + TypeJSONSchemaYaml output.Type = "json-schema-yaml" ) func newPackageListParamsCmd(fs afero.Fs, out io.Writer) *cobra.Command { list := &packageListParamsCmd{fs: fs, out: out} + var outputStr string cmd := &cobra.Command{ Use: "parameters [operator]", Short: "List operator parameters", @@ -59,6 +70,23 @@ func newPackageListParamsCmd(fs afero.Fs, out io.Writer) *cobra.Command { if err == nil { list.pathOrName = args[0] } + + switch output.Type(outputStr) { + case "": + // Nothing to set + case output.TypeJSON, output.TypeYAML: + list.Output = output.Type(outputStr) + list.Format = outputFormatList + case TypeJSONSchema: + list.Output = output.TypeJSON + list.Format = outputFormatJSONSchema + case TypeJSONSchemaYaml: + list.Output = output.TypeYAML + list.Format = outputFormatJSONSchema + default: + return fmt.Errorf("output must be one of json, yaml, json-schema, json-schema-yaml") + } + return list.run(&Settings) }, } @@ -70,6 +98,7 @@ func newPackageListParamsCmd(fs afero.Fs, out io.Writer) *cobra.Command { f.StringVar(&list.RepoName, "repo", "", "Name of repository configuration to use. (default defined by context)") f.StringVar(&list.AppVersion, "app-version", "", "A specific app version in the official GitHub repo. (default to the most recent)") f.StringVar(&list.OperatorVersion, "operator-version", "", "A specific operator version in the official GitHub repo. (default to the most recent)") + f.StringVarP(&outputStr, "output", "o", "", "Output format (json, yaml, json-schema or json-schema-yaml. Human readable if not specified)") return cmd } @@ -87,11 +116,14 @@ func (c *packageListParamsCmd) run(settings *env.Settings) error { return err } - if err := displayParamsTable(pf.Files, c.out, c.requiredOnly, c.namesOnly, c.descriptions); err != nil { - return err + switch c.Format { + case outputFormatList: + return output.WriteObject(pf.Files.Params, c.Output, c.out) + case outputFormatJSONSchema: + return packageconvert.WriteJSONSchema(pf, c.Output, c.out) + default: + return displayParamsTable(pf.Files, c.out, c.requiredOnly, c.namesOnly, c.descriptions) } - - return nil } func displayParamsTable(pf *packages.Files, out io.Writer, printRequired, printNames, printDesc bool) error { diff --git a/pkg/kudoctl/packages/convert/fullschema.json b/pkg/kudoctl/packages/convert/fullschema.json new file mode 100644 index 000000000..04d207951 --- /dev/null +++ b/pkg/kudoctl/packages/convert/fullschema.json @@ -0,0 +1,197 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$recursiveAnchor": true, + "$id": "https://kudo.dev/json-schema", + "title": "KUDO JSON-Schema", + "type": ["object", "boolean"], + "additionalProperties": false, + "properties": { + "$id": { + "type": "string", + "format": "uri-reference", + "$comment": "Non-empty fragments not allowed.", + "pattern": "^[^#]*#?$" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$anchor": { + "type": "string", + "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$recursiveRef": { + "type": "string", + "format": "uri-reference" + }, + "$recursiveAnchor": { + "type": "boolean", + "default": false + }, + "$comment": { + "type": "string" + }, + "$defs": { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "examples": { + "type": "array", + "items": true + }, + "default": true, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, + "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, + "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, + "minContains": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 1 + }, + "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, + "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/$defs/stringArray" }, + "dependentRequired": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/stringArray" + } + }, + "const": true, + "enum": { + "type": "array", + "items": true + }, + "type": { + "anyOf": [ + { "$ref": "#/$defs/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "additionalItems": { "$recursiveRef": "#" }, + "unevaluatedItems": { "$recursiveRef": "#" }, + "items": { + "anyOf": [ + { "$recursiveRef": "#" }, + { "$ref": "#/$defs/schemaArray" } + ] + }, + "contains": { "$recursiveRef": "#" }, + "unevaluatedProperties": { "$recursiveRef": "#" }, + "properties": { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependentSchemas": { + "type": "object", + "additionalProperties": { + "$recursiveRef": "#" + } + }, + "propertyNames": { "$recursiveRef": "#" }, + "if": { "$recursiveRef": "#" }, + "then": { "$recursiveRef": "#" }, + "else": { "$recursiveRef": "#" }, + "allOf": { "$ref": "#/$defs/schemaArray" }, + "anyOf": { "$ref": "#/$defs/schemaArray" }, + "oneOf": { "$ref": "#/$defs/schemaArray" }, + "not": { "$recursiveRef": "#" }, + "advanced": { + "type": "boolean", + "default": false + }, + "hint": { + "type": "string" + }, + "listName": { + "type": "string" + }, + "immutable": { + "type": "boolean", + "default": false + }, + "priority": { + "type": "integer" + } + }, + "$defs": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$recursiveRef": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 0 + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + } +} diff --git a/pkg/kudoctl/packages/convert/json-schema-extensions.go b/pkg/kudoctl/packages/convert/json-schema-extensions.go new file mode 100644 index 000000000..235e7acdc --- /dev/null +++ b/pkg/kudoctl/packages/convert/json-schema-extensions.go @@ -0,0 +1,39 @@ +package convert + +import ( + "context" + + "github.com/qri-io/jsonpointer" + "github.com/qri-io/jsonschema" +) + +// Advanced keyword +type IsAdvanced bool + +// newIsAdvanced is a jsonschama.KeyMaker +func newIsAdvanced() jsonschema.Keyword { + return new(IsAdvanced) +} + +// Register implements jsonschema.Keyword +func (f *IsAdvanced) Register(uri string, registry *jsonschema.SchemaRegistry) {} + +// Resolve implements jsonschema.Keyword +func (f *IsAdvanced) Resolve(pointer jsonpointer.Pointer, uri string) *jsonschema.Schema { + return nil +} + +// ValidateKeyword implements jsonschema.Keyword +func (f *IsAdvanced) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data interface{}) { + isAdvanced, _ := data.(bool) + if isAdvanced { + + currentState.Local.HasKeyword("default") + + } +} + +func init() { + jsonschema.LoadDraft2019_09() + jsonschema.RegisterKeyword("advanced", newIsAdvanced) +} diff --git a/pkg/kudoctl/packages/convert/json-schema.go b/pkg/kudoctl/packages/convert/json-schema.go new file mode 100644 index 000000000..b68cb5c56 --- /dev/null +++ b/pkg/kudoctl/packages/convert/json-schema.go @@ -0,0 +1,188 @@ +package convert + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + + "github.com/qri-io/jsonschema" + + kudoapi "github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1" + "github.com/kudobuilder/kudo/pkg/kudoctl/cmd/output" + "github.com/kudobuilder/kudo/pkg/kudoctl/packages" +) + +type jsonSchema struct { + Schema string `json:"$schema,omitempty"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Priority int `json:"priority,omitempty"` + Default interface{} `json:"default,omitempty"` + Type string `json:"type,omitempty"` + Immutable bool `json:"immutable,omitempty"` + Advanced bool `json:"advanced,omitempty"` + Hint string `json:"hint,omitempty"` + ListName string `json:"listName,omitempty"` + Properties map[string]*jsonSchema `json:"properties,omitempty"` + Required []string `json:"required,omitempty"` +} + +func newSchema() *jsonSchema { + return &jsonSchema{ + Properties: map[string]*jsonSchema{}, + Required: []string{}, + } +} + +func buildGroups(pkg *packages.Package) map[string]packages.Group { + params := pkg.Files.Params + + groups := map[string]packages.Group{} + for _, g := range params.Groups { + groups[g.Name] = g + } + for _, p := range params.Parameters { + if p.Group != "" { + if _, ok := groups[p.Group]; !ok { + groups[p.Group] = packages.Group{ + Name: p.Group, + } + } + } + } + + return groups +} + +func buildTopLevelGroups(groups map[string]packages.Group) map[string]*jsonSchema { + topLevelGroups := map[string]*jsonSchema{} + + for _, v := range groups { + g := newSchema() + + if v.DisplayName != "" { + g.Title = v.DisplayName + } else { + g.Title = v.Name + } + if v.Description != "" { + g.Description = v.Description + } + if v.Priority != 0 { + g.Priority = v.Priority + } + + topLevelGroups[v.Name] = g + } + + return topLevelGroups +} + +func jsonSchemaTypeFromKudoType(parameterType kudoapi.ParameterType) string { + switch parameterType { + // Most types are exactly as in json-schema + case kudoapi.StringValueType, + kudoapi.IntegerValueType, + kudoapi.NumberValueType, + kudoapi.BooleanValueType, + kudoapi.ArrayValueType: + return string(parameterType) + + // Objects are the equivalent to maps + case kudoapi.MapValueType: + return "object" + + // All other types are defined as strings + default: + return "string" + } +} + +func buildParamSchema(p packages.Parameter) *jsonSchema { + param := newSchema() + + if p.DisplayName != "" { + param.Title = p.DisplayName + } + if p.Description != "" { + param.Description = p.Description + } + if p.HasDefault() { + param.Default = p.Default + } + param.Type = jsonSchemaTypeFromKudoType(p.Type) + if p.IsImmutable() { + param.Immutable = true + } + if p.IsAdvanced() { + param.Advanced = true + } + if p.Hint != "" { + param.Hint = p.Hint + } + param.ListName = p.Name + + return param +} + +func WriteJSONSchema(pkg *packages.Package, outputType output.Type, out io.Writer) error { + root := newSchema() + topLevelGroups := buildTopLevelGroups(buildGroups(pkg)) + + root.Properties = topLevelGroups + root.Type = "object" + root.Description = "All parameters for this operator" + root.Title = fmt.Sprintf("Parameters for %s", pkg.Files.Operator.Name) + + for _, p := range pkg.Files.Params.Parameters { + param := buildParamSchema(p) + + // Assign to correct parent + parent := topLevelGroups[p.Group] + if parent == nil { + parent = root + } + + if p.IsRequired() { + parent.Required = append(parent.Required, p.Name) + } + + parent.Properties[p.Name] = param + } + + buf := new(bytes.Buffer) + err := output.WriteObject(root, output.TypeJSON, buf) + if err != nil { + return err + } + + g, err := ioutil.ReadFile("pkg/kudoctl/packages/convert/fullschema.json") + if err != nil { + return fmt.Errorf("failed to read thingy: %v", err) + } + + s := &jsonschema.Schema{} + if err := json.Unmarshal(g, s); err != nil { + return fmt.Errorf("failed to unmarshal json-schema: %v", err) + } + + var doc interface{} + if err := json.Unmarshal(buf.Bytes(), &doc); err != nil { + return fmt.Errorf("error parsing JSON bytes: %s", err.Error()) + } + + fmt.Printf("Start validating...\n") + vs := s.Validate(context.TODO(), doc) + errs := *vs.Errs + if len(errs) > 0 { + for _, e := range errs { + fmt.Printf("Error: %v\n", e) + } + return fmt.Errorf("failed to validate json schema") + } + + return output.WriteObject(root, outputType, out) +} From d4fbc5f7436a28a526b20ad7ff110106d5824ea9 Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Thu, 22 Oct 2020 10:32:01 +0200 Subject: [PATCH 2/9] Add new fields to OperatorVersion, generate json-schema from OV not the package format Signed-off-by: Andreas Neumann --- .../kudo/v1beta1/operatorversion_types.go | 3 +- pkg/apis/kudo/v1beta1/parameter_types.go | 11 +++ .../kudo/v1beta1/parameter_types_helpers.go | 4 + pkg/kudoctl/cmd/package_list_params.go | 8 +- pkg/kudoctl/packages/convert/json-schema.go | 25 +++--- pkg/kudoctl/packages/convert/parameters.go | 83 +++++++++++++++++++ pkg/kudoctl/packages/convert/resources.go | 18 ++++ pkg/util/convert/convert.go | 5 ++ 8 files changed, 140 insertions(+), 17 deletions(-) diff --git a/pkg/apis/kudo/v1beta1/operatorversion_types.go b/pkg/apis/kudo/v1beta1/operatorversion_types.go index 29e2d9772..cda009ff3 100644 --- a/pkg/apis/kudo/v1beta1/operatorversion_types.go +++ b/pkg/apis/kudo/v1beta1/operatorversion_types.go @@ -32,7 +32,8 @@ type OperatorVersionSpec struct { // List of all tasks available in this OperatorVersion. Tasks []Task `json:"tasks,omitempty"` - Parameters []Parameter `json:"parameters,omitempty"` + Groups []ParameterGroup `json:"groups,omitempty"` + Parameters []Parameter `json:"parameters,omitempty"` // Plans maps a plan name to a plan. // +nullable diff --git a/pkg/apis/kudo/v1beta1/parameter_types.go b/pkg/apis/kudo/v1beta1/parameter_types.go index b1de4ddbc..797238701 100644 --- a/pkg/apis/kudo/v1beta1/parameter_types.go +++ b/pkg/apis/kudo/v1beta1/parameter_types.go @@ -33,4 +33,15 @@ type Parameter struct { // 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"` + + Group string `json:"group,omitempty"` + Advanced *bool `json:"advanced,omitempty"` + Hint string `json:"hint,omitempty"` +} + +type ParameterGroup struct { + Name string `json:"name,omitempty"` + DisplayName string `json:"displayName,omitempty"` + Description string `json:"description,omitempty"` + Priority int `json:"prio,omitempty"` } diff --git a/pkg/apis/kudo/v1beta1/parameter_types_helpers.go b/pkg/apis/kudo/v1beta1/parameter_types_helpers.go index 79a45e876..bb99199da 100644 --- a/pkg/apis/kudo/v1beta1/parameter_types_helpers.go +++ b/pkg/apis/kudo/v1beta1/parameter_types_helpers.go @@ -16,6 +16,10 @@ 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 } diff --git a/pkg/kudoctl/cmd/package_list_params.go b/pkg/kudoctl/cmd/package_list_params.go index bd193de0d..c1ba33656 100644 --- a/pkg/kudoctl/cmd/package_list_params.go +++ b/pkg/kudoctl/cmd/package_list_params.go @@ -134,9 +134,13 @@ func (c *packageListParamsCmd) run(settings *env.Settings) error { switch c.Format { case outputFormatList: - return output.WriteObject(pr.OperatorVersion.Spec.Parameters, c.Output, c.out) + paramFile, err := packageconvert.ResourcesToParamFile(pr) + if err != nil { + return err + } + return output.WriteObject(paramFile, c.Output, c.out) case outputFormatJSONSchema: - return packageconvert.WriteJSONSchema(nil, c.Output, c.out) + return packageconvert.WriteJSONSchema(pr.OperatorVersion, c.Output, c.out) default: return displayParamsTable(pr.OperatorVersion.Spec.Parameters, c.out, c.requiredOnly, c.namesOnly, c.descriptions) } diff --git a/pkg/kudoctl/packages/convert/json-schema.go b/pkg/kudoctl/packages/convert/json-schema.go index 530b9bb6e..5ff08d380 100644 --- a/pkg/kudoctl/packages/convert/json-schema.go +++ b/pkg/kudoctl/packages/convert/json-schema.go @@ -12,7 +12,6 @@ import ( kudoapi "github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1" "github.com/kudobuilder/kudo/pkg/kudoctl/cmd/output" - "github.com/kudobuilder/kudo/pkg/kudoctl/packages" ) type jsonSchema struct { @@ -37,17 +36,15 @@ func newSchema() *jsonSchema { } } -func buildGroups(pkg *packages.Files) map[string]packages.Group { - params := pkg.Params - - groups := map[string]packages.Group{} - for _, g := range params.Groups { +func buildGroups(ov *kudoapi.OperatorVersion) map[string]kudoapi.ParameterGroup { + groups := map[string]kudoapi.ParameterGroup{} + for _, g := range ov.Spec.Groups { groups[g.Name] = g } - for _, p := range params.Parameters { + for _, p := range ov.Spec.Parameters { if p.Group != "" { if _, ok := groups[p.Group]; !ok { - groups[p.Group] = packages.Group{ + groups[p.Group] = kudoapi.ParameterGroup{ Name: p.Group, } } @@ -57,7 +54,7 @@ func buildGroups(pkg *packages.Files) map[string]packages.Group { return groups } -func buildTopLevelGroups(groups map[string]packages.Group) map[string]*jsonSchema { +func buildTopLevelGroups(groups map[string]kudoapi.ParameterGroup) map[string]*jsonSchema { topLevelGroups := map[string]*jsonSchema{} for _, v := range groups { @@ -101,7 +98,7 @@ func jsonSchemaTypeFromKudoType(parameterType kudoapi.ParameterType) string { } } -func buildParamSchema(p packages.Parameter) *jsonSchema { +func buildParamSchema(p kudoapi.Parameter) *jsonSchema { param := newSchema() if p.DisplayName != "" { @@ -128,16 +125,16 @@ func buildParamSchema(p packages.Parameter) *jsonSchema { return param } -func WriteJSONSchema(pkg *packages.Files, outputType output.Type, out io.Writer) error { +func WriteJSONSchema(ov *kudoapi.OperatorVersion, outputType output.Type, out io.Writer) error { root := newSchema() - topLevelGroups := buildTopLevelGroups(buildGroups(pkg)) + topLevelGroups := buildTopLevelGroups(buildGroups(ov)) root.Properties = topLevelGroups root.Type = "object" root.Description = "All parameters for this operator" - root.Title = fmt.Sprintf("Parameters for %s", pkg.Operator.Name) + root.Title = fmt.Sprintf("Parameters for %s", ov.Name) - for _, p := range pkg.Params.Parameters { + for _, p := range ov.Spec.Parameters { param := buildParamSchema(p) // Assign to correct parent diff --git a/pkg/kudoctl/packages/convert/parameters.go b/pkg/kudoctl/packages/convert/parameters.go index a715d5218..120008425 100644 --- a/pkg/kudoctl/packages/convert/parameters.go +++ b/pkg/kudoctl/packages/convert/parameters.go @@ -8,6 +8,86 @@ import ( utilconvert "github.com/kudobuilder/kudo/pkg/util/convert" ) +func ParameterGroupsToPackageType(groups []kudoapi.ParameterGroup) packages.Groups { + if len(groups) == 0 { + return nil + } + + result := make([]packages.Group, 0, len(groups)) + + for _, group := range groups { + result = append(result, packages.Group{ + Name: group.Name, + DisplayName: group.DisplayName, + Description: group.Description, + }) + } + + return result +} + +func ParameterGroupsToCRDType(groups packages.Groups) []kudoapi.ParameterGroup { + if len(groups) == 0 { + return nil + } + result := make([]kudoapi.ParameterGroup, 0, len(groups)) + + for _, group := range groups { + result = append(result, kudoapi.ParameterGroup{ + Name: group.Name, + DisplayName: group.DisplayName, + Description: group.Description, + }) + } + + return result +} + +func ParametersToPackageType(parameters []kudoapi.Parameter) (packages.Parameters, error) { + result := make([]packages.Parameter, 0, len(parameters)) + + for _, parameter := range parameters { + var defaultVal interface{} = nil + if parameter.HasDefault() { + var err error + defaultVal, err = utilconvert.UnwrapParamValue(parameter.Default, parameter.Type) + if err != nil { + return nil, fmt.Errorf("failed to convert %s default for parameter '%s': %w", parameter.Type, parameter.Name, err) + } + } + var enumValues *[]interface{} + if parameter.IsEnum() { + var ev []interface{} + for _, v := range parameter.EnumValues() { + v := v + vUnwrapped, err := utilconvert.UnwrapParamValue(&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, vUnwrapped) + } + enumValues = &ev + } + + result = append(result, packages.Parameter{ + DisplayName: parameter.DisplayName, + Name: parameter.Name, + Description: parameter.Description, + Required: parameter.Required, + Advanced: parameter.Advanced, + Hint: parameter.Hint, + Group: parameter.Group, + Default: defaultVal, + Trigger: parameter.Trigger, + Type: parameter.Type, + Immutable: parameter.Immutable, + Enum: enumValues, + }) + } + + return result, nil +} + // ParametersToCRDType converts parameters to an array of 'Parameter' defined in the KUDO API. func ParametersToCRDType(parameters packages.Parameters) ([]kudoapi.Parameter, error) { result := make([]kudoapi.Parameter, 0, len(parameters)) @@ -36,6 +116,9 @@ func ParametersToCRDType(parameters packages.Parameters) ([]kudoapi.Parameter, e Name: parameter.Name, Description: parameter.Description, Required: parameter.Required, + Advanced: parameter.Advanced, + Hint: parameter.Hint, + Group: parameter.Group, Default: d, Trigger: parameter.Trigger, Type: parameter.Type, diff --git a/pkg/kudoctl/packages/convert/resources.go b/pkg/kudoctl/packages/convert/resources.go index 456d1a023..88d6967a8 100644 --- a/pkg/kudoctl/packages/convert/resources.go +++ b/pkg/kudoctl/packages/convert/resources.go @@ -14,6 +14,21 @@ import ( "github.com/kudobuilder/kudo/pkg/util/kudo" ) +func ResourcesToParamFile(resources *packages.Resources) (*packages.ParamsFile, error) { + groups := ParameterGroupsToPackageType(resources.OperatorVersion.Spec.Groups) + params, err := ParametersToPackageType(resources.OperatorVersion.Spec.Parameters) + if err != nil { + return nil, err + } + + result := &packages.ParamsFile{ + APIVersion: packages.APIVersion, + Groups: groups, + Parameters: params, + } + return result, nil +} + func FilesToResources(files *packages.Files) (*packages.Resources, error) { if files.Operator == nil { return nil, errors.New("operator.yaml file is missing") @@ -54,6 +69,8 @@ func FilesToResources(files *packages.Files) (*packages.Resources, error) { return nil, err } + groups := ParameterGroupsToCRDType(files.Params.Groups) + fv := &kudoapi.OperatorVersion{ TypeMeta: metav1.TypeMeta{ Kind: "OperatorVersion", @@ -72,6 +89,7 @@ func FilesToResources(files *packages.Files) (*packages.Resources, error) { Templates: files.Templates, Tasks: files.Operator.Tasks, Parameters: parameters, + Groups: groups, Plans: files.Operator.Plans, UpgradableFrom: nil, }, diff --git a/pkg/util/convert/convert.go b/pkg/util/convert/convert.go index 5a309ac8b..bae56d81c 100644 --- a/pkg/util/convert/convert.go +++ b/pkg/util/convert/convert.go @@ -2,6 +2,7 @@ package convert import ( "fmt" + "strconv" "sigs.k8s.io/yaml" @@ -30,6 +31,10 @@ func UnwrapParamValue(wrapped *string, parameterType kudoapi.ParameterType) (unw unwrapped, err = ToYAMLMap(StringValue(wrapped)) case kudoapi.ArrayValueType: unwrapped, err = ToYAMLArray(StringValue(wrapped)) + case kudoapi.IntegerValueType: + unwrapped, err = strconv.ParseInt(StringValue(wrapped), 10, 32) + case kudoapi.NumberValueType: + unwrapped, err = strconv.ParseFloat(StringValue(wrapped), 64) case kudoapi.StringValueType: fallthrough default: From 662e4ed99ed61928483ecb64c5b5793b9f87af6d Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Thu, 22 Oct 2020 11:59:30 +0200 Subject: [PATCH 3/9] Correctly convert enum values Make sure topLevelGroups are required if they have required properties without defaults Updated generated files Signed-off-by: Andreas Neumann --- config/crds/kudo.dev_operatorversions.yaml | 19 +++++ .../kudo/v1beta1/zz_generated.deepcopy.go | 26 +++++++ pkg/kudoctl/kudoinit/crd/bindata.go | 2 +- pkg/kudoctl/packages/convert/json-schema.go | 71 +++++++++++++++++-- pkg/util/convert/convert.go | 2 + 5 files changed, 114 insertions(+), 6 deletions(-) diff --git a/config/crds/kudo.dev_operatorversions.yaml b/config/crds/kudo.dev_operatorversions.yaml index a7327fb5e..ef14f1b0e 100644 --- a/config/crds/kudo.dev_operatorversions.yaml +++ b/config/crds/kudo.dev_operatorversions.yaml @@ -35,6 +35,19 @@ spec: connectionString: description: ConnectionString defines a templated string that can be used to connect to an instance of the Operator. type: string + groups: + items: + properties: + description: + type: string + displayName: + type: string + name: + type: string + prio: + type: integer + type: object + type: array operator: description: 'ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don''t make new APIs embed an underspecified API type they do not control. Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .' properties: @@ -64,6 +77,8 @@ spec: items: description: Parameter captures the variability of an OperatorVersion being instantiated in an instance. properties: + advanced: + type: boolean default: description: Default is a default value if no parameter is provided by the instance. type: string @@ -78,6 +93,10 @@ spec: items: type: string type: array + group: + type: string + hint: + type: string 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/zz_generated.deepcopy.go b/pkg/apis/kudo/v1beta1/zz_generated.deepcopy.go index 0e2b9b921..fa37a68fa 100644 --- a/pkg/apis/kudo/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/kudo/v1beta1/zz_generated.deepcopy.go @@ -371,6 +371,11 @@ func (in *OperatorVersionSpec) DeepCopyInto(out *OperatorVersionSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]ParameterGroup, len(*in)) + copy(*out, *in) + } if in.Parameters != nil { in, out := &in.Parameters, &out.Parameters *out = make([]Parameter, len(*in)) @@ -446,6 +451,11 @@ func (in *Parameter) DeepCopyInto(out *Parameter) { copy(*out, *in) } } + if in.Advanced != nil { + in, out := &in.Advanced, &out.Advanced + *out = new(bool) + **out = **in + } return } @@ -459,6 +469,22 @@ func (in *Parameter) DeepCopy() *Parameter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ParameterGroup) DeepCopyInto(out *ParameterGroup) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParameterGroup. +func (in *ParameterGroup) DeepCopy() *ParameterGroup { + if in == nil { + return nil + } + out := new(ParameterGroup) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Phase) DeepCopyInto(out *Phase) { *out = *in diff --git a/pkg/kudoctl/kudoinit/crd/bindata.go b/pkg/kudoctl/kudoinit/crd/bindata.go index 5e5f64f8a..8899bec83 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\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") +var _configCrdsKudoDev_operatorversionsYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x3c\x6b\x8f\x1b\x37\x92\xdf\xe7\x57\x14\x26\x1f\x9c\x04\xa3\xd6\x3a\xd9\xc5\x1d\xe6\x5b\xd6\x4e\x0e\x73\xf1\xda\x86\x1f\x7b\x38\xe4\x02\x0c\xd5\x2c\xa9\xb9\xc3\x26\x79\x7c\x48\xd6\x05\xf9\xef\x87\x2a\xb2\x1f\xd2\xe8\xd1\x33\x4e\x72\x3e\xac\xf5\xc5\x56\x37\x59\x6f\xd6\x8b\x35\xba\x98\xcd\x66\x17\xc2\xa9\xbf\xa3\x0f\xca\x9a\x6b\x10\x4e\xe1\x87\x88\x86\xbe\x85\xea\xee\x5f\x43\xa5\xec\x7c\xfd\x74\x81\x51\x3c\xbd\xb8\x53\x46\x5e\xc3\xb3\x14\xa2\x6d\xdf\x60\xb0\xc9\xd7\xf8\x1c\x97\xca\xa8\xa8\xac\xb9\x68\x31\x0a\x29\xa2\xb8\xbe\x00\x10\xc6\xd8\x28\xe8\x71\xa0\xaf\x00\xb5\x35\xd1\x5b\xad\xd1\xcf\x56\x68\xaa\xbb\xb4\xc0\x45\x52\x5a\xa2\x67\x0c\x1d\xfe\xf5\x9f\xaa\x3f\x57\x7f\xba\x00\xa8\x3d\xf2\xf6\x77\xaa\xc5\x10\x45\xeb\xae\xc1\x24\xad\x2f\x00\x8c\x68\xf1\x1a\xac\x43\x2f\xa2\xf5\x65\x67\xa8\xee\x92\xb4\x95\xc4\xf5\x45\x70\x58\x13\xce\x95\xb7\xc9\x5d\x43\xff\x3c\xef\x2c\xe4\x64\x56\x5e\x15\x20\x85\x7d\x7e\xa3\x55\x88\x3f\x1e\x7a\xfb\x42\x85\xc8\x2b\x9c\x4e\x5e\xe8\xfb\x24\xf0\xcb\xa0\xcc\x2a\x69\xe1\xef\xbd\xbe\x00\x08\xb5\x75\x78\x0d\x2f\x89\x0c\x27\x6a\x94\x17\x00\x6b\xa1\x95\x64\x4e\x33\x61\xd6\xa1\xf9\xee\xf5\xcd\xdf\xbf\x7d\x5b\x37\xd8\x8a\xfc\x10\x40\x62\xa8\xbd\x72\xbc\x6e\x9f\x30\x50\x01\x62\x83\x90\x77\xc0\xd2\x7a\xfe\xba\x4f\x1e\x7c\xf7\xfa\xa6\x2a\xe0\x9c\xa7\xb7\x51\x75\xe2\xa0\xcf\xc8\x0c\xfa\x67\x7b\x88\x9f\x10\x65\x05\xa9\x24\xc5\x63\xc6\x5c\x50\xa0\x84\x90\x69\xb0\x4b\x88\x8d\x0a\xe0\xd1\x79\x0c\x68\xb2\x29\xd0\x63\x61\xc0\x2e\xfe\x81\x75\xac\xe0\x2d\x32\x6d\x10\x1a\x9b\xb4\x24\x0b\x59\xa3\x8f\xe0\xb1\xb6\x2b\xa3\xfe\xa7\x87\x16\x20\x5a\x46\xa3\x45\xc4\x10\x41\x99\x88\xde\x08\x4d\xb2\x4b\x78\x05\xc2\x48\x68\xc5\x16\x3c\x12\x5c\x48\x66\x04\x81\x97\x84\x0a\xfe\x66\x3d\x82\x32\x4b\x7b\x0d\x4d\x8c\x2e\x5c\xcf\xe7\x2b\x15\x3b\x03\xaf\x6d\xdb\x26\xa3\xe2\x76\xce\x66\xaa\x16\x29\x5a\x1f\xe6\x12\xd7\xa8\xe7\x41\xad\x66\xc2\xd7\x8d\x8a\x58\xc7\xe4\x71\x2e\x9c\x9a\x31\xb1\x86\xed\xbb\x6a\xe5\x17\xbe\x9c\x86\xf0\x64\x24\xba\xb8\x25\x6d\x87\xe8\x95\x59\xf5\x8f\xd9\xf0\x8e\xca\x97\x0c\x8f\xd4\x29\xca\xb6\x4c\xff\x20\x46\x7a\x44\x92\x78\xf3\xfd\xdb\x77\xd0\x21\xcd\xa2\xce\x52\x1d\x96\x86\x41\xc0\x24\x1c\x65\x96\xe8\xf3\xca\xa5\xb7\x2d\x43\x41\x23\x9d\x55\x26\xf2\x97\x5a\x2b\x34\x11\x42\x5a\xb4\x2a\x92\xe6\xfe\x3b\x61\x88\x24\xfb\x0a\x9e\xf1\x71\x86\x05\x42\x72\x52\x44\x94\x15\xdc\x18\x78\x26\x5a\xd4\xcf\x44\xc0\xdf\x5d\xbc\x24\xc9\x30\x23\xd1\x9d\x17\xf0\xd8\x0b\xed\x2e\xcc\x12\xea\x1f\x77\x7e\xe2\xa0\x26\xf6\x8e\xd8\x5b\x87\xf5\x8e\xc5\x4b\x0c\xca\x93\x85\x46\x11\x91\xec\x7a\x6f\x43\x35\x02\x7c\xe8\xb0\xe5\x03\xe7\x0e\x1c\xb8\xa3\x8c\x15\x2f\x6a\xb0\x26\x12\xdf\xf2\xcb\xfd\x8d\x3b\x3c\x3c\xdb\x5b\xdc\x33\x20\x20\x62\xeb\xe8\x34\xc9\xce\xce\x62\x23\x22\xd4\xc2\xb0\x8e\x03\x4a\x3a\x72\x05\x19\xfd\x57\x18\x50\x26\x44\x61\x6a\xcc\x67\x1b\x7b\x86\xab\xa9\xb4\xb3\x3f\x0e\xfb\x14\xab\x88\xed\xbd\x87\xc7\x65\x76\x9f\xcd\x03\xaf\x4f\x10\x51\xf6\xab\xe0\xb4\xd8\x92\x23\x7e\xd4\x7e\xf3\xd8\x8d\xce\x2b\x7b\x6a\x23\xb9\xb6\x15\xfa\x7b\x2b\x0e\x1a\xf0\xf8\x95\xf0\x5e\x6c\x77\xde\x74\xee\xff\xa4\x89\x3c\x79\xc5\x20\xdf\xe0\x12\x3d\x92\x72\xe9\x80\x0a\x65\x02\xa0\xb1\x69\xd5\xf0\x99\xf6\x6d\xf6\xde\xd1\x82\xc6\x08\x5b\x9b\xc8\x18\x1c\x99\x86\xf5\xd0\x5a\xa9\x96\x5b\x36\x09\x4f\x60\xe8\x54\x74\x1e\x7e\x36\x9b\xc1\x4b\xdc\x90\x45\x85\x3e\x26\x10\xc1\x20\x3c\x92\x16\x6a\x9b\xbc\x58\xa1\x84\x05\xd6\x22\x05\x36\x2e\xa9\x96\x4b\x55\x27\x1d\xb7\x85\xd6\x05\x19\x28\xf9\xa4\x14\xc4\x0a\x61\xd3\xa0\x01\x6c\x17\x28\x25\x4a\x50\x86\xe2\x5a\xa8\x00\x9e\x56\x70\xb3\x32\x96\xf0\x2f\x15\x6a\x49\xcf\x6e\x28\x5a\xd4\x3a\x49\x24\x2f\x68\xb6\xe5\x0d\x6c\x1a\x55\x37\x4c\x04\xf9\xb5\x15\x1a\xf4\x42\xeb\x2d\x34\x96\x01\x54\x00\x3f\x58\xdf\x9b\xfc\x15\x74\xd9\x4e\x17\xfc\x28\xe4\xfc\x40\xa0\x5e\x8b\x98\xe1\x2c\x6c\x6c\x28\x0e\x6e\xc1\x0b\x8f\x7a\x9b\x03\x3b\x91\x27\xea\x98\x84\xce\xc4\x57\x00\xdf\x90\xef\xcc\x2f\x33\x3f\x0d\x6a\x57\x48\x0d\xa0\x5a\x67\x43\x50\x0b\x8d\x7c\xec\xa4\x64\x37\xa5\x96\xaa\xe6\x75\x1c\xdc\x95\x91\x6a\xad\xe4\x18\xe8\x8d\x81\xd6\x86\x38\x88\x85\x5f\x84\x2b\x52\x8b\xcf\xd2\x76\xc2\x47\x12\xab\xf0\x6c\x06\x1e\xc9\x48\xd9\x3b\x04\xd0\xea\x0e\xaf\xe0\xb2\x4d\x21\x66\x25\x82\x35\x7a\xcb\x61\x97\x3c\x2f\x7c\xc7\x0c\xff\xf5\x92\xf4\x7d\xf9\xfe\xe6\x39\x4b\xad\xc8\x2a\x3f\xa4\x13\x01\xbc\x7f\x81\x3d\x6c\x94\x97\x15\x23\x7b\xd7\xd8\x80\xe4\x5e\x4a\x14\xd9\xa0\xd6\x9d\x72\x51\xee\x6a\xb4\x02\xf8\x96\x44\x54\x5b\x13\x54\x88\x14\x93\x86\x1c\xa9\x02\xf8\x6b\xb1\x14\x32\xb8\xcc\x65\x31\xa6\x25\xdb\x70\xbc\xca\x19\x49\xbf\x05\x7c\xd2\xfb\x6b\x60\xb1\xcd\x7b\xaf\x8a\x25\xb4\xe2\x0e\x03\xa8\x08\x8d\xf0\x92\x85\x9c\x02\x45\xce\x68\xc1\x79\x94\xaa\x8e\xb0\x21\x0f\xb9\x51\x5a\x43\x23\x9c\x43\x22\xe5\xcf\x15\xbc\x6b\xb0\xb3\xa9\xde\x0a\x54\xeb\x3c\xd6\x2a\x20\x4b\xcd\xae\xd1\xeb\x2d\x94\x47\x15\x40\x17\xe3\x49\x16\xa2\x7b\x0e\xad\x70\x8e\x1d\xb1\x05\x01\xef\xdf\xbc\x20\xd0\x2a\xb0\x4b\x76\xde\xca\x54\x23\x88\x76\xa1\x56\x49\xc5\x6d\x3e\xc7\x89\x1d\x37\x27\x43\xce\x63\xc9\xb0\x08\x23\x85\x6e\x45\x5a\xcf\x69\x42\x81\x3c\xb2\x92\x5a\x84\x62\x1b\x20\xd1\xa1\x91\x68\xea\x2d\x91\x44\x87\xbc\xc1\xec\xa9\xaf\x86\xf4\x22\x39\x8d\x8c\x93\xa0\x8f\xf2\xbd\x2e\x14\x14\x0b\x0f\xd1\xa7\x3a\x5b\xb1\xf7\xa8\x71\x2d\x4c\xac\x00\xfe\x52\xc1\x7f\xf4\xca\x47\x11\x94\xde\x42\xdd\x08\xb3\x42\x50\x71\x47\xa1\x9d\x73\x50\x61\xe7\x7c\xf3\xc1\xd5\xb6\xce\xe5\xc4\x55\xc9\x41\x4a\x56\xd8\xed\xa1\x0f\x6b\x47\x2c\x97\xe4\x99\x4c\x6a\xd1\xdb\x14\xba\x1c\xb2\x02\x78\x6e\xcd\x93\x27\x91\x75\x0d\x06\x37\xec\x37\x32\x22\x8a\x6f\xc9\x48\xf4\xe5\xb0\xa1\xa4\x97\x19\x70\x6c\x70\x0b\xd2\xb2\xba\x4a\x11\x43\xe6\x19\x22\x0a\x49\x02\x48\x21\xc7\xcf\x42\xc8\x55\xae\x5c\x48\xfa\x44\xb2\x66\xd5\xdb\xb5\x92\x8c\x45\x96\xe0\x9a\x01\x0b\x16\x16\x1d\x86\xd9\xd2\xd6\xfc\xc6\x1a\xf2\xaf\x3e\x9f\x42\xf2\xc8\x15\x7b\x22\xfc\x20\x5a\xa7\xf1\x8a\x53\x3a\x55\x63\xef\xb0\x03\x1b\xab\x90\xad\x0a\xac\x11\x8f\x2b\x15\xa2\xcf\x05\xc5\x38\x17\x6b\xd2\xa2\xaa\x6d\x3b\xa7\xc2\xcb\x1b\x8c\x18\x28\xd1\x9a\x2f\xb4\x5d\xcc\x49\x59\x22\xe0\xec\x69\xf5\xf4\x5f\xe6\x3d\xac\x31\xa8\xf9\xfa\xe9\x9c\x5d\x41\xb5\xb2\x5f\xbc\xf8\xcb\xb7\xdf\x42\xf5\xe4\x62\x3f\xb4\x1d\x8f\xd8\x87\x4b\x8b\x83\x11\x89\xe4\xbe\x67\x5e\x45\x16\x71\x3f\xd3\x80\x73\xf1\x76\xd9\x79\xe8\xb3\x58\x9f\xdc\x2c\x4b\xf4\xea\xcf\xa0\x53\x98\x93\x9d\xbe\x62\xe1\x78\x50\xb4\x2e\x0c\x50\x7e\xea\xb1\xbc\xbb\xca\x16\x50\xb2\xa9\xa1\xa2\xa1\x60\x0a\xa2\x04\x83\x7f\x7f\xfb\xea\xe5\xfc\xdf\x6c\xa6\x0b\x44\x5d\x63\x08\x39\x83\x6c\xd9\x71\x85\x44\x41\x29\x74\xc9\xe5\x5b\x7a\x53\xb5\xc2\xa8\x25\x86\x58\x15\x68\xe8\xc3\x4f\xdf\xfc\xbc\x67\x16\x2a\x4b\xaa\xaf\x01\xba\x70\xae\x42\x66\xa6\xdf\x0b\x1b\x15\x1b\x26\xc9\x59\x59\x88\xde\x30\xb1\x91\x8e\x85\x2d\xc4\x26\xe4\x98\x70\x0d\x97\x74\x22\x46\xa8\x7f\x21\x47\xff\xeb\x25\x7c\xb9\xe1\xc0\xc2\x7e\xff\x32\x23\xec\xcb\x34\x8e\x05\x45\x77\x03\x62\x36\xf7\xe8\xd5\x6a\x85\x14\xa2\xb9\xfe\xa0\x1c\xff\x2b\x8a\x1f\x6a\x09\xc6\x8e\x16\x33\x08\x92\x67\x7f\x1e\xf7\x09\xf9\xe9\x9b\x9f\x2f\xe1\xcb\x5d\xbe\x28\x32\xe2\x07\xf8\x86\x9c\x06\x73\xe6\xac\xfc\xaa\x38\xd2\xb0\x35\x51\x7c\x20\x98\x35\x05\x23\xd3\x47\xb8\x46\xac\x11\x82\x6d\x73\x54\x9a\xe5\x1c\x59\xc2\x46\x6c\x89\x87\x4e\x94\xa4\x55\xc1\x31\x74\xaf\x88\x7d\xf7\xea\xf9\xab\xeb\x8c\x8d\xd4\xb6\x32\x9d\x6b\x5f\x2a\x2a\x51\xb3\xc7\xa4\xa2\x8b\x75\x4e\x84\xa4\xac\x24\x4a\xb0\x8b\x17\xcc\x5e\x77\x99\xa8\xfc\xb9\x77\xae\xce\x5a\xf9\x7e\x45\x79\xd8\xc0\x39\xea\xec\x1f\xa8\xff\xa3\xba\x6d\x12\x5b\xc7\x72\xec\x5d\xb6\x5e\x8e\x6c\xed\x24\x5b\x83\xdf\x23\xce\xa4\xad\x03\x31\x55\xa3\x8b\x61\x4e\x21\x7a\xad\x70\x33\xdf\x58\x7f\xa7\xcc\x6a\x46\xc6\x34\xcb\x1a\x0e\x73\xee\x1a\xcd\xbf\xe0\x7f\x1e\xc5\x05\xf7\x7a\xa6\xb1\xc2\x4b\xff\x08\x7e\x08\x4f\x98\x3f\x98\x1d\xbf\x9b\x07\x9f\x67\xea\x6d\x97\xbd\xee\xed\x24\xf3\xcf\xa9\x57\xe9\x13\x8d\x3c\x56\x2b\x64\x76\x69\xc2\x6c\x7f\x77\x13\x25\xa1\x25\x4f\xb8\xb7\xb3\x12\xde\x67\xc2\xc8\x59\x9f\x7e\xd6\xdb\x07\x4b\x29\xa9\x09\x07\x92\xd2\xe8\x3f\xc4\x70\x93\x7a\xf0\xe9\x3b\x5a\x6e\x3a\xe1\x45\x8b\x11\xfd\xd4\x3a\x7e\x87\xe7\xd7\xdd\x6e\xa8\x85\x23\x85\x94\xee\xa1\xf0\x4a\x2c\x94\xa6\xcc\x36\x3b\xd7\xfd\x06\xe7\x02\x73\xaa\x4b\xe5\x58\x54\xdc\xb7\xa0\x18\x36\x34\x25\xee\xa7\x06\xa7\x1b\x08\x42\xae\x69\xdb\x41\x35\x75\xec\x2f\xac\xd5\x28\xcc\x41\x45\x2e\x45\xd2\xf1\xf0\xe6\x1d\x8e\x9f\xe7\x95\xb9\xa5\x57\xb6\x95\xf8\x9a\x43\x5e\x2f\x50\x5a\xd2\x27\x8a\x8b\x5c\x4f\x1f\xe7\x0e\x26\xf4\x38\xce\xf5\x48\xf6\x08\xed\xbf\x0c\xca\xa1\x14\xd6\xac\xd0\x8f\x97\x92\x86\x1a\xbb\x61\xfa\x06\xe2\x39\xf3\x2e\xad\xa3\xc7\x51\x7b\xae\x23\xb3\x4b\xed\xb0\x7a\xa7\x69\xb5\xd8\xc2\xfb\x9b\xf0\x28\x02\xd0\xa4\x76\x9a\x42\x4b\x0b\x4d\xab\x90\x73\x01\xad\xed\x66\xd4\x69\xbe\x59\x8e\xb5\x1e\x30\x72\x0e\xf0\xbd\x49\x6d\x97\x19\x18\xa5\xfb\x22\x35\x0d\x55\x73\x97\xb4\x30\x60\x91\xeb\x82\x83\x04\x1d\x39\x6a\x93\xd8\x3c\xde\x2f\xca\x9f\x7c\x69\xf2\x18\x01\x36\xca\x1c\x39\x11\x67\x36\xaa\xb6\x4d\x51\x2c\xf4\x14\xc5\x97\x70\x42\xb5\xfa\x72\xcf\x04\x8b\x1d\xe4\x9c\x4a\x82\x58\x46\xf4\xe5\x14\xa9\xa8\x84\xce\xa7\x49\xeb\xfe\x26\x62\x7c\x47\xf2\x28\x37\x70\xbc\x0b\xb8\x43\xf3\xe5\xcb\x92\xcc\x12\xc2\x71\xbb\xb5\x54\x09\x9d\xf1\x96\x34\xb0\x6b\xcd\xc2\x52\x69\xe4\xea\x6e\x9c\xe5\xdf\xe6\xdb\xaf\x67\xaf\xde\xbf\x7c\x77\x4b\xeb\x4d\x5f\x80\x76\x8e\x54\xb3\x29\x09\xce\x9d\x4b\x16\xff\x5f\x26\x77\xbb\x39\x82\x3b\xad\x6a\x11\xae\x01\x7e\xf9\x05\x2a\x76\xc9\xa1\x62\x78\xf0\xeb\xaf\x97\x8f\x51\x60\xe9\x37\x1c\x71\xa6\x3b\xb2\x78\x53\x96\xf6\x89\xfd\x01\x45\xaa\xd0\x43\xa4\x2c\x61\x81\x3b\x7e\x51\x68\xdd\xfb\xc5\x70\x45\xb5\xc3\xa6\xc1\xd8\xa0\x1f\x39\x58\x32\x85\x90\x96\x4b\x75\xda\x75\x1e\xd7\x6c\x29\x53\x26\x30\xf4\x2e\xaf\x04\x25\x29\xa7\x60\x86\x98\x1b\x2d\x4c\x56\xf2\x0a\x63\x00\xfc\x80\x75\x8a\x5d\xa7\x2b\x97\x26\x83\xe1\xb2\xc5\x86\x4e\xff\x37\x7d\x9f\xbd\x54\x18\x23\x6f\x72\x9b\x5b\x1f\xb7\x9c\x1c\x65\x24\x5c\xf7\x30\x26\xae\x99\xf0\x83\x0a\x91\xe4\x42\x22\xd9\xa8\x80\xa0\xe2\x93\x00\xb7\x12\x9d\xb6\xdb\xdb\x47\xb9\x46\x76\x53\x33\x5e\x34\x41\x20\x5b\x87\x23\xed\x0e\x6e\x8e\xf6\xf7\xcc\x70\xad\x78\x9b\x31\x3e\x86\xa8\x47\xf4\xc3\x49\x5a\xf7\xbc\xa6\x90\x92\xef\xae\x85\x7e\x7d\x22\x5f\xd8\x4d\x62\x48\xea\x03\x83\x02\x02\x7a\x95\xdb\xdb\xaf\x1b\x11\x98\x67\xd2\x06\xf6\xe6\x5b\x5b\x3a\xbe\xf1\x50\x64\x3c\x9d\xa5\x38\x86\x37\x41\xe8\x05\x71\x2b\x1c\x11\xc4\xdb\xb2\x39\x70\xf1\xcf\x6f\x3b\x73\x7a\x78\x44\xb9\x8f\x69\x87\xfd\x2e\x0e\x86\x88\xae\xf0\xde\xf5\x3d\x7e\xec\x53\xd7\x82\xfd\x48\x68\x3e\x27\x87\xfc\x39\xee\x6e\xf3\xe7\x6c\xe4\xa3\x0f\x53\x79\x0a\xca\x6e\xb8\x61\x9e\x8a\x58\x69\xeb\x48\xaa\x1d\xdf\xc3\x55\xda\x7d\x76\x21\x44\xbe\x8f\x10\xc3\x8d\xee\x31\x09\xc0\x39\x35\x1c\x21\x6f\x74\xa9\xd7\xdf\x14\x50\xd2\x61\x97\x25\xb8\x71\x83\x9f\xd5\x62\xeb\x3a\xdd\xbb\xae\xdb\xfd\x4c\xd1\x43\xfe\x9c\xd3\x46\xfe\x4c\xd2\x49\x59\x2a\xc2\xdd\x59\xac\x93\x64\xf4\x60\xd4\xe7\x92\xa2\xfd\x75\x07\xfd\xce\x43\xc1\x71\x6b\x15\x57\xdb\xc9\xc6\xf8\xca\x4b\xcc\xdd\xc5\xfe\xf4\x75\x89\x78\x48\x0b\x96\xcb\xd0\xf8\xd2\xc2\xcc\xb3\x1f\x18\xb2\x0c\x1e\xbf\x91\x60\xd3\x11\x2f\x30\xa6\xfd\xa4\xdc\xce\x4a\xc1\x24\xad\x39\xa3\x83\xe8\x13\x3e\x22\x0d\x3d\x2d\x9c\x3f\x56\x2c\x8f\x0d\x44\xf7\xa2\xc6\xe0\xa0\x29\x84\x0c\x9e\x84\xbe\xee\xa3\x3e\x29\xc1\xa3\x48\x0f\x9e\xa1\x1d\x3a\x5e\x0c\x55\x4b\x5e\x0d\x62\x2d\x94\xee\xb2\x46\x96\xd2\x89\x51\x06\x98\x58\xe9\xbf\x13\xe1\x2e\x17\xbd\x2b\x6d\x17\x42\x5f\x81\xb3\x7a\xdb\x5a\xef\x1a\x55\x83\xa2\x78\xd8\xee\x4c\x05\x69\x0d\x2e\x2d\xb4\xaa\xf5\x76\x44\x11\x53\xf8\xc0\xa0\x79\xac\x1d\xfa\xfb\x5d\xea\xef\x0f\x92\x9c\x90\x09\xcf\x91\xf0\x95\x53\xc8\x3c\xf7\x97\xbd\x24\x30\x02\x14\x4a\xbf\x9a\xcb\xca\x00\x29\x37\xf0\xd7\x56\x49\xd8\x78\xc5\x03\x40\x35\x0f\xe0\x41\x32\xf3\x56\xf8\xd0\x08\xad\xb9\xf9\xce\xf7\x93\x6c\xd4\xdc\xda\x76\xc2\x07\x84\x1a\x3d\x87\xe2\x72\x47\x99\xaf\xfb\x08\x48\xb9\xea\x63\xbc\x3f\x2a\x23\xf3\x55\xa6\xb4\x1b\x13\x94\xc4\xfe\xb2\x5e\x38\xe7\xad\xa8\x1b\x50\x7c\x61\x28\x46\x57\xcc\xf9\x6a\x98\x72\x6d\xbe\x0d\x16\xeb\xfe\x26\xb4\xe4\xa4\x08\x81\x6c\xfc\x1f\xc1\x66\x6b\x0f\x14\x07\x55\x47\xe4\x02\x6b\xdb\x76\x97\x9a\x36\x85\x7e\x6e\xad\xcb\xe2\x99\x01\xcf\x97\x87\xad\x5a\x35\x11\x3c\xae\x55\x50\x71\x9f\xb0\x71\xf7\xbc\x3b\xd6\xbc\xa4\xc3\x60\x40\x85\x90\x8e\x94\x02\xe7\x23\xde\xf1\x31\x9d\x23\x8a\x1e\x45\x62\xe1\x5c\x7f\x9b\x55\x08\xb5\x54\x97\x50\x3d\xea\xd1\xd9\xab\x8e\xdb\xfe\xf2\x84\x2f\x68\x3d\xd6\x68\x8e\x07\x98\x09\x2e\x5a\x5a\x73\x22\x3c\x9f\xab\x7f\xe8\xb3\x14\x51\xe8\x8f\x03\xd1\xd5\x69\xc7\xbb\x3a\x13\xb9\xb1\xbb\x1e\xe9\xe1\x7a\xe8\x00\x3c\x5e\x19\x60\xcd\xe1\x40\x36\x91\x05\x27\xea\x3b\xb1\x3a\x21\x86\x1d\xd2\x51\x71\x49\x4b\x54\x75\x3b\xf9\x08\x5d\xe5\x4b\xe5\xfe\xd9\xd2\x6a\x89\x9e\xaa\x60\x61\xe0\xfd\x9b\x17\x3c\xb0\x50\xde\x45\xe1\x17\x42\xeb\xaa\x9b\x14\xe8\x65\x30\x6e\x84\x5c\xf1\x7c\x6c\x1d\x75\x6e\xe1\x79\x0c\x56\xaf\xb1\x54\xe4\x19\x4e\x37\xc4\xe0\xc9\x2f\x8c\xee\xb2\xfa\x53\x5e\x36\xc9\x01\x03\x91\x7a\x3c\xc7\x98\x24\xad\x52\x1d\x7f\x94\xd9\xf4\x50\x7e\x50\xc7\xba\x4b\xf7\x24\x3f\xbe\xbc\xdc\x6d\xc5\x7c\xc9\x23\x08\x43\xa3\xe6\xb6\x7b\x1d\x6e\x8b\x1e\xbe\xca\xa3\x95\xdd\xbd\x06\xc2\xd7\x4e\x78\x34\xf1\xeb\x61\x78\x2e\x8f\x1a\x45\xce\xd7\x87\x16\x00\xc3\xef\xc6\xee\x9c\x75\x89\xb1\x32\x84\xba\x51\x5a\x7e\xdd\x77\x05\x2a\x8a\x20\x55\xdf\x48\xbf\x1f\x1c\x1f\x22\x1e\x75\xac\xa2\x87\x29\x89\xf6\x6e\x7e\xa3\x1c\x96\x21\xc9\x3c\xd5\x93\xb3\x30\x91\x59\xeb\xb8\xce\x1d\x1c\x5e\xdc\x65\x08\x5d\x85\xc4\x73\x2b\xe5\x12\x48\x9e\xc0\x3a\xb5\x42\x41\xb3\x3e\xad\xf5\xfc\x99\x5c\x25\x2c\x7f\x4b\x60\x77\x78\x32\xed\x7f\x18\xac\xa3\xf9\xce\x83\x81\x4d\xaa\x6e\x26\xe4\xf6\x30\xa9\x04\x72\xf6\x04\xdd\x13\x28\xee\x67\xae\x3f\xc2\x86\x27\x09\xe6\x37\xe3\x78\x23\x4c\xfc\xde\x9f\xf5\x69\xa7\x82\xea\x49\x15\x3d\xa2\x2b\xd6\xfb\xb0\xc7\x75\xc6\x4e\xc8\x6f\x37\xf7\xed\xd0\xe4\xa2\xa0\xeb\x9d\xf8\x61\x8c\x29\x5a\xf8\xcf\xef\xfe\xf6\x62\x20\x08\xf6\xdc\xed\xf0\xa2\x04\x3d\x72\x19\xf4\x60\x34\x2c\x25\xcb\x6c\x3b\x95\x0d\x87\xe7\x92\x0f\x08\x27\xb9\x95\x17\x92\x14\xfc\x83\xb7\xf7\xee\x80\x76\xd8\x78\xbf\xb3\x94\xd9\xc8\x79\xfc\x5e\xc9\x14\x86\x79\xea\x0c\x1d\xfb\x61\xb5\xdf\xa0\xb8\xfa\x3c\xb4\xfb\x79\x68\xf7\xf3\xd0\xee\xe7\xa1\xdd\xcf\x43\xbb\x9f\x87\x76\x3f\x72\x68\xf7\xec\x9c\xcc\xc9\xc1\xdd\x8f\x1b\xdd\x3d\x9b\x7d\x9d\x1c\xdf\xfd\x3c\xc0\xfb\x79\x80\xf7\xff\xcf\x00\xef\x59\x5b\x3f\x5e\xc5\x7d\xfa\x63\xbc\x1f\xd1\x59\xff\xe4\x86\x79\x27\xf1\x72\x74\xa0\xf7\x13\x1d\xe9\x9d\x30\xc1\x73\x76\xac\xf7\x9f\x65\xb0\xf7\xac\xac\x8e\x0c\xf7\x7e\x72\xe3\xbd\xbf\xfd\x04\xcd\xfa\x41\x7f\xac\x7c\xf8\x6f\xae\xa3\x88\x29\x4c\xfe\xab\x6b\x5e\xbd\xf3\x77\xd7\x76\x11\xd0\xaf\x27\xfe\xe1\xf5\x01\x12\xf6\x1e\x0d\xbf\x3b\x51\x7e\xe2\xa2\x7f\xc4\x44\xce\xca\x8f\x4d\x0c\x6f\x01\x32\xfe\x51\x2f\x28\x44\x4b\xe5\x70\x79\x32\x70\x48\xe9\x81\x8b\x28\x5f\xee\xff\xea\xc4\x65\x9e\xb2\xeb\x7e\x46\x82\xbf\xd6\xd6\xe4\x7e\x4b\xb8\x86\x9f\x7e\xbe\x80\xd2\x17\xed\xfa\x09\xfc\xf0\x7f\x03\x00\x00\xff\xff\x2c\x6d\xe8\xf2\xad\x43\x00\x00") func configCrdsKudoDev_operatorversionsYamlBytes() ([]byte, error) { return bindataRead( diff --git a/pkg/kudoctl/packages/convert/json-schema.go b/pkg/kudoctl/packages/convert/json-schema.go index 5ff08d380..d37aab197 100644 --- a/pkg/kudoctl/packages/convert/json-schema.go +++ b/pkg/kudoctl/packages/convert/json-schema.go @@ -12,6 +12,7 @@ import ( kudoapi "github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1" "github.com/kudobuilder/kudo/pkg/kudoctl/cmd/output" + "github.com/kudobuilder/kudo/pkg/util/convert" ) type jsonSchema struct { @@ -20,6 +21,7 @@ type jsonSchema struct { Description string `json:"description,omitempty"` Priority int `json:"priority,omitempty"` Default interface{} `json:"default,omitempty"` + Enum []interface{} `json:"enum,omitempty"` Type string `json:"type,omitempty"` Immutable bool `json:"immutable,omitempty"` Advanced bool `json:"advanced,omitempty"` @@ -29,6 +31,20 @@ type jsonSchema struct { Required []string `json:"required,omitempty"` } +func (j *jsonSchema) hasRequiredPropertyWithoutDefault() bool { + for _, pName := range j.Required { + prop := j.Properties[pName] + + if prop.Default == nil { + return true + } + if prop.hasRequiredPropertyWithoutDefault() { + return true + } + } + return false +} + func newSchema() *jsonSchema { return &jsonSchema{ Properties: map[string]*jsonSchema{}, @@ -98,7 +114,37 @@ func jsonSchemaTypeFromKudoType(parameterType kudoapi.ParameterType) string { } } -func buildParamSchema(p kudoapi.Parameter) *jsonSchema { +func UnwrapToJSONType(wrapped string, parameterType kudoapi.ParameterType) (unwrapped interface{}, err error) { + switch parameterType { + case kudoapi.MapValueType, + kudoapi.ArrayValueType, + kudoapi.NumberValueType, + kudoapi.BooleanValueType: + return convert.UnwrapParamValue(&wrapped, parameterType) + case kudoapi.IntegerValueType, + kudoapi.StringValueType: + // We keep integers as strings, as JSON only supports numbers, which may not always map correctly to integers + return wrapped, nil + default: + return wrapped, nil + } +} + +func UnwrapEnumValues(values []string, parameterType kudoapi.ParameterType) ([]interface{}, error) { + result := make([]interface{}, 0, len(values)) + for _, v := range values { + vUnwrapped, err := UnwrapToJSONType(v, parameterType) + if err != nil { + return nil, err + } + result = append(result, vUnwrapped) + } + return result, nil +} + +func buildParamSchema(p kudoapi.Parameter) (*jsonSchema, error) { + var err error + param := newSchema() if p.DisplayName != "" { @@ -108,7 +154,9 @@ func buildParamSchema(p kudoapi.Parameter) *jsonSchema { param.Description = p.Description } if p.HasDefault() { - param.Default = p.Default + if param.Default, err = UnwrapToJSONType(*p.Default, p.Type); err != nil { + return nil, fmt.Errorf("failed to convert default value %s: %v", *p.Default, err) + } } param.Type = jsonSchemaTypeFromKudoType(p.Type) if p.IsImmutable() { @@ -121,8 +169,13 @@ func buildParamSchema(p kudoapi.Parameter) *jsonSchema { param.Hint = p.Hint } param.ListName = p.Name + if p.IsEnum() { + if param.Enum, err = UnwrapEnumValues(p.EnumValues(), p.Type); err != nil { + return nil, fmt.Errorf("failed to convert enum values: %v", err) + } + } - return param + return param, nil } func WriteJSONSchema(ov *kudoapi.OperatorVersion, outputType output.Type, out io.Writer) error { @@ -135,7 +188,10 @@ func WriteJSONSchema(ov *kudoapi.OperatorVersion, outputType output.Type, out io root.Title = fmt.Sprintf("Parameters for %s", ov.Name) for _, p := range ov.Spec.Parameters { - param := buildParamSchema(p) + param, err := buildParamSchema(p) + if err != nil { + return fmt.Errorf("failed to convert parameter %s: %v", p.Name, err) + } // Assign to correct parent parent := topLevelGroups[p.Group] @@ -150,6 +206,12 @@ func WriteJSONSchema(ov *kudoapi.OperatorVersion, outputType output.Type, out io parent.Properties[p.Name] = param } + for k, v := range topLevelGroups { + if v.hasRequiredPropertyWithoutDefault() { + root.Required = append(root.Required, k) + } + } + buf := new(bytes.Buffer) err := output.WriteObject(root, output.TypeJSON, buf) if err != nil { @@ -171,7 +233,6 @@ func WriteJSONSchema(ov *kudoapi.OperatorVersion, outputType output.Type, out io return fmt.Errorf("error parsing JSON bytes: %s", err.Error()) } - fmt.Printf("Start validating...\n") vs := s.Validate(context.TODO(), doc) errs := *vs.Errs if len(errs) > 0 { diff --git a/pkg/util/convert/convert.go b/pkg/util/convert/convert.go index bae56d81c..55ce3dc2f 100644 --- a/pkg/util/convert/convert.go +++ b/pkg/util/convert/convert.go @@ -35,6 +35,8 @@ func UnwrapParamValue(wrapped *string, parameterType kudoapi.ParameterType) (unw unwrapped, err = strconv.ParseInt(StringValue(wrapped), 10, 32) case kudoapi.NumberValueType: unwrapped, err = strconv.ParseFloat(StringValue(wrapped), 64) + case kudoapi.BooleanValueType: + unwrapped, err = strconv.ParseBool(StringValue(wrapped)) case kudoapi.StringValueType: fallthrough default: From ef05faae053e9cba88515ab97e134ec2e25ef7be Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Thu, 22 Oct 2020 13:49:53 +0200 Subject: [PATCH 4/9] update golden Signed-off-by: Andreas Neumann --- Makefile | 6 ++++ .../cmd/testdata/deploy-kudo-ns.yaml.golden | 19 ++++++++++++ .../cmd/testdata/deploy-kudo-sa.yaml.golden | 19 ++++++++++++ .../cmd/testdata/deploy-kudo.json.golden | 29 +++++++++++++++++++ .../cmd/testdata/deploy-kudo.yaml.golden | 19 ++++++++++++ 5 files changed, 92 insertions(+) diff --git a/Makefile b/Makefile index 2b84ce6bc..85e7bcea7 100644 --- a/Makefile +++ b/Makefile @@ -197,7 +197,13 @@ endif # example: make update-golden # tests in update==true mode show as failures update-golden: ## Updates golden files +ifdef _INTELLIJ_FORCE_SET_GOFLAGS + # Run tests from a Goland terminal. Goland already set '-mod=readonly' + go test ./pkg/... --update=true +else go test ./pkg/... -v -mod=readonly --update=true +endif + .PHONY: todo # Show to-do items per file. diff --git a/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden index b9282d20b..09388b7c8 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden @@ -104,6 +104,19 @@ spec: connectionString: description: ConnectionString defines a templated string that can be used to connect to an instance of the Operator. type: string + groups: + items: + properties: + description: + type: string + displayName: + type: string + name: + type: string + prio: + type: integer + type: object + type: array operator: description: 'ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don''t make new APIs embed an underspecified API type they do not control. Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .' properties: @@ -133,6 +146,8 @@ spec: items: description: Parameter captures the variability of an OperatorVersion being instantiated in an instance. properties: + advanced: + type: boolean default: description: Default is a default value if no parameter is provided by the instance. type: string @@ -147,6 +162,10 @@ spec: items: type: string type: array + group: + type: string + hint: + 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-sa.yaml.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden index 3841e6775..275666bb4 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden @@ -104,6 +104,19 @@ spec: connectionString: description: ConnectionString defines a templated string that can be used to connect to an instance of the Operator. type: string + groups: + items: + properties: + description: + type: string + displayName: + type: string + name: + type: string + prio: + type: integer + type: object + type: array operator: description: 'ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don''t make new APIs embed an underspecified API type they do not control. Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .' properties: @@ -133,6 +146,8 @@ spec: items: description: Parameter captures the variability of an OperatorVersion being instantiated in an instance. properties: + advanced: + type: boolean default: description: Default is a default value if no parameter is provided by the instance. type: string @@ -147,6 +162,10 @@ spec: items: type: string type: array + group: + type: string + hint: + 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.json.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo.json.golden index 5fbf6eeaa..654ede5c4 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo.json.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo.json.golden @@ -144,6 +144,26 @@ "description": "ConnectionString defines a templated string that can be used to connect to an instance of the Operator.", "type": "string" }, + "groups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "prio": { + "type": "integer" + } + } + } + }, "operator": { "description": "ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, \"must refer only to types A and B\" or \"UID not honored\" or \"name must be restricted\". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .", "type": "object", @@ -184,6 +204,9 @@ "description": "Parameter captures the variability of an OperatorVersion being instantiated in an instance.", "type": "object", "properties": { + "advanced": { + "type": "boolean" + }, "default": { "description": "Default is a default value if no parameter is provided by the instance.", "type": "string" @@ -203,6 +226,12 @@ "type": "string" } }, + "group": { + "type": "string" + }, + "hint": { + "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 5364ad650..0b49025f8 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo.yaml.golden @@ -104,6 +104,19 @@ spec: connectionString: description: ConnectionString defines a templated string that can be used to connect to an instance of the Operator. type: string + groups: + items: + properties: + description: + type: string + displayName: + type: string + name: + type: string + prio: + type: integer + type: object + type: array operator: description: 'ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don''t make new APIs embed an underspecified API type they do not control. Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .' properties: @@ -133,6 +146,8 @@ spec: items: description: Parameter captures the variability of an OperatorVersion being instantiated in an instance. properties: + advanced: + type: boolean default: description: Default is a default value if no parameter is provided by the instance. type: string @@ -147,6 +162,10 @@ spec: items: type: string type: array + group: + type: string + hint: + type: string immutable: description: Specifies if the parameter can be changed after the initial installation of the operator type: boolean From 38f2eb02259df96f3da70eabe2f7adc7442d0a46 Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Tue, 27 Oct 2020 11:54:01 +0100 Subject: [PATCH 5/9] Added Priority in conversion Signed-off-by: Andreas Neumann --- pkg/kudoctl/packages/convert/parameters.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/kudoctl/packages/convert/parameters.go b/pkg/kudoctl/packages/convert/parameters.go index 120008425..91d750b82 100644 --- a/pkg/kudoctl/packages/convert/parameters.go +++ b/pkg/kudoctl/packages/convert/parameters.go @@ -20,6 +20,7 @@ func ParameterGroupsToPackageType(groups []kudoapi.ParameterGroup) packages.Grou Name: group.Name, DisplayName: group.DisplayName, Description: group.Description, + Priority: group.Priority, }) } @@ -37,6 +38,7 @@ func ParameterGroupsToCRDType(groups packages.Groups) []kudoapi.ParameterGroup { Name: group.Name, DisplayName: group.DisplayName, Description: group.Description, + Priority: group.Priority, }) } From e55c5c600fafce0ab1fba2a0ebe4eedb7a7e6061 Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Wed, 28 Oct 2020 15:23:52 +0100 Subject: [PATCH 6/9] Fixed missing fields Adjusted json schema for schemas Added tests Signed-off-by: Andreas Neumann --- Makefile | 1 + .../json-schema/full.json | 5 +- config/json-schema/limited.json | 130 ++++++++++++++++++ pkg/kudoctl/cmd/package_list.go | 2 +- pkg/kudoctl/cmd/package_list_params.go | 4 +- pkg/kudoctl/cmd/package_list_params_test.go | 61 +++++--- pkg/kudoctl/cmd/testdata/listop/operator.yaml | 40 ++++++ pkg/kudoctl/cmd/testdata/listop/params.yaml | 72 ++++++++++ .../listop/templates/statefulset.yaml | 90 ++++++++++++ .../testdata/listop/templates/validation.yaml | 27 ++++ pkg/kudoctl/cmd/testdata/params-list.golden | 3 - pkg/kudoctl/packages/convert/json-schema.go | 27 ++-- .../packages/convert/json-schema_test.go | 108 +++++++++++++++ 13 files changed, 536 insertions(+), 34 deletions(-) rename pkg/kudoctl/packages/convert/fullschema.json => config/json-schema/full.json (98%) create mode 100644 config/json-schema/limited.json create mode 100644 pkg/kudoctl/cmd/testdata/listop/operator.yaml create mode 100644 pkg/kudoctl/cmd/testdata/listop/params.yaml create mode 100644 pkg/kudoctl/cmd/testdata/listop/templates/statefulset.yaml create mode 100644 pkg/kudoctl/cmd/testdata/listop/templates/validation.yaml delete mode 100644 pkg/kudoctl/cmd/testdata/params-list.golden create mode 100644 pkg/kudoctl/packages/convert/json-schema_test.go diff --git a/Makefile b/Makefile index 85e7bcea7..1ef9d3a16 100644 --- a/Makefile +++ b/Makefile @@ -177,6 +177,7 @@ ifeq (, $(shell which go-bindata)) go get github.com/go-bindata/go-bindata/v3/go-bindata@$$(go list -f '{{.Version}}' -m github.com/go-bindata/go-bindata/v3) endif go-bindata -pkg crd -o pkg/kudoctl/kudoinit/crd/bindata.go -ignore README.md -nometadata config/crds + go-bindata -pkg convert -o pkg/kudoctl/packages/convert/bindata.go -nometadata config/json-schema ./hack/update_codegen.sh .PHONY: generate-clean diff --git a/pkg/kudoctl/packages/convert/fullschema.json b/config/json-schema/full.json similarity index 98% rename from pkg/kudoctl/packages/convert/fullschema.json rename to config/json-schema/full.json index 04d207951..29324679f 100644 --- a/pkg/kudoctl/packages/convert/fullschema.json +++ b/config/json-schema/full.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2019-09/schema", "$recursiveAnchor": true, - "$id": "https://kudo.dev/json-schema", + "$id": "https://kudo.dev/json-schema-full", "title": "KUDO JSON-Schema", "type": ["object", "boolean"], "additionalProperties": false, @@ -154,6 +154,9 @@ "listName": { "type": "string" }, + "trigger": { + "type": "string" + }, "immutable": { "type": "boolean", "default": false diff --git a/config/json-schema/limited.json b/config/json-schema/limited.json new file mode 100644 index 000000000..45e24e214 --- /dev/null +++ b/config/json-schema/limited.json @@ -0,0 +1,130 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$recursiveAnchor": true, + "$id": "https://kudo.dev/json-schema-limited", + "title": "Limited KUDO JSON-Schema", + "type": ["object", "boolean"], + "additionalProperties": false, + "properties": { + "type": { + "$ref": "#/$defs/simpleTypes" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "properties": { + "additionalProperties": { + "anyOf": [ + { "$ref": "#/$defs/group" }, + { "$ref": "#/$defs/parameter" } + ] + } + } + }, + "$defs": { + "group": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "const": "object" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "priority": { + "type": "integer" + }, + "required": { "$ref": "#/$defs/stringArray" }, + "properties": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#/$defs/group" }, + { "$ref": "#/$defs/parameter" } + ] + }, + "default": {} + } + } + }, + "parameter": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "listName" + ], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "required": { "$ref": "#/$defs/stringArray" }, + "enum": { + "type": "array", + "items": true + }, + "type": { + "anyOf": [ + { "$ref": "#/$defs/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "advanced": { + "type": "boolean", + "default": false + }, + "hint": { + "type": "string" + }, + "listName": { + "type": "string" + }, + "trigger": { + "type": "string" + }, + "immutable": { + "type": "boolean", + "default": false + } + } + }, + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$recursiveRef": "#" } + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + } +} diff --git a/pkg/kudoctl/cmd/package_list.go b/pkg/kudoctl/cmd/package_list.go index 1e7920198..eb05d4f0a 100644 --- a/pkg/kudoctl/cmd/package_list.go +++ b/pkg/kudoctl/cmd/package_list.go @@ -53,7 +53,7 @@ func newPackageParamsCmd(fs afero.Fs, out io.Writer) *cobra.Command { Long: packageListDesc, Example: packageListExamples, } - cmd.AddCommand(newPackageListParamsCmd(fs, out)) + cmd.AddCommand(newPackageListParamsCmd(&packageListParamsCmd{fs: fs, out: out})) cmd.AddCommand(newPackageListPlansCmd(fs, out)) cmd.AddCommand(newPackageListTasksCmd(fs, out)) diff --git a/pkg/kudoctl/cmd/package_list_params.go b/pkg/kudoctl/cmd/package_list_params.go index c1ba33656..b4ea77622 100644 --- a/pkg/kudoctl/cmd/package_list_params.go +++ b/pkg/kudoctl/cmd/package_list_params.go @@ -62,9 +62,7 @@ const ( TypeJSONSchemaYaml output.Type = "json-schema-yaml" ) -func newPackageListParamsCmd(fs afero.Fs, out io.Writer) *cobra.Command { - list := &packageListParamsCmd{fs: fs, out: out} - +func newPackageListParamsCmd(list *packageListParamsCmd) *cobra.Command { var outputStr string cmd := &cobra.Command{ Use: "parameters [operator]", diff --git a/pkg/kudoctl/cmd/package_list_params_test.go b/pkg/kudoctl/cmd/package_list_params_test.go index acedf02e4..518f71425 100644 --- a/pkg/kudoctl/cmd/package_list_params_test.go +++ b/pkg/kudoctl/cmd/package_list_params_test.go @@ -7,30 +7,57 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/kudobuilder/kudo/pkg/kudoctl/cmd/output" ) func TestParamsList(t *testing.T) { - file := "params-list" - out := &bytes.Buffer{} - cmd := newPackageListParamsCmd(fs, out) - if err := cmd.RunE(cmd, []string{"../packages/testdata/zk.tgz"}); err != nil { - t.Fatal(err) + tests := []struct { + name string + outputType output.Type + formatType string + err string + }{ + {name: "list-output.txt"}, + {name: "list-output.yaml", outputType: output.TypeYAML, formatType: outputFormatList}, + {name: "list-output.json", outputType: output.TypeJSON, formatType: outputFormatList}, + {name: "schema-output.yaml", outputType: output.TypeYAML, formatType: outputFormatJSONSchema}, + {name: "schema-output.json", outputType: output.TypeJSON, formatType: outputFormatJSONSchema}, } - gp := filepath.Join("testdata", file+".golden") + for _, tt := range tests { + tt := tt - if *updateGolden { - t.Log("update golden file") + t.Run(tt.name, func(t *testing.T) { - //nolint:gosec - if err := ioutil.WriteFile(gp, out.Bytes(), 0644); err != nil { - t.Fatalf("failed to update golden file: %s", err) - } - } - g, err := ioutil.ReadFile(gp) - if err != nil { - t.Fatalf("failed reading .golden: %s", err) + file := tt.name + out := &bytes.Buffer{} + params := &packageListParamsCmd{fs: fs, out: out, Output: tt.outputType, Format: tt.formatType} + + cmd := newPackageListParamsCmd(params) + + if err := cmd.RunE(cmd, []string{"./testdata/listop"}); err != nil { + t.Fatal(err) + } + + gp := filepath.Join("testdata", file+".golden") + + if *updateGolden { + t.Log("update golden file") + + //nolint:gosec + if err := ioutil.WriteFile(gp, out.Bytes(), 0644); err != nil { + t.Fatalf("failed to update golden file: %s", err) + } + } + g, err := ioutil.ReadFile(gp) + if err != nil { + t.Fatalf("failed reading .golden: %s", err) + } + + assert.Equal(t, string(g), out.String(), "output does not match .golden file %s", gp) + + }) } - assert.Equal(t, string(g), out.String(), "yaml does not match .golden file %s", gp) } diff --git a/pkg/kudoctl/cmd/testdata/listop/operator.yaml b/pkg/kudoctl/cmd/testdata/listop/operator.yaml new file mode 100644 index 000000000..5a150f1d9 --- /dev/null +++ b/pkg/kudoctl/cmd/testdata/listop/operator.yaml @@ -0,0 +1,40 @@ +apiVersion: kudo.dev/v1beta1 +name: "zookeeper" +operatorVersion: "0.1.0" +appVersion: "3.4.10" +kudoVersion: 0.2.0 +kubernetesVersion: 1.15.0 +maintainers: + - name: Alena Varkockova + email: avarkockova@mesosphere.com + - name: Tom Runyon + email: runyontr@gmail.com + - name: Ken Sipe + email: kensipe@gmail.com +url: https://zookeeper.apache.org/ +tasks: + - name: app + kind: Apply + spec: + resources: + - statefulset.yaml +plans: + deploy: + strategy: serial + phases: + - name: zookeeper + strategy: parallel + steps: + - name: everything + tasks: + - infra + - app + validation: + strategy: serial + phases: + - name: connection + strategy: parallel + steps: + - name: connection + tasks: + - validation \ No newline at end of file diff --git a/pkg/kudoctl/cmd/testdata/listop/params.yaml b/pkg/kudoctl/cmd/testdata/listop/params.yaml new file mode 100644 index 000000000..ae2dc5792 --- /dev/null +++ b/pkg/kudoctl/cmd/testdata/listop/params.yaml @@ -0,0 +1,72 @@ +apiVersion: kudo.dev/v1beta1 +groups: + - name: general + title: "Main Parameters" + description: "All the standard parameters" + prio: 10 + - name: advanced + title: "Advanced Configuration" + description: "Options that most users don't need to adjust" + prio: 20 +parameters: + - name: NODE_COUNT + description: "The number of Zookeeper nodes to create for the cluster. Should be an odd number of nodes and at least 3 to create a useful quorum." + default: "3" + displayName: "Node Count" + hint: "Number of nodes." + type: integer + group: general + + - name: NODE_CPU_MC + displayName: "CPU Request" + hint: "Allowed CPU usage in millicores." + description: "CPU request for the containers." + type: integer + default: "1000" + group: general + + - name: NODE_CPU_LIMIT_MC + displayName: "CPU Limit" + hint: "Temporary allowed CPU limit in millicores." + description: "CPU limit for the containers." + default: "1000" + type: integer + group: general + + - name: NODE_MEM_MIB + displayName: "Memory Request" + description: "Memory request for the containers." + hint: "Allowed Memory usage in MiB." + default: "4096" + type: integer + group: general + + - name: NODE_MEM_LIMIT_MIB + displayName: "Memory Limit" + description: "Memory limit for the Cassandra node containers." + hint: "Temporary allowed Memory limit in MiB." + default: "4096" + type: integer + group: general + + - name: DOCKER_IMAGE + displayName: "Main Docker Image" + type: string + description: "Zookeeper Docker image." + hint: "Docker Image." + default: "k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10" + advanced: true + group: advanced + + - name: DOCKER_IMAGE_PULL_POLICY + displayName: "Main Docker Image Pull Policy" + type: string + description: "Zookeeper Docker image pull policy." + hint: "Pull policy." + default: "Always" + advanced: true + group: advanced + enum: + - "Always" + - "IfNotPresent" + - "Never" diff --git a/pkg/kudoctl/cmd/testdata/listop/templates/statefulset.yaml b/pkg/kudoctl/cmd/testdata/listop/templates/statefulset.yaml new file mode 100644 index 000000000..e727386a2 --- /dev/null +++ b/pkg/kudoctl/cmd/testdata/listop/templates/statefulset.yaml @@ -0,0 +1,90 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ .Name }} + namespace: {{ .Namespace }} +spec: + selector: + matchLabels: + app: zookeeper + zookeeper: {{ .Name }} + serviceName: {{ .Name }}-hs + replicas: {{ .Params.NODE_COUNT }} + updateStrategy: + type: RollingUpdate + podManagementPolicy: Parallel + template: + metadata: + labels: + app: zookeeper + zookeeper: {{ .Name }} + spec: + containers: + - name: kubernetes-zookeeper + imagePullPolicy: {{ .Params.DOCKER_IMAGE_PULL_POLICY }} + image: "{{ .Params.DOCKER_IMAGE }}" + resources: + requests: + memory: "{{ $.Params.NODE_MEM_MIB }}Mi" + cpu: "{{ $.Params.NODE_CPU_MC }}m" + limits: + memory: "{{ $.Params.NODE_MEM_LIMIT_MIB }}Mi" + cpu: "{{ $.Params.NODE_CPU_LIMIT_MC }}m" + ports: + - containerPort: 2181 + name: client + - containerPort: 2888 + name: server + - containerPort: 3888 + name: leader-election + command: + - sh + - -c + - "start-zookeeper \ + --servers=3 \ + --data_dir=/var/lib/zookeeper/data \ + --data_log_dir=/var/lib/zookeeper/data/log \ + --conf_dir=/opt/zookeeper/conf \ + --client_port=2181 \ + --election_port=3888 \ + --server_port=2888 \ + --tick_time=2000 \ + --init_limit=10 \ + --sync_limit=5 \ + --heap=512M \ + --max_client_cnxns=60 \ + --snap_retain_count=3 \ + --purge_interval=12 \ + --max_session_timeout=40000 \ + --min_session_timeout=4000 \ + --log_level=INFO" + readinessProbe: + exec: + command: + - sh + - -c + - "zookeeper-ready 2181" + initialDelaySeconds: 10 + timeoutSeconds: 5 + livenessProbe: + exec: + command: + - sh + - -c + - "zookeeper-ready 2181" + initialDelaySeconds: 10 + timeoutSeconds: 5 + volumeMounts: + - name: datadir + mountPath: /var/lib/zookeeper + securityContext: + runAsUser: 1000 + fsGroup: 1000 + volumeClaimTemplates: + - metadata: + name: datadir + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 2Gi diff --git a/pkg/kudoctl/cmd/testdata/listop/templates/validation.yaml b/pkg/kudoctl/cmd/testdata/listop/templates/validation.yaml new file mode 100644 index 000000000..e04a60050 --- /dev/null +++ b/pkg/kudoctl/cmd/testdata/listop/templates/validation.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: zookeeper-validation +spec: + template: + metadata: + name: "validation" + spec: + restartPolicy: Never + containers: + - name: kubernetes-zookeeper + imagePullPolicy: {{ .Params.DOCKER_IMAGE_PULL_POLICY }} + image: "{{ .Params.DOCKER_IMAGE }}" + env: + - name: CONN + value: zk-zk-0.zk-hs:2181,zk-zk-1.zk-hs:2181,zk-zk-2.zk-hs:2181 + resources: + requests: + memory: "{{ $.Params.NODE_MEM_MIB }}Mi" + cpu: "{{ $.Params.NODE_CPU_MC }}m" + command: + - sh + - -c + - "/opt/zookeeper/bin/zkCLI.sh \ + --server $CONN \ + ls /" diff --git a/pkg/kudoctl/cmd/testdata/params-list.golden b/pkg/kudoctl/cmd/testdata/params-list.golden deleted file mode 100644 index dbd0d2a08..000000000 --- a/pkg/kudoctl/cmd/testdata/params-list.golden +++ /dev/null @@ -1,3 +0,0 @@ -Name Default Required Immutable -cpus 0.25 true false -memory 1Gi true false diff --git a/pkg/kudoctl/packages/convert/json-schema.go b/pkg/kudoctl/packages/convert/json-schema.go index d37aab197..814750dc9 100644 --- a/pkg/kudoctl/packages/convert/json-schema.go +++ b/pkg/kudoctl/packages/convert/json-schema.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "github.com/qri-io/jsonschema" @@ -15,6 +14,11 @@ import ( "github.com/kudobuilder/kudo/pkg/util/convert" ) +const ( + jsonSchemaTypeObject = "object" + jsonSchemaTypeString = "string" +) + type jsonSchema struct { Schema string `json:"$schema,omitempty"` Title string `json:"title,omitempty"` @@ -26,6 +30,7 @@ type jsonSchema struct { Immutable bool `json:"immutable,omitempty"` Advanced bool `json:"advanced,omitempty"` Hint string `json:"hint,omitempty"` + Trigger string `json:"trigger,omitempty"` ListName string `json:"listName,omitempty"` Properties map[string]*jsonSchema `json:"properties,omitempty"` Required []string `json:"required,omitempty"` @@ -76,6 +81,8 @@ func buildTopLevelGroups(groups map[string]kudoapi.ParameterGroup) map[string]*j for _, v := range groups { g := newSchema() + g.Type = jsonSchemaTypeObject + if v.DisplayName != "" { g.Title = v.DisplayName } else { @@ -106,11 +113,11 @@ func jsonSchemaTypeFromKudoType(parameterType kudoapi.ParameterType) string { // Objects are the equivalent to maps case kudoapi.MapValueType: - return "object" + return jsonSchemaTypeObject // All other types are defined as strings default: - return "string" + return jsonSchemaTypeString } } @@ -168,6 +175,9 @@ func buildParamSchema(p kudoapi.Parameter) (*jsonSchema, error) { if p.Hint != "" { param.Hint = p.Hint } + if p.Trigger != "" { + param.Trigger = p.Trigger + } param.ListName = p.Name if p.IsEnum() { if param.Enum, err = UnwrapEnumValues(p.EnumValues(), p.Type); err != nil { @@ -183,7 +193,7 @@ func WriteJSONSchema(ov *kudoapi.OperatorVersion, outputType output.Type, out io topLevelGroups := buildTopLevelGroups(buildGroups(ov)) root.Properties = topLevelGroups - root.Type = "object" + root.Type = jsonSchemaTypeObject root.Description = "All parameters for this operator" root.Title = fmt.Sprintf("Parameters for %s", ov.Name) @@ -218,13 +228,11 @@ func WriteJSONSchema(ov *kudoapi.OperatorVersion, outputType output.Type, out io return err } - g, err := ioutil.ReadFile("pkg/kudoctl/packages/convert/fullschema.json") - if err != nil { - return fmt.Errorf("failed to read thingy: %v", err) - } + //schemaJSON := MustAsset("config/json-schema/full.json") + schemaJSON := MustAsset("config/json-schema/limited.json") s := &jsonschema.Schema{} - if err := json.Unmarshal(g, s); err != nil { + if err := json.Unmarshal(schemaJSON, s); err != nil { return fmt.Errorf("failed to unmarshal json-schema: %v", err) } @@ -239,6 +247,7 @@ func WriteJSONSchema(ov *kudoapi.OperatorVersion, outputType output.Type, out io for _, e := range errs { fmt.Printf("Error: %v\n", e) } + fmt.Printf("%s\n", buf.String()) return fmt.Errorf("failed to validate json schema") } diff --git a/pkg/kudoctl/packages/convert/json-schema_test.go b/pkg/kudoctl/packages/convert/json-schema_test.go new file mode 100644 index 000000000..dd722cc59 --- /dev/null +++ b/pkg/kudoctl/packages/convert/json-schema_test.go @@ -0,0 +1,108 @@ +package convert + +import ( + "bytes" + "flag" + "fmt" + "io/ioutil" + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + kudoapi "github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1" + "github.com/kudobuilder/kudo/pkg/kudoctl/cmd/output" +) + +var update = flag.Bool("update", false, "update .golden files") + +func Test_JsonSchemaExport(t *testing.T) { + + tests := []struct { + name string + ov *kudoapi.OperatorVersion + outputType output.Type + err string + }{ + {name: "simple.json", ov: simpleOv(), outputType: output.TypeJSON}, + {name: "simple.yaml", ov: simpleOv(), outputType: output.TypeYAML}, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + out := &bytes.Buffer{} + err := WriteJSONSchema(tt.ov, tt.outputType, out) + assert.NoError(t, err) + + rendered := out.String() + + gf := fmt.Sprintf("testdata/%s.golden", tt.name) + + if *update { + t.Log("update golden file") + + //nolint:gosec + if err := ioutil.WriteFile(gf, []byte(rendered), 0644); err != nil { + t.Fatalf("failed to update golden file: %s", err) + } + } + + golden, err := ioutil.ReadFile(gf) + if err != nil { + t.Fatalf("failed reading .golden: %s", err) + } + + assert.Equal(t, string(golden), rendered, "for golden file: %s", gf) + }) + } + +} + +func simpleOv() *kudoapi.OperatorVersion { + return &kudoapi.OperatorVersion{ + ObjectMeta: v1.ObjectMeta{ + Name: "Test", + }, + Spec: kudoapi.OperatorVersionSpec{ + Groups: []kudoapi.ParameterGroup{ + { + Name: "simplegroup", + DisplayName: "SimpleGroup", + Description: "The description for this group.", + Priority: 10, + }, + }, + Parameters: []kudoapi.Parameter{ + { + Name: "my-param", + DisplayName: "MyParam", + Description: "My parameter description", + Type: kudoapi.StringValueType, + Immutable: boolPtr(true), + Default: strPointer("defaultVal"), + Hint: "My Param Hint.", + Trigger: "my-plan", + }, + { + Name: "grouped-param", + DisplayName: "GroupedParam", + Description: "My parameter description in a group", + Type: kudoapi.IntegerValueType, + Immutable: boolPtr(false), + Default: strPointer("23"), + Hint: "My Group Param Hint.", + Group: "simplegroup", + }, + }, + }, + } +} + +func boolPtr(v bool) *bool { + return &v +} +func strPointer(v string) *string { + return &v +} From ef079d320c8bf92be3d42d3ccc98180a0b4afc57 Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Wed, 28 Oct 2020 15:37:48 +0100 Subject: [PATCH 7/9] Added missing bindata for schemas Signed-off-by: Andreas Neumann --- pkg/kudoctl/packages/convert/bindata.go | 272 ++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 pkg/kudoctl/packages/convert/bindata.go diff --git a/pkg/kudoctl/packages/convert/bindata.go b/pkg/kudoctl/packages/convert/bindata.go new file mode 100644 index 000000000..d14912c2b --- /dev/null +++ b/pkg/kudoctl/packages/convert/bindata.go @@ -0,0 +1,272 @@ +// Code generated by go-bindata. (@generated) DO NOT EDIT. + + //Package convert generated by go-bindata.// sources: +// config/json-schema/full.json +// config/json-schema/limited.json +package convert + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +// Name return file name +func (fi bindataFileInfo) Name() string { + return fi.name +} + +// Size return file size +func (fi bindataFileInfo) Size() int64 { + return fi.size +} + +// Mode return file mode +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} + +// ModTime return file modify time +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} + +// IsDir return file whether a directory +func (fi bindataFileInfo) IsDir() bool { + return fi.mode&os.ModeDir != 0 +} + +// Sys return file is sys mode +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _configJsonSchemaFullJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x58\x4b\x73\xdb\x36\x10\xbe\xfb\x57\x60\x60\x9f\x32\xa6\x25\xf7\x66\x5d\x3a\x9a\xe6\x92\x3e\xa4\x4e\xd2\x5e\xaa\x51\x3a\x10\xb9\xa4\x90\xe2\xc1\x80\x80\x2a\x55\xa3\xff\xde\xe1\x1b\x20\x41\x8b\xb4\xda\xdc\x2c\x60\x1f\xdf\x2e\x76\xbf\x5d\xfa\x7c\x87\x10\x7e\xc8\xc2\x3d\x70\x82\x17\x08\xef\xb5\x4e\xb3\xc5\x6c\xf6\x25\x93\x22\x28\x8f\x9f\xa4\x4a\x66\x91\x22\xb1\x9e\x7d\x37\x7f\x7e\x09\xe6\x2f\xb3\x4a\xfe\xb1\x50\x56\x10\x1a\x95\xd1\x03\x2c\x45\xb8\x97\x0a\x2f\x90\x56\x06\xca\x3b\x1a\xd9\x46\xff\x32\x91\x7c\x8a\xe0\x60\x5b\x0f\x62\xc3\x58\x69\x49\x53\xcd\x20\x97\xff\xe9\xf7\xf7\x6b\xf4\xe3\xa7\xf5\x2a\xf8\x64\x39\xd2\xa7\x34\xbf\xdd\x60\xb9\xfb\x02\xa1\xc6\x8f\x08\xef\xa4\x64\x40\x04\xde\x16\x02\x24\x8a\xa8\xa6\x52\x10\xf6\xab\x92\x29\x28\x4d\x21\xc3\x0b\x14\x13\x96\x95\x70\x52\xfb\x38\x8f\xbc\x81\x58\xfe\x68\x9d\xe0\x4c\x2b\x2a\x92\xc2\x71\x71\x1e\x4b\xc5\x89\xce\x6f\x8c\xa2\x81\x82\x18\x14\x88\x10\x5a\x81\x87\x50\x72\x0e\xa2\x10\x59\x49\x11\x00\x4f\xf5\x09\xc5\x8a\x24\xf9\x69\x86\x84\xd4\x88\x30\x26\xff\x86\xe8\xa9\xd5\x4a\x89\xd6\xa0\x44\xae\xf4\x79\xf3\xf9\x7e\xfb\xee\xfe\xfb\x07\x5c\x5c\x5e\x1e\x2b\x7c\xcd\xdb\x4c\xc3\xd8\xb1\x42\xea\xb7\xb9\x6a\xc5\x81\xb4\x0c\xfe\x20\xc1\x3f\xdb\x4d\x50\xfe\x31\x0f\x5e\x9e\x16\x7f\x6e\xdf\x75\x31\x2a\x88\x6f\x49\x62\xd7\x58\x55\x4f\x1f\xff\x17\xab\xcb\xc1\x4c\xd4\xd5\xd4\x58\x8e\x20\x26\x86\xe9\xba\x86\x5c\x83\xed\x7b\x0f\x21\x74\xe5\x23\x88\x33\x9f\x70\x5d\xcd\xf5\xf9\x40\x15\x9f\xfb\x99\xc1\xf7\xb8\x36\xef\x80\x3d\x5f\x1c\xcf\x75\x5b\x8d\x82\x19\x41\x16\x2a\x9a\xe6\x00\xc6\xaa\xc0\x91\xf0\x94\x81\x37\x38\xa2\x14\x39\xb5\xb1\x51\x0d\x3c\xab\x08\xa2\xe3\xb6\x06\x5f\x73\x07\x42\x98\x1b\xa6\x69\xca\x60\xed\x2d\x03\x61\xf8\x0e\x54\x6b\x1b\x8e\x21\x33\x79\x72\x7e\xa1\x82\x72\xc3\xf1\x02\xcd\x1d\x1f\x9c\x1c\xab\xf3\x21\x5b\x9d\xb0\x6a\x7b\xd3\xf4\x78\xe3\x7e\xa2\x9b\x69\x7a\x9c\x1c\x7f\x06\x91\xe8\x7d\x53\x1b\x65\x49\xcc\x8a\x42\x9b\x09\x29\x56\x90\x10\x4d\x0f\xf0\x41\x68\x48\x40\x61\x1b\xe1\x34\xd5\xf7\xe5\xeb\xcc\x5b\x13\x2d\x49\x4c\x68\x4f\x05\x09\x1c\x7b\x51\x7c\xa8\x6a\x62\x7a\x10\x93\x34\xfb\x31\x18\x41\xbf\x1a\x68\x8c\xdc\xc4\x06\x9c\x1c\x7f\x90\x42\x13\x2a\xde\x16\x8a\xad\xdc\x4c\x94\xeb\x26\x3c\xd8\x9e\xbb\xb8\x3c\x2c\x32\x05\xd9\x74\xf5\x7e\xa6\x15\x7c\x35\x54\x41\xe4\xb7\x50\x16\xcc\xb2\x60\x0a\x8b\x10\x52\x10\x11\x08\xfd\xd1\xd2\x7d\x2b\x75\x56\xf7\x9e\x9c\xda\xbe\x2b\x29\x97\x3c\x43\x29\xb2\x0e\x2d\x81\xf0\x77\xe9\x58\xae\xab\xe4\x1b\x7d\x22\x4e\x05\xc3\x6d\x1a\x98\xbe\x24\xd1\x9c\x63\x7f\x3b\xa5\x90\x59\xa4\x8f\xac\xd8\x06\x91\x38\x68\x26\x98\x76\x9b\xec\xd9\xb9\x70\x7b\xa7\x09\xb1\xcd\x1f\x42\x5b\x27\xe8\xf6\x69\xdc\xae\x1d\x1a\x68\xd8\x08\x38\x10\x66\x88\x86\x68\xa4\x06\xed\x76\xf2\x50\x62\x87\x87\xa8\x3f\xf5\xc5\x02\x56\xd7\xa7\x37\xba\xb0\xdb\xfb\x63\xc2\x9a\x30\xde\x7d\x9b\xeb\xb7\x5c\x21\x2a\xba\xf7\x76\xd5\x7f\x8e\xa2\x8a\xf5\xb4\x22\xbc\x56\xe8\xce\x90\x31\x98\x1b\x06\x29\xbf\x21\x6e\x82\xec\x10\x48\x17\xba\x97\x37\x3c\x41\xbc\x5a\xbb\xf1\x75\x19\xbd\x07\x71\x5d\x0a\x58\x06\xd7\xa5\x08\x63\xeb\x78\x80\x0e\x9c\x72\xaf\xe5\xab\x56\x1a\x2b\x2f\x05\x4c\x92\x17\x52\x8f\x00\x1d\x1d\x88\x08\xbd\xa3\x60\xe2\xb8\xde\xd3\xf1\x8b\x3b\xa3\x99\xce\x5f\x71\xac\xbc\x56\x34\xc9\x87\xe8\x48\x71\xca\xb9\xd1\x64\xe7\xdf\xd0\x27\x05\x95\x2a\x2a\x15\xd5\x27\x9f\x21\x5a\x4d\xf6\xbb\xba\x52\x2f\xe5\xf7\xb9\xf3\x51\x82\xed\xa7\xb9\x3e\xe0\x7c\xc3\xa1\x33\x67\xfa\x4f\xe9\x20\xf6\xac\x1e\xaf\x60\xb7\x1d\x7b\x17\xfc\x57\x56\x91\x9b\xd7\x2a\xd7\x93\x3d\x32\x2d\xd3\xd5\x66\xd0\xce\x9b\xde\x24\xee\xbd\x28\xea\xc7\x97\x47\x52\xff\x4f\xa4\xf9\xed\x7c\xeb\xa0\x1e\x73\x21\xb7\xb8\xba\xf3\xc9\x5e\x73\xc6\x6f\x2e\xe7\x5e\xe5\x5a\xbc\xdb\xdf\x00\x3c\x59\xdb\x6c\xdb\x82\xbb\xbb\xdc\xfd\x1b\x00\x00\xff\xff\x45\x47\xff\xdc\x6d\x12\x00\x00") + +func configJsonSchemaFullJsonBytes() ([]byte, error) { + return bindataRead( + _configJsonSchemaFullJson, + "config/json-schema/full.json", + ) +} + +func configJsonSchemaFullJson() (*asset, error) { + bytes, err := configJsonSchemaFullJsonBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "config/json-schema/full.json", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _configJsonSchemaLimitedJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x56\x3d\x6f\xdb\x30\x10\xdd\xf3\x2b\x08\x36\x63\x1c\x25\xdd\xe2\x2d\x40\x97\xb4\x45\x5c\x34\xed\x64\x78\xa0\xa5\xb3\x7c\xa9\x44\x2a\x24\x65\xc0\x30\xfc\xdf\x0b\x8a\xfa\xa0\x28\x32\xb6\x83\x02\x5d\x0c\xeb\x78\xef\xbe\xde\x3b\x4a\x87\x2b\x42\xe8\xb5\x4a\xb7\x50\x32\x3a\x27\x74\xab\x75\xa5\xe6\x49\xf2\xaa\x04\x9f\x59\xf3\xad\x90\x79\x92\x49\xb6\xd1\xc9\xe7\xbb\xfb\x87\xd9\xdd\x43\xd2\xfa\xdf\x34\x60\x09\x69\x2d\x15\xee\xe0\x91\xa7\x5b\x21\xe9\x9c\x68\x59\x83\x3d\xc3\xcc\x0d\xfa\xa7\xce\xc4\x6d\x06\x3b\x37\xfa\xac\xc0\x12\x35\x64\x36\x98\x46\x5d\x80\x81\x7c\xb7\x56\xf2\xed\xf7\x97\x05\xf9\xfa\xb2\x78\x9e\xbd\x38\x39\xf5\xbe\x32\x5e\x4b\x2a\xd6\xaf\x90\x6a\x7a\x43\xe8\x5a\x88\x02\x18\xa7\xab\xc6\x81\x65\x19\x6a\x14\x9c\x15\x3f\xa4\xa8\x40\x6a\x04\x45\xe7\x64\xc3\x0a\x65\x2b\xab\x5c\xb3\x19\xc2\x10\xd5\x3e\xd9\xce\x36\xa6\x96\x4f\xc9\x75\x06\x1b\x95\x28\x2c\xab\x02\x7e\xed\x2b\x50\xb4\xf1\x39\xde\xb4\xc0\xb6\xea\x1e\xd9\x46\xa2\x4a\x4b\xe4\xf9\xd8\x39\x03\x95\x4a\xac\x4c\x75\xe7\x42\x02\xc5\xc6\x7b\xec\xce\x8d\x07\xdf\x2f\x4c\x07\xcb\xde\x44\xc8\x61\xda\x57\x2e\x45\x5d\xd1\x2e\x5b\xd4\xad\x62\x92\x95\xa0\x41\x52\x72\xec\x3d\x57\xed\x3f\x6b\x31\xbf\x47\xcb\xbc\x81\x0c\xa3\xb5\x29\x02\xed\x76\x04\x9e\x68\xaa\x27\x2e\x3e\x8f\x29\x81\x8d\x2d\x15\x5c\x69\x27\x53\x7f\xe6\xb4\x3b\x21\x30\xca\x88\x0f\x0c\x93\x79\x36\xbc\x92\x28\x24\xea\x7d\x0c\x8b\x5c\x43\x0e\x32\x0c\x96\xf0\x56\xa3\x04\xb3\x61\x01\xb6\x6c\xda\x47\x29\xd9\x9e\x7a\x39\x83\xc3\x8b\x73\xf2\x1e\x2f\x2e\x3e\x22\x38\x72\xbe\xe8\x22\xae\x41\xe1\x11\x47\x7c\xde\x64\x1a\x5e\x36\xac\x2e\x0c\xef\x87\x01\x72\x1c\x6b\xb5\x5b\xae\x3e\xfa\xbf\x92\xa7\x43\xcc\xd2\x13\xa7\xc3\x43\x81\x4a\x3f\xb3\x12\x3a\x72\x57\xa7\xe5\xfd\x9f\x54\x3a\x0c\xb3\xbb\xd8\xa7\x8d\x5e\xa2\x40\xe0\x75\x19\xab\x83\x35\xee\x23\x2e\x51\x43\xa9\xda\xe4\xe1\xed\x0d\xac\x7d\x58\x89\xa1\x2a\x9d\x4b\xdd\x57\xe3\xc1\xd3\xe6\x3b\x55\x8e\x2a\xbd\x30\x0d\x21\xb4\x44\xfe\xd4\x82\xef\x27\x87\x35\xc7\xb7\x1a\x9e\x42\x63\x20\x8e\xac\xc9\x68\x23\xdc\x01\xb1\x6c\xc7\x78\x6a\x79\x0a\x0c\x7d\x78\x79\x46\x76\xa8\x91\x77\x30\xf2\x16\xb9\xfe\x88\xa4\x7a\xf1\x7f\x00\xab\x25\xe6\xf9\x68\x5d\xcf\x86\x62\x59\xd6\x9a\xad\xe3\x5b\x74\xd9\x20\x82\xf7\x89\xfd\xac\xb1\xb2\x0f\xdc\x28\x63\xe9\x04\x89\xf7\x74\xd4\x7e\x5d\xfd\x6c\x05\x45\xfd\x7c\x8e\xb2\x9c\x7c\xed\x92\x39\x17\x90\x2f\xda\x40\xb3\xfd\x0b\xc7\x31\xf1\xba\x28\xc6\xcf\xe5\x7a\xec\x31\x79\x5f\x78\x1c\xac\xc6\xf5\x3a\xd7\xc2\xe9\xf9\x38\xa3\xf0\x08\x1e\x88\x0d\x2c\x48\x7f\x34\x70\xb7\x5c\x0d\x9f\x28\x57\xc7\xab\xbf\x01\x00\x00\xff\xff\x1d\xf9\xe7\x5f\xf9\x0a\x00\x00") + +func configJsonSchemaLimitedJsonBytes() ([]byte, error) { + return bindataRead( + _configJsonSchemaLimitedJson, + "config/json-schema/limited.json", + ) +} + +func configJsonSchemaLimitedJson() (*asset, error) { + bytes, err := configJsonSchemaLimitedJsonBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "config/json-schema/limited.json", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "config/json-schema/full.json": configJsonSchemaFullJson, + "config/json-schema/limited.json": configJsonSchemaLimitedJson, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "config": &bintree{nil, map[string]*bintree{ + "json-schema": &bintree{nil, map[string]*bintree{ + "full.json": &bintree{configJsonSchemaFullJson, map[string]*bintree{}}, + "limited.json": &bintree{configJsonSchemaLimitedJson, map[string]*bintree{}}, + }}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} From 1602ef765be8bb6873c48e0591f2573ed3e57eb6 Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Wed, 28 Oct 2020 16:27:55 +0100 Subject: [PATCH 8/9] Missing golden files Signed-off-by: Andreas Neumann --- .../cmd/testdata/list-output.json.golden | 101 ++++++++++++++++++ .../cmd/testdata/list-output.txt.golden | 8 ++ .../cmd/testdata/list-output.yaml.golden | 79 ++++++++++++++ .../cmd/testdata/schema-output.json.golden | 97 +++++++++++++++++ .../cmd/testdata/schema-output.yaml.golden | 81 ++++++++++++++ .../convert/testdata/simple.json.golden | 33 ++++++ .../convert/testdata/simple.yaml.golden | 27 +++++ 7 files changed, 426 insertions(+) create mode 100644 pkg/kudoctl/cmd/testdata/list-output.json.golden create mode 100644 pkg/kudoctl/cmd/testdata/list-output.txt.golden create mode 100644 pkg/kudoctl/cmd/testdata/list-output.yaml.golden create mode 100644 pkg/kudoctl/cmd/testdata/schema-output.json.golden create mode 100644 pkg/kudoctl/cmd/testdata/schema-output.yaml.golden create mode 100644 pkg/kudoctl/packages/convert/testdata/simple.json.golden create mode 100644 pkg/kudoctl/packages/convert/testdata/simple.yaml.golden diff --git a/pkg/kudoctl/cmd/testdata/list-output.json.golden b/pkg/kudoctl/cmd/testdata/list-output.json.golden new file mode 100644 index 000000000..d77de50ed --- /dev/null +++ b/pkg/kudoctl/cmd/testdata/list-output.json.golden @@ -0,0 +1,101 @@ +{ + "apiVersion": "kudo.dev/v1beta1", + "groups": [ + { + "name": "general", + "description": "All the standard parameters", + "prio": 10 + }, + { + "name": "advanced", + "description": "Options that most users don't need to adjust", + "prio": 20 + } + ], + "parameters": [ + { + "displayName": "Node Count", + "name": "NODE_COUNT", + "description": "The number of Zookeeper nodes to create for the cluster. Should be an odd number of nodes and at least 3 to create a useful quorum.", + "required": true, + "default": 3, + "type": "integer", + "immutable": false, + "group": "general", + "hint": "Number of nodes." + }, + { + "displayName": "CPU Request", + "name": "NODE_CPU_MC", + "description": "CPU request for the containers.", + "required": true, + "default": 1000, + "type": "integer", + "immutable": false, + "group": "general", + "hint": "Allowed CPU usage in millicores." + }, + { + "displayName": "CPU Limit", + "name": "NODE_CPU_LIMIT_MC", + "description": "CPU limit for the containers.", + "required": true, + "default": 1000, + "type": "integer", + "immutable": false, + "group": "general", + "hint": "Temporary allowed CPU limit in millicores." + }, + { + "displayName": "Memory Request", + "name": "NODE_MEM_MIB", + "description": "Memory request for the containers.", + "required": true, + "default": 4096, + "type": "integer", + "immutable": false, + "group": "general", + "hint": "Allowed Memory usage in MiB." + }, + { + "displayName": "Memory Limit", + "name": "NODE_MEM_LIMIT_MIB", + "description": "Memory limit for the Cassandra node containers.", + "required": true, + "default": 4096, + "type": "integer", + "immutable": false, + "group": "general", + "hint": "Temporary allowed Memory limit in MiB." + }, + { + "displayName": "Main Docker Image", + "name": "DOCKER_IMAGE", + "description": "Zookeeper Docker image.", + "required": true, + "default": "k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10", + "type": "string", + "immutable": false, + "group": "advanced", + "advanced": true, + "hint": "Docker Image." + }, + { + "displayName": "Main Docker Image Pull Policy", + "name": "DOCKER_IMAGE_PULL_POLICY", + "description": "Zookeeper Docker image pull policy.", + "required": true, + "default": "Always", + "type": "string", + "immutable": false, + "enum": [ + "Always", + "IfNotPresent", + "Never" + ], + "group": "advanced", + "advanced": true, + "hint": "Pull policy." + } + ] +} diff --git a/pkg/kudoctl/cmd/testdata/list-output.txt.golden b/pkg/kudoctl/cmd/testdata/list-output.txt.golden new file mode 100644 index 000000000..572ecee61 --- /dev/null +++ b/pkg/kudoctl/cmd/testdata/list-output.txt.golden @@ -0,0 +1,8 @@ +Name Default Required Immutable +DOCKER_IMAGE k8s.gcr.io/kubernetes-zookeeper:... true false +DOCKER_IMAGE_PULL_POLICY Always true false +NODE_COUNT 3 true false +NODE_CPU_LIMIT_MC 1000 true false +NODE_CPU_MC 1000 true false +NODE_MEM_LIMIT_MIB 4096 true false +NODE_MEM_MIB 4096 true false diff --git a/pkg/kudoctl/cmd/testdata/list-output.yaml.golden b/pkg/kudoctl/cmd/testdata/list-output.yaml.golden new file mode 100644 index 000000000..fe3538ed8 --- /dev/null +++ b/pkg/kudoctl/cmd/testdata/list-output.yaml.golden @@ -0,0 +1,79 @@ +apiVersion: kudo.dev/v1beta1 +groups: +- description: All the standard parameters + name: general + prio: 10 +- description: Options that most users don't need to adjust + name: advanced + prio: 20 +parameters: +- default: 3 + description: The number of Zookeeper nodes to create for the cluster. Should be an odd number of nodes and at least 3 to create a useful quorum. + displayName: Node Count + group: general + hint: Number of nodes. + immutable: false + name: NODE_COUNT + required: true + type: integer +- default: 1000 + description: CPU request for the containers. + displayName: CPU Request + group: general + hint: Allowed CPU usage in millicores. + immutable: false + name: NODE_CPU_MC + required: true + type: integer +- default: 1000 + description: CPU limit for the containers. + displayName: CPU Limit + group: general + hint: Temporary allowed CPU limit in millicores. + immutable: false + name: NODE_CPU_LIMIT_MC + required: true + type: integer +- default: 4096 + description: Memory request for the containers. + displayName: Memory Request + group: general + hint: Allowed Memory usage in MiB. + immutable: false + name: NODE_MEM_MIB + required: true + type: integer +- default: 4096 + description: Memory limit for the Cassandra node containers. + displayName: Memory Limit + group: general + hint: Temporary allowed Memory limit in MiB. + immutable: false + name: NODE_MEM_LIMIT_MIB + required: true + type: integer +- advanced: true + default: k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10 + description: Zookeeper Docker image. + displayName: Main Docker Image + group: advanced + hint: Docker Image. + immutable: false + name: DOCKER_IMAGE + required: true + type: string +- advanced: true + default: Always + description: Zookeeper Docker image pull policy. + displayName: Main Docker Image Pull Policy + enum: + - Always + - IfNotPresent + - Never + group: advanced + hint: Pull policy. + immutable: false + name: DOCKER_IMAGE_PULL_POLICY + required: true + type: string + diff --git a/pkg/kudoctl/cmd/testdata/schema-output.json.golden b/pkg/kudoctl/cmd/testdata/schema-output.json.golden new file mode 100644 index 000000000..ba6b6ea8d --- /dev/null +++ b/pkg/kudoctl/cmd/testdata/schema-output.json.golden @@ -0,0 +1,97 @@ +{ + "title": "Parameters for zookeeper-3.4.10-0.1.0", + "description": "All parameters for this operator", + "type": "object", + "properties": { + "advanced": { + "title": "advanced", + "description": "Options that most users don't need to adjust", + "priority": 20, + "type": "object", + "properties": { + "DOCKER_IMAGE": { + "title": "Main Docker Image", + "description": "Zookeeper Docker image.", + "default": "k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10", + "type": "string", + "advanced": true, + "hint": "Docker Image.", + "listName": "DOCKER_IMAGE" + }, + "DOCKER_IMAGE_PULL_POLICY": { + "title": "Main Docker Image Pull Policy", + "description": "Zookeeper Docker image pull policy.", + "default": "Always", + "enum": [ + "Always", + "IfNotPresent", + "Never" + ], + "type": "string", + "advanced": true, + "hint": "Pull policy.", + "listName": "DOCKER_IMAGE_PULL_POLICY" + } + }, + "required": [ + "DOCKER_IMAGE", + "DOCKER_IMAGE_PULL_POLICY" + ] + }, + "general": { + "title": "general", + "description": "All the standard parameters", + "priority": 10, + "type": "object", + "properties": { + "NODE_COUNT": { + "title": "Node Count", + "description": "The number of Zookeeper nodes to create for the cluster. Should be an odd number of nodes and at least 3 to create a useful quorum.", + "default": "3", + "type": "integer", + "hint": "Number of nodes.", + "listName": "NODE_COUNT" + }, + "NODE_CPU_LIMIT_MC": { + "title": "CPU Limit", + "description": "CPU limit for the containers.", + "default": "1000", + "type": "integer", + "hint": "Temporary allowed CPU limit in millicores.", + "listName": "NODE_CPU_LIMIT_MC" + }, + "NODE_CPU_MC": { + "title": "CPU Request", + "description": "CPU request for the containers.", + "default": "1000", + "type": "integer", + "hint": "Allowed CPU usage in millicores.", + "listName": "NODE_CPU_MC" + }, + "NODE_MEM_LIMIT_MIB": { + "title": "Memory Limit", + "description": "Memory limit for the Cassandra node containers.", + "default": "4096", + "type": "integer", + "hint": "Temporary allowed Memory limit in MiB.", + "listName": "NODE_MEM_LIMIT_MIB" + }, + "NODE_MEM_MIB": { + "title": "Memory Request", + "description": "Memory request for the containers.", + "default": "4096", + "type": "integer", + "hint": "Allowed Memory usage in MiB.", + "listName": "NODE_MEM_MIB" + } + }, + "required": [ + "NODE_COUNT", + "NODE_CPU_MC", + "NODE_CPU_LIMIT_MC", + "NODE_MEM_MIB", + "NODE_MEM_LIMIT_MIB" + ] + } + } +} diff --git a/pkg/kudoctl/cmd/testdata/schema-output.yaml.golden b/pkg/kudoctl/cmd/testdata/schema-output.yaml.golden new file mode 100644 index 000000000..a33ae3532 --- /dev/null +++ b/pkg/kudoctl/cmd/testdata/schema-output.yaml.golden @@ -0,0 +1,81 @@ +description: All parameters for this operator +properties: + advanced: + description: Options that most users don't need to adjust + priority: 20 + properties: + DOCKER_IMAGE: + advanced: true + default: k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10 + description: Zookeeper Docker image. + hint: Docker Image. + listName: DOCKER_IMAGE + title: Main Docker Image + type: string + DOCKER_IMAGE_PULL_POLICY: + advanced: true + default: Always + description: Zookeeper Docker image pull policy. + enum: + - Always + - IfNotPresent + - Never + hint: Pull policy. + listName: DOCKER_IMAGE_PULL_POLICY + title: Main Docker Image Pull Policy + type: string + required: + - DOCKER_IMAGE + - DOCKER_IMAGE_PULL_POLICY + title: advanced + type: object + general: + description: All the standard parameters + priority: 10 + properties: + NODE_COUNT: + default: "3" + description: The number of Zookeeper nodes to create for the cluster. Should be an odd number of nodes and at least 3 to create a useful quorum. + hint: Number of nodes. + listName: NODE_COUNT + title: Node Count + type: integer + NODE_CPU_LIMIT_MC: + default: "1000" + description: CPU limit for the containers. + hint: Temporary allowed CPU limit in millicores. + listName: NODE_CPU_LIMIT_MC + title: CPU Limit + type: integer + NODE_CPU_MC: + default: "1000" + description: CPU request for the containers. + hint: Allowed CPU usage in millicores. + listName: NODE_CPU_MC + title: CPU Request + type: integer + NODE_MEM_LIMIT_MIB: + default: "4096" + description: Memory limit for the Cassandra node containers. + hint: Temporary allowed Memory limit in MiB. + listName: NODE_MEM_LIMIT_MIB + title: Memory Limit + type: integer + NODE_MEM_MIB: + default: "4096" + description: Memory request for the containers. + hint: Allowed Memory usage in MiB. + listName: NODE_MEM_MIB + title: Memory Request + type: integer + required: + - NODE_COUNT + - NODE_CPU_MC + - NODE_CPU_LIMIT_MC + - NODE_MEM_MIB + - NODE_MEM_LIMIT_MIB + title: general + type: object +title: Parameters for zookeeper-3.4.10-0.1.0 +type: object + diff --git a/pkg/kudoctl/packages/convert/testdata/simple.json.golden b/pkg/kudoctl/packages/convert/testdata/simple.json.golden new file mode 100644 index 000000000..32603b6e4 --- /dev/null +++ b/pkg/kudoctl/packages/convert/testdata/simple.json.golden @@ -0,0 +1,33 @@ +{ + "title": "Parameters for Test", + "description": "All parameters for this operator", + "type": "object", + "properties": { + "my-param": { + "title": "MyParam", + "description": "My parameter description", + "default": "defaultVal", + "type": "string", + "immutable": true, + "hint": "My Param Hint.", + "trigger": "my-plan", + "listName": "my-param" + }, + "simplegroup": { + "title": "SimpleGroup", + "description": "The description for this group.", + "priority": 10, + "type": "object", + "properties": { + "grouped-param": { + "title": "GroupedParam", + "description": "My parameter description in a group", + "default": "23", + "type": "integer", + "hint": "My Group Param Hint.", + "listName": "grouped-param" + } + } + } + } +} diff --git a/pkg/kudoctl/packages/convert/testdata/simple.yaml.golden b/pkg/kudoctl/packages/convert/testdata/simple.yaml.golden new file mode 100644 index 000000000..7127abacd --- /dev/null +++ b/pkg/kudoctl/packages/convert/testdata/simple.yaml.golden @@ -0,0 +1,27 @@ +description: All parameters for this operator +properties: + my-param: + default: defaultVal + description: My parameter description + hint: My Param Hint. + immutable: true + listName: my-param + title: MyParam + trigger: my-plan + type: string + simplegroup: + description: The description for this group. + priority: 10 + properties: + grouped-param: + default: "23" + description: My parameter description in a group + hint: My Group Param Hint. + listName: grouped-param + title: GroupedParam + type: integer + title: SimpleGroup + type: object +title: Parameters for Test +type: object + From de3cb3c977e095d90f8e80b37100bd0659470aa2 Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Fri, 20 Nov 2020 17:00:10 +0100 Subject: [PATCH 9/9] Updated golden files Signed-off-by: Andreas Neumann --- .../cmd/testdata/deploy-kudo-ns.yaml.golden | 19 ++++++++++++ .../cmd/testdata/deploy-kudo-sa.yaml.golden | 19 ++++++++++++ .../cmd/testdata/deploy-kudo.json.golden | 29 +++++++++++++++++++ .../cmd/testdata/deploy-kudo.yaml.golden | 19 ++++++++++++ 4 files changed, 86 insertions(+) diff --git a/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden index 3f392f412..f1f11d17f 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden @@ -105,6 +105,19 @@ spec: connectionString: description: ConnectionString defines a templated string that can be used to connect to an instance of the Operator. type: string + groups: + items: + properties: + description: + type: string + displayName: + type: string + name: + type: string + prio: + type: integer + type: object + type: array operator: description: 'ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don''t make new APIs embed an underspecified API type they do not control. Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .' properties: @@ -134,6 +147,8 @@ spec: items: description: Parameter captures the variability of an OperatorVersion being instantiated in an instance. properties: + advanced: + type: boolean default: description: Default is a default value if no parameter is provided by the instance. type: string @@ -148,6 +163,10 @@ spec: items: type: string type: array + group: + type: string + hint: + 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-sa.yaml.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden index 26d5d1002..79038ce59 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden @@ -105,6 +105,19 @@ spec: connectionString: description: ConnectionString defines a templated string that can be used to connect to an instance of the Operator. type: string + groups: + items: + properties: + description: + type: string + displayName: + type: string + name: + type: string + prio: + type: integer + type: object + type: array operator: description: 'ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don''t make new APIs embed an underspecified API type they do not control. Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .' properties: @@ -134,6 +147,8 @@ spec: items: description: Parameter captures the variability of an OperatorVersion being instantiated in an instance. properties: + advanced: + type: boolean default: description: Default is a default value if no parameter is provided by the instance. type: string @@ -148,6 +163,10 @@ spec: items: type: string type: array + group: + type: string + hint: + 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.json.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo.json.golden index 86e43d777..d76a8e8f9 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo.json.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo.json.golden @@ -147,6 +147,26 @@ "description": "ConnectionString defines a templated string that can be used to connect to an instance of the Operator.", "type": "string" }, + "groups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "prio": { + "type": "integer" + } + } + } + }, "operator": { "description": "ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, \"must refer only to types A and B\" or \"UID not honored\" or \"name must be restricted\". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .", "type": "object", @@ -187,6 +207,9 @@ "description": "Parameter captures the variability of an OperatorVersion being instantiated in an instance.", "type": "object", "properties": { + "advanced": { + "type": "boolean" + }, "default": { "description": "Default is a default value if no parameter is provided by the instance.", "type": "string" @@ -206,6 +229,12 @@ "type": "string" } }, + "group": { + "type": "string" + }, + "hint": { + "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 1be6a7136..61d365863 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo.yaml.golden @@ -105,6 +105,19 @@ spec: connectionString: description: ConnectionString defines a templated string that can be used to connect to an instance of the Operator. type: string + groups: + items: + properties: + description: + type: string + displayName: + type: string + name: + type: string + prio: + type: integer + type: object + type: array operator: description: 'ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don''t make new APIs embed an underspecified API type they do not control. Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .' properties: @@ -134,6 +147,8 @@ spec: items: description: Parameter captures the variability of an OperatorVersion being instantiated in an instance. properties: + advanced: + type: boolean default: description: Default is a default value if no parameter is provided by the instance. type: string @@ -148,6 +163,10 @@ spec: items: type: string type: array + group: + type: string + hint: + type: string immutable: description: Specifies if the parameter can be changed after the initial installation of the operator type: boolean