Function to set labels.

This commit is contained in:
Jeffrey Regan
2020-03-26 17:19:30 -07:00
committed by jregan
parent 85e9127071
commit 2be48ca96a
11 changed files with 371 additions and 189 deletions

View File

@@ -4,15 +4,18 @@
package annotations package annotations
import ( import (
"sigs.k8s.io/kustomize/api/filters/filtersutil"
"sigs.k8s.io/kustomize/api/filters/fsslice" "sigs.k8s.io/kustomize/api/filters/fsslice"
"sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/kustomize/kyaml/yaml"
) )
type annoMap map[string]string
type Filter struct { type Filter struct {
// Annotations is the set of annotations to apply to the inputs // Annotations is the set of annotations to apply to the inputs
Annotations map[string]string `yaml:"annotations,omitempty"` Annotations annoMap `yaml:"annotations,omitempty"`
// FsSlice contains the FieldSpecs to locate the namespace field // FsSlice contains the FieldSpecs to locate the namespace field
FsSlice types.FsSlice FsSlice types.FsSlice
@@ -21,24 +24,19 @@ type Filter struct {
var _ kio.Filter = Filter{} var _ kio.Filter = Filter{}
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
for i := range nodes { keys := filtersutil.SortedMapKeys(f.Annotations)
if err := f.run(nodes[i]); err != nil { _, err := kio.FilterAll(yaml.FilterFunc(
return nil, err func(node *yaml.RNode) (*yaml.RNode, error) {
} for _, k := range keys {
} if err := node.PipeE(fsslice.Filter{
return nodes, nil FsSlice: f.FsSlice,
} SetValue: fsslice.SetEntry(k, f.Annotations[k]),
CreateKind: yaml.MappingNode, // Annotations are MappingNodes.
// run applies the filter to a single node. }); err != nil {
func (f Filter) run(node *yaml.RNode) error { return nil, err
for key, value := range f.Annotations { }
if err := node.PipeE(fsslice.Filter{ }
FsSlice: f.FsSlice, return node, nil
SetValue: fsslice.SetEntry(key, value), })).Filter(nodes)
CreateKind: yaml.MappingNode, // Annotations are MappingNodes. return nodes, err
}); err != nil {
return err
}
}
return nil
} }

View File

@@ -4,17 +4,17 @@
package annotations package annotations
import ( import (
"bytes"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig" "sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
"sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
) )
var annosFs = builtinconfig.MakeDefaultConfig().CommonAnnotations
func TestAnnotations_Filter(t *testing.T) { func TestAnnotations_Filter(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
input string input string
@@ -28,11 +28,9 @@ apiVersion: example.com/v1
kind: Foo kind: Foo
metadata: metadata:
name: instance name: instance
--- annotations:
apiVersion: example.com/v1 hero: batman
kind: Bar fiend: riddler
metadata:
name: instance
`, `,
expectedOutput: ` expectedOutput: `
apiVersion: example.com/v1 apiVersion: example.com/v1
@@ -40,17 +38,18 @@ kind: Foo
metadata: metadata:
name: instance name: instance
annotations: annotations:
sleater: kinney hero: batman
--- fiend: riddler
apiVersion: example.com/v1 auto: ford
kind: Bar bean: cannellini
metadata: clown: emmett kelley
name: instance dragon: smaug
annotations:
sleater: kinney
`, `,
filter: Filter{Annotations: map[string]string{ filter: Filter{Annotations: annoMap{
"sleater": "kinney", "clown": "emmett kelley",
"auto": "ford",
"dragon": "smaug",
"bean": "cannellini",
}}, }},
}, },
"update": { "update": {
@@ -60,7 +59,8 @@ kind: Foo
metadata: metadata:
name: instance name: instance
annotations: annotations:
foo: foo hero: batman
fiend: riddler
`, `,
expectedOutput: ` expectedOutput: `
apiVersion: example.com/v1 apiVersion: example.com/v1
@@ -68,10 +68,16 @@ kind: Foo
metadata: metadata:
name: instance name: instance
annotations: annotations:
foo: bar hero: superman
fiend: luthor
bean: cannellini
clown: emmett kelley
`, `,
filter: Filter{Annotations: map[string]string{ filter: Filter{Annotations: annoMap{
"foo": "bar", "clown": "emmett kelley",
"hero": "superman",
"fiend": "luthor",
"bean": "cannellini",
}}, }},
}, },
"data-fieldspecs": { "data-fieldspecs": {
@@ -107,7 +113,7 @@ a:
b: b:
sleater: kinney sleater: kinney
`, `,
filter: Filter{Annotations: map[string]string{ filter: Filter{Annotations: annoMap{
"sleater": "kinney", "sleater": "kinney",
}}, }},
fsslice: []types.FieldSpec{ fsslice: []types.FieldSpec{
@@ -121,126 +127,13 @@ a:
for tn, tc := range testCases { for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) { t.Run(tn, func(t *testing.T) {
config := builtinconfig.MakeDefaultConfig()
filter := tc.filter filter := tc.filter
filter.FsSlice = append(config.CommonAnnotations, tc.fsslice...) filter.FsSlice = append(annosFs, 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, if !assert.Equal(t,
strings.TrimSpace(tc.expectedOutput), strings.TrimSpace(tc.expectedOutput),
strings.TrimSpace(out.String())) { strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, filter))) {
t.FailNow() 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

@@ -5,11 +5,25 @@ package filtersutil
import ( import (
"encoding/json" "encoding/json"
"sort"
"sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/kustomize/kyaml/yaml"
) )
// SortedMapKeys returns a sorted slice of keys to the given map.
// Writing this function never gets old.
func SortedMapKeys(m map[string]string) []string {
keys := make([]string, len(m))
i := 0
for k := range m {
keys[i] = k
i++
}
sort.Strings(keys)
return keys
}
// ApplyToJSON applies the filter to the json objects. // ApplyToJSON applies the filter to the json objects.
func ApplyToJSON(filter kio.Filter, objs ...marshalerUnmarshaler) error { func ApplyToJSON(filter kio.Filter, objs ...marshalerUnmarshaler) error {
var nodes []*yaml.RNode var nodes []*yaml.RNode

View File

@@ -11,6 +11,32 @@ import (
"sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/kustomize/kyaml/yaml"
) )
func TestSortedKeys(t *testing.T) {
testCases := map[string]struct {
input map[string]string
expected []string
}{
"empty": {
input: map[string]string{},
expected: []string{}},
"one": {
input: map[string]string{"a": "aaa"},
expected: []string{"a"}},
"three": {
input: map[string]string{"c": "ccc", "b": "bbb", "a": "aaa"},
expected: []string{"a", "b", "c"}},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
if !assert.Equal(t,
filtersutil.SortedMapKeys(tc.input),
tc.expected) {
t.FailNow()
}
})
}
}
func TestApplyToJSON(t *testing.T) { func TestApplyToJSON(t *testing.T) {
instance1 := bytes.NewBufferString(`{"kind": "Foo"}`) instance1 := bytes.NewBufferString(`{"kind": "Foo"}`)
instance2 := bytes.NewBufferString(`{"kind": "Bar"}`) instance2 := bytes.NewBufferString(`{"kind": "Bar"}`)

View File

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

View File

@@ -0,0 +1,55 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package labels
import (
"bytes"
"log"
"os"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
"sigs.k8s.io/kustomize/kyaml/kio"
)
func ExampleFilter() {
fss := builtinconfig.MakeDefaultConfig().CommonLabels
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{
Labels: 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
// labels:
// foo: bar
// ---
// apiVersion: example.com/v1
// kind: Bar
// metadata:
// name: instance
// labels:
// foo: bar
}

View File

@@ -0,0 +1,43 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package labels
import (
"sigs.k8s.io/kustomize/api/filters/filtersutil"
"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 labelMap map[string]string
// Filter sets labels.
type Filter struct {
// Labels is the set of labels to apply to the inputs
Labels labelMap `yaml:"labels,omitempty"`
// FsSlice identifies the label fields.
FsSlice types.FsSlice
}
var _ kio.Filter = Filter{}
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
keys := filtersutil.SortedMapKeys(f.Labels)
_, err := kio.FilterAll(yaml.FilterFunc(
func(node *yaml.RNode) (*yaml.RNode, error) {
for _, k := range keys {
if err := node.PipeE(fsslice.Filter{
FsSlice: f.FsSlice,
SetValue: fsslice.SetEntry(k, f.Labels[k]),
CreateKind: yaml.MappingNode, // Labels are MappingNodes.
}); err != nil {
return nil, err
}
}
return node, nil
})).Filter(nodes)
return nodes, err
}

View File

@@ -0,0 +1,139 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package labels
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
"sigs.k8s.io/kustomize/api/types"
)
var labelsFs = builtinconfig.MakeDefaultConfig().CommonLabels
func TestLabels_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
labels:
hero: batman
fiend: riddler
`,
expectedOutput: `
apiVersion: example.com/v1
kind: Foo
metadata:
name: instance
labels:
hero: batman
fiend: riddler
auto: ford
bean: cannellini
clown: emmett kelley
dragon: smaug
`,
filter: Filter{Labels: labelMap{
"clown": "emmett kelley",
"auto": "ford",
"dragon": "smaug",
"bean": "cannellini",
}},
},
"update": {
input: `
apiVersion: example.com/v1
kind: Foo
metadata:
name: instance
labels:
hero: batman
fiend: riddler
`,
expectedOutput: `
apiVersion: example.com/v1
kind: Foo
metadata:
name: instance
labels:
hero: superman
fiend: luthor
bean: cannellini
clown: emmett kelley
`,
filter: Filter{Labels: labelMap{
"clown": "emmett kelley",
"hero": "superman",
"fiend": "luthor",
"bean": "cannellini",
}},
},
"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
labels:
sleater: kinney
a:
b:
sleater: kinney
---
apiVersion: example.com/v1
kind: Bar
metadata:
name: instance
labels:
sleater: kinney
a:
b:
sleater: kinney
`,
filter: Filter{Labels: labelMap{
"sleater": "kinney",
}},
fsSlice: []types.FieldSpec{
{
Path: "a/b",
CreateIfNotPresent: true,
},
},
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
filter := tc.filter
filter.FsSlice = append(labelsFs, tc.fsSlice...)
if !assert.Equal(t,
strings.TrimSpace(tc.expectedOutput),
strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, filter))) {
t.FailNow()
}
})
}
}

View File

@@ -21,19 +21,14 @@ type Filter struct {
var _ kio.Filter = Filter{} var _ kio.Filter = Filter{}
func (ns Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { func (ns Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
for i := range nodes { return kio.FilterAll(yaml.FilterFunc(ns.run)).Filter(nodes)
if err := ns.run(nodes[i]); err != nil {
return nil, err
}
}
return nodes, nil
} }
// Run runs the filter on a single node rather than a slice // Run runs the filter on a single node rather than a slice
func (ns Filter) run(node *yaml.RNode) error { func (ns Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
// hacks for hardcoded types -- :( // hacks for hardcoded types -- :(
if err := ns.hacks(node); err != nil { if err := ns.hacks(node); err != nil {
return err return nil, err
} }
// Remove the fieldspecs that are for hardcoded fields. The fieldspecs // Remove the fieldspecs that are for hardcoded fields. The fieldspecs
@@ -45,11 +40,12 @@ func (ns Filter) run(node *yaml.RNode) error {
ns.FsSlice = ns.removeFieldSpecsForHacks(ns.FsSlice) ns.FsSlice = ns.removeFieldSpecsForHacks(ns.FsSlice)
// transformations based on data -- :) // transformations based on data -- :)
return node.PipeE(fsslice.Filter{ err := node.PipeE(fsslice.Filter{
FsSlice: ns.FsSlice, FsSlice: ns.FsSlice,
SetValue: fsslice.SetScalar(ns.Namespace), SetValue: fsslice.SetScalar(ns.Namespace),
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
}) })
return node, err
} }
// hacks applies the namespace transforms that are hardcoded rather // hacks applies the namespace transforms that are hardcoded rather

View File

@@ -4,15 +4,14 @@
package namespace_test package namespace_test
import ( import (
"bytes"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/api/filters/namespace" "sigs.k8s.io/kustomize/api/filters/namespace"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig" "sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
"sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/kio"
) )
var tests = []TestCase{ var tests = []TestCase{
@@ -270,27 +269,10 @@ func TestNamespace_Filter(t *testing.T) {
test := tests[i] test := tests[i]
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
test.filter.FsSlice = append(config.NameSpace, test.fsslice...) test.filter.FsSlice = append(config.NameSpace, test.fsslice...)
out := &bytes.Buffer{}
rw := &kio.ByteReadWriter{
Reader: bytes.NewBufferString(test.input),
Writer: out,
}
// run the filter
err := kio.Pipeline{
Inputs: []kio.Reader{rw},
Filters: []kio.Filter{test.filter},
Outputs: []kio.Writer{rw},
}.Execute()
if !assert.NoError(t, err) {
t.FailNow()
}
// check results
if !assert.Equal(t, if !assert.Equal(t,
strings.TrimSpace(test.expected), strings.TrimSpace(test.expected),
strings.TrimSpace(out.String())) { strings.TrimSpace(
filtertest_test.RunFilter(t, test.input, test.filter))) {
t.FailNow() t.FailNow()
} }
}) })

View File

@@ -0,0 +1,30 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package filtertest_test
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/kio"
)
func RunFilter(t *testing.T, input string, f kio.Filter) string {
var out bytes.Buffer
rw := kio.ByteReadWriter{
Reader: bytes.NewBufferString(input),
Writer: &out,
}
err := kio.Pipeline{
Inputs: []kio.Reader{&rw},
Filters: []kio.Filter{f},
Outputs: []kio.Writer{&rw},
}.Execute()
if !assert.NoError(t, err) {
t.FailNow()
}
return out.String()
}