Skip to content

Commit e371bd5

Browse files
authored
Merge pull request #207 from mortent/OrderResourceForPrune
Prune/destroy resources in reverse order from apply
2 parents 48230a7 + 724553f commit e371bd5

File tree

5 files changed

+114
-86
lines changed

5 files changed

+114
-86
lines changed

pkg/apply/applier.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"sigs.k8s.io/cli-utils/pkg/inventory"
2727
"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
2828
"sigs.k8s.io/cli-utils/pkg/object"
29+
"sigs.k8s.io/cli-utils/pkg/ordering"
2930
"sigs.k8s.io/controller-runtime/pkg/client"
3031
)
3132

@@ -182,7 +183,7 @@ func (a *Applier) prepareObjects(infos []*resource.Info) (*ResourceObjects, erro
182183
return nil, err
183184
}
184185

185-
sort.Sort(ResourceInfos(resources))
186+
sort.Sort(ordering.SortableInfos(resources))
186187

187188
if !validateNamespace(resources) {
188189
return nil, fmt.Errorf("objects have differing namespaces")

pkg/apply/prune/prune.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ package prune
1313

1414
import (
1515
"fmt"
16+
"sort"
1617

1718
apierrors "k8s.io/apimachinery/pkg/api/errors"
1819
"k8s.io/apimachinery/pkg/api/meta"
@@ -26,6 +27,7 @@ import (
2627
"sigs.k8s.io/cli-utils/pkg/apply/event"
2728
"sigs.k8s.io/cli-utils/pkg/common"
2829
"sigs.k8s.io/cli-utils/pkg/inventory"
30+
"sigs.k8s.io/cli-utils/pkg/ordering"
2931
)
3032

3133
// PruneOptions encapsulates the necessary information to
@@ -103,6 +105,11 @@ func (po *PruneOptions) Prune(currentObjects []*resource.Info, eventChannel chan
103105
}
104106
klog.V(4).Infof("prune %d currently applied objects", len(po.currentUids))
105107
klog.V(4).Infof("prune %d previously applied objects", len(pastObjs))
108+
109+
// Sort the resources in reverse order using the same rules as is
110+
// used for apply.
111+
sort.Sort(sort.Reverse(ordering.SortableMetas(pastObjs)))
112+
106113
// Iterate through set of all previously applied objects.
107114
for _, past := range pastObjs {
108115
mapping, err := po.mapper.RESTMapping(past.GroupKind)

pkg/apply/prune/prune_test.go

Lines changed: 62 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ import (
2020

2121
var testNamespace = "test-inventory-namespace"
2222
var inventoryObjName = "test-inventory-obj"
23-
var pod1Name = "pod-1"
24-
var pod2Name = "pod-2"
25-
var pod3Name = "pod-3"
23+
var namespaceName = "namespace"
24+
var pdbName = "pdb"
25+
var roleName = "role"
2626

2727
var testInventoryLabel = "test-app-label"
2828

@@ -40,58 +40,58 @@ var inventoryObj = unstructured.Unstructured{
4040
},
4141
}
4242

43-
var pod1 = unstructured.Unstructured{
43+
var namespace = unstructured.Unstructured{
4444
Object: map[string]interface{}{
4545
"apiVersion": "v1",
46-
"kind": "Pod",
46+
"kind": "Namespace",
4747
"metadata": map[string]interface{}{
48-
"name": pod1Name,
48+
"name": namespaceName,
4949
"namespace": testNamespace,
5050
"uid": "uid1",
5151
},
5252
},
5353
}
5454

55-
var pod1Info = &resource.Info{
55+
var namespaceInfo = &resource.Info{
5656
Namespace: testNamespace,
57-
Name: pod1Name,
58-
Object: &pod1,
57+
Name: namespaceName,
58+
Object: &namespace,
5959
}
6060

61-
var pod2 = unstructured.Unstructured{
61+
var pdb = unstructured.Unstructured{
6262
Object: map[string]interface{}{
63-
"apiVersion": "v1",
64-
"kind": "Pod",
63+
"apiVersion": "policy/v1beta1",
64+
"kind": "PodDisruptionBudget",
6565
"metadata": map[string]interface{}{
66-
"name": pod2Name,
66+
"name": pdbName,
6767
"namespace": testNamespace,
6868
"uid": "uid2",
6969
},
7070
},
7171
}
7272

73-
var pod2Info = &resource.Info{
73+
var pdbInfo = &resource.Info{
7474
Namespace: testNamespace,
75-
Name: pod2Name,
76-
Object: &pod2,
75+
Name: pdbName,
76+
Object: &pdb,
7777
}
7878

79-
var pod3 = unstructured.Unstructured{
79+
var role = unstructured.Unstructured{
8080
Object: map[string]interface{}{
81-
"apiVersion": "v1",
82-
"kind": "Pod",
81+
"apiVersion": "rbac.authorization.k8s.io/v1",
82+
"kind": "Role",
8383
"metadata": map[string]interface{}{
84-
"name": pod3Name,
84+
"name": roleName,
8585
"namespace": testNamespace,
8686
"uid": "uid3",
8787
},
8888
},
8989
}
9090

91-
var pod3Info = &resource.Info{
91+
var roleInfo = &resource.Info{
9292
Namespace: testNamespace,
93-
Name: pod3Name,
94-
Object: &pod3,
93+
Name: roleName,
94+
Object: &role,
9595
}
9696

9797
// Returns a inventory object with the inventory set from
@@ -151,32 +151,32 @@ func TestPrune(t *testing.T) {
151151
isError: false,
152152
},
153153
"Past and current objects are the same; no pruned objects": {
154-
pastInfos: []*resource.Info{pod1Info, pod2Info},
155-
currentInfos: []*resource.Info{pod2Info, pod1Info},
154+
pastInfos: []*resource.Info{namespaceInfo, pdbInfo},
155+
currentInfos: []*resource.Info{pdbInfo, namespaceInfo},
156156
prunedInfos: []*resource.Info{},
157157
isError: false,
158158
},
159159
"No past objects; no pruned objects": {
160160
pastInfos: []*resource.Info{},
161-
currentInfos: []*resource.Info{pod2Info, pod1Info},
161+
currentInfos: []*resource.Info{pdbInfo, namespaceInfo},
162162
prunedInfos: []*resource.Info{},
163163
isError: false,
164164
},
165-
"No current objects; all previous objects pruned": {
166-
pastInfos: []*resource.Info{pod1Info, pod2Info, pod3Info},
165+
"No current objects; all previous objects pruned in correct order": {
166+
pastInfos: []*resource.Info{namespaceInfo, pdbInfo, roleInfo},
167167
currentInfos: []*resource.Info{},
168-
prunedInfos: []*resource.Info{pod1Info, pod2Info, pod3Info},
168+
prunedInfos: []*resource.Info{pdbInfo, roleInfo, namespaceInfo},
169169
isError: false,
170170
},
171171
"Omitted object is pruned": {
172-
pastInfos: []*resource.Info{pod1Info, pod2Info},
173-
currentInfos: []*resource.Info{pod2Info, pod3Info},
174-
prunedInfos: []*resource.Info{pod1Info},
172+
pastInfos: []*resource.Info{namespaceInfo, pdbInfo},
173+
currentInfos: []*resource.Info{pdbInfo, roleInfo},
174+
prunedInfos: []*resource.Info{namespaceInfo},
175175
isError: false,
176176
},
177177
"Prevent delete lifecycle annotation stops pruning": {
178-
pastInfos: []*resource.Info{preventDeleteInfo, pod2Info},
179-
currentInfos: []*resource.Info{pod2Info, pod3Info},
178+
pastInfos: []*resource.Info{preventDeleteInfo, pdbInfo},
179+
currentInfos: []*resource.Info{pdbInfo, roleInfo},
180180
prunedInfos: []*resource.Info{},
181181
isError: false,
182182
},
@@ -191,29 +191,42 @@ func TestPrune(t *testing.T) {
191191
// Set up the currently applied objects.
192192
currentInventoryInfo := createInventoryInfo("current-group", tc.currentInfos...)
193193
currentInfos := append(tc.currentInfos, currentInventoryInfo)
194-
// The event channel can not block; make sure its bigger than all
195-
// the events that can be put on it.
196-
eventChannel := make(chan event.Event, len(tc.pastInfos)+1) // Add one for inventory object
197-
defer close(eventChannel)
198194
// Set up the fake dynamic client to recognize all objects, and the RESTMapper.
199195
po.client = fake.NewSimpleDynamicClient(scheme.Scheme,
200-
pod1Info.Object, pod2Info.Object, pod3Info.Object)
196+
namespaceInfo.Object, pdbInfo.Object, roleInfo.Object)
201197
po.mapper = testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme,
202198
scheme.Scheme.PrioritizedVersionsAllGroups()...)
203-
// Run the prune and validate.
204-
err := po.Prune(currentInfos, eventChannel, Options{
205-
DryRun: true,
206-
})
199+
// The event channel can not block; make sure its bigger than all
200+
// the events that can be put on it.
201+
eventChannel := make(chan event.Event, len(tc.pastInfos)+1) // Add one for inventory object
202+
err := func() error {
203+
defer close(eventChannel)
204+
// Run the prune and validate.
205+
return po.Prune(currentInfos, eventChannel, Options{
206+
DryRun: true,
207+
})
208+
}()
209+
207210
if !tc.isError {
208211
if err != nil {
209212
t.Fatalf("Unexpected error during Prune(): %#v", err)
210213
}
211-
// Validate the prune events on the event channel.
212-
expectedPruneEvents := len(tc.prunedInfos) + 1 // One extra for pruning inventory object
213-
actualPruneEvents := len(eventChannel)
214-
if expectedPruneEvents != actualPruneEvents {
215-
t.Errorf("Expected (%d) prune events, got (%d)",
216-
expectedPruneEvents, actualPruneEvents)
214+
215+
var actualPruneEvents []event.Event
216+
for e := range eventChannel {
217+
actualPruneEvents = append(actualPruneEvents, e)
218+
}
219+
if want, got := len(tc.prunedInfos)+1, len(actualPruneEvents); want != got {
220+
t.Errorf("Expected (%d) prune events, got (%d)", want, got)
221+
}
222+
223+
for i, info := range tc.prunedInfos {
224+
e := actualPruneEvents[i]
225+
expKind := info.Object.GetObjectKind().GroupVersionKind().Kind
226+
actKind := e.PruneEvent.Object.GetObjectKind().GroupVersionKind().Kind
227+
if expKind != actKind {
228+
t.Errorf("Expected kind %s, got %s", expKind, actKind)
229+
}
217230
}
218231
} else if err == nil {
219232
t.Fatalf("Expected error during Prune() but received none")

pkg/apply/resource_infos.go renamed to pkg/ordering/sort.go

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,43 @@
11
// Copyright 2020 The Kubernetes Authors.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
package apply
4+
package ordering
55

66
import (
77
"sort"
88

99
"k8s.io/apimachinery/pkg/runtime/schema"
1010
"k8s.io/cli-runtime/pkg/resource"
11+
"sigs.k8s.io/cli-utils/pkg/object"
1112
)
1213

13-
type ResourceInfos []*resource.Info
14+
type SortableInfos []*resource.Info
1415

15-
var _ sort.Interface = ResourceInfos{}
16+
var _ sort.Interface = SortableInfos{}
1617

17-
func (a ResourceInfos) Len() int { return len(a) }
18-
func (a ResourceInfos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
19-
func (a ResourceInfos) Less(i, j int) bool {
20-
x := a[i].Object.GetObjectKind().GroupVersionKind()
21-
o := a[j].Object.GetObjectKind().GroupVersionKind()
22-
if !Equals(x, o) {
23-
return IsLessThan(x, o)
18+
func (a SortableInfos) Len() int { return len(a) }
19+
func (a SortableInfos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
20+
func (a SortableInfos) Less(i, j int) bool {
21+
return less(object.InfoToObjMeta(a[i]), object.InfoToObjMeta(a[j]))
22+
}
23+
24+
type SortableMetas []object.ObjMetadata
25+
26+
var _ sort.Interface = SortableMetas{}
27+
28+
func (a SortableMetas) Len() int { return len(a) }
29+
func (a SortableMetas) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
30+
func (a SortableMetas) Less(i, j int) bool {
31+
return less(a[i], a[j])
32+
}
33+
34+
func less(i, j object.ObjMetadata) bool {
35+
if !Equals(i.GroupKind, j.GroupKind) {
36+
return IsLessThan(i.GroupKind, j.GroupKind)
2437
}
2538
// In case of tie, compare the namespace and name combination so that the output
2639
// order is consistent irrespective of input order
27-
return a[i].Namespace+a[i].Name < a[j].Namespace+a[j].Name
40+
return i.Namespace+i.Name < j.Namespace+j.Name
2841
}
2942

3043
// An attempt to order things to help k8s, e.g.
@@ -70,13 +83,11 @@ func getIndexByKind(kind string) int {
7083
return m[kind]
7184
}
7285

73-
// Equals returns true if the GVK's have equal fields.
74-
func Equals(x schema.GroupVersionKind, o schema.GroupVersionKind) bool {
75-
return x.Group == o.Group && x.Version == o.Version && x.Kind == o.Kind
86+
func Equals(x schema.GroupKind, o schema.GroupKind) bool {
87+
return x.Group == o.Group && x.Kind == o.Kind
7688
}
7789

78-
// IsLessThan compares two GVK's as per orderFirst and orderLast, returns boolean result.
79-
func IsLessThan(x schema.GroupVersionKind, o schema.GroupVersionKind) bool {
90+
func IsLessThan(x schema.GroupKind, o schema.GroupKind) bool {
8091
indexI := getIndexByKind(x.Kind)
8192
indexJ := getIndexByKind(o.Kind)
8293
if indexI != indexJ {

pkg/apply/resource_infos_test.go renamed to pkg/ordering/sort_test.go

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2020 The Kubernetes Authors.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
package apply
4+
package ordering
55

66
import (
77
"sort"
@@ -78,7 +78,7 @@ func TestResourceOrdering(t *testing.T) {
7878
}
7979

8080
infos := []*resource.Info{&deploymentInfo, &configMapInfo, &namespaceInfo, &deploymentInfo2}
81-
sort.Sort(ResourceInfos(infos))
81+
sort.Sort(SortableInfos(infos))
8282

8383
assert.Equal(t, infos[0].Name, "testspace")
8484
assert.Equal(t, infos[1].Name, "the-map")
@@ -92,33 +92,29 @@ func TestResourceOrdering(t *testing.T) {
9292
}
9393

9494
func TestGvkLessThan(t *testing.T) {
95-
gvk1 := schema.GroupVersionKind{
96-
Group: "",
97-
Version: "v1",
98-
Kind: "Deployment",
95+
gk1 := schema.GroupKind{
96+
Group: "",
97+
Kind: "Deployment",
9998
}
10099

101-
gvk2 := schema.GroupVersionKind{
102-
Group: "",
103-
Version: "v1",
104-
Kind: "Namespace",
100+
gk2 := schema.GroupKind{
101+
Group: "",
102+
Kind: "Namespace",
105103
}
106104

107-
assert.Equal(t, IsLessThan(gvk1, gvk2), false)
105+
assert.Equal(t, IsLessThan(gk1, gk2), false)
108106
}
109107

110108
func TestGvkEquals(t *testing.T) {
111-
gvk1 := schema.GroupVersionKind{
112-
Group: "",
113-
Version: "v1",
114-
Kind: "Deployment",
109+
gk1 := schema.GroupKind{
110+
Group: "",
111+
Kind: "Deployment",
115112
}
116113

117-
gvk2 := schema.GroupVersionKind{
118-
Group: "",
119-
Version: "v1",
120-
Kind: "Deployment",
114+
gk2 := schema.GroupKind{
115+
Group: "",
116+
Kind: "Deployment",
121117
}
122118

123-
assert.Equal(t, Equals(gvk1, gvk2), true)
119+
assert.Equal(t, Equals(gk1, gk2), true)
124120
}

0 commit comments

Comments
 (0)