Skip to content

Commit 8f4238a

Browse files
authored
Merge pull request #614 from manfredlift/mliiv-patch-1
fix: objects flagged by CurrentUIDFilter should be removed from the inventory
2 parents 11a1550 + fa95a60 commit 8f4238a

File tree

4 files changed

+133
-2
lines changed

4 files changed

+133
-2
lines changed

pkg/apply/prune/prune.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ func (p *Pruner) Prune(
153153
}
154154
}
155155

156+
// Remove the object from inventory if it was determined that the object should not be pruned,
157+
// because it had recently been applied. This probably means that the object is in the inventory
158+
// more than one time with a different group (e.g. kind Ingress and apiGroups networking.k8s.io & extensions)
159+
// due to being cohabitated: https://github.com/kubernetes/kubernetes/blob/v1.25.0/pkg/kubeapiserver/default_storage_factory_builder.go#L124-L131
160+
var deleteAfterApplyErr *filter.ApplyPreventedDeletionError
161+
if errors.As(filterErr, &deleteAfterApplyErr) {
162+
if !opts.DryRunStrategy.ClientOrServerDryRun() {
163+
// Register for removal from the inventory.
164+
taskContext.AddAbandonedObject(id)
165+
}
166+
}
167+
156168
taskContext.SendEvent(eventFactory.CreateSkippedEvent(obj, filterErr))
157169
taskContext.InventoryManager().AddSkippedDelete(id)
158170
break

pkg/apply/prune/prune_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ func TestPrune(t *testing.T) {
325325
},
326326
},
327327
},
328-
"UID match means prune skipped": {
328+
"UID match means prune skipped and object abandoned": {
329329
clusterObjs: []*unstructured.Unstructured{pod},
330330
pruneObjs: []*unstructured.Unstructured{pod},
331331
pruneFilters: []filter.ValidationFilter{
@@ -351,8 +351,11 @@ func TestPrune(t *testing.T) {
351351
expectedSkipped: object.ObjMetadataSet{
352352
object.UnstructuredToObjMetadata(pod),
353353
},
354+
expectedAbandoned: object.ObjMetadataSet{
355+
object.UnstructuredToObjMetadata(pod),
356+
},
354357
},
355-
"UID match for only one object one pruned, one skipped": {
358+
"UID match for only one object one pruned, one skipped and abandoned": {
356359
clusterObjs: []*unstructured.Unstructured{pod, pdb},
357360
pruneObjs: []*unstructured.Unstructured{pod, pdb},
358361
pruneFilters: []filter.ValidationFilter{
@@ -386,6 +389,9 @@ func TestPrune(t *testing.T) {
386389
expectedSkipped: object.ObjMetadataSet{
387390
object.UnstructuredToObjMetadata(pod),
388391
},
392+
expectedAbandoned: object.ObjMetadataSet{
393+
object.UnstructuredToObjMetadata(pod),
394+
},
389395
},
390396
"Prevent delete annotation equals prune skipped": {
391397
clusterObjs: []*unstructured.Unstructured{

test/e2e/current_uid_filter_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2021 The Kubernetes Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package e2e
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
. "github.com/onsi/ginkgo/v2"
11+
. "github.com/onsi/gomega"
12+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
13+
"sigs.k8s.io/cli-utils/pkg/apply"
14+
"sigs.k8s.io/cli-utils/test/e2e/e2eutil"
15+
"sigs.k8s.io/cli-utils/test/e2e/invconfig"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
17+
)
18+
19+
const v1EventTemplate = `
20+
apiVersion: v1
21+
involvedObject:
22+
apiVersion: v1
23+
kind: Pod
24+
name: pod
25+
namespace: {{.Namespace}}
26+
kind: Event
27+
message: Back-off restarting failed container
28+
metadata:
29+
name: test
30+
namespace: {{.Namespace}}
31+
reason: BackOff
32+
type: Warning
33+
`
34+
35+
const v1EventsEventTemplate = `
36+
apiVersion: events.k8s.io/v1
37+
eventTime: null
38+
kind: Event
39+
metadata:
40+
name: test
41+
namespace: {{.Namespace}}
42+
note: Back-off restarting failed container
43+
reason: BackOff
44+
regarding:
45+
apiVersion: v1
46+
kind: Pod
47+
name: pod
48+
namespace: {{.Namespace}}
49+
type: Warning
50+
`
51+
52+
// Note this tests the scenario of "cohabitating" k8s objects (an object available via multiple apiGroups), but having the same UID.
53+
// As of k8s 1.25 an example of such "cohabitating" kinds is Event which is available via both "v1" and "events.k8s.io/v1".
54+
// See the full list of cohabitating resources on the storage level here:
55+
// - https://github.com/kubernetes/kubernetes/blob/v1.25.0/pkg/kubeapiserver/default_storage_factory_builder.go#L124-L131
56+
// We test that when the user upgrades their manifest from one cohabitated apiGroup to the other, then:
57+
// - it should not result in object being pruned
58+
// - object pruning should be skipped due to CurrentUIDFilter (even though a diff is found)
59+
// - inventory should not double-track the object i.e. we should hold reference only to the object with the groupKind that was most recently applied
60+
func currentUIDFilterTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) {
61+
applier := invConfig.ApplierFactoryFunc()
62+
inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName)
63+
inventoryInfo := invconfig.CreateInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID)
64+
65+
templateFields := struct{ Namespace string }{Namespace: namespaceName}
66+
v1Event := e2eutil.TemplateToUnstructured(v1EventTemplate, templateFields)
67+
v1EventsEvent := e2eutil.TemplateToUnstructured(v1EventsEventTemplate, templateFields)
68+
69+
By("Apply resource with deprecated groupKind")
70+
resources := []*unstructured.Unstructured{
71+
v1Event,
72+
}
73+
err := e2eutil.Run(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{}))
74+
Expect(err).ToNot(HaveOccurred())
75+
76+
By("Verify resource available in both apiGroups")
77+
objDeprecated := e2eutil.AssertUnstructuredExists(ctx, c, v1Event)
78+
objNew := e2eutil.AssertUnstructuredExists(ctx, c, v1EventsEvent)
79+
80+
By("Verify UID matches for cohabitating resources")
81+
uid := objDeprecated.GetUID()
82+
Expect(uid).ToNot(BeEmpty())
83+
Expect(objDeprecated.GetUID()).To(Equal(objNew.GetUID()))
84+
85+
By("Verify only 1 item in inventory")
86+
invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, 1, 1)
87+
88+
By("Apply resource with new groupKind")
89+
resources = []*unstructured.Unstructured{
90+
v1EventsEvent,
91+
}
92+
err = e2eutil.Run(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{}))
93+
Expect(err).ToNot(HaveOccurred())
94+
95+
By("Verify resource still available in both apiGroups")
96+
objDeprecated = e2eutil.AssertUnstructuredExists(ctx, c, v1Event)
97+
objNew = e2eutil.AssertUnstructuredExists(ctx, c, v1EventsEvent)
98+
99+
By("Verify UID matches for cohabitating resources")
100+
Expect(objDeprecated.GetUID()).To(Equal(objNew.GetUID()))
101+
102+
By("Verify UID matches the UID from previous apply")
103+
Expect(objDeprecated.GetUID()).To(Equal(uid))
104+
105+
By("Verify still only 1 item in inventory")
106+
// Expecting statusCount=2:
107+
// one object applied and one prune skipped
108+
invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, 1, 2)
109+
}

test/e2e/e2e_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ var _ = Describe("E2E", func() {
183183
namespaceFilterTest(ctx, c, invConfig, inventoryName, namespace.GetName())
184184
})
185185

186+
It("CurrentUIDFilter", func() {
187+
currentUIDFilterTest(ctx, c, invConfig, inventoryName, namespace.GetName())
188+
})
189+
186190
It("PruneRetrievalError", func() {
187191
pruneRetrieveErrorTest(ctx, c, invConfig, inventoryName, namespace.GetName())
188192
})

0 commit comments

Comments
 (0)