Skip to content

Commit

Permalink
fix: use kyaml to preserve kustomization (#274)
Browse files Browse the repository at this point in the history
* fix: use kyaml to preserve kustomization

fixes #250
fixes #268
fixes #247

* chore: linter error

* fix: tests and linting

* go mod tidy
  • Loading branch information
iamnoah authored Oct 21, 2021
1 parent 89b10c1 commit 4b13043
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 34 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
79 changes: 57 additions & 22 deletions pkg/argocd/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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 := ""
Expand All @@ -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,
Expand Down
83 changes: 74 additions & 9 deletions pkg/argocd/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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",
}},
Expand All @@ -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())
})
}
}
6 changes: 6 additions & 0 deletions pkg/argocd/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -1765,6 +1767,8 @@ images:
- name: bar
newName: baz
newTag: "123"
replacements: []
`, string(kust))

// test the merge case too
Expand All @@ -1781,6 +1785,8 @@ images:
newTag: "123"
- name: bar
newName: qux
replacements: []
`, string(kust))
})

Expand Down

0 comments on commit 4b13043

Please sign in to comment.