diff --git a/kyaml/fieldmeta/fieldmeta.go b/kyaml/fieldmeta/fieldmeta.go index 07a9ca7a6..0e83a8767 100644 --- a/kyaml/fieldmeta/fieldmeta.go +++ b/kyaml/fieldmeta/fieldmeta.go @@ -17,7 +17,7 @@ import ( type FieldMeta struct { Schema spec.Schema - Extensions XKustomize + Extensions *XKustomize } type XKustomize struct { @@ -64,7 +64,11 @@ func (fm *FieldMeta) Read(n *yaml.RNode) error { // Write writes the FieldMeta to a node func (fm *FieldMeta) Write(n *yaml.RNode) error { - fm.Schema.VendorExtensible.AddExtension("x-kustomize", fm.Extensions) + if fm.Extensions != nil { + fm.Schema.VendorExtensible.AddExtension("x-kustomize", fm.Extensions) + } else { + delete(fm.Schema.VendorExtensible.Extensions, "x-kustomize") + } b, err := json.Marshal(fm.Schema) if err != nil { return errors.Wrap(err) diff --git a/kyaml/setters2/add.go b/kyaml/setters2/add.go new file mode 100644 index 000000000..9dac90cfb --- /dev/null +++ b/kyaml/setters2/add.go @@ -0,0 +1,74 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package setters2 + +import ( + "strings" + + "github.com/go-openapi/spec" + "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/fieldmeta" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// Add creates or updates setter or substitution references from resource fields. +// Requires that at least one of FieldValue and FieldName have been set. +type Add struct { + // FieldValue if set will add the OpenAPI reference to fields if they have this value. + // Optional. If unspecified match all field values. + FieldValue string + + // FieldName if set will add the OpenAPI reference to fields with this name or path + // FieldName may be the full name of the field, full path to the field, or the path suffix. + // e.g. all of the following would match spec.template.spec.containers.image -- + // [image, containers.image, spec.containers.image, template.spec.containers.image, + // spec.template.spec.containers.image] + // Optional. If unspecified match all field names. + FieldName string + + // Ref is the OpenAPI reference to set on the matching fields as a comment. + Ref string +} + +// Filter implements yaml.Filter +func (a *Add) Filter(object *yaml.RNode) (*yaml.RNode, error) { + if a.FieldName == "" && a.FieldValue == "" { + return nil, errors.Errorf("must specify either fieldName or fieldValue") + } + if a.Ref == "" { + return nil, errors.Errorf("must specify ref") + } + return object, accept(a, object) +} + +// visitScalar implements visitor +// visitScalar will set the field metadata on each scalar field whose name + value match +func (a *Add) visitScalar(object *yaml.RNode, p string) error { + // check if the field matches + if a.FieldName != "" && !strings.HasSuffix(p, a.FieldName) { + return nil + } + if a.FieldValue != "" && a.FieldValue != object.YNode().Value { + return nil + } + + // read the field metadata + fm := fieldmeta.FieldMeta{} + if err := fm.Read(object); err != nil { + return err + } + + // create the ref on the field metadata + r, err := spec.NewRef(a.Ref) + if err != nil { + return err + } + fm.Schema.Ref = r + + // write the field metadata + if err := fm.Write(object); err != nil { + return err + } + return nil +} diff --git a/kyaml/setters2/add_test.go b/kyaml/setters2/add_test.go new file mode 100644 index 000000000..8a867c63e --- /dev/null +++ b/kyaml/setters2/add_test.go @@ -0,0 +1,206 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package setters2 + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func TestAdd_Filter(t *testing.T) { + var tests = []struct { + name string + add Add + input string + expected string + err string + }{ + { + name: "add-replicas", + add: Add{ + FieldValue: "3", + Ref: "#/definitions/io.k8s.cli.setters.replicas", + }, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 + `, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} + `, + }, + { + name: "add-replicas-annotations", + add: Add{ + FieldValue: "3", + Ref: "#/definitions/io.k8s.cli.setters.replicas", + }, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + something: 3 +spec: + replicas: 3 + `, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + something: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} +spec: + replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} + `, + }, + { + name: "add-replicas-name", + add: Add{ + FieldValue: "3", + FieldName: "replicas", + Ref: "#/definitions/io.k8s.cli.setters.replicas", + }, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + something: 3 +spec: + replicas: 3 + `, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + something: 3 +spec: + replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} + `, + }, + { + name: "add-replicas-2x", + add: Add{ + FieldValue: "3", + FieldName: "replicas", + Ref: "#/definitions/io.k8s.cli.setters.replicas", + }, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + replicas: 3 +spec: + replicas: 3 + `, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} +spec: + replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} + `, + }, + { + name: "add-replicas-1x", + add: Add{ + FieldValue: "3", + FieldName: "spec.replicas", + Ref: "#/definitions/io.k8s.cli.setters.replicas", + }, + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + replicas: 3 +spec: + replicas: 3 + `, + expected: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + replicas: 3 +spec: + replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} + `, + }, + { + name: "add-replicas-error", + add: Add{ + Ref: "#/definitions/io.k8s.cli.setters.replicas", + }, + err: "must specify either fieldName or fieldValue", + input: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 + `, + }, + } + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + // parse the input to be modified + r, err := yaml.Parse(test.input) + if !assert.NoError(t, err) { + t.FailNow() + } + + // invoke add + result, err := test.add.Filter(r) + if test.err != "" { + if !assert.Equal(t, test.err, err.Error()) { + t.FailNow() + } + return + } + + if !assert.NoError(t, err) { + t.FailNow() + } + + // compare the actual and expected output + actual, err := result.String() + if !assert.NoError(t, err) { + t.FailNow() + } + actual = strings.TrimSpace(actual) + expected := strings.TrimSpace(test.expected) + if !assert.Equal(t, expected, actual) { + t.FailNow() + } + }) + } +} diff --git a/kyaml/setters2/doc.go b/kyaml/setters2/doc.go index 5eaa60a98..90ae3ed90 100644 --- a/kyaml/setters2/doc.go +++ b/kyaml/setters2/doc.go @@ -154,4 +154,10 @@ // to "1.8.2", then calling either Set{Name: "image-name"}.Filter(deployment) or // Set{Name: "image-tag"}.Filter(deployment) would update the Deployment field // spec.template.spec.container[name=nginx].image from "nginx:1.8.1" to "nginx:1.8.2". +// +// Adding Field References +// +// References to setters and substitutions may be added to fields using the Add Filter. +// Add will write a JSON OpenAPI string as a comment to any fields matching the specified +// FieldName add FieldValue. package setters2 diff --git a/kyaml/setters2/example_test.go b/kyaml/setters2/example_test.go index 1f6e06ebb..10e3c918a 100644 --- a/kyaml/setters2/example_test.go +++ b/kyaml/setters2/example_test.go @@ -128,3 +128,75 @@ spec: // - name: nginx // image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} } + +// ExampleAdd demonstrates adding a setter reference to fields. +func ExampleAdd_fieldName() { + deployment := ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + something: 3 +spec: + replicas: 3 +` + + object := yaml.MustParse(deployment) // parse the configuration + err := object.PipeE(&Add{ + Ref: "#/definitions/io.k8s.cli.setters.replicas", + FieldName: "replicas", + }) + if err != nil { + panic(err) + } + + // Print the object with the update value + fmt.Println(object.MustString()) + + // Output: + // apiVersion: apps/v1 + // kind: Deployment + // metadata: + // name: nginx-deployment + // annotations: + // something: 3 + // spec: + // replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} +} + +// ExampleAdd demonstrates adding a setter reference to fields. +func ExampleAdd_fieldValue() { + deployment := ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + something: 3 +spec: + replicas: 3 +` + + object := yaml.MustParse(deployment) // parse the configuration + err := object.PipeE(&Add{ + Ref: "#/definitions/io.k8s.cli.setters.replicas", + FieldValue: "3", + }) + if err != nil { + panic(err) + } + + // Print the object with the update value + fmt.Println(object.MustString()) + + // Output: + // apiVersion: apps/v1 + // kind: Deployment + // metadata: + // name: nginx-deployment + // annotations: + // something: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} + // spec: + // replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"} +}