From 3b8a8793f2b610c4c00058398bdcec2e6e35ef3a Mon Sep 17 00:00:00 2001 From: Jaye Doepke Date: Tue, 9 Jan 2024 14:17:01 -0600 Subject: [PATCH] Preserve field order when updating kustomization.yaml file This commit causes argocd-image-updater to preserve the order of fields in the kustomization.yaml file when updating the image(s). Doing so will minimize git diffs in the case that other tools also edit the kustomization.yaml file. Fixes #502 --- pkg/argocd/git.go | 34 ++++++++++++++++++-- pkg/argocd/git_test.go | 72 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/pkg/argocd/git.go b/pkg/argocd/git.go index 76af3910..e48d8491 100644 --- a/pkg/argocd/git.go +++ b/pkg/argocd/git.go @@ -12,6 +12,7 @@ import ( "sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kyaml/order" kyaml "sigs.k8s.io/kustomize/kyaml/yaml" "github.com/argoproj-labs/argocd-image-updater/pkg/image" @@ -314,14 +315,43 @@ func writeKustomization(app *v1alpha1.Application, wbc *WriteBackConfig, gitC gi if err != nil { return err, false } - err = kyaml.UpdateFile(filterFunc, kustFile) - if err != nil { + + if err = updateKustomizeFile(filterFunc, kustFile); err != nil { return err, false } return nil, false } +// updateKustomizeFile reads the kustomization file at path, applies the filter to it, and writes the result back +// to the file. This is the same behavior as kyaml.UpdateFile, but it preserves the original order +// of YAML fields to minimize git diffs. +func updateKustomizeFile(filter kyaml.Filter, path string) error { + // Read the yaml + y, err := kyaml.ReadFile(path) + if err != nil { + return err + } + + // Update the yaml + yCpy := y.Copy() + if err := yCpy.PipeE(filter); err != nil { + return err + } + + // Preserve the original order of fields + if err := order.SyncOrder(y, yCpy); err != nil { + return err + } + + // Write the yaml + if err := kyaml.WriteFile(yCpy, path); err != nil { + return err + } + + return nil +} + func imagesFilter(images v1alpha1.KustomizeImages) (kyaml.Filter, error) { var overrides []kyaml.Filter for _, img := range images { diff --git a/pkg/argocd/git_test.go b/pkg/argocd/git_test.go index 69f7027f..4070ae8a 100644 --- a/pkg/argocd/git_test.go +++ b/pkg/argocd/git_test.go @@ -1,6 +1,7 @@ package argocd import ( + "os" "testing" "text/template" "time" @@ -215,3 +216,74 @@ images: }) } } + +func Test_updateKustomizeFile(t *testing.T) { + makeTmpKustomization := func(t *testing.T, content []byte) string { + f, err := os.CreateTemp("", "kustomization-*.yaml") + if err != nil { + t.Fatal(err) + } + _, err = f.Write(content) + if err != nil { + t.Fatal(err) + } + f.Close() + t.Cleanup(func() { + os.Remove(f.Name()) + }) + return f.Name() + } + + filter, err := imagesFilter(v1alpha1.KustomizeImages{"foo@sha23456"}) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + content []byte + wantContent []byte + filter kyaml.Filter + wantErr bool + }{ + { + name: "sorted", + content: []byte(`images: +- digest: sha12345 + name: foo +`), + wantContent: []byte(`images: +- digest: sha23456 + name: foo +`), + filter: filter, + }, + { + name: "not-sorted", + content: []byte(`images: +- name: foo + digest: sha12345 +`), + wantContent: []byte(`images: +- name: foo + digest: sha23456 +`), + filter: filter, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + path := makeTmpKustomization(t, tt.content) + err := updateKustomizeFile(tt.filter, path) + if tt.wantErr { + assert.Error(t, err) + } else { + got, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, string(tt.wantContent), string(got)) + } + }) + } +}