/* Copyright © 2022 - 2025 SUSE LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package controllers import ( "context" "encoding/json" "errors" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" elementalv1 "github.com/rancher/elemental-operator/api/v1beta1" systemagent "github.com/rancher/elemental-operator/internal/system-agent" "github.com/rancher/elemental-operator/pkg/network" "github.com/rancher/elemental-operator/pkg/test" apierrors "k8s.io/apimachinery/pkg/api/errors" ) var _ = Describe("reconcile machine inventory", func() { var r *MachineInventoryReconciler var mInventory *elementalv1.MachineInventory var planSecret *corev1.Secret BeforeEach(func() { r = &MachineInventoryReconciler{ Client: cl, } mInventory = &elementalv1.MachineInventory{ ObjectMeta: metav1.ObjectMeta{ Finalizers: []string{elementalv1.MachineInventoryFinalizer}, Name: "machine-inventory-suite", Namespace: "default", }, } planSecret = &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, Data: map[string][]byte{ "applied-checksum": []byte("appliedchecksum"), }, } Expect(cl.Create(ctx, mInventory)).To(Succeed()) }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, mInventory, planSecret)).To(Succeed()) }) It("should reconcile machine inventory object when plan secret doesn't exist", func() { _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, mInventory)).To(Succeed()) cond := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.ReadyCondition) Expect(cond).NotTo(BeNil()) Expect(cond.Reason).To(Equal(elementalv1.WaitingForPlanReason)) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Message).To(Equal("waiting for plan to be applied")) }) It("should reconcile machine inventory object when plan secret already exists", func() { Expect(cl.Create(ctx, planSecret)).To(Succeed()) _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, mInventory)).To(Succeed()) Expect(mInventory.Status.Plan.Checksum).To(Equal(string(planSecret.Data["applied-checksum"]))) Expect(mInventory.Status.Plan.State).To(Equal(elementalv1.PlanApplied)) cond := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.ReadyCondition) Expect(cond).NotTo(BeNil()) Expect(cond.Reason).To(Equal(elementalv1.PlanSuccessfullyAppliedReason)) Expect(cond.Status).To(Equal(metav1.ConditionTrue)) Expect(cond.Message).To(Equal("plan successfully applied")) }) It("should add finalizer if not exist", func() { noFinalizerMI := &elementalv1.MachineInventory{ ObjectMeta: metav1.ObjectMeta{ Name: "machine-inventory-no-finalizer", Namespace: "default", }, } Expect(cl.Create(ctx, noFinalizerMI)).To(Succeed()) _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, noFinalizerMI)).To(Succeed()) Expect(controllerutil.ContainsFinalizer(noFinalizerMI, elementalv1.MachineInventoryFinalizer)).To(BeTrue()) Expect(test.CleanupAndWait(ctx, cl, noFinalizerMI)).To(Succeed()) }) }) var _ = Describe("createPlanSecret", func() { var r *MachineInventoryReconciler var mInventory *elementalv1.MachineInventory var planSecret *corev1.Secret BeforeEach(func() { r = &MachineInventoryReconciler{ Client: cl, } mInventory = &elementalv1.MachineInventory{ ObjectMeta: metav1.ObjectMeta{ Name: "machine-inventory-suite", Namespace: "default", }, } planSecret = &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, } Expect(cl.Create(ctx, mInventory)).To(Succeed()) }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, mInventory, planSecret)).To(Succeed()) }) It("should succesfully create plan secret", func() { Expect(r.createPlanSecret(ctx, mInventory)).To(Succeed()) Expect(r.Get(ctx, types.NamespacedName{Namespace: mInventory.Namespace, Name: mInventory.Name}, planSecret)).To(Succeed()) Expect(planSecret.OwnerReferences).To(HaveLen(1)) Expect(planSecret.OwnerReferences[0].APIVersion).To(Equal(elementalv1.GroupVersion.String())) Expect(planSecret.OwnerReferences[0].Kind).To(Equal("MachineInventory")) Expect(planSecret.OwnerReferences[0].Name).To(Equal(mInventory.Name)) Expect(planSecret.OwnerReferences[0].UID).To(Equal(mInventory.UID)) Expect(planSecret.OwnerReferences[0].Controller).To(Equal(ptr.To(true))) Expect(planSecret.Labels).To(HaveKey(elementalv1.ElementalManagedLabel)) Expect(planSecret.Type).To(Equal(elementalv1.PlanSecretType)) Expect(planSecret.Data).To(HaveKey("plan")) Expect(mInventory.Status.Plan.PlanSecretRef.Name).To(Equal(mInventory.Name)) Expect(mInventory.Status.Plan.PlanSecretRef.Namespace).To(Equal(mInventory.Namespace)) cond := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.ReadyCondition) Expect(cond).NotTo(BeNil()) Expect(cond.Reason).To(Equal(elementalv1.WaitingForPlanReason)) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Message).To(Equal("waiting for plan to be applied")) }) It("shouldn't return error is secret already exists", func() { Expect(r.createPlanSecret(ctx, mInventory)).To(Succeed()) Expect(r.createPlanSecret(ctx, mInventory)).To(Succeed()) }) It("should return error is secret fails to be created", func() { r.Client = machineInventoryFailingClient{} err := r.createPlanSecret(ctx, mInventory) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to create")) }) It("should do nothing if ready condition is present", func() { meta.SetStatusCondition(&mInventory.Status.Conditions, metav1.Condition{ Type: elementalv1.ReadyCondition, Reason: elementalv1.PlanSuccessfullyAppliedReason, Status: metav1.ConditionTrue, Message: "plan successfully applied", }) Expect(r.createPlanSecret(ctx, mInventory)).To(Succeed()) }) It("should not reset network if configurator none", func() { inventoryWithNoneNetwork := &elementalv1.MachineInventory{ ObjectMeta: metav1.ObjectMeta{ Name: "machine-inventory-network-none", Namespace: "default", }, Spec: elementalv1.MachineInventorySpec{ Network: elementalv1.NetworkConfig{ Configurator: network.ConfiguratorNone, }, }, } Expect(r.Create(ctx, inventoryWithNoneNetwork)).Should(Succeed()) Expect(r.createPlanSecret(ctx, inventoryWithNoneNetwork)).To(Succeed()) Expect(r.Get(ctx, types.NamespacedName{Namespace: inventoryWithNoneNetwork.Namespace, Name: inventoryWithNoneNetwork.Name}, planSecret)).To(Succeed()) Expect(r.updatePlanSecretWithReset(ctx, inventoryWithNoneNetwork, planSecret)).Should(Succeed()) Expect(r.Get(ctx, types.NamespacedName{Namespace: inventoryWithNoneNetwork.Namespace, Name: inventoryWithNoneNetwork.Name}, planSecret)).To(Succeed()) planData, found := planSecret.Data["plan"] Expect(found).To(BeTrue(), "Secret should contain reset plan data") plan := &systemagent.Plan{} Expect(json.Unmarshal(planData, plan)).Should(Succeed()) Expect(len(plan.OneTimeInstructions)).Should(Equal(2), "Should contain 2 reset instructions") Expect(plan.OneTimeInstructions).ShouldNot(ContainElement(systemagent.OneTimeInstruction{ CommonInstruction: systemagent.CommonInstruction{ Name: "restore first boot config", Command: "elemental-register", Args: []string{"--debug", "--reset-network"}, }})) Expect(test.CleanupAndWait(ctx, cl, inventoryWithNoneNetwork)).To(Succeed()) }) It("should reset network if configurator not none", func() { inventoryWithNetwork := &elementalv1.MachineInventory{ ObjectMeta: metav1.ObjectMeta{ Name: "machine-inventory-network-nmc", Namespace: "default", }, Spec: elementalv1.MachineInventorySpec{ Network: elementalv1.NetworkConfig{ Configurator: network.ConfiguratorNmc, }, }, } Expect(r.Create(ctx, inventoryWithNetwork)).Should(Succeed()) Expect(r.createPlanSecret(ctx, inventoryWithNetwork)).To(Succeed()) Expect(r.Get(ctx, types.NamespacedName{Namespace: inventoryWithNetwork.Namespace, Name: inventoryWithNetwork.Name}, planSecret)).To(Succeed()) Expect(r.updatePlanSecretWithReset(ctx, inventoryWithNetwork, planSecret)).Should(Succeed()) Expect(r.Get(ctx, types.NamespacedName{Namespace: inventoryWithNetwork.Namespace, Name: inventoryWithNetwork.Name}, planSecret)).To(Succeed()) planData, found := planSecret.Data["plan"] Expect(found).To(BeTrue(), "Secret should contain reset plan data") plan := &systemagent.Plan{} Expect(json.Unmarshal(planData, plan)).Should(Succeed()) Expect(plan.OneTimeInstructions).Should(ContainElement(systemagent.OneTimeInstruction{ CommonInstruction: systemagent.CommonInstruction{ Name: "restore first boot config", Command: "elemental-register", Args: []string{"--debug", "--reset-network"}, }})) Expect(test.CleanupAndWait(ctx, cl, inventoryWithNetwork)).To(Succeed()) }) }) var _ = Describe("updateInventoryWithAdoptionStatus", func() { var r *MachineInventoryReconciler var mInventory *elementalv1.MachineInventory var miSelector *elementalv1.MachineInventorySelector BeforeEach(func() { r = &MachineInventoryReconciler{ Client: cl, } miSelector = &elementalv1.MachineInventorySelector{ ObjectMeta: metav1.ObjectMeta{ Name: "machine-selector", Namespace: "default", UID: "test", }, } mInventory = &elementalv1.MachineInventory{ ObjectMeta: metav1.ObjectMeta{ Name: "machine-inventory-suite", Namespace: "default", OwnerReferences: []metav1.OwnerReference{ { APIVersion: elementalv1.GroupVersion.String(), Kind: "MachineInventorySelector", Name: miSelector.ObjectMeta.Name, UID: miSelector.ObjectMeta.UID, }, }, }, } Expect(cl.Create(ctx, mInventory)).To(Succeed()) Expect(cl.Create(ctx, miSelector)).To(Succeed()) }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, mInventory, miSelector)).To(Succeed()) }) It("successfully verifies owner reference in status", func() { // Set selector reference Expect(cl.Get(ctx, types.NamespacedName{ Namespace: miSelector.Namespace, Name: miSelector.Name, }, miSelector)).To(Succeed()) miSelector.Status.MachineInventoryRef = &corev1.LocalObjectReference{ Name: mInventory.Name, } Expect(cl.Status().Update(ctx, miSelector)).To(Succeed()) requeue, err := r.updateInventoryWithAdoptionStatus(ctx, mInventory) Expect(err).To(Succeed()) Expect(requeue).To(BeFalse()) cond := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.AdoptionReadyCondition) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionTrue)) Expect(cond.Reason).To(Equal(elementalv1.SuccessfullyAdoptedReason)) }) It("reaches adoption validation timeout", func() { testTimeout := time.Now().Add(adoptionTimeout * 2 * time.Second) requeue, err := r.updateInventoryWithAdoptionStatus(ctx, mInventory) for err == nil { Expect(err).To(Succeed()) Expect(requeue).To(BeTrue()) Expect(time.Now().Before(testTimeout)).To(BeTrue()) time.Sleep(time.Second) requeue, err = r.updateInventoryWithAdoptionStatus(ctx, mInventory) } Expect(err.Error()).To(ContainSubstring("Adoption timeout")) cond := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.AdoptionReadyCondition) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) Expect(cond.Reason).To(Equal(elementalv1.ValidatingAdoptionReason)) }) It("detects an adoption mismatch", func() { // Set selector reference Expect(cl.Get(ctx, types.NamespacedName{ Namespace: miSelector.Namespace, Name: miSelector.Name, }, miSelector)).To(Succeed()) miSelector.Status.MachineInventoryRef = &corev1.LocalObjectReference{ Name: "unexpectedName", } Expect(cl.Status().Update(ctx, miSelector)).To(Succeed()) requeue, err := r.updateInventoryWithAdoptionStatus(ctx, mInventory) Expect(err).NotTo(Succeed()) Expect(err.Error()).To(ContainSubstring("Ownership mismatch")) Expect(requeue).To(BeFalse()) }) It("has nothing if no owner is set", func() { mInventory.ObjectMeta.OwnerReferences = []metav1.OwnerReference{} requeue, err := r.updateInventoryWithAdoptionStatus(ctx, mInventory) Expect(err).To(Succeed()) Expect(requeue).To(BeFalse()) cond := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.AdoptionReadyCondition) Expect(cond).NotTo(BeNil()) Expect(cond.Status).To(Equal(metav1.ConditionFalse)) Expect(cond.Reason).To(Equal(elementalv1.WaitingToBeAdoptedReason)) }) }) var _ = Describe("updateInventoryWithPlanStatus", func() { var r *MachineInventoryReconciler var mInventory *elementalv1.MachineInventory var planSecret *corev1.Secret BeforeEach(func() { r = &MachineInventoryReconciler{ Client: cl, } mInventory = &elementalv1.MachineInventory{ ObjectMeta: metav1.ObjectMeta{ Name: "machine-inventory-suite", Namespace: "default", }, } planSecret = &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, } Expect(cl.Create(ctx, mInventory)).To(Succeed()) }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, mInventory, planSecret)).To(Succeed()) }) It("should succesfully update when plan is applied", func() { planSecret.Data = map[string][]byte{ "applied-checksum": []byte("applied-checksum"), } Expect(cl.Create(ctx, planSecret)).To(Succeed()) mInventory.Status.Plan = &elementalv1.PlanStatus{} Expect(r.updateInventoryWithPlanStatus(ctx, mInventory)).To(Succeed()) Expect(mInventory.Status.Plan.State).To(Equal(elementalv1.PlanApplied)) Expect(mInventory.Status.Conditions).To(HaveLen(1)) Expect(mInventory.Status.Conditions[0].Type).To(Equal(elementalv1.ReadyCondition)) Expect(mInventory.Status.Conditions[0].Reason).To(Equal(elementalv1.PlanSuccessfullyAppliedReason)) Expect(mInventory.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) Expect(mInventory.Status.Conditions[0].Message).To(Equal("plan successfully applied")) }) It("should return error when plan failed to be applied", func() { planSecret.Data = map[string][]byte{ "failed-checksum": []byte("failed-checksum"), } Expect(cl.Create(ctx, planSecret)).To(Succeed()) mInventory.Status.Plan = &elementalv1.PlanStatus{} err := r.updateInventoryWithPlanStatus(ctx, mInventory) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to apply plan")) }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, mInventory, planSecret)).To(Succeed()) }) }) var _ = Describe("handle finalizer", func() { var r *MachineInventoryReconciler var mInventory *elementalv1.MachineInventory var planSecret *corev1.Secret BeforeEach(func() { r = &MachineInventoryReconciler{ Client: cl, } mInventory = &elementalv1.MachineInventory{ ObjectMeta: metav1.ObjectMeta{ DeletionTimestamp: &metav1.Time{Time: time.Now()}, Annotations: map[string]string{elementalv1.MachineInventoryResettableAnnotation: "true"}, Finalizers: []string{elementalv1.MachineInventoryFinalizer}, Name: "machine-inventory-suite-finalizer", Namespace: "default", }, } planSecret = &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, } // 1. Create initial MachineInventory Expect(cl.Create(ctx, mInventory)).To(Succeed()) // 2. Create initial plan Secret _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) // 3. Update meta.DeletionTimestamp Expect(cl.Delete(ctx, mInventory)).To(Succeed()) // 4. Update secret with reset plan _, err = r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) // 5. Update MachineInventory plan status _, err = r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) }) It("should update secret with reset plan", func() { Expect(cl.Get(ctx, client.ObjectKey{ Name: planSecret.Name, Namespace: planSecret.Namespace, }, planSecret)).To(Succeed()) Expect(cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, mInventory)).To(Succeed()) wantChecksum, wantPlan, err := r.newResetPlan(ctx, false) Expect(err).ToNot(HaveOccurred()) // Check Plan status Expect(mInventory.Status.Plan.Checksum).To(Equal(wantChecksum)) Expect(mInventory.Status.Plan.PlanSecretRef.Name).To(Equal(planSecret.Name)) Expect(mInventory.Status.Plan.PlanSecretRef.Namespace).To(Equal(planSecret.Namespace)) Expect(mInventory.Status.Plan.State).To(Equal(elementalv1.PlanState(""))) // Check MachineInventory status Expect(mInventory.Status.Conditions[0].Type).To(Equal(elementalv1.ReadyCondition)) Expect(mInventory.Status.Conditions[0].Reason).To(Equal(elementalv1.WaitingForPlanReason)) Expect(mInventory.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse)) Expect(mInventory.Status.Conditions[0].Message).To(Equal("waiting for plan to be applied")) // Check plan secret was updated Expect(planSecret.Annotations[elementalv1.PlanTypeAnnotation]).To(Equal(elementalv1.PlanTypeReset)) Expect(planSecret.Data["plan"]).To(Equal(wantPlan)) Expect(planSecret.Data["applied-checksum"]).To(Equal([]byte(""))) Expect(planSecret.Data["failed-checksum"]).To(Equal([]byte(""))) // Check we are holding on the MachineInventory (preventing actual deletion) _, err = r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, mInventory)).To(Succeed()) }) It("should remove finalizer on reset plan applied", func() { // 6. Mark the reset plan as applied Expect(cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, planSecret)).To(Succeed()) planSecret.Data["applied-checksum"] = []byte("applied") Expect(cl.Update(ctx, planSecret)).To(Succeed()) // 7. Trigger finalizer removal _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) // Check MachineInventory was actually deleted err = cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, mInventory) Expect(apierrors.IsNotFound(err)).To(BeTrue()) }) It("should delete by removing finalizer when resettable annotation is false", func() { // 6. Manually disable resettable annotation Expect(cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, mInventory)).To(Succeed()) mInventory.Annotations[elementalv1.MachineInventoryResettableAnnotation] = "false" Expect(cl.Update(ctx, mInventory)).To(Succeed()) // 7. Trigger finalizer removal _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) // Check MachineInventory was actually deleted err = cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, mInventory) Expect(apierrors.IsNotFound(err)).To(BeTrue()) }) It("should delete by removing finalizer when up for deletion", func() { // 6. Manually remove finalizer Expect(cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, mInventory)).To(Succeed()) controllerutil.RemoveFinalizer(mInventory, elementalv1.MachineInventoryFinalizer) Expect(cl.Update(ctx, mInventory)).To(Succeed()) // Check MachineInventory was actually deleted err := cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, mInventory) Expect(apierrors.IsNotFound(err)).To(BeTrue()) }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, mInventory, planSecret)).To(Succeed()) }) }) var _ = Describe("handle unmanaged finalizer", func() { var r *MachineInventoryReconciler var mInventory *elementalv1.MachineInventory var planSecret *corev1.Secret BeforeEach(func() { r = &MachineInventoryReconciler{ Client: cl, } mInventory = &elementalv1.MachineInventory{ ObjectMeta: metav1.ObjectMeta{ DeletionTimestamp: &metav1.Time{Time: time.Now()}, Annotations: map[string]string{elementalv1.MachineInventoryResettableAnnotation: "true", elementalv1.MachineInventoryOSUnmanagedAnnotation: "true"}, Finalizers: []string{elementalv1.MachineInventoryFinalizer}, Name: "machine-inventory-suite-finalizer", Namespace: "default", }, } planSecret = &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, } // 1. Create initial MachineInventory Expect(cl.Create(ctx, mInventory)).To(Succeed()) // 2. Create initial plan Secret _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) // 3. Update meta.DeletionTimestamp Expect(cl.Delete(ctx, mInventory)).To(Succeed()) // 4. Update secret with reset plan _, err = r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) // 5. Update MachineInventory plan status _, err = r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) }) It("should update secret with reset plan when unmanaged annotation is true", func() { // Manually enable os.unmanaged annotation Expect(cl.Get(ctx, client.ObjectKey{ Name: planSecret.Name, Namespace: planSecret.Namespace, }, planSecret)).To(Succeed()) Expect(cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, mInventory)).To(Succeed()) mInventory.Annotations[elementalv1.MachineInventoryOSUnmanagedAnnotation] = "true" Expect(cl.Update(ctx, mInventory)).To(Succeed()) _, wantPlan, err := r.newUnmanagedResetPlan(ctx) Expect(err).ToNot(HaveOccurred()) // Check Plan status Expect(mInventory.Status.Plan.PlanSecretRef.Name).To(Equal(planSecret.Name)) Expect(mInventory.Status.Plan.PlanSecretRef.Namespace).To(Equal(planSecret.Namespace)) Expect(mInventory.Status.Plan.State).To(Equal(elementalv1.PlanState(""))) // Check MachineInventory status Expect(mInventory.Status.Conditions[0].Type).To(Equal(elementalv1.ReadyCondition)) Expect(mInventory.Status.Conditions[0].Reason).To(Equal(elementalv1.WaitingForPlanReason)) Expect(mInventory.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse)) Expect(mInventory.Status.Conditions[0].Message).To(Equal("waiting for plan to be applied")) // Check plan secret was updated Expect(planSecret.Annotations[elementalv1.PlanTypeAnnotation]).To(Equal(elementalv1.PlanTypeReset)) Expect(string(planSecret.Data["plan"])).To(Equal(string(wantPlan))) Expect(string(planSecret.Data["applied-checksum"])).To(Equal("")) Expect(string(planSecret.Data["failed-checksum"])).To(Equal("")) // Check we are holding on the MachineInventory (preventing actual deletion) _, err = r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) Expect(cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, mInventory)).To(Succeed()) }) It("should remove finalizer on unmanaged reset plan applied", func() { // 6. Mark the reset plan as applied Expect(cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, planSecret)).To(Succeed()) mInventory.Annotations[elementalv1.MachineInventoryOSUnmanagedAnnotation] = "true" planSecret.Data["applied-checksum"] = []byte("applied") Expect(cl.Update(ctx, planSecret)).To(Succeed()) // 7. Trigger finalizer removal _, err := r.Reconcile(ctx, reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: mInventory.Namespace, Name: mInventory.Name, }, }) Expect(err).ToNot(HaveOccurred()) // Check MachineInventory was actually deleted err = cl.Get(ctx, client.ObjectKey{ Name: mInventory.Name, Namespace: mInventory.Namespace, }, mInventory) Expect(apierrors.IsNotFound(err)).To(BeTrue()) }) AfterEach(func() { Expect(test.CleanupAndWait(ctx, cl, mInventory, planSecret)).To(Succeed()) }) }) type machineInventoryFailingClient struct { client.Client } func (cl machineInventoryFailingClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { return errors.New("failed to create") }