diff --git a/fleetshard/pkg/central/reconciler/tenant_cleanup.go b/fleetshard/pkg/central/reconciler/tenant_cleanup.go index dd7f98ffd..74bcc4775 100644 --- a/fleetshard/pkg/central/reconciler/tenant_cleanup.go +++ b/fleetshard/pkg/central/reconciler/tenant_cleanup.go @@ -8,7 +8,8 @@ import ( "github.com/stackrox/acs-fleet-manager/fleetshard/pkg/k8s" "github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/api/private" "helm.sh/helm/v3/pkg/chart" - "k8s.io/kubernetes/pkg/apis/core" + corev1 "k8s.io/api/core/v1" + ctrlClient "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -30,9 +31,10 @@ func NewTenantCleanup(k8sClient ctrlClient.Client, secureTenantNetwork bool) *Te // DeleteStaleTenantK8sResources deletes all namespaces on the cluster that are labeled // as tenant namespaces but are not in the given list of ManagedCentrals func (t *TenantCleanup) DeleteStaleTenantK8sResources(ctx context.Context, centralListFromFmAPI *private.ManagedCentralList) error { - namespaceList := core.NamespaceList{} - labels := map[string]string{k8s.ManagedByLabelKey: k8s.ManagedByFleetshardValue} - if err := t.k8sClient.List(ctx, &namespaceList, ctrlClient.MatchingLabels(labels)); err != nil { + namespaceList := corev1.NamespaceList{} + matchLabels := ctrlClient.MatchingLabels{k8s.ManagedByLabelKey: k8s.ManagedByFleetshardValue} + hasLabels := ctrlClient.HasLabels{TenantIDLabelKey, crNameLabelKey} + if err := t.k8sClient.List(ctx, &namespaceList, matchLabels, hasLabels); err != nil { return fmt.Errorf("Failed to list all tenant namespaces: %w", err) } @@ -46,13 +48,17 @@ func (t *TenantCleanup) DeleteStaleTenantK8sResources(ctx context.Context, centr } for _, central := range centralListFromFmAPI.Items { - // the namespaceName is equal to the ID of a central - delete(namespaceNameToCrName, central.Id) + delete(namespaceNameToCrName, central.Metadata.Namespace) } for namespace, crName := range namespaceNameToCrName { + glog.Infof("delete resources for stale tenant in namespace: %s", namespace) + if crName == "" { + glog.Infof("namespace %q was not propperly labeled with a tenant name, skipping deletion", namespace) + continue + } if _, err := t.DeleteK8sResources(ctx, namespace, crName); err != nil { - glog.Errorf("Failed to delete k8s resources for central: %s: %s", namespace, err.Error()) + glog.Errorf("failed to delete k8s resources for central: %s: %s", namespace, err.Error()) } } diff --git a/fleetshard/pkg/central/reconciler/tenant_cleanup_test.go b/fleetshard/pkg/central/reconciler/tenant_cleanup_test.go new file mode 100644 index 000000000..2d9cd88f9 --- /dev/null +++ b/fleetshard/pkg/central/reconciler/tenant_cleanup_test.go @@ -0,0 +1,138 @@ +package reconciler + +import ( + "context" + "testing" + + "github.com/stackrox/acs-fleet-manager/fleetshard/pkg/testutils" + "github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/api/private" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrlClient "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestDeleteStaleTenant(t *testing.T) { + keepNamespaces := []string{ + "keep-because-in-fm-list-1", + "keep-because-in-fm-list-2", + "keep-because-empty-tenant-name", + "keep-because-not-managed-by-fleetshard", + "keep-because-missing-tenant-label", + } + deleteNamespaces := []string{ + "delete-1", + "delete-2", + } + + existingObjs := []ctrlClient.Object{ + &corev1.Namespace{ + ObjectMeta: v1.ObjectMeta{ + Name: keepNamespaces[0], + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "rhacs-fleetshard", + "rhacs.redhat.com/tenant": keepNamespaces[0], + "app.kubernetes.io/instance": keepNamespaces[0], + }}, + }, + &corev1.Namespace{ + ObjectMeta: v1.ObjectMeta{ + Name: deleteNamespaces[0], + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "rhacs-fleetshard", + "rhacs.redhat.com/tenant": deleteNamespaces[0], + "app.kubernetes.io/instance": deleteNamespaces[0], + }}, + }, + &corev1.Namespace{ + ObjectMeta: v1.ObjectMeta{ + Name: keepNamespaces[1], + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "rhacs-fleetshard", + "rhacs.redhat.com/tenant": keepNamespaces[1], + "app.kubernetes.io/instance": keepNamespaces[1], + }}, + }, + &corev1.Namespace{ + ObjectMeta: v1.ObjectMeta{ + Name: keepNamespaces[2], + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "rhacs-fleetshard", + "rhacs.redhat.com/tenant": keepNamespaces[2], + "app.kubernetes.io/instance": "", + }, + }, + }, + &corev1.Namespace{ + ObjectMeta: v1.ObjectMeta{ + Name: deleteNamespaces[1], + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "rhacs-fleetshard", + "rhacs.redhat.com/tenant": deleteNamespaces[1], + "app.kubernetes.io/instance": deleteNamespaces[1], + }, + }, + }, + &corev1.Namespace{ + ObjectMeta: v1.ObjectMeta{ + Name: keepNamespaces[3], + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "something-else", + "rhacs.redhat.com/tenant": keepNamespaces[3], + "app.kubernetes.io/instance": keepNamespaces[3], + }, + }, + }, + &corev1.Namespace{ + ObjectMeta: v1.ObjectMeta{ + Name: keepNamespaces[4], + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "rhacs-fleetshard", + "app.kubernetes.io/instance": keepNamespaces[4], + }, + }, + }, + } + + listFromFM := &private.ManagedCentralList{ + Items: []private.ManagedCentral{ + { + Metadata: private.ManagedCentralAllOfMetadata{ + Namespace: keepNamespaces[0], + }, + }, + { + Metadata: private.ManagedCentralAllOfMetadata{ + Namespace: keepNamespaces[1], + }, + }, + }, + } + + fakeClient, _ := testutils.NewFakeClientWithTracker(t, existingObjs...) + tenantCleanup := NewTenantCleanup(fakeClient, true) + err := tenantCleanup.DeleteStaleTenantK8sResources(context.TODO(), listFromFM) + require.NoError(t, err, "unexpected error deleting stale tenants") + + namespaceList := corev1.NamespaceList{} + err = fakeClient.List(context.TODO(), &namespaceList) + require.NoError(t, err, "unexpected error listing remaining namespaces") + + remainingNames := make(map[string]bool, len(namespaceList.Items)) + for _, ns := range namespaceList.Items { + remainingNames[ns.Name] = true + } + + for _, keepNS := range keepNamespaces { + if !remainingNames[keepNS] { + t.Fatalf("expected namespace %q to still exist, but it was deleted", keepNS) + } + } + + for _, deleteNS := range deleteNamespaces { + if remainingNames[deleteNS] { + t.Fatalf("expected namespace %q to be deleted, but was not", deleteNS) + } + } + +}