Support for framework.Selector substitutions

This commit is contained in:
Phillip Wittrock
2020-11-17 08:52:34 -08:00
parent 02e7589323
commit 8c4841c28f
3 changed files with 358 additions and 1 deletions

View File

@@ -6,6 +6,7 @@ package framework_test
import (
"bytes"
"fmt"
"os"
"path/filepath"
"text/template"
@@ -786,3 +787,106 @@ metadata:
// annotations:
// a: b
}
func ExampleSelector_templatizeKinds() {
type api struct {
KindName string `yaml:"kindName"`
}
rl := &framework.ResourceList{
FunctionConfig: &api{KindName: "Deployment"},
Reader: bytes.NewBufferString(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
namespace: default
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: bar
namespace: default
`),
Writer: os.Stdout,
}
if err := rl.Read(); err != nil {
panic(err)
}
var err error
s := &framework.Selector{
TemplatizeValues: true,
Kinds: []string{"{{ .KindName }}"},
}
rl.Items, err = s.GetMatches(rl)
if err != nil {
panic(err)
}
if err := rl.Write(); err != nil {
panic(err)
}
// Output:
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: foo
// namespace: default
// annotations:
// config.kubernetes.io/index: '0'
}
func ExampleSelector_templatizeAnnotations() {
type api struct {
Value string `yaml:"vaue"`
}
rl := &framework.ResourceList{
FunctionConfig: &api{Value: "bar"},
Reader: bytes.NewBufferString(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
namespace: default
annotations:
key: foo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
namespace: default
annotations:
key: bar
`),
Writer: os.Stdout,
}
if err := rl.Read(); err != nil {
panic(err)
}
var err error
s := &framework.Selector{
TemplatizeValues: true,
Annotations: map[string]string{"key": "{{ .Value }}"},
}
rl.Items, err = s.GetMatches(rl)
if err != nil {
panic(err)
}
if err := rl.Write(); err != nil {
panic(err)
}
// Output:
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: bar
// namespace: default
// annotations:
// key: bar
// config.kubernetes.io/index: '1'
}

View File

@@ -131,6 +131,10 @@ type Selector struct {
// matches contains a list of matching reosurces.
matches []*yaml.RNode
// TemplatizeValues if set to true will parse the selector values as templates
// and execute them with the functionConfig
TemplatizeValues bool
}
// GetMatches returns them matching resources from rl
@@ -141,7 +145,65 @@ func (s *Selector) GetMatches(rl *ResourceList) ([]*yaml.RNode, error) {
return s.matches, nil
}
// templatize templatizes the value
func (s *Selector) templatize(value string, api interface{}) (string, error) {
t, err := template.New("kinds").Parse(value)
if err != nil {
return "", errors.Wrap(err)
}
var b bytes.Buffer
err = t.Execute(&b, api)
if err != nil {
return "", errors.Wrap(err)
}
return b.String(), nil
}
func (s *Selector) templatizeSlice(values []string, api interface{}) error {
var err error
for i := range values {
values[i], err = s.templatize(values[i], api)
if err != nil {
return err
}
}
return nil
}
func (s *Selector) templatizeMap(values map[string]string, api interface{}) error {
var err error
for k := range values {
values[k], err = s.templatize(values[k], api)
if err != nil {
return err
}
}
return nil
}
func (s *Selector) init(rl *ResourceList) error {
if s.TemplatizeValues {
// templatize the selector values from the input configuration
if err := s.templatizeSlice(s.Kinds, rl.FunctionConfig); err != nil {
return err
}
if err := s.templatizeSlice(s.APIVersions, rl.FunctionConfig); err != nil {
return err
}
if err := s.templatizeSlice(s.Names, rl.FunctionConfig); err != nil {
return err
}
if err := s.templatizeSlice(s.Namespaces, rl.FunctionConfig); err != nil {
return err
}
if err := s.templatizeMap(s.Labels, rl.FunctionConfig); err != nil {
return err
}
if err := s.templatizeMap(s.Annotations, rl.FunctionConfig); err != nil {
return err
}
}
// index the selectors
s.matches = nil
s.kindsSet = sets.String{}
@@ -153,7 +215,7 @@ func (s *Selector) init(rl *ResourceList) error {
s.namespaceSet = sets.String{}
s.namespaceSet.Insert(s.Namespaces...)
//check each resource that matches the patch selector
// check each resource that matches the patch selector
for i := range rl.Items {
if match, err := s.isMatch(rl.Items[i]); err != nil {
return err

View File

@@ -4,10 +4,13 @@
package framework_test
import (
"bytes"
"strings"
"testing"
"text/template"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/fn/framework/frameworktestutil"
"sigs.k8s.io/kustomize/kyaml/testutil"
@@ -79,3 +82,191 @@ metadata:
frameworktestutil.ResultsChecker{Command: cmdFn, TestDataDirectory: "patchtestdata"}.Assert(t)
}
func TestSelector(t *testing.T) {
type Test struct {
// Name is the name of the test
Name string
// Fn configures the selector
Fn func(*framework.Selector)
// ValueFoo is the value to substitute to select the foo resource
ValueFoo string
// ValueBar is the value to substitute to select the bar resource
ValueBar string
// Value is set by the test to either ValueFoo or ValueBar
// and substituted into the selector
Value string
}
tests := []Test{
// Test the name template
{
Name: "names",
Fn: func(s *framework.Selector) {
s.Names = []string{"{{ .Value }}"}
},
ValueFoo: "foo",
ValueBar: "bar",
},
// Test the kind template
{
Name: "kinds",
Fn: func(s *framework.Selector) {
s.Kinds = []string{"{{ .Value }}"}
},
ValueFoo: "StatefulSet",
ValueBar: "Deployment",
},
// Test the apiVersion template
{
Fn: func(s *framework.Selector) {
s.APIVersions = []string{"{{ .Value }}"}
},
ValueFoo: "apps/v1beta1",
ValueBar: "apps/v1",
},
// Test the namespace template
{
Name: "namespaces",
Fn: func(s *framework.Selector) {
s.Namespaces = []string{"{{ .Value }}"}
},
ValueFoo: "foo-default",
ValueBar: "bar-default",
},
// Test the annotations template
{
Name: "annotations",
Fn: func(s *framework.Selector) {
s.Annotations = map[string]string{"key": "{{ .Value }}"}
},
ValueFoo: "foo-a",
ValueBar: "bar-a",
},
// Test the labels template
{
Name: "labels",
Fn: func(s *framework.Selector) {
s.Labels = map[string]string{"key": "{{ .Value }}"}
},
ValueFoo: "foo-l",
ValueBar: "bar-l",
},
}
// input is the input resources that are selected
input := `
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: foo
namespace: foo-default
annotations:
key: foo-a
labels:
key: foo-l
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
namespace: bar-default
annotations:
key: bar-a
labels:
key: bar-l
`
// expectedFoo is the expected output when the FooValue is substituted
expectedFoo := `
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: foo
namespace: foo-default
annotations:
key: foo-a
config.kubernetes.io/index: '0'
labels:
key: foo-l
`
// expectedFoo is the expected output when the BarValue is substituted
expectedBar := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
namespace: bar-default
annotations:
key: bar-a
config.kubernetes.io/index: '1'
labels:
key: bar-l
`
// Run the tests by substituting the FooValues
var err error
for i := range tests {
test := tests[i]
t.Run(tests[i].Name+"-foo", func(t *testing.T) {
test.Value = test.ValueFoo
var out bytes.Buffer
rl := &framework.ResourceList{
FunctionConfig: test,
Reader: bytes.NewBufferString(input),
Writer: &out,
}
if !assert.NoError(t, rl.Read()) {
t.FailNow()
}
s := &framework.Selector{TemplatizeValues: true}
test.Fn(s)
rl.Items, err = s.GetMatches(rl)
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, rl.Write()) {
t.FailNow()
}
if !assert.Equal(t, strings.TrimSpace(expectedFoo), strings.TrimSpace(out.String())) {
t.FailNow()
}
})
}
// Run the tests by substituting the BarValues
for i := range tests {
test := tests[i]
t.Run(tests[i].Name+"-bar", func(t *testing.T) {
test.Value = test.ValueBar
var out bytes.Buffer
rl := &framework.ResourceList{
FunctionConfig: test,
Reader: bytes.NewBufferString(input),
Writer: &out,
}
if !assert.NoError(t, rl.Read()) {
t.FailNow()
}
s := &framework.Selector{TemplatizeValues: true}
test.Fn(s)
rl.Items, err = s.GetMatches(rl)
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, rl.Write()) {
t.FailNow()
}
if !assert.Equal(t, strings.TrimSpace(expectedBar), strings.TrimSpace(out.String())) {
t.FailNow()
}
})
}
}