diff --git a/apis/core/v1alpha1/featureflagconfiguration_types.go b/apis/core/v1alpha1/featureflagconfiguration_types.go index ecec1ba6e..a699aee91 100644 --- a/apis/core/v1alpha1/featureflagconfiguration_types.go +++ b/apis/core/v1alpha1/featureflagconfiguration_types.go @@ -53,7 +53,7 @@ type FlagDSpec struct { } type FeatureFlagSyncProvider struct { - Name string `json:"name"` + Name SyncProviderType `json:"name"` // +optional // +nullable HttpSyncConfiguration *HttpSyncConfiguration `json:"httpSyncConfiguration"` @@ -67,18 +67,6 @@ type HttpSyncConfiguration struct { BearerToken string `json:"bearerToken,omitempty"` } -func (ffsp FeatureFlagSyncProvider) IsKubernetes() bool { - return ffsp.Name == "kubernetes" -} - -func (ffsp FeatureFlagSyncProvider) IsHttp() bool { - return ffsp.Name == "http" -} - -func (ffsp FeatureFlagSyncProvider) IsFilepath() bool { - return ffsp.Name == "filepath" -} - type FeatureFlagServiceProvider struct { // +kubebuilder:validation:Enum=flagd Name string `json:"name"` diff --git a/apis/core/v1alpha1/flagsourceconfiguration_types.go b/apis/core/v1alpha1/flagsourceconfiguration_types.go index 69e887405..eeec80999 100644 --- a/apis/core/v1alpha1/flagsourceconfiguration_types.go +++ b/apis/core/v1alpha1/flagsourceconfiguration_types.go @@ -26,23 +26,30 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +type SyncProviderType string + const ( - SidecarEnvVarPrefix string = "SIDECAR_ENV_VAR_PREFIX" - SidecarMetricPortEnvVar string = "METRICS_PORT" - SidecarPortEnvVar string = "PORT" - SidecarSocketPathEnvVar string = "SOCKET_PATH" - SidecarEvaluatorEnvVar string = "EVALUATOR" - SidecarImageEnvVar string = "IMAGE" - SidecarVersionEnvVar string = "TAG" - SidecarProviderArgsEnvVar string = "PROVIDER_ARGS" - defaultSidecarEnvVarPrefix string = "FLAGD" - InputConfigurationEnvVarPrefix string = "SIDECAR" - defaultMetricPort int32 = 8014 - defaultPort int32 = 8013 - defaultSocketPath string = "" - defaultEvaluator string = "json" - defaultImage string = "ghcr.io/open-feature/flagd" - defaultTag string = "v0.3.1" + SidecarEnvVarPrefix string = "SIDECAR_ENV_VAR_PREFIX" + SidecarMetricPortEnvVar string = "METRICS_PORT" + SidecarPortEnvVar string = "PORT" + SidecarSocketPathEnvVar string = "SOCKET_PATH" + SidecarEvaluatorEnvVar string = "EVALUATOR" + SidecarImageEnvVar string = "IMAGE" + SidecarVersionEnvVar string = "TAG" + SidecarProviderArgsEnvVar string = "PROVIDER_ARGS" + SidecarDefaultSyncProviderEnvVar string = "SYNC_PROVIDER" + defaultSidecarEnvVarPrefix string = "FLAGD" + InputConfigurationEnvVarPrefix string = "SIDECAR" + defaultMetricPort int32 = 8014 + defaultPort int32 = 8013 + defaultSocketPath string = "" + defaultEvaluator string = "json" + defaultImage string = "ghcr.io/open-feature/flagd" + defaultTag string = "v0.3.1" + SyncProviderKubernetes SyncProviderType = "kubernetes" + SyncProviderFilepath SyncProviderType = "filepath" + SyncProviderHttp SyncProviderType = "http" + defaultSyncProvider = SyncProviderKubernetes ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! @@ -80,17 +87,22 @@ type FlagSourceConfigurationSpec struct { // Tag to be appended to the sidecar image, defaults to 'main' // +optional Tag string `json:"tag"` + + // DefaultSyncProvider defines the default sync provider + // +optional + DefaultSyncProvider SyncProviderType `json:"defaultSyncProvider"` } func NewFlagSourceConfigurationSpec() (*FlagSourceConfigurationSpec, error) { fsc := &FlagSourceConfigurationSpec{ - MetricsPort: defaultMetricPort, - Port: defaultPort, - SocketPath: defaultSocketPath, - SyncProviderArgs: []string{}, - Evaluator: defaultEvaluator, - Image: defaultImage, - Tag: defaultTag, + MetricsPort: defaultMetricPort, + Port: defaultPort, + SocketPath: defaultSocketPath, + SyncProviderArgs: []string{}, + Evaluator: defaultEvaluator, + Image: defaultImage, + Tag: defaultTag, + DefaultSyncProvider: SyncProviderKubernetes, } if metricsPort := os.Getenv(fmt.Sprintf("%s_%s", InputConfigurationEnvVarPrefix, SidecarMetricPortEnvVar)); metricsPort != "" { @@ -129,6 +141,10 @@ func NewFlagSourceConfigurationSpec() (*FlagSourceConfigurationSpec, error) { fsc.SyncProviderArgs = strings.Split(syncProviderArgs, ",") // todo: add documentation for this } + if syncProvider := os.Getenv(fmt.Sprintf("%s_%s", InputConfigurationEnvVarPrefix, SidecarDefaultSyncProviderEnvVar)); syncProvider != "" { + fsc.DefaultSyncProvider = SyncProviderType(syncProvider) + } + return fsc, nil } @@ -157,6 +173,9 @@ func (fc *FlagSourceConfigurationSpec) Merge(new *FlagSourceConfigurationSpec) { if new.SyncProviderArgs != nil && len(new.SyncProviderArgs) > 0 { fc.SyncProviderArgs = append(fc.SyncProviderArgs, new.SyncProviderArgs...) } + if new.DefaultSyncProvider != "" { + fc.DefaultSyncProvider = new.DefaultSyncProvider + } } func (fc *FlagSourceConfigurationSpec) ToEnvVars() []corev1.EnvVar { @@ -229,3 +248,15 @@ type FlagSourceConfigurationList struct { func init() { SchemeBuilder.Register(&FlagSourceConfiguration{}, &FlagSourceConfigurationList{}) } + +func (s SyncProviderType) IsKubernetes() bool { + return s == SyncProviderKubernetes +} + +func (s SyncProviderType) IsHttp() bool { + return s == SyncProviderHttp +} + +func (s SyncProviderType) IsFilepath() bool { + return s == SyncProviderFilepath +} diff --git a/apis/core/v1alpha2/featureflagconfiguration_conversion.go b/apis/core/v1alpha2/featureflagconfiguration_conversion.go index dc32ef277..3e5701f2c 100644 --- a/apis/core/v1alpha2/featureflagconfiguration_conversion.go +++ b/apis/core/v1alpha2/featureflagconfiguration_conversion.go @@ -43,7 +43,7 @@ func (src *FeatureFlagConfiguration) ConvertTo(dstRaw conversion.Hub) error { } if src.Spec.SyncProvider != nil { - dst.Spec.SyncProvider = &v1alpha1.FeatureFlagSyncProvider{Name: src.Spec.SyncProvider.Name} + dst.Spec.SyncProvider = &v1alpha1.FeatureFlagSyncProvider{Name: v1alpha1.SyncProviderType(src.Spec.SyncProvider.Name)} if src.Spec.SyncProvider.HttpSyncConfiguration != nil { dst.Spec.SyncProvider.HttpSyncConfiguration = &v1alpha1.HttpSyncConfiguration{ Target: src.Spec.SyncProvider.HttpSyncConfiguration.Target, @@ -79,7 +79,7 @@ func (dst *FeatureFlagConfiguration) ConvertFrom(srcRaw conversion.Hub) error { if src.Spec.SyncProvider != nil { dst.Spec.SyncProvider = &FeatureFlagSyncProvider{ - Name: src.Spec.SyncProvider.Name, + Name: string(src.Spec.SyncProvider.Name), } if src.Spec.SyncProvider.HttpSyncConfiguration != nil { dst.Spec.SyncProvider.HttpSyncConfiguration = &HttpSyncConfiguration{ diff --git a/apis/core/v1alpha2/flagsourceconfiguration_conversion.go b/apis/core/v1alpha2/flagsourceconfiguration_conversion.go index 5dd6f98b5..e0b7fcb62 100644 --- a/apis/core/v1alpha2/flagsourceconfiguration_conversion.go +++ b/apis/core/v1alpha2/flagsourceconfiguration_conversion.go @@ -33,13 +33,14 @@ func (src *FlagSourceConfiguration) ConvertTo(dstRaw conversion.Hub) error { dst.ObjectMeta = src.ObjectMeta dst.Spec = v1alpha1.FlagSourceConfigurationSpec{ - MetricsPort: src.Spec.MetricsPort, - Port: src.Spec.Port, - SocketPath: src.Spec.SocketPath, - SyncProviderArgs: src.Spec.SyncProviderArgs, - Evaluator: src.Spec.Evaluator, - Image: src.Spec.Image, - Tag: src.Spec.Tag, + MetricsPort: src.Spec.MetricsPort, + Port: src.Spec.Port, + SocketPath: src.Spec.SocketPath, + SyncProviderArgs: src.Spec.SyncProviderArgs, + Evaluator: src.Spec.Evaluator, + Image: src.Spec.Image, + Tag: src.Spec.Tag, + DefaultSyncProvider: v1alpha1.SyncProviderType(src.Spec.DefaultSyncProvider), } return nil } @@ -49,13 +50,14 @@ func (dst *FlagSourceConfiguration) ConvertFrom(srcRaw conversion.Hub) error { dst.ObjectMeta = src.ObjectMeta dst.Spec = FlagSourceConfigurationSpec{ - MetricsPort: src.Spec.MetricsPort, - Port: src.Spec.Port, - SocketPath: src.Spec.SocketPath, - SyncProviderArgs: src.Spec.SyncProviderArgs, - Evaluator: src.Spec.Evaluator, - Image: src.Spec.Image, - Tag: src.Spec.Tag, + MetricsPort: src.Spec.MetricsPort, + Port: src.Spec.Port, + SocketPath: src.Spec.SocketPath, + SyncProviderArgs: src.Spec.SyncProviderArgs, + Evaluator: src.Spec.Evaluator, + Image: src.Spec.Image, + Tag: src.Spec.Tag, + DefaultSyncProvider: string(src.Spec.DefaultSyncProvider), } return nil } diff --git a/apis/core/v1alpha2/flagsourceconfiguration_types.go b/apis/core/v1alpha2/flagsourceconfiguration_types.go index 8f3bdbf7f..dd7189e50 100644 --- a/apis/core/v1alpha2/flagsourceconfiguration_types.go +++ b/apis/core/v1alpha2/flagsourceconfiguration_types.go @@ -55,6 +55,10 @@ type FlagSourceConfigurationSpec struct { // Tag to be appended to the sidecar image, defaults to 'main' // +optional Tag string `json:"tag"` + + // DefaultSyncProvider defines the default sync provider + // +optional + DefaultSyncProvider string `json:"defaultSyncProvider"` } // FlagSourceConfigurationStatus defines the observed state of FlagSourceConfiguration diff --git a/chart/open-feature-operator/README.md b/chart/open-feature-operator/README.md index 3072d034c..c04dc0990 100644 --- a/chart/open-feature-operator/README.md +++ b/chart/open-feature-operator/README.md @@ -49,9 +49,10 @@ The command removes all the Kubernetes components associated with the chart and | `sidecarConfiguration.port` | 8013 | Sets the value of the `XXX_PORT` environment variable for the injected sidecar container.| | `sidecarConfiguration.metricsPort` | 8014 | Sets the value of the `XXX_METRICS_PORT` environment variable for the injected sidecar container.| | `sidecarConfiguration.socketPath` | `""` | Sets the value of the `XXX_SOCKET_PATH` environment variable for the injected sidecar container.| -| `sidecarConfiguration.repository` | `ghcr.io/open-feature/flagd` | Sets the image for the injected sidecar container. | -| `sidecarConfiguration.tag` | current flagd version: `v0.3.2` | Sets the version tag for the injected sidecar container. | +| `sidecarConfiguration.image.repository` | `ghcr.io/open-feature/flagd` | Sets the image for the injected sidecar container. | +| `sidecarConfiguration.image.tag` | current flagd version: `v0.3.2` | Sets the version tag for the injected sidecar container. | | `sidecarConfiguration.providerArgs` | `""` | Used to append arguments to the sidecar startup command. This value is a comma separated string of key values separated by '=', e.g. `key=value,key2=value2` results in the appending of `--sync-provider-args key=value --sync-provider-args key2=value2` | +| `sidecarConfiguration.defaultSyncProvider` | `kubernetes` | Sets the value of the `XXX_SYNC_PROVIDER` environment variable for the injected sidecar container. There are 3 valid sync providers: `kubernetes`, `filepath` and `http` | ### Operator resource configuration diff --git a/chart/open-feature-operator/templates/rendered.yaml b/chart/open-feature-operator/templates/rendered.yaml index 8736f238a..914756f65 100644 --- a/chart/open-feature-operator/templates/rendered.yaml +++ b/chart/open-feature-operator/templates/rendered.yaml @@ -607,6 +607,9 @@ spec: description: FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration properties: + defaultSyncProvider: + description: DefaultSyncProvider defines the default sync provider + type: string evaluator: description: Evaluator sets an evaluator, defaults to 'json' type: string @@ -668,6 +671,9 @@ spec: description: FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration properties: + defaultSyncProvider: + description: DefaultSyncProvider defines the default sync provider + type: string evaluator: description: Evaluator sets an evaluator, defaults to 'json' type: string @@ -1035,6 +1041,8 @@ spec: value: '{{ .Values.sidecarConfiguration.providerArgs }}' - name: SIDECAR_ENV_VAR_PREFIX value: '{{ .Values.sidecarConfiguration.envVarPrefix }}' + - name: SIDECAR_SYNC_PROVIDER + value: '{{ .Values.sidecarConfiguration.defaultSyncProvider }}' image: '{{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag }}' imagePullPolicy: IfNotPresent diff --git a/chart/open-feature-operator/values.yaml b/chart/open-feature-operator/values.yaml index 073248a70..78e9b5bda 100644 --- a/chart/open-feature-operator/values.yaml +++ b/chart/open-feature-operator/values.yaml @@ -11,6 +11,7 @@ sidecarConfiguration: tag: v0.3.2 providerArgs: "" envVarPrefix: "FLAGD" + defaultSyncProvider: kubernetes controllerManager: kubeRbacProxy: diff --git a/config/crd/bases/core.openfeature.dev_flagsourceconfigurations.yaml b/config/crd/bases/core.openfeature.dev_flagsourceconfigurations.yaml index 06a19ead6..d643881d6 100644 --- a/config/crd/bases/core.openfeature.dev_flagsourceconfigurations.yaml +++ b/config/crd/bases/core.openfeature.dev_flagsourceconfigurations.yaml @@ -39,6 +39,9 @@ spec: description: FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration properties: + defaultSyncProvider: + description: DefaultSyncProvider defines the default sync provider + type: string evaluator: description: Evaluator sets an evaluator, defaults to 'json' type: string @@ -100,6 +103,9 @@ spec: description: FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration properties: + defaultSyncProvider: + description: DefaultSyncProvider defines the default sync provider + type: string evaluator: description: Evaluator sets an evaluator, defaults to 'json' type: string diff --git a/config/overlays/helm/manager.yaml b/config/overlays/helm/manager.yaml index 4c0171761..255227425 100644 --- a/config/overlays/helm/manager.yaml +++ b/config/overlays/helm/manager.yaml @@ -32,6 +32,8 @@ spec: value: "{{ .Values.sidecarConfiguration.providerArgs }}" - name: SIDECAR_ENV_VAR_PREFIX value: "{{ .Values.sidecarConfiguration.envVarPrefix }}" + - name: SIDECAR_SYNC_PROVIDER + value: "{{ .Values.sidecarConfiguration.defaultSyncProvider }}" - name: kube-rbac-proxy image: "{{ .Values.controllerManager.kubeRbacProxy.image.repository }}:{{ .Values.controllerManager.kubeRbacProxy.image.tag }}" resources: diff --git a/webhooks/pod_webhook.go b/webhooks/pod_webhook.go index 1807cbf4c..3011b2148 100644 --- a/webhooks/pod_webhook.go +++ b/webhooks/pod_webhook.go @@ -171,7 +171,12 @@ func (m *PodMutator) Handle(ctx context.Context, req admission.Request) admissio m.Log.V(1).Info(fmt.Sprintf("FeatureFlagConfiguration could not be found for %s", ffName)) return admission.Errored(http.StatusBadRequest, err) } - if ff.Spec.SyncProvider != nil && !ff.Spec.SyncProvider.IsKubernetes() { + if ff.Spec.SyncProvider == nil || ff.Spec.SyncProvider.Name == "" { + ff.Spec.SyncProvider = &corev1alpha1.FeatureFlagSyncProvider{ + Name: flagSourceConfigurationSpec.DefaultSyncProvider, + } + } + if !ff.Spec.SyncProvider.Name.IsKubernetes() { // Check for ConfigMap and create it if it doesn't exist (only required if sync provider isn't kubernetes) cm := corev1.ConfigMap{} if err := m.Client.Get(ctx, client.ObjectKey{Name: name, Namespace: req.Namespace}, &cm); errors.IsNotFound(err) { @@ -343,7 +348,7 @@ func (m *PodMutator) injectSidecar( } switch { // kubernetes sync is the default state - case featureFlag.Spec.SyncProvider == nil || featureFlag.Spec.SyncProvider.IsKubernetes(): + case featureFlag.Spec.SyncProvider == nil || featureFlag.Spec.SyncProvider.Name.IsKubernetes(): m.Log.V(1).Info(fmt.Sprintf("FeatureFlagConfiguration %s using kubernetes sync implementation", featureFlag.Name)) commandSequence = append( commandSequence, @@ -355,7 +360,7 @@ func (m *PodMutator) injectSidecar( ), ) // if http is explicitly set - case featureFlag.Spec.SyncProvider.IsHttp(): + case featureFlag.Spec.SyncProvider.Name.IsHttp(): m.Log.V(1).Info(fmt.Sprintf("FeatureFlagConfiguration %s using http sync implementation", featureFlag.Name)) if featureFlag.Spec.SyncProvider.HttpSyncConfiguration != nil { commandSequence = append( @@ -375,7 +380,7 @@ func (m *PodMutator) injectSidecar( m.Log.V(1).Error(err, "unable to add http sync provider") } // if filepath is explicitly set - case featureFlag.Spec.SyncProvider.IsFilepath(): + case featureFlag.Spec.SyncProvider.Name.IsFilepath(): m.Log.V(1).Info(fmt.Sprintf("FeatureFlagConfiguration %s using filepath sync implementation", featureFlag.Name)) commandSequence = append( commandSequence, diff --git a/webhooks/pod_webhook_test.go b/webhooks/pod_webhook_test.go index 2381baba9..23b92060a 100644 --- a/webhooks/pod_webhook_test.go +++ b/webhooks/pod_webhook_test.go @@ -434,10 +434,12 @@ var _ = Describe("pod mutation webhook", func() { os.Setenv(fmt.Sprintf("%s_%s", corev1alpha1.InputConfigurationEnvVarPrefix, corev1alpha1.SidecarEvaluatorEnvVar), "evaluator") os.Setenv(fmt.Sprintf("%s_%s", corev1alpha1.InputConfigurationEnvVarPrefix, corev1alpha1.SidecarImageEnvVar), "image") os.Setenv(fmt.Sprintf("%s_%s", corev1alpha1.InputConfigurationEnvVarPrefix, corev1alpha1.SidecarVersionEnvVar), "version") + os.Setenv(fmt.Sprintf("%s_%s", corev1alpha1.InputConfigurationEnvVarPrefix, corev1alpha1.SidecarDefaultSyncProviderEnvVar), "filepath") os.Setenv(fmt.Sprintf("%s_%s", corev1alpha1.InputConfigurationEnvVarPrefix, corev1alpha1.SidecarProviderArgsEnvVar), "key=value,key2=value2") pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - "openfeature.dev": "enabled", + "openfeature.dev": "enabled", + "openfeature.dev/featureflagconfiguration": fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), }) err := k8sClient.Create(testCtx, pod) Expect(err).ShouldNot(HaveOccurred()) @@ -453,6 +455,8 @@ var _ = Describe("pod mutation webhook", func() { Expect(pod.Spec.Containers[1].Image).To(Equal("image:version")) Expect(pod.Spec.Containers[1].Args).To(Equal([]string{ "start", + "--uri", + "file:/etc/flagd/test-feature-flag-configuration.json", "--sync-provider-args", "key=value", "--sync-provider-args", @@ -469,6 +473,7 @@ var _ = Describe("pod mutation webhook", func() { os.Setenv(fmt.Sprintf("%s_%s", corev1alpha1.InputConfigurationEnvVarPrefix, corev1alpha1.SidecarEvaluatorEnvVar), "") os.Setenv(fmt.Sprintf("%s_%s", corev1alpha1.InputConfigurationEnvVarPrefix, corev1alpha1.SidecarImageEnvVar), "") os.Setenv(fmt.Sprintf("%s_%s", corev1alpha1.InputConfigurationEnvVarPrefix, corev1alpha1.SidecarVersionEnvVar), "") + os.Setenv(fmt.Sprintf("%s_%s", corev1alpha1.InputConfigurationEnvVarPrefix, corev1alpha1.SidecarDefaultSyncProviderEnvVar), "") os.Setenv(fmt.Sprintf("%s_%s", corev1alpha1.InputConfigurationEnvVarPrefix, corev1alpha1.SidecarProviderArgsEnvVar), "key=value,key2=value2") pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{