Skip to content

Commit 8c1d87b

Browse files
authored
Merge pull request #1470 from cappyzawa/update-kustomize-v5.7.0
Update kustomize to v5.7.0 and add regression test for multiple patch delete
2 parents 9f784c5 + 8479377 commit 8c1d87b

File tree

3 files changed

+263
-10
lines changed

3 files changed

+263
-10
lines changed

go.mod

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ require (
4747
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e
4848
sigs.k8s.io/controller-runtime v0.21.0
4949
sigs.k8s.io/kustomize/api v0.19.0
50-
sigs.k8s.io/yaml v1.4.0
50+
sigs.k8s.io/yaml v1.5.0
5151
)
5252

53-
// Pin kustomize to v5.6.0
53+
// Pin kustomize to v5.7.0
5454
replace (
55-
sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.19.0
56-
sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.19.0
55+
sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.20.0
56+
sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.20.0
5757
)
5858

5959
// Fix CVE-2022-28948
@@ -106,6 +106,7 @@ require (
106106
github.com/beorn7/perks v1.0.1 // indirect
107107
github.com/blang/semver v3.5.1+incompatible // indirect
108108
github.com/blang/semver/v4 v4.0.0 // indirect
109+
github.com/carapace-sh/carapace-shlex v1.0.1 // indirect
109110
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
110111
github.com/cespare/xxhash/v2 v2.3.0 // indirect
111112
github.com/chai2010/gettext-go v1.0.3 // indirect
@@ -226,6 +227,8 @@ require (
226227
go.opentelemetry.io/otel/trace v1.35.0 // indirect
227228
go.uber.org/multierr v1.11.0 // indirect
228229
go.uber.org/zap v1.27.0 // indirect
230+
go.yaml.in/yaml/v2 v2.4.2 // indirect
231+
go.yaml.in/yaml/v3 v3.0.3 // indirect
229232
golang.org/x/crypto v0.38.0 // indirect
230233
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
231234
golang.org/x/sync v0.14.0 // indirect
@@ -252,7 +255,7 @@ require (
252255
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
253256
k8s.io/kubectl v0.33.0 // indirect
254257
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
255-
sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
258+
sigs.k8s.io/kustomize/kyaml v0.20.0 // indirect
256259
sigs.k8s.io/randfill v1.0.0 // indirect
257260
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
258261
)

go.sum

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn
119119
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
120120
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
121121
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
122+
github.com/carapace-sh/carapace-shlex v1.0.1 h1:ww0JCgWpOVuqWG7k3724pJ18Lq8gh5pHQs9j3ojUs1c=
123+
github.com/carapace-sh/carapace-shlex v1.0.1/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
122124
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
123125
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
124126
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -492,6 +494,10 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
492494
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
493495
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
494496
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
497+
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
498+
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
499+
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
500+
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
495501
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
496502
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
497503
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -595,14 +601,15 @@ sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytI
595601
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
596602
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
597603
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
598-
sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
599-
sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
600-
sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA=
601-
sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY=
604+
sigs.k8s.io/kustomize/api v0.20.0 h1:xPLqcobHI0bThyRUteO+nCV8G4d1Rlo5HafO57VRcas=
605+
sigs.k8s.io/kustomize/api v0.20.0/go.mod h1:F6CfaV27oevRCMJgehLqyX81dlUnRX/Fc13Uo7+OSo4=
606+
sigs.k8s.io/kustomize/kyaml v0.20.0 h1:tT8KMKi4R3hCJ1+9HDdek2VoXpkerP92ZfF6fDgGw14=
607+
sigs.k8s.io/kustomize/kyaml v0.20.0/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po=
602608
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
603609
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
604610
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
605611
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
606612
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
607-
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
608613
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
614+
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
615+
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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+
apierrors "k8s.io/apimachinery/pkg/api/errors"
31+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32+
"k8s.io/apimachinery/pkg/types"
33+
"sigs.k8s.io/controller-runtime/pkg/client"
34+
35+
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
36+
)
37+
38+
// TestKustomizationReconciler_MultiplePatchDelete tests the handling of multiple
39+
// $patch: delete directives in strategic merge patches.
40+
// This test ensures that the controller properly handles scenarios where multiple
41+
// resources are deleted using a single patch specification.
42+
func TestKustomizationReconciler_MultiplePatchDelete(t *testing.T) {
43+
g := NewWithT(t)
44+
id := "multi-patch-delete-" + randStringRunes(5)
45+
revision := "v1.0.0"
46+
47+
err := createNamespace(id)
48+
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
49+
50+
err = createKubeConfigSecret(id)
51+
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
52+
53+
// Create test files with multiple ConfigMaps
54+
manifests := func(name string, data string) []testserver.File {
55+
return []testserver.File{
56+
{
57+
Name: "configmaps.yaml",
58+
Body: `---
59+
apiVersion: v1
60+
kind: ConfigMap
61+
metadata:
62+
name: cm1
63+
namespace: ` + name + `
64+
data:
65+
key: ` + data + `1
66+
---
67+
apiVersion: v1
68+
kind: ConfigMap
69+
metadata:
70+
name: cm2
71+
namespace: ` + name + `
72+
data:
73+
key: ` + data + `2
74+
---
75+
apiVersion: v1
76+
kind: ConfigMap
77+
metadata:
78+
name: cm3
79+
namespace: ` + name + `
80+
data:
81+
key: ` + data + `3
82+
`,
83+
},
84+
}
85+
}
86+
87+
artifact, err := testServer.ArtifactFromFiles(manifests(id, randStringRunes(5)))
88+
g.Expect(err).NotTo(HaveOccurred())
89+
90+
repositoryName := types.NamespacedName{
91+
Name: randStringRunes(5),
92+
Namespace: id,
93+
}
94+
95+
err = applyGitRepository(repositoryName, artifact, revision)
96+
g.Expect(err).NotTo(HaveOccurred())
97+
98+
kustomizationKey := types.NamespacedName{
99+
Name: "patch-delete-" + randStringRunes(5),
100+
Namespace: id,
101+
}
102+
103+
t.Run("multiple patch delete in single patch should work", func(t *testing.T) {
104+
// This test verifies that multiple $patch: delete directives in a single patch work correctly
105+
// Ref: https://github.com/fluxcd/kustomize-controller/issues/1306
106+
kustomization := &kustomizev1.Kustomization{
107+
ObjectMeta: metav1.ObjectMeta{
108+
Name: kustomizationKey.Name,
109+
Namespace: kustomizationKey.Namespace,
110+
},
111+
Spec: kustomizev1.KustomizationSpec{
112+
Interval: metav1.Duration{Duration: reconciliationInterval},
113+
Path: "./",
114+
KubeConfig: &meta.KubeConfigReference{
115+
SecretRef: meta.SecretKeyReference{
116+
Name: "kubeconfig",
117+
},
118+
},
119+
SourceRef: kustomizev1.CrossNamespaceSourceReference{
120+
Name: repositoryName.Name,
121+
Namespace: repositoryName.Namespace,
122+
Kind: sourcev1.GitRepositoryKind,
123+
},
124+
Prune: true,
125+
Patches: []kustomize.Patch{
126+
{
127+
// Multiple $patch: delete in a single patch
128+
Patch: `$patch: delete
129+
apiVersion: v1
130+
kind: ConfigMap
131+
metadata:
132+
name: cm1
133+
namespace: ` + id + `
134+
---
135+
$patch: delete
136+
apiVersion: v1
137+
kind: ConfigMap
138+
metadata:
139+
name: cm2
140+
namespace: ` + id + ``,
141+
},
142+
},
143+
},
144+
}
145+
146+
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
147+
148+
// Wait for reconciliation and check that it succeeds without panic
149+
g.Eventually(func() bool {
150+
var obj kustomizev1.Kustomization
151+
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), &obj)
152+
return obj.Status.LastAppliedRevision == revision
153+
}, timeout, time.Second).Should(BeTrue())
154+
155+
// Verify that only cm3 ConfigMap exists (cm1 and cm2 should be deleted)
156+
var cm corev1.ConfigMap
157+
err := k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm1", Namespace: id}, &cm)
158+
g.Expect(err).To(HaveOccurred(), "cm1 should have been deleted")
159+
160+
err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm2", Namespace: id}, &cm)
161+
g.Expect(err).To(HaveOccurred(), "cm2 should have been deleted")
162+
163+
err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm3", Namespace: id}, &cm)
164+
g.Expect(err).NotTo(HaveOccurred(), "cm3 should still exist")
165+
166+
// Cleanup
167+
g.Expect(k8sClient.Delete(context.Background(), kustomization)).To(Succeed())
168+
g.Eventually(func() bool {
169+
err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), kustomization)
170+
return apierrors.IsNotFound(err)
171+
}, timeout, time.Second).Should(BeTrue())
172+
})
173+
174+
t.Run("multiple patch delete in separate patches should work", func(t *testing.T) {
175+
// This test verifies that separate patches (which was previously a workaround) still work correctly
176+
kustomizationSeparate := &kustomizev1.Kustomization{
177+
ObjectMeta: metav1.ObjectMeta{
178+
Name: kustomizationKey.Name + "-separate",
179+
Namespace: kustomizationKey.Namespace,
180+
},
181+
Spec: kustomizev1.KustomizationSpec{
182+
Interval: metav1.Duration{Duration: reconciliationInterval},
183+
Path: "./",
184+
KubeConfig: &meta.KubeConfigReference{
185+
SecretRef: meta.SecretKeyReference{
186+
Name: "kubeconfig",
187+
},
188+
},
189+
SourceRef: kustomizev1.CrossNamespaceSourceReference{
190+
Name: repositoryName.Name,
191+
Namespace: repositoryName.Namespace,
192+
Kind: sourcev1.GitRepositoryKind,
193+
},
194+
Prune: true,
195+
Patches: []kustomize.Patch{
196+
{
197+
Patch: `$patch: delete
198+
apiVersion: v1
199+
kind: ConfigMap
200+
metadata:
201+
name: cm1
202+
namespace: ` + id + ``,
203+
},
204+
{
205+
Patch: `$patch: delete
206+
apiVersion: v1
207+
kind: ConfigMap
208+
metadata:
209+
name: cm2
210+
namespace: ` + id + ``,
211+
},
212+
},
213+
},
214+
}
215+
216+
g.Expect(k8sClient.Create(context.Background(), kustomizationSeparate)).To(Succeed())
217+
218+
// Wait for successful reconciliation
219+
g.Eventually(func() bool {
220+
var obj kustomizev1.Kustomization
221+
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomizationSeparate), &obj)
222+
return obj.Status.LastAppliedRevision == revision
223+
}, timeout, time.Second).Should(BeTrue())
224+
225+
// Verify that only cm3 ConfigMap exists
226+
var cm corev1.ConfigMap
227+
err := k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm1", Namespace: id}, &cm)
228+
g.Expect(err).To(HaveOccurred(), "cm1 should have been deleted")
229+
230+
err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm2", Namespace: id}, &cm)
231+
g.Expect(err).To(HaveOccurred(), "cm2 should have been deleted")
232+
233+
err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm3", Namespace: id}, &cm)
234+
g.Expect(err).NotTo(HaveOccurred(), "cm3 should still exist")
235+
236+
// Cleanup
237+
g.Expect(k8sClient.Delete(context.Background(), kustomizationSeparate)).To(Succeed())
238+
g.Eventually(func() bool {
239+
err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomizationSeparate), kustomizationSeparate)
240+
return apierrors.IsNotFound(err)
241+
}, timeout, time.Second).Should(BeTrue())
242+
})
243+
}

0 commit comments

Comments
 (0)