Ensure object are finalized under impersonation

If the service account used for impersonation has been deleted, skip pruning, log the error and continue with finalization to allow tenants removals from clusters.

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
Stefan Prodan 2022-01-31 13:18:58 +02:00
parent cd6fff0220
commit 65aaa1d69a
No known key found for this signature in database
GPG Key ID: 3299AEB0E4085BAF
3 changed files with 60 additions and 7 deletions

View File

@ -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)
if impersonation.CanFinalize(ctx) {
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 {
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)
}
}

View File

@ -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 != "" {

View File

@ -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())
})
}