Skip to content

Commit 410fb4a

Browse files
committed
Add test for multiple patch delete in strategic merge patches
This test verifies that multiple $patch: delete directives in a single strategic merge patch work correctly. The test currently fails due to a panic in kustomize (upstream issue kubernetes-sigs/kustomize#5552), but will pass once the fix is included in a future kustomize release. Ref: #1306 Signed-off-by: cappyzawa <[email protected]>
1 parent 9f784c5 commit 410fb4a

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
/*
2+
Copyright 2025 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controller
18+
19+
import (
20+
"context"
21+
"testing"
22+
"time"
23+
24+
"github.com/fluxcd/pkg/apis/kustomize"
25+
"github.com/fluxcd/pkg/apis/meta"
26+
"github.com/fluxcd/pkg/testserver"
27+
sourcev1 "github.com/fluxcd/source-controller/api/v1"
28+
. "github.com/onsi/gomega"
29+
corev1 "k8s.io/api/core/v1"
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/apimachinery/pkg/types"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
33+
34+
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
35+
)
36+
37+
// TestKustomizationReconciler_MultiplePatchDelete tests the handling of multiple
38+
// $patch: delete directives in strategic merge patches.
39+
// This test ensures that the controller properly handles scenarios where multiple
40+
// resources are deleted using a single patch specification.
41+
func TestKustomizationReconciler_MultiplePatchDelete(t *testing.T) {
42+
g := NewWithT(t)
43+
id := "multi-patch-delete-" + randStringRunes(5)
44+
revision := "v1.0.0"
45+
46+
err := createNamespace(id)
47+
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
48+
49+
err = createKubeConfigSecret(id)
50+
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
51+
52+
// Create test files with multiple ConfigMaps
53+
manifests := func(name string, data string) []testserver.File {
54+
return []testserver.File{
55+
{
56+
Name: "namespace.yaml",
57+
Body: `---
58+
apiVersion: v1
59+
kind: Namespace
60+
metadata:
61+
name: ` + name,
62+
},
63+
{
64+
Name: "configmaps.yaml",
65+
Body: `---
66+
apiVersion: v1
67+
kind: ConfigMap
68+
metadata:
69+
name: cm1
70+
namespace: ` + name + `
71+
data:
72+
key: ` + data + `1
73+
---
74+
apiVersion: v1
75+
kind: ConfigMap
76+
metadata:
77+
name: cm2
78+
namespace: ` + name + `
79+
data:
80+
key: ` + data + `2
81+
---
82+
apiVersion: v1
83+
kind: ConfigMap
84+
metadata:
85+
name: cm3
86+
namespace: ` + name + `
87+
data:
88+
key: ` + data + `3
89+
`,
90+
},
91+
}
92+
}
93+
94+
artifact, err := testServer.ArtifactFromFiles(manifests(id, randStringRunes(5)))
95+
g.Expect(err).NotTo(HaveOccurred())
96+
97+
repositoryName := types.NamespacedName{
98+
Name: randStringRunes(5),
99+
Namespace: id,
100+
}
101+
102+
err = applyGitRepository(repositoryName, artifact, revision)
103+
g.Expect(err).NotTo(HaveOccurred())
104+
105+
kustomizationKey := types.NamespacedName{
106+
Name: "patch-delete-" + randStringRunes(5),
107+
Namespace: id,
108+
}
109+
110+
t.Run("multiple patch delete in single patch should work", func(t *testing.T) {
111+
// This test verifies that multiple $patch: delete directives in a single patch work correctly
112+
// Ref: https://github.com/fluxcd/kustomize-controller/issues/1306
113+
kustomization := &kustomizev1.Kustomization{
114+
ObjectMeta: metav1.ObjectMeta{
115+
Name: kustomizationKey.Name,
116+
Namespace: kustomizationKey.Namespace,
117+
},
118+
Spec: kustomizev1.KustomizationSpec{
119+
Interval: metav1.Duration{Duration: reconciliationInterval},
120+
Path: "./",
121+
KubeConfig: &meta.KubeConfigReference{
122+
SecretRef: meta.SecretKeyReference{
123+
Name: "kubeconfig",
124+
},
125+
},
126+
SourceRef: kustomizev1.CrossNamespaceSourceReference{
127+
Name: repositoryName.Name,
128+
Namespace: repositoryName.Namespace,
129+
Kind: sourcev1.GitRepositoryKind,
130+
},
131+
Prune: true,
132+
Patches: []kustomize.Patch{
133+
{
134+
// Multiple $patch: delete in a single patch
135+
Patch: `$patch: delete
136+
apiVersion: v1
137+
kind: ConfigMap
138+
metadata:
139+
name: cm1
140+
namespace: ` + id + `
141+
---
142+
$patch: delete
143+
apiVersion: v1
144+
kind: ConfigMap
145+
metadata:
146+
name: cm2
147+
namespace: ` + id + ``,
148+
},
149+
},
150+
},
151+
}
152+
153+
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
154+
155+
// Wait for reconciliation and check that it succeeds without panic
156+
g.Eventually(func() bool {
157+
var obj kustomizev1.Kustomization
158+
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), &obj)
159+
return obj.Status.LastAppliedRevision == revision
160+
}, timeout, time.Second).Should(BeTrue())
161+
162+
// Verify that only cm3 ConfigMap exists (cm1 and cm2 should be deleted)
163+
var cm corev1.ConfigMap
164+
err := k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm1", Namespace: id}, &cm)
165+
g.Expect(err).To(HaveOccurred(), "cm1 should have been deleted")
166+
167+
err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm2", Namespace: id}, &cm)
168+
g.Expect(err).To(HaveOccurred(), "cm2 should have been deleted")
169+
170+
err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm3", Namespace: id}, &cm)
171+
g.Expect(err).NotTo(HaveOccurred(), "cm3 should still exist")
172+
173+
// Cleanup
174+
g.Expect(k8sClient.Delete(context.Background(), kustomization)).To(Succeed())
175+
})
176+
177+
t.Run("multiple patch delete in separate patches should work", func(t *testing.T) {
178+
// This test shows the workaround: using separate patches
179+
kustomizationWorkaround := &kustomizev1.Kustomization{
180+
ObjectMeta: metav1.ObjectMeta{
181+
Name: kustomizationKey.Name + "-workaround",
182+
Namespace: kustomizationKey.Namespace,
183+
},
184+
Spec: kustomizev1.KustomizationSpec{
185+
Interval: metav1.Duration{Duration: reconciliationInterval},
186+
Path: "./",
187+
KubeConfig: &meta.KubeConfigReference{
188+
SecretRef: meta.SecretKeyReference{
189+
Name: "kubeconfig",
190+
},
191+
},
192+
SourceRef: kustomizev1.CrossNamespaceSourceReference{
193+
Name: repositoryName.Name,
194+
Namespace: repositoryName.Namespace,
195+
Kind: sourcev1.GitRepositoryKind,
196+
},
197+
Prune: true,
198+
Patches: []kustomize.Patch{
199+
{
200+
Patch: `$patch: delete
201+
apiVersion: v1
202+
kind: ConfigMap
203+
metadata:
204+
name: cm1
205+
namespace: ` + id + ``,
206+
},
207+
{
208+
Patch: `$patch: delete
209+
apiVersion: v1
210+
kind: ConfigMap
211+
metadata:
212+
name: cm2
213+
namespace: ` + id + ``,
214+
},
215+
},
216+
},
217+
}
218+
219+
g.Expect(k8sClient.Create(context.Background(), kustomizationWorkaround)).To(Succeed())
220+
221+
// Wait for successful reconciliation
222+
g.Eventually(func() bool {
223+
var obj kustomizev1.Kustomization
224+
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomizationWorkaround), &obj)
225+
return obj.Status.LastAppliedRevision == revision
226+
}, timeout, time.Second).Should(BeTrue())
227+
228+
// Verify that only cm3 ConfigMap exists
229+
var cm corev1.ConfigMap
230+
err := k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm1", Namespace: id}, &cm)
231+
g.Expect(err).To(HaveOccurred(), "cm1 should have been deleted")
232+
233+
err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm2", Namespace: id}, &cm)
234+
g.Expect(err).To(HaveOccurred(), "cm2 should have been deleted")
235+
236+
err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm3", Namespace: id}, &cm)
237+
g.Expect(err).NotTo(HaveOccurred(), "cm3 should still exist")
238+
239+
// Cleanup
240+
g.Expect(k8sClient.Delete(context.Background(), kustomizationWorkaround)).To(Succeed())
241+
})
242+
}

0 commit comments

Comments
 (0)