diff --git a/pkg/apply/prune/prune.go b/pkg/apply/prune/prune.go index 72687056..5db251ef 100644 --- a/pkg/apply/prune/prune.go +++ b/pkg/apply/prune/prune.go @@ -13,6 +13,7 @@ package prune import ( "sort" + "strings" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -82,8 +83,15 @@ type Options struct { // objects. Returns an error if there was a problem. func (po *PruneOptions) Prune(localInv *unstructured.Unstructured, localObjs []*unstructured.Unstructured, currentUIDs sets.String, eventChannel chan<- event.Event, o Options) error { - invNamespace := localInv.GetNamespace() + invNamespace := strings.TrimSpace(strings.ToLower(localInv.GetNamespace())) klog.V(4).Infof("prune local inventory object: %s/%s", invNamespace, localInv.GetName()) + // Create the set of namespaces for currently (locally) applied objects, including + // the namespace the inventory object lives in (if it's not cluster-scoped). When + // pruning, check this set of namespaces to ensure these namespaces are not deleted. + localNamespaces := mergeObjNamespaces(localObjs) + if invNamespace != "" { + localNamespaces.Insert(invNamespace) + } clusterObjs, err := po.InvClient.GetClusterObjs(localInv) if err != nil { return err @@ -126,11 +134,12 @@ func (po *PruneOptions) Prune(localInv *unstructured.Unstructured, localObjs []* eventChannel <- createPruneEvent(obj, event.PruneSkipped) continue } - // If regular pruning (not destroying), skip deleting inventory namespace. + // If regular pruning (not destroying), skip deleting namespace containing + // currently applied objects. if !po.Destroy { if clusterObj.GroupKind == object.CoreV1Namespace.GroupKind() && - clusterObj.Name == invNamespace { - klog.V(7).Infof("skip pruning inventory namespace: %s", obj) + localNamespaces.Has(strings.ToLower(clusterObj.Name)) { + klog.V(7).Infof("skip pruning namespace: %s", clusterObj.Name) eventChannel <- createPruneEvent(obj, event.PruneSkipped) continue } @@ -148,6 +157,20 @@ func (po *PruneOptions) Prune(localInv *unstructured.Unstructured, localObjs []* return po.InvClient.Replace(localInv, localIds) } +// mergeObjNamespaces returns a set of strings of all the namespaces +// for non cluster-scoped objects. These namespaces are forced to +// lower-case. +func mergeObjNamespaces(objs []*unstructured.Unstructured) sets.String { + namespaces := sets.NewString() + for _, obj := range objs { + namespace := strings.TrimSpace(strings.ToLower(obj.GetNamespace())) + if namespace != "" { + namespaces.Insert(namespace) + } + } + return namespaces +} + // preventDeleteAnnotation returns true if the "onRemove:keep" // annotation exists within the annotation map; false otherwise. func preventDeleteAnnotation(annotations map[string]string) bool { diff --git a/pkg/apply/prune/prune_test.go b/pkg/apply/prune/prune_test.go index 7863e176..fa13d7b3 100644 --- a/pkg/apply/prune/prune_test.go +++ b/pkg/apply/prune/prune_test.go @@ -20,7 +20,7 @@ import ( var testNamespace = "test-inventory-namespace" var inventoryObjName = "test-inventory-obj" -var namespaceName = "namespace" +var podName = "pod-1" var pdbName = "pdb" var roleName = "role" @@ -45,7 +45,18 @@ var namespace = &unstructured.Unstructured{ "apiVersion": "v1", "kind": "Namespace", "metadata": map[string]interface{}{ - "name": namespaceName, + "name": testNamespace, + "uid": "uid1", + }, + }, +} + +var pod = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": podName, "namespace": testNamespace, "uid": "uid1", }, @@ -150,6 +161,12 @@ func TestPrune(t *testing.T) { prunedObjs: []*unstructured.Unstructured{}, isError: false, }, + "Namespace not pruned if objects are still in it": { + pastObjs: []*unstructured.Unstructured{namespace, pdb, pod}, + currentObjs: []*unstructured.Unstructured{pod}, + prunedObjs: []*unstructured.Unstructured{pdb}, + isError: false, + }, } for name, tc := range tests { for i := range common.Strategies {