diff --git a/pkg/apply/destroyer.go b/pkg/apply/destroyer.go index 5793a07..6bd4f65 100644 --- a/pkg/apply/destroyer.go +++ b/pkg/apply/destroyer.go @@ -70,6 +70,11 @@ func (d *Destroyer) Run() <-chan event.Event { go func() { defer close(ch) infos, _ := d.ApplyOptions.GetObjects() + // Clear the data/inventory section of the grouping object configmap, + // so the prune will calculate the prune set as all the objects, + // deleting everything. We can ignore the error, since the Prune + // will catch the same problems. + _ = prune.ClearGroupingObj(infos) err := d.PruneOptions.Prune(infos, ch) if err != nil { // If we see an error here we just report it on the channel and then diff --git a/pkg/apply/prune/grouping.go b/pkg/apply/prune/grouping.go index e240d56..5e47d10 100644 --- a/pkg/apply/prune/grouping.go +++ b/pkg/apply/prune/grouping.go @@ -224,6 +224,39 @@ func RetrieveInventoryFromGroupingObj(infos []*resource.Info) ([]*ObjMetadata, e return inventory, nil } +// ClearGroupingObj finds the grouping object in the list of objects, +// and sets an empty inventory. Returns error if the grouping object +// is not Unstructured, the grouping object does not exist, or if +// we can't set the empty inventory on the grouping object. If successful, +// returns nil. +func ClearGroupingObj(infos []*resource.Info) error { + // Initially, find the grouping object ConfigMap (in Unstructured format). + var groupingObj *unstructured.Unstructured + for _, info := range infos { + obj := info.Object + if IsGroupingObject(obj) { + var ok bool + groupingObj, ok = obj.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("grouping object is not an Unstructured: %#v", groupingObj) + } + break + } + } + if groupingObj == nil { + return fmt.Errorf("grouping object not found") + } + // Clears the inventory map of the ConfigMap "data" section. + emptyMap := map[string]string{} + err := unstructured.SetNestedStringMap(groupingObj.UnstructuredContent(), + emptyMap, "data") + if err != nil { + return err + } + + return nil +} + // calcInventoryHash returns an unsigned int32 representing the hash // of the inventory strings. If there is an error writing bytes to // the hash, then the error is returned; nil is returned otherwise. diff --git a/pkg/apply/prune/grouping_test.go b/pkg/apply/prune/grouping_test.go index 1af749c..819cd3f 100644 --- a/pkg/apply/prune/grouping_test.go +++ b/pkg/apply/prune/grouping_test.go @@ -581,6 +581,65 @@ func TestAddSuffixToName(t *testing.T) { } } +func TestClearGroupingObject(t *testing.T) { + tests := map[string]struct { + infos []*resource.Info + isError bool + }{ + "Empty infos should error": { + infos: []*resource.Info{}, + isError: true, + }, + "Non-Unstructured grouping object should error": { + infos: []*resource.Info{nonUnstructuredGroupingInfo}, + isError: true, + }, + "Info with nil Object should error": { + infos: []*resource.Info{nilInfo}, + isError: true, + }, + "Single grouping object should work": { + infos: []*resource.Info{copyGroupingInfo()}, + isError: false, + }, + "Single non-grouping object should error": { + infos: []*resource.Info{pod1Info}, + isError: true, + }, + "Multiple non-grouping objects should error": { + infos: []*resource.Info{pod1Info, pod2Info}, + isError: true, + }, + "Grouping object with single inventory object should work": { + infos: []*resource.Info{copyGroupingInfo(), pod1Info}, + isError: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + err := ClearGroupingObj(tc.infos) + if tc.isError { + if err == nil { + t.Errorf("Should have produced an error, but returned none.") + } + } + if !tc.isError { + if err != nil { + t.Fatalf("Received unexpected error: %#v", err) + } + objMetadata, err := RetrieveInventoryFromGroupingObj(tc.infos) + if err != nil { + t.Fatalf("Received unexpected error: %#v", err) + } + if len(objMetadata) > 0 { + t.Errorf("Grouping object inventory not cleared: %#v\n", objMetadata) + } + } + }) + } +} + func TestPrependGroupingObject(t *testing.T) { tests := []struct { infos []*resource.Info