diff --git a/api/filters/imagetag/imagetag.go b/api/filters/imagetag/imagetag.go index 699fdc18d..24ab99f74 100644 --- a/api/filters/imagetag/imagetag.go +++ b/api/filters/imagetag/imagetag.go @@ -4,6 +4,7 @@ package imagetag 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" @@ -22,9 +23,17 @@ type Filter struct { // FsSlice contains the FieldSpecs to locate an image field, // e.g. Path: "spec/myContainers[]/image" FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"` + + trackableSetter filtersutil.TrackableSetter } var _ kio.Filter = Filter{} +var _ kio.TrackableFilter = &Filter{} + +// 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) { _, err := kio.FilterAll(yaml.FilterFunc(f.filter)).Filter(nodes) @@ -39,9 +48,10 @@ func (f Filter) filter(node *yaml.RNode) (*yaml.RNode, error) { return node, nil } if err := node.PipeE(fsslice.Filter{ - FsSlice: f.FsSlice, + FsSlice: f.FsSlice, SetValue: imageTagUpdater{ - ImageTag: f.ImageTag, + ImageTag: f.ImageTag, + trackableSetter: f.trackableSetter, }.SetImageValue, }); err != nil { return nil, err diff --git a/api/filters/imagetag/imagetag_test.go b/api/filters/imagetag/imagetag_test.go index 96d68366a..a62f95a9f 100644 --- a/api/filters/imagetag/imagetag_test.go +++ b/api/filters/imagetag/imagetag_test.go @@ -10,14 +10,35 @@ import ( "github.com/stretchr/testify/assert" filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest" "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kyaml/yaml" ) +type setValueArg struct { + Key string + Value string + Tag string + PrevValue string +} + +var setValueArgs []setValueArg + +func setValueCallbackStub(key, value, tag string, node *yaml.RNode) { + setValueArgs = append(setValueArgs, setValueArg{ + Key: key, + Value: value, + Tag: tag, + PrevValue: node.YNode().Value, + }) +} + func TestImageTagUpdater_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 + setValueCallback func(key, value, tag string, node *yaml.RNode) + expectedSetValueArgs []setValueArg }{ "ignore CustomResourceDefinition": { input: ` @@ -658,17 +679,108 @@ spec: }, }, }, + "mutation tracker": { + input: ` +group: apps +apiVersion: v1 +kind: Deployment +metadata: + name: deploy1 +spec: + template: + spec: + containers: + - image: nginx:1.7.9 + name: nginx-tagged + - image: nginx:latest + name: nginx-latest + - image: foobar:1 + name: replaced-with-digest + - image: postgres:1.8.0 + name: postgresdb + initContainers: + - image: nginx + name: nginx-notag + - image: nginx@sha256:111111111111111111 + name: nginx-sha256 + - image: alpine:1.8.0 + name: init-alpine +`, + expectedOutput: ` +group: apps +apiVersion: v1 +kind: Deployment +metadata: + name: deploy1 +spec: + template: + spec: + containers: + - image: busybox:v3 + name: nginx-tagged + - image: busybox:v3 + name: nginx-latest + - image: foobar:1 + name: replaced-with-digest + - image: postgres:1.8.0 + name: postgresdb + initContainers: + - image: busybox:v3 + name: nginx-notag + - image: busybox:v3 + name: nginx-sha256 + - image: alpine:1.8.0 + name: init-alpine +`, + filter: Filter{ + ImageTag: types.Image{ + Name: "nginx", + NewName: "busybox", + NewTag: "v3", + }, + }, + fsSlice: []types.FieldSpec{ + { + Path: "spec/template/spec/containers[]/image", + }, + { + Path: "spec/template/spec/initContainers[]/image", + }, + }, + setValueCallback: setValueCallbackStub, + expectedSetValueArgs: []setValueArg{ + { + Value: "busybox:v3", + PrevValue: "nginx:1.7.9", + }, + { + Value: "busybox:v3", + PrevValue: "nginx:latest", + }, + { + Value: "busybox:v3", + PrevValue: "nginx", + }, + { + Value: "busybox:v3", + PrevValue: "nginx@sha256:111111111111111111", + }, + }, + }, } for tn, tc := range testCases { + setValueArgs = nil t.Run(tn, func(t *testing.T) { filter := tc.filter + filter.WithMutationTracker(tc.setValueCallback) filter.FsSlice = tc.fsSlice if !assert.Equal(t, strings.TrimSpace(tc.expectedOutput), strings.TrimSpace(filtertest.RunFilter(t, tc.input, filter))) { t.FailNow() } + assert.Equal(t, tc.expectedSetValueArgs, setValueArgs) }) } } diff --git a/api/filters/imagetag/updater.go b/api/filters/imagetag/updater.go index af8de4393..50c0dcdc8 100644 --- a/api/filters/imagetag/updater.go +++ b/api/filters/imagetag/updater.go @@ -14,8 +14,9 @@ import ( // that will update the value of the yaml node based on the provided // ImageTag if the current value matches the format of an image reference. type imageTagUpdater struct { - Kind string `yaml:"kind,omitempty"` - ImageTag types.Image `yaml:"imageTag,omitempty"` + Kind string `yaml:"kind,omitempty"` + ImageTag types.Image `yaml:"imageTag,omitempty"` + trackableSetter filtersutil.TrackableSetter } func (u imageTagUpdater) SetImageValue(rn *yaml.RNode) error { @@ -40,7 +41,7 @@ func (u imageTagUpdater) SetImageValue(rn *yaml.RNode) error { tag = "@" + u.ImageTag.Digest } - return filtersutil.SetScalar(name + tag)(rn) + return u.trackableSetter.SetScalar(name + tag)(rn) } func (u imageTagUpdater) Filter(rn *yaml.RNode) (*yaml.RNode, error) {