diff --git a/cmd/main.go b/cmd/main.go index b8188608d..f1633446a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -25,6 +25,7 @@ import ( "path/filepath" "time" + "github.com/spf13/pflag" "sigs.k8s.io/controller-runtime/pkg/metrics/filters" intController "github.com/splunk/splunk-operator/internal/controller" @@ -89,24 +90,44 @@ func main() { // TLS certificate configuration for metrics var metricsCertPath, metricsCertName, metricsCertKey string - flag.StringVar(&logEncoder, "log-encoder", "json", "log encoding ('json' or 'console')") - flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") - flag.BoolVar(&enableLeaderElection, "leader-elect", false, + pflag.StringVar(&logEncoder, "log-encoder", "json", "log encoding ('json' or 'console')") + pflag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + pflag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") - flag.BoolVar(&pprofActive, "pprof", true, "Enable pprof endpoint") - flag.IntVar(&logLevel, "log-level", int(zapcore.InfoLevel), "set log level") - flag.IntVar(&leaseDurationSecond, "lease-duration", leaseDurationSecond, "manager lease duration in seconds") - flag.IntVar(&renewDeadlineSecond, "renew-duration", renewDeadlineSecond, "manager renew duration in seconds") - flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metrics endpoint binds to. "+ + pflag.BoolVar(&pprofActive, "pprof", true, "Enable pprof endpoint") + pflag.IntVar(&logLevel, "log-level", int(zapcore.InfoLevel), "set log level") + pflag.IntVar(&leaseDurationSecond, "lease-duration", leaseDurationSecond, "manager lease duration in seconds") + pflag.IntVar(&renewDeadlineSecond, "renew-duration", renewDeadlineSecond, "manager renew duration in seconds") + pflag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metrics endpoint binds to. "+ "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") - flag.BoolVar(&secureMetrics, "metrics-secure", false, + pflag.BoolVar(&secureMetrics, "metrics-secure", false, "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") // TLS certificate flags for metrics server - flag.StringVar(&metricsCertPath, "metrics-cert-path", "", "The directory that contains the metrics server certificate.") - flag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.") - flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.") + pflag.StringVar(&metricsCertPath, "metrics-cert-path", "", "The directory that contains the metrics server certificate.") + pflag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.") + pflag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.") + + config.DefaultMutableFeatureGate.AddFlag(pflag.CommandLine) + + opts := zap.Options{ + Development: true, + TimeEncoder: zapcore.RFC3339NanoTimeEncoder, + } + opts.BindFlags(flag.CommandLine) + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + if allGates := config.DefaultMutableFeatureGate.GetAll(); len(allGates) > 0 { + effectiveStates := make(map[string]bool, len(allGates)) + for gate := range allGates { + effectiveStates[string(gate)] = config.DefaultMutableFeatureGate.Enabled(gate) + } + setupLog.Info("Feature gates initialized", "gates", effectiveStates) + } // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. // More info: @@ -147,16 +168,6 @@ func main() { renewDeadline = time.Duration(renewDeadlineSecond) * time.Second } - opts := zap.Options{ - Development: true, - TimeEncoder: zapcore.RFC3339NanoTimeEncoder, - } - opts.BindFlags(flag.CommandLine) - flag.Parse() - - // Logging setup - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - // Configure metrics certificate watcher if metrics certs are provided var metricsCertWatcher *certwatcher.CertWatcher if len(metricsCertPath) > 0 { @@ -280,10 +291,11 @@ func main() { os.Exit(1) } - // Setup centralized validation webhook server (opt-in via ENABLE_VALIDATION_WEBHOOK env var, defaults to false) - enableWebhooks := os.Getenv("ENABLE_VALIDATION_WEBHOOK") - if enableWebhooks == "true" { - // Parse optional timeout configurations from environment + if _, ok := os.LookupEnv("ENABLE_VALIDATION_WEBHOOK"); ok { + setupLog.Info("DEPRECATED: ENABLE_VALIDATION_WEBHOOK env var is deprecated and will be removed in a future release; use --feature-gates=ValidationWebhook=true instead") + } + + if config.DefaultMutableFeatureGate.Enabled(config.ValidationWebhook) { readTimeout := 10 * time.Second if val := os.Getenv("WEBHOOK_READ_TIMEOUT"); val != "" { if d, err := time.ParseDuration(val); err == nil { @@ -306,16 +318,15 @@ func main() { Client: mgr.GetClient(), }) - // Add webhook server as a runnable to the manager if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { return webhookServer.Start(ctx) })); err != nil { setupLog.Error(err, "unable to add webhook server to manager") os.Exit(1) } - setupLog.Info("Validation webhook enabled via ENABLE_VALIDATION_WEBHOOK=true") + setupLog.Info("Validation webhook enabled") } else { - setupLog.Info("Validation webhook disabled (set ENABLE_VALIDATION_WEBHOOK=true to enable)") + setupLog.Info("Validation webhook disabled (set --feature-gates=ValidationWebhook=true to enable)") } //+kubebuilder:scaffold:builder diff --git a/config/default-with-webhook/kustomization-cluster.yaml b/config/default-with-webhook/kustomization-cluster.yaml index c596f0c68..f4ddabdb2 100644 --- a/config/default-with-webhook/kustomization-cluster.yaml +++ b/config/default-with-webhook/kustomization-cluster.yaml @@ -1,7 +1,7 @@ # Adds namespace to all resources. # Cluster-scoped deployment WITH webhook enabled (opt-in) # Requires cert-manager to be installed in the cluster -namespace: splunk-operator +namespace: splunk-operator # Value of this field is prepended to the # names of all resources, e.g. a deployment named @@ -115,7 +115,7 @@ patches: patch: |- - op: add path: /spec/template/spec/containers/0/env - value: + value: - name: WATCH_NAMESPACE value: WATCH_NAMESPACE_VALUE - name: RELATED_IMAGE_SPLUNK_ENTERPRISE @@ -124,12 +124,13 @@ patches: value: splunk-operator - name: SPLUNK_GENERAL_TERMS value: SPLUNK_GENERAL_TERMS_VALUE - - name: ENABLE_VALIDATION_WEBHOOK - value: "true" - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name + - op: add + path: /spec/template/spec/containers/0/args/- + value: --feature-gates=ValidationWebhook=true # [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443. # More info: https://book.kubebuilder.io/reference/metrics - path: manager_metrics_patch.yaml diff --git a/config/default-with-webhook/kustomization-namespace.yaml b/config/default-with-webhook/kustomization-namespace.yaml index 193791601..2bc1845c6 100644 --- a/config/default-with-webhook/kustomization-namespace.yaml +++ b/config/default-with-webhook/kustomization-namespace.yaml @@ -1,7 +1,7 @@ # Adds namespace to all resources. # Namespace-scoped deployment WITH webhook enabled (opt-in) # Requires cert-manager to be installed in the cluster -namespace: splunk-operator +namespace: splunk-operator # Value of this field is prepended to the # names of all resources, e.g. a deployment named @@ -115,7 +115,7 @@ patches: patch: |- - op: add path: /spec/template/spec/containers/0/env - value: + value: - name: WATCH_NAMESPACE valueFrom: fieldRef: @@ -126,12 +126,13 @@ patches: value: splunk-operator - name: SPLUNK_GENERAL_TERMS value: SPLUNK_GENERAL_TERMS_VALUE - - name: ENABLE_VALIDATION_WEBHOOK - value: "true" - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name + - op: add + path: /spec/template/spec/containers/0/args/- + value: --feature-gates=ValidationWebhook=true # [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443. # More info: https://book.kubebuilder.io/reference/metrics - path: manager_metrics_patch.yaml diff --git a/config/default-with-webhook/kustomization.yaml b/config/default-with-webhook/kustomization.yaml index 5ba87fec1..b146e575d 100644 --- a/config/default-with-webhook/kustomization.yaml +++ b/config/default-with-webhook/kustomization.yaml @@ -1,7 +1,7 @@ # Adds namespace to all resources. # Cluster-scoped deployment WITH webhook enabled (opt-in) # Requires cert-manager to be installed in the cluster -namespace: splunk-operator +namespace: splunk-operator # Value of this field is prepended to the # names of all resources, e.g. a deployment named @@ -115,7 +115,7 @@ patches: patch: |- - op: add path: /spec/template/spec/containers/0/env - value: + value: - name: WATCH_NAMESPACE value: WATCH_NAMESPACE_VALUE - name: RELATED_IMAGE_SPLUNK_ENTERPRISE @@ -124,12 +124,13 @@ patches: value: splunk-operator - name: SPLUNK_GENERAL_TERMS value: WATCH_NAMESPACE_VALUE - - name: ENABLE_VALIDATION_WEBHOOK - value: "true" - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name + - op: add + path: /spec/template/spec/containers/0/args/- + value: --feature-gates=ValidationWebhook=true # [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443. # More info: https://book.kubebuilder.io/reference/metrics - path: manager_metrics_patch.yaml diff --git a/docs/FeatureGates.md b/docs/FeatureGates.md new file mode 100644 index 000000000..6661ab055 --- /dev/null +++ b/docs/FeatureGates.md @@ -0,0 +1,110 @@ +# Feature Gates + +The Splunk Operator uses the Kubernetes [FeatureGate](https://pkg.go.dev/k8s.io/component-base/featuregate) pattern to control rollout of new functionality. Feature gates allow new code to be merged to the main branch without activating in production, giving teams a safe, per-environment opt-in mechanism. + +## Usage + +Enable or disable feature gates at operator startup: + +```bash +/manager --feature-gates=ValidationWebhook=true +``` + +## Maturity Lifecycle + +| Stage | Default | Can Override | Next Step | +|-----------|---------|-------------|-----------------------------------------| +| **Alpha** | off | Yes | Promote to Beta after validation | +| **Beta** | on | Yes | Promote to GA after sustained stability | +| **GA** | on | No | Remove gate in a future release | + +## Current Feature Gates + +| Gate | Default | Stage | Since | Description | +|-----------------------|---------|-------|---------|----------------------------------------------------------| +| `ValidationWebhook` | `false` | Alpha | v3.2.0 | Centralized validation webhook server for CR admission | + +## Adding a New Feature Gate + +Follow these steps: + +### 1. Register the gate in `pkg/config/featuregates.go` + +Add a constant and an entry in `defaultFeatureGates`: + +```go +const ( + MyNewFeature featuregate.Feature = "MyNewFeature" +) + +var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ + // existing gates … + MyNewFeature: {Default: false, PreRelease: featuregate.Alpha}, +} +``` + +### 2. Guard the code path + +Check the gate wherever the feature-specific logic runs: + +```go +if config.DefaultMutableFeatureGate.Enabled(config.MyNewFeature) { + // feature-specific logic +} +``` + +This can guard anything — a reconciler code path, a helper function, a webhook handler, an HTTP endpoint, etc. + +### Example: Gating a New Controller (CRD) + +When the feature gate introduces an entirely new CRD and controller, there are additional steps beyond the basic gate check. All three steps below are **mandatory** for any new CRD behind a feature gate. + +#### a. Gate controller registration in `cmd/main.go` + +Wrap the `SetupWithManager` call so the controller only starts when the gate is on: + +```go +if config.DefaultMutableFeatureGate.Enabled(config.MyNewFeature) { + if err = (&controller.MyNewReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "MyNew") + os.Exit(1) + } +} +``` + +#### b. Add a validating webhook for the gated CRD group + +A validating webhook **must** reject CR creation when the gate is off. Without this, users can create resources that no controller will reconcile, leading to silent failures: + +```go +func (v *MyNewValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + if !config.DefaultMutableFeatureGate.Enabled(config.MyNewFeature) { + return nil, fmt.Errorf( + "the MyNewFeature feature is not enabled; "+ + "set --feature-gates=MyNewFeature=true to activate") + } + return nil, nil +} +``` + +#### c. Label the CRD manifests + +Every gated CRD **must** carry maturity annotations and labels in `config/crd/bases/`. These signal to operators and tooling which gate controls the CRD and its current stability level: + +```yaml +metadata: + annotations: + splunk.com/feature-gate: MyNewFeature + splunk.com/feature-stage: Alpha + labels: + splunk.com/feature-stage: alpha +``` + +## Promoting a Gate + +- **Alpha → Beta**: Change `Default: false` to `Default: true` in `featuregates.go`; update the CRD label to `beta` +- **Beta → GA**: Set `LockToDefault: true` in the `FeatureSpec`; update the CRD label to `ga` +- **GA → Removed**: Delete the constant and `FeatureSpec` entry; remove the `if` guard in `cmd/main.go`; remove the CRD annotations/labels and the validating webhook diff --git a/docs/ValidationWebhook.md b/docs/ValidationWebhook.md index a3ae4c2de..04a24c2c5 100644 --- a/docs/ValidationWebhook.md +++ b/docs/ValidationWebhook.md @@ -85,23 +85,23 @@ If you prefer not to use cert-manager, you can provide your own TLS certificates #### Option 1: Use the Webhook-Enabled Kustomize Overlay -Deploy using the `config/default-with-webhook` overlay which includes all necessary webhook components: +Deploy using the `config/default-with-webhook` overlay which includes all necessary webhook components and enables the `ValidationWebhook` feature gate automatically: ```bash # Build and apply the webhook-enabled configuration kustomize build config/default-with-webhook | kubectl apply -f - ``` -#### Option 2: Enable Webhook on Existing Deployment +#### Option 2: Enable via Feature Gate on Existing Deployment -If you already have the operator deployed, you can enable the webhook by setting the `ENABLE_VALIDATION_WEBHOOK` environment variable: +If you already have the operator deployed with the webhook Kubernetes resources (Service, ValidatingWebhookConfiguration, TLS certificates), enable the feature gate by patching the container args: ```bash -kubectl set env deployment/splunk-operator-controller-manager \ - ENABLE_VALIDATION_WEBHOOK=true -n splunk-operator +kubectl patch deployment splunk-operator-controller-manager -n splunk-operator \ + --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=ValidationWebhook=true"}]' ``` -**Note:** This option also requires the webhook service, ValidatingWebhookConfiguration, and TLS certificates to be deployed. Use Option 1 for a complete deployment. +**Note:** This requires the webhook service, ValidatingWebhookConfiguration, and TLS certificates to already be deployed. Use Option 1 for a complete deployment. #### Option 3: Modify Default Kustomization @@ -119,6 +119,14 @@ Then deploy: make deploy IMG= SPLUNK_GENERAL_TERMS="--accept-sgt-current-at-splunk-com" ``` +### Legacy: ENABLE_VALIDATION_WEBHOOK Environment Variable + +> **Deprecated:** The `ENABLE_VALIDATION_WEBHOOK` environment variable is deprecated and will be removed in a future release. Use the `--feature-gates=ValidationWebhook=true` flag instead. + +For backwards compatibility, setting `ENABLE_VALIDATION_WEBHOOK=true` as an environment variable on the operator container will still enable the validation webhook. The operator logs a deprecation warning when this method is used. + +When both the `--feature-gates=ValidationWebhook=...` CLI flag and the `ENABLE_VALIDATION_WEBHOOK` env var are set, the **CLI flag takes precedence**. The env var is applied at startup before flag parsing, so the CLI value overwrites it. + ## Validated Fields The webhook validates the following spec fields: @@ -290,7 +298,7 @@ kubectl get validatingwebhookconfiguration splunk-operator-validating-webhook-co ```bash kubectl logs -n splunk-operator deployment/splunk-operator-controller-manager | grep -i webhook -# Look for: "Validation webhook enabled via ENABLE_VALIDATION_WEBHOOK=true" +# Look for: "Validation webhook enabled via feature gate" # Look for: "Starting webhook server" {"port": 9443} ``` @@ -362,7 +370,7 @@ kubectl logs -n splunk-operator deployment/splunk-operator-controller-manager | If you see "Validation webhook disabled" in the logs, ensure: -1. The `ENABLE_VALIDATION_WEBHOOK` environment variable is set to `true` +1. The `--feature-gates=ValidationWebhook=true` flag is set on the operator container args (or the legacy `ENABLE_VALIDATION_WEBHOOK=true` env var is set) 2. You're using the correct kustomize overlay (`config/default-with-webhook`) ## Architecture @@ -457,7 +465,7 @@ var GVR = schema.GroupVersionResource{ // Add to DefaultValidators map var DefaultValidators = map[schema.GroupVersionResource]Validator{ // ... existing validators ... - + GVR: &GenericValidator[*enterpriseApi.]{ ValidateCreateFunc: ValidateCreate, ValidateUpdateFunc: ValidateUpdate, @@ -503,12 +511,7 @@ If your CRD doesn't need context-aware validation, you can omit `ValidateCreateW ## Disabling the Webhook -To disable the webhook after it has been enabled: - -```bash -kubectl set env deployment/splunk-operator-controller-manager \ - ENABLE_VALIDATION_WEBHOOK=false -n splunk-operator -``` +To disable the webhook after it has been enabled, remove the `--feature-gates=ValidationWebhook=true` flag from the container args (or remove the `ENABLE_VALIDATION_WEBHOOK` env var if using the legacy method). Or redeploy using the default kustomization (without webhook): diff --git a/go.mod b/go.mod index 8b5adb30c..0ff959e8b 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/onsi/gomega v1.39.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.22.0 + github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.11.1 github.com/wk8/go-ordered-map/v2 v2.1.7 go.uber.org/zap v1.27.0 @@ -31,6 +32,7 @@ require ( k8s.io/apiextensions-apiserver v0.33.0 k8s.io/apimachinery v0.33.0 k8s.io/client-go v0.33.0 + k8s.io/component-base v0.33.0 k8s.io/kubectl v0.26.2 sigs.k8s.io/controller-runtime v0.21.0 ) @@ -113,7 +115,6 @@ require ( github.com/rs/xid v1.2.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -152,7 +153,6 @@ require ( gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiserver v0.33.0 // indirect - k8s.io/component-base v0.33.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect diff --git a/pkg/config/featuregates.go b/pkg/config/featuregates.go new file mode 100644 index 000000000..934807511 --- /dev/null +++ b/pkg/config/featuregates.go @@ -0,0 +1,67 @@ +// Copyright (c) 2018-2026 Splunk Inc. All rights reserved. + +// +// 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 config + +import ( + "fmt" + "os" + + "k8s.io/component-base/featuregate" +) + +// Feature gate constants. Add new feature gates here following the checklist +// in docs/FeatureGates.md. +// +// Lifecycle: +// +// Alpha – off by default, opt-in via --feature-gates==true +// Beta – on by default, opt-out via --feature-gates==false +// GA – on, locked; remove the gate in a subsequent release +const ( + // ValidationWebhook gates the centralized validation webhook server. + // When enabled, the operator runs a validating webhook that enforces + // CR schema rules at admission time. + // Replaces the legacy ENABLE_VALIDATION_WEBHOOK env var. + ValidationWebhook featuregate.Feature = "ValidationWebhook" +) + +// defaultFeatureGates is the authoritative registry of all feature gates and +// their default state / maturity. Each entry here automatically becomes +// available via --feature-gates on the operator binary. +var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ + ValidationWebhook: {Default: false, PreRelease: featuregate.Alpha}, +} + +var DefaultMutableFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate() + +func init() { + if err := DefaultMutableFeatureGate.Add(defaultFeatureGates); err != nil { + panic(err) + } + applyLegacyValidationWebhookEnv(DefaultMutableFeatureGate) +} + +// applyLegacyValidationWebhookEnv preserves backwards compatibility for +// deployments using the ENABLE_VALIDATION_WEBHOOK env var instead of +// --feature-gates=ValidationWebhook=true. +// Remove once the ENABLE_VALIDATION_WEBHOOK deprecation period ends. +func applyLegacyValidationWebhookEnv(fg featuregate.MutableFeatureGate) { + if os.Getenv("ENABLE_VALIDATION_WEBHOOK") == "true" { + if err := fg.SetFromMap(map[string]bool{string(ValidationWebhook): true}); err != nil { + fmt.Fprintf(os.Stderr, "WARNING: failed to apply legacy env var ENABLE_VALIDATION_WEBHOOK: %v\n", err) + } + } +} diff --git a/pkg/config/featuregates_test.go b/pkg/config/featuregates_test.go new file mode 100644 index 000000000..03f9d9779 --- /dev/null +++ b/pkg/config/featuregates_test.go @@ -0,0 +1,91 @@ +// Copyright (c) 2018-2026 Splunk Inc. All rights reserved. + +// +// 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 config + +import ( + "os" + "testing" + + "k8s.io/component-base/featuregate" +) + +func TestValidationWebhookRegistered(t *testing.T) { + all := DefaultMutableFeatureGate.GetAll() + spec, ok := all[ValidationWebhook] + if !ok { + t.Fatal("ValidationWebhook gate not registered") + } + if spec.Default != false { + t.Errorf("ValidationWebhook default: got %v, want false", spec.Default) + } + if spec.PreRelease != featuregate.Alpha { + t.Errorf("ValidationWebhook prerelease: got %v, want Alpha", spec.PreRelease) + } +} + +func TestValidationWebhookOffByDefault(t *testing.T) { + if DefaultMutableFeatureGate.Enabled(ValidationWebhook) { + t.Error("ValidationWebhook should be disabled by default (Alpha)") + } +} + +func TestLegacyEnvVarEnablesGate(t *testing.T) { + fg := featuregate.NewFeatureGate() + if err := fg.Add(map[featuregate.Feature]featuregate.FeatureSpec{ + ValidationWebhook: {Default: false, PreRelease: featuregate.Alpha}, + }); err != nil { + t.Fatalf("Add: %v", err) + } + + t.Setenv("ENABLE_VALIDATION_WEBHOOK", "true") + applyLegacyValidationWebhookEnv(fg) + + if !fg.Enabled(ValidationWebhook) { + t.Error("ValidationWebhook should be enabled when ENABLE_VALIDATION_WEBHOOK=true") + } +} + +func TestLegacyEnvVarIgnoredWhenUnset(t *testing.T) { + fg := featuregate.NewFeatureGate() + if err := fg.Add(map[featuregate.Feature]featuregate.FeatureSpec{ + ValidationWebhook: {Default: false, PreRelease: featuregate.Alpha}, + }); err != nil { + t.Fatalf("Add: %v", err) + } + + os.Unsetenv("ENABLE_VALIDATION_WEBHOOK") + applyLegacyValidationWebhookEnv(fg) + + if fg.Enabled(ValidationWebhook) { + t.Error("ValidationWebhook should remain disabled when env var is not set") + } +} + +func TestLegacyEnvVarIgnoredWhenNotTrue(t *testing.T) { + fg := featuregate.NewFeatureGate() + if err := fg.Add(map[featuregate.Feature]featuregate.FeatureSpec{ + ValidationWebhook: {Default: false, PreRelease: featuregate.Alpha}, + }); err != nil { + t.Fatalf("Add: %v", err) + } + + t.Setenv("ENABLE_VALIDATION_WEBHOOK", "false") + applyLegacyValidationWebhookEnv(fg) + + if fg.Enabled(ValidationWebhook) { + t.Error("ValidationWebhook should remain disabled when ENABLE_VALIDATION_WEBHOOK=false") + } +} diff --git a/pkg/splunk/enterprise/telemetry.go b/pkg/splunk/enterprise/telemetry.go index a095e1e6e..0339d43f1 100644 --- a/pkg/splunk/enterprise/telemetry.go +++ b/pkg/splunk/enterprise/telemetry.go @@ -5,6 +5,8 @@ import ( "encoding/json" "errors" "fmt" + "time" + enterpriseApiV3 "github.com/splunk/splunk-operator/api/v3" enterpriseApi "github.com/splunk/splunk-operator/api/v4" splclient "github.com/splunk/splunk-operator/pkg/splunk/client" @@ -13,7 +15,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "time" corev1 "k8s.io/api/core/v1" )