Skip to content

Commit 96a7722

Browse files
Nalumstefanprodan
andcommitted
Setup field for manager overrides on KustomizationReconciler struct and build up the disallow list to include these
Signed-off-by: Luke Mallon (Nalum) <[email protected]> Setup new flag to allow overriding additional managers and pass this data to the KustomizationReconciler instance Signed-off-by: Luke Mallon (Nalum) <[email protected]> Update field name to be more specific Co-authored-by: Stefan Prodan <[email protected]> Signed-off-by: Luke Mallon <[email protected]> Update the remaining fieldManagers vars to match the new definition Signed-off-by: Luke Mallon (Nalum) <[email protected]> Change AdditionalFieldManagers to DisallowedFieldManagers Signed-off-by: Luke Mallon (Nalum) <[email protected]> Add unit test to cover the new disallowed field manager change Signed-off-by: Luke Mallon (Nalum) <[email protected]> Use correct variable in the final Run Signed-off-by: Luke Mallon (Nalum) <[email protected]> Undo the timeout multiplication Signed-off-by: Luke Mallon (Nalum) <[email protected]> Update internal/controller/kustomization_disallowed_managers_test.go Co-authored-by: Stefan Prodan <[email protected]> Signed-off-by: Luke Mallon <[email protected]> Check for we're not getting errors on the Patch calls and remove the eventually as not needed here Signed-off-by: Luke Mallon (Nalum) <[email protected]> Update main.go Co-authored-by: Stefan Prodan <[email protected]> Signed-off-by: Luke Mallon <[email protected]>
1 parent 250f620 commit 96a7722

File tree

4 files changed

+245
-69
lines changed

4 files changed

+245
-69
lines changed

internal/controller/kustomization_controller.go

+49-34
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,19 @@ type KustomizationReconciler struct {
8383
kuberecorder.EventRecorder
8484
runtimeCtrl.Metrics
8585

86-
artifactFetcher *fetch.ArchiveFetcher
87-
requeueDependency time.Duration
88-
StatusPoller *polling.StatusPoller
89-
PollingOpts polling.Options
90-
ControllerName string
91-
statusManager string
92-
NoCrossNamespaceRefs bool
93-
NoRemoteBases bool
94-
FailFast bool
95-
DefaultServiceAccount string
96-
KubeConfigOpts runtimeClient.KubeConfigOptions
97-
ConcurrentSSA int
86+
artifactFetcher *fetch.ArchiveFetcher
87+
requeueDependency time.Duration
88+
StatusPoller *polling.StatusPoller
89+
PollingOpts polling.Options
90+
ControllerName string
91+
statusManager string
92+
NoCrossNamespaceRefs bool
93+
NoRemoteBases bool
94+
FailFast bool
95+
DefaultServiceAccount string
96+
KubeConfigOpts runtimeClient.KubeConfigOptions
97+
ConcurrentSSA int
98+
DisallowedFieldManagers []string
9899
}
99100

100101
// KustomizationReconcilerOptions contains options for the KustomizationReconciler.
@@ -669,6 +670,41 @@ func (r *KustomizationReconciler) apply(ctx context.Context,
669670
fmt.Sprintf("%s/force", kustomizev1.GroupVersion.Group): kustomizev1.EnabledValue,
670671
}
671672

673+
fieldManagers := []ssa.FieldManager{
674+
{
675+
// to undo changes made with 'kubectl apply --server-side --force-conflicts'
676+
Name: "kubectl",
677+
OperationType: metav1.ManagedFieldsOperationApply,
678+
},
679+
{
680+
// to undo changes made with 'kubectl apply'
681+
Name: "kubectl",
682+
OperationType: metav1.ManagedFieldsOperationUpdate,
683+
},
684+
{
685+
// to undo changes made with 'kubectl apply'
686+
Name: "before-first-apply",
687+
OperationType: metav1.ManagedFieldsOperationUpdate,
688+
},
689+
{
690+
// to undo changes made by the controller before SSA
691+
Name: r.ControllerName,
692+
OperationType: metav1.ManagedFieldsOperationUpdate,
693+
},
694+
}
695+
696+
for _, fieldManager := range r.DisallowedFieldManagers {
697+
fieldManagers = append(fieldManagers, ssa.FieldManager{
698+
Name: fieldManager,
699+
OperationType: metav1.ManagedFieldsOperationApply,
700+
})
701+
// to undo changes made by the controller before SSA
702+
fieldManagers = append(fieldManagers, ssa.FieldManager{
703+
Name: fieldManager,
704+
OperationType: metav1.ManagedFieldsOperationUpdate,
705+
})
706+
}
707+
672708
applyOpts.Cleanup = ssa.ApplyCleanupOptions{
673709
Annotations: []string{
674710
// remove the kubectl annotation
@@ -681,28 +717,7 @@ func (r *KustomizationReconciler) apply(ctx context.Context,
681717
// remove deprecated fluxcd.io labels
682718
"fluxcd.io/sync-gc-mark",
683719
},
684-
FieldManagers: []ssa.FieldManager{
685-
{
686-
// to undo changes made with 'kubectl apply --server-side --force-conflicts'
687-
Name: "kubectl",
688-
OperationType: metav1.ManagedFieldsOperationApply,
689-
},
690-
{
691-
// to undo changes made with 'kubectl apply'
692-
Name: "kubectl",
693-
OperationType: metav1.ManagedFieldsOperationUpdate,
694-
},
695-
{
696-
// to undo changes made with 'kubectl apply'
697-
Name: "before-first-apply",
698-
OperationType: metav1.ManagedFieldsOperationUpdate,
699-
},
700-
{
701-
// to undo changes made by the controller before SSA
702-
Name: r.ControllerName,
703-
OperationType: metav1.ManagedFieldsOperationUpdate,
704-
},
705-
},
720+
FieldManagers: fieldManagers,
706721
Exclusions: map[string]string{
707722
fmt.Sprintf("%s/ssa", kustomizev1.GroupVersion.Group): kustomizev1.MergeValue,
708723
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
Copyright 2023 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+
"fmt"
22+
"testing"
23+
"time"
24+
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+
ctrl "sigs.k8s.io/controller-runtime"
33+
"sigs.k8s.io/controller-runtime/pkg/client"
34+
35+
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
36+
)
37+
38+
func TestKustomizationReconciler_DisallowedManagers(t *testing.T) {
39+
g := NewWithT(t)
40+
id := "disallowed-managers-" + randStringRunes(5)
41+
revision := "v1.0.0"
42+
43+
err := createNamespace(id)
44+
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
45+
46+
err = createKubeConfigSecret(id)
47+
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
48+
49+
manifests := func(name string, data string) []testserver.File {
50+
return []testserver.File{
51+
{
52+
Name: "configmap.yaml",
53+
Body: fmt.Sprintf(`---
54+
apiVersion: v1
55+
kind: ConfigMap
56+
metadata:
57+
name: %[1]s
58+
data:
59+
key: %[2]s
60+
`, name, data),
61+
},
62+
}
63+
}
64+
65+
artifact, err := testServer.ArtifactFromFiles(manifests(id, randStringRunes(5)))
66+
g.Expect(err).NotTo(HaveOccurred(), "failed to create artifact from files")
67+
68+
repositoryName := types.NamespacedName{
69+
Name: fmt.Sprintf("disallowed-managers-%s", randStringRunes(5)),
70+
Namespace: id,
71+
}
72+
73+
err = applyGitRepository(repositoryName, artifact, revision)
74+
g.Expect(err).NotTo(HaveOccurred())
75+
76+
kustomizationKey := types.NamespacedName{
77+
Name: fmt.Sprintf("disallowed-managers-%s", randStringRunes(5)),
78+
Namespace: id,
79+
}
80+
kustomization := &kustomizev1.Kustomization{
81+
ObjectMeta: metav1.ObjectMeta{
82+
Name: kustomizationKey.Name,
83+
Namespace: kustomizationKey.Namespace,
84+
},
85+
Spec: kustomizev1.KustomizationSpec{
86+
Interval: metav1.Duration{Duration: reconciliationInterval},
87+
Path: "./",
88+
KubeConfig: &meta.KubeConfigReference{
89+
SecretRef: meta.SecretKeyReference{
90+
Name: "kubeconfig",
91+
},
92+
},
93+
SourceRef: kustomizev1.CrossNamespaceSourceReference{
94+
Name: repositoryName.Name,
95+
Namespace: repositoryName.Namespace,
96+
Kind: sourcev1.GitRepositoryKind,
97+
},
98+
HealthChecks: []meta.NamespacedObjectKindReference{
99+
{
100+
APIVersion: "v1",
101+
Kind: "ConfigMap",
102+
Name: id,
103+
Namespace: id,
104+
},
105+
},
106+
TargetNamespace: id,
107+
Force: false,
108+
},
109+
}
110+
111+
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
112+
113+
resultK := &kustomizev1.Kustomization{}
114+
initialConfigMap := &corev1.ConfigMap{}
115+
badConfigMap := &corev1.ConfigMap{}
116+
fixedConfigMap := &corev1.ConfigMap{}
117+
118+
t.Run("creates configmap", func(t *testing.T) {
119+
g.Eventually(func() bool {
120+
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
121+
return resultK.Status.LastAppliedRevision == revision
122+
}, timeout, time.Second).Should(BeTrue())
123+
logStatus(t, resultK)
124+
125+
kstatusCheck.CheckErr(ctx, resultK)
126+
g.Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: id, Namespace: id}, initialConfigMap)).Should(Succeed())
127+
g.Expect(initialConfigMap.Data).Should(HaveKey("key"))
128+
})
129+
130+
t.Run("update configmap with new data", func(t *testing.T) {
131+
configMap := corev1.ConfigMap{
132+
ObjectMeta: metav1.ObjectMeta{
133+
Name: id,
134+
Namespace: id,
135+
},
136+
}
137+
err = k8sClient.Patch(context.Background(), &configMap, client.RawPatch(types.MergePatchType, []byte(`{"data":{"bad-key":"overridden field manager"}}`)), &client.PatchOptions{FieldManager: overrideManagerName})
138+
g.Expect(err).NotTo(HaveOccurred())
139+
err = k8sClient.Patch(context.Background(), &configMap, client.RawPatch(types.MergePatchType, []byte(`{"data":{"key2":"not overridden field manager"}}`)), &client.PatchOptions{FieldManager: "good-name"})
140+
g.Expect(err).NotTo(HaveOccurred())
141+
err = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(initialConfigMap), badConfigMap)
142+
g.Expect(err).NotTo(HaveOccurred())
143+
g.Expect(badConfigMap.Data).Should(HaveKey("bad-key"))
144+
g.Expect(badConfigMap.Data).Should(HaveKey("key2"))
145+
})
146+
147+
t.Run("bad-key should be removed from the configmap", func(t *testing.T) {
148+
reconciler.Reconcile(context.Background(), ctrl.Request{
149+
NamespacedName: kustomizationKey,
150+
})
151+
g.Eventually(func() bool {
152+
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(initialConfigMap), fixedConfigMap)
153+
return g.Expect(fixedConfigMap.Data).ShouldNot(HaveKey("bad-key")) && g.Expect(fixedConfigMap.Data).Should(HaveKey("key2"))
154+
}, timeout, time.Second).Should(BeTrue())
155+
})
156+
}

internal/controller/suite_test.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const (
5757
interval = time.Second * 1
5858
reconciliationInterval = time.Second * 5
5959
vaultVersion = "1.13.2"
60+
overrideManagerName = "node-fetch"
6061
)
6162

6263
var (
@@ -173,11 +174,12 @@ func TestMain(m *testing.M) {
173174
kstatusInProgressCheck = kcheck.NewInProgressChecker(testEnv.Client)
174175
kstatusInProgressCheck.DisableFetch = true
175176
reconciler = &KustomizationReconciler{
176-
ControllerName: controllerName,
177-
Client: testEnv,
178-
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
179-
Metrics: testMetricsH,
180-
ConcurrentSSA: 4,
177+
ControllerName: controllerName,
178+
Client: testEnv,
179+
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
180+
Metrics: testMetricsH,
181+
ConcurrentSSA: 4,
182+
DisallowedFieldManagers: []string{overrideManagerName},
181183
}
182184
if err := (reconciler).SetupWithManager(ctx, testEnv, KustomizationReconcilerOptions{
183185
DependencyRequeueInterval: 2 * time.Second,

main.go

+33-30
Original file line numberDiff line numberDiff line change
@@ -76,24 +76,25 @@ func init() {
7676

7777
func main() {
7878
var (
79-
metricsAddr string
80-
eventsAddr string
81-
healthAddr string
82-
concurrent int
83-
concurrentSSA int
84-
requeueDependency time.Duration
85-
clientOptions runtimeClient.Options
86-
kubeConfigOpts runtimeClient.KubeConfigOptions
87-
logOptions logger.Options
88-
leaderElectionOptions leaderelection.Options
89-
rateLimiterOptions runtimeCtrl.RateLimiterOptions
90-
watchOptions runtimeCtrl.WatchOptions
91-
intervalJitterOptions jitter.IntervalOptions
92-
aclOptions acl.Options
93-
noRemoteBases bool
94-
httpRetry int
95-
defaultServiceAccount string
96-
featureGates feathelper.FeatureGates
79+
metricsAddr string
80+
eventsAddr string
81+
healthAddr string
82+
concurrent int
83+
concurrentSSA int
84+
requeueDependency time.Duration
85+
clientOptions runtimeClient.Options
86+
kubeConfigOpts runtimeClient.KubeConfigOptions
87+
logOptions logger.Options
88+
leaderElectionOptions leaderelection.Options
89+
rateLimiterOptions runtimeCtrl.RateLimiterOptions
90+
watchOptions runtimeCtrl.WatchOptions
91+
intervalJitterOptions jitter.IntervalOptions
92+
aclOptions acl.Options
93+
noRemoteBases bool
94+
httpRetry int
95+
defaultServiceAccount string
96+
featureGates feathelper.FeatureGates
97+
disallowedFieldManagers []string
9798
)
9899

99100
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
@@ -106,6 +107,7 @@ func main() {
106107
"Disallow remote bases usage in Kustomize overlays. When this flag is enabled, all resources must refer to local files included in the source artifact.")
107108
flag.IntVar(&httpRetry, "http-retry", 9, "The maximum number of retries when failing to fetch artifacts over HTTP.")
108109
flag.StringVar(&defaultServiceAccount, "default-service-account", "", "Default service account used for impersonation.")
110+
flag.StringArrayVar(&disallowedFieldManagers, "override-manager", []string{}, "Field manager disallowed to perform changes on managed resources.")
109111

110112
clientOptions.BindFlags(flag.CommandLine)
111113
logOptions.BindFlags(flag.CommandLine)
@@ -227,18 +229,19 @@ func main() {
227229
}
228230

229231
if err = (&controller.KustomizationReconciler{
230-
ControllerName: controllerName,
231-
DefaultServiceAccount: defaultServiceAccount,
232-
Client: mgr.GetClient(),
233-
Metrics: metricsH,
234-
EventRecorder: eventRecorder,
235-
NoCrossNamespaceRefs: aclOptions.NoCrossNamespaceRefs,
236-
NoRemoteBases: noRemoteBases,
237-
FailFast: failFast,
238-
ConcurrentSSA: concurrentSSA,
239-
KubeConfigOpts: kubeConfigOpts,
240-
PollingOpts: pollingOpts,
241-
StatusPoller: polling.NewStatusPoller(mgr.GetClient(), mgr.GetRESTMapper(), pollingOpts),
232+
ControllerName: controllerName,
233+
DefaultServiceAccount: defaultServiceAccount,
234+
Client: mgr.GetClient(),
235+
Metrics: metricsH,
236+
EventRecorder: eventRecorder,
237+
NoCrossNamespaceRefs: aclOptions.NoCrossNamespaceRefs,
238+
NoRemoteBases: noRemoteBases,
239+
FailFast: failFast,
240+
ConcurrentSSA: concurrentSSA,
241+
KubeConfigOpts: kubeConfigOpts,
242+
PollingOpts: pollingOpts,
243+
StatusPoller: polling.NewStatusPoller(mgr.GetClient(), mgr.GetRESTMapper(), pollingOpts),
244+
DisallowedFieldManagers: disallowedFieldManagers,
242245
}).SetupWithManager(ctx, mgr, controller.KustomizationReconcilerOptions{
243246
DependencyRequeueInterval: requeueDependency,
244247
HTTPRetry: httpRetry,

0 commit comments

Comments
 (0)