diff --git a/internal/controller/management_controller.go b/internal/controller/management_controller.go index 30f51663d..1bab92ac8 100644 --- a/internal/controller/management_controller.go +++ b/internal/controller/management_controller.go @@ -45,15 +45,6 @@ import ( "github.com/Mirantis/hmc/internal/utils/status" ) -// Those are only needed for the initial installation -var enforcedValues = map[string]any{ - "controller": map[string]any{ - "createManagement": false, - "createTemplateManagement": false, - "createRelease": false, - }, -} - // ManagementReconciler reconciles a Management object type ManagementReconciler struct { client.Client @@ -92,101 +83,139 @@ func (r *ManagementReconciler) Update(ctx context.Context, management *hmc.Manag if controllerutil.AddFinalizer(management, hmc.ManagementFinalizer) { if err := r.Client.Update(ctx, management); err != nil { - l.Error(err, "Failed to update Management finalizers") + l.Error(err, "failed to update Management finalizers") return ctrl.Result{}, err } return ctrl.Result{}, nil } - if err := r.ensureTemplateManagement(ctx, management); err != nil { - l.Error(err, "Failed to ensure TemplateManagement is created") + if err := r.cleanupRemovedComponents(ctx, management); err != nil { + l.Error(err, "failed to cleanup removed components") return ctrl.Result{}, err } - release := &hmc.Release{} - if err := r.Client.Get(ctx, client.ObjectKey{Name: management.Spec.Release}, release); err != nil { - l.Error(err, "failed to get Release object") + if err := r.ensureTemplateManagement(ctx, management); err != nil { + l.Error(err, "failed to ensure TemplateManagement is created") return ctrl.Result{}, err } - var ( - errs error - - detectedProviders = hmc.Providers{} - detectedComponents = make(map[string]hmc.ComponentStatus) - detectedContracts = make(map[string]hmc.CompatibilityContracts) - ) - - err := r.enableAdditionalComponents(ctx, management) - if err != nil { + if err := r.enableAdditionalComponents(ctx, management); err != nil { // TODO (zerospiel): i wonder, do we need to reflect these changes and changes from the `wrappedComponents` in the spec? l.Error(err, "failed to enable additional HMC components") return ctrl.Result{}, err } - components, err := wrappedComponents(management, release) + components, err := getWrappedComponents(ctx, r.Client, management) if err != nil { l.Error(err, "failed to wrap HMC components") return ctrl.Result{}, err } + + var ( + errs error + + statusAccumulator = &mgmtStatusAccumulator{ + providers: hmc.Providers{}, + components: make(map[string]hmc.ComponentStatus), + compatibilityContracts: make(map[string]hmc.CompatibilityContracts), + } + ) for _, component := range components { - template := &hmc.ProviderTemplate{} - err := r.Get(ctx, client.ObjectKey{ - Name: component.Template, - }, template) - if err != nil { + l.V(1).Info("reconciling components", "component", component) + template := new(hmc.ProviderTemplate) + if err := r.Get(ctx, client.ObjectKey{Name: component.Template}, template); err != nil { errMsg := fmt.Sprintf("Failed to get ProviderTemplate %s: %s", component.Template, err) - updateComponentsStatus(detectedComponents, &detectedProviders, detectedContracts, component.helmReleaseName, component.Template, template.Status.Providers, template.Status.CAPIContracts, errMsg) + updateComponentsStatus(statusAccumulator, component, nil, errMsg) errs = errors.Join(errs, errors.New(errMsg)) + continue } + if !template.Status.Valid { errMsg := fmt.Sprintf("Template %s is not marked as valid", component.Template) - updateComponentsStatus(detectedComponents, &detectedProviders, detectedContracts, component.helmReleaseName, component.Template, template.Status.Providers, template.Status.CAPIContracts, errMsg) + updateComponentsStatus(statusAccumulator, component, nil, errMsg) errs = errors.Join(errs, errors.New(errMsg)) + continue } - _, _, err = helm.ReconcileHelmRelease(ctx, r.Client, component.helmReleaseName, r.SystemNamespace, helm.ReconcileHelmReleaseOpts{ + if _, _, err := helm.ReconcileHelmRelease(ctx, r.Client, component.helmReleaseName, r.SystemNamespace, helm.ReconcileHelmReleaseOpts{ Values: component.Config, ChartRef: template.Status.ChartRef, DependsOn: component.dependsOn, TargetNamespace: component.targetNamespace, CreateNamespace: component.createNamespace, - }) - if err != nil { - errMsg := fmt.Sprintf("error reconciling HelmRelease %s/%s: %s", r.SystemNamespace, component.Template, err) - updateComponentsStatus(detectedComponents, &detectedProviders, detectedContracts, component.helmReleaseName, component.Template, template.Status.Providers, template.Status.CAPIContracts, errMsg) + }); err != nil { + errMsg := fmt.Sprintf("Failed to reconcile HelmRelease %s/%s: %s", r.SystemNamespace, component.helmReleaseName, err) + updateComponentsStatus(statusAccumulator, component, nil, errMsg) errs = errors.Join(errs, errors.New(errMsg)) + continue } if component.Template != hmc.CoreHMCName { if err := r.checkProviderStatus(ctx, component.Template); err != nil { - updateComponentsStatus(detectedComponents, &detectedProviders, detectedContracts, component.helmReleaseName, component.Template, template.Status.Providers, template.Status.CAPIContracts, err.Error()) + updateComponentsStatus(statusAccumulator, component, nil, fmt.Sprintf("Failed to check provider status: %s", err)) errs = errors.Join(errs, err) continue } } - updateComponentsStatus(detectedComponents, &detectedProviders, detectedContracts, component.helmReleaseName, component.Template, template.Status.Providers, template.Status.CAPIContracts, "") + updateComponentsStatus(statusAccumulator, component, template, "") } + management.Status.AvailableProviders = statusAccumulator.providers + management.Status.CAPIContracts = statusAccumulator.compatibilityContracts + management.Status.Components = statusAccumulator.components management.Status.ObservedGeneration = management.Generation - management.Status.AvailableProviders = detectedProviders - management.Status.CAPIContracts = detectedContracts - management.Status.Components = detectedComponents management.Status.Release = management.Spec.Release + if err := r.Status().Update(ctx, management); err != nil { - errs = errors.Join(errs, fmt.Errorf("failed to update status for Management %s: %w", - management.Name, err)) + errs = errors.Join(errs, fmt.Errorf("failed to update status for Management %s: %w", management.Name, err)) } + if errs != nil { l.Error(errs, "Multiple errors during Management reconciliation") return ctrl.Result{}, errs } + return ctrl.Result{}, nil } +func (r *ManagementReconciler) cleanupRemovedComponents(ctx context.Context, management *hmc.Management) error { + var ( + errs error + l = ctrl.LoggerFrom(ctx) + ) + + managedHelmReleases := new(fluxv2.HelmReleaseList) + if err := r.Client.List(ctx, managedHelmReleases, + client.MatchingLabels{hmc.HMCManagedLabelKey: hmc.HMCManagedLabelValue}, + client.InNamespace(r.SystemNamespace), // all helmreleases are being installed only in the system namespace + ); err != nil { + return fmt.Errorf("failed to list %s: %w", fluxv2.GroupVersion.WithKind(fluxv2.HelmReleaseKind), err) + } + + for _, hr := range managedHelmReleases.Items { + componentName := hr.Name // providers(components) names map 1-1 to the helmreleases names + + if componentName == hmc.CoreCAPIName || + componentName == hmc.CoreHMCName || + slices.ContainsFunc(management.Spec.Providers, func(newComp hmc.Provider) bool { return componentName == newComp.Name }) { + continue + } + + l.Info("Found component to remove", "component_name", componentName) + + if err := r.Client.Delete(ctx, &hr); client.IgnoreNotFound(err) != nil { + errs = errors.Join(errs, fmt.Errorf("failed to delete %s: %w", client.ObjectKeyFromObject(&hr), err)) + continue + } + l.Info("Removed HelmRelease", "reference", client.ObjectKeyFromObject(&hr).String()) + } + + return errs +} + func (r *ManagementReconciler) ensureTemplateManagement(ctx context.Context, mgmt *hmc.Management) error { l := ctrl.LoggerFrom(ctx) if !r.CreateTemplateManagement { @@ -225,7 +254,7 @@ func (r *ManagementReconciler) ensureTemplateManagement(ctx context.Context, mgm } // checkProviderStatus checks the status of a provider associated with a given -// ProviderTemplate name. Since there's no way to determine resource Kind from +// ProviderTemplate name. Since there's no way to determine resource Kind from // the given template iterate over all possible provider types. func (r *ManagementReconciler) checkProviderStatus(ctx context.Context, providerTemplateName string) error { var errs error @@ -361,6 +390,16 @@ func applyHMCDefaults(config *apiextensionsv1.JSON) (*apiextensionsv1.JSON, erro return nil, err } } + + // Those are only needed for the initial installation + enforcedValues := map[string]any{ + "controller": map[string]any{ + "createManagement": false, + "createTemplateManagement": false, + "createRelease": false, + }, + } + chartutil.CoalesceTables(values, enforcedValues) raw, err := json.Marshal(values) if err != nil { @@ -369,10 +408,16 @@ func applyHMCDefaults(config *apiextensionsv1.JSON) (*apiextensionsv1.JSON, erro return &apiextensionsv1.JSON{Raw: raw}, nil } -func wrappedComponents(mgmt *hmc.Management, release *hmc.Release) ([]component, error) { +func getWrappedComponents(ctx context.Context, cl client.Client, mgmt *hmc.Management) ([]component, error) { if mgmt.Spec.Core == nil { return nil, nil } + + release := &hmc.Release{} + if err := cl.Get(ctx, client.ObjectKey{Name: mgmt.Spec.Release}, release); err != nil { + return nil, fmt.Errorf("failed to get Release %s: %w", mgmt.Spec.Release, err) + } + components := make([]component, 0, len(mgmt.Spec.Providers)+2) hmcComp := component{Component: mgmt.Spec.Core.HMC, helmReleaseName: hmc.CoreHMCName} if hmcComp.Template == "" { @@ -424,8 +469,7 @@ func (r *ManagementReconciler) enableAdditionalComponents(ctx context.Context, m config := make(map[string]any) if hmcComponent.Config != nil { - err := json.Unmarshal(hmcComponent.Config.Raw, &config) - if err != nil { + if err := json.Unmarshal(hmcComponent.Config.Raw, &config); err != nil { return fmt.Errorf("failed to unmarshal HMC config into map[string]any: %w", err) } } @@ -450,13 +494,15 @@ func (r *ManagementReconciler) enableAdditionalComponents(ctx context.Context, m capiOperatorValues = v } - err := certmanager.VerifyAPI(ctx, r.Config, r.SystemNamespace) - if err != nil { - return fmt.Errorf("failed to check in the cert-manager API is installed: %w", err) + if r.Config != nil { + if err := certmanager.VerifyAPI(ctx, r.Config, r.SystemNamespace); err != nil { + return fmt.Errorf("failed to check in the cert-manager API is installed: %w", err) + } + + l.Info("Cert manager is installed, enabling the HMC admission webhook") + admissionWebhookValues["enabled"] = true } - l.Info("Cert manager is installed, enabling the HMC admission webhook") - admissionWebhookValues["enabled"] = true config["admissionWebhook"] = admissionWebhookValues // Enable HMC capi operator only if it was not explicitly disabled in the config to @@ -475,35 +521,41 @@ func (r *ManagementReconciler) enableAdditionalComponents(ctx context.Context, m if err != nil { return fmt.Errorf("failed to marshal HMC config: %w", err) } - hmcComponent.Config = &apiextensionsv1.JSON{ - Raw: updatedConfig, - } + + hmcComponent.Config = &apiextensionsv1.JSON{Raw: updatedConfig} + return nil } +type mgmtStatusAccumulator struct { + components map[string]hmc.ComponentStatus + compatibilityContracts map[string]hmc.CompatibilityContracts + providers hmc.Providers +} + func updateComponentsStatus( - components map[string]hmc.ComponentStatus, - providers *hmc.Providers, - capiContracts map[string]hmc.CompatibilityContracts, - componentName string, - templateName string, - templateProviders hmc.Providers, - templateContracts hmc.CompatibilityContracts, + stAcc *mgmtStatusAccumulator, + comp component, + template *hmc.ProviderTemplate, err string, ) { - components[componentName] = hmc.ComponentStatus{ + if stAcc == nil { + return + } + + stAcc.components[comp.helmReleaseName] = hmc.ComponentStatus{ Error: err, Success: err == "", - Template: templateName, + Template: comp.Component.Template, } - if err == "" { - *providers = append(*providers, templateProviders...) - slices.Sort(*providers) - *providers = slices.Compact(*providers) + if err == "" && template != nil { + stAcc.providers = append(stAcc.providers, template.Status.Providers...) + slices.Sort(stAcc.providers) + stAcc.providers = slices.Compact(stAcc.providers) - for _, v := range templateProviders { - capiContracts[v] = templateContracts + for _, v := range template.Status.Providers { + stAcc.compatibilityContracts[v] = template.Status.CAPIContracts } } } diff --git a/internal/controller/management_controller_test.go b/internal/controller/management_controller_test.go index cad5c732a..f4eee8691 100644 --- a/internal/controller/management_controller_test.go +++ b/internal/controller/management_controller_test.go @@ -16,15 +16,22 @@ package controller import ( "context" + "fmt" + "time" + helmcontrollerv2 "github.com/fluxcd/helm-controller/api/v2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" hmcmirantiscomv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" + "github.com/Mirantis/hmc/internal/utils" ) var _ = Describe("Management Controller", func() { @@ -42,7 +49,7 @@ var _ = Describe("Management Controller", func() { BeforeEach(func() { By("creating the custom resource for the Kind Management") err := k8sClient.Get(ctx, typeNamespacedName, management) - if err != nil && errors.IsNotFound(err) { + if err != nil && apierrors.IsNotFound(err) { resource := &hmcmirantiscomv1alpha1.Management{ ObjectMeta: metav1.ObjectMeta{ Name: resourceName, @@ -61,7 +68,9 @@ var _ = Describe("Management Controller", func() { By("Cleanup the specific resource instance Management") Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) }) + It("should successfully reconcile the resource", func() { + // NOTE: this node just checks that the finalizer has been set By("Reconciling the created resource") controllerReconciler := &ManagementReconciler{ Client: k8sClient, @@ -73,5 +82,189 @@ var _ = Describe("Management Controller", func() { }) Expect(err).NotTo(HaveOccurred()) }) + + It("should successfully delete providers components on its removal", func() { + const ( + mgmtName = "test-management-name-mgmt-removal" + + providerTemplateName = "test-provider-template-name-mgmt-removal" + providerTemplateUID = types.UID("some-uid") + providerTemplateRequiredComponent = "test-provider-for-required-mgmt-removal" + + someComponentName = "test-component-name-mgmt-removal" + + helmChartName, helmChartNamespace = "helm-chart-test-name", utils.DefaultSystemNamespace + + helmReleaseName = someComponentName // WARN: helm release name should be equal to the component name + helmReleaseNamespace = utils.DefaultSystemNamespace + + timeout = time.Second * 10 + interval = time.Millisecond * 250 + ) + + // NOTE: other tests for some reason are manipulating with the NS globally and interfer with each other, + // so try to avoid depending on their implementation ignoring its removal + By("Creating the hmc-system namespace") + Expect(client.IgnoreAlreadyExists(k8sClient.Create(ctx, &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: utils.DefaultSystemNamespace, + }, + }))).To(Succeed()) + Eventually(k8sClient.Get).WithArguments(ctx, client.ObjectKey{Name: utils.DefaultSystemNamespace}, &corev1.Namespace{}). + WithTimeout(10 * time.Second).WithPolling(250 * time.Millisecond).Should(Succeed()) + + By("Creating the Release object") + release := &hmcmirantiscomv1alpha1.Release{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-release-name", + }, + Spec: hmcmirantiscomv1alpha1.ReleaseSpec{ + Version: "test-version", + HMC: hmcmirantiscomv1alpha1.CoreProviderTemplate{Template: "test-release-hmc"}, + CAPI: hmcmirantiscomv1alpha1.CoreProviderTemplate{Template: "test-release-capi"}, + }, + } + Expect(k8sClient.Create(ctx, release)).To(Succeed()) + Eventually(k8sClient.Get).WithArguments(ctx, client.ObjectKeyFromObject(release), release). + WithTimeout(10 * time.Second).WithPolling(250 * time.Millisecond).Should(Succeed()) + + By("Creating a ProviderTemplate object for other required components") + providerTemplateRequired := &hmcmirantiscomv1alpha1.ProviderTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Name: providerTemplateRequiredComponent, + }, + Spec: hmcmirantiscomv1alpha1.ProviderTemplateSpec{ + Helm: hmcmirantiscomv1alpha1.HelmSpec{ + ChartName: "required-chart", + ChartVersion: "required-version", + }, + }, + } + Expect(k8sClient.Create(ctx, providerTemplateRequired)).To(Succeed()) + providerTemplateRequired.Status = hmcmirantiscomv1alpha1.ProviderTemplateStatus{ + TemplateStatusCommon: hmcmirantiscomv1alpha1.TemplateStatusCommon{ + TemplateValidationStatus: hmcmirantiscomv1alpha1.TemplateValidationStatus{ + Valid: true, + }, + ChartRef: &helmcontrollerv2.CrossNamespaceSourceReference{ + Kind: sourcev1.HelmChartKind, + Name: "required-chart", + Namespace: helmChartNamespace, + }, + }, + } + Expect(k8sClient.Status().Update(ctx, providerTemplateRequired)).To(Succeed()) + + By("Creating a HelmRelease object for the removed component") + helmRelease := &helmcontrollerv2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: helmReleaseName, + Namespace: helmReleaseNamespace, + Labels: map[string]string{ + hmcmirantiscomv1alpha1.HMCManagedLabelKey: hmcmirantiscomv1alpha1.HMCManagedLabelValue, + }, + }, + Spec: helmcontrollerv2.HelmReleaseSpec{ + ChartRef: &helmcontrollerv2.CrossNamespaceSourceReference{ + Kind: sourcev1.HelmChartKind, + Name: helmChartName, + Namespace: helmChartNamespace, + }, + }, + } + Expect(k8sClient.Create(ctx, helmRelease)).To(Succeed()) + + By("Creating a Management object with removed component in the spec and containing it in the status") + mgmt := &hmcmirantiscomv1alpha1.Management{ + ObjectMeta: metav1.ObjectMeta{ + Name: mgmtName, + Finalizers: []string{hmcmirantiscomv1alpha1.ManagementFinalizer}, + }, + Spec: hmcmirantiscomv1alpha1.ManagementSpec{ + Release: release.Name, + Core: &hmcmirantiscomv1alpha1.Core{ + HMC: hmcmirantiscomv1alpha1.Component{ + Template: providerTemplateRequiredComponent, + }, + CAPI: hmcmirantiscomv1alpha1.Component{ + Template: providerTemplateRequiredComponent, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, mgmt)).To(Succeed()) + mgmt.Status = hmcmirantiscomv1alpha1.ManagementStatus{ + AvailableProviders: []string{someComponentName}, + Components: map[string]hmcmirantiscomv1alpha1.ComponentStatus{ + someComponentName: {Template: providerTemplateName}, + }, + } + Expect(k8sClient.Status().Update(ctx, mgmt)).To(Succeed()) + + By("Checking created objects have expected spec and status") + Eventually(func() error { + // Management + if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(mgmt), mgmt); err != nil { + return err + } + if l := len(mgmt.Status.AvailableProviders); l != 1 { + return fmt.Errorf("expected .status.availableProviders length to be exactly 1, got %d", l) + } + if l := len(mgmt.Status.Components); l != 1 { + return fmt.Errorf("expected .status.components length to be exactly 2, got %d", l) + } + if v := mgmt.Status.Components[someComponentName]; v.Template != providerTemplateName { + return fmt.Errorf("expected .status.components[%s] template be %s, got %s", someComponentName, providerTemplateName, v.Template) + } + + // HelmRelease + return k8sClient.Get(ctx, client.ObjectKey{Name: helmReleaseName, Namespace: helmReleaseNamespace}, &helmcontrollerv2.HelmRelease{}) + }).WithTimeout(timeout).WithPolling(interval).Should(Succeed()) + + By("Reconciling the Management object") + controllerReconciler := &ManagementReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + SystemNamespace: utils.DefaultSystemNamespace, + DynamicClient: dynamicClient, + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: client.ObjectKeyFromObject(mgmt), + }) + Expect(err).NotTo(HaveOccurred()) + + By("Checking the HelmRelease objects have been removed") + Eventually(func() bool { + return apierrors.IsNotFound(k8sClient.Get(ctx, client.ObjectKeyFromObject(helmRelease), &helmcontrollerv2.HelmRelease{})) + }).WithTimeout(timeout).WithPolling(interval).Should(BeTrue()) + + By("Checking the Management object does not have the removed component in its spec") + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(mgmt), mgmt)).To(Succeed()) + Expect(mgmt.Status.AvailableProviders).To(BeEmpty()) + Expect(mgmt.Status.Components).To(HaveLen(2)) // required: capi, hmc + Expect(mgmt.Status.Components).To(BeEquivalentTo(map[string]hmcmirantiscomv1alpha1.ComponentStatus{ + hmcmirantiscomv1alpha1.CoreHMCName: {Success: true, Template: providerTemplateRequiredComponent}, + hmcmirantiscomv1alpha1.CoreCAPIName: {Success: true, Template: providerTemplateRequiredComponent}, + })) + + By("Removing the leftover objects") + mgmt.Finalizers = nil + Expect(k8sClient.Update(ctx, mgmt)).To(Succeed()) + Expect(k8sClient.Delete(ctx, mgmt)).To(Succeed()) + Eventually(func() bool { + return apierrors.IsNotFound(k8sClient.Get(ctx, client.ObjectKeyFromObject(mgmt), &hmcmirantiscomv1alpha1.Management{})) + }).WithTimeout(timeout).WithPolling(interval).Should(BeTrue()) + + Expect(k8sClient.Delete(ctx, release)).To(Succeed()) + Eventually(func() bool { + return apierrors.IsNotFound(k8sClient.Get(ctx, client.ObjectKeyFromObject(release), &hmcmirantiscomv1alpha1.Release{})) + }).WithTimeout(timeout).WithPolling(interval).Should(BeTrue()) + + Expect(k8sClient.Delete(ctx, providerTemplateRequired)).To(Succeed()) + Eventually(func() bool { + return apierrors.IsNotFound(k8sClient.Get(ctx, client.ObjectKeyFromObject(providerTemplateRequired), &hmcmirantiscomv1alpha1.ProviderTemplate{})) + }).WithTimeout(timeout).WithPolling(interval).Should(BeTrue()) + }) }) }) diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 12e321f03..395afc88f 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -32,6 +32,7 @@ import ( . "github.com/onsi/gomega" sveltosv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" admissionv1 "k8s.io/api/admissionregistration/v1" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" utilyaml "sigs.k8s.io/cluster-api/util/yaml" @@ -56,12 +57,13 @@ const ( ) var ( - cfg *rest.Config - k8sClient client.Client - mgrClient client.Client - testEnv *envtest.Environment - ctx context.Context - cancel context.CancelFunc + cfg *rest.Config + k8sClient client.Client + dynamicClient *dynamic.DynamicClient + mgrClient client.Client + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc ) func TestControllers(t *testing.T) { @@ -122,6 +124,10 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) + dynamicClient, err = dynamic.NewForConfig(cfg) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + // start webhook server using Manager webhookInstallOptions := &testEnv.WebhookInstallOptions diff --git a/internal/utils/kube.go b/internal/utils/kube.go index dbda4ad2d..54ad37a15 100644 --- a/internal/utils/kube.go +++ b/internal/utils/kube.go @@ -20,7 +20,6 @@ import ( "fmt" "os" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" @@ -39,7 +38,7 @@ func EnsureDeleteAllOf(ctx context.Context, cl client.Client, gvk schema.GroupVe var errs error for _, item := range itemsList.Items { if item.DeletionTimestamp.IsZero() { - if err := cl.Delete(ctx, &item); err != nil && !apierrors.IsNotFound(err) { + if err := cl.Delete(ctx, &item); client.IgnoreNotFound(err) != nil { errs = errors.Join(errs, err) continue }