From be5db09db1c1d2b4f928b468bf411f0ff607d560 Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Thu, 19 Nov 2020 10:00:49 -0800 Subject: [PATCH] Function framework support for patching containers --- kyaml/fn/framework/containers.go | 60 ++++++++ kyaml/fn/framework/example_test.go | 225 +++++++++++++++++++++++++++++ kyaml/kio/byteio_reader.go | 14 ++ 3 files changed, 299 insertions(+) create mode 100644 kyaml/fn/framework/containers.go diff --git a/kyaml/fn/framework/containers.go b/kyaml/fn/framework/containers.go new file mode 100644 index 000000000..bfdce2a27 --- /dev/null +++ b/kyaml/fn/framework/containers.go @@ -0,0 +1,60 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package framework + +import ( + "bytes" + "text/template" + + "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/sets" + "sigs.k8s.io/kustomize/kyaml/yaml" + "sigs.k8s.io/kustomize/kyaml/yaml/merge2" +) + +// PatchTemplateContainers executes t as a template and patches each container in each resource +// with the result. +func PatchTemplateContainers(resources []*yaml.RNode, t string, input interface{}, containers ...string) error { + resourcePatch := template.Must(template.New("containers").Parse(t)) + var b bytes.Buffer + if err := resourcePatch.Execute(&b, input); err != nil { + return errors.Wrap(err) + } + patch, err := yaml.Parse(b.String()) + if err != nil { + return errors.WrapPrefixf(err, b.String()) + } + return PatchContainers(resources, patch, containers...) +} + +// PatchContainers applies patch to each container in each resource. +func PatchContainers(resources []*yaml.RNode, patch *yaml.RNode, containers ...string) error { + names := sets.String{} + names.Insert(containers...) + + for i := range resources { + containers, err := resources[i].Pipe(yaml.Lookup("spec", "template", "spec", "containers")) + if err != nil { + return errors.Wrap(err) + } + if containers == nil { + continue + } + err = containers.VisitElements(func(node *yaml.RNode) error { + f := node.Field("name") + if f == nil { + return nil + } + if names.Len() > 0 && !names.Has(yaml.GetValue(f.Value)) { + return nil + } + _, err := merge2.Merge(patch, node, yaml.MergeOptions{}) + return errors.Wrap(err) + }) + if err != nil { + return errors.Wrap(err) + } + } + return nil +} diff --git a/kyaml/fn/framework/example_test.go b/kyaml/fn/framework/example_test.go index 28548dfdb..8f762258c 100644 --- a/kyaml/fn/framework/example_test.go +++ b/kyaml/fn/framework/example_test.go @@ -6,12 +6,14 @@ package framework_test import ( "bytes" "fmt" + "log" "os" "path/filepath" "text/template" "github.com/spf13/pflag" "sigs.k8s.io/kustomize/kyaml/fn/framework" + "sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/yaml" ) @@ -890,3 +892,226 @@ metadata: // key: bar // config.kubernetes.io/index: '1' } + +// ExamplePatchTemplateContainers_names patches all containers. +func ExamplePatchTemplateContainers() { + resources, err := kio.ParseAll(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: foo +spec: + template: + spec: + containers: + - name: foo + image: a + - name: bar + image: b +--- +apiVersion: v1 +kind: Service +metadata: + name: foo +spec: + selector: + foo: bar +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: bar +spec: + template: + spec: + containers: + - name: foo + image: a + - name: baz + image: b +--- +apiVersion: v1 +kind: Service +metadata: + name: bar +spec: + selector: + foo: bar +`) + if err != nil { + log.Fatal(err) + } + + input := struct{ Value string }{Value: "new-value"} + err = framework.PatchTemplateContainers(resources, ` +env: + KEY: {{ .Value }} +`, input) + if err != nil { + log.Fatal(err) + } + + fmt.Println(kio.StringAll(resources)) + + // Output: + // apiVersion: apps/v1 + // kind: Deployment + // metadata: + // name: foo + // spec: + // template: + // spec: + // containers: + // - name: foo + // image: a + // env: + // KEY: new-value + // - name: bar + // image: b + // env: + // KEY: new-value + // --- + // apiVersion: v1 + // kind: Service + // metadata: + // name: foo + // spec: + // selector: + // foo: bar + // --- + // apiVersion: apps/v1 + // kind: Deployment + // metadata: + // name: bar + // spec: + // template: + // spec: + // containers: + // - name: foo + // image: a + // env: + // KEY: new-value + // - name: baz + // image: b + // env: + // KEY: new-value + // --- + // apiVersion: v1 + // kind: Service + // metadata: + // name: bar + // spec: + // selector: + // foo: bar + // +} + +// ExamplePatchTemplateContainers_names patches containers matching +// a specific name. +func ExamplePatchTemplateContainers_names() { + resources, err := kio.ParseAll(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: foo +spec: + template: + spec: + containers: + - name: foo + image: a + - name: bar + image: b +--- +apiVersion: v1 +kind: Service +metadata: + name: foo +spec: + selector: + foo: bar +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: bar +spec: + template: + spec: + containers: + - name: foo + image: a + - name: baz + image: b +--- +apiVersion: v1 +kind: Service +metadata: + name: bar +spec: + selector: + foo: bar +`) + if err != nil { + log.Fatal(err) + } + + input := struct{ Value string }{Value: "new-value"} + err = framework.PatchTemplateContainers(resources, ` +env: + KEY: {{ .Value }} +`, input, "foo") + if err != nil { + log.Fatal(err) + } + + fmt.Println(kio.StringAll(resources)) + + // Output: + // apiVersion: apps/v1 + // kind: Deployment + // metadata: + // name: foo + // spec: + // template: + // spec: + // containers: + // - name: foo + // image: a + // env: + // KEY: new-value + // - name: bar + // image: b + // --- + // apiVersion: v1 + // kind: Service + // metadata: + // name: foo + // spec: + // selector: + // foo: bar + // --- + // apiVersion: apps/v1 + // kind: Deployment + // metadata: + // name: bar + // spec: + // template: + // spec: + // containers: + // - name: foo + // image: a + // env: + // KEY: new-value + // - name: baz + // image: b + // --- + // apiVersion: v1 + // kind: Service + // metadata: + // name: bar + // spec: + // selector: + // foo: bar + // +} diff --git a/kyaml/kio/byteio_reader.go b/kyaml/kio/byteio_reader.go index 7d0aeda79..97f3d2e48 100644 --- a/kyaml/kio/byteio_reader.go +++ b/kyaml/kio/byteio_reader.go @@ -78,6 +78,20 @@ func (rw *ByteReadWriter) Write(nodes []*yaml.RNode) error { }.Write(nodes) } +// ParseAll reads all of the inputs into resources +func ParseAll(inputs ...string) ([]*yaml.RNode, error) { + return (&ByteReader{ + Reader: bytes.NewBufferString(strings.Join(inputs, "\n---\n")), + }).Read() +} + +// StringAll writes all of the resources to a string +func StringAll(resources []*yaml.RNode) (string, error) { + var b bytes.Buffer + err := (&ByteWriter{Writer: &b}).Write(resources) + return b.String(), err +} + // ByteReader decodes ResourceNodes from bytes. // By default, Read will set the config.kubernetes.io/index annotation on each RNode as it // is read so they can be written back in the same order.