diff --git a/api/filters/annotations/annotations.go b/api/filters/annotations/annotations.go index cd0e3b4d5..562decca9 100644 --- a/api/filters/annotations/annotations.go +++ b/api/filters/annotations/annotations.go @@ -19,10 +19,25 @@ type Filter struct { // FsSlice contains the FieldSpecs to locate the namespace field FsSlice types.FsSlice + + // SetEntryCallback is invoked each time an annotation is applied + // Example use cases: + // - Tracking all paths where annotations have been applied + SetEntryCallback func(key, value, tag string, node *yaml.RNode) } var _ kio.Filter = Filter{} +func (f Filter) setEntry(key, value, tag string) filtersutil.SetFn { + baseSetEntryFunc := filtersutil.SetEntry(key, value, tag) + return func(node *yaml.RNode) error { + if f.SetEntryCallback != nil { + f.SetEntryCallback(key, value, tag, node) + } + return baseSetEntryFunc(node) + } +} + func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { keys := yaml.SortedMapKeys(f.Annotations) _, err := kio.FilterAll(yaml.FilterFunc( @@ -30,7 +45,7 @@ func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { for _, k := range keys { if err := node.PipeE(fsslice.Filter{ FsSlice: f.FsSlice, - SetValue: filtersutil.SetEntry( + SetValue: f.setEntry( k, f.Annotations[k], yaml.NodeTagString), CreateKind: yaml.MappingNode, // Annotations are MappingNodes. CreateTag: yaml.NodeTagMap, diff --git a/api/filters/annotations/annotations_test.go b/api/filters/annotations/annotations_test.go index b69e0ddc6..32d23229a 100644 --- a/api/filters/annotations/annotations_test.go +++ b/api/filters/annotations/annotations_test.go @@ -11,16 +11,36 @@ import ( "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/kyaml/yaml" ) var annosFs = builtinconfig.MakeDefaultConfig().CommonAnnotations +type setEntryArg struct { + Key string + Value string + Tag string + NodePath []string +} + +var setEntryArgs []setEntryArg + +func setEntryCallbackStub(key, value, tag string, node *yaml.RNode) { + setEntryArgs = append(setEntryArgs, setEntryArg{ + Key: key, + Value: value, + Tag: tag, + NodePath: node.FieldPath(), + }) +} + func TestAnnotations_Filter(t *testing.T) { testCases := map[string]struct { - input string - expectedOutput string - filter Filter - fsslice types.FsSlice + input string + expectedOutput string + filter Filter + fsslice types.FsSlice + expectedSetEntryArgs []setEntryArg }{ "add": { input: ` @@ -210,9 +230,74 @@ metadata: "b": "b1", }}, }, + + // test usage of SetEntryCallback + "set_entry_callback": { + input: ` +apiVersion: example.com/v1 +kind: Foo +metadata: + name: instance +`, + expectedOutput: ` +apiVersion: example.com/v1 +kind: Foo +metadata: + name: instance + annotations: + a: a1 + b: b1 +spec: + template: + metadata: + annotations: + a: a1 + b: b1 +`, + filter: Filter{ + Annotations: annoMap{ + "a": "a1", + "b": "b1", + }, + SetEntryCallback: setEntryCallbackStub, + }, + fsslice: []types.FieldSpec{ + { + Path: "spec/template/metadata/annotations", + CreateIfNotPresent: true, + }, + }, + expectedSetEntryArgs: []setEntryArg{ + { + Key: "a", + Value: "a1", + Tag: "!!str", + NodePath: []string{"metadata", "annotations"}, + }, + { + Key: "a", + Value: "a1", + Tag: "!!str", + NodePath: []string{"spec", "template", "metadata", "annotations"}, + }, + { + Key: "b", + Value: "b1", + Tag: "!!str", + NodePath: []string{"metadata", "annotations"}, + }, + { + Key: "b", + Value: "b1", + Tag: "!!str", + NodePath: []string{"spec", "template", "metadata", "annotations"}, + }, + }, + }, } for tn, tc := range testCases { + setEntryArgs = nil t.Run(tn, func(t *testing.T) { filter := tc.filter filter.FsSlice = append(annosFs, tc.fsslice...) @@ -221,6 +306,9 @@ metadata: strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, filter))) { t.FailNow() } + if !assert.Equal(t, tc.expectedSetEntryArgs, setEntryArgs) { + t.FailNow() + } }) } }