diff --git a/controllers/kustomization_controller.go b/controllers/kustomization_controller.go index dc6f519..60d047b 100644 --- a/controllers/kustomization_controller.go +++ b/controllers/kustomization_controller.go @@ -922,13 +922,12 @@ func (r *KustomizationReconciler) finalize(ctx context.Context, kustomization ku objects, _ := ListObjectsInInventory(kustomization.Status.Inventory) impersonation := NewKustomizeImpersonation(kustomization, r.Client, r.StatusPoller, r.DefaultServiceAccount) - kubeClient, _, err := impersonation.GetClient(ctx) - if err != nil { - // when impersonation fails, log the stale objects and continue with the finalization - msg := fmt.Sprintf("unable to prune objects: \n%s", ssa.FmtUnstructuredList(objects)) - log.Error(fmt.Errorf("failed to build kube client: %w", err), msg) - r.event(ctx, kustomization, kustomization.Status.LastAppliedRevision, events.EventSeverityError, msg, nil) - } else { + if impersonation.CanFinalize(ctx) { + kubeClient, _, err := impersonation.GetClient(ctx) + if err != nil { + return ctrl.Result{}, err + } + resourceManager := ssa.NewResourceManager(kubeClient, nil, ssa.Owner{ Field: r.ControllerName, Group: kustomizev1.GroupVersion.Group, @@ -953,6 +952,11 @@ func (r *KustomizationReconciler) finalize(ctx context.Context, kustomization ku if changeSet != nil && len(changeSet.Entries) > 0 { r.event(ctx, kustomization, kustomization.Status.LastAppliedRevision, events.EventSeverityInfo, changeSet.String(), nil) } + } else { + // when the account to impersonate is gone, log the stale objects and continue with the finalization + msg := fmt.Sprintf("unable to prune objects: \n%s", ssa.FmtUnstructuredList(objects)) + log.Error(fmt.Errorf("skiping pruning, failed to find account to impersonate"), msg) + r.event(ctx, kustomization, kustomization.Status.LastAppliedRevision, events.EventSeverityError, msg, nil) } } diff --git a/controllers/kustomization_impersonation.go b/controllers/kustomization_impersonation.go index 8b64696..a5e75c1 100644 --- a/controllers/kustomization_impersonation.go +++ b/controllers/kustomization_impersonation.go @@ -19,7 +19,9 @@ package controllers import ( "context" "fmt" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -69,6 +71,33 @@ func (ki *KustomizeImpersonation) GetClient(ctx context.Context) (client.Client, } } +// CanFinalize asserts if the given Kustomization can be finalized using impersonation. +func (ki *KustomizeImpersonation) CanFinalize(ctx context.Context) bool { + name := ki.defaultServiceAccount + if sa := ki.kustomization.Spec.ServiceAccountName; sa != "" { + name = sa + } + if name == "" { + return true + } + + sa := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + Kind: "ServiceAccount", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ki.kustomization.Namespace, + }, + } + if err := ki.Client.Get(ctx, client.ObjectKeyFromObject(sa), sa); err != nil { + return false + } + + return true +} + func (ki *KustomizeImpersonation) setImpersonationConfig(restConfig *rest.Config) { name := ki.defaultServiceAccount if sa := ki.kustomization.Spec.ServiceAccountName; sa != "" { diff --git a/controllers/kustomization_impersonation_test.go b/controllers/kustomization_impersonation_test.go index 02abf4a..50341a9 100644 --- a/controllers/kustomization_impersonation_test.go +++ b/controllers/kustomization_impersonation_test.go @@ -29,6 +29,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -101,6 +102,7 @@ data: Kind: sourcev1.GitRepositoryKind, }, TargetNamespace: id, + Prune: true, }, } @@ -187,4 +189,22 @@ data: g.Expect(readyCondition.Reason).To(Equal(meta.ReconciliationSucceededReason)) }) + + t.Run("can finalize impersonating service account", func(t *testing.T) { + saK := &kustomizev1.Kustomization{} + err = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), saK) + g.Expect(err).NotTo(HaveOccurred()) + + err = k8sClient.Delete(context.Background(), saK) + g.Expect(err).NotTo(HaveOccurred()) + + g.Eventually(func() bool { + err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK) + return apierrors.IsNotFound(err) + }, timeout, time.Second).Should(BeTrue()) + + resultConfig := &corev1.ConfigMap{} + err := k8sClient.Get(context.Background(), types.NamespacedName{Name: id, Namespace: id}, resultConfig) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }) }