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:
parent
250f620fbe
commit
96a772293a
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
})
|
||||
}
|
||||
|
|
@ -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
63
main.go
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue