mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
fn framework -- add support for patch templates
This commit is contained in:
@@ -603,3 +603,186 @@ func ExampleTemplateCommand_files() {
|
|||||||
// annotations:
|
// annotations:
|
||||||
// a: b
|
// a: b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExampleTemplateCommand_preprocess provides an example for using the TemplateCommand
|
||||||
|
// with PreProcess to configure the template based on the input resources observed.
|
||||||
|
func ExampleTemplateCommand_preprocess() {
|
||||||
|
config := &struct {
|
||||||
|
Key string `json:"key" yaml:"key"`
|
||||||
|
Value string `json:"value" yaml:"value"`
|
||||||
|
Short bool
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// create the template
|
||||||
|
cmd := framework.TemplateCommand{
|
||||||
|
// Template input
|
||||||
|
API: config,
|
||||||
|
PreProcess: func(rl *framework.ResourceList) error {
|
||||||
|
config.Short = len(rl.Items) < 3
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
// Template
|
||||||
|
Template: template.Must(template.New("example").Parse(`
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
{{ .Key }}: {{ .Value }}
|
||||||
|
{{- if .Short }}
|
||||||
|
short: 'true'
|
||||||
|
{{- end }}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: bar
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
{{ .Key }}: {{ .Value }}
|
||||||
|
{{- if .Short }}
|
||||||
|
short: 'true'
|
||||||
|
{{- end }}
|
||||||
|
`)),
|
||||||
|
}.GetCommand()
|
||||||
|
|
||||||
|
cmd.SetArgs([]string{filepath.Join("testdata", "template", "config.yaml")})
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// apiVersion: apps/v1
|
||||||
|
// kind: Deployment
|
||||||
|
// metadata:
|
||||||
|
// name: foo
|
||||||
|
// namespace: default
|
||||||
|
// annotations:
|
||||||
|
// a: b
|
||||||
|
// short: 'true'
|
||||||
|
// ---
|
||||||
|
// apiVersion: apps/v1
|
||||||
|
// kind: Deployment
|
||||||
|
// metadata:
|
||||||
|
// name: bar
|
||||||
|
// namespace: default
|
||||||
|
// annotations:
|
||||||
|
// a: b
|
||||||
|
// short: 'true'
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleTemplateCommand_postprocess provides an example for using the TemplateCommand
|
||||||
|
// with PostProcess to modify the results.
|
||||||
|
func ExampleTemplateCommand_postprocess() {
|
||||||
|
config := &struct {
|
||||||
|
Key string `json:"key" yaml:"key"`
|
||||||
|
Value string `json:"value" yaml:"value"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// create the template
|
||||||
|
cmd := framework.TemplateCommand{
|
||||||
|
// Template input
|
||||||
|
API: config,
|
||||||
|
// Template
|
||||||
|
Template: template.Must(template.New("example").Parse(`
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
{{ .Key }}: {{ .Value }}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: bar
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
{{ .Key }}: {{ .Value }}
|
||||||
|
`)),
|
||||||
|
PostProcess: func(rl *framework.ResourceList) error {
|
||||||
|
// trim the first resources
|
||||||
|
rl.Items = rl.Items[1:]
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}.GetCommand()
|
||||||
|
|
||||||
|
cmd.SetArgs([]string{filepath.Join("testdata", "template", "config.yaml")})
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// apiVersion: apps/v1
|
||||||
|
// kind: Deployment
|
||||||
|
// metadata:
|
||||||
|
// name: bar
|
||||||
|
// namespace: default
|
||||||
|
// annotations:
|
||||||
|
// a: b
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleTemplateCommand_patch provides an example for using the TemplateCommand to
|
||||||
|
// create a function which patches resources.
|
||||||
|
func ExampleTemplateCommand_patch() {
|
||||||
|
// patch the foo resource only
|
||||||
|
s := framework.Selector{Names: []string{"foo"}}
|
||||||
|
|
||||||
|
cmd := framework.TemplateCommand{
|
||||||
|
API: &struct {
|
||||||
|
Key string `json:"key" yaml:"key"`
|
||||||
|
Value string `json:"value" yaml:"value"`
|
||||||
|
}{},
|
||||||
|
Template: template.Must(template.New("example").Parse(`
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
{{ .Key }}: {{ .Value }}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: bar
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
{{ .Key }}: {{ .Value }}
|
||||||
|
`)),
|
||||||
|
// PatchTemplates are applied to BOTH ResourceList input resources AND templated resources
|
||||||
|
PatchTemplates: []framework.PatchTemplate{{
|
||||||
|
Selector: &s,
|
||||||
|
Template: template.Must(template.New("test").Parse(`
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
patched: 'true'
|
||||||
|
`)),
|
||||||
|
}},
|
||||||
|
}.GetCommand()
|
||||||
|
|
||||||
|
cmd.SetArgs([]string{filepath.Join("testdata", "template", "config.yaml")})
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// apiVersion: apps/v1
|
||||||
|
// kind: Deployment
|
||||||
|
// metadata:
|
||||||
|
// name: foo
|
||||||
|
// namespace: default
|
||||||
|
// annotations:
|
||||||
|
// a: b
|
||||||
|
// patched: 'true'
|
||||||
|
// ---
|
||||||
|
// apiVersion: apps/v1
|
||||||
|
// kind: Deployment
|
||||||
|
// metadata:
|
||||||
|
// name: bar
|
||||||
|
// namespace: default
|
||||||
|
// annotations:
|
||||||
|
// a: b
|
||||||
|
}
|
||||||
|
|||||||
@@ -256,6 +256,9 @@ type TemplateCommand struct {
|
|||||||
// Templates is a list of templates to render.
|
// Templates is a list of templates to render.
|
||||||
Templates []*template.Template
|
Templates []*template.Template
|
||||||
|
|
||||||
|
// PatchTemplates is a list of templates to render into Patches and apply.
|
||||||
|
PatchTemplates []PatchTemplate
|
||||||
|
|
||||||
// TemplateFiles list of templates to read from disk which are appended
|
// TemplateFiles list of templates to read from disk which are appended
|
||||||
// to Templates.
|
// to Templates.
|
||||||
TemplatesFiles []string
|
TemplatesFiles []string
|
||||||
@@ -342,6 +345,12 @@ func (tc TemplateCommand) GetCommand() cobra.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := range tc.PatchTemplates {
|
||||||
|
if err := tc.PatchTemplates[i].Apply(&rl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if tc.MergeResources {
|
if tc.MergeResources {
|
||||||
rl.Items, err = filters.MergeFilter{}.Filter(rl.Items)
|
rl.Items, err = filters.MergeFilter{}.Filter(rl.Items)
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ type ResultsChecker struct {
|
|||||||
// Defaults to "input*.yaml"
|
// Defaults to "input*.yaml"
|
||||||
InputFilenameGlob string
|
InputFilenameGlob string
|
||||||
|
|
||||||
// ExpectedOutputFilname is the file with the expected output of the function
|
// ExpectedOutputFilename is the file with the expected output of the function
|
||||||
// Defaults to "expected.yaml". Directories containing neither this file
|
// Defaults to "expected.yaml". Directories containing neither this file
|
||||||
// nore ExpectedErrorFilename will be skipped.
|
// nore ExpectedErrorFilename will be skipped.
|
||||||
ExpectedOutputFilname string
|
ExpectedOutputFilename string
|
||||||
|
|
||||||
// ExpectedErrorFilename is the file containing part of an expected error message
|
// ExpectedErrorFilename is the file containing part of an expected error message
|
||||||
// Defaults to "error.yaml". Directories containing neither this file
|
// Defaults to "error.yaml". Directories containing neither this file
|
||||||
@@ -57,8 +57,8 @@ func (rc ResultsChecker) Assert(t *testing.T) bool {
|
|||||||
if rc.ConfigInputFilename == "" {
|
if rc.ConfigInputFilename == "" {
|
||||||
rc.ConfigInputFilename = "config.yaml"
|
rc.ConfigInputFilename = "config.yaml"
|
||||||
}
|
}
|
||||||
if rc.ExpectedOutputFilname == "" {
|
if rc.ExpectedOutputFilename == "" {
|
||||||
rc.ExpectedOutputFilname = "expected.yaml"
|
rc.ExpectedOutputFilename = "expected.yaml"
|
||||||
}
|
}
|
||||||
if rc.ExpectedErrorFilename == "" {
|
if rc.ExpectedErrorFilename == "" {
|
||||||
rc.ExpectedErrorFilename = "error.yaml"
|
rc.ExpectedErrorFilename = "error.yaml"
|
||||||
@@ -90,11 +90,19 @@ func (rc ResultsChecker) compare(t *testing.T, path string) {
|
|||||||
// missing input
|
// missing input
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
args := []string{configPath}
|
||||||
|
|
||||||
|
expectedOutput, expectedError := rc.getExpected(t, path)
|
||||||
|
if expectedError == "" && expectedOutput == "" {
|
||||||
|
// missing expected
|
||||||
|
return
|
||||||
|
}
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
args := []string{configPath}
|
|
||||||
|
|
||||||
|
// run the test
|
||||||
|
t.Run(path, func(t *testing.T) {
|
||||||
if rc.InputFilenameGlob != "" {
|
if rc.InputFilenameGlob != "" {
|
||||||
inputs, err := filepath.Glob(filepath.Join(path, rc.InputFilenameGlob))
|
inputs, err := filepath.Glob(filepath.Join(path, rc.InputFilenameGlob))
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
@@ -109,12 +117,6 @@ func (rc ResultsChecker) compare(t *testing.T, path string) {
|
|||||||
cmd.SetOut(&actualOutput)
|
cmd.SetOut(&actualOutput)
|
||||||
cmd.SetErr(&actualError)
|
cmd.SetErr(&actualError)
|
||||||
|
|
||||||
expectedOutput, expectedError := rc.getExpected(t, path)
|
|
||||||
if expectedError == "" && expectedOutput == "" {
|
|
||||||
// missing expected
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmd.Execute()
|
err = cmd.Execute()
|
||||||
|
|
||||||
// Compae the results
|
// Compae the results
|
||||||
@@ -134,20 +136,21 @@ func (rc ResultsChecker) compare(t *testing.T, path string) {
|
|||||||
strings.TrimSpace(expectedError), actualOutput.String()) {
|
strings.TrimSpace(expectedError), actualOutput.String()) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// getExpected reads the expected results and error files
|
// getExpected reads the expected results and error files
|
||||||
func (rc ResultsChecker) getExpected(t *testing.T, path string) (string, string) {
|
func (rc ResultsChecker) getExpected(t *testing.T, path string) (string, string) {
|
||||||
// read the expected results
|
// read the expected results
|
||||||
var expectedOutput, expectedError string
|
var expectedOutput, expectedError string
|
||||||
if rc.ExpectedOutputFilname != "" {
|
if rc.ExpectedOutputFilename != "" {
|
||||||
_, err := os.Stat(filepath.Join(path, rc.ExpectedOutputFilname))
|
_, err := os.Stat(filepath.Join(path, rc.ExpectedOutputFilename))
|
||||||
if !os.IsNotExist(err) && err != nil {
|
if !os.IsNotExist(err) && err != nil {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// only read the file if it exists
|
// only read the file if it exists
|
||||||
b, err := ioutil.ReadFile(filepath.Join(path, rc.ExpectedOutputFilname))
|
b, err := ioutil.ReadFile(filepath.Join(path, rc.ExpectedOutputFilename))
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|||||||
202
kyaml/fn/framework/patch.go
Normal file
202
kyaml/fn/framework/patch.go
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package framework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/sets"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Applier applies some modification to a ResourceList
|
||||||
|
type Applier interface {
|
||||||
|
Apply(rl *ResourceList) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Applier = PatchTemplate{}
|
||||||
|
|
||||||
|
// PatchTemplate applies a patch to a collection of Resources
|
||||||
|
type PatchTemplate struct {
|
||||||
|
// Template is a template to render into one or more patches.
|
||||||
|
Template *template.Template
|
||||||
|
|
||||||
|
// Selector targets the rendered patch to specific resources.
|
||||||
|
Selector *Selector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies the patch to all matching resources in the list. The rl.FunctionConfig
|
||||||
|
// is provided to the template as input.
|
||||||
|
func (p PatchTemplate) Apply(rl *ResourceList) error {
|
||||||
|
if p.Selector == nil {
|
||||||
|
// programming error -- user shouldn't see this
|
||||||
|
return errors.Errorf("must specify PatchTemplate.Selector")
|
||||||
|
}
|
||||||
|
|
||||||
|
matches, err := p.Selector.GetMatches(rl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return p.apply(rl, p.Template, matches)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PatchTemplate) apply(rl *ResourceList, t *template.Template, matches []*yaml.RNode) error {
|
||||||
|
// render the patches
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := t.Execute(&b, rl.FunctionConfig); err != nil {
|
||||||
|
return errors.WrapPrefixf(err, "failed to render patch template %v", t.DefinedTemplates())
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the patches into RNodes
|
||||||
|
var nodes []*yaml.RNode
|
||||||
|
for _, s := range strings.Split(b.String(), "\n---\n") {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newNodes, err := (&kio.ByteReader{Reader: bytes.NewBufferString(s)}).Read()
|
||||||
|
if err != nil {
|
||||||
|
// create the debug string
|
||||||
|
lines := strings.Split(s, "\n")
|
||||||
|
for j := range lines {
|
||||||
|
lines[j] = fmt.Sprintf("%03d %s", j+1, lines[j])
|
||||||
|
}
|
||||||
|
s = strings.Join(lines, "\n")
|
||||||
|
return errors.WrapPrefixf(err, "failed to parse rendered patch template into a resource:\n%s\n", s)
|
||||||
|
}
|
||||||
|
nodes = append(nodes, newNodes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the patches to the matching resources
|
||||||
|
var err error
|
||||||
|
for j := range matches {
|
||||||
|
for i := range nodes {
|
||||||
|
matches[j], err = merge2.Merge(nodes[i], p.Selector.matches[j], yaml.MergeOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return errors.WrapPrefixf(err, "failed to merge templated patch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selector matches resources. A resource matches if and only if ALL of the Selector fields
|
||||||
|
// match the resource. An empty Selector matches all resources.
|
||||||
|
type Selector struct {
|
||||||
|
// Names is a list of metadata.names to match. If empty match all names.
|
||||||
|
// e.g. Names: ["foo", "bar"] matches if `metadata.name` is either "foo" or "bar".
|
||||||
|
Names []string `json:"names" yaml:"names"`
|
||||||
|
|
||||||
|
namesSet sets.String
|
||||||
|
|
||||||
|
// Namespaces is a list of metadata.namespaces to match. If empty match all namespaces.
|
||||||
|
// e.g. Namespaces: ["foo", "bar"] matches if `metadata.namespace` is either "foo" or "bar".
|
||||||
|
Namespaces []string `json:"namespaces" yaml:"namespaces"`
|
||||||
|
|
||||||
|
namespaceSet sets.String
|
||||||
|
|
||||||
|
// Kinds is a list of kinds to match. If empty match all kinds.
|
||||||
|
// e.g. Kinds: ["foo", "bar"] matches if `kind` is either "foo" or "bar".
|
||||||
|
Kinds []string `json:"kinds" yaml:"kinds"`
|
||||||
|
|
||||||
|
kindsSet sets.String
|
||||||
|
|
||||||
|
// APIVersions is a list of apiVersions to match. If empty apply match all apiVersions.
|
||||||
|
// e.g. APIVersions: ["foo/v1", "bar/v1"] matches if `apiVersion` is either "foo/v1" or "bar/v1".
|
||||||
|
APIVersions []string `json:"apiVersions" yaml:"apiVersions"`
|
||||||
|
|
||||||
|
apiVersionsSet sets.String
|
||||||
|
|
||||||
|
// Labels is a collection of labels to match. All labels must match exactly.
|
||||||
|
// e.g. Labels: {"foo": "bar", "baz": "buz"] matches if BOTH "foo" and "baz" labels match.
|
||||||
|
Labels map[string]string `json:"labels" yaml:"labels"`
|
||||||
|
|
||||||
|
// Annotations is a collection of annotations to match. All annotations must match exactly.
|
||||||
|
// e.g. Annotations: {"foo": "bar", "baz": "buz"] matches if BOTH "foo" and "baz" annotations match.
|
||||||
|
Annotations map[string]string `json:"annotations" yaml:"annotations"`
|
||||||
|
|
||||||
|
// Filter is an arbitrary filter function to match a resource.
|
||||||
|
// Selector matches if the function returns true.
|
||||||
|
Filter func(*yaml.RNode) bool
|
||||||
|
|
||||||
|
// matches contains a list of matching reosurces.
|
||||||
|
matches []*yaml.RNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMatches returns them matching resources from rl
|
||||||
|
func (s *Selector) GetMatches(rl *ResourceList) ([]*yaml.RNode, error) {
|
||||||
|
if err := s.init(rl); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.matches, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Selector) init(rl *ResourceList) error {
|
||||||
|
// index the selectors
|
||||||
|
s.matches = nil
|
||||||
|
s.kindsSet = sets.String{}
|
||||||
|
s.kindsSet.Insert(s.Kinds...)
|
||||||
|
s.apiVersionsSet = sets.String{}
|
||||||
|
s.apiVersionsSet.Insert(s.APIVersions...)
|
||||||
|
s.namesSet = sets.String{}
|
||||||
|
s.namesSet.Insert(s.Names...)
|
||||||
|
s.namespaceSet = sets.String{}
|
||||||
|
s.namespaceSet.Insert(s.Namespaces...)
|
||||||
|
|
||||||
|
//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
|
||||||
|
} else if !match {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.matches = append(s.matches, rl.Items[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isMatch returns true if r matches the patch selector
|
||||||
|
func (s *Selector) isMatch(r *yaml.RNode) (bool, error) {
|
||||||
|
m, err := r.GetMeta()
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
if s.kindsSet.Len() > 0 && !s.kindsSet.Has(m.Kind) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if s.apiVersionsSet.Len() > 0 && !s.apiVersionsSet.Has(m.APIVersion) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if s.namesSet.Len() > 0 && !s.namesSet.Has(m.Name) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if s.namespaceSet.Len() > 0 && !s.namespaceSet.Has(m.Namespace) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
for k := range s.Labels {
|
||||||
|
if m.Labels == nil || m.Labels[k] != s.Labels[k] {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k := range s.Annotations {
|
||||||
|
if m.Annotations == nil || m.Annotations[k] != s.Annotations[k] {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.Filter != nil {
|
||||||
|
if match := s.Filter(r); !match {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
81
kyaml/fn/framework/patch_test.go
Normal file
81
kyaml/fn/framework/patch_test.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package framework_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/fn/framework"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/fn/framework/frameworktestutil"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/testutil"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPatchTemplate(t *testing.T) {
|
||||||
|
// TODO: make this test pass on windows -- current failure seems spurious
|
||||||
|
testutil.SkipWindows(t)
|
||||||
|
|
||||||
|
cmdFn := func() cobra.Command {
|
||||||
|
type api struct {
|
||||||
|
Selector framework.Selector `json:"selector" yaml:"selector"`
|
||||||
|
A string `json:"a" yaml:"a"`
|
||||||
|
B string `json:"b" yaml:"b"`
|
||||||
|
Special string `json:"special" yaml:"special"`
|
||||||
|
LongList bool
|
||||||
|
}
|
||||||
|
var config api
|
||||||
|
filter := framework.Selector{
|
||||||
|
// this is a special manual filter for the Selector for when the built-in matchers
|
||||||
|
// are insufficient
|
||||||
|
Filter: func(rn *yaml.RNode) bool {
|
||||||
|
m, _ := rn.GetMeta()
|
||||||
|
return config.Special != "" && m.Annotations["foo"] == config.Special
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return framework.TemplateCommand{
|
||||||
|
API: &config,
|
||||||
|
PreProcess: func(rl *framework.ResourceList) error {
|
||||||
|
// do some extra processing based on the inputs
|
||||||
|
config.LongList = len(rl.Items) > 2
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
PatchTemplates: []framework.PatchTemplate{
|
||||||
|
{
|
||||||
|
// Apply these rendered patches
|
||||||
|
Template: template.Must(template.New("test").Parse(`
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: foo
|
||||||
|
image: example/sidecar:{{ .B }}
|
||||||
|
---
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
patched: '{{ .A }}'
|
||||||
|
{{- if .LongList }}
|
||||||
|
long: 'true'
|
||||||
|
{{- end }}
|
||||||
|
`)),
|
||||||
|
// Use the selector from the input
|
||||||
|
Selector: &config.Selector,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Apply these rendered patches
|
||||||
|
Template: template.Must(template.New("test").Parse(`
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
filterPatched: '{{ .A }}'
|
||||||
|
`)),
|
||||||
|
// Use an explicit selector
|
||||||
|
Selector: &filter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}.GetCommand()
|
||||||
|
}
|
||||||
|
|
||||||
|
frameworktestutil.ResultsChecker{Command: cmdFn, TestDataDirectory: "patchtestdata"}.Assert(t)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: patcher
|
||||||
|
selector:
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
a: a
|
||||||
|
b: b
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
patched: 'a'
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: foo
|
||||||
|
image: example/sidecar:b
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: patcher
|
||||||
|
selector:
|
||||||
|
apiVersions: ["apps/v1alpha1"]
|
||||||
|
a: a
|
||||||
|
b: b
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1alpha1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
patched: 'a'
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: foo
|
||||||
|
image: example/sidecar:b
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1alpha1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: patcher
|
||||||
|
a: a
|
||||||
|
b: b
|
||||||
|
special: bar
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
patched: 'a'
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: foo
|
||||||
|
image: example/sidecar:b
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
namespace: test
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
patched: 'a'
|
||||||
|
filterPatched: 'a'
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: foo
|
||||||
|
image: example/sidecar:b
|
||||||
17
kyaml/fn/framework/patchtestdata/filterselector/input.yaml
Normal file
17
kyaml/fn/framework/patchtestdata/filterselector/input.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
namespace: test
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: patcher
|
||||||
|
selector:
|
||||||
|
kinds: ["Deployment"]
|
||||||
|
a: a
|
||||||
|
b: b
|
||||||
23
kyaml/fn/framework/patchtestdata/kindselector/expected.yaml
Normal file
23
kyaml/fn/framework/patchtestdata/kindselector/expected.yaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
patched: 'a'
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: foo
|
||||||
|
image: example/sidecar:b
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
16
kyaml/fn/framework/patchtestdata/kindselector/input.yaml
Normal file
16
kyaml/fn/framework/patchtestdata/kindselector/input.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
10
kyaml/fn/framework/patchtestdata/labelselector/config.yaml
Normal file
10
kyaml/fn/framework/patchtestdata/labelselector/config.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: patcher
|
||||||
|
selector:
|
||||||
|
labels:
|
||||||
|
foo: bar
|
||||||
|
a: a
|
||||||
|
b: b
|
||||||
24
kyaml/fn/framework/patchtestdata/labelselector/expected.yaml
Normal file
24
kyaml/fn/framework/patchtestdata/labelselector/expected.yaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
labels:
|
||||||
|
baz: foo
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
labels:
|
||||||
|
foo: bar
|
||||||
|
annotations:
|
||||||
|
patched: 'a'
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: foo
|
||||||
|
image: example/sidecar:b
|
||||||
16
kyaml/fn/framework/patchtestdata/labelselector/input.yaml
Normal file
16
kyaml/fn/framework/patchtestdata/labelselector/input.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
labels:
|
||||||
|
baz: foo
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
labels:
|
||||||
|
foo: bar
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: patcher
|
||||||
|
selector:
|
||||||
|
kinds: ["Deployment"]
|
||||||
|
a: a
|
||||||
|
b: b
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
long: 'true'
|
||||||
|
patched: 'a'
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: foo
|
||||||
|
image: example/sidecar:b
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-3
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
long: 'true'
|
||||||
|
patched: 'a'
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: foo
|
||||||
|
image: example/sidecar:b
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-3
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: patcher
|
||||||
|
selector:
|
||||||
|
names: ["test-2"]
|
||||||
|
a: a
|
||||||
|
b: b
|
||||||
23
kyaml/fn/framework/patchtestdata/nameselector/expected.yaml
Normal file
23
kyaml/fn/framework/patchtestdata/nameselector/expected.yaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
patched: 'a'
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: foo
|
||||||
|
image: example/sidecar:b
|
||||||
16
kyaml/fn/framework/patchtestdata/nameselector/input.yaml
Normal file
16
kyaml/fn/framework/patchtestdata/nameselector/input.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: example.com/v1alpha1
|
||||||
|
kind: patcher
|
||||||
|
selector:
|
||||||
|
namespaces: ["test"]
|
||||||
|
a: a
|
||||||
|
b: b
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
namespace: test
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
patched: 'a'
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: foo
|
||||||
|
image: example/sidecar:b
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-1
|
||||||
|
annotations:
|
||||||
|
baz: foo
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-2
|
||||||
|
namespace: test
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
Reference in New Issue
Block a user