diff --git a/cmd/flux/build_kustomization_test.go b/cmd/flux/build_kustomization_test.go index d1d068172a..383fb1fc07 100644 --- a/cmd/flux/build_kustomization_test.go +++ b/cmd/flux/build_kustomization_test.go @@ -159,6 +159,7 @@ spec: tmpl := map[string]string{ "fluxns": allocateNamespace("flux-system"), } + setup(t, tmpl) testEnv.CreateObjectFile("./testdata/build-kustomization/podinfo-source.yaml", tmpl, t) diff --git a/internal/build/build.go b/internal/build/build.go index 98f0f68310..a9e55c6840 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -204,19 +204,36 @@ func NewBuilder(name, resources string, opts ...BuilderOptionFunc) (*Builder, er return b, nil } +func (b *Builder) resolveKustomization(liveKus *kustomizev1.Kustomization) (k *kustomizev1.Kustomization, err error) { + // local kustomization file takes precedence over live kustomization + if b.kustomizationFile != "" { + k, err = b.unMarshallKustomization() + if err != nil { + return + } + if !b.dryRun && liveKus != nil && liveKus.Status.Inventory != nil { + // merge the live kustomization status with the local kustomization in order to get the + // live resources status + k.Status = *liveKus.Status.DeepCopy() + } + } else { + k = liveKus + } + return +} + func (b *Builder) getKustomization(ctx context.Context) (*kustomizev1.Kustomization, error) { + liveKus := &kustomizev1.Kustomization{} namespacedName := types.NamespacedName{ Namespace: b.namespace, Name: b.name, } - - k := &kustomizev1.Kustomization{} - err := b.client.Get(ctx, namespacedName, k) + err := b.client.Get(ctx, namespacedName, liveKus) if err != nil { return nil, err } - return k, nil + return liveKus, nil } // Build builds the yaml manifests from the kustomization object @@ -251,19 +268,18 @@ func (b *Builder) build() (m resmap.ResMap, err error) { defer cancel() // Get the kustomization object - var k *kustomizev1.Kustomization - if b.kustomizationFile != "" { - k, err = b.unMarshallKustomization() - if err != nil { - return - } - } else { - k, err = b.getKustomization(ctx) + liveKus := &kustomizev1.Kustomization{} + if !b.dryRun { + liveKus, err = b.getKustomization(ctx) if err != nil { - err = fmt.Errorf("failed to get kustomization object: %w", err) - return + return nil, fmt.Errorf("failed to get kustomization object: %w", err) } } + k, err := b.resolveKustomization(liveKus) + if err != nil { + err = fmt.Errorf("failed to get kustomization object: %w", err) + return + } // store the kustomization object b.kustomization = k diff --git a/internal/build/build_test.go b/internal/build/build_test.go index df4a70993d..cb40c82c03 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -17,10 +17,15 @@ limitations under the License. package build import ( + "fmt" "strings" "testing" + "time" + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + "github.com/fluxcd/pkg/apis/meta" "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/kyaml/yaml" ) @@ -215,3 +220,135 @@ func Test_unMarshallKustomization(t *testing.T) { }) } } + +func Test_ResolveKustomization(t *testing.T) { + tests := []struct { + name string + localKsFile string + liveKustomization *kustomizev1.Kustomization + dryrun bool + }{ + { + name: "valid kustomization", + localKsFile: "testdata/local-kustomization/valid.yaml", + }, + { + name: "local and live kustomization", + localKsFile: "testdata/local-kustomization/valid.yaml", + liveKustomization: &kustomizev1.Kustomization{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podinfo", + Namespace: "flux-system", + }, + Spec: kustomizev1.KustomizationSpec{ + Interval: metav1.Duration{Duration: time.Minute * 5}, + Path: "./testdata/local-kustomization/valid.yaml", + }, + Status: kustomizev1.KustomizationStatus{ + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + Inventory: &kustomizev1.ResourceInventory{ + Entries: []kustomizev1.ResourceRef{ + { + ID: "flux-system_podinfo_v1_service_podinfo", + Version: "v1", + }, + }, + }, + }, + }, + }, + { + name: "local and live kustomization with dryrun", + localKsFile: "testdata/local-kustomization/valid.yaml", + liveKustomization: &kustomizev1.Kustomization{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podinfo", + Namespace: "flux-system", + }, + Spec: kustomizev1.KustomizationSpec{ + Interval: metav1.Duration{Duration: time.Minute * 5}, + Path: "./testdata/local-kustomization/valid.yaml", + }, + Status: kustomizev1.KustomizationStatus{ + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + Inventory: &kustomizev1.ResourceInventory{ + Entries: []kustomizev1.ResourceRef{ + { + ID: "flux-system_podinfo_v1_service_podinfo", + Version: "v1", + }, + }, + }, + }, + }, + dryrun: true, + }, + { + name: "live kustomization", + liveKustomization: &kustomizev1.Kustomization{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podinfo", + Namespace: "flux-system", + }, + Spec: kustomizev1.KustomizationSpec{ + Interval: metav1.Duration{Duration: time.Minute * 5}, + Path: "./testdata/local-kustomization/valid.yaml", + }, + Status: kustomizev1.KustomizationStatus{ + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + Inventory: &kustomizev1.ResourceInventory{ + Entries: []kustomizev1.ResourceRef{ + { + ID: "flux-system_podinfo_v1_service_podinfo", + Version: "v1", + }, + }, + }, + }, + }, + }, + } + + b := &Builder{} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b.kustomizationFile = tt.localKsFile + b.dryRun = tt.dryrun + ks, err := b.resolveKustomization(tt.liveKustomization) + if err != nil { + t.Errorf("unexpected err '%s'", err) + } + if !tt.dryrun { + if b.kustomizationFile == "" { + if cmp.Diff(ks, tt.liveKustomization) != "" { + t.Errorf("expected kustomization to match live kustomization") + } + } else { + if tt.liveKustomization != nil && cmp.Diff(ks.Status, tt.liveKustomization.Status) != "" { + t.Errorf("expected kustomization status to match live kustomization status") + } + } + } else { + if ks.Status.Inventory != nil { + fmt.Println(ks.Status.Inventory) + t.Errorf("expected kustomization status to be nil") + } + } + }) + } +} diff --git a/internal/build/diff.go b/internal/build/diff.go index e693e1d969..c316df9f20 100644 --- a/internal/build/diff.go +++ b/internal/build/diff.go @@ -136,11 +136,14 @@ func (b *Builder) Diff() (string, bool, error) { if b.kustomization.Spec.Prune && len(diffErrs) == 0 { oldStatus := b.kustomization.Status.DeepCopy() if oldStatus.Inventory != nil { - diffObjects, err := diffInventory(oldStatus.Inventory, newInventory) + staleObjects, err := diffInventory(oldStatus.Inventory, newInventory) if err != nil { return "", createdOrDrifted, err } - for _, object := range diffObjects { + if len(staleObjects) > 0 { + createdOrDrifted = true + } + for _, object := range staleObjects { output.WriteString(writeString(fmt.Sprintf("► %s deleted\n", ssa.FmtUnstructured(object)), bunt.OrangeRed)) } }