From 4b130437536e3e321b4ad1ca9dc725f771f3d697 Mon Sep 17 00:00:00 2001 From: noah Date: Thu, 21 Oct 2021 21:34:05 +1300 Subject: [PATCH] fix: use kyaml to preserve kustomization (#274) * fix: use kyaml to preserve kustomization fixes #250 fixes #268 fixes #247 * chore: linter error * fix: tests and linting * go mod tidy --- go.mod | 3 +- go.sum | 2 - pkg/argocd/git.go | 79 ++++++++++++++++++++++++++----------- pkg/argocd/git_test.go | 83 ++++++++++++++++++++++++++++++++++----- pkg/argocd/update_test.go | 6 +++ 5 files changed, 139 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index 2dc56795..969f9762 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,8 @@ require ( k8s.io/api v1.21.0 k8s.io/apimachinery v1.21.0 k8s.io/client-go v11.0.1-0.20190816222228-6d55c1b1f1ca+incompatible - sigs.k8s.io/kustomize v2.0.3+incompatible + sigs.k8s.io/kustomize/api v0.8.5 + sigs.k8s.io/kustomize/kyaml v0.10.15 ) replace ( diff --git a/go.sum b/go.sum index 6bb31521..b58c847a 100644 --- a/go.sum +++ b/go.sum @@ -1294,8 +1294,6 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/controller-runtime v0.8.3/go.mod h1:U/l+DUopBc1ecfRZ5aviA9JDmGFQKvLf5YkZNx2e0sU= -sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= -sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/kustomize/api v0.8.5 h1:bfCXGXDAbFbb/Jv5AhMj2BB8a5VAJuuQ5/KU69WtDjQ= sigs.k8s.io/kustomize/api v0.8.5/go.mod h1:M377apnKT5ZHJS++6H4rQoCHmWtt6qTpp3mbe7p6OLY= sigs.k8s.io/kustomize/cmd/config v0.9.7/go.mod h1:MvXCpHs77cfyxRmCNUQjIqCmZyYsbn5PyQpWiq44nW0= diff --git a/pkg/argocd/git.go b/pkg/argocd/git.go index 7a524216..c191a311 100644 --- a/pkg/argocd/git.go +++ b/pkg/argocd/git.go @@ -9,9 +9,9 @@ import ( "path/filepath" "text/template" - "sigs.k8s.io/kustomize/pkg/commands/kustfile" - "sigs.k8s.io/kustomize/pkg/fs" - image2 "sigs.k8s.io/kustomize/pkg/image" + "sigs.k8s.io/kustomize/api/konfig" + "sigs.k8s.io/kustomize/api/types" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" "github.com/argoproj-labs/argocd-image-updater/pkg/image" @@ -221,36 +221,71 @@ func writeKustomization(app *v1alpha1.Application, wbc *WriteBackConfig, gitC gi log.Infof("updating base %s", base) - kf, err := kustfile.NewKustomizationFile(fs.MakeRealFS()) + kustFile := findKustomization(base) + if kustFile == "" { + return fmt.Errorf("could not find kustomization in %s", base), false + } + + filterFunc, err := imagesFilter(app.Spec.Source.Kustomize.Images) if err != nil { - return + return err, false } - kustomization, err := kf.Read() + err = kyaml.UpdateFile(filterFunc, kustFile) if err != nil { - return + return err, false } -Images: - for _, img := range app.Spec.Source.Kustomize.Images { - override := parseImageOverride(img) - for i, imgSet := range kustomization.Images { - if imgSet.Name == override.Name { - kustomization.Images[i] = override - continue Images - } + return nil, false +} + +func imagesFilter(images v1alpha1.KustomizeImages) (kyaml.Filter, error) { + var overrides []kyaml.Filter + for _, img := range images { + override, err := imageFilter(parseImageOverride(img)) + if err != nil { + return nil, err } - // wasn't an existing override, add one - kustomization.Images = append(kustomization.Images, override) + overrides = append(overrides, override) } - if err := kf.Write(kustomization); err != nil { - return err, false + return kyaml.FilterFunc(func(object *kyaml.RNode) (*kyaml.RNode, error) { + err := object.PipeE(append([]kyaml.Filter{kyaml.LookupCreate( + kyaml.SequenceNode, "images", + )}, overrides...)...) + return object, err + }), nil +} + +func imageFilter(imgSet types.Image) (kyaml.Filter, error) { + data, err := kyaml.Marshal(imgSet) + if err != nil { + return nil, err + } + update, err := kyaml.Parse(string(data)) + if err != nil { + return nil, err } + setter := kyaml.ElementSetter{ + Element: update.YNode(), + Keys: []string{"name"}, + Values: []string{imgSet.Name}, + } + return kyaml.FilterFunc(func(object *kyaml.RNode) (*kyaml.RNode, error) { + return object, object.PipeE(setter) + }), nil +} - return +func findKustomization(base string) string { + for _, f := range konfig.RecognizedKustomizationFileNames() { + kustFile := path.Join(base, f) + if stat, err := os.Stat(kustFile); err == nil && !stat.IsDir() { + return kustFile + } + } + return "" } -func parseImageOverride(str v1alpha1.KustomizeImage) image2.Image { +func parseImageOverride(str v1alpha1.KustomizeImage) types.Image { // TODO is this a valid use? format could diverge img := image.NewFromIdentifier(string(str)) tagName := "" @@ -267,7 +302,7 @@ func parseImageOverride(str v1alpha1.KustomizeImage) image2.Image { img.ImageAlias = img.ImageName img.ImageName = "" // inside baseball (see return): name isn't changing, just tag, so don't write newName } - return image2.Image{ + return types.Image{ Name: img.ImageAlias, NewName: img.ImageName, NewTag: tagName, diff --git a/pkg/argocd/git_test.go b/pkg/argocd/git_test.go index 5a60d57d..92731d51 100644 --- a/pkg/argocd/git_test.go +++ b/pkg/argocd/git_test.go @@ -9,9 +9,11 @@ import ( "github.com/argoproj-labs/argocd-image-updater/pkg/image" "github.com/argoproj-labs/argocd-image-updater/pkg/tag" + "sigs.k8s.io/kustomize/api/types" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/stretchr/testify/assert" - image2 "sigs.k8s.io/kustomize/pkg/image" ) func Test_TemplateCommitMessage(t *testing.T) { @@ -44,36 +46,36 @@ func Test_parseImageOverride(t *testing.T) { cases := []struct { name string override v1alpha1.KustomizeImage - expected image2.Image + expected types.Image }{ - {"tag update", "ghcr.io:1234/foo/foo:123", image2.Image{ + {"tag update", "ghcr.io:1234/foo/foo:123", types.Image{ Name: "ghcr.io:1234/foo/foo", NewTag: "123", }}, - {"image update", "ghcr.io:1234/foo/foo=ghcr.io:1234/bar", image2.Image{ + {"image update", "ghcr.io:1234/foo/foo=ghcr.io:1234/bar", types.Image{ Name: "ghcr.io:1234/foo/foo", NewName: "ghcr.io:1234/bar", }}, - {"update everything", "ghcr.io:1234/foo/foo=1234.foo.com:9876/bar:123", image2.Image{ + {"update everything", "ghcr.io:1234/foo/foo=1234.foo.com:9876/bar:123", types.Image{ Name: "ghcr.io:1234/foo/foo", NewName: "1234.foo.com:9876/bar", NewTag: "123", }}, - {"change registry and tag", "ghcr.io:1234/foo/foo=1234.dkr.ecr.us-east-1.amazonaws.com/bar:123", image2.Image{ + {"change registry and tag", "ghcr.io:1234/foo/foo=1234.dkr.ecr.us-east-1.amazonaws.com/bar:123", types.Image{ Name: "ghcr.io:1234/foo/foo", NewName: "1234.dkr.ecr.us-east-1.amazonaws.com/bar", NewTag: "123", }}, - {"change only registry", "0001.dkr.ecr.us-east-1.amazonaws.com/bar=1234.dkr.ecr.us-east-1.amazonaws.com/bar", image2.Image{ + {"change only registry", "0001.dkr.ecr.us-east-1.amazonaws.com/bar=1234.dkr.ecr.us-east-1.amazonaws.com/bar", types.Image{ Name: "0001.dkr.ecr.us-east-1.amazonaws.com/bar", NewName: "1234.dkr.ecr.us-east-1.amazonaws.com/bar", }}, - {"change image and set digest", "foo=acme/app@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", image2.Image{ + {"change image and set digest", "foo=acme/app@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", types.Image{ Name: "foo", NewName: "acme/app", Digest: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", }}, - {"set digest", "acme/app@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", image2.Image{ + {"set digest", "acme/app@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", types.Image{ Name: "acme/app", Digest: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", }}, @@ -86,3 +88,66 @@ func Test_parseImageOverride(t *testing.T) { } } + +func Test_imagesFilter(t *testing.T) { + for _, tt := range []struct { + name string + images v1alpha1.KustomizeImages + expected string + }{ + {name: "simple", images: v1alpha1.KustomizeImages{"foo"}, expected: ` +images: +- name: foo +`}, + {name: "tagged", images: v1alpha1.KustomizeImages{"foo:bar"}, expected: ` +images: +- name: foo + newTag: bar +`}, + {name: "rename", images: v1alpha1.KustomizeImages{"baz=foo:bar"}, expected: ` +images: +- name: baz + newName: foo + newTag: bar +`}, + {name: "digest", images: v1alpha1.KustomizeImages{"baz=foo@sha12345"}, expected: ` +images: +- name: baz + newName: foo + digest: sha12345 +`}, + {name: "digest simple", images: v1alpha1.KustomizeImages{"foo@sha12345"}, expected: ` +images: +- name: foo + digest: sha12345 +`}, + {name: "all", images: v1alpha1.KustomizeImages{ + "foo", + "foo=bar", // merges with above + "baz@sha12345", + "bar:123", + "foo=bar:123", // merges and overwrites the first two + }, expected: ` +images: +- name: foo + newName: bar + newTag: "123" +- name: baz + digest: sha12345 +- name: bar + newTag: "123" +`}, + } { + t.Run(tt.name, func(t *testing.T) { + filter, err := imagesFilter(tt.images) + assert.NoError(t, err) + + node := kyaml.NewRNode(&kyaml.Node{Kind: kyaml.DocumentNode, Content: []*kyaml.Node{ + kyaml.NewMapRNode(nil).YNode(), + }}) + node, err = filter.Filter(node) + assert.NoError(t, err) + assert.YAMLEq(t, tt.expected, node.MustString()) + }) + } +} diff --git a/pkg/argocd/update_test.go b/pkg/argocd/update_test.go index 5b94b314..8ae9b2c3 100644 --- a/pkg/argocd/update_test.go +++ b/pkg/argocd/update_test.go @@ -1737,6 +1737,8 @@ func Test_CommitUpdates(t *testing.T) { assert.NoError(t, ioutil.WriteFile(kf, []byte(` kind: Kustomization apiVersion: kustomize.config.k8s.io/v1beta1 + +replacements: [] `), os.ModePerm)) gitMock.On("Checkout", mock.Anything).Run(func(args mock.Arguments) { @@ -1765,6 +1767,8 @@ images: - name: bar newName: baz newTag: "123" + +replacements: [] `, string(kust)) // test the merge case too @@ -1781,6 +1785,8 @@ images: newTag: "123" - name: bar newName: qux + +replacements: [] `, string(kust)) })