Setup field for manager overrides on KustomizationReconciler struct and build up the disallow list to include these

Signed-off-by: Luke Mallon (Nalum) <luke@mallon.ie>

Setup new flag to allow overriding additional managers and pass this data to the KustomizationReconciler instance

Signed-off-by: Luke Mallon (Nalum) <luke@mallon.ie>

Update field name to be more specific

Co-authored-by: Stefan Prodan <stefan.prodan@gmail.com>
Signed-off-by: Luke Mallon <luke@mallon.ie>

Update the remaining fieldManagers vars to match the new definition

Signed-off-by: Luke Mallon (Nalum) <luke@mallon.ie>

Change AdditionalFieldManagers to DisallowedFieldManagers

Signed-off-by: Luke Mallon (Nalum) <luke@mallon.ie>

Add unit test to cover the new disallowed field manager change

Signed-off-by: Luke Mallon (Nalum) <luke@mallon.ie>

Use correct variable in the final Run

Signed-off-by: Luke Mallon (Nalum) <luke@mallon.ie>

Undo the timeout multiplication

Signed-off-by: Luke Mallon (Nalum) <luke@mallon.ie>

Update internal/controller/kustomization_disallowed_managers_test.go

Co-authored-by: Stefan Prodan <stefan.prodan@gmail.com>
Signed-off-by: Luke Mallon <luke@mallon.ie>

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) <luke@mallon.ie>

Update main.go

Co-authored-by: Stefan Prodan <stefan.prodan@gmail.com>
Signed-off-by: Luke Mallon <luke@mallon.ie>
This commit is contained in:
Luke Mallon (Nalum) 2023-11-25 13:43:47 +00:00
parent 250f620fbe
commit 96a772293a
No known key found for this signature in database
GPG Key ID: 8E0B5700B8A6F987
4 changed files with 245 additions and 69 deletions

View File

@ -83,18 +83,19 @@ type KustomizationReconciler struct {
kuberecorder.EventRecorder
runtimeCtrl.Metrics
artifactFetcher *fetch.ArchiveFetcher
requeueDependency time.Duration
StatusPoller *polling.StatusPoller
PollingOpts polling.Options
ControllerName string
statusManager string
NoCrossNamespaceRefs bool
NoRemoteBases bool
FailFast bool
DefaultServiceAccount string
KubeConfigOpts runtimeClient.KubeConfigOptions
ConcurrentSSA int
artifactFetcher *fetch.ArchiveFetcher
requeueDependency time.Duration
StatusPoller *polling.StatusPoller
PollingOpts polling.Options
ControllerName string
statusManager string
NoCrossNamespaceRefs bool
NoRemoteBases bool
FailFast bool
DefaultServiceAccount string
KubeConfigOpts runtimeClient.KubeConfigOptions
ConcurrentSSA int
DisallowedFieldManagers []string
}
// KustomizationReconcilerOptions contains options for the KustomizationReconciler.
@ -669,6 +670,41 @@ func (r *KustomizationReconciler) apply(ctx context.Context,
fmt.Sprintf("%s/force", kustomizev1.GroupVersion.Group): kustomizev1.EnabledValue,
}
fieldManagers := []ssa.FieldManager{
{
// to undo changes made with 'kubectl apply --server-side --force-conflicts'
Name: "kubectl",
OperationType: metav1.ManagedFieldsOperationApply,
},
{
// to undo changes made with 'kubectl apply'
Name: "kubectl",
OperationType: metav1.ManagedFieldsOperationUpdate,
},
{
// to undo changes made with 'kubectl apply'
Name: "before-first-apply",
OperationType: metav1.ManagedFieldsOperationUpdate,
},
{
// to undo changes made by the controller before SSA
Name: r.ControllerName,
OperationType: metav1.ManagedFieldsOperationUpdate,
},
}
for _, fieldManager := range r.DisallowedFieldManagers {
fieldManagers = append(fieldManagers, ssa.FieldManager{
Name: fieldManager,
OperationType: metav1.ManagedFieldsOperationApply,
})
// to undo changes made by the controller before SSA
fieldManagers = append(fieldManagers, ssa.FieldManager{
Name: fieldManager,
OperationType: metav1.ManagedFieldsOperationUpdate,
})
}
applyOpts.Cleanup = ssa.ApplyCleanupOptions{
Annotations: []string{
// remove the kubectl annotation
@ -681,28 +717,7 @@ func (r *KustomizationReconciler) apply(ctx context.Context,
// remove deprecated fluxcd.io labels
"fluxcd.io/sync-gc-mark",
},
FieldManagers: []ssa.FieldManager{
{
// to undo changes made with 'kubectl apply --server-side --force-conflicts'
Name: "kubectl",
OperationType: metav1.ManagedFieldsOperationApply,
},
{
// to undo changes made with 'kubectl apply'
Name: "kubectl",
OperationType: metav1.ManagedFieldsOperationUpdate,
},
{
// to undo changes made with 'kubectl apply'
Name: "before-first-apply",
OperationType: metav1.ManagedFieldsOperationUpdate,
},
{
// to undo changes made by the controller before SSA
Name: r.ControllerName,
OperationType: metav1.ManagedFieldsOperationUpdate,
},
},
FieldManagers: fieldManagers,
Exclusions: map[string]string{
fmt.Sprintf("%s/ssa", kustomizev1.GroupVersion.Group): kustomizev1.MergeValue,
},

View File

@ -0,0 +1,156 @@
/*
Copyright 2023 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
"fmt"
"testing"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
)
func TestKustomizationReconciler_DisallowedManagers(t *testing.T) {
g := NewWithT(t)
id := "disallowed-managers-" + randStringRunes(5)
revision := "v1.0.0"
err := createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
err = createKubeConfigSecret(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
manifests := func(name string, data string) []testserver.File {
return []testserver.File{
{
Name: "configmap.yaml",
Body: fmt.Sprintf(`---
apiVersion: v1
kind: ConfigMap
metadata:
name: %[1]s
data:
key: %[2]s
`, name, data),
},
}
}
artifact, err := testServer.ArtifactFromFiles(manifests(id, randStringRunes(5)))
g.Expect(err).NotTo(HaveOccurred(), "failed to create artifact from files")
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("disallowed-managers-%s", randStringRunes(5)),
Namespace: id,
}
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("disallowed-managers-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
HealthChecks: []meta.NamespacedObjectKindReference{
{
APIVersion: "v1",
Kind: "ConfigMap",
Name: id,
Namespace: id,
},
},
TargetNamespace: id,
Force: false,
},
}
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
resultK := &kustomizev1.Kustomization{}
initialConfigMap := &corev1.ConfigMap{}
badConfigMap := &corev1.ConfigMap{}
fixedConfigMap := &corev1.ConfigMap{}
t.Run("creates configmap", func(t *testing.T) {
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return resultK.Status.LastAppliedRevision == revision
}, timeout, time.Second).Should(BeTrue())
logStatus(t, resultK)
kstatusCheck.CheckErr(ctx, resultK)
g.Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: id, Namespace: id}, initialConfigMap)).Should(Succeed())
g.Expect(initialConfigMap.Data).Should(HaveKey("key"))
})
t.Run("update configmap with new data", func(t *testing.T) {
configMap := corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: id,
Namespace: id,
},
}
err = k8sClient.Patch(context.Background(), &configMap, client.RawPatch(types.MergePatchType, []byte(`{"data":{"bad-key":"overridden field manager"}}`)), &client.PatchOptions{FieldManager: overrideManagerName})
g.Expect(err).NotTo(HaveOccurred())
err = k8sClient.Patch(context.Background(), &configMap, client.RawPatch(types.MergePatchType, []byte(`{"data":{"key2":"not overridden field manager"}}`)), &client.PatchOptions{FieldManager: "good-name"})
g.Expect(err).NotTo(HaveOccurred())
err = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(initialConfigMap), badConfigMap)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(badConfigMap.Data).Should(HaveKey("bad-key"))
g.Expect(badConfigMap.Data).Should(HaveKey("key2"))
})
t.Run("bad-key should be removed from the configmap", func(t *testing.T) {
reconciler.Reconcile(context.Background(), ctrl.Request{
NamespacedName: kustomizationKey,
})
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(initialConfigMap), fixedConfigMap)
return g.Expect(fixedConfigMap.Data).ShouldNot(HaveKey("bad-key")) && g.Expect(fixedConfigMap.Data).Should(HaveKey("key2"))
}, timeout, time.Second).Should(BeTrue())
})
}

View File

@ -57,6 +57,7 @@ const (
interval = time.Second * 1
reconciliationInterval = time.Second * 5
vaultVersion = "1.13.2"
overrideManagerName = "node-fetch"
)
var (
@ -173,11 +174,12 @@ func TestMain(m *testing.M) {
kstatusInProgressCheck = kcheck.NewInProgressChecker(testEnv.Client)
kstatusInProgressCheck.DisableFetch = true
reconciler = &KustomizationReconciler{
ControllerName: controllerName,
Client: testEnv,
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
Metrics: testMetricsH,
ConcurrentSSA: 4,
ControllerName: controllerName,
Client: testEnv,
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
Metrics: testMetricsH,
ConcurrentSSA: 4,
DisallowedFieldManagers: []string{overrideManagerName},
}
if err := (reconciler).SetupWithManager(ctx, testEnv, KustomizationReconcilerOptions{
DependencyRequeueInterval: 2 * time.Second,

63
main.go
View File

@ -76,24 +76,25 @@ func init() {
func main() {
var (
metricsAddr string
eventsAddr string
healthAddr string
concurrent int
concurrentSSA int
requeueDependency time.Duration
clientOptions runtimeClient.Options
kubeConfigOpts runtimeClient.KubeConfigOptions
logOptions logger.Options
leaderElectionOptions leaderelection.Options
rateLimiterOptions runtimeCtrl.RateLimiterOptions
watchOptions runtimeCtrl.WatchOptions
intervalJitterOptions jitter.IntervalOptions
aclOptions acl.Options
noRemoteBases bool
httpRetry int
defaultServiceAccount string
featureGates feathelper.FeatureGates
metricsAddr string
eventsAddr string
healthAddr string
concurrent int
concurrentSSA int
requeueDependency time.Duration
clientOptions runtimeClient.Options
kubeConfigOpts runtimeClient.KubeConfigOptions
logOptions logger.Options
leaderElectionOptions leaderelection.Options
rateLimiterOptions runtimeCtrl.RateLimiterOptions
watchOptions runtimeCtrl.WatchOptions
intervalJitterOptions jitter.IntervalOptions
aclOptions acl.Options
noRemoteBases bool
httpRetry int
defaultServiceAccount string
featureGates feathelper.FeatureGates
disallowedFieldManagers []string
)
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
@ -106,6 +107,7 @@ func main() {
"Disallow remote bases usage in Kustomize overlays. When this flag is enabled, all resources must refer to local files included in the source artifact.")
flag.IntVar(&httpRetry, "http-retry", 9, "The maximum number of retries when failing to fetch artifacts over HTTP.")
flag.StringVar(&defaultServiceAccount, "default-service-account", "", "Default service account used for impersonation.")
flag.StringArrayVar(&disallowedFieldManagers, "override-manager", []string{}, "Field manager disallowed to perform changes on managed resources.")
clientOptions.BindFlags(flag.CommandLine)
logOptions.BindFlags(flag.CommandLine)
@ -227,18 +229,19 @@ func main() {
}
if err = (&controller.KustomizationReconciler{
ControllerName: controllerName,
DefaultServiceAccount: defaultServiceAccount,
Client: mgr.GetClient(),
Metrics: metricsH,
EventRecorder: eventRecorder,
NoCrossNamespaceRefs: aclOptions.NoCrossNamespaceRefs,
NoRemoteBases: noRemoteBases,
FailFast: failFast,
ConcurrentSSA: concurrentSSA,
KubeConfigOpts: kubeConfigOpts,
PollingOpts: pollingOpts,
StatusPoller: polling.NewStatusPoller(mgr.GetClient(), mgr.GetRESTMapper(), pollingOpts),
ControllerName: controllerName,
DefaultServiceAccount: defaultServiceAccount,
Client: mgr.GetClient(),
Metrics: metricsH,
EventRecorder: eventRecorder,
NoCrossNamespaceRefs: aclOptions.NoCrossNamespaceRefs,
NoRemoteBases: noRemoteBases,
FailFast: failFast,
ConcurrentSSA: concurrentSSA,
KubeConfigOpts: kubeConfigOpts,
PollingOpts: pollingOpts,
StatusPoller: polling.NewStatusPoller(mgr.GetClient(), mgr.GetRESTMapper(), pollingOpts),
DisallowedFieldManagers: disallowedFieldManagers,
}).SetupWithManager(ctx, mgr, controller.KustomizationReconcilerOptions{
DependencyRequeueInterval: requeueDependency,
HTTPRetry: httpRetry,