diff --git a/kyaml/setters2/list.go b/kyaml/setters2/list.go new file mode 100644 index 000000000..a0436732f --- /dev/null +++ b/kyaml/setters2/list.go @@ -0,0 +1,117 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package setters2 + +import ( + "sort" + "strings" + + "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/openapi" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// List lists the setters specified in the OpenAPI +type List struct { + Name string + + Setters []SetterDefinition +} + +// List initializes l.Setters with the setters from the OpenAPI definitions in the file +func (l *List) List(openAPIPath, resourcePath string) error { + if err := openapi.AddSchemaFromFile(openAPIPath); err != nil { + return err + } + y, err := yaml.ReadFile(openAPIPath) + if err != nil { + return err + } + return l.list(y, resourcePath) +} + +func (l *List) list(object *yaml.RNode, resourcePath string) error { + // read the OpenAPI definitions + def, err := object.Pipe(yaml.LookupCreate(yaml.MappingNode, "openAPI", "definitions")) + if err != nil { + return err + } + if yaml.IsEmpty(def) { + return nil + } + + // iterate over definitions -- find those that are setters + err = def.VisitFields(func(node *yaml.MapNode) error { + setter := SetterDefinition{} + + // the definition key -- contains the setter name + key := node.Key.YNode().Value + + if !strings.HasPrefix(key, SetterDefinitionPrefix) { + // not a setter -- doesn't have the right prefix + return nil + } + + setterNode, err := node.Value.Pipe(yaml.Lookup(K8sCliExtensionKey, "setter")) + if err != nil { + return err + } + if yaml.IsEmpty(setterNode) { + // has the setter prefix, but missing the setter extension + return errors.Errorf("missing x-k8s-cli.setter for %s", key) + } + + // unmarshal the yaml for the setter extension into the definition struct + b, err := setterNode.String() + if err != nil { + return err + } + if err := yaml.Unmarshal([]byte(b), &setter); err != nil { + return err + } + + if l.Name != "" && l.Name != setter.Name { + // not the setter that was requested by list + return nil + } + + // the description is not part of the extension, and should be pulled out + // separately from the extension values. + description := node.Value.Field("description") + if description != nil { + setter.Description = description.Value.YNode().Value + } + + // count the number of fields set by this setter + setter.Count, err = l.count(resourcePath, setter.Name) + if err != nil { + return err + } + + l.Setters = append(l.Setters, setter) + return nil + }) + if err != nil { + return err + } + + // sort the setters by their name + sort.Slice(l.Setters, func(i, j int) bool { + return l.Setters[i].Name < l.Setters[j].Name + }) + + return nil +} + +// count returns the number of fields set by the setter with name +func (l *List) count(path, name string) (int, error) { + s := &Set{Name: name} + err := kio.Pipeline{ + Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: path}}, + Filters: []kio.Filter{kio.FilterAll(s)}, + }.Execute() + + return s.Count, err +} diff --git a/kyaml/setters2/list_test.go b/kyaml/setters2/list_test.go new file mode 100644 index 000000000..4b1a24874 --- /dev/null +++ b/kyaml/setters2/list_test.go @@ -0,0 +1,289 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package setters2 + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/kyaml/openapi" +) + +func TestList(t *testing.T) { + var tests = []struct { + name string + setter string + openapi string + input string + expected []SetterDefinition + }{ + { + name: "list-replicas", + openapi: ` +openAPI: + definitions: + io.k8s.cli.setters.replicas: + x-k8s-cli: + setter: + name: replicas + value: "3" + setBy: me + description: "hello world" + `, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} + `, + expected: []SetterDefinition{ + {Name: "replicas", Value: "3", SetBy: "me", Description: "hello world", Count: 1}, + }, + }, + { + name: "list-multiple", + openapi: ` +openAPI: + definitions: + io.k8s.cli.setters.replicas: + description: "hello world 1" + x-k8s-cli: + setter: + name: replicas + value: "3" + setBy: me1 + io.k8s.cli.setters.image: + description: "hello world 2" + x-k8s-cli: + setter: + name: image + value: "nginx" + setBy: me2 + io.k8s.cli.setters.tag: + description: "hello world 3" + x-k8s-cli: + setter: + name: tag + value: "1.7.9" + setBy: me3 + io.k8s.cli.substitutions.image: + x-k8s-cli: + substitution: + name: image + pattern: IMAGE:TAG + values: + - marker: IMAGE + ref: '#/definitions/io.k8s.cli.setters.image' + - marker: TAG + ref: '#/definitions/io.k8s.cli.setters.tag' + `, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} + template: + spec: + containers: + - name: nginx + image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} + - name: nginx2 + image: nginx # {"$ref": "#/definitions/io.k8s.cli.setters.image"} + `, + expected: []SetterDefinition{ + {Name: "image", Value: "nginx", SetBy: "me2", Description: "hello world 2", Count: 2}, + {Name: "replicas", Value: "3", SetBy: "me1", Description: "hello world 1", Count: 1}, + {Name: "tag", Value: "1.7.9", SetBy: "me3", Description: "hello world 3", Count: 1}, + }, + }, + { + name: "list-multiple-resources", + openapi: ` +openAPI: + definitions: + io.k8s.cli.setters.replicas: + description: "hello world 1" + x-k8s-cli: + setter: + name: replicas + value: "3" + setBy: me1 + io.k8s.cli.setters.image: + description: "hello world 2" + x-k8s-cli: + setter: + name: image + value: "nginx" + setBy: me2 + io.k8s.cli.setters.tag: + description: "hello world 3" + x-k8s-cli: + setter: + name: tag + value: "1.7.9" + setBy: me3 + io.k8s.cli.substitutions.image: + x-k8s-cli: + substitution: + name: image + pattern: IMAGE:TAG + values: + - marker: IMAGE + ref: '#/definitions/io.k8s.cli.setters.image' + - marker: TAG + ref: '#/definitions/io.k8s.cli.setters.tag' + `, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-1 +spec: + replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} + template: + spec: + containers: + - name: nginx + image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} + - name: nginx2 + image: nginx # {"$ref": "#/definitions/io.k8s.cli.setters.image"} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-2 +spec: + replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} + template: + spec: + containers: + - name: nginx + image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} + - name: nginx2 + image: nginx +`, + expected: []SetterDefinition{ + {Name: "image", Value: "nginx", SetBy: "me2", Description: "hello world 2", Count: 3}, + {Name: "replicas", Value: "3", SetBy: "me1", Description: "hello world 1", Count: 2}, + {Name: "tag", Value: "1.7.9", SetBy: "me3", Description: "hello world 3", Count: 2}, + }, + }, + { + name: "list-name", + openapi: ` +openAPI: + definitions: + io.k8s.cli.setters.replicas: + description: "hello world 1" + x-k8s-cli: + setter: + name: replicas + value: "3" + setBy: me1 + io.k8s.cli.setters.image: + description: "hello world 2" + x-k8s-cli: + setter: + name: image + value: "nginx" + setBy: me2 + io.k8s.cli.setters.tag: + description: "hello world 3" + x-k8s-cli: + setter: + name: tag + value: "1.7.9" + setBy: me3 + io.k8s.cli.substitutions.image: + x-k8s-cli: + substitution: + name: image + pattern: IMAGE:TAG + values: + - marker: IMAGE + ref: '#/definitions/io.k8s.cli.setters.image' + - marker: TAG + ref: '#/definitions/io.k8s.cli.setters.tag' + `, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-1 +spec: + replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} + template: + spec: + containers: + - name: nginx + image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} + - name: nginx2 + image: nginx # {"$ref": "#/definitions/io.k8s.cli.setters.image"} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-2 +spec: + replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} + template: + spec: + containers: + - name: nginx + image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} + - name: nginx2 + image: nginx +`, + setter: "image", + expected: []SetterDefinition{ + {Name: "image", Value: "nginx", SetBy: "me2", Description: "hello world 2", Count: 3}, + }, + }, + } + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + // reset the openAPI afterward + defer openapi.ResetOpenAPI() + initSchema(t, test.openapi) + + f, err := ioutil.TempFile("", "k8s-cli-") + if !assert.NoError(t, err) { + t.FailNow() + } + defer os.Remove(f.Name()) + err = ioutil.WriteFile(f.Name(), []byte(test.openapi), 0600) + if !assert.NoError(t, err) { + t.FailNow() + } + + r, err := ioutil.TempFile("", "k8s-cli-*.yaml") + if !assert.NoError(t, err) { + t.FailNow() + } + defer os.Remove(r.Name()) + err = ioutil.WriteFile(r.Name(), []byte(test.input), 0600) + if !assert.NoError(t, err) { + t.FailNow() + } + + // invoke the setter + instance := &List{Name: test.setter} + err = instance.List(f.Name(), r.Name()) + if !assert.NoError(t, err) { + t.FailNow() + } + + if !assert.Equal(t, test.expected, instance.Setters) { + t.FailNow() + } + }) + } +}