Skip to content

Commit 735996d

Browse files
Add metadata cleanup to no-op namespace cleanup handler, check for protected namespaces in all namespace cleanup handler (#2894)
1 parent 750c922 commit 735996d

File tree

1 file changed

+91
-35
lines changed

1 file changed

+91
-35
lines changed

pkg/cli/cleanup_namespaces.go

Lines changed: 91 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ const (
1919
vClusterMetadataPrefix = "vcluster.loft.sh/"
2020
)
2121

22-
// NamespaceCleanupHandler defines the function signature for namespace cleanup operations.
2322
type NamespaceCleanupHandler func(
2423
ctx context.Context,
2524
mainPhysicalNamespace string,
@@ -29,7 +28,6 @@ type NamespaceCleanupHandler func(
2928
logger log.Logger,
3029
) error
3130

32-
// GetNamespaceCleanupHandler returns a NamespaceCleanupHandler function based on the provided policy.
3331
func GetNamespaceCleanupHandler(policy config.HostDeletionPolicy) (NamespaceCleanupHandler, error) {
3432
switch policy {
3533
case config.HostDeletionPolicyAll:
@@ -43,14 +41,33 @@ func GetNamespaceCleanupHandler(policy config.HostDeletionPolicy) (NamespaceClea
4341
}
4442
}
4543

46-
// cleanupNoneNamespaces is a no-op handler for the 'none' policy.
44+
// cleanupNoneNamespaces is a no-op handler for the 'none' policy. It only removes namespace metadata added by vCluster.
4745
func cleanupNoneNamespaces(
48-
_ context.Context,
49-
_ string, _ string,
46+
ctx context.Context,
47+
mainPhysicalNamespace string,
48+
vClusterName string,
5049
_ config.SyncToHostNamespaces,
51-
_ *kubernetes.Clientset,
52-
_ log.Logger,
50+
k8sClient *kubernetes.Clientset,
51+
logger log.Logger,
5352
) error {
53+
logger.Infof("Starting metadata cleanup for vCluster '%s' namespaces.", vClusterName)
54+
managedNamespaces, err := getManagedNamespaces(ctx, k8sClient, mainPhysicalNamespace, vClusterName, logger)
55+
if err != nil {
56+
return err
57+
}
58+
59+
var errs []error
60+
for _, ns := range managedNamespaces {
61+
if err := cleanupNamespaceMetadata(ctx, k8sClient, &ns); err != nil {
62+
errs = append(errs, err)
63+
}
64+
}
65+
66+
if len(errs) > 0 {
67+
return fmt.Errorf("metadata cleanup for vCluster '%s' namespaces finished with errors: %w", vClusterName, errors.Join(errs...))
68+
}
69+
70+
logger.Infof("Metadata cleanup for vCluster '%s' namespaces finished.", vClusterName)
5471
return nil
5572
}
5673

@@ -66,38 +83,24 @@ func cleanupSyncedNamespaces(
6683
) error {
6784
logger.Infof("Starting cleanup of vCluster '%s' namespaces.", vClusterName)
6885

69-
if mainPhysicalNamespace == "" || vClusterName == "" {
70-
return fmt.Errorf("main physical namespace or vCluster name is empty")
71-
}
72-
73-
labelSelector := translate.MarkerLabel + "=" + translate.SafeConcatName(mainPhysicalNamespace, "x", vClusterName)
74-
nsList, err := k8sClient.CoreV1().Namespaces().List(ctx, metav1.ListOptions{LabelSelector: labelSelector})
86+
managedNamespaces, err := getManagedNamespaces(ctx, k8sClient, mainPhysicalNamespace, vClusterName, logger)
7587
if err != nil {
76-
return fmt.Errorf("list namespaces: %w", err)
77-
}
78-
79-
if nsList == nil || len(nsList.Items) == 0 {
80-
logger.Infof("No additional managed namespaces found with label selector '%s'.", labelSelector)
81-
return nil
88+
return err
8289
}
8390

8491
var errs []error
85-
for _, ns := range nsList.Items {
86-
// Check if namespace was imported, if yes we skip deletion for 'synced' policy.
87-
if ns.Annotations != nil && ns.Annotations[translate.ImportedMarkerAnnotation] == "true" {
92+
for _, ns := range managedNamespaces {
93+
if isImportedNamespace(&ns) {
8894
logger.Infof("Namespace %s was imported, cleaning up import.", ns.Name)
8995
if err := cleanupNamespaceMetadata(ctx, k8sClient, &ns); err != nil {
9096
errs = append(errs, err)
9197
}
9298
continue
9399
}
94100

95-
logger.Infof("Attempting to delete virtual cluster namespace %s.", ns.Name)
96-
err := k8sClient.CoreV1().Namespaces().Delete(ctx, ns.Name, metav1.DeleteOptions{})
101+
err := deleteAndLogNamespace(ctx, k8sClient, ns.Name, logger)
97102
if err != nil {
98-
errs = append(errs, fmt.Errorf("namespace %s: %w", ns.Name, err))
99-
} else {
100-
logger.Donef("Successfully deleted virtual cluster namespace %s.", ns.Name)
103+
errs = append(errs, err)
101104
}
102105
}
103106

@@ -120,16 +123,16 @@ func cleanupAllNamespaces(
120123
logger log.Logger,
121124
) error {
122125
logger.Infof("Starting cleanup of vCluster '%s' namespaces.", vClusterName)
123-
mappingsConfig := nsSyncConfig.Mappings
124126

127+
mappingsConfig := nsSyncConfig.Mappings
125128
if len(mappingsConfig.ByName) == 0 {
126129
logger.Infof("No namespace mappings defined.")
127130
logger.Infof("Cleanup of vCluster '%s' namespaces finished.", vClusterName)
128131
return nil
129132
}
130133
logger.Debugf("Processing %d namespace mappings for potential deletion.", len(mappingsConfig.ByName))
131134

132-
hostNamespaces, err := k8sClient.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
135+
hostNamespaces, err := getNamespaces(ctx, k8sClient, metav1.ListOptions{})
133136
if err != nil {
134137
return fmt.Errorf("list all host namespaces for mapping check: %w", err)
135138
}
@@ -154,14 +157,17 @@ func cleanupAllNamespaces(
154157
}
155158

156159
if currentRuleMatches {
157-
logger.Infof("Attempting to delete virtual cluster namespace %s.", hostNs.Name)
158-
err := k8sClient.CoreV1().Namespaces().Delete(ctx, hostNs.Name, metav1.DeleteOptions{})
159-
if err != nil {
160-
errs = append(errs, fmt.Errorf("delete virtual cluster namespace %s: %w", hostNs.Name, err))
160+
var err error
161+
if isProtectedNamespace(hostNs.Name) {
162+
logger.Infof("Namespace %s is protected, cleaning up its metadata.", hostNs.Name)
163+
err = cleanupNamespaceMetadata(ctx, k8sClient, &hostNs)
161164
} else {
162-
logger.Donef("Successfully deleted virtual cluster namespace %s.", hostNs.Name)
165+
err = deleteAndLogNamespace(ctx, k8sClient, hostNs.Name, logger)
166+
}
167+
168+
if err != nil {
169+
errs = append(errs, err)
163170
}
164-
// This namespace has been handled. Skip other mappings and move to the next one.
165171
break
166172
}
167173
}
@@ -175,6 +181,45 @@ func cleanupAllNamespaces(
175181
return nil
176182
}
177183

184+
func getNamespaces(ctx context.Context, k8sClient *kubernetes.Clientset, listOptions metav1.ListOptions) (*corev1.NamespaceList, error) {
185+
nsList, err := k8sClient.CoreV1().Namespaces().List(ctx, listOptions)
186+
if err != nil {
187+
return nil, fmt.Errorf("listing namespaces with options %+v: %w", listOptions, err)
188+
}
189+
return nsList, nil
190+
}
191+
192+
func getManagedNamespaces(ctx context.Context, k8sClient *kubernetes.Clientset, mainPhysicalNamespace, vClusterName string, logger log.Logger) ([]corev1.Namespace, error) {
193+
if mainPhysicalNamespace == "" || vClusterName == "" {
194+
return nil, fmt.Errorf("main physical namespace or vCluster name is empty")
195+
}
196+
197+
labelSelector := translate.MarkerLabel + "=" + translate.SafeConcatName(mainPhysicalNamespace, "x", vClusterName)
198+
listOptions := metav1.ListOptions{LabelSelector: labelSelector}
199+
200+
nsList, err := getNamespaces(ctx, k8sClient, listOptions)
201+
if err != nil {
202+
return nil, err
203+
}
204+
205+
if nsList == nil || len(nsList.Items) == 0 {
206+
logger.Infof("No additional managed namespaces found with label selector '%s'.", labelSelector)
207+
return nil, nil
208+
}
209+
210+
return nsList.Items, nil
211+
}
212+
213+
func deleteAndLogNamespace(ctx context.Context, k8sClient *kubernetes.Clientset, nsName string, logger log.Logger) error {
214+
logger.Infof("Attempting to delete virtual cluster namespace %s.", nsName)
215+
err := k8sClient.CoreV1().Namespaces().Delete(ctx, nsName, metav1.DeleteOptions{})
216+
if err != nil {
217+
return fmt.Errorf("namespace %s: %w", nsName, err)
218+
}
219+
logger.Donef("Successfully deleted virtual cluster namespace %s.", nsName)
220+
return nil
221+
}
222+
178223
func cleanupNamespaceMetadata(
179224
ctx context.Context,
180225
k8sClient *kubernetes.Clientset,
@@ -199,3 +244,14 @@ func cleanupNamespaceMetadata(
199244

200245
return nil
201246
}
247+
248+
// isProtectedNamespace checks if a namespace should be protected from deletion.
249+
// Protected namespaces include 'default' and any namespace prefixed with 'kube-'.
250+
func isProtectedNamespace(name string) bool {
251+
return name == "default" || strings.HasPrefix(name, "kube-")
252+
}
253+
254+
// isImportedNamespace checks if a namespace was imported
255+
func isImportedNamespace(ns *corev1.Namespace) bool {
256+
return ns.Annotations != nil && ns.Annotations[translate.ImportedMarkerAnnotation] == "true"
257+
}

0 commit comments

Comments
 (0)