diff --git a/main.go b/main.go index cf48d2a008c..5f047db8a0e 100644 --- a/main.go +++ b/main.go @@ -426,7 +426,9 @@ func setupControllers(ctx context.Context, mgr ctrl.Manager, tracker *readiness. if *externaldata.ExternalDataEnabled { providerCache = frameworksexternaldata.NewCache() args = append(args, rego.AddExternalDataProviderCache(providerCache)) - mutationOpts.ProviderCache = providerCache + if mutation.Enabled() { + mutationOpts.ProviderCache = providerCache + } switch { case *externaldataProviderResponseCacheTTL > 0: @@ -460,7 +462,9 @@ func setupControllers(ctx context.Context, mgr ctrl.Manager, tracker *readiness. args = append(args, rego.EnableExternalDataClientAuth(), rego.AddExternalDataClientCertWatcher(certWatcher)) // register the client cert watcher to the mutation system - mutationOpts.ClientCertWatcher = certWatcher + if mutation.Enabled() { + mutationOpts.ClientCertWatcher = certWatcher + } } cfArgs := []constraintclient.Opt{constraintclient.Targets(&target.K8sValidationTarget{})} @@ -508,7 +512,7 @@ func setupControllers(ctx context.Context, mgr ctrl.Manager, tracker *readiness. } } - mutationSystem := mutation.NewSystem(mutationOpts) + mutationSystem := newMutationSystem(mutationOpts) expansionSystem := expansion.NewSystem(mutationSystem) exportSystem := export.NewSystem() @@ -644,6 +648,17 @@ func setupControllers(ctx context.Context, mgr ctrl.Manager, tracker *readiness. return nil } +// newMutationSystem constructs the mutation system only when a mutation +// operation is assigned, so non-mutation processes do not carry an unused +// mutation system. expansion.System accepts a nil mutation system and skips +// applying mutators in that case. +func newMutationSystem(opts mutation.SystemOpts) *mutation.System { + if !mutation.Enabled() { + return nil + } + return mutation.NewSystem(opts) +} + func setLoggerForProduction(encoder zapcore.LevelEncoder, dest io.Writer) { sink := zapcore.AddSync(os.Stderr) if dest != nil { diff --git a/main_test.go b/main_test.go new file mode 100644 index 00000000000..94673c307a4 --- /dev/null +++ b/main_test.go @@ -0,0 +1,36 @@ +package main + +import ( + "flag" + "testing" + + "github.com/open-policy-agent/gatekeeper/v3/pkg/mutation" +) + +// TestNewMutationSystem exercises the operation-dependent construction of +// the mutation system. The --operation flag can only accumulate values +// within a process, so the mutation-disabled case runs before the +// mutation-enabled cases. +func TestNewMutationSystem(t *testing.T) { + // Non-mutation operation set: no mutation system is constructed. + if err := flag.Set("operation", "audit"); err != nil { + t.Fatalf("setting operation flag: %v", err) + } + if mutation.Enabled() { + t.Fatalf("expected mutation to be disabled for audit-only operations") + } + if system := newMutationSystem(mutation.SystemOpts{}); system != nil { + t.Errorf("newMutationSystem() = %v, want nil when no mutation operation is assigned", system) + } + + // Adding a mutation operation constructs a real system. + if err := flag.Set("operation", "mutation-controller"); err != nil { + t.Fatalf("setting operation flag: %v", err) + } + if !mutation.Enabled() { + t.Fatalf("expected mutation to be enabled once a mutation operation is assigned") + } + if system := newMutationSystem(mutation.SystemOpts{}); system == nil { + t.Error("newMutationSystem() = nil, want a system when a mutation operation is assigned") + } +} diff --git a/pkg/controller/mutators/instances/mutator_controllers.go b/pkg/controller/mutators/instances/mutator_controllers.go index 12b2cc433a4..c7e57864ac6 100644 --- a/pkg/controller/mutators/instances/mutator_controllers.go +++ b/pkg/controller/mutators/instances/mutator_controllers.go @@ -62,6 +62,10 @@ func routeConflictEvents(ctx context.Context, events <-chan event.GenericEvent, // Add creates all mutation controllers and adds them to the manager. func (a *Adder) Add(mgr manager.Manager) error { + if !mutation.Enabled() { + return nil + } + // events is shared across all mutators that can affect the implied schema // of kinds to be mutated, since these mutators can set each other into conflict events := make(chan event.GenericEvent, eventQueueSize) diff --git a/pkg/controller/mutators/instances/mutator_controllers_test.go b/pkg/controller/mutators/instances/mutator_controllers_test.go index 896ad1f1a46..1d36fe763a6 100644 --- a/pkg/controller/mutators/instances/mutator_controllers_test.go +++ b/pkg/controller/mutators/instances/mutator_controllers_test.go @@ -2,6 +2,7 @@ package instances import ( "context" + "flag" "testing" "time" @@ -142,3 +143,19 @@ func awaitCondition(t *testing.T, cond func() bool, message string) { t.Fatal(message) } + +func TestAddSkipsSetupWhenMutationDisabled(t *testing.T) { + // Restrict this process to a non-mutation operation. The operation set + // is process-global and only accumulates via the flag, so this test + // must not run in parallel with operation-dependent tests. + if err := flag.Set("operation", "audit"); err != nil { + t.Fatalf("setting operation flag: %v", err) + } + + // When mutation is disabled Add must return before touching the manager, + // registering the conflict-routing runnable, or building any channels. + a := &Adder{} + if err := a.Add(nil); err != nil { + t.Fatalf("Add() with mutation disabled = %v, want nil", err) + } +}