Function framework support for patching containers

This commit is contained in:
Phillip Wittrock
2020-11-19 10:00:49 -08:00
parent a6833bc4c0
commit be5db09db1
3 changed files with 299 additions and 0 deletions

View File

@@ -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
}

View File

@@ -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
// <nil>
}
// 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
// <nil>
}

View File

@@ -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.