diff --git a/api/filters/annotations/annotations.go b/api/filters/annotations/annotations.go index 562decca90..4998f5a3e7 100644 --- a/api/filters/annotations/annotations.go +++ b/api/filters/annotations/annotations.go @@ -20,22 +20,15 @@ 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) + trackableSetter filtersutil.TrackableSetter } var _ kio.Filter = Filter{} +var _ kio.TrackableFilter = &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) - } +// WithMutationTracker registers a callback which will be invoked each time a field is mutated +func (f *Filter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) { + f.trackableSetter.WithMutationTracker(callback) } func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { @@ -45,7 +38,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: f.setEntry( + SetValue: f.trackableSetter.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 32d23229ab..39fd8e2e26 100644 --- a/api/filters/annotations/annotations_test.go +++ b/api/filters/annotations/annotations_test.go @@ -40,6 +40,7 @@ func TestAnnotations_Filter(t *testing.T) { expectedOutput string filter Filter fsslice types.FsSlice + setEntryCallback func(key, value, tag string, node *yaml.RNode) expectedSetEntryArgs []setEntryArg }{ "add": { @@ -259,8 +260,8 @@ spec: "a": "a1", "b": "b1", }, - SetEntryCallback: setEntryCallbackStub, }, + setEntryCallback: setEntryCallbackStub, fsslice: []types.FieldSpec{ { Path: "spec/template/metadata/annotations", @@ -300,6 +301,7 @@ spec: setEntryArgs = nil t.Run(tn, func(t *testing.T) { filter := tc.filter + filter.WithMutationTracker(tc.setEntryCallback) filter.FsSlice = append(annosFs, tc.fsslice...) if !assert.Equal(t, strings.TrimSpace(tc.expectedOutput), diff --git a/api/filters/filtersutil/setters.go b/api/filters/filtersutil/setters.go index cc04afe898..fd44f80c59 100644 --- a/api/filters/filtersutil/setters.go +++ b/api/filters/filtersutil/setters.go @@ -31,3 +31,39 @@ func SetEntry(key, value, tag string) SetFn { }) } } + +type TrackableSetter struct { + // SetValueCallback will be invoked each time a field is set + setValueCallback func(key, value, tag string, node *yaml.RNode) +} + +// WithMutationTracker registers a callback which will be invoked each time a field is mutated +func (s *TrackableSetter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) { + s.setValueCallback = callback +} + +// SetScalar returns a SetFn to set a scalar value +// if a mutation tracker has been registered, the tracker will be invoked each +// time a scalar is set +func (s TrackableSetter) SetScalar(value string) SetFn { + origSetScalar := SetScalar(value) + return func(node *yaml.RNode) error { + if s.setValueCallback != nil { + s.setValueCallback("", value, "", node) + } + return origSetScalar(node) + } +} + +// SetEntry returns a SetFn to set an entry in a map +// if a mutation tracker has been registered, the tracker will be invoked each +// time an entry is set +func (s TrackableSetter) SetEntry(key, value, tag string) SetFn { + origSetEntry := SetEntry(key, value, tag) + return func(node *yaml.RNode) error { + if s.setValueCallback != nil { + s.setValueCallback(key, value, tag, node) + } + return origSetEntry(node) + } +} diff --git a/api/filters/labels/labels.go b/api/filters/labels/labels.go index 6b19369d9c..b67d4d4b13 100644 --- a/api/filters/labels/labels.go +++ b/api/filters/labels/labels.go @@ -21,22 +21,15 @@ type Filter struct { // FsSlice identifies the label fields. FsSlice types.FsSlice - // SetEntryCallback is invoked each time a label is applied - // Example use cases: - // - Tracking all paths where labels have been applied - SetEntryCallback func(key, value, tag string, node *yaml.RNode) + trackableSetter filtersutil.TrackableSetter } var _ kio.Filter = Filter{} +var _ kio.TrackableFilter = &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) - } +// WithMutationTracker registers a callback which will be invoked each time a field is mutated +func (f *Filter) WithMutationTracker(callback func(key, value, tag string, node *yaml.RNode)) { + f.trackableSetter.WithMutationTracker(callback) } func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { @@ -46,7 +39,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: f.setEntry( + SetValue: f.trackableSetter.SetEntry( k, f.Labels[k], yaml.NodeTagString), CreateKind: yaml.MappingNode, // Labels are MappingNodes. CreateTag: yaml.NodeTagMap, diff --git a/api/filters/labels/labels_test.go b/api/filters/labels/labels_test.go index 77eb7e5c1e..45f4eff4e4 100644 --- a/api/filters/labels/labels_test.go +++ b/api/filters/labels/labels_test.go @@ -37,6 +37,7 @@ func TestLabels_Filter(t *testing.T) { input string expectedOutput string filter Filter + setEntryCallback func(key, value, tag string, node *yaml.RNode) expectedSetEntryArgs []setEntryArg }{ "add": { @@ -456,8 +457,8 @@ a: CreateIfNotPresent: true, }, }, - SetEntryCallback: setEntryCallbackStub, }, + setEntryCallback: setEntryCallbackStub, expectedSetEntryArgs: []setEntryArg{ { Key: "mage", @@ -478,6 +479,7 @@ a: for tn, tc := range testCases { setEntryArgs = nil t.Run(tn, func(t *testing.T) { + tc.filter.WithMutationTracker(tc.setEntryCallback) if !assert.Equal(t, strings.TrimSpace(tc.expectedOutput), strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, tc.filter))) { diff --git a/kyaml/kio/kio.go b/kyaml/kio/kio.go index 10388acd3b..776b6a73aa 100644 --- a/kyaml/kio/kio.go +++ b/kyaml/kio/kio.go @@ -57,6 +57,13 @@ type Filter interface { Filter([]*yaml.RNode) ([]*yaml.RNode, error) } +// TrackableFilter is an extension of Filter which is also capable of tracking +// which fields were mutated by the filter. +type TrackableFilter interface { + Filter + WithMutationTracker(func(key, value, tag string, node *yaml.RNode)) +} + // FilterFunc implements a Filter as a function. type FilterFunc func([]*yaml.RNode) ([]*yaml.RNode, error)