diff --git a/api/filters/imagetag/imagetag.go b/api/filters/imagetag/imagetag.go index 077031754..24ab99f74 100644 --- a/api/filters/imagetag/imagetag.go +++ b/api/filters/imagetag/imagetag.go @@ -23,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) @@ -40,8 +48,11 @@ func (f Filter) filter(node *yaml.RNode) (*yaml.RNode, error) { return node, nil } if err := node.PipeE(fsslice.Filter{ - FsSlice: f.FsSlice, - SetValue: updateImageTagFn(f.ImageTag), + FsSlice: f.FsSlice, + SetValue: imageTagUpdater{ + ImageTag: f.ImageTag, + trackableSetter: f.trackableSetter, + }.SetImageValue, }); err != nil { return nil, err } @@ -59,11 +70,3 @@ func (f Filter) isOnDenyList(node *yaml.RNode) bool { // https://github.com/kubernetes-sigs/kustomize/issues/890 return meta.Kind == `CustomResourceDefinition` } - -func updateImageTagFn(imageTag types.Image) filtersutil.SetFn { - return func(node *yaml.RNode) error { - return node.PipeE(imageTagUpdater{ - ImageTag: imageTag, - }) - } -} 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 1c3637cde..50c0dcdc8 100644 --- a/api/filters/imagetag/updater.go +++ b/api/filters/imagetag/updater.go @@ -4,6 +4,7 @@ package imagetag import ( + "sigs.k8s.io/kustomize/api/filters/filtersutil" "sigs.k8s.io/kustomize/api/image" "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/yaml" @@ -13,19 +14,20 @@ 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) Filter(rn *yaml.RNode) (*yaml.RNode, error) { +func (u imageTagUpdater) SetImageValue(rn *yaml.RNode) error { if err := yaml.ErrorIfInvalid(rn, yaml.ScalarNode); err != nil { - return nil, err + return err } value := rn.YNode().Value if !image.IsImageMatched(value, u.ImageTag.Name) { - return rn, nil + return nil } name, tag := image.Split(value) @@ -39,5 +41,12 @@ func (u imageTagUpdater) Filter(rn *yaml.RNode) (*yaml.RNode, error) { tag = "@" + u.ImageTag.Digest } - return rn.Pipe(yaml.FieldSetter{StringValue: name + tag}) + return u.trackableSetter.SetScalar(name + tag)(rn) +} + +func (u imageTagUpdater) Filter(rn *yaml.RNode) (*yaml.RNode, error) { + if err := u.SetImageValue(rn); err != nil { + return nil, err + } + return rn, nil }