Add annotations filter based on kyaml libraries

This commit is contained in:
Morten Torkildsen
2020-03-25 20:25:43 -07:00
parent 114e676cb1
commit f39f28d38f
8 changed files with 433 additions and 24 deletions

View File

@@ -4,6 +4,8 @@
package builtins
import (
"sigs.k8s.io/kustomize/api/filters/annotations"
"sigs.k8s.io/kustomize/api/filters/filtersutil"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/transform"
"sigs.k8s.io/kustomize/api/types"
@@ -14,6 +16,10 @@ import (
type AnnotationsTransformerPlugin struct {
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
// YAMLSupport can be set to true to use the kyaml filter instead of the
// kunstruct transformer
YAMLSupport bool
}
func (p *AnnotationsTransformerPlugin) Config(
@@ -24,14 +30,27 @@ func (p *AnnotationsTransformerPlugin) Config(
}
func (p *AnnotationsTransformerPlugin) Transform(m resmap.ResMap) error {
t, err := transform.NewMapTransformer(
p.FieldSpecs,
p.Annotations,
)
if err != nil {
return err
if p.YAMLSupport {
for _, r := range m.Resources() {
err := filtersutil.ApplyToJSON(annotations.Filter{
Annotations: p.Annotations,
FsSlice: p.FieldSpecs,
}, r.Kunstructured)
if err != nil {
return err
}
}
return nil
} else {
t, err := transform.NewMapTransformer(
p.FieldSpecs,
p.Annotations,
)
if err != nil {
return err
}
return t.Transform(m)
}
return t.Transform(m)
}
func NewAnnotationsTransformerPlugin() resmap.TransformerPlugin {

View File

@@ -0,0 +1,44 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package annotations
import (
"sigs.k8s.io/kustomize/api/filters/fsslice"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type Filter struct {
// Annotations is the set of annotations to apply to the inputs
Annotations map[string]string `yaml:"annotations,omitempty"`
// FsSlice contains the FieldSpecs to locate the namespace field
FsSlice types.FsSlice
}
var _ kio.Filter = Filter{}
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
for i := range nodes {
if err := f.run(nodes[i]); err != nil {
return nil, err
}
}
return nodes, nil
}
// run applies the filter to a single node.
func (f Filter) run(node *yaml.RNode) error {
for key, value := range f.Annotations {
if err := node.PipeE(fsslice.Filter{
FsSlice: f.FsSlice,
SetValue: fsslice.SetEntry(key, value),
CreateKind: yaml.MappingNode, // Annotations are MappingNodes.
}); err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,246 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package annotations
import (
"bytes"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestAnnotations_Filter(t *testing.T) {
testCases := map[string]struct {
input string
expectedOutput string
filter Filter
fsslice types.FsSlice
}{
"add": {
input: `
apiVersion: example.com/v1
kind: Foo
metadata:
name: instance
---
apiVersion: example.com/v1
kind: Bar
metadata:
name: instance
`,
expectedOutput: `
apiVersion: example.com/v1
kind: Foo
metadata:
name: instance
annotations:
sleater: kinney
---
apiVersion: example.com/v1
kind: Bar
metadata:
name: instance
annotations:
sleater: kinney
`,
filter: Filter{Annotations: map[string]string{
"sleater": "kinney",
}},
},
"update": {
input: `
apiVersion: example.com/v1
kind: Foo
metadata:
name: instance
annotations:
foo: foo
`,
expectedOutput: `
apiVersion: example.com/v1
kind: Foo
metadata:
name: instance
annotations:
foo: bar
`,
filter: Filter{Annotations: map[string]string{
"foo": "bar",
}},
},
"data-fieldspecs": {
input: `
apiVersion: example.com/v1
kind: Foo
metadata:
name: instance
---
apiVersion: example.com/v1
kind: Bar
metadata:
name: instance
`,
expectedOutput: `
apiVersion: example.com/v1
kind: Foo
metadata:
name: instance
annotations:
sleater: kinney
a:
b:
sleater: kinney
---
apiVersion: example.com/v1
kind: Bar
metadata:
name: instance
annotations:
sleater: kinney
a:
b:
sleater: kinney
`,
filter: Filter{Annotations: map[string]string{
"sleater": "kinney",
}},
fsslice: []types.FieldSpec{
{
Path: "a/b",
CreateIfNotPresent: true,
},
},
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
config := builtinconfig.MakeDefaultConfig()
filter := tc.filter
filter.FsSlice = append(config.CommonAnnotations, tc.fsslice...)
var out bytes.Buffer
rw := kio.ByteReadWriter{
Reader: bytes.NewBufferString(tc.input),
Writer: &out,
}
err := kio.Pipeline{
Inputs: []kio.Reader{&rw},
Filters: []kio.Filter{filter},
Outputs: []kio.Writer{&rw},
}.Execute()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(tc.expectedOutput),
strings.TrimSpace(out.String())) {
t.FailNow()
}
})
}
}
func TestAnnotations_Filter_Multiple(t *testing.T) {
input := `
apiVersion: example.com/v1
kind: Foo
metadata:
name: instance
---
apiVersion: example.com/v1
kind: Bar
metadata:
name: instance
`
annos := map[string]string{
"sleater": "kinney",
"sonic": "youth",
}
config := builtinconfig.MakeDefaultConfig()
filter := Filter{Annotations: annos}
filter.FsSlice = config.CommonAnnotations
var out bytes.Buffer
rw := kio.ByteReadWriter{
Reader: bytes.NewBufferString(input),
Writer: &out,
}
err := kio.Pipeline{
Inputs: []kio.Reader{&rw},
Filters: []kio.Filter{filter},
Outputs: []kio.Writer{&rw},
}.Execute()
if !assert.NoError(t, err) {
t.FailNow()
}
assertHasAnnotation(t, out.String(), annos)
}
func assertHasAnnotation(t *testing.T, y string, exp map[string]string) bool {
var out bytes.Buffer
rw := kio.ByteReadWriter{
Reader: bytes.NewBufferString(y),
Writer: &out,
}
filter := &captureAnnotationFilter{
annotations: make(map[string]annotations),
}
err := kio.Pipeline{
Inputs: []kio.Reader{&rw},
Filters: []kio.Filter{filter},
Outputs: []kio.Writer{&rw},
}.Execute()
if err != nil {
t.Error(err)
return false
}
for name, annos := range filter.annotations {
for key, val := range exp {
v, found := annos[key]
if !found {
t.Errorf("expected annotation with key %s in object %s, but didn't find it",
key, name)
return false
}
if want, got := val, v; got != want {
t.Errorf("exected annotation %s in object %s to have value %s, but found %s",
key, name, want, got)
return false
}
}
}
return true
}
type annotations map[string]string
type captureAnnotationFilter struct {
annotations map[string]annotations
}
func (c captureAnnotationFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode,
error) {
for _, n := range nodes {
meta, err := n.GetMeta()
if err != nil {
return nodes, err
}
name := meta.Name
annos := meta.Annotations
c.annotations[name] = annos
}
return nodes, nil
}

View File

@@ -0,0 +1,6 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package annotations contains a kio.Filter implementation of the kustomize
// annotations transformer.
package annotations

View File

@@ -0,0 +1,55 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package annotations
import (
"bytes"
"log"
"os"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
"sigs.k8s.io/kustomize/kyaml/kio"
)
func ExampleFilter() {
fss := builtinconfig.MakeDefaultConfig().CommonAnnotations
err := kio.Pipeline{
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
apiVersion: example.com/v1
kind: Foo
metadata:
name: instance
---
apiVersion: example.com/v1
kind: Bar
metadata:
name: instance
`)}},
Filters: []kio.Filter{Filter{
Annotations: map[string]string{
"foo": "bar",
},
FsSlice: fss,
}},
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
}.Execute()
if err != nil {
log.Fatal(err)
}
// Output:
// apiVersion: example.com/v1
// kind: Foo
// metadata:
// name: instance
// annotations:
// foo: bar
// ---
// apiVersion: example.com/v1
// kind: Bar
// metadata:
// name: instance
// annotations:
// foo: bar
}