diff --git a/api/v1alpha1/conditions.go b/api/v1alpha1/conditions.go index f30922fa..13a3644a 100644 --- a/api/v1alpha1/conditions.go +++ b/api/v1alpha1/conditions.go @@ -28,7 +28,14 @@ func SetGitOpsSetReadiness(set *GitOpsSet, status metav1.ConditionStatus, reason apimeta.SetStatusCondition(&set.Status.Conditions, newCondition) } +// SetReadyWithInventory updates the GitOpsSet to reflect the new readiness and +// store the current inventory. func SetReadyWithInventory(set *GitOpsSet, inventory *ResourceInventory, reason, message string) { set.Status.Inventory = inventory + + if len(inventory.Entries) == 0 { + set.Status.Inventory = nil + } + SetGitOpsSetReadiness(set, metav1.ConditionTrue, reason, message) } diff --git a/api/v1alpha1/inventory.go b/api/v1alpha1/inventory.go index 733bbf74..9990ccf7 100644 --- a/api/v1alpha1/inventory.go +++ b/api/v1alpha1/inventory.go @@ -3,7 +3,7 @@ package v1alpha1 // ResourceInventory contains a list of Kubernetes resource object references that have been applied by a Kustomization. type ResourceInventory struct { // Entries of Kubernetes resource object references. - Entries []ResourceRef `json:"entries"` + Entries []ResourceRef `json:"entries,omitempty"` } // ResourceRef contains the information necessary to locate a resource within a cluster. diff --git a/config/crd/bases/templates.weave.works_gitopssets.yaml b/config/crd/bases/templates.weave.works_gitopssets.yaml index 0309ed1b..ebd926a2 100644 --- a/config/crd/bases/templates.weave.works_gitopssets.yaml +++ b/config/crd/bases/templates.weave.works_gitopssets.yaml @@ -196,8 +196,6 @@ spec: - v type: object type: array - required: - - entries type: object observedGeneration: description: ObservedGeneration is the last observed generation of diff --git a/controllers/gitopsset_controller.go b/controllers/gitopsset_controller.go index 67041b9f..7cf86140 100644 --- a/controllers/gitopsset_controller.go +++ b/controllers/gitopsset_controller.go @@ -56,8 +56,9 @@ type GitOpsSetReconciler struct { // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile -func (r *GitOpsSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *GitOpsSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) { logger := log.FromContext(ctx) + var gitOpsSet templatesv1.GitOpsSet if err := r.Client.Get(ctx, req.NamespacedName, &gitOpsSet); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) @@ -82,8 +83,10 @@ func (r *GitOpsSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( if inventory != nil { templatesv1.SetReadyWithInventory(&gitOpsSet, inventory, templatesv1.ReconciliationSucceededReason, fmt.Sprintf("%d resources created", len(inventory.Entries))) + if err := r.patchStatus(ctx, req, gitOpsSet.Status); err != nil { logger.Error(err, "failed to reconcile") + return ctrl.Result{}, fmt.Errorf("failed to update status and inventory: %w", err) } } diff --git a/controllers/gitopsset_controller_test.go b/controllers/gitopsset_controller_test.go index 8bba146f..ae980f2a 100644 --- a/controllers/gitopsset_controller_test.go +++ b/controllers/gitopsset_controller_test.go @@ -267,6 +267,56 @@ func TestReconciliation(t *testing.T) { t.Fatalf("failed to update Kustomization:\n%s", diff) } }) + + t.Run("reconciling with no generated resources", func(t *testing.T) { + ctx := context.TODO() + devKS := makeTestKustomization(nsn("default", "engineering-dev-demo"), func(k *kustomizev1.Kustomization) { + k.ObjectMeta.Annotations = map[string]string{ + "testing": "existingResource", + } + }) + test.AssertNoError(t, k8sClient.Create(ctx, test.ToUnstructured(t, devKS))) + defer deleteAllKustomizations(t, k8sClient) + + gs := makeTestGitOpsSet(t, func(gs *templatesv1.GitOpsSet) { + // No templates to generate resources from + gs.Spec.Templates = []templatesv1.GitOpsSetTemplate{} + gs.Spec.Generators = []templatesv1.GitOpsSetGenerator{ + { + List: &templatesv1.ListGenerator{ + Elements: []apiextensionsv1.JSON{ + {Raw: []byte(`{"cluster": "engineering-dev"}`)}, + }, + }, + }, + } + }) + test.AssertNoError(t, k8sClient.Create(ctx, gs)) + defer cleanupResource(t, k8sClient, gs) + + ref, err := resourceRefFromObject(devKS) + test.AssertNoError(t, err) + + gs.Status.Inventory = &templatesv1.ResourceInventory{ + Entries: []templatesv1.ResourceRef{ref}, + } + if err := k8sClient.Status().Update(ctx, gs); err != nil { + t.Fatal(err) + } + + _, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(gs)}) + if err != nil { + t.Fatal(err) + } + + updated := &templatesv1.GitOpsSet{} + if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gs), updated); err != nil { + t.Fatal(err) + } + + assertInventoryHasNoItems(t, updated) + }) + } func deleteAllKustomizations(t *testing.T, cl client.Client) { @@ -344,6 +394,17 @@ func assertInventoryHasItems(t *testing.T, gs *templatesv1.GitOpsSet, objs ...ru } } +func assertInventoryHasNoItems(t *testing.T, gs *templatesv1.GitOpsSet) { + t.Helper() + if gs.Status.Inventory == nil { + return + } + + if l := len(gs.Status.Inventory.Entries); l != 0 { + t.Errorf("expected inventory to have 0 items, got %v", l) + } +} + func cleanupResource(t *testing.T, cl client.Client, obj client.Object) { if err := cl.Delete(context.TODO(), obj); err != nil { t.Fatal(err)