diff --git a/.github/scripts/create-reports.sh b/.github/scripts/create-reports.sh index 5b71a57a0..ca6840273 100755 --- a/.github/scripts/create-reports.sh +++ b/.github/scripts/create-reports.sh @@ -31,7 +31,7 @@ for namespace in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name} createResourceReport "$logsDir/$namespace" "$namespace" "Daemonsets" false createResourceReport "$logsDir/$namespace" "$namespace" "Statefulsets" false createResourceReport "$logsDir/$namespace" "$namespace" "Jobs" false - createResourceReport "$logsDir/$namespace" "$namespace" "FeatureFlagConfiguration" false - createResourceReport "$logsDir/$namespace" "$namespace" "FlagSourceConfiguration" false + createResourceReport "$logsDir/$namespace" "$namespace" "FeatureFlag" false + createResourceReport "$logsDir/$namespace" "$namespace" "FeatureFlagSource" false done diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 434615a07..eed3e37cd 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -141,12 +141,12 @@ jobs: IMG=open-feature-operator-local:${{ github.sha }} make deploy-operator IMG=open-feature-operator-local:${{ github.sha }} make e2e-test-kuttl - name: Create reports - if: always() + if: failure() working-directory: ./.github/scripts run: ./create-reports.sh - name: Upload cluster logs - if: always() + if: failure() uses: actions/upload-artifact@v3 with: name: e2e-tests diff --git a/.gitignore b/.gitignore index cfd1841c7..6b323cd6b 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ testbin/* *.swo *~ +go.work +go.work.sum diff --git a/Dockerfile b/Dockerfile index 05dc2c319..39902ae41 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ COPY main.go main.go COPY apis/ apis/ COPY webhooks/ webhooks/ COPY controllers/ controllers/ -COPY pkg/ pkg/ +COPY common/ common/ ARG TARGETOS ARG TARGETARCH diff --git a/chart/open-feature-operator/README.md b/chart/open-feature-operator/README.md index cbd97ec25..c63554cb3 100644 --- a/chart/open-feature-operator/README.md +++ b/chart/open-feature-operator/README.md @@ -54,16 +54,16 @@ OpenFeature Operator's CRDs are templated, and can be updated apart from the ope helm template openfeature/open-feature-operator -s templates/{CRD} | kubectl apply -f - ``` -For the `featureflagconfigurations.core.openfeature.dev` CRD: +For the `featureflags.core.openfeature.dev` CRD: ```sh -helm template openfeature/open-feature-operator -s templates/apiextensions.k8s.io_v1_customresourcedefinition_featureflagconfigurations.core.openfeature.dev.yaml | kubectl apply -f - +helm template openfeature/open-feature-operator -s templates/apiextensions.k8s.io_v1_customresourcedefinition_featureflags.core.openfeature.dev.yaml | kubectl apply -f - ``` -For the `flagsourceconfigurations.core.openfeature.dev` CRD: +For the `featureflagsources.core.openfeature.dev` CRD: ```sh -helm template openfeature/open-feature-operator -s templates/apiextensions.k8s.io_v1_customresourcedefinition_flagsourceconfigurations.core.openfeature.dev.yaml | kubectl apply -f - +helm template openfeature/open-feature-operator -s templates/apiextensions.k8s.io_v1_customresourcedefinition_featureflagsources.core.openfeature.dev.yaml | kubectl apply -f - ``` Keep in mind, you can set values as usual during this process: @@ -95,10 +95,10 @@ The command removes all the Kubernetes components associated with the chart and | Name | Description | Value | | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | | `sidecarConfiguration.port` | Sets the value of the `XXX_PORT` environment variable for the injected sidecar. | `8013` | -| `sidecarConfiguration.metricsPort` | Sets the value of the `XXX_METRICS_PORT` environment variable for the injected sidecar. | `8014` | +| `sidecarConfiguration.managementPort` | Sets the value of the `XXX_MANAGEMENT_PORT` environment variable for the injected sidecar. | `8014` | | `sidecarConfiguration.socketPath` | Sets the value of the `XXX_SOCKET_PATH` environment variable for the injected sidecar. | `""` | | `sidecarConfiguration.image.repository` | Sets the image for the injected sidecar. | `ghcr.io/open-feature/flagd` | -| `sidecarConfiguration.image.tag` | Sets the version tag for the injected sidecar. | `v0.6.3` | +| `sidecarConfiguration.image.tag` | Sets the version tag for the injected sidecar. | `v0.7.0` | | `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.envVarPrefix` | Sets the prefix for all environment variables set in the injected sidecar. | `FLAGD` | | `sidecarConfiguration.defaultSyncProvider` | Sets the value of the `XXX_SYNC_PROVIDER` environment variable for the injected sidecar container. There are 4 valid sync providers: `kubernetes`, `grpc`, `filepath` and `http`. | `kubernetes` | @@ -114,7 +114,7 @@ The command removes all the Kubernetes components associated with the chart and | Name | Description | Value | | ------------------------------------------ | ------------------------------------------------------------------------------- | ---------------------------------- | | `flagdProxyConfiguration.port` | Sets the port to expose the sync API on. | `8015` | -| `flagdProxyConfiguration.metricsPort` | Sets the port to expose the metrics API on. | `8016` | +| `flagdProxyConfiguration.managementPort` | Sets the port to expose the management API on. | `8016` | | `flagdProxyConfiguration.image.repository` | Sets the image for the flagd-proxy deployment. | `ghcr.io/open-feature/flagd-proxy` | | `flagdProxyConfiguration.image.tag` | Sets the tag for the flagd-proxy deployment. | `v0.2.8` | | `flagdProxyConfiguration.debugLogging` | Controls the addition of the `--debug` flag to the container startup arguments. | `false` | diff --git a/chart/open-feature-operator/values.yaml b/chart/open-feature-operator/values.yaml index b414a6a3f..6f1174c74 100644 --- a/chart/open-feature-operator/values.yaml +++ b/chart/open-feature-operator/values.yaml @@ -7,8 +7,8 @@ defaultNamespace: open-feature-operator-system sidecarConfiguration: ## @param sidecarConfiguration.port Sets the value of the `XXX_PORT` environment variable for the injected sidecar. port: 8013 - ## @param sidecarConfiguration.metricsPort Sets the value of the `XXX_METRICS_PORT` environment variable for the injected sidecar. - metricsPort: 8014 + ## @param sidecarConfiguration.managementPort Sets the value of the `XXX_MANAGEMENT_PORT` environment variable for the injected sidecar. + managementPort: 8014 ## @param sidecarConfiguration.socketPath Sets the value of the `XXX_SOCKET_PATH` environment variable for the injected sidecar. socketPath: "" image: @@ -16,7 +16,7 @@ sidecarConfiguration: ## @param sidecarConfiguration.image.repository Sets the image for the injected sidecar. repository: "ghcr.io/open-feature/flagd" ## @param sidecarConfiguration.image.tag Sets the version tag for the injected sidecar. - tag: v0.6.3 + tag: v0.7.0 ## @param 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`. providerArgs: "" ## @param sidecarConfiguration.envVarPrefix Sets the prefix for all environment variables set in the injected sidecar. @@ -40,8 +40,8 @@ sidecarConfiguration: flagdProxyConfiguration: ## @param flagdProxyConfiguration.port Sets the port to expose the sync API on. port: 8015 - ## @param flagdProxyConfiguration.metricsPort Sets the port to expose the metrics API on. - metricsPort: 8016 + ## @param flagdProxyConfiguration.managementPort Sets the port to expose the management API on. + managementPort: 8016 image: ## @param flagdProxyConfiguration.image.repository Sets the image for the flagd-proxy deployment. repository: "ghcr.io/open-feature/flagd-proxy" diff --git a/controllers/common/common.go b/common/common.go similarity index 58% rename from controllers/common/common.go rename to common/common.go index 4ad4e9c97..a6c714919 100644 --- a/controllers/common/common.go +++ b/common/common.go @@ -5,22 +5,22 @@ import ( "fmt" "time" - "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" + api "github.com/open-feature/open-feature-operator/apis/core/v1beta1" appsV1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) const ( - ReconcileErrorInterval = 10 * time.Second - ReconcileSuccessInterval = 120 * time.Second - FinalizerName = "featureflagconfiguration.core.openfeature.dev/finalizer" - OpenFeatureAnnotationPath = "spec.template.metadata.annotations.openfeature.dev/openfeature.dev" - FlagSourceConfigurationAnnotation = "flagsourceconfiguration" - OpenFeatureAnnotationRoot = "openfeature.dev" + ReconcileErrorInterval = 10 * time.Second + ReconcileSuccessInterval = 120 * time.Second + FinalizerName = "featureflag.core.openfeature.dev/finalizer" + OpenFeatureAnnotationPath = "spec.template.metadata.annotations.openfeature.dev/openfeature.dev" + FeatureFlagSourceAnnotation = "featureflagsource" + OpenFeatureAnnotationRoot = "openfeature.dev" ) -func FlagSourceConfigurationIndex(o client.Object) []string { +func FeatureFlagSourceIndex(o client.Object) []string { deployment, ok := o.(*appsV1.Deployment) if !ok { return []string{ @@ -33,7 +33,7 @@ func FlagSourceConfigurationIndex(o client.Object) []string { "false", } } - if _, ok := deployment.Spec.Template.ObjectMeta.Annotations[fmt.Sprintf("openfeature.dev/%s", FlagSourceConfigurationAnnotation)]; ok { + if _, ok := deployment.Spec.Template.ObjectMeta.Annotations[fmt.Sprintf("openfeature.dev/%s", FeatureFlagSourceAnnotation)]; ok { return []string{ "true", } @@ -43,8 +43,8 @@ func FlagSourceConfigurationIndex(o client.Object) []string { } } -func FindFlagConfig(ctx context.Context, c client.Client, namespace string, name string) (*v1alpha1.FeatureFlagConfiguration, error) { - ffConfig := &v1alpha1.FeatureFlagConfiguration{} +func FindFlagConfig(ctx context.Context, c client.Client, namespace string, name string) (*api.FeatureFlag, error) { + ffConfig := &api.FeatureFlag{} if err := c.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, ffConfig); err != nil { return nil, err } diff --git a/controllers/common/common_test.go b/common/common_test.go similarity index 56% rename from controllers/common/common_test.go rename to common/common_test.go index 8422141b8..155955fbb 100644 --- a/controllers/common/common_test.go +++ b/common/common_test.go @@ -1,17 +1,21 @@ package common import ( + "context" "fmt" "testing" + api "github.com/open-feature/open-feature-operator/apis/core/v1beta1" "github.com/stretchr/testify/require" appsV1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" ) -func TestFlagSourceConfigurationIndex(t *testing.T) { +func TestFeatureFlagSourceIndex(t *testing.T) { tests := []struct { name string obj client.Object @@ -49,7 +53,7 @@ func TestFlagSourceConfigurationIndex(t *testing.T) { Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - fmt.Sprintf("openfeature.dev/%s", FlagSourceConfigurationAnnotation): "true", + fmt.Sprintf("openfeature.dev/%s", FeatureFlagSourceAnnotation): "true", }, }, }, @@ -61,7 +65,7 @@ func TestFlagSourceConfigurationIndex(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - out := FlagSourceConfigurationIndex(tt.obj) + out := FeatureFlagSourceIndex(tt.obj) require.Equal(t, tt.out, out) }) @@ -102,8 +106,60 @@ func TestSharedOwnership(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := SharedOwnership(tt.owner1, tt.owner2); got != tt.want { - t.Errorf("podOwnerIsOwner() = %v, want %v", got, tt.want) + t.Errorf("SharedOwnership() = %v, want %v", got, tt.want) } }) } } + +func TestFindFlagConfig(t *testing.T) { + ff := &api.FeatureFlag{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + } + + tests := []struct { + name string + ns string + obj *api.FeatureFlag + want *api.FeatureFlag + wantErr bool + }{ + { + name: "test", + ns: "default", + obj: ff, + want: ff, + wantErr: false, + }, + { + name: "non-existing", + ns: "default", + obj: ff, + want: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := api.AddToScheme(scheme.Scheme) + require.Nil(t, err) + fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.obj).Build() + + got, err := FindFlagConfig(context.TODO(), fakeClient, tt.ns, tt.name) + + if (err != nil) != tt.wantErr { + t.Errorf("FindFlagConfig() = expected error %t, got %v", tt.wantErr, err) + } + + if !tt.wantErr { + require.Equal(t, tt.want.Name, got.Name) + require.Equal(t, tt.want.Namespace, got.Namespace) + } + + }) + } +} diff --git a/controllers/common/constant/configuration.go b/common/constant/configuration.go similarity index 73% rename from controllers/common/constant/configuration.go rename to common/constant/configuration.go index 3201eeb83..b3115d4ea 100644 --- a/controllers/common/constant/configuration.go +++ b/common/constant/configuration.go @@ -7,8 +7,11 @@ const ( ClusterRoleBindingName string = "open-feature-operator-flagd-kubernetes-sync" AllowKubernetesSyncAnnotation = "allowkubernetessync" OpenFeatureAnnotationPrefix = "openfeature.dev" + OpenFeatureAnnotationPath = "metadata.annotations.openfeature.dev" SourceConfigParam = "--sources" ProbeReadiness = "/readyz" ProbeLiveness = "/healthz" ProbeInitialDelay = 5 + FeatureFlagSourceAnnotation = "featureflagsource" + EnabledAnnotation = "enabled" ) diff --git a/controllers/common/constant/errors.go b/common/constant/errors.go similarity index 100% rename from controllers/common/constant/errors.go rename to common/constant/errors.go diff --git a/common/flagdinjector/fake/flagdinjector_mock.go b/common/flagdinjector/fake/flagdinjector_mock.go new file mode 100644 index 000000000..150db2954 --- /dev/null +++ b/common/flagdinjector/fake/flagdinjector_mock.go @@ -0,0 +1,65 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: controllers/common/flagd-injector.go + +// Package commonmock is a generated GoMock package. +package commonmock + +import ( + context "context" + gomock "github.com/golang/mock/gomock" + api "github.com/open-feature/open-feature-operator/apis/core/v1beta1" + v1 "k8s.io/api/core/v1" + v10 "k8s.io/apimachinery/pkg/apis/meta/v1" + reflect "reflect" +) + +// MockFlagdContainerInjector is a mock of IFlagdContainerInjector interface. +type MockFlagdContainerInjector struct { + ctrl *gomock.Controller + recorder *MockFlagdContainerInjectorMockRecorder +} + +// MockFlagdContainerInjectorMockRecorder is the mock recorder for MockFlagdContainerInjector. +type MockFlagdContainerInjectorMockRecorder struct { + mock *MockFlagdContainerInjector +} + +// NewMockFlagdContainerInjector creates a new mock instance. +func NewMockFlagdContainerInjector(ctrl *gomock.Controller) *MockFlagdContainerInjector { + mock := &MockFlagdContainerInjector{ctrl: ctrl} + mock.recorder = &MockFlagdContainerInjectorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFlagdContainerInjector) EXPECT() *MockFlagdContainerInjectorMockRecorder { + return m.recorder +} + +// EnableClusterRoleBinding mocks base method. +func (m *MockFlagdContainerInjector) EnableClusterRoleBinding(ctx context.Context, namespace, serviceAccountName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnableClusterRoleBinding", ctx, namespace, serviceAccountName) + ret0, _ := ret[0].(error) + return ret0 +} + +// EnableClusterRoleBinding indicates an expected call of EnableClusterRoleBinding. +func (mr *MockFlagdContainerInjectorMockRecorder) EnableClusterRoleBinding(ctx, namespace, serviceAccountName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableClusterRoleBinding", reflect.TypeOf((*MockFlagdContainerInjector)(nil).EnableClusterRoleBinding), ctx, namespace, serviceAccountName) +} + +// InjectFlagd mocks base method. +func (m *MockFlagdContainerInjector) InjectFlagd(ctx context.Context, objectMeta *v10.ObjectMeta, podSpec *v1.PodSpec, flagSourceConfig *api.FeatureFlagSourceSpec) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InjectFlagd", ctx, objectMeta, podSpec, flagSourceConfig) + ret0, _ := ret[0].(error) + return ret0 +} + +// InjectFlagd indicates an expected call of InjectFlagd. +func (mr *MockFlagdContainerInjectorMockRecorder) InjectFlagd(ctx, objectMeta, podSpec, flagSourceConfig interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InjectFlagd", reflect.TypeOf((*MockFlagdContainerInjector)(nil).InjectFlagd), ctx, objectMeta, podSpec, flagSourceConfig) +} diff --git a/controllers/common/flagd-injector.go b/common/flagdinjector/flagdinjector.go similarity index 83% rename from controllers/common/flagd-injector.go rename to common/flagdinjector/flagdinjector.go index 3cd732e2c..d315bb00f 100644 --- a/controllers/common/flagd-injector.go +++ b/common/flagdinjector/flagdinjector.go @@ -1,4 +1,4 @@ -package common +package flagdinjector import ( "context" @@ -7,10 +7,13 @@ import ( "time" "github.com/go-logr/logr" - "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - "github.com/open-feature/open-feature-operator/controllers/common/constant" - "github.com/open-feature/open-feature-operator/pkg/types" - "github.com/open-feature/open-feature-operator/pkg/utils" + api "github.com/open-feature/open-feature-operator/apis/core/v1beta1" + apicommon "github.com/open-feature/open-feature-operator/apis/core/v1beta1/common" + "github.com/open-feature/open-feature-operator/common" + "github.com/open-feature/open-feature-operator/common/constant" + "github.com/open-feature/open-feature-operator/common/flagdproxy" + "github.com/open-feature/open-feature-operator/common/types" + "github.com/open-feature/open-feature-operator/common/utils" appsV1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -29,7 +32,7 @@ type IFlagdContainerInjector interface { ctx context.Context, objectMeta *metav1.ObjectMeta, podSpec *corev1.PodSpec, - flagSourceConfig *v1alpha1.FlagSourceConfigurationSpec, + flagSourceConfig *api.FeatureFlagSourceSpec, ) error EnableClusterRoleBinding( @@ -42,7 +45,7 @@ type IFlagdContainerInjector interface { type FlagdContainerInjector struct { Client client.Client Logger logr.Logger - FlagdProxyConfig *FlagdProxyConfiguration + FlagdProxyConfig *flagdproxy.FlagdProxyConfiguration FlagDResourceRequirements corev1.ResourceRequirements } @@ -51,15 +54,15 @@ func (fi *FlagdContainerInjector) InjectFlagd( ctx context.Context, objectMeta *metav1.ObjectMeta, podSpec *corev1.PodSpec, - flagSourceConfig *v1alpha1.FlagSourceConfigurationSpec, + flagSourceConfig *api.FeatureFlagSourceSpec, ) error { fi.Logger.V(1).Info(fmt.Sprintf("creating flagdContainer for pod %s/%s", objectMeta.Namespace, objectMeta.Name)) flagdContainer := fi.generateBasicFlagdContainer(flagSourceConfig) // Enable probes if flagSourceConfig.ProbesEnabled != nil && *flagSourceConfig.ProbesEnabled { - flagdContainer.LivenessProbe = buildProbe(constant.ProbeLiveness, int(flagSourceConfig.MetricsPort)) - flagdContainer.ReadinessProbe = buildProbe(constant.ProbeReadiness, int(flagSourceConfig.MetricsPort)) + flagdContainer.LivenessProbe = buildProbe(constant.ProbeLiveness, int(flagSourceConfig.ManagementPort)) + flagdContainer.ReadinessProbe = buildProbe(constant.ProbeReadiness, int(flagSourceConfig.ManagementPort)) } if err := fi.handleSidecarSources(ctx, objectMeta, podSpec, flagSourceConfig, &flagdContainer); err != nil { @@ -168,7 +171,7 @@ func (fi *FlagdContainerInjector) EnableClusterRoleBinding(ctx context.Context, return nil } -func (fi *FlagdContainerInjector) handleSidecarSources(ctx context.Context, objectMeta *metav1.ObjectMeta, podSpec *corev1.PodSpec, flagSourceConfig *v1alpha1.FlagSourceConfigurationSpec, sidecar *corev1.Container) error { +func (fi *FlagdContainerInjector) handleSidecarSources(ctx context.Context, objectMeta *metav1.ObjectMeta, podSpec *corev1.PodSpec, flagSourceConfig *api.FeatureFlagSourceSpec, sidecar *corev1.Container) error { sources, err := fi.buildSources(ctx, objectMeta, flagSourceConfig, podSpec, sidecar) if err != nil { return err @@ -182,7 +185,7 @@ func (fi *FlagdContainerInjector) handleSidecarSources(ctx context.Context, obje } //nolint:gocyclo -func (fi *FlagdContainerInjector) buildSources(ctx context.Context, objectMeta *metav1.ObjectMeta, flagSourceConfig *v1alpha1.FlagSourceConfigurationSpec, podSpec *corev1.PodSpec, sidecar *corev1.Container) ([]types.SourceConfig, error) { +func (fi *FlagdContainerInjector) buildSources(ctx context.Context, objectMeta *metav1.ObjectMeta, flagSourceConfig *api.FeatureFlagSourceSpec, podSpec *corev1.PodSpec, sidecar *corev1.Container) ([]types.SourceConfig, error) { var sourceCfgCollection []types.SourceConfig for _, source := range flagSourceConfig.Sources { @@ -224,7 +227,7 @@ func (fi *FlagdContainerInjector) buildSources(ctx context.Context, objectMeta * return sourceCfgCollection, nil } -func (fi *FlagdContainerInjector) toFilepathProviderConfig(ctx context.Context, objectMeta *metav1.ObjectMeta, podSpec *corev1.PodSpec, sidecar *corev1.Container, source v1alpha1.Source) (types.SourceConfig, error) { +func (fi *FlagdContainerInjector) toFilepathProviderConfig(ctx context.Context, objectMeta *metav1.ObjectMeta, podSpec *corev1.PodSpec, sidecar *corev1.Container, source api.Source) (types.SourceConfig, error) { // create config map ns, n := utils.ParseAnnotation(source.Source, objectMeta.Namespace) cm := corev1.ConfigMap{} @@ -237,7 +240,7 @@ func (fi *FlagdContainerInjector) toFilepathProviderConfig(ctx context.Context, } // Add owner reference of the pod's owner - if !SharedOwnership(objectMeta.OwnerReferences, cm.OwnerReferences) { + if !common.SharedOwnership(objectMeta.OwnerReferences, cm.OwnerReferences) { fi.updateCMOwnerReference(ctx, objectMeta, cm) } @@ -253,7 +256,7 @@ func (fi *FlagdContainerInjector) toFilepathProviderConfig(ctx context.Context, }, }) - mountPath := fmt.Sprintf("%s/%s", rootFileSyncMountPath, utils.FeatureFlagConfigurationId(ns, n)) + mountPath := fmt.Sprintf("%s/%s", rootFileSyncMountPath, utils.FeatureFlagId(ns, n)) sidecar.VolumeMounts = append(sidecar.VolumeMounts, corev1.VolumeMount{ Name: n, // create a directory mount per featureFlag spec @@ -262,7 +265,7 @@ func (fi *FlagdContainerInjector) toFilepathProviderConfig(ctx context.Context, }) return types.SourceConfig{ - URI: fmt.Sprintf("%s/%s", mountPath, utils.FeatureFlagConfigurationConfigMapKey(ns, n)), + URI: fmt.Sprintf("%s/%s", mountPath, utils.FeatureFlagConfigMapKey(ns, n)), // todo - this constant needs to be aligned with flagd. We have a mixed usage of file vs filepath Provider: "file", }, nil @@ -281,18 +284,18 @@ func (fi *FlagdContainerInjector) updateCMOwnerReference(ctx context.Context, ob } } -func (fi *FlagdContainerInjector) toHttpProviderConfig(source v1alpha1.Source) types.SourceConfig { +func (fi *FlagdContainerInjector) toHttpProviderConfig(source api.Source) types.SourceConfig { return types.SourceConfig{ URI: source.Source, - Provider: string(v1alpha1.SyncProviderHttp), + Provider: string(apicommon.SyncProviderHttp), BearerToken: source.HttpSyncBearerToken, } } -func (fi *FlagdContainerInjector) toGrpcProviderConfig(source v1alpha1.Source) types.SourceConfig { +func (fi *FlagdContainerInjector) toGrpcProviderConfig(source api.Source) types.SourceConfig { return types.SourceConfig{ URI: source.Source, - Provider: string(v1alpha1.SyncProviderGrpc), + Provider: string(apicommon.SyncProviderGrpc), TLS: source.TLS, CertPath: source.CertPath, ProviderID: source.ProviderID, @@ -300,7 +303,7 @@ func (fi *FlagdContainerInjector) toGrpcProviderConfig(source v1alpha1.Source) t } } -func (fi *FlagdContainerInjector) toFlagdProxyConfig(ctx context.Context, objectMeta *metav1.ObjectMeta, source v1alpha1.Source) (types.SourceConfig, error) { +func (fi *FlagdContainerInjector) toFlagdProxyConfig(ctx context.Context, objectMeta *metav1.ObjectMeta, source api.Source) (types.SourceConfig, error) { // does the proxy exist exists, ready, err := fi.isFlagdProxyReady(ctx) if err != nil { @@ -313,13 +316,13 @@ func (fi *FlagdContainerInjector) toFlagdProxyConfig(ctx context.Context, object return types.SourceConfig{ Provider: "grpc", Selector: fmt.Sprintf("core.openfeature.dev/%s/%s", ns, n), - URI: fmt.Sprintf("%s.%s.svc.cluster.local:%d", FlagdProxyServiceName, fi.FlagdProxyConfig.Namespace, fi.FlagdProxyConfig.Port), + URI: fmt.Sprintf("%s.%s.svc.cluster.local:%d", flagdproxy.FlagdProxyServiceName, fi.FlagdProxyConfig.Namespace, fi.FlagdProxyConfig.Port), }, nil } func (fi *FlagdContainerInjector) isFlagdProxyReady(ctx context.Context) (bool, bool, error) { d := appsV1.Deployment{} - err := fi.Client.Get(ctx, client.ObjectKey{Name: FlagdProxyDeploymentName, Namespace: fi.FlagdProxyConfig.Namespace}, &d) + err := fi.Client.Get(ctx, client.ObjectKey{Name: flagdproxy.FlagdProxyDeploymentName, Namespace: fi.FlagdProxyConfig.Namespace}, &d) if err != nil { if errors.IsNotFound(err) { // does not exist, is not ready, no error @@ -343,12 +346,12 @@ func (fi *FlagdContainerInjector) isFlagdProxyReady(ctx context.Context) (bool, return true, true, nil } -func (fi *FlagdContainerInjector) toKubernetesProviderConfig(ctx context.Context, objectMeta *metav1.ObjectMeta, podSpec *corev1.PodSpec, source v1alpha1.Source) (types.SourceConfig, error) { +func (fi *FlagdContainerInjector) toKubernetesProviderConfig(ctx context.Context, objectMeta *metav1.ObjectMeta, podSpec *corev1.PodSpec, source api.Source) (types.SourceConfig, error) { ns, n := utils.ParseAnnotation(source.Source, objectMeta.Namespace) - // ensure that the FeatureFlagConfiguration exists - if _, err := FindFlagConfig(ctx, fi.Client, ns, n); err != nil { - return types.SourceConfig{}, fmt.Errorf("could not retrieve feature flag configuration %s/%s: %w", ns, n, err) + // ensure that the FeatureFlag exists + if _, err := common.FindFlagConfig(ctx, fi.Client, ns, n); err != nil { + return types.SourceConfig{}, fmt.Errorf("could not retrieve featureflag %s/%s: %w", ns, n, err) } // add permissions to pod @@ -365,11 +368,11 @@ func (fi *FlagdContainerInjector) toKubernetesProviderConfig(ctx context.Context // build K8s config return types.SourceConfig{ URI: fmt.Sprintf("%s/%s", ns, n), - Provider: string(v1alpha1.SyncProviderKubernetes), + Provider: string(apicommon.SyncProviderKubernetes), }, nil } -func (fi *FlagdContainerInjector) generateBasicFlagdContainer(flagSourceConfig *v1alpha1.FlagSourceConfigurationSpec) corev1.Container { +func (fi *FlagdContainerInjector) generateBasicFlagdContainer(flagSourceConfig *api.FeatureFlagSourceSpec) corev1.Container { return corev1.Container{ Name: "flagd", Image: fmt.Sprintf("%s:%s", flagSourceConfig.Image, flagSourceConfig.Tag), @@ -382,7 +385,7 @@ func (fi *FlagdContainerInjector) generateBasicFlagdContainer(flagSourceConfig * Ports: []corev1.ContainerPort{ { Name: "metrics", - ContainerPort: flagSourceConfig.MetricsPort, + ContainerPort: flagSourceConfig.ManagementPort, }, }, SecurityContext: getSecurityContext(), @@ -397,16 +400,19 @@ func (fi *FlagdContainerInjector) createConfigMap(ctx context.Context, namespace references = append(references, ownerReferences[0]) references[0].Controller = utils.FalseVal() } - ff, err := FindFlagConfig(ctx, fi.Client, namespace, name) + ff, err := common.FindFlagConfig(ctx, fi.Client, namespace, name) if err != nil { - return fmt.Errorf("could not retrieve feature flag configuration %s/%s: %w", namespace, name, err) + return fmt.Errorf("could not retrieve featureflag %s/%s: %w", namespace, name, err) } references = append(references, ff.GetReference()) - cm := ff.GenerateConfigMap(name, namespace, references) + cm, err := ff.GenerateConfigMap(name, namespace, references) + if err != nil { + return fmt.Errorf("could generate configmap for featureflag %s/%s: %w", namespace, name, err) + } - return fi.Client.Create(ctx, &cm) + return fi.Client.Create(ctx, cm) } func addFlagdContainer(spec *corev1.PodSpec, flagdContainer corev1.Container) { diff --git a/controllers/common/flagd-injector_test.go b/common/flagdinjector/flagdinjector_test.go similarity index 91% rename from controllers/common/flagd-injector_test.go rename to common/flagdinjector/flagdinjector_test.go index 37ef4ebcb..fc902cb65 100644 --- a/controllers/common/flagd-injector_test.go +++ b/common/flagdinjector/flagdinjector_test.go @@ -1,4 +1,4 @@ -package common +package flagdinjector import ( "context" @@ -7,9 +7,11 @@ import ( "testing" "github.com/go-logr/logr/testr" - "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - "github.com/open-feature/open-feature-operator/controllers/common/constant" - "github.com/open-feature/open-feature-operator/pkg/utils" + api "github.com/open-feature/open-feature-operator/apis/core/v1beta1" + apicommon "github.com/open-feature/open-feature-operator/apis/core/v1beta1/common" + "github.com/open-feature/open-feature-operator/common/constant" + "github.com/open-feature/open-feature-operator/common/flagdproxy" + "github.com/open-feature/open-feature-operator/common/utils" "github.com/stretchr/testify/require" appsV1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -45,9 +47,9 @@ func TestFlagdContainerInjector_InjectDefaultSyncProvider(t *testing.T) { flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.DefaultSyncProvider = v1alpha1.SyncProviderGrpc + flagSourceConfig.DefaultSyncProvider = apicommon.SyncProviderGrpc - flagSourceConfig.Sources = []v1alpha1.Source{{}} + flagSourceConfig.Sources = []api.Source{{}} err := fi.InjectFlagd(context.Background(), &deployment.ObjectMeta, &deployment.Spec.Template.Spec, flagSourceConfig) require.Nil(t, err) @@ -82,11 +84,11 @@ func TestFlagdContainerInjector_InjectDefaultSyncProvider_WithDebugLogging(t *te flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.DefaultSyncProvider = v1alpha1.SyncProviderGrpc + flagSourceConfig.DefaultSyncProvider = apicommon.SyncProviderGrpc flagSourceConfig.DebugLogging = utils.TrueVal() - flagSourceConfig.Sources = []v1alpha1.Source{{}} + flagSourceConfig.Sources = []api.Source{{}} err := fi.InjectFlagd(context.Background(), &deployment.ObjectMeta, &deployment.Spec.Template.Spec, flagSourceConfig) require.Nil(t, err) @@ -121,11 +123,11 @@ func TestFlagdContainerInjector_InjectDefaultSyncProvider_WithOtelCollectorUri(t flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.DefaultSyncProvider = v1alpha1.SyncProviderGrpc + flagSourceConfig.DefaultSyncProvider = apicommon.SyncProviderGrpc flagSourceConfig.OtelCollectorUri = "localhost:4317" - flagSourceConfig.Sources = []v1alpha1.Source{{}} + flagSourceConfig.Sources = []api.Source{{}} err := fi.InjectFlagd(context.Background(), &deployment.ObjectMeta, &deployment.Spec.Template.Spec, flagSourceConfig) require.Nil(t, err) @@ -160,7 +162,7 @@ func TestFlagdContainerInjector_InjectDefaultSyncProvider_WithResources(t *testi flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.DefaultSyncProvider = v1alpha1.SyncProviderGrpc + flagSourceConfig.DefaultSyncProvider = apicommon.SyncProviderGrpc flagSourceConfig.Resources = corev1.ResourceRequirements{ Limits: map[corev1.ResourceName]resource.Quantity{ @@ -173,7 +175,7 @@ func TestFlagdContainerInjector_InjectDefaultSyncProvider_WithResources(t *testi }, } - flagSourceConfig.Sources = []v1alpha1.Source{{}} + flagSourceConfig.Sources = []api.Source{{}} err := fi.InjectFlagd(context.Background(), &deployment.ObjectMeta, &deployment.Spec.Template.Spec, flagSourceConfig) require.Nil(t, err) @@ -211,9 +213,9 @@ func TestFlagdContainerInjector_InjectDefaultSyncProvider_WithSyncProviderArgs(t flagSourceConfig.SyncProviderArgs = []string{"arg-1", "arg-2"} - flagSourceConfig.DefaultSyncProvider = v1alpha1.SyncProviderGrpc + flagSourceConfig.DefaultSyncProvider = apicommon.SyncProviderGrpc - flagSourceConfig.Sources = []v1alpha1.Source{{}} + flagSourceConfig.Sources = []api.Source{{}} err := fi.InjectFlagd(context.Background(), &deployment.ObjectMeta, &deployment.Spec.Template.Spec, flagSourceConfig) require.Nil(t, err) @@ -247,10 +249,10 @@ func TestFlagdContainerInjector_InjectFlagdKubernetesSource(t *testing.T) { flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.Sources = []v1alpha1.Source{ + flagSourceConfig.Sources = []api.Source{ { Source: "my-namespace/server-side", - Provider: v1alpha1.SyncProviderKubernetes, + Provider: apicommon.SyncProviderKubernetes, }, } @@ -299,10 +301,10 @@ func TestFlagdContainerInjector_InjectFlagdFilePathSource(t *testing.T) { flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.Sources = []v1alpha1.Source{ + flagSourceConfig.Sources = []api.Source{ { Source: "my-namespace/server-side", - Provider: v1alpha1.SyncProviderFilepath, + Provider: apicommon.SyncProviderFilepath, }, } @@ -383,10 +385,10 @@ func TestFlagdContainerInjector_InjectFlagdFilePathSource_UpdateReferencedConfig flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.Sources = []v1alpha1.Source{ + flagSourceConfig.Sources = []api.Source{ { Source: "my-namespace/server-side", - Provider: v1alpha1.SyncProviderFilepath, + Provider: apicommon.SyncProviderFilepath, }, } @@ -454,11 +456,11 @@ func TestFlagdContainerInjector_InjectHttpSource(t *testing.T) { flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.Sources = []v1alpha1.Source{ + flagSourceConfig.Sources = []api.Source{ { Source: "http://localhost:8013", HttpSyncBearerToken: "my-token", - Provider: v1alpha1.SyncProviderHttp, + Provider: apicommon.SyncProviderHttp, }, } @@ -496,10 +498,10 @@ func TestFlagdContainerInjector_InjectGrpcSource(t *testing.T) { flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.Sources = []v1alpha1.Source{ + flagSourceConfig.Sources = []api.Source{ { Source: "grpc://localhost:8013", - Provider: v1alpha1.SyncProviderGrpc, + Provider: apicommon.SyncProviderGrpc, TLS: true, CertPath: "cert-path", ProviderID: "provider-id", @@ -541,9 +543,9 @@ func TestFlagdContainerInjector_InjectProxySource_ProxyNotAvailable(t *testing.T flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.Sources = []v1alpha1.Source{ + flagSourceConfig.Sources = []api.Source{ { - Provider: v1alpha1.SyncProviderFlagdProxy, + Provider: apicommon.SyncProviderFlagdProxy, }, } @@ -559,7 +561,7 @@ func TestFlagdContainerInjector_InjectProxySource_ProxyNotReady(t *testing.T) { namespace, fakeClient := initContainerInjectionTestEnv() flagdProxyDeployment := &appsV1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: FlagdProxyDeploymentName, Namespace: namespace}, + ObjectMeta: metav1.ObjectMeta{Name: flagdproxy.FlagdProxyDeploymentName, Namespace: namespace}, } err := fakeClient.Create(context.Background(), flagdProxyDeployment) @@ -582,9 +584,9 @@ func TestFlagdContainerInjector_InjectProxySource_ProxyNotReady(t *testing.T) { flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.Sources = []v1alpha1.Source{ + flagSourceConfig.Sources = []api.Source{ { - Provider: v1alpha1.SyncProviderFlagdProxy, + Provider: apicommon.SyncProviderFlagdProxy, }, } @@ -598,7 +600,7 @@ func TestFlagdContainerInjector_InjectProxySource_ProxyIsReady(t *testing.T) { namespace, fakeClient := initContainerInjectionTestEnv() flagdProxyDeployment := &appsV1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: FlagdProxyDeploymentName, Namespace: namespace}, + ObjectMeta: metav1.ObjectMeta{Name: flagdproxy.FlagdProxyDeploymentName, Namespace: namespace}, } err := fakeClient.Create(context.Background(), flagdProxyDeployment) @@ -626,9 +628,9 @@ func TestFlagdContainerInjector_InjectProxySource_ProxyIsReady(t *testing.T) { flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.Sources = []v1alpha1.Source{ + flagSourceConfig.Sources = []api.Source{ { - Provider: v1alpha1.SyncProviderFlagdProxy, + Provider: apicommon.SyncProviderFlagdProxy, }, } @@ -707,7 +709,7 @@ func TestFlagdContainerInjector_InjectUnknownSyncProvider(t *testing.T) { flagSourceConfig := getFlagSourceConfigSpec() - flagSourceConfig.Sources = []v1alpha1.Source{ + flagSourceConfig.Sources = []api.Source{ { Provider: "unknown", }, @@ -721,7 +723,7 @@ func TestFlagdContainerInjector_InjectUnknownSyncProvider(t *testing.T) { func TestFlagdContainerInjector_createConfigMap(t *testing.T) { - _ = v1alpha1.AddToScheme(scheme.Scheme) + _ = api.AddToScheme(scheme.Scheme) fakeClientBuilder := fake.NewClientBuilder(). WithScheme(scheme.Scheme) @@ -736,7 +738,7 @@ func TestFlagdContainerInjector_createConfigMap(t *testing.T) { wantErr error }{ { - name: "feature flag config not found", + name: "featureflag not found", flagdInjector: &FlagdContainerInjector{ Client: fakeClientBuilder.Build(), Logger: testr.New(t), @@ -744,12 +746,12 @@ func TestFlagdContainerInjector_createConfigMap(t *testing.T) { namespace: "myns", confname: "mypod", ownerRefs: []metav1.OwnerReference{{}}, - wantErr: errors.New("could not retrieve feature flag configuration myns/mypod: featureflagconfigurations.core.openfeature.dev \"mypod\" not found"), + wantErr: errors.New("could not retrieve featureflag myns/mypod: featureflags.core.openfeature.dev \"mypod\" not found"), }, { - name: "feature flag config found, config map created", + name: "featureflag found, config map created", flagdInjector: &FlagdContainerInjector{ - Client: fakeClientBuilder.WithObjects(&v1alpha1.FeatureFlagConfiguration{ + Client: fakeClientBuilder.WithObjects(&api.FeatureFlag{ ObjectMeta: metav1.ObjectMeta{ Name: "myconf", Namespace: "myns", @@ -776,7 +778,7 @@ func TestFlagdContainerInjector_createConfigMap(t *testing.T) { require.Nil(t, err) require.Equal(t, map[string]string{ - "openfeature.dev/featureflagconfiguration": tt.confname, + "openfeature.dev/featureflag": tt.confname, }, ffConfig.Annotations) require.EqualValues(t, utils.FalseVal(), ffConfig.OwnerReferences[0].Controller) @@ -795,7 +797,7 @@ func TestFlagdContainerInjector_createConfigMap(t *testing.T) { func initContainerInjectionTestEnv() (string, client.WithWatch) { namespace := "my-namespace" - _ = v1alpha1.AddToScheme(scheme.Scheme) + _ = api.AddToScheme(scheme.Scheme) serviceAccount := &v1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ @@ -810,12 +812,12 @@ func initContainerInjectionTestEnv() (string, client.WithWatch) { }, } - ffConfig := &v1alpha1.FeatureFlagConfiguration{ + ffConfig := &api.FeatureFlag{ ObjectMeta: metav1.ObjectMeta{ Name: "server-side", Namespace: namespace, }, - Spec: v1alpha1.FeatureFlagConfigurationSpec{}, + Spec: api.FeatureFlagSpec{}, } fakeClientBuilder := fake.NewClientBuilder(). @@ -825,14 +827,14 @@ func initContainerInjectionTestEnv() (string, client.WithWatch) { return namespace, fakeClient } -func getFlagSourceConfigSpec() *v1alpha1.FlagSourceConfigurationSpec { +func getFlagSourceConfigSpec() *api.FeatureFlagSourceSpec { probesEnabled := true - return &v1alpha1.FlagSourceConfigurationSpec{ - MetricsPort: 8014, - Port: 8013, - Image: "flagd", - Tag: "0.5.0", + return &api.FeatureFlagSourceSpec{ + ManagementPort: 8014, + Port: 8013, + Image: "flagd", + Tag: "0.5.0", EnvVars: []v1.EnvVar{ { Name: "my-env-var", @@ -944,14 +946,14 @@ func intPtr(i int64) *int64 { return &i } -func getProxyConfig() *FlagdProxyConfiguration { - return &FlagdProxyConfiguration{ - Port: 8013, - MetricsPort: 8014, - DebugLogging: false, - Image: "flagd", - Tag: "0.5.0", - Namespace: "my-namespace", +func getProxyConfig() *flagdproxy.FlagdProxyConfiguration { + return &flagdproxy.FlagdProxyConfiguration{ + Port: 8013, + ManagementPort: 8014, + DebugLogging: false, + Image: "flagd", + Tag: "0.5.0", + Namespace: "my-namespace", } } @@ -1181,7 +1183,7 @@ func TestFlagdContainerInjector_EnableClusterRoleBinding_ServiceAccountNotFound( func initEnableClusterroleBindingTestEnv() (string, client.WithWatch) { namespace := "my-namespace" - _ = v1alpha1.AddToScheme(scheme.Scheme) + _ = api.AddToScheme(scheme.Scheme) fakeClientBuilder := fake.NewClientBuilder(). WithScheme(scheme.Scheme) diff --git a/controllers/common/flagd-proxy.go b/common/flagdproxy/flagdproxy.go similarity index 85% rename from controllers/common/flagd-proxy.go rename to common/flagdproxy/flagdproxy.go index 43475cf42..c40249a9d 100644 --- a/controllers/common/flagd-proxy.go +++ b/common/flagdproxy/flagdproxy.go @@ -1,4 +1,4 @@ -package common +package flagdproxy import ( "context" @@ -6,8 +6,7 @@ import ( "os" "github.com/go-logr/logr" - corev1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - "github.com/open-feature/open-feature-operator/pkg/utils" + "github.com/open-feature/open-feature-operator/common/utils" appsV1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -22,20 +21,20 @@ const ( FlagdProxyServiceAccountName = "open-feature-operator-flagd-proxy" FlagdProxyServiceName = "flagd-proxy-svc" // renovate: datasource=github-tags depName=open-feature/flagd/flagd-proxy - DefaultFlagdProxyTag = "v0.2.8" - DefaultFlagdProxyImage = "ghcr.io/open-feature/flagd-proxy" - DefaultFlagdProxyPort = 8015 - DefaultFlagdProxyMetricsPort = 8016 - DefaultFlagdProxyDebugLogging = false - DefaultFlagdProxyNamespace = "open-feature-operator-system" - - envVarPodNamespace = "POD_NAMESPACE" - envVarProxyImage = "FLAGD_PROXY_IMAGE" - envVarProxyTag = "FLAGD_PROXY_TAG" - envVarProxyPort = "FLAGD_PROXY_PORT" - envVarProxyMetricsPort = "FLAGD_PROXY_METRICS_PORT" - envVarProxyDebugLogging = "FLAGD_PROXY_DEBUG_LOGGING" - operatorDeploymentName = "open-feature-operator-controller-manager" + DefaultFlagdProxyTag = "v0.3.0" + DefaultFlagdProxyImage = "ghcr.io/open-feature/flagd-proxy" + DefaultFlagdProxyPort = 8015 + DefaultFlagdProxyManagementPort = 8016 + DefaultFlagdProxyDebugLogging = false + DefaultFlagdProxyNamespace = "open-feature-operator-system" + + envVarPodNamespace = "POD_NAMESPACE" + envVarProxyImage = "FLAGD_PROXY_IMAGE" + envVarProxyTag = "FLAGD_PROXY_TAG" + envVarProxyPort = "FLAGD_PROXY_PORT" + envVarProxyManagementPort = "FLAGD_PROXY_MANAGEMENT_PORT" + envVarProxyDebugLogging = "FLAGD_PROXY_DEBUG_LOGGING" + operatorDeploymentName = "open-feature-operator-controller-manager" ) type FlagdProxyHandler struct { @@ -46,7 +45,7 @@ type FlagdProxyHandler struct { type FlagdProxyConfiguration struct { Port int - MetricsPort int + ManagementPort int DebugLogging bool Image string Tag string @@ -79,11 +78,11 @@ func NewFlagdProxyConfiguration() (*FlagdProxyConfiguration, error) { } config.Port = port - metricsPort, err := utils.GetIntEnvVar(envVarProxyMetricsPort, DefaultFlagdProxyMetricsPort) + managementPort, err := utils.GetIntEnvVar(envVarProxyManagementPort, DefaultFlagdProxyManagementPort) if err != nil { return config, err } - config.MetricsPort = metricsPort + config.ManagementPort = managementPort kpDebugLogging, err := utils.GetBoolEnvVar(envVarProxyDebugLogging, DefaultFlagdProxyDebugLogging) if err != nil { @@ -106,7 +105,7 @@ func (f *FlagdProxyHandler) Config() *FlagdProxyConfiguration { return f.config } -func (f *FlagdProxyHandler) HandleFlagdProxy(ctx context.Context, flagSourceConfiguration *corev1alpha1.FlagSourceConfiguration) error { +func (f *FlagdProxyHandler) HandleFlagdProxy(ctx context.Context) error { exists, err := f.doesFlagdProxyExist(ctx) if err != nil { return err @@ -165,7 +164,7 @@ func (f *FlagdProxyHandler) newFlagdProxyManifest(ownerReferences []metav1.Owner args := []string{ "start", "--metrics-port", - fmt.Sprintf("%d", f.config.MetricsPort), + fmt.Sprintf("%d", f.config.ManagementPort), } if f.config.DebugLogging { args = append(args, "--debug") @@ -210,7 +209,7 @@ func (f *FlagdProxyHandler) newFlagdProxyManifest(ownerReferences []metav1.Owner }, { Name: "metrics-port", - ContainerPort: int32(f.config.MetricsPort), + ContainerPort: int32(f.config.ManagementPort), }, }, Args: args, diff --git a/controllers/common/flagd-proxy_test.go b/common/flagdproxy/flagdproxy_test.go similarity index 93% rename from controllers/common/flagd-proxy_test.go rename to common/flagdproxy/flagdproxy_test.go index 11b90bc03..2dd461d61 100644 --- a/controllers/common/flagd-proxy_test.go +++ b/common/flagdproxy/flagdproxy_test.go @@ -1,4 +1,4 @@ -package common +package flagdproxy import ( "context" @@ -20,7 +20,7 @@ func TestNewFlagdProxyConfiguration(t *testing.T) { require.NotNil(t, kpConfig) require.Equal(t, &FlagdProxyConfiguration{ Port: 8015, - MetricsPort: 8016, + ManagementPort: 8016, DebugLogging: false, Image: DefaultFlagdProxyImage, Tag: DefaultFlagdProxyTag, @@ -35,7 +35,7 @@ func TestNewFlagdProxyConfiguration_OverrideEnvVars(t *testing.T) { t.Setenv(envVarProxyTag, "my-tag") t.Setenv(envVarPodNamespace, "my-namespace") t.Setenv(envVarProxyPort, "8080") - t.Setenv(envVarProxyMetricsPort, "8081") + t.Setenv(envVarProxyManagementPort, "8081") t.Setenv(envVarProxyDebugLogging, "true") kpConfig, err := NewFlagdProxyConfiguration() @@ -44,7 +44,7 @@ func TestNewFlagdProxyConfiguration_OverrideEnvVars(t *testing.T) { require.NotNil(t, kpConfig) require.Equal(t, &FlagdProxyConfiguration{ Port: 8080, - MetricsPort: 8081, + ManagementPort: 8081, DebugLogging: true, Image: "my-image", Tag: "my-tag", @@ -96,7 +96,7 @@ func TestFlagdProxyHandler_HandleFlagdProxy_ProxyExists(t *testing.T) { require.NotNil(t, ph) - err = ph.HandleFlagdProxy(context.Background(), nil) + err = ph.HandleFlagdProxy(context.Background()) require.Nil(t, err) @@ -125,7 +125,7 @@ func TestFlagdProxyHandler_HandleFlagdProxy_CreateProxy(t *testing.T) { require.NotNil(t, ph) - err = ph.HandleFlagdProxy(context.Background(), nil) + err = ph.HandleFlagdProxy(context.Background()) require.Nil(t, err) diff --git a/pkg/types/source-config.go b/common/types/source-config.go similarity index 100% rename from pkg/types/source-config.go rename to common/types/source-config.go diff --git a/pkg/utils/utils.go b/common/utils/utils.go similarity index 84% rename from pkg/utils/utils.go rename to common/utils/utils.go index a8c6b1070..a9b530b85 100644 --- a/pkg/utils/utils.go +++ b/common/utils/utils.go @@ -59,11 +59,11 @@ func GetBoolEnvVar(key string, defaultVal bool) (bool, error) { } // unique string used to create unique volume mount and file name -func FeatureFlagConfigurationId(namespace, name string) string { +func FeatureFlagId(namespace, name string) string { return fmt.Sprintf("%s_%s", namespace, name) } // unique key (and filename) for configMap data -func FeatureFlagConfigurationConfigMapKey(namespace, name string) string { - return fmt.Sprintf("%s.flagd.json", FeatureFlagConfigurationId(namespace, name)) +func FeatureFlagConfigMapKey(namespace, name string) string { + return fmt.Sprintf("%s.flagd.json", FeatureFlagId(namespace, name)) } diff --git a/pkg/utils/utils_test.go b/common/utils/utils_test.go similarity index 66% rename from pkg/utils/utils_test.go rename to common/utils/utils_test.go index 7e7b6d170..441bf1d45 100644 --- a/pkg/utils/utils_test.go +++ b/common/utils/utils_test.go @@ -6,12 +6,12 @@ import ( "github.com/stretchr/testify/require" ) -func Test_FeatureFlagConfigurationId(t *testing.T) { - require.Equal(t, "namespace_name", FeatureFlagConfigurationId("namespace", "name")) +func Test_FeatureFlagId(t *testing.T) { + require.Equal(t, "namespace_name", FeatureFlagId("namespace", "name")) } -func Test_FeatureFlagConfigurationConfigMapKey(t *testing.T) { - require.Equal(t, "namespace_name.flagd.json", FeatureFlagConfigurationConfigMapKey("namespace", "name")) +func Test_FeatureFlagConfigMapKey(t *testing.T) { + require.Equal(t, "namespace_name.flagd.json", FeatureFlagConfigMapKey("namespace", "name")) } func Test_FalseVal(t *testing.T) { diff --git a/config/crd/bases/core.openfeature.dev_featureflagsources.yaml b/config/crd/bases/core.openfeature.dev_featureflagsources.yaml index 1324cfed2..534b071b7 100644 --- a/config/crd/bases/core.openfeature.dev_featureflagsources.yaml +++ b/config/crd/bases/core.openfeature.dev_featureflagsources.yaml @@ -50,8 +50,8 @@ spec: type: string envVars: description: EnvVars define the env vars to be applied to the sidecar, - any env vars in FeatureFlagConfiguration CRs are added at the lowest - index, all values will have the EnvVarPrefix applied, default FLAGD + any env vars in FeatureFlag CRs are added at the lowest index, all + values will have the EnvVarPrefix applied, default FLAGD items: description: EnvVar represents an environment variable present in a Container. @@ -170,9 +170,9 @@ spec: description: LogFormat allows for the sidecar log format to be overridden, defaults to 'json' type: string - metricsPort: - description: MetricsPort defines the port to serve metrics on, defaults - to 8014 + managementPort: + description: ManagemetPort defines the port to serve management on, + defaults to 8014 format: int32 type: integer otelCollectorUri: diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 7c73909b0..b7902f7e0 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -2,8 +2,6 @@ # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/default resources: -- bases/core.openfeature.dev_featureflagconfigurations.yaml -- bases/core.openfeature.dev_flagsourceconfigurations.yaml - bases/core.openfeature.dev_featureflags.yaml - bases/core.openfeature.dev_featureflagsources.yaml #+kubebuilder:scaffold:crdkustomizeresource @@ -11,16 +9,12 @@ resources: patchesStrategicMerge: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD -- patches/webhook_in_featureflagconfigurations.yaml -- patches/webhook_in_flagsourceconfigurations.yaml #- patches/webhook_in_featureflags.yaml #- patches/webhook_in_featureflagsources.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD -- patches/cainjection_in_featureflagconfigurations.yaml -- patches/cainjection_in_flagsourceconfigurations.yaml #- patches/cainjection_in_featureflags.yaml #- patches/cainjection_in_featureflagsources.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch diff --git a/config/crd/patches/cainjection_in_core_flagsourceconfigurations.yaml b/config/crd/patches/cainjection_in_core_flagsourceconfigurations.yaml deleted file mode 100644 index 11e3fce17..000000000 --- a/config/crd/patches/cainjection_in_core_flagsourceconfigurations.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# The following patch adds a directive for certmanager to inject CA into the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: flagsourceconfigurations.core.openfeature.dev diff --git a/config/crd/patches/cainjection_in_featureflagconfigurations.yaml b/config/crd/patches/cainjection_in_featureflagconfigurations.yaml deleted file mode 100644 index 74c4735c4..000000000 --- a/config/crd/patches/cainjection_in_featureflagconfigurations.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# The following patch adds a directive for certmanager to inject CA into the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: featureflagconfigurations.core.openfeature.dev diff --git a/config/crd/patches/cainjection_in_flagsourceconfigurations.yaml b/config/crd/patches/cainjection_in_flagsourceconfigurations.yaml deleted file mode 100644 index 11e3fce17..000000000 --- a/config/crd/patches/cainjection_in_flagsourceconfigurations.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# The following patch adds a directive for certmanager to inject CA into the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: flagsourceconfigurations.core.openfeature.dev diff --git a/config/crd/patches/webhook_in_core_featureflags.yaml b/config/crd/patches/webhook_in_core_featureflags.yaml deleted file mode 100644 index d52034067..000000000 --- a/config/crd/patches/webhook_in_core_featureflags.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# The following patch enables a conversion webhook for the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: featureflags.core.openfeature.dev -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - namespace: system - name: webhook-service - path: /convert - conversionReviewVersions: - - v1 diff --git a/config/crd/patches/webhook_in_core_featureflagsources.yaml b/config/crd/patches/webhook_in_core_featureflagsources.yaml deleted file mode 100644 index 624268108..000000000 --- a/config/crd/patches/webhook_in_core_featureflagsources.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# The following patch enables a conversion webhook for the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: featureflagsources.core.openfeature.dev -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - namespace: system - name: webhook-service - path: /convert - conversionReviewVersions: - - v1 diff --git a/config/crd/patches/webhook_in_core_flagsourceconfigurations.yaml b/config/crd/patches/webhook_in_core_flagsourceconfigurations.yaml deleted file mode 100644 index 2ae67ba6a..000000000 --- a/config/crd/patches/webhook_in_core_flagsourceconfigurations.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# The following patch enables a conversion webhook for the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: flagsourceconfigurations.core.openfeature.dev -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - namespace: system - name: webhook-service - path: /convert - conversionReviewVersions: - - v1 diff --git a/config/crd/patches/webhook_in_featureflagconfigurations.yaml b/config/crd/patches/webhook_in_featureflagconfigurations.yaml deleted file mode 100644 index 0983a2ebb..000000000 --- a/config/crd/patches/webhook_in_featureflagconfigurations.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# The following patch enables a conversion webhook for the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: featureflagconfigurations.core.openfeature.dev -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - namespace: system - name: webhook-service - path: /convert - conversionReviewVersions: - - v1 diff --git a/config/crd/patches/webhook_in_flagsourceconfigurations.yaml b/config/crd/patches/webhook_in_flagsourceconfigurations.yaml deleted file mode 100644 index 2ae67ba6a..000000000 --- a/config/crd/patches/webhook_in_flagsourceconfigurations.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# The following patch enables a conversion webhook for the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: flagsourceconfigurations.core.openfeature.dev -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - namespace: system - name: webhook-service - path: /convert - conversionReviewVersions: - - v1 diff --git a/config/default/webhookcainjection_patch.yaml b/config/default/webhookcainjection_patch.yaml index 02ab515d4..28b1826d4 100644 --- a/config/default/webhookcainjection_patch.yaml +++ b/config/default/webhookcainjection_patch.yaml @@ -6,10 +6,3 @@ metadata: name: mutating-webhook-configuration annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - name: validating-webhook-configuration - annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) diff --git a/config/overlays/helm/manager.yaml b/config/overlays/helm/manager.yaml index 5b7098fe5..94818e3b1 100644 --- a/config/overlays/helm/manager.yaml +++ b/config/overlays/helm/manager.yaml @@ -18,8 +18,8 @@ spec: cpu: "{{ .Values.controllerManager.manager.resources.requests.cpu }}" memory: "{{ .Values.controllerManager.manager.resources.requests.memory }}" env: - - name: SIDECAR_METRICS_PORT - value: "{{ .Values.sidecarConfiguration.metricsPort }}" + - name: SIDECAR_MANAGEMENT_PORT + value: "{{ .Values.sidecarConfiguration.managementPort }}" - name: SIDECAR_PORT value: "{{ .Values.sidecarConfiguration.port }}" - name: SIDECAR_SOCKET_PATH @@ -46,8 +46,8 @@ spec: value: "{{ .Values.flagdProxyConfiguration.image.tag }}" - name: FLAGD_PROXY_PORT value: "{{ .Values.flagdProxyConfiguration.port }}" - - name: FLAGD_PROXY_METRICS_PORT - value: "{{ .Values.flagdProxyConfiguration.metricsPort }}" + - name: FLAGD_PROXY_MANAGEMENT_PORT + value: "{{ .Values.flagdProxyConfiguration.managementPort }}" - name: FLAGD_PROXY_DEBUG_LOGGING value: "{{ .Values.flagdProxyConfiguration.debugLogging }}" - name: kube-rbac-proxy diff --git a/config/rbac/core_flagsourceconfiguration_editor_role.yaml b/config/rbac/core_flagsourceconfiguration_editor_role.yaml deleted file mode 100644 index 16e463740..000000000 --- a/config/rbac/core_flagsourceconfiguration_editor_role.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# permissions for end users to edit flagsourceconfigurations. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/name: clusterrole - app.kubernetes.io/instance: flagsourceconfiguration-editor-role - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: open-feature-operator - app.kubernetes.io/part-of: open-feature-operator - app.kubernetes.io/managed-by: kustomize - name: flagsourceconfiguration-editor-role -rules: -- apiGroups: - - core.openfeature.dev - resources: - - flagsourceconfigurations - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - core.openfeature.dev - resources: - - flagsourceconfigurations/status - verbs: - - get diff --git a/config/rbac/core_flagsourceconfiguration_viewer_role.yaml b/config/rbac/core_flagsourceconfiguration_viewer_role.yaml deleted file mode 100644 index 1193eed64..000000000 --- a/config/rbac/core_flagsourceconfiguration_viewer_role.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# permissions for end users to view flagsourceconfigurations. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/name: clusterrole - app.kubernetes.io/instance: flagsourceconfiguration-viewer-role - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: open-feature-operator - app.kubernetes.io/part-of: open-feature-operator - app.kubernetes.io/managed-by: kustomize - name: flagsourceconfiguration-viewer-role -rules: -- apiGroups: - - core.openfeature.dev - resources: - - flagsourceconfigurations - verbs: - - get - - list - - watch -- apiGroups: - - core.openfeature.dev - resources: - - flagsourceconfigurations/status - verbs: - - get diff --git a/config/rbac/featureflagconfiguration_editor_role.yaml b/config/rbac/featureflagconfiguration_editor_role.yaml deleted file mode 100644 index ecc6f1600..000000000 --- a/config/rbac/featureflagconfiguration_editor_role.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# permissions for end users to edit featureflagconfigurations. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: featureflagconfiguration-editor-role -rules: -- apiGroups: - - config.openfeature.dev - resources: - - featureflagconfigurations - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - config.openfeature.dev - resources: - - featureflagconfigurations/status - verbs: - - get diff --git a/config/rbac/featureflagconfiguration_viewer_role.yaml b/config/rbac/featureflagconfiguration_viewer_role.yaml deleted file mode 100644 index 1a2c23ba3..000000000 --- a/config/rbac/featureflagconfiguration_viewer_role.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# permissions for end users to view featureflagconfigurations. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: featureflagconfiguration-viewer-role -rules: -- apiGroups: - - config.openfeature.dev - resources: - - featureflagconfigurations - verbs: - - get - - list - - watch -- apiGroups: - - config.openfeature.dev - resources: - - featureflagconfigurations/status - verbs: - - get diff --git a/config/rbac/flagd_kubernetes_sync_clusterrole.yaml b/config/rbac/flagd_kubernetes_sync_clusterrole.yaml index b6849439e..65e20af9b 100644 --- a/config/rbac/flagd_kubernetes_sync_clusterrole.yaml +++ b/config/rbac/flagd_kubernetes_sync_clusterrole.yaml @@ -5,5 +5,5 @@ metadata: name: flagd-kubernetes-sync rules: - apiGroups: ["core.openfeature.dev"] - resources: ["flagsourceconfigurations", "featureflagconfigurations"] + resources: ["featureflagsources", "featureflags"] verbs: ["get", "watch", "list"] diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index eca339434..0fb890069 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -60,7 +60,7 @@ rules: - apiGroups: - core.openfeature.dev resources: - - featureflagconfigurations + - featureflagsources verbs: - create - delete @@ -72,39 +72,13 @@ rules: - apiGroups: - core.openfeature.dev resources: - - featureflagconfigurations/finalizers + - featureflagsources/finalizers verbs: - update - apiGroups: - core.openfeature.dev resources: - - featureflagconfigurations/status - verbs: - - get - - patch - - update -- apiGroups: - - core.openfeature.dev - resources: - - flagsourceconfigurations - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - core.openfeature.dev - resources: - - flagsourceconfigurations/finalizers - verbs: - - update -- apiGroups: - - core.openfeature.dev - resources: - - flagsourceconfigurations/status + - featureflagsources/status verbs: - get - patch diff --git a/config/samples/core_v1beta1_featureflagsource.yaml b/config/samples/core_v1beta1_featureflagsource.yaml index 9e5c9f9d1..c1651528a 100644 --- a/config/samples/core_v1beta1_featureflagsource.yaml +++ b/config/samples/core_v1beta1_featureflagsource.yaml @@ -3,7 +3,7 @@ kind: FeatureFlagSource metadata: name: featureflagsource-sample spec: - metricsPort: 8080 + managementPort: 8080 evaluator: json defaultSyncProvider: file tag: latest diff --git a/config/samples/crds/custom_provider.yaml b/config/samples/crds/custom_provider.yaml deleted file mode 100644 index 79dcad67e..000000000 --- a/config/samples/crds/custom_provider.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: core.openfeature.dev/v1alpha1 -kind: FeatureFlagConfiguration -metadata: - name: featureflagconfiguration-sample -spec: - flagDSpec: - envs: - - name: FOO - value: BAR - serviceProvider: - name: "flagd" - credentials: - name: "sample-provider-secret" - namespace: "default" - featureFlagSpec: | - {} diff --git a/config/samples/crds/featureflagconfiguration.yaml b/config/samples/crds/featureflagconfiguration.yaml deleted file mode 100644 index 427e4c5f7..000000000 --- a/config/samples/crds/featureflagconfiguration.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: core.openfeature.dev/v1alpha1 -kind: FeatureFlagConfiguration -metadata: - name: featureflagconfiguration-sample -spec: - flagDSpec: - envs: - - name: FOO - value: BAR - featureFlagSpec: | - {} \ No newline at end of file diff --git a/config/samples/deployment.yaml b/config/samples/deployment.yaml index 95eb3dadf..218b7b5ba 100644 --- a/config/samples/deployment.yaml +++ b/config/samples/deployment.yaml @@ -13,7 +13,7 @@ spec: app: nginx annotations: openfeature.dev/enabled: "true" - openfeature.dev/featureflagconfiguration: "featureflagconfiguration-sample" + openfeature.dev/featureflagsource: "configuration-sample" spec: containers: - name: nginx @@ -36,7 +36,7 @@ spec: app: nginx annotations: openfeature.dev/enabled: "true" - openfeature.dev/featureflagconfiguration: "featureflagconfiguration-sample" + openfeature.dev/featureflagsource: "configuration-sample" spec: containers: - name: nginx diff --git a/config/samples/end-to-end.yaml b/config/samples/end-to-end.yaml index 4403a55a9..27485bf81 100644 --- a/config/samples/end-to-end.yaml +++ b/config/samples/end-to-end.yaml @@ -4,48 +4,22 @@ kind: Namespace metadata: name: open-feature-demo --- -apiVersion: core.openfeature.dev/v1alpha2 -kind: FeatureFlagConfiguration +apiVersion: core.openfeature.dev/v1beta1 +kind: FeatureFlag metadata: - name: end-to-end - namespace: open-feature-demo + name: featureflag-sample spec: - featureFlagSpec: + flagSpec: flags: - new-welcome-message: - state: ENABLED + "simple-flag": + state: "ENABLED" variants: "on": true "off": false defaultVariant: "on" - hex-color: - state: ENABLED - variants: - red: CC0000 - green: 00CC00 - blue: 0000CC - yellow: yellow - defaultVariant: blue - fib-algo: - state: ENABLED - variants: - recursive: recursive - memo: memo - loop: loop - binet: binet - defaultVariant: recursive - "targeting": { - "if": [ - { - "in": [ "@faas.com", { - "var": [ "email" ] - } ] - }, "binet", null - ] - } --- -apiVersion: core.openfeature.dev/v1alpha2 -kind: FlagSourceConfiguration +apiVersion: core.openfeature.dev/v1beta1 +kind: FeatureFlagSource metadata: name: end-to-end namespace: open-feature-demo @@ -73,7 +47,7 @@ spec: app: open-feature-demo annotations: openfeature.dev/enabled: "true" - openfeature.dev/flagsourceconfiguration: "end-to-end" + openfeature.dev/featureflagsource: "end-to-end" spec: serviceAccountName: open-feature-demo-sa containers: diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 4c44bd898..e03b39199 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -25,30 +25,3 @@ webhooks: resources: - pods sideEffects: NoneOnDryRun ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - creationTimestamp: null - name: validating-webhook-configuration -webhooks: -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-v1alpha1-featureflagconfiguration - failurePolicy: Fail - name: validate.featureflagconfiguration.openfeature.dev - rules: - - apiGroups: - - core.openfeature.dev - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - featureflagconfigurations - sideEffects: None diff --git a/controllers/common/mock/flagd-injector.go b/controllers/common/mock/flagd-injector.go deleted file mode 100644 index d08494763..000000000 --- a/controllers/common/mock/flagd-injector.go +++ /dev/null @@ -1,65 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: controllers/common/flagd-injector.go - -// Package commonmock is a generated GoMock package. -package commonmock - -import ( - context "context" - reflect "reflect" - gomock "github.com/golang/mock/gomock" - v1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - v1 "k8s.io/api/core/v1" - v10 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// MockIFlagdContainerInjector is a mock of IFlagdContainerInjector interface. -type MockIFlagdContainerInjector struct { - ctrl *gomock.Controller - recorder *MockIFlagdContainerInjectorMockRecorder -} - -// MockIFlagdContainerInjectorMockRecorder is the mock recorder for MockIFlagdContainerInjector. -type MockIFlagdContainerInjectorMockRecorder struct { - mock *MockIFlagdContainerInjector -} - -// NewMockIFlagdContainerInjector creates a new mock instance. -func NewMockIFlagdContainerInjector(ctrl *gomock.Controller) *MockIFlagdContainerInjector { - mock := &MockIFlagdContainerInjector{ctrl: ctrl} - mock.recorder = &MockIFlagdContainerInjectorMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockIFlagdContainerInjector) EXPECT() *MockIFlagdContainerInjectorMockRecorder { - return m.recorder -} - -// EnableClusterRoleBinding mocks base method. -func (m *MockIFlagdContainerInjector) EnableClusterRoleBinding(ctx context.Context, namespace, serviceAccountName string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EnableClusterRoleBinding", ctx, namespace, serviceAccountName) - ret0, _ := ret[0].(error) - return ret0 -} - -// EnableClusterRoleBinding indicates an expected call of EnableClusterRoleBinding. -func (mr *MockIFlagdContainerInjectorMockRecorder) EnableClusterRoleBinding(ctx, namespace, serviceAccountName interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableClusterRoleBinding", reflect.TypeOf((*MockIFlagdContainerInjector)(nil).EnableClusterRoleBinding), ctx, namespace, serviceAccountName) -} - -// InjectFlagd mocks base method. -func (m *MockIFlagdContainerInjector) InjectFlagd(ctx context.Context, objectMeta *v10.ObjectMeta, podSpec *v1.PodSpec, flagSourceConfig *v1alpha1.FlagSourceConfigurationSpec) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "InjectFlagd", ctx, objectMeta, podSpec, flagSourceConfig) - ret0, _ := ret[0].(error) - return ret0 -} - -// InjectFlagd indicates an expected call of InjectFlagd. -func (mr *MockIFlagdContainerInjectorMockRecorder) InjectFlagd(ctx, objectMeta, podSpec, flagSourceConfig interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InjectFlagd", reflect.TypeOf((*MockIFlagdContainerInjector)(nil).InjectFlagd), ctx, objectMeta, podSpec, flagSourceConfig) -} diff --git a/controllers/core/featureflagconfiguration/controller.go b/controllers/core/featureflagconfiguration/controller.go deleted file mode 100644 index 564a438e5..000000000 --- a/controllers/core/featureflagconfiguration/controller.go +++ /dev/null @@ -1,187 +0,0 @@ -/* -Copyright 2022. - -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 featureflagconfiguration - -import ( - "context" - - "github.com/go-logr/logr" - corev1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - "github.com/open-feature/open-feature-operator/controllers/common" - "github.com/open-feature/open-feature-operator/pkg/utils" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/predicate" -) - -// FeatureFlagConfigurationReconciler reconciles a FeatureFlagConfiguration object -type FeatureFlagConfigurationReconciler struct { - client.Client - - // Scheme contains the scheme of this controller - Scheme *runtime.Scheme - // ReqLogger contains the Logger of this controller - Log logr.Logger -} - -//+kubebuilder:rbac:groups=core.openfeature.dev,resources=featureflagconfigurations,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=core.openfeature.dev,resources=featureflagconfigurations/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=core.openfeature.dev,resources=featureflagconfigurations/finalizers,verbs=update - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the FeatureFlagConfiguration object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.0/pkg/reconcile - -const CrdName = "FeatureFlagConfiguration" - -//nolint:gocognit,gocyclo -func (r *FeatureFlagConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - r.Log.Info("Reconciling" + CrdName) - - ffconf := &corev1alpha1.FeatureFlagConfiguration{} - if err := r.Client.Get(ctx, req.NamespacedName, ffconf); err != nil { - if errors.IsNotFound(err) { - // taking down all associated K8s resources is handled by K8s - r.Log.Info(CrdName + " resource not found. Ignoring since object must be deleted") - return r.finishReconcile(nil, false) - } - r.Log.Error(err, "Failed to get the "+CrdName) - return r.finishReconcile(err, false) - } - - if ffconf.ObjectMeta.DeletionTimestamp.IsZero() { - // The object is not being deleted, so if it does not have our finalizer, - // then lets add the finalizer and update the object. This is equivalent - // registering our finalizer. - if !utils.ContainsString(ffconf.GetFinalizers(), common.FinalizerName) { - controllerutil.AddFinalizer(ffconf, common.FinalizerName) - if err := r.Update(ctx, ffconf); err != nil { - return r.finishReconcile(err, false) - } - } - } else { - // The object is being deleted - if utils.ContainsString(ffconf.GetFinalizers(), common.FinalizerName) { - controllerutil.RemoveFinalizer(ffconf, common.FinalizerName) - if err := r.Update(ctx, ffconf); err != nil { - return ctrl.Result{}, err - } - } - // Stop reconciliation as the item is being deleted - return r.finishReconcile(nil, false) - } - - // Check the provider on the FeatureFlagConfiguration - if !ffconf.Spec.ServiceProvider.IsSet() { - r.Log.Info("No service provider specified for FeatureFlagConfiguration, using FlagD") - ffconf.Spec.ServiceProvider = &corev1alpha1.FeatureFlagServiceProvider{ - Name: "flagd", - } - if err := r.Update(ctx, ffconf); err != nil { - r.Log.Error(err, "Failed to update FeatureFlagConfiguration service provider") - return r.finishReconcile(err, false) - } - } - - // Get list of configmaps - configMapList := &corev1.ConfigMapList{} - var ffConfigMapList []corev1.ConfigMap - if err := r.List(ctx, configMapList); err != nil { - return r.finishReconcile(err, false) - } - - // Get list of configmaps with annotation - for _, cm := range configMapList.Items { - val, ok := cm.GetAnnotations()["openfeature.dev/featureflagconfiguration"] - if ok && val == ffconf.Name { - ffConfigMapList = append(ffConfigMapList, cm) - } - } - - for _, cm := range ffConfigMapList { - // Append OwnerReference if not set - if !r.featureFlagResourceIsOwner(ffconf, cm) { - r.Log.Info("Setting owner reference for " + cm.Name) - cm.OwnerReferences = append(cm.OwnerReferences, ffconf.GetReference()) - err := r.Client.Update(ctx, &cm) - if err != nil { - return r.finishReconcile(err, true) - } - } else if len(cm.OwnerReferences) == 1 { - // Delete ConfigMap if the Controller is the only reference - r.Log.Info("Deleting configmap " + cm.Name) - err := r.Client.Delete(ctx, &cm) - return r.finishReconcile(err, true) - } - // Update ConfigMap Spec - r.Log.Info("Updating ConfigMap Spec " + cm.Name) - cm.Data = map[string]string{ - utils.FeatureFlagConfigurationConfigMapKey(cm.Namespace, cm.Name): ffconf.Spec.FeatureFlagSpec, - } - err := r.Client.Update(ctx, &cm) - if err != nil { - return r.finishReconcile(err, true) - } - } - - return r.finishReconcile(nil, false) -} - -func (r *FeatureFlagConfigurationReconciler) finishReconcile(err error, requeueImmediate bool) (ctrl.Result, error) { - if err != nil { - interval := common.ReconcileErrorInterval - if requeueImmediate { - interval = 0 - } - r.Log.Error(err, "Finished Reconciling "+CrdName+" with error: %w") - return ctrl.Result{Requeue: true, RequeueAfter: interval}, err - } - interval := common.ReconcileSuccessInterval - if requeueImmediate { - interval = 0 - } - r.Log.Info("Finished Reconciling " + CrdName) - return ctrl.Result{Requeue: true, RequeueAfter: interval}, nil -} - -func (r *FeatureFlagConfigurationReconciler) featureFlagResourceIsOwner(ff *corev1alpha1.FeatureFlagConfiguration, cm corev1.ConfigMap) bool { - for _, cmOwner := range cm.OwnerReferences { - if cmOwner.UID == ff.GetReference().UID { - return true - } - } - return false -} - -// SetupWithManager sets up the controller with the Manager. -func (r *FeatureFlagConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&corev1alpha1.FeatureFlagConfiguration{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). - Owns(&corev1.ConfigMap{}). - Complete(r) -} diff --git a/controllers/core/featureflagconfiguration/controller_test.go b/controllers/core/featureflagconfiguration/controller_test.go deleted file mode 100644 index f26d7d0f0..000000000 --- a/controllers/core/featureflagconfiguration/controller_test.go +++ /dev/null @@ -1,175 +0,0 @@ -package featureflagconfiguration - -import ( - "context" - "testing" - - "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - "github.com/open-feature/open-feature-operator/pkg/utils" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" -) - -func TestFlagSourceConfigurationReconciler_Reconcile(t *testing.T) { - const ( - testNamespace = "test-namespace" - ffConfigName = "test-config" - cmName = "test-cm" - ) - - tests := []struct { - name string - ffConfig *v1alpha1.FeatureFlagConfiguration - cm *corev1.ConfigMap - wantProvider string - wantCM *corev1.ConfigMap - cmDeleted bool - }{ - { - name: "no provider set + no owner set -> ffconfig and cm will be updated", - cm: &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: cmName, - Namespace: testNamespace, - Annotations: map[string]string{ - "openfeature.dev/featureflagconfiguration": ffConfigName, - }, - }, - }, - ffConfig: createTestFFConfig(ffConfigName, testNamespace, cmName, ""), - wantProvider: "flagd", - wantCM: &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: cmName, - Namespace: testNamespace, - Annotations: map[string]string{ - "openfeature.dev/featureflagconfiguration": ffConfigName, - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "core.openfeature.dev/v1alpha1", - Kind: "FeatureFlagConfiguration", - Name: ffConfigName, - }, - }, - }, - Data: map[string]string{ - utils.FeatureFlagConfigurationConfigMapKey(testNamespace, cmName): "spec", - }, - }, - cmDeleted: false, - }, - { - name: "one owner ref set -> cm will be deleted", - cm: &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: cmName, - Namespace: testNamespace, - Annotations: map[string]string{ - "openfeature.dev/featureflagconfiguration": ffConfigName, - }, - }, - }, - ffConfig: createTestFFConfig(ffConfigName, testNamespace, cmName, ""), - wantProvider: "flagd", - wantCM: &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: cmName, - Namespace: testNamespace, - Annotations: map[string]string{ - "openfeature.dev/featureflagconfiguration": ffConfigName, - }, - }, - }, - cmDeleted: true, - }, - } - - err := v1alpha1.AddToScheme(scheme.Scheme) - require.Nil(t, err) - req := ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: testNamespace, - Name: ffConfigName, - }, - } - - ctx := context.TODO() - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // set up k8s fake client - fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.ffConfig, tt.cm).Build() - - r := &FeatureFlagConfigurationReconciler{ - Client: fakeClient, - Log: ctrl.Log.WithName("featureflagconfiguration-controller"), - Scheme: fakeClient.Scheme(), - } - - if tt.cmDeleted { - // if configMap should be deleted, we need to set ffConfig as the only OwnerRef before executing the Reconcile function - ffConfig := &v1alpha1.FeatureFlagConfiguration{} - err = fakeClient.Get(ctx, types.NamespacedName{Name: ffConfigName, Namespace: testNamespace}, ffConfig) - require.Nil(t, err) - - cm := &corev1.ConfigMap{} - err = fakeClient.Get(ctx, types.NamespacedName{Name: cmName, Namespace: testNamespace}, cm) - require.Nil(t, err) - - cm.OwnerReferences = append(cm.OwnerReferences, ffConfig.GetReference()) - err := r.Client.Update(ctx, cm) - require.Nil(t, err) - } - - // reconcile - _, err = r.Reconcile(ctx, req) - require.Nil(t, err) - - ffConfig := &v1alpha1.FeatureFlagConfiguration{} - err = fakeClient.Get(ctx, types.NamespacedName{Name: ffConfigName, Namespace: testNamespace}, ffConfig) - require.Nil(t, err) - - // check that the provider name is set correctly - require.Equal(t, tt.wantProvider, ffConfig.Spec.ServiceProvider.Name) - - cm := &corev1.ConfigMap{} - err = fakeClient.Get(ctx, types.NamespacedName{Name: cmName, Namespace: testNamespace}, cm) - - if !tt.cmDeleted { - // if configMap should not be deleted, check the correct values - require.Nil(t, err) - require.Equal(t, tt.wantCM.Data, cm.Data) - require.Len(t, cm.OwnerReferences, len(tt.wantCM.OwnerReferences)) - require.Equal(t, tt.wantCM.OwnerReferences[0].APIVersion, cm.OwnerReferences[0].APIVersion) - require.Equal(t, tt.wantCM.OwnerReferences[0].Name, cm.OwnerReferences[0].Name) - require.Equal(t, tt.wantCM.OwnerReferences[0].Kind, cm.OwnerReferences[0].Kind) - } else { - // if configMap should be deleted, we expect error - require.NotNil(t, err) - } - }) - } -} - -func createTestFFConfig(ffConfigName string, testNamespace string, cmName string, provider string) *v1alpha1.FeatureFlagConfiguration { - fsConfig := &v1alpha1.FeatureFlagConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: ffConfigName, - Namespace: testNamespace, - }, - Spec: v1alpha1.FeatureFlagConfigurationSpec{ - ServiceProvider: &v1alpha1.FeatureFlagServiceProvider{ - Name: provider, - }, - FeatureFlagSpec: "spec", - }, - } - - return fsConfig -} diff --git a/controllers/core/flagsourceconfiguration/controller.go b/controllers/core/featureflagsource/controller.go similarity index 69% rename from controllers/core/flagsourceconfiguration/controller.go rename to controllers/core/featureflagsource/controller.go index 07894641d..93c62e87c 100644 --- a/controllers/core/flagsourceconfiguration/controller.go +++ b/controllers/core/featureflagsource/controller.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package flagsourceconfiguration +package featureflagsource import ( "context" @@ -23,8 +23,9 @@ import ( "time" "github.com/go-logr/logr" - corev1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - "github.com/open-feature/open-feature-operator/controllers/common" + api "github.com/open-feature/open-feature-operator/apis/core/v1beta1" + "github.com/open-feature/open-feature-operator/common" + "github.com/open-feature/open-feature-operator/common/flagdproxy" appsV1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -34,20 +35,20 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" ) -// FlagSourceConfigurationReconciler reconciles a FlagSourceConfiguration object -type FlagSourceConfigurationReconciler struct { +// FeatureFlagSourceReconciler reconciles a FeatureFlagSource object +type FeatureFlagSourceReconciler struct { client.Client Scheme *runtime.Scheme // ReqLogger contains the Logger of this controller Log logr.Logger - FlagdProxy *common.FlagdProxyHandler + FlagdProxy *flagdproxy.FlagdProxyHandler } -//+kubebuilder:rbac:groups=core.openfeature.dev,resources=flagsourceconfigurations,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=core.openfeature.dev,resources=flagsourceconfigurations/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=core.openfeature.dev,resources=featureflagsources,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=core.openfeature.dev,resources=featureflagsources/status,verbs=get;update;patch //+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups="",resources=services,verbs=get;list;create -//+kubebuilder:rbac:groups=core.openfeature.dev,resources=flagsourceconfigurations/finalizers,verbs=update +//+kubebuilder:rbac:groups=core.openfeature.dev,resources=featureflagsources/finalizers,verbs=update // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -56,11 +57,11 @@ type FlagSourceConfigurationReconciler struct { // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile // //nolint:gocyclo -func (r *FlagSourceConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - r.Log.Info("Searching for FlagSourceConfiguration") +func (r *FeatureFlagSourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Log.Info("Searching for FeatureFlagSource") - // Fetch the FlagSourceConfiguration from the cache - fsConfig := &corev1alpha1.FlagSourceConfiguration{} + // Fetch the FeatureFlagSource from the cache + fsConfig := &api.FeatureFlagSource{} if err := r.Client.Get(ctx, req.NamespacedName, fsConfig); err != nil { if errors.IsNotFound(err) { // taking down all associated K8s resources is handled by K8s @@ -73,8 +74,8 @@ func (r *FlagSourceConfigurationReconciler) Reconcile(ctx context.Context, req c for _, source := range fsConfig.Spec.Sources { if source.Provider.IsFlagdProxy() { - r.Log.Info(fmt.Sprintf("flagsourceconfiguration %s uses flagd-proxy, checking deployment", req.NamespacedName)) - if err := r.FlagdProxy.HandleFlagdProxy(ctx, fsConfig); err != nil { + r.Log.Info(fmt.Sprintf("featureflagsource %s uses flagd-proxy, checking deployment", req.NamespacedName)) + if err := r.FlagdProxy.HandleFlagdProxy(ctx); err != nil { r.Log.Error(err, "error handling the flagd-proxy deployment") } break @@ -90,17 +91,17 @@ func (r *FlagSourceConfigurationReconciler) Reconcile(ctx context.Context, req c // and our resource exists within the cluster deployList := &appsV1.DeploymentList{} if err := r.Client.List(ctx, deployList, client.MatchingFields{ - fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationPath, common.FlagSourceConfigurationAnnotation): "true", + fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationPath, common.FeatureFlagSourceAnnotation): "true", }); err != nil { - r.Log.Error(err, fmt.Sprintf("Failed to get the pods with annotation %s/%s", common.OpenFeatureAnnotationPath, common.FlagSourceConfigurationAnnotation)) + r.Log.Error(err, fmt.Sprintf("Failed to get the pods with annotation %s/%s", common.OpenFeatureAnnotationPath, common.FeatureFlagSourceAnnotation)) return r.finishReconcile(err, false) } - // Loop through all deployments containing the openfeature.dev/flagsourceconfiguration annotation + // Loop through all deployments containing the openfeature.dev/featureflagsource annotation // and trigger a restart for any which have our resource listed as a configuration for _, deployment := range deployList.Items { annotations := deployment.Spec.Template.Annotations - annotation, ok := annotations[fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationRoot, common.FlagSourceConfigurationAnnotation)] + annotation, ok := annotations[fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationRoot, common.FeatureFlagSourceAnnotation)] if !ok { continue } @@ -117,7 +118,7 @@ func (r *FlagSourceConfigurationReconciler) Reconcile(ctx context.Context, req c return r.finishReconcile(nil, false) } -func (r *FlagSourceConfigurationReconciler) isUsingConfiguration(namespace string, name string, deploymentNamespace string, annotation string) bool { +func (r *FeatureFlagSourceReconciler) isUsingConfiguration(namespace string, name string, deploymentNamespace string, annotation string) bool { s := strings.Split(annotation, ",") // parse annotation list for _, target := range s { ss := strings.Split(strings.TrimSpace(target), "/") @@ -131,23 +132,23 @@ func (r *FlagSourceConfigurationReconciler) isUsingConfiguration(namespace strin return false } -func (r *FlagSourceConfigurationReconciler) finishReconcile(err error, requeueImmediate bool) (ctrl.Result, error) { +func (r *FeatureFlagSourceReconciler) finishReconcile(err error, requeueImmediate bool) (ctrl.Result, error) { if err != nil { interval := common.ReconcileErrorInterval if requeueImmediate { interval = 0 } - r.Log.Error(err, "Finished Reconciling FlagSourceConfiguration with error: %w") + r.Log.Error(err, "Finished Reconciling FeatureFlagSource with error: %w") return ctrl.Result{Requeue: true, RequeueAfter: interval}, err } - r.Log.Info("Finished Reconciling FlagSourceConfiguration") + r.Log.Info("Finished Reconciling FeatureFlagSource") return ctrl.Result{Requeue: false}, nil } // SetupWithManager sets up the controller with the Manager. -func (r *FlagSourceConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *FeatureFlagSourceReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&corev1alpha1.FlagSourceConfiguration{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + For(&api.FeatureFlagSource{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). // we are only interested in update events for this reconciliation loop WithEventFilter(predicate.GenerationChangedPredicate{}). Complete(r) diff --git a/controllers/core/flagsourceconfiguration/controller_test.go b/controllers/core/featureflagsource/controller_test.go similarity index 75% rename from controllers/core/flagsourceconfiguration/controller_test.go rename to controllers/core/featureflagsource/controller_test.go index 39a905e03..e2693698f 100644 --- a/controllers/core/flagsourceconfiguration/controller_test.go +++ b/controllers/core/featureflagsource/controller_test.go @@ -1,4 +1,4 @@ -package flagsourceconfiguration +package featureflagsource import ( "context" @@ -6,8 +6,10 @@ import ( "testing" "time" - "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - "github.com/open-feature/open-feature-operator/controllers/common" + api "github.com/open-feature/open-feature-operator/apis/core/v1beta1" + apicommon "github.com/open-feature/open-feature-operator/apis/core/v1beta1/common" + "github.com/open-feature/open-feature-operator/common" + "github.com/open-feature/open-feature-operator/common/flagdproxy" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -19,7 +21,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" ) -func TestFlagSourceConfigurationReconciler_Reconcile(t *testing.T) { +func TestFeatureFlagSourceReconciler_Reconcile(t *testing.T) { const ( testNamespace = "test-namespace" fsConfigName = "test-config" @@ -28,7 +30,7 @@ func TestFlagSourceConfigurationReconciler_Reconcile(t *testing.T) { tests := []struct { name string - fsConfig *v1alpha1.FlagSourceConfiguration + fsConfig *api.FeatureFlagSource deployment *appsv1.Deployment restartedAtValueBeforeReconcile string restartedAtValueAfterReconcile string @@ -36,28 +38,28 @@ func TestFlagSourceConfigurationReconciler_Reconcile(t *testing.T) { }{ { name: "deployment gets restarted with rollout", - fsConfig: createTestFSConfig(fsConfigName, testNamespace, deploymentName, true, v1alpha1.SyncProviderHttp), + fsConfig: createTestFSConfig(fsConfigName, testNamespace, deploymentName, true, apicommon.SyncProviderHttp), deployment: createTestDeployment(fsConfigName, testNamespace, deploymentName), restartedAtValueBeforeReconcile: "", restartedAtValueAfterReconcile: time.Now().Format(time.RFC3339), }, { name: "deployment without rollout", - fsConfig: createTestFSConfig(fsConfigName, testNamespace, deploymentName, false, v1alpha1.SyncProviderHttp), + fsConfig: createTestFSConfig(fsConfigName, testNamespace, deploymentName, false, apicommon.SyncProviderHttp), deployment: createTestDeployment(fsConfigName, testNamespace, deploymentName), restartedAtValueBeforeReconcile: "", restartedAtValueAfterReconcile: "", }, { name: "no deployment", - fsConfig: createTestFSConfig(fsConfigName, testNamespace, deploymentName, true, v1alpha1.SyncProviderHttp), + fsConfig: createTestFSConfig(fsConfigName, testNamespace, deploymentName, true, apicommon.SyncProviderHttp), deployment: nil, restartedAtValueBeforeReconcile: "", restartedAtValueAfterReconcile: "", }, { name: "no deployment, kube proxy deployment", - fsConfig: createTestFSConfig(fsConfigName, testNamespace, deploymentName, true, v1alpha1.SyncProviderFlagdProxy), + fsConfig: createTestFSConfig(fsConfigName, testNamespace, deploymentName, true, apicommon.SyncProviderFlagdProxy), deployment: nil, restartedAtValueBeforeReconcile: "", restartedAtValueAfterReconcile: "", @@ -65,7 +67,7 @@ func TestFlagSourceConfigurationReconciler_Reconcile(t *testing.T) { }, } - err := v1alpha1.AddToScheme(scheme.Scheme) + err := api.AddToScheme(scheme.Scheme) require.Nil(t, err) req := ctrl.Request{ @@ -82,23 +84,23 @@ func TestFlagSourceConfigurationReconciler_Reconcile(t *testing.T) { // setting up fake k8s client var fakeClient client.Client if tt.deployment != nil { - fakeClient = fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.fsConfig, tt.deployment).WithIndex(&appsv1.Deployment{}, fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationPath, common.FlagSourceConfigurationAnnotation), common.FlagSourceConfigurationIndex).Build() + fakeClient = fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.fsConfig, tt.deployment).WithIndex(&appsv1.Deployment{}, fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationPath, common.FeatureFlagSourceAnnotation), common.FeatureFlagSourceIndex).Build() } else { - fakeClient = fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.fsConfig).WithIndex(&appsv1.Deployment{}, fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationPath, common.FlagSourceConfigurationAnnotation), common.FlagSourceConfigurationIndex).Build() + fakeClient = fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.fsConfig).WithIndex(&appsv1.Deployment{}, fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationPath, common.FeatureFlagSourceAnnotation), common.FeatureFlagSourceIndex).Build() } - kpConfig, err := common.NewFlagdProxyConfiguration() + kpConfig, err := flagdproxy.NewFlagdProxyConfiguration() require.Nil(t, err) kpConfig.Namespace = testNamespace - kph := common.NewFlagdProxyHandler( + kph := flagdproxy.NewFlagdProxyHandler( kpConfig, fakeClient, - ctrl.Log.WithName("flagsourceconfiguration-FlagdProxyhandler"), + ctrl.Log.WithName("featureflagsource-FlagdProxyhandler"), ) - r := &FlagSourceConfigurationReconciler{ + r := &FeatureFlagSourceReconciler{ Client: fakeClient, - Log: ctrl.Log.WithName("flagsourceconfiguration-controller"), + Log: ctrl.Log.WithName("featureflagsource-controller"), Scheme: fakeClient.Scheme(), FlagdProxy: kph, } @@ -129,14 +131,14 @@ func TestFlagSourceConfigurationReconciler_Reconcile(t *testing.T) { // check that a deployment exists in the default namespace with the correct image and tag // ensure that the associated service has also been deployed deployment := &appsv1.Deployment{} - err = fakeClient.Get(ctx, types.NamespacedName{Name: common.FlagdProxyDeploymentName, Namespace: testNamespace}, deployment) + err = fakeClient.Get(ctx, types.NamespacedName{Name: flagdproxy.FlagdProxyDeploymentName, Namespace: testNamespace}, deployment) require.Nil(t, err) require.Equal(t, len(deployment.Spec.Template.Spec.Containers), 1) require.Equal(t, len(deployment.Spec.Template.Spec.Containers[0].Ports), 2) - require.Equal(t, deployment.Spec.Template.Spec.Containers[0].Image, fmt.Sprintf("%s:%s", common.DefaultFlagdProxyImage, common.DefaultFlagdProxyTag)) + require.Equal(t, deployment.Spec.Template.Spec.Containers[0].Image, fmt.Sprintf("%s:%s", flagdproxy.DefaultFlagdProxyImage, flagdproxy.DefaultFlagdProxyTag)) service := &corev1.Service{} - err = fakeClient.Get(ctx, types.NamespacedName{Name: common.FlagdProxyServiceName, Namespace: testNamespace}, service) + err = fakeClient.Get(ctx, types.NamespacedName{Name: flagdproxy.FlagdProxyServiceName, Namespace: testNamespace}, service) require.Nil(t, err) require.Equal(t, len(service.Spec.Ports), 1) require.Equal(t, service.Spec.Ports[0].TargetPort.IntVal, deployment.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort) @@ -155,8 +157,8 @@ func createTestDeployment(fsConfigName string, testNamespace string, deploymentN Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationPath, common.FlagSourceConfigurationAnnotation): "true", - fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationRoot, common.FlagSourceConfigurationAnnotation): fmt.Sprintf("%s/%s", testNamespace, fsConfigName), + fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationPath, common.FeatureFlagSourceAnnotation): "true", + fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationRoot, common.FeatureFlagSourceAnnotation): fmt.Sprintf("%s/%s", testNamespace, fsConfigName), }, Labels: map[string]string{ "app": "test", @@ -186,15 +188,15 @@ func createTestDeployment(fsConfigName string, testNamespace string, deploymentN return deployment } -func createTestFSConfig(fsConfigName string, testNamespace string, deploymentName string, rollout bool, provider v1alpha1.SyncProviderType) *v1alpha1.FlagSourceConfiguration { - fsConfig := &v1alpha1.FlagSourceConfiguration{ +func createTestFSConfig(fsConfigName string, testNamespace string, deploymentName string, rollout bool, provider apicommon.SyncProviderType) *api.FeatureFlagSource { + fsConfig := &api.FeatureFlagSource{ ObjectMeta: metav1.ObjectMeta{ Name: fsConfigName, Namespace: testNamespace, }, - Spec: v1alpha1.FlagSourceConfigurationSpec{ + Spec: api.FeatureFlagSourceSpec{ Image: deploymentName, - Sources: []v1alpha1.Source{ + Sources: []api.Source{ { Source: "my-source", Provider: provider, diff --git a/docs/crds.md b/docs/crds.md index a1dd3fc05..3c0832885 100644 --- a/docs/crds.md +++ b/docs/crds.md @@ -2,2613 +2,28 @@ Packages: -- [core.openfeature.dev/v1alpha1](#coreopenfeaturedevv1alpha1) -- [core.openfeature.dev/v1alpha2](#coreopenfeaturedevv1alpha2) - [core.openfeature.dev/v1beta1](#coreopenfeaturedevv1beta1) -- [core.openfeature.dev/v1alpha3](#coreopenfeaturedevv1alpha3) -# core.openfeature.dev/v1alpha1 - -Resource Types: - -- [FeatureFlagConfiguration](#featureflagconfiguration) - -- [FlagSourceConfiguration](#flagsourceconfiguration) - - - - -## FeatureFlagConfiguration -[↩ Parent](#coreopenfeaturedevv1alpha1 ) - - - - - - -FeatureFlagConfiguration is the Schema for the featureflagconfigurations API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
apiVersionstringcore.openfeature.dev/v1alpha1true
kindstringFeatureFlagConfigurationtrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject - FeatureFlagConfigurationSpec defines the desired state of FeatureFlagConfiguration
-
false
statusobject - FeatureFlagConfigurationStatus defines the observed state of FeatureFlagConfiguration
-
false
- - -### FeatureFlagConfiguration.spec -[↩ Parent](#featureflagconfiguration) - - - -FeatureFlagConfigurationSpec defines the desired state of FeatureFlagConfiguration - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
featureFlagSpecstring - FeatureFlagSpec is the json representation of the feature flag
-
false
flagDSpecobject - FlagDSpec [DEPRECATED]: superseded by FlagSourceConfiguration
-
false
serviceProviderobject - ServiceProvider [DEPRECATED]: superseded by FlagSourceConfiguration
-
false
syncProviderobject - SyncProvider [DEPRECATED]: superseded by FlagSourceConfiguration
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec -[↩ Parent](#featureflagconfigurationspec) - - - -FlagDSpec [DEPRECATED]: superseded by FlagSourceConfiguration - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
envs[]object -
-
false
metricsPortinteger -
-
- Format: int32
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec.envs[index] -[↩ Parent](#featureflagconfigurationspecflagdspec) - - - -EnvVar represents an environment variable present in a Container. - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
namestring - Name of the environment variable. Must be a C_IDENTIFIER.
-
true
valuestring - Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".
-
false
valueFromobject - Source for the environment variable's value. Cannot be used if value is not empty.
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec.envs[index].valueFrom -[↩ Parent](#featureflagconfigurationspecflagdspecenvsindex) - - - -Source for the environment variable's value. Cannot be used if value is not empty. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
configMapKeyRefobject - Selects a key of a ConfigMap.
-
false
fieldRefobject - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['<KEY>']`, `metadata.annotations['<KEY>']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.
-
false
resourceFieldRefobject - Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.
-
false
secretKeyRefobject - Selects a key of a secret in the pod's namespace
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec.envs[index].valueFrom.configMapKeyRef -[↩ Parent](#featureflagconfigurationspecflagdspecenvsindexvaluefrom) - - - -Selects a key of a ConfigMap. - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
keystring - The key to select.
-
true
namestring - Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?
-
false
optionalboolean - Specify whether the ConfigMap or its key must be defined
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec.envs[index].valueFrom.fieldRef -[↩ Parent](#featureflagconfigurationspecflagdspecenvsindexvaluefrom) - - - -Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['<KEY>']`, `metadata.annotations['<KEY>']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
fieldPathstring - Path of the field to select in the specified API version.
-
true
apiVersionstring - Version of the schema the FieldPath is written in terms of, defaults to "v1".
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec.envs[index].valueFrom.resourceFieldRef -[↩ Parent](#featureflagconfigurationspecflagdspecenvsindexvaluefrom) - - - -Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
resourcestring - Required: resource to select
-
true
containerNamestring - Container name: required for volumes, optional for env vars
-
false
divisorint or string - Specifies the output format of the exposed resources, defaults to "1"
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec.envs[index].valueFrom.secretKeyRef -[↩ Parent](#featureflagconfigurationspecflagdspecenvsindexvaluefrom) - - - -Selects a key of a secret in the pod's namespace - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
keystring - The key of the secret to select from. Must be a valid secret key.
-
true
namestring - Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?
-
false
optionalboolean - Specify whether the Secret or its key must be defined
-
false
- - -### FeatureFlagConfiguration.spec.serviceProvider -[↩ Parent](#featureflagconfigurationspec) - - - -ServiceProvider [DEPRECATED]: superseded by FlagSourceConfiguration - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
nameenum -
-
- Enum: flagd
-
true
credentialsobject - ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. - Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .
-
false
- - -### FeatureFlagConfiguration.spec.serviceProvider.credentials -[↩ Parent](#featureflagconfigurationspecserviceprovider) - - - -ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. - Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
apiVersionstring - API version of the referent.
-
false
fieldPathstring - If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.
-
false
kindstring - Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
-
false
namestring - Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
-
false
namespacestring - Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
-
false
resourceVersionstring - Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
-
false
uidstring - UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
-
false
- - -### FeatureFlagConfiguration.spec.syncProvider -[↩ Parent](#featureflagconfigurationspec) - - - -SyncProvider [DEPRECATED]: superseded by FlagSourceConfiguration - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
namestring -
-
true
httpSyncConfigurationobject - HttpSyncConfiguration defines the desired configuration for a http sync
-
false
- - -### FeatureFlagConfiguration.spec.syncProvider.httpSyncConfiguration -[↩ Parent](#featureflagconfigurationspecsyncprovider) - - - -HttpSyncConfiguration defines the desired configuration for a http sync - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
targetstring - Target is the target url for flagd to poll
-
true
bearerTokenstring -
-
false
- -## FlagSourceConfiguration -[↩ Parent](#coreopenfeaturedevv1alpha1 ) - - - - - - -FlagSourceConfiguration is the Schema for the FlagSourceConfigurations API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
apiVersionstringcore.openfeature.dev/v1alpha1true
kindstringFlagSourceConfigurationtrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject - FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration
-
false
statusobject - FlagSourceConfigurationStatus defines the observed state of FlagSourceConfiguration
-
false
- - -### FlagSourceConfiguration.spec -[↩ Parent](#flagsourceconfiguration) - - - -FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
sources[]object - Sources defines the syncProviders and associated configuration to be applied to the sidecar
-
true
debugLoggingboolean - DebugLogging defines whether to enable --debug flag of flagd sidecar. Default false (disabled).
-
false
defaultSyncProviderstring - DefaultSyncProvider defines the default sync provider
-
false
envVarPrefixstring - EnvVarPrefix defines the prefix to be applied to all environment variables applied to the sidecar, default FLAGD
-
false
envVars[]object - EnvVars define the env vars to be applied to the sidecar, any env vars in FeatureFlagConfiguration CRs are added at the lowest index, all values will have the EnvVarPrefix applied
-
false
evaluatorstring - Evaluator sets an evaluator, defaults to 'json'
-
false
imagestring - Image allows for the sidecar image to be overridden, defaults to 'ghcr.io/open-feature/flagd'
-
false
logFormatstring - LogFormat allows for the sidecar log format to be overridden, defaults to 'json'
-
false
metricsPortinteger - MetricsPort defines the port to serve metrics on, defaults to 8014
-
- Format: int32
-
false
otelCollectorUristring - OtelCollectorUri defines whether to enable --otel-collector-uri flag of flagd sidecar. Default false (disabled).
-
false
portinteger - Port defines the port to listen on, defaults to 8013
-
- Format: int32
-
false
probesEnabledboolean - ProbesEnabled defines whether to enable liveness and readiness probes of flagd sidecar. Default true (enabled).
-
false
resourcesobject - Resources defines flagd sidecar resources. Default to operator sidecar-cpu-* and sidecar-ram-* flags.
-
false
rolloutOnChangeboolean - RolloutOnChange dictates whether annotated deployments will be restarted when configuration changes are detected in this CR, defaults to false
-
false
socketPathstring - SocketPath defines the unix socket path to listen on
-
false
syncProviderArgs[]string - SyncProviderArgs are string arguments passed to all sync providers, defined as key values separated by =
-
false
tagstring - Tag to be appended to the sidecar image, defaults to 'main'
-
false
- - -### FlagSourceConfiguration.spec.sources[index] -[↩ Parent](#flagsourceconfigurationspec) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
sourcestring - Source is a URI of the flag sources
-
true
certPathstring - CertPath is a path of a certificate to be used by grpc TLS connection
-
false
httpSyncBearerTokenstring - HttpSyncBearerToken is a bearer token. Used by http(s) sync provider only
-
false
providerstring - Provider type - kubernetes, http, grpc or filepath
-
false
providerIDstring - ProviderID is an identifier to be used in grpc provider
-
false
selectorstring - Selector is a flag configuration selector used by grpc provider
-
false
tlsboolean - TLS - Enable/Disable secure TLS connectivity. Currently used only by GRPC sync
-
false
- - -### FlagSourceConfiguration.spec.envVars[index] -[↩ Parent](#flagsourceconfigurationspec) - - - -EnvVar represents an environment variable present in a Container. - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
namestring - Name of the environment variable. Must be a C_IDENTIFIER.
-
true
valuestring - Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".
-
false
valueFromobject - Source for the environment variable's value. Cannot be used if value is not empty.
-
false
- - -### FlagSourceConfiguration.spec.envVars[index].valueFrom -[↩ Parent](#flagsourceconfigurationspecenvvarsindex) - - - -Source for the environment variable's value. Cannot be used if value is not empty. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
configMapKeyRefobject - Selects a key of a ConfigMap.
-
false
fieldRefobject - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['<KEY>']`, `metadata.annotations['<KEY>']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.
-
false
resourceFieldRefobject - Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.
-
false
secretKeyRefobject - Selects a key of a secret in the pod's namespace
-
false
- - -### FlagSourceConfiguration.spec.envVars[index].valueFrom.configMapKeyRef -[↩ Parent](#flagsourceconfigurationspecenvvarsindexvaluefrom) - - - -Selects a key of a ConfigMap. - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
keystring - The key to select.
-
true
namestring - Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?
-
false
optionalboolean - Specify whether the ConfigMap or its key must be defined
-
false
- - -### FlagSourceConfiguration.spec.envVars[index].valueFrom.fieldRef -[↩ Parent](#flagsourceconfigurationspecenvvarsindexvaluefrom) - - - -Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['<KEY>']`, `metadata.annotations['<KEY>']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
fieldPathstring - Path of the field to select in the specified API version.
-
true
apiVersionstring - Version of the schema the FieldPath is written in terms of, defaults to "v1".
-
false
- - -### FlagSourceConfiguration.spec.envVars[index].valueFrom.resourceFieldRef -[↩ Parent](#flagsourceconfigurationspecenvvarsindexvaluefrom) - - - -Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
resourcestring - Required: resource to select
-
true
containerNamestring - Container name: required for volumes, optional for env vars
-
false
divisorint or string - Specifies the output format of the exposed resources, defaults to "1"
-
false
- - -### FlagSourceConfiguration.spec.envVars[index].valueFrom.secretKeyRef -[↩ Parent](#flagsourceconfigurationspecenvvarsindexvaluefrom) - - - -Selects a key of a secret in the pod's namespace - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
keystring - The key of the secret to select from. Must be a valid secret key.
-
true
namestring - Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?
-
false
optionalboolean - Specify whether the Secret or its key must be defined
-
false
- - -### FlagSourceConfiguration.spec.resources -[↩ Parent](#flagsourceconfigurationspec) - - - -Resources defines flagd sidecar resources. Default to operator sidecar-cpu-* and sidecar-ram-* flags. - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
claims[]object - Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers.
-
false
limitsmap[string]int or string - Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
-
false
requestsmap[string]int or string - Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
-
false
- - -### FlagSourceConfiguration.spec.resources.claims[index] -[↩ Parent](#flagsourceconfigurationspecresources) - - - -ResourceClaim references one entry in PodSpec.ResourceClaims. - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
namestring - Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container.
-
true
- -# core.openfeature.dev/v1alpha2 - -Resource Types: - -- [FeatureFlagConfiguration](#featureflagconfiguration) - -- [FlagSourceConfiguration](#flagsourceconfiguration) - - - - -## FeatureFlagConfiguration -[↩ Parent](#coreopenfeaturedevv1alpha2 ) - - - - - - -FeatureFlagConfiguration is the Schema for the featureflagconfigurations API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
apiVersionstringcore.openfeature.dev/v1alpha2true
kindstringFeatureFlagConfigurationtrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject - FeatureFlagConfigurationSpec defines the desired state of FeatureFlagConfiguration
-
false
statusobject - FeatureFlagConfigurationStatus defines the observed state of FeatureFlagConfiguration
-
false
- - -### FeatureFlagConfiguration.spec -[↩ Parent](#featureflagconfiguration-1) - - - -FeatureFlagConfigurationSpec defines the desired state of FeatureFlagConfiguration - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
featureFlagSpecobject - FeatureFlagSpec is the structured representation of the feature flag specification
-
false
flagDSpecobject - FlagDSpec [DEPRECATED]: superseded by FlagSourceConfiguration
-
false
resourcesobject - Resources defines flagd sidecar resources. Default to operator sidecar-cpu-* and sidecar-ram-* flags.
-
false
serviceProviderobject - ServiceProvider [DEPRECATED]: superseded by FlagSourceConfiguration
-
false
syncProviderobject - SyncProvider [DEPRECATED]: superseded by FlagSourceConfiguration
-
false
- - -### FeatureFlagConfiguration.spec.featureFlagSpec -[↩ Parent](#featureflagconfigurationspec-1) - - - -FeatureFlagSpec is the structured representation of the feature flag specification - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
flagsmap[string]object -
-
true
$evaluatorsobject -
-
false
- - -### FeatureFlagConfiguration.spec.featureFlagSpec.flags[key] -[↩ Parent](#featureflagconfigurationspecfeatureflagspec) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
defaultVariantstring -
-
true
stateenum -
-
- Enum: ENABLED, DISABLED
-
true
variantsobject -
-
true
targetingobject - Targeting is the json targeting rule
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec -[↩ Parent](#featureflagconfigurationspec-1) - - - -FlagDSpec [DEPRECATED]: superseded by FlagSourceConfiguration - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
envs[]object -
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec.envs[index] -[↩ Parent](#featureflagconfigurationspecflagdspec-1) - - - -EnvVar represents an environment variable present in a Container. - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
namestring - Name of the environment variable. Must be a C_IDENTIFIER.
-
true
valuestring - Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".
-
false
valueFromobject - Source for the environment variable's value. Cannot be used if value is not empty.
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec.envs[index].valueFrom -[↩ Parent](#featureflagconfigurationspecflagdspecenvsindex-1) - - - -Source for the environment variable's value. Cannot be used if value is not empty. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
configMapKeyRefobject - Selects a key of a ConfigMap.
-
false
fieldRefobject - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['<KEY>']`, `metadata.annotations['<KEY>']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.
-
false
resourceFieldRefobject - Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.
-
false
secretKeyRefobject - Selects a key of a secret in the pod's namespace
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec.envs[index].valueFrom.configMapKeyRef -[↩ Parent](#featureflagconfigurationspecflagdspecenvsindexvaluefrom-1) - - - -Selects a key of a ConfigMap. - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
keystring - The key to select.
-
true
namestring - Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?
-
false
optionalboolean - Specify whether the ConfigMap or its key must be defined
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec.envs[index].valueFrom.fieldRef -[↩ Parent](#featureflagconfigurationspecflagdspecenvsindexvaluefrom-1) - - - -Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['<KEY>']`, `metadata.annotations['<KEY>']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
fieldPathstring - Path of the field to select in the specified API version.
-
true
apiVersionstring - Version of the schema the FieldPath is written in terms of, defaults to "v1".
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec.envs[index].valueFrom.resourceFieldRef -[↩ Parent](#featureflagconfigurationspecflagdspecenvsindexvaluefrom-1) - - - -Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
resourcestring - Required: resource to select
-
true
containerNamestring - Container name: required for volumes, optional for env vars
-
false
divisorint or string - Specifies the output format of the exposed resources, defaults to "1"
-
false
- - -### FeatureFlagConfiguration.spec.flagDSpec.envs[index].valueFrom.secretKeyRef -[↩ Parent](#featureflagconfigurationspecflagdspecenvsindexvaluefrom-1) - - - -Selects a key of a secret in the pod's namespace - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
keystring - The key of the secret to select from. Must be a valid secret key.
-
true
namestring - Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?
-
false
optionalboolean - Specify whether the Secret or its key must be defined
-
false
- - -### FeatureFlagConfiguration.spec.resources -[↩ Parent](#featureflagconfigurationspec-1) - - - -Resources defines flagd sidecar resources. Default to operator sidecar-cpu-* and sidecar-ram-* flags. - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
claims[]object - Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers.
-
false
limitsmap[string]int or string - Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
-
false
requestsmap[string]int or string - Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
-
false
- - -### FeatureFlagConfiguration.spec.resources.claims[index] -[↩ Parent](#featureflagconfigurationspecresources) - - - -ResourceClaim references one entry in PodSpec.ResourceClaims. - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
namestring - Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container.
-
true
- - -### FeatureFlagConfiguration.spec.serviceProvider -[↩ Parent](#featureflagconfigurationspec-1) - - - -ServiceProvider [DEPRECATED]: superseded by FlagSourceConfiguration - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
nameenum -
-
- Enum: flagd
-
true
credentialsobject - ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. - Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .
-
false
- - -### FeatureFlagConfiguration.spec.serviceProvider.credentials -[↩ Parent](#featureflagconfigurationspecserviceprovider-1) - - - -ObjectReference contains enough information to let you inspect or modify the referred object. --- New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". Those cannot be well described when embedded. 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple and the version of the actual struct is irrelevant. 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. - Instead of using this type, create a locally provided and used type that is well-focused on your reference. For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
apiVersionstring - API version of the referent.
-
false
fieldPathstring - If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.
-
false
kindstring - Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
-
false
namestring - Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
-
false
namespacestring - Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
-
false
resourceVersionstring - Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
-
false
uidstring - UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
-
false
- - -### FeatureFlagConfiguration.spec.syncProvider -[↩ Parent](#featureflagconfigurationspec-1) - - - -SyncProvider [DEPRECATED]: superseded by FlagSourceConfiguration - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
namestring -
-
true
httpSyncConfigurationobject - HttpSyncConfiguration defines the desired configuration for a http sync
-
false
- - -### FeatureFlagConfiguration.spec.syncProvider.httpSyncConfiguration -[↩ Parent](#featureflagconfigurationspecsyncprovider-1) - - - -HttpSyncConfiguration defines the desired configuration for a http sync - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
targetstring - Target is the target url for flagd to poll
-
true
bearerTokenstring -
-
false
- -## FlagSourceConfiguration -[↩ Parent](#coreopenfeaturedevv1alpha2 ) - - - - - - -FlagSourceConfiguration is the Schema for the FlagSourceConfigurations API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
apiVersionstringcore.openfeature.dev/v1alpha2true
kindstringFlagSourceConfigurationtrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject - FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration
-
false
statusobject - FlagSourceConfigurationStatus defines the observed state of FlagSourceConfiguration
-
false
- - -### FlagSourceConfiguration.spec -[↩ Parent](#flagsourceconfiguration-1) - - - -FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
defaultSyncProviderstring - DefaultSyncProvider defines the default sync provider
-
false
evaluatorstring - Evaluator sets an evaluator, defaults to 'json'
-
false
imagestring - Image allows for the sidecar image to be overridden, defaults to 'ghcr.io/open-feature/flagd'
-
false
logFormatstring - LogFormat allows for the sidecar log format to be overridden, defaults to 'json'
-
false
metricsPortinteger - MetricsPort defines the port to serve metrics on, defaults to 8013
-
- Format: int32
-
false
otelCollectorUristring - OtelCollectorUri defines whether to enable --otel-collector-uri flag of flagd sidecar. Default false (disabled).
-
false
portinteger - Port defines the port to listen on, defaults to 8014
-
- Format: int32
-
false
probesEnabledboolean - ProbesEnabled defines whether to enable liveness and readiness probes of flagd sidecar. Default true (enabled).
-
false
socketPathstring - SocketPath defines the unix socket path to listen on
-
false
syncProviderArgs[]string - SyncProviderArgs are string arguments passed to all sync providers, defined as key values separated by =
-
false
tagstring - Tag to be appended to the sidecar image, defaults to 'main'
-
false
- -# core.openfeature.dev/v1beta1 - -Resource Types: - -- [FeatureFlag](#featureflag) - -- [FeatureFlagSource](#featureflagsource) - - - - -## FeatureFlag -[↩ Parent](#coreopenfeaturedevv1beta1 ) - - - - - - -FeatureFlag is the Schema for the featureflags API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
apiVersionstringcore.openfeature.dev/v1beta1true
kindstringFeatureFlagtrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject - FeatureFlagSpec defines the desired state of FeatureFlag
-
false
statusobject - FeatureFlagStatus defines the observed state of FeatureFlag
-
false
- - -### FeatureFlag.spec -[↩ Parent](#featureflag) - - - -FeatureFlagSpec defines the desired state of FeatureFlag - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
flagSpecobject - FlagSpec is the structured representation of the feature flag specification
-
false
- - -### FeatureFlag.spec.flagSpec -[↩ Parent](#featureflagspec) - - - -FlagSpec is the structured representation of the feature flag specification - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
flagsmap[string]object -
-
true
$evaluatorsobject -
-
false
- - -### FeatureFlag.spec.flagSpec.flags[key] -[↩ Parent](#featureflagspecflagspec) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
defaultVariantstring -
-
true
stateenum -
-
- Enum: ENABLED, DISABLED
-
true
variantsobject -
-
true
targetingobject - Targeting is the json targeting rule
-
false
- -## FeatureFlagSource -[↩ Parent](#coreopenfeaturedevv1beta1 ) - - - - - - -FeatureFlagSource is the Schema for the FeatureFlagSources API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
apiVersionstringcore.openfeature.dev/v1beta1true
kindstringFeatureFlagSourcetrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject - FeatureFlagSourceSpec defines the desired state of FeatureFlagSource
-
false
statusobject - FeatureFlagSourceStatus defines the observed state of FeatureFlagSource
-
false
- - -### FeatureFlagSource.spec -[↩ Parent](#featureflagsource) - - - -FeatureFlagSourceSpec defines the desired state of FeatureFlagSource - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
sources[]object - SyncProviders define the syncProviders and associated configuration to be applied to the sidecar
-
true
debugLoggingboolean - DebugLogging defines whether to enable --debug flag of flagd sidecar. Default false (disabled).
-
false
defaultSyncProviderstring - DefaultSyncProvider defines the default sync provider
-
false
envVarPrefixstring - EnvVarPrefix defines the prefix to be applied to all environment variables applied to the sidecar, default FLAGD
-
false
envVars[]object - EnvVars define the env vars to be applied to the sidecar, any env vars in FeatureFlagConfiguration CRs are added at the lowest index, all values will have the EnvVarPrefix applied, default FLAGD
-
false
evaluatorstring - Evaluator sets an evaluator, defaults to 'json'
-
false
imagestring - Image allows for the sidecar image to be overridden, defaults to 'ghcr.io/open-feature/flagd'
-
false
logFormatstring - LogFormat allows for the sidecar log format to be overridden, defaults to 'json'
-
false
metricsPortinteger - MetricsPort defines the port to serve metrics on, defaults to 8014
-
- Format: int32
-
false
otelCollectorUristring - OtelCollectorUri defines whether to enable --otel-collector-uri flag of flagd sidecar. Default false (disabled).
-
false
portinteger - Port defines the port to listen on, defaults to 8013
-
- Format: int32
-
false
probesEnabledboolean - ProbesEnabled defines whether to enable liveness and readiness probes of flagd sidecar. Default true (enabled).
-
false
resourcesobject - Resources defines flagd sidecar resources. Default to operator sidecar-cpu-* and sidecar-ram-* flags.
-
false
rolloutOnChangeboolean - RolloutOnChange dictates whether annotated deployments will be restarted when configuration changes are detected in this CR, defaults to false
-
false
socketPathstring - SocketPath defines the unix socket path to listen on
-
false
syncProviderArgs[]string - SyncProviderArgs are string arguments passed to all sync providers, defined as key values separated by =
-
false
tagstring - Tag to be appended to the sidecar image, defaults to 'main'
-
false
- - -### FeatureFlagSource.spec.sources[index] -[↩ Parent](#featureflagsourcespec) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
sourcestring - Source is a URI of the flag sources
-
true
certPathstring - CertPath is a path of a certificate to be used by grpc TLS connection
-
false
httpSyncBearerTokenstring - HttpSyncBearerToken is a bearer token. Used by http(s) sync provider only
-
false
providerstring - Provider type - kubernetes, http(s), grpc(s) or filepath
-
false
providerIDstring - ProviderID is an identifier to be used in grpc provider
-
false
selectorstring - Selector is a flag configuration selector used by grpc provider
-
false
tlsboolean - TLS - Enable/Disable secure TLS connectivity. Currently used only by GRPC sync
-
false
- - -### FeatureFlagSource.spec.envVars[index] -[↩ Parent](#featureflagsourcespec) - - - -EnvVar represents an environment variable present in a Container. - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
namestring - Name of the environment variable. Must be a C_IDENTIFIER.
-
true
valuestring - Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".
-
false
valueFromobject - Source for the environment variable's value. Cannot be used if value is not empty.
-
false
- - -### FeatureFlagSource.spec.envVars[index].valueFrom -[↩ Parent](#featureflagsourcespecenvvarsindex) - - - -Source for the environment variable's value. Cannot be used if value is not empty. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
configMapKeyRefobject - Selects a key of a ConfigMap.
-
false
fieldRefobject - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['<KEY>']`, `metadata.annotations['<KEY>']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.
-
false
resourceFieldRefobject - Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.
-
false
secretKeyRefobject - Selects a key of a secret in the pod's namespace
-
false
+# core.openfeature.dev/v1beta1 +Resource Types: -### FeatureFlagSource.spec.envVars[index].valueFrom.configMapKeyRef -[↩ Parent](#featureflagsourcespecenvvarsindexvaluefrom) +- [FeatureFlag](#featureflag) +- [FeatureFlagSource](#featureflagsource) -Selects a key of a ConfigMap. - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescriptionRequired
keystring - The key to select.
-
true
namestring - Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?
-
false
optionalboolean - Specify whether the ConfigMap or its key must be defined
-
false
+## FeatureFlag +[↩ Parent](#coreopenfeaturedevv1beta1 ) -### FeatureFlagSource.spec.envVars[index].valueFrom.fieldRef -[↩ Parent](#featureflagsourcespecenvvarsindexvaluefrom) -Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['<KEY>']`, `metadata.annotations['<KEY>']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + + +FeatureFlag is the Schema for the featureflags API @@ -2620,29 +35,46 @@ Selects a field of the pod: supports metadata.name, metadata.namespace, `metadat - - + + + + + + + + + + + + + + + + + + + - + - - + +
fieldPathstringapiVersionstringcore.openfeature.dev/v1beta1true
kindstringFeatureFlagtrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject - Path of the field to select in the specified API version.
+ FeatureFlagSpec defines the desired state of FeatureFlag
truefalse
apiVersionstringstatusobject - Version of the schema the FieldPath is written in terms of, defaults to "v1".
+ FeatureFlagStatus defines the observed state of FeatureFlag
false
-### FeatureFlagSource.spec.envVars[index].valueFrom.resourceFieldRef -[↩ Parent](#featureflagsourcespecenvvarsindexvaluefrom) +### FeatureFlag.spec +[↩ Parent](#featureflag) -Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. +FeatureFlagSpec defines the desired state of FeatureFlag @@ -2654,36 +86,22 @@ Selects a resource of the container: only resources limits and requests (limits. - - - - - - - - - - - - + +
resourcestring - Required: resource to select
-
true
containerNamestring - Container name: required for volumes, optional for env vars
-
false
divisorint or stringflagSpecobject - Specifies the output format of the exposed resources, defaults to "1"
+ FlagSpec is the structured representation of the feature flag specification
false
-### FeatureFlagSource.spec.envVars[index].valueFrom.secretKeyRef -[↩ Parent](#featureflagsourcespecenvvarsindexvaluefrom) +### FeatureFlag.spec.flagSpec +[↩ Parent](#featureflagspec) -Selects a key of a secret in the pod's namespace +FlagSpec is the structured representation of the feature flag specification @@ -2695,36 +113,29 @@ Selects a key of a secret in the pod's namespace - - + + - - - - - - - + +
keystringflagsmap[string]object - The key of the secret to select from. Must be a valid secret key.
+
true
namestring - Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?
-
false
optionalboolean$evaluatorsobject - Specify whether the Secret or its key must be defined
+
false
-### FeatureFlagSource.spec.resources -[↩ Parent](#featureflagsourcespec) +### FeatureFlag.spec.flagSpec.flags[key] +[↩ Parent](#featureflagspecflagspec) + -Resources defines flagd sidecar resources. Default to operator sidecar-cpu-* and sidecar-ram-* flags. @@ -2736,76 +147,47 @@ Resources defines flagd sidecar resources. Default to operator sidecar-cpu-* and - - + + - + - - + + - + - - + + - - -
claims[]objectdefaultVariantstring - Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers.
+
falsetrue
limitsmap[string]int or stringstateenum - Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+
+
+ Enum: ENABLED, DISABLED
falsetrue
requestsmap[string]int or stringvariantsobject - Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+
false
- - -### FeatureFlagSource.spec.resources.claims[index] -[↩ Parent](#featureflagsourcespecresources) - - - -ResourceClaim references one entry in PodSpec.ResourceClaims. - - - - - - - - - - - - - + + + + - +
NameTypeDescriptionRequired
namestringtrue
targetingobject - Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container.
+ Targeting is the json targeting rule
truefalse
-# core.openfeature.dev/v1alpha3 - -Resource Types: - -- [FlagSourceConfiguration](#flagsourceconfiguration) - - - - -## FlagSourceConfiguration -[↩ Parent](#coreopenfeaturedevv1alpha3 ) +## FeatureFlagSource +[↩ Parent](#coreopenfeaturedevv1beta1 ) -FlagSourceConfiguration is the Schema for the FlagSourceConfigurations API +FeatureFlagSource is the Schema for the FeatureFlagSources API @@ -2819,13 +201,13 @@ FlagSourceConfiguration is the Schema for the FlagSourceConfigurations API - + - + @@ -2834,29 +216,29 @@ FlagSourceConfiguration is the Schema for the FlagSourceConfigurations API - +
apiVersion stringcore.openfeature.dev/v1alpha3core.openfeature.dev/v1beta1 true
kind stringFlagSourceConfigurationFeatureFlagSource true
Refer to the Kubernetes API documentation for the fields of the `metadata` field. true
specspec object - FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration
+ FeatureFlagSourceSpec defines the desired state of FeatureFlagSource
false
status object - FlagSourceConfigurationStatus defines the observed state of FlagSourceConfiguration
+ FeatureFlagSourceStatus defines the observed state of FeatureFlagSource
false
-### FlagSourceConfiguration.spec -[↩ Parent](#flagsourceconfiguration-1) +### FeatureFlagSource.spec +[↩ Parent](#featureflagsource) -FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration +FeatureFlagSourceSpec defines the desired state of FeatureFlagSource @@ -2868,7 +250,7 @@ FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration - + - + @@ -2924,10 +306,10 @@ FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration - + @@ -2956,7 +338,7 @@ FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration - +
sourcessources []object SyncProviders define the syncProviders and associated configuration to be applied to the sidecar
@@ -2896,10 +278,10 @@ FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration
false
envVarsenvVars []object - EnvVars define the env vars to be applied to the sidecar, any env vars in FeatureFlagConfiguration CRs are added at the lowest index, all values will have the EnvVarPrefix applied, default FLAGD
+ EnvVars define the env vars to be applied to the sidecar, any env vars in FeatureFlag CRs are added at the lowest index, all values will have the EnvVarPrefix applied, default FLAGD
false
false
metricsPortmanagementPort integer - MetricsPort defines the port to serve metrics on, defaults to 8014
+ ManagemetPort defines the port to serve management on, defaults to 8014

Format: int32
false
resourcesresources object Resources defines flagd sidecar resources. Default to operator sidecar-cpu-* and sidecar-ram-* flags.
@@ -2994,8 +376,8 @@ FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration
-### FlagSourceConfiguration.spec.sources[index] -[↩ Parent](#flagsourceconfigurationspec-1) +### FeatureFlagSource.spec.sources[index] +[↩ Parent](#featureflagsourcespec) @@ -3063,8 +445,8 @@ FlagSourceConfigurationSpec defines the desired state of FlagSourceConfiguration -### FlagSourceConfiguration.spec.envVars[index] -[↩ Parent](#flagsourceconfigurationspec-1) +### FeatureFlagSource.spec.envVars[index] +[↩ Parent](#featureflagsourcespec) @@ -3094,7 +476,7 @@ EnvVar represents an environment variable present in a Container. false - valueFrom + valueFrom object Source for the environment variable's value. Cannot be used if value is not empty.
@@ -3104,8 +486,8 @@ EnvVar represents an environment variable present in a Container. -### FlagSourceConfiguration.spec.envVars[index].valueFrom -[↩ Parent](#flagsourceconfigurationspecenvvarsindex-1) +### FeatureFlagSource.spec.envVars[index].valueFrom +[↩ Parent](#featureflagsourcespecenvvarsindex) @@ -3121,28 +503,28 @@ Source for the environment variable's value. Cannot be used if value is not empt - configMapKeyRef + configMapKeyRef object Selects a key of a ConfigMap.
false - fieldRef + fieldRef object Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['<KEY>']`, `metadata.annotations['<KEY>']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.
false - resourceFieldRef + resourceFieldRef object Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.
false - secretKeyRef + secretKeyRef object Selects a key of a secret in the pod's namespace
@@ -3152,8 +534,8 @@ Source for the environment variable's value. Cannot be used if value is not empt -### FlagSourceConfiguration.spec.envVars[index].valueFrom.configMapKeyRef -[↩ Parent](#flagsourceconfigurationspecenvvarsindexvaluefrom-1) +### FeatureFlagSource.spec.envVars[index].valueFrom.configMapKeyRef +[↩ Parent](#featureflagsourcespecenvvarsindexvaluefrom) @@ -3193,8 +575,8 @@ Selects a key of a ConfigMap. -### FlagSourceConfiguration.spec.envVars[index].valueFrom.fieldRef -[↩ Parent](#flagsourceconfigurationspecenvvarsindexvaluefrom-1) +### FeatureFlagSource.spec.envVars[index].valueFrom.fieldRef +[↩ Parent](#featureflagsourcespecenvvarsindexvaluefrom) @@ -3227,8 +609,8 @@ Selects a field of the pod: supports metadata.name, metadata.namespace, `metadat -### FlagSourceConfiguration.spec.envVars[index].valueFrom.resourceFieldRef -[↩ Parent](#flagsourceconfigurationspecenvvarsindexvaluefrom-1) +### FeatureFlagSource.spec.envVars[index].valueFrom.resourceFieldRef +[↩ Parent](#featureflagsourcespecenvvarsindexvaluefrom) @@ -3268,8 +650,8 @@ Selects a resource of the container: only resources limits and requests (limits. -### FlagSourceConfiguration.spec.envVars[index].valueFrom.secretKeyRef -[↩ Parent](#flagsourceconfigurationspecenvvarsindexvaluefrom-1) +### FeatureFlagSource.spec.envVars[index].valueFrom.secretKeyRef +[↩ Parent](#featureflagsourcespecenvvarsindexvaluefrom) @@ -3309,8 +691,8 @@ Selects a key of a secret in the pod's namespace -### FlagSourceConfiguration.spec.resources -[↩ Parent](#flagsourceconfigurationspec-1) +### FeatureFlagSource.spec.resources +[↩ Parent](#featureflagsourcespec) @@ -3326,7 +708,7 @@ Resources defines flagd sidecar resources. Default to operator sidecar-cpu-* and - claims + claims []object Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. @@ -3352,8 +734,8 @@ Resources defines flagd sidecar resources. Default to operator sidecar-cpu-* and -### FlagSourceConfiguration.spec.resources.claims[index] -[↩ Parent](#flagsourceconfigurationspecresources-1) +### FeatureFlagSource.spec.resources.claims[index] +[↩ Parent](#featureflagsourcespecresources) diff --git a/go.mod b/go.mod index 3121ce4a5..ff0ddce96 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,12 @@ module github.com/open-feature/open-feature-operator go 1.19 require ( - github.com/evanphx/json-patch/v5 v5.6.0 github.com/go-logr/logr v1.2.4 github.com/golang/mock v1.4.4 - github.com/open-feature/open-feature-operator/apis v0.2.37 - github.com/open-feature/schemas v0.2.8 + github.com/open-feature/open-feature-operator/apis v0.2.38-0.20231116103437-e3c8b4290be9 github.com/stretchr/testify v1.8.4 - github.com/xeipuuv/gojsonschema v1.2.0 go.uber.org/zap v1.24.0 k8s.io/api v0.26.4 - k8s.io/apiextensions-apiserver v0.26.4 k8s.io/apimachinery v0.26.4 k8s.io/client-go v0.26.4 sigs.k8s.io/controller-runtime v0.14.6 @@ -24,6 +20,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logr/zapr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect @@ -53,8 +50,6 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect golang.org/x/net v0.10.0 // indirect @@ -69,6 +64,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.26.4 // indirect k8s.io/component-base v0.26.4 // indirect k8s.io/klog/v2 v2.80.1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect diff --git a/go.sum b/go.sum index 065ba02e7..76e7ec7f1 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -161,6 +163,7 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -221,8 +224,8 @@ github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/open-feature/open-feature-operator/apis v0.2.37 h1:JU/g9hKWfVChgd/uKXj3TYxu1uG5YDSsSmHofg9VBlg= github.com/open-feature/open-feature-operator/apis v0.2.37/go.mod h1:BpsW/HPbvNaAT2h2X47bYjLNmADF1I3Kg2DSj//TuZ0= -github.com/open-feature/schemas v0.2.8 h1:oA75hJXpOd9SFgmNI2IAxWZkwzQPUDm7Jyyh3q489wM= -github.com/open-feature/schemas v0.2.8/go.mod h1:vj+rfTsOLlh5PtGGkAbitnJmFPYuTHXTjOy13kzNgKQ= +github.com/open-feature/open-feature-operator/apis v0.2.38-0.20231116103437-e3c8b4290be9 h1:N94/iQZmTvfdQCpqlL9T5lx3X/m7JXK+xG5buPy/xIU= +github.com/open-feature/open-feature-operator/apis v0.2.38-0.20231116103437-e3c8b4290be9/go.mod h1:BpsW/HPbvNaAT2h2X47bYjLNmADF1I3Kg2DSj//TuZ0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -272,12 +275,6 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/main.go b/main.go index f7d21808e..bddfc025a 100644 --- a/main.go +++ b/main.go @@ -22,13 +22,12 @@ import ( "fmt" "os" - corev1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - corev1alpha2 "github.com/open-feature/open-feature-operator/apis/core/v1alpha2" - corev1alpha3 "github.com/open-feature/open-feature-operator/apis/core/v1alpha3" corev1beta1 "github.com/open-feature/open-feature-operator/apis/core/v1beta1" - controllercommon "github.com/open-feature/open-feature-operator/controllers/common" - "github.com/open-feature/open-feature-operator/controllers/core/featureflagconfiguration" - "github.com/open-feature/open-feature-operator/controllers/core/flagsourceconfiguration" + "github.com/open-feature/open-feature-operator/common" + "github.com/open-feature/open-feature-operator/common/constant" + "github.com/open-feature/open-feature-operator/common/flagdinjector" + "github.com/open-feature/open-feature-operator/common/flagdproxy" + "github.com/open-feature/open-feature-operator/controllers/core/featureflagsource" webhooks "github.com/open-feature/open-feature-operator/webhooks" "go.uber.org/zap/zapcore" appsV1 "k8s.io/api/apps/v1" @@ -55,10 +54,6 @@ const ( sidecarRamLimitFlagName = "sidecar-ram-limit" sidecarCpuRequestFlagName = "sidecar-cpu-request" sidecarRamRequestFlagName = "sidecar-ram-request" - flagdCpuLimitFlagName = "flagd-cpu-limit" - flagdRamLimitFlagName = "flagd-ram-limit" - flagdCpuRequestFlagName = "flagd-cpu-request" - flagdRamRequestFlagName = "flagd-ram-request" sidecarCpuLimitDefault = "0.5" sidecarRamLimitDefault = "64M" sidecarCpuRequestDefault = "0.2" @@ -72,16 +67,11 @@ var ( enableLeaderElection bool probeAddr string verbose bool - flagDCpuLimit, flagDRamLimit, flagDCpuRequest, flagDRamRequest string sidecarCpuLimit, sidecarRamLimit, sidecarCpuRequest, sidecarRamRequest string ) func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - - utilruntime.Must(corev1alpha1.AddToScheme(scheme)) - utilruntime.Must(corev1alpha2.AddToScheme(scheme)) - utilruntime.Must(corev1alpha3.AddToScheme(scheme)) utilruntime.Must(corev1beta1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } @@ -101,11 +91,6 @@ func main() { flag.StringVar(&sidecarCpuRequest, sidecarCpuRequestFlagName, sidecarCpuRequestDefault, "sidecar CPU minimum, in cores. (500m = .5 cores)") flag.StringVar(&sidecarRamRequest, sidecarRamRequestFlagName, sidecarRamRequestDefault, "sidecar memory minimum, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)") - flag.StringVar(&flagDCpuLimit, flagdCpuLimitFlagName, sidecarCpuLimitDefault, "DEPRECATED: superseded by --sidecar-cpu-limit. flagd CPU limit, in cores. (500m = .5 cores)") - flag.StringVar(&flagDRamLimit, flagdRamLimitFlagName, sidecarRamLimitDefault, "DEPRECATED: superseded by --sidecar-ram-limit. flagd memory limit, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)") - flag.StringVar(&flagDCpuRequest, flagdCpuRequestFlagName, sidecarCpuRequestDefault, "DEPRECATED: superseded by --sidecar-cpu-request. flagd CPU minimum, in cores. (500m = .5 cores)") - flag.StringVar(&flagDRamRequest, flagdRamRequestFlagName, sidecarRamRequestDefault, "DEPRECATED: superseded by --sidecar-ram-request. flagd memory minimum, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)") - level := zapcore.InfoLevel if verbose { level = zapcore.DebugLevel @@ -119,44 +104,27 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - if flagDCpuLimit != sidecarCpuLimitDefault { - ctrl.Log.Info("DEPRECATED: the --flagd-cpu-limit flag has been superseded by --sidecar-cpu-limit") - sidecarCpuLimit = flagDCpuLimit - } - if flagDRamLimit != sidecarRamLimitDefault { - ctrl.Log.Info("DEPRECATED: the --flagd-ram-limit flag has been superseded by --sidecar-ram-limit") - sidecarRamLimit = flagDRamLimit - } - if flagDCpuRequest != sidecarCpuRequestDefault { - ctrl.Log.Info("DEPRECATED: the --flagd-cpu-request flag has been superseded by --sidecar-cpu-request") - sidecarCpuRequest = flagDCpuRequest - } - if flagDRamRequest != sidecarRamRequestDefault { - ctrl.Log.Info("DEPRECATED: the --flagd-ram-request flag has been superseded by --sidecar-ram-request") - sidecarRamRequest = flagDRamRequest - } - cpuLimitResource, err := resource.ParseQuantity(sidecarCpuLimit) if err != nil { - setupLog.Error(err, "parse sidecar cpu limit", sidecarCpuLimitFlagName, flagDCpuLimit) + setupLog.Error(err, "parse sidecar cpu limit", sidecarCpuLimitFlagName, sidecarCpuLimit) os.Exit(1) } - ramLimitResource, err := resource.ParseQuantity(flagDRamLimit) + ramLimitResource, err := resource.ParseQuantity(sidecarRamLimit) if err != nil { - setupLog.Error(err, "parse sidecar ram limit", sidecarRamLimitFlagName, flagDRamLimit) + setupLog.Error(err, "parse sidecar ram limit", sidecarRamLimitFlagName, sidecarRamLimit) os.Exit(1) } - cpuRequestResource, err := resource.ParseQuantity(flagDCpuRequest) + cpuRequestResource, err := resource.ParseQuantity(sidecarCpuRequest) if err != nil { - setupLog.Error(err, "parse sidecar cpu request", sidecarCpuRequestFlagName, flagDCpuRequest) + setupLog.Error(err, "parse sidecar cpu request", sidecarCpuRequestFlagName, sidecarCpuRequest) os.Exit(1) } - ramRequestResource, err := resource.ParseQuantity(flagDRamRequest) + ramRequestResource, err := resource.ParseQuantity(sidecarRamRequest) if err != nil { - setupLog.Error(err, "parse sidecar ram request", sidecarRamRequestFlagName, flagDRamRequest) + setupLog.Error(err, "parse sidecar ram request", sidecarRamRequestFlagName, sidecarRamRequest) os.Exit(1) } @@ -186,14 +154,14 @@ func main() { if err := mgr.GetFieldIndexer().IndexField( context.Background(), &corev1.Pod{}, - fmt.Sprintf("%s/%s", webhooks.OpenFeatureAnnotationPath, webhooks.AllowKubernetesSyncAnnotation), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPath, constant.AllowKubernetesSyncAnnotation), webhooks.OpenFeatureEnabledAnnotationIndex, ); err != nil { setupLog.Error( err, "unable to create indexer", "webhook", - fmt.Sprintf("%s/%s", webhooks.OpenFeatureAnnotationPath, webhooks.AllowKubernetesSyncAnnotation), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPath, constant.AllowKubernetesSyncAnnotation), ) os.Exit(1) } @@ -201,55 +169,37 @@ func main() { if err := mgr.GetFieldIndexer().IndexField( context.Background(), &appsV1.Deployment{}, - fmt.Sprintf("%s/%s", controllercommon.OpenFeatureAnnotationPath, controllercommon.FlagSourceConfigurationAnnotation), - controllercommon.FlagSourceConfigurationIndex, + fmt.Sprintf("%s/%s", common.OpenFeatureAnnotationPath, common.FeatureFlagSourceAnnotation), + common.FeatureFlagSourceIndex, ); err != nil { setupLog.Error( err, "unable to create indexer", "webhook", - fmt.Sprintf("%s/%s", webhooks.OpenFeatureAnnotationPath, webhooks.FlagSourceConfigurationAnnotation), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPath, common.FeatureFlagSourceAnnotation), ) os.Exit(1) } - if err = (&featureflagconfiguration.FeatureFlagConfigurationReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Log: ctrl.Log.WithName("FeatureFlagConfiguration Controller"), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "FeatureFlagConfiguration") - os.Exit(1) - } - - if err := (&corev1alpha1.FeatureFlagConfiguration{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "FeatureFlagConfiguration") - os.Exit(1) - } - cnfg, err := controllercommon.NewFlagdProxyConfiguration() + cnfg, err := flagdproxy.NewFlagdProxyConfiguration() if err != nil { - setupLog.Error(err, "unable to create kube proxy handler configuration", "controller", "FlagSourceConfiguration") + setupLog.Error(err, "unable to create kube proxy handler configuration", "controller", "FeatureFlagSource") os.Exit(1) } - kph := controllercommon.NewFlagdProxyHandler( + kph := flagdproxy.NewFlagdProxyHandler( cnfg, mgr.GetClient(), - ctrl.Log.WithName("FlagSourceConfiguration FlagdProxyHandler"), + ctrl.Log.WithName("FeatureFlagSource FlagdProxyHandler"), ) - flagSourceController := &flagsourceconfiguration.FlagSourceConfigurationReconciler{ + flagSourceController := &featureflagsource.FeatureFlagSourceReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Log: ctrl.Log.WithName("FlagSourceConfiguration Controller"), + Log: ctrl.Log.WithName("FeatureFlagSource Controller"), FlagdProxy: kph, } if err = flagSourceController.SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "FlagSourceConfiguration") - os.Exit(1) - } - - if err := (&corev1alpha1.FlagSourceConfiguration{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "FlagSourceConfiguration") + setupLog.Error(err, "unable to create controller", "controller", "FeatureFlagSource") os.Exit(1) } @@ -259,7 +209,7 @@ func main() { Client: mgr.GetClient(), Log: ctrl.Log.WithName("mutating-pod-webhook"), FlagdProxyConfig: kph.Config(), - FlagdInjector: &controllercommon.FlagdContainerInjector{ + FlagdInjector: &flagdinjector.FlagdContainerInjector{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("flagd-container injector"), FlagdProxyConfig: kph.Config(), @@ -276,10 +226,6 @@ func main() { }, } hookServer.Register("/mutate-v1-pod", &webhook.Admission{Handler: podMutator}) - hookServer.Register("/validate-v1alpha1-featureflagconfiguration", &webhook.Admission{Handler: &webhooks.FeatureFlagConfigurationValidator{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("validating-featureflagconfiguration-webhook"), - }}) if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") diff --git a/test/e2e/kuttl/assets/manifests.yaml b/test/e2e/kuttl/assets/manifests.yaml index 661e9009f..ecc006c2a 100644 --- a/test/e2e/kuttl/assets/manifests.yaml +++ b/test/e2e/kuttl/assets/manifests.yaml @@ -1,21 +1,16 @@ -apiVersion: core.openfeature.dev/v1alpha1 -kind: FeatureFlagConfiguration +apiVersion: core.openfeature.dev/v1beta1 +kind: FeatureFlag metadata: name: end-to-end-test spec: - featureFlagSpec: | - { - "flags": { - "simple-flag": { - "state": "ENABLED", - "variants": { - "on": true, - "off": false - }, - "defaultVariant": "on" - } - } - } + flagSpec: + flags: + "simple-flag": + state: "ENABLED" + variants: + "on": true + "off": false + defaultVariant: "on" --- apiVersion: v1 kind: ConfigMap @@ -32,6 +27,12 @@ data: } } --- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: open-feature-e2e-test-sa +automountServiceAccountToken: true +--- # Deployment of nginx using our custom resource apiVersion: apps/v1 kind: Deployment @@ -50,8 +51,9 @@ spec: app: open-feature-e2e-test annotations: openfeature.dev/enabled: "true" - openfeature.dev/flagsourceconfiguration: "source-configuration" + openfeature.dev/featureflagsource: "source-configuration" spec: + serviceAccountName: open-feature-e2e-test-sa volumes: - name: open-feature-e2e-nginx-conf configMap: diff --git a/test/e2e/kuttl/beta-resources/00-assert.yaml b/test/e2e/kuttl/beta-resources/00-assert.yaml deleted file mode 100644 index 931a2e295..000000000 --- a/test/e2e/kuttl/beta-resources/00-assert.yaml +++ /dev/null @@ -1,28 +0,0 @@ ---- -apiVersion: core.openfeature.dev/v1beta1 -kind: FeatureFlag -metadata: - name: featureflag -spec: - flagSpec: - flags: - "simple-flag": - state: "ENABLED" - variants: - "on": true - "off": false - defaultVariant: "on" ---- -apiVersion: core.openfeature.dev/v1beta1 -kind: FeatureFlagSource -metadata: - name: featureflagsource -spec: - metricsPort: 8080 - evaluator: json - defaultSyncProvider: file - tag: latest - sources: - - source: end-to-end-test - provider: file - probesEnabled: true diff --git a/test/e2e/kuttl/beta-resources/00-install.yaml b/test/e2e/kuttl/beta-resources/00-install.yaml deleted file mode 100644 index 931a2e295..000000000 --- a/test/e2e/kuttl/beta-resources/00-install.yaml +++ /dev/null @@ -1,28 +0,0 @@ ---- -apiVersion: core.openfeature.dev/v1beta1 -kind: FeatureFlag -metadata: - name: featureflag -spec: - flagSpec: - flags: - "simple-flag": - state: "ENABLED" - variants: - "on": true - "off": false - defaultVariant: "on" ---- -apiVersion: core.openfeature.dev/v1beta1 -kind: FeatureFlagSource -metadata: - name: featureflagsource -spec: - metricsPort: 8080 - evaluator: json - defaultSyncProvider: file - tag: latest - sources: - - source: end-to-end-test - provider: file - probesEnabled: true diff --git a/test/e2e/kuttl/flagd-disabled/00-install.yaml b/test/e2e/kuttl/flagd-disabled/00-install.yaml index b9d4648b6..db284067c 100644 --- a/test/e2e/kuttl/flagd-disabled/00-install.yaml +++ b/test/e2e/kuttl/flagd-disabled/00-install.yaml @@ -1,24 +1,19 @@ # This configuration deploys the means to test the mutating injection webhook by proxying through an nginx server # to assert that flagd is reachable --- -apiVersion: core.openfeature.dev/v1alpha1 -kind: FeatureFlagConfiguration +apiVersion: core.openfeature.dev/v1beta1 +kind: FeatureFlag metadata: name: end-to-end-test spec: - featureFlagSpec: | - { - "flags": { - "simple-flag": { - "state": "ENABLED", - "variants": { - "on": true, - "off": false - }, - "defaultVariant": "on" - } - } - } + flagSpec: + flags: + "simple-flag": + state: "ENABLED" + variants: + "on": true + "off": false + defaultVariant: "on" --- apiVersion: v1 kind: ServiceAccount diff --git a/test/e2e/kuttl/fsconfig-file-sync/00-install.yaml b/test/e2e/kuttl/fsconfig-file-sync/00-install.yaml index 5f732b2a7..82a0acb63 100644 --- a/test/e2e/kuttl/fsconfig-file-sync/00-install.yaml +++ b/test/e2e/kuttl/fsconfig-file-sync/00-install.yaml @@ -1,15 +1,14 @@ --- -apiVersion: core.openfeature.dev/v1alpha3 -kind: FlagSourceConfiguration +apiVersion: core.openfeature.dev/v1beta1 +kind: FeatureFlagSource metadata: name: source-configuration spec: - metricsPort: 8080 evaluator: json - defaultSyncProvider: filepath + defaultSyncProvider: file # renovate: datasource=github-tags depName=open-feature/flagd/flagd - tag: v0.6.3 + tag: v0.7.0 sources: - source: end-to-end-test - provider: filepath + provider: file probesEnabled: true diff --git a/test/e2e/kuttl/fsconfig-file-sync/01-assert.yaml b/test/e2e/kuttl/fsconfig-file-sync/01-assert.yaml index 39ee90dfa..747459483 100644 --- a/test/e2e/kuttl/fsconfig-file-sync/01-assert.yaml +++ b/test/e2e/kuttl/fsconfig-file-sync/01-assert.yaml @@ -3,7 +3,7 @@ kind: Pod metadata: annotations: openfeature.dev/enabled: "true" - openfeature.dev/flagsourceconfiguration: source-configuration + openfeature.dev/featureflagsource: source-configuration labels: app: open-feature-e2e-test status: @@ -13,4 +13,4 @@ spec: - name: open-feature-e2e-test image: nginx:stable-alpine - name: flagd # this part verifies flagd injection happened - image: ghcr.io/open-feature/flagd:v0.6.3 + image: ghcr.io/open-feature/flagd:v0.7.0 diff --git a/test/e2e/kuttl/fsconfig-flagd-proxy-sync/00-install.yaml b/test/e2e/kuttl/fsconfig-flagd-proxy-sync/00-install.yaml index bb7c41d5d..752b21c3b 100644 --- a/test/e2e/kuttl/fsconfig-flagd-proxy-sync/00-install.yaml +++ b/test/e2e/kuttl/fsconfig-flagd-proxy-sync/00-install.yaml @@ -1,14 +1,13 @@ --- -apiVersion: core.openfeature.dev/v1alpha3 -kind: FlagSourceConfiguration +apiVersion: core.openfeature.dev/v1beta1 +kind: FeatureFlagSource metadata: name: source-configuration spec: - metricsPort: 8080 evaluator: json defaultSyncProvider: flagd-proxy # renovate: datasource=github-tags depName=open-feature/flagd/flagd - tag: v0.6.3 + tag: v0.7.0 sources: - source: end-to-end-test provider: flagd-proxy diff --git a/test/e2e/kuttl/fsconfig-flagd-proxy-sync/01-assert.yaml b/test/e2e/kuttl/fsconfig-flagd-proxy-sync/01-assert.yaml index 39ee90dfa..747459483 100644 --- a/test/e2e/kuttl/fsconfig-flagd-proxy-sync/01-assert.yaml +++ b/test/e2e/kuttl/fsconfig-flagd-proxy-sync/01-assert.yaml @@ -3,7 +3,7 @@ kind: Pod metadata: annotations: openfeature.dev/enabled: "true" - openfeature.dev/flagsourceconfiguration: source-configuration + openfeature.dev/featureflagsource: source-configuration labels: app: open-feature-e2e-test status: @@ -13,4 +13,4 @@ spec: - name: open-feature-e2e-test image: nginx:stable-alpine - name: flagd # this part verifies flagd injection happened - image: ghcr.io/open-feature/flagd:v0.6.3 + image: ghcr.io/open-feature/flagd:v0.7.0 diff --git a/test/e2e/kuttl/fsconfig-k8s-sync/00-install.yaml b/test/e2e/kuttl/fsconfig-k8s-sync/00-install.yaml index 4db30d9c8..4a3cdef95 100644 --- a/test/e2e/kuttl/fsconfig-k8s-sync/00-install.yaml +++ b/test/e2e/kuttl/fsconfig-k8s-sync/00-install.yaml @@ -1,14 +1,13 @@ --- -apiVersion: core.openfeature.dev/v1alpha3 -kind: FlagSourceConfiguration +apiVersion: core.openfeature.dev/v1beta1 +kind: FeatureFlagSource metadata: name: source-configuration spec: - metricsPort: 8080 evaluator: json defaultSyncProvider: kubernetes # renovate: datasource=github-tags depName=open-feature/flagd/flagd - tag: v0.6.3 + tag: v0.7.0 sources: - source: end-to-end-test provider: kubernetes diff --git a/test/e2e/kuttl/fsconfig-k8s-sync/01-assert.yaml b/test/e2e/kuttl/fsconfig-k8s-sync/01-assert.yaml index 8e694b734..f3bf1ed9c 100644 --- a/test/e2e/kuttl/fsconfig-k8s-sync/01-assert.yaml +++ b/test/e2e/kuttl/fsconfig-k8s-sync/01-assert.yaml @@ -4,7 +4,7 @@ metadata: annotations: openfeature.dev/allowkubernetessync: "true" openfeature.dev/enabled: "true" - openfeature.dev/flagsourceconfiguration: source-configuration + openfeature.dev/featureflagsource: source-configuration labels: app: open-feature-e2e-test status: @@ -14,4 +14,4 @@ spec: - name: open-feature-e2e-test image: nginx:stable-alpine - name: flagd # this part verifies flagd injection happened - image: ghcr.io/open-feature/flagd:v0.6.3 + image: ghcr.io/open-feature/flagd:v0.7.0 diff --git a/test/e2e/kuttl/inject-flagd/00-assert.yaml b/test/e2e/kuttl/inject-flagd/00-assert.yaml deleted file mode 100644 index 7e70f3316..000000000 --- a/test/e2e/kuttl/inject-flagd/00-assert.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -commands: - - command: kubectl wait --for=condition=complete job flagd-query-test -n $NAMESPACE -collectors: - - command: kubectl logs -l job-name=flagd-query-test -n $NAMESPACE diff --git a/test/e2e/kuttl/inject-flagd/00-install.yaml b/test/e2e/kuttl/inject-flagd/00-install.yaml deleted file mode 100644 index 1f9b5d162..000000000 --- a/test/e2e/kuttl/inject-flagd/00-install.yaml +++ /dev/null @@ -1,123 +0,0 @@ -# This configuration deploys the means to test the mutating injection webhook by proxying through an nginx server -# to assert that flagd is reachable ---- -apiVersion: core.openfeature.dev/v1alpha1 -kind: FeatureFlagConfiguration -metadata: - name: end-to-end-test -spec: - featureFlagSpec: | - { - "flags": { - "simple-flag": { - "state": "ENABLED", - "variants": { - "on": true, - "off": false - }, - "defaultVariant": "on" - } - } - } ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: open-feature-e2e-test-sa -automountServiceAccountToken: true ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: open-feature-e2e-nginx-conf -data: - nginx.conf: | - events {} - http { - server { - location / { - proxy_pass http://127.0.0.1:8013; - } - } - } ---- -# Deployment of nginx using our custom resource -apiVersion: apps/v1 -kind: Deployment -metadata: - name: open-feature-e2e-test-deployment - labels: - app: open-feature-e2e-test -spec: - replicas: 1 - selector: - matchLabels: - app: open-feature-e2e-test - template: - metadata: - labels: - app: open-feature-e2e-test - annotations: - openfeature.dev/enabled: "true" - openfeature.dev/featureflagconfiguration: "end-to-end-test" - spec: - serviceAccountName: open-feature-e2e-test-sa - volumes: - - name: open-feature-e2e-nginx-conf - configMap: - name: open-feature-e2e-nginx-conf - items: - - key: nginx.conf - path: nginx.conf - containers: - - name: open-feature-e2e-test - image: nginx:stable-alpine - ports: - - containerPort: 80 - volumeMounts: - - name: open-feature-e2e-nginx-conf - mountPath: /etc/nginx - readOnly: true ---- -# Service exposed using NodePort -apiVersion: v1 -kind: Service -metadata: - name: open-feature-e2e-test-service -spec: - type: ClusterIP - selector: - app: open-feature-e2e-test - ports: - - protocol: TCP - port: 30000 - targetPort: 80 ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: flagd-query-test -spec: - backoffLimit: 10 - template: - spec: - containers: - - name: test-flagd-endpoint - image: curlimages/curl:8.2.1 - args: - - /bin/sh - - -ec - - | - EXPECTED_RESPONSE_CONTAIN='"value":true,"reason":"STATIC","variant":"on"' - RESPONSE=$(curl -s -X POST "open-feature-e2e-test-service:30000/schema.v1.Service/ResolveBoolean" -d '{"flagKey":"simple-flag","context":{}}' -H "Content-Type: application/json") - RESPONSE="${RESPONSE//[[:space:]]/}" # strip whitespace from response - - if [[ "$RESPONSE" == *"$EXPECTED_RESPONSE_CONTAIN"* ]] - then - exit 0 - fi - - echo "Expected response to contain: $EXPECTED_RESPONSE_CONTAIN" - echo "Got response: $RESPONSE" - exit 1 - restartPolicy: OnFailure diff --git a/test/e2e/kuttl/inject-flagd/01-assert.yaml b/test/e2e/kuttl/inject-flagd/01-assert.yaml deleted file mode 100644 index 32078dd54..000000000 --- a/test/e2e/kuttl/inject-flagd/01-assert.yaml +++ /dev/null @@ -1,18 +0,0 @@ - -apiVersion: v1 -kind: Pod -metadata: - annotations: - openfeature.dev/allowkubernetessync: "true" - openfeature.dev/enabled: "true" - openfeature.dev/featureflagconfiguration: end-to-end-test - labels: - app: open-feature-e2e-test -status: - phase: Running -spec: - containers: - - name: open-feature-e2e-test - image: nginx:stable-alpine - - name: flagd # this part verifies flagd injection happened - image: ghcr.io/open-feature/flagd:v0.6.3 diff --git a/webhooks/common.go b/webhooks/common.go new file mode 100644 index 000000000..138087ebb --- /dev/null +++ b/webhooks/common.go @@ -0,0 +1,58 @@ +package webhooks + +import ( + "fmt" + "strings" + + api "github.com/open-feature/open-feature-operator/apis/core/v1beta1" + "github.com/open-feature/open-feature-operator/common/constant" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func OpenFeatureEnabledAnnotationIndex(o client.Object) []string { + pod, ok := o.(*corev1.Pod) + if !ok { + return []string{"false"} + } + if pod.ObjectMeta.Annotations == nil { + return []string{ + "false", + } + } + val, ok := pod.ObjectMeta.Annotations[fmt.Sprintf("openfeature.dev/%s", constant.AllowKubernetesSyncAnnotation)] + if ok && val == "true" { + return []string{ + "true", + } + } + return []string{ + "false", + } +} + +func parseList(s string) []string { + out := []string{} + ss := strings.Split(s, ",") + for i := 0; i < len(ss); i++ { + newS := strings.TrimSpace(ss[i]) + if newS != "" { //function should not add empty values + out = append(out, newS) + } + } + return out +} + +func containsK8sProvider(sources []api.Source) bool { + for _, source := range sources { + if source.Provider.IsKubernetes() { + return true + } + } + return false +} + +func checkOFEnabled(annotations map[string]string) bool { + val, ok := annotations[fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.EnabledAnnotation)] + return ok && val == "true" +} diff --git a/webhooks/common_test.go b/webhooks/common_test.go new file mode 100644 index 000000000..5fe8bcac9 --- /dev/null +++ b/webhooks/common_test.go @@ -0,0 +1,142 @@ +package webhooks + +import ( + "fmt" + "reflect" + "testing" + + api "github.com/open-feature/open-feature-operator/apis/core/v1beta1" + apicommon "github.com/open-feature/open-feature-operator/apis/core/v1beta1/common" + "github.com/open-feature/open-feature-operator/common/constant" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestOpenFeatureEnabledAnnotationIndex(t *testing.T) { + + tests := []struct { + name string + o client.Object + want []string + }{ + { + name: "not a pod", + o: &corev1.ConfigMap{}, + want: []string{"false"}, + }, { + name: "no annotations", + o: &corev1.Pod{}, + want: []string{"false"}, + }, { + name: "annotated wrong", + o: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"test/ann": "nope", "openfeature.dev/allowkubernetessync": "false"}}}, + want: []string{"false"}, + }, { + name: "annotated with enabled index", + o: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"openfeature.dev/allowkubernetessync": "true"}}}, + want: []string{"true"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := OpenFeatureEnabledAnnotationIndex(tt.o); !reflect.DeepEqual(got, tt.want) { + t.Errorf("OpenFeatureEnabledAnnotationIndex() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPodMutator_checkOFEnabled(t *testing.T) { + + tests := []struct { + name string + annotations map[string]string + want bool + }{ + { + name: "enabled", + annotations: map[string]string{fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.EnabledAnnotation): "true"}, + want: true, + }, { + name: "disabled", + annotations: map[string]string{fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.EnabledAnnotation): "false"}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := checkOFEnabled(tt.annotations); got != tt.want { + t.Errorf("checkOFEnabled() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_parseList(t *testing.T) { + + tests := []struct { + name string + s string + want []string + }{ + { + name: "empty string", + s: "", + want: []string{}, + }, { + name: "nice list with spaces", + s: "annotation1, annotation2, annotation4 , annotation3,", + want: []string{"annotation1", "annotation2", "annotation4", "annotation3"}, + }, { + name: "list with no spaces", + s: "annotation1, annotation2,annotation4, annotation3", + want: []string{"annotation1", "annotation2", "annotation4", "annotation3"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := parseList(tt.s); !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseList() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPodMutator_containsK8sProvider(t *testing.T) { + + tests := []struct { + name string + sources []api.Source + want bool + }{ + { + name: "empty", + sources: []api.Source{}, + want: false, + }, + { + name: "non-kubernetes", + sources: []api.Source{ + {Provider: apicommon.SyncProviderFilepath}, + {Provider: apicommon.SyncProviderGrpc}, + }, + want: false, + }, + { + name: "kubernetes", + sources: []api.Source{ + {Provider: apicommon.SyncProviderFilepath}, + {Provider: apicommon.SyncProviderKubernetes}, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := containsK8sProvider(tt.sources); got != tt.want { + t.Errorf("containsK8sProvider() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/webhooks/error_types.go b/webhooks/error_types.go deleted file mode 100644 index 6ef21407f..000000000 --- a/webhooks/error_types.go +++ /dev/null @@ -1,13 +0,0 @@ -package webhooks - -import ( - goErr "errors" - "net/http" -) - -func (m *PodMutator) IsReady(_ *http.Request) error { - if m.ready { - return nil - } - return goErr.New("pod mutator is not ready") -} diff --git a/webhooks/featureflagconfiguration_webhook.go b/webhooks/featureflagconfiguration_webhook.go deleted file mode 100644 index 4edf5603d..000000000 --- a/webhooks/featureflagconfiguration_webhook.go +++ /dev/null @@ -1,101 +0,0 @@ -package webhooks - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strings" - - "github.com/go-logr/logr" - corev1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - schemas "github.com/open-feature/schemas/json" - "github.com/xeipuuv/gojsonschema" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// NOTE: RBAC not needed here. -//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:webhook:path=/validate-v1alpha1-featureflagconfiguration,mutating=false,failurePolicy=fail,sideEffects=None,groups=core.openfeature.dev,resources=featureflagconfigurations,verbs=create;update,versions=v1alpha1,name=validate.featureflagconfiguration.openfeature.dev,admissionReviewVersions=v1 - -// FeatureFlagConfigurationValidator annotates Pods -type FeatureFlagConfigurationValidator struct { - Client client.Client - decoder *admission.Decoder - Log logr.Logger -} - -// Handle validates a FeatureFlagConfiguration -func (m *FeatureFlagConfigurationValidator) Handle(ctx context.Context, req admission.Request) admission.Response { - config := corev1alpha1.FeatureFlagConfiguration{} - if err := m.decoder.Decode(req, &config); err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - if err := m.validateFlagSourceConfiguration(ctx, config); err != nil { - return admission.Denied(err.Error()) - } - - return admission.Allowed("") -} - -// FeatureFlagConfigurationValidator implements admission.DecoderInjector. -// A decoder will be automatically injected. - -// InjectDecoder injects the decoder. -func (m *FeatureFlagConfigurationValidator) InjectDecoder(d *admission.Decoder) error { - m.decoder = d - return nil -} - -func (m *FeatureFlagConfigurationValidator) isJSON(str string) bool { - var js json.RawMessage - return json.Unmarshal([]byte(str), &js) == nil -} - -func validateJSONSchema(schemaJSON string, inputJSON string) error { - schemaLoader := gojsonschema.NewBytesLoader([]byte(schemaJSON)) - valuesLoader := gojsonschema.NewBytesLoader([]byte(inputJSON)) - result, err := gojsonschema.Validate(schemaLoader, valuesLoader) - if err != nil { - return err - } - - if !result.Valid() { - var sb strings.Builder - for _, desc := range result.Errors() { - sb.WriteString(fmt.Sprintf("- %s\n", desc)) - } - return errors.NewBadRequest(sb.String()) - } - return nil -} - -func (m *FeatureFlagConfigurationValidator) validateFlagSourceConfiguration(ctx context.Context, config corev1alpha1.FeatureFlagConfiguration) error { - if config.Spec.FeatureFlagSpec != "" { - if !m.isJSON(config.Spec.FeatureFlagSpec) { - return fmt.Errorf("FeatureFlagSpec is not valid JSON: %s", config.Spec.FeatureFlagSpec) - } - err := validateJSONSchema(schemas.FlagdDefinitions, config.Spec.FeatureFlagSpec) - if err != nil { - return fmt.Errorf("FeatureFlagSpec is not valid JSON: %s", err.Error()) - } - } - - if config.Spec.ServiceProvider != nil && config.Spec.ServiceProvider.Credentials != nil { - // Check the provider and whether it has an existing secret - providerKeySecret := corev1.Secret{} - if err := m.Client.Get(ctx, client.ObjectKey{ - Name: config.Spec.ServiceProvider.Credentials.Name, - Namespace: config.Spec.ServiceProvider.Credentials.Namespace, - }, &providerKeySecret); errors.IsNotFound(err) { - return fmt.Errorf("credentials secret not found") - } - } - - return nil -} diff --git a/webhooks/featureflagconfiguration_webhook_test.go b/webhooks/featureflagconfiguration_webhook_test.go deleted file mode 100644 index f5606fd2c..000000000 --- a/webhooks/featureflagconfiguration_webhook_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package webhooks - -import ( - "context" - "fmt" - "testing" - - "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - corev1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/scheme" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" -) - -func TestFeatureFlagConfigurationWebhook_validateFlagSourceConfiguration(t *testing.T) { - const credentialsName = "credentials-name" - const featureFlagConfigurationName = "test-feature-flag-configuration" - - tests := []struct { - name string - obj corev1alpha1.FeatureFlagConfiguration - secret *corev1.Secret - out error - }{ - { - name: "valid without ServiceProvider", - obj: corev1alpha1.FeatureFlagConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: featureFlagConfigurationName, - Namespace: featureFlagConfigurationNamespace, - }, - Spec: corev1alpha1.FeatureFlagConfigurationSpec{ - FeatureFlagSpec: featureFlagSpec, - }, - }, - out: nil, - }, - { - name: "invalid json", - obj: corev1alpha1.FeatureFlagConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: featureFlagConfigurationName, - Namespace: featureFlagConfigurationNamespace, - }, - Spec: corev1alpha1.FeatureFlagConfigurationSpec{ - FeatureFlagSpec: `{"invalid":json}`, - }, - }, - out: fmt.Errorf("FeatureFlagSpec is not valid JSON: {\"invalid\":json}"), - }, - { - name: "invalid schema", - obj: corev1alpha1.FeatureFlagConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: featureFlagConfigurationName, - Namespace: featureFlagConfigurationNamespace, - }, - Spec: corev1alpha1.FeatureFlagConfigurationSpec{ - FeatureFlagSpec: `{ - "flags":{ - "foo": {} - } - }`, - }, - }, - out: fmt.Errorf("FeatureFlagSpec is not valid JSON: - flags.foo: Must validate one and only one schema (oneOf)\n- flags.foo: state is required\n- flags.foo: defaultVariant is required\n- flags.foo: Must validate all the schemas (allOf)\n"), - }, - { - name: "valid with ServiceProvider", - obj: corev1alpha1.FeatureFlagConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: featureFlagConfigurationName, - Namespace: featureFlagConfigurationNamespace, - }, - Spec: corev1alpha1.FeatureFlagConfigurationSpec{ - FeatureFlagSpec: featureFlagSpec, - ServiceProvider: &corev1alpha1.FeatureFlagServiceProvider{ - Name: "flagd", - Credentials: &corev1.ObjectReference{ - Name: credentialsName, - Namespace: featureFlagConfigurationNamespace, - }, - }, - }, - }, - secret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: credentialsName, - Namespace: featureFlagConfigurationNamespace, - }, - }, - out: nil, - }, - { - name: "non-existing secret in ServiceProvider", - obj: corev1alpha1.FeatureFlagConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: featureFlagConfigurationName, - Namespace: featureFlagConfigurationNamespace, - }, - Spec: corev1alpha1.FeatureFlagConfigurationSpec{ - FeatureFlagSpec: featureFlagSpec, - ServiceProvider: &corev1alpha1.FeatureFlagServiceProvider{ - Name: "flagd", - Credentials: &corev1.ObjectReference{ - Name: credentialsName, - Namespace: featureFlagConfigurationNamespace, - }, - }, - }, - }, - out: fmt.Errorf("credentials secret not found"), - }, - } - - err := v1alpha1.AddToScheme(scheme.Scheme) - require.Nil(t, err) - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - validator := FeatureFlagConfigurationValidator{ - Client: fake.NewClientBuilder().WithScheme(scheme.Scheme).Build(), - Log: ctrl.Log.WithName("webhook"), - } - - if tt.secret != nil { - err := validator.Client.Create(context.TODO(), tt.secret) - require.Nil(t, err) - } - - out := validator.validateFlagSourceConfiguration(context.TODO(), tt.obj) - require.Equal(t, tt.out, out) - }) - - } -} diff --git a/webhooks/pod_webhook.go b/webhooks/pod_webhook.go index 76794d04b..45b1da869 100644 --- a/webhooks/pod_webhook.go +++ b/webhooks/pod_webhook.go @@ -3,40 +3,23 @@ package webhooks import ( "context" "encoding/json" - goErr "errors" + "errors" "fmt" "net/http" - "reflect" - "strings" "time" "github.com/go-logr/logr" - "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - controllercommon "github.com/open-feature/open-feature-operator/controllers/common" - "github.com/open-feature/open-feature-operator/controllers/common/constant" - "github.com/open-feature/open-feature-operator/pkg/utils" + api "github.com/open-feature/open-feature-operator/apis/core/v1beta1" + "github.com/open-feature/open-feature-operator/common/constant" + "github.com/open-feature/open-feature-operator/common/flagdinjector" + "github.com/open-feature/open-feature-operator/common/flagdproxy" + "github.com/open-feature/open-feature-operator/common/utils" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) -// we likely want these to be configurable, eventually -const ( - FlagDImagePullPolicy corev1.PullPolicy = "Always" - clusterRoleBindingName string = "open-feature-operator-flagd-kubernetes-sync" - OpenFeatureAnnotationPath = "metadata.annotations.openfeature.dev" - OpenFeatureAnnotationPrefix = "openfeature.dev" - AllowKubernetesSyncAnnotation = "allowkubernetessync" - FlagSourceConfigurationAnnotation = "flagsourceconfiguration" - FeatureFlagConfigurationAnnotation = "featureflagconfiguration" - EnabledAnnotation = "enabled" - ProbeReadiness = "/readyz" - ProbeLiveness = "/healthz" - SourceConfigParam = "--sources" -) - // NOTE: RBAC not needed here. //+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;update;patch;delete @@ -51,8 +34,8 @@ type PodMutator struct { decoder *admission.Decoder Log logr.Logger ready bool - FlagdProxyConfig *controllercommon.FlagdProxyConfiguration - FlagdInjector controllercommon.IFlagdContainerInjector + FlagdProxyConfig *flagdproxy.FlagdProxyConfiguration + FlagdInjector flagdinjector.IFlagdContainerInjector } // Handle injects the flagd sidecar (if the prerequisites are all met) @@ -66,20 +49,13 @@ func (m *PodMutator) Handle(ctx context.Context, req admission.Request) admissio }() pod := &corev1.Pod{} err := m.decoder.Decode(req, pod) - - if pod.Namespace == "" { - pod.Namespace = req.Namespace - } - if err != nil { return admission.Errored(http.StatusBadRequest, err) } - // Check enablement annotations := pod.GetAnnotations() - enabled := m.checkOFEnabled(annotations) - - if !enabled { + // Check enablement + if !checkOFEnabled(annotations) { m.Log.V(2).Info(`openfeature.dev/enabled annotation is not set to "true"`) return admission.Allowed("OpenFeature is disabled") } @@ -90,22 +66,23 @@ func (m *PodMutator) Handle(ctx context.Context, req admission.Request) admissio } // merge any provided flagd specs - flagSourceConfigurationSpec, code, err := m.createFSConfigSpec(ctx, req, annotations, pod) + featureFlagSourceSpec, code, err := m.createFSConfigSpec(ctx, req, annotations, pod) if err != nil { return admission.Errored(code, err) } // Check for the correct clusterrolebinding for the pod if we use the Kubernetes mode - if containsK8sProvider(flagSourceConfigurationSpec.Sources) { + if containsK8sProvider(featureFlagSourceSpec.Sources) { if err := m.FlagdInjector.EnableClusterRoleBinding(ctx, pod.Namespace, pod.Spec.ServiceAccountName); err != nil { return admission.Denied(err.Error()) } } - if err := m.FlagdInjector.InjectFlagd(ctx, &pod.ObjectMeta, &pod.Spec, flagSourceConfigurationSpec); err != nil { - if goErr.Is(err, constant.ErrFlagdProxyNotReady) { + if err := m.FlagdInjector.InjectFlagd(ctx, &pod.ObjectMeta, &pod.Spec, featureFlagSourceSpec); err != nil { + if errors.Is(err, constant.ErrFlagdProxyNotReady) { return admission.Denied(err.Error()) } + //test m.Log.Error(err, "unable to inject flagd sidecar") return admission.Errored(http.StatusInternalServerError, err) } @@ -118,72 +95,32 @@ func (m *PodMutator) Handle(ctx context.Context, req admission.Request) admissio return admission.PatchResponseFromRaw(req.Object.Raw, marshaledPod) } -func containsK8sProvider(sources []v1alpha1.Source) bool { - for _, source := range sources { - if source.Provider.IsKubernetes() { - return true - } - } - return false -} - -func (m *PodMutator) createFSConfigSpec(ctx context.Context, req admission.Request, annotations map[string]string, pod *corev1.Pod) (*v1alpha1.FlagSourceConfigurationSpec, int32, error) { +func (m *PodMutator) createFSConfigSpec(ctx context.Context, req admission.Request, annotations map[string]string, pod *corev1.Pod) (*api.FeatureFlagSourceSpec, int32, error) { // Check configuration fscNames := []string{} - val, ok := annotations[fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FlagSourceConfigurationAnnotation)] + val, ok := annotations[fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.FeatureFlagSourceAnnotation)] if ok { fscNames = parseList(val) } - flagSourceConfigurationSpec, err := v1alpha1.NewFlagSourceConfigurationSpec() + featureFlagSourceSpec, err := api.NewFeatureFlagSourceSpec() if err != nil { - m.Log.V(1).Error(err, "unable to parse env var configuration", "webhook", "handle") + m.Log.V(1).Error(err, "unable to create new FeatureFlagSourceSpec", "webhook", "handle") return nil, http.StatusBadRequest, err } for _, fscName := range fscNames { ns, name := utils.ParseAnnotation(fscName, req.Namespace) + + fc, err := m.getFeatureFlagSource(ctx, ns, name) if err != nil { - m.Log.V(1).Info(fmt.Sprintf("failed to parse annotation %s error: %s", fscName, err.Error())) - return nil, http.StatusBadRequest, err + m.Log.V(1).Info(fmt.Sprintf("FeatureFlagSource could not be found for %s", fscName)) + return nil, http.StatusNotFound, err } - fc := m.getFlagSourceConfiguration(ctx, ns, name) - if reflect.DeepEqual(fc, v1alpha1.FlagSourceConfiguration{}) { - m.Log.V(1).Info(fmt.Sprintf("FlagSourceConfiguration could not be found for %s", fscName)) - return nil, http.StatusBadRequest, err - } - flagSourceConfigurationSpec.Merge(&fc.Spec) + featureFlagSourceSpec.Merge(&fc.Spec) } - // maintain backwards compatibility of the openfeature.dev/featureflagconfiguration annotation - ffConfigAnnotation, ffConfigAnnotationOk := pod.GetAnnotations()[fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation)] - if ffConfigAnnotationOk { - m.Log.V(1).Info("DEPRECATED: The openfeature.dev/featureflagconfiguration annotation has been superseded by the openfeature.dev/flagsourceconfiguration annotation. " + - "Docs: https://github.com/open-feature/open-feature-operator/blob/main/docs/annotations.md") - if err := m.handleFeatureFlagConfigurationAnnotation(ctx, flagSourceConfigurationSpec, ffConfigAnnotation, req.Namespace); err != nil { - m.Log.Error(err, "unable to handle openfeature.dev/featureflagconfiguration annotation") - return nil, http.StatusInternalServerError, err - } - } - return flagSourceConfigurationSpec, 0, nil -} - -func (m *PodMutator) checkOFEnabled(annotations map[string]string) bool { - val, ok := annotations[OpenFeatureAnnotationPrefix] - if ok { - m.Log.V(1).Info("DEPRECATED: The openfeature.dev annotation has been superseded by the openfeature.dev/enabled annotation. " + - "Docs: https://github.com/open-feature/open-feature-operator/blob/main/docs/annotations.md") - if val == "enabled" { - return true - } - } - val, ok = annotations[fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation)] - if ok { - if val == "true" { - return true - } - } - return false + return featureFlagSourceSpec, 0, nil } // BackfillPermissions recovers the state of the flagd-kubernetes-sync role binding in the event of upgrade @@ -192,13 +129,13 @@ func (m *PodMutator) BackfillPermissions(ctx context.Context) error { m.ready = true }() for i := 0; i < 5; i++ { - // fetch all pods with the fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation) annotation set to "true" + // fetch all pods with the fmt.Sprintf("%s/%s", OpenFeatureAnnotationPath, EnabledAnnotation) annotation set to "true" podList := &corev1.PodList{} err := m.Client.List(ctx, podList, client.MatchingFields{ - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPath, AllowKubernetesSyncAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPath, constant.AllowKubernetesSyncAnnotation): "true", }) if err != nil { - if !goErr.Is(err, &cache.ErrCacheNotStarted{}) { + if !errors.Is(err, &cache.ErrCacheNotStarted{}) { return err } time.Sleep(1 * time.Second) @@ -213,66 +150,32 @@ func (m *PodMutator) BackfillPermissions(ctx context.Context) error { err, fmt.Sprintf("unable backfill permissions for pod %s/%s", pod.Namespace, pod.Name), "webhook", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPath, AllowKubernetesSyncAnnotation), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPath, constant.AllowKubernetesSyncAnnotation), ) } } return nil } - return goErr.New("unable to backfill permissions for the flagd-kubernetes-sync role binding: timeout") -} - -func parseList(s string) []string { - out := []string{} - ss := strings.Split(s, ",") - for i := 0; i < len(ss); i++ { - newS := strings.TrimSpace(ss[i]) - if newS != "" { //function should not add empty values - out = append(out, newS) - } - } - return out + return errors.New("unable to backfill permissions for the flagd-kubernetes-sync role binding: timeout") } -// PodMutator implements admission.DecoderInjector. -// A decoder will be automatically injected. - // InjectDecoder injects the decoder. func (m *PodMutator) InjectDecoder(d *admission.Decoder) error { m.decoder = d return nil } -func (m *PodMutator) getFeatureFlag(ctx context.Context, namespace string, name string) v1alpha1.FeatureFlagConfiguration { - ffConfig := v1alpha1.FeatureFlagConfiguration{} - if err := m.Client.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, &ffConfig); errors.IsNotFound(err) { - return v1alpha1.FeatureFlagConfiguration{} +func (m *PodMutator) getFeatureFlagSource(ctx context.Context, namespace string, name string) (*api.FeatureFlagSource, error) { + fcConfig := &api.FeatureFlagSource{} + if err := m.Client.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, fcConfig); err != nil { + return nil, err } - return ffConfig + return fcConfig, nil } -func (m *PodMutator) getFlagSourceConfiguration(ctx context.Context, namespace string, name string) v1alpha1.FlagSourceConfiguration { - fcConfig := v1alpha1.FlagSourceConfiguration{} - if err := m.Client.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, &fcConfig); errors.IsNotFound(err) { - return v1alpha1.FlagSourceConfiguration{} - } - return fcConfig -} - -func OpenFeatureEnabledAnnotationIndex(o client.Object) []string { - pod := o.(*corev1.Pod) - if pod.ObjectMeta.Annotations == nil { - return []string{ - "false", - } - } - val, ok := pod.ObjectMeta.Annotations[fmt.Sprintf("openfeature.dev/%s", AllowKubernetesSyncAnnotation)] - if ok && val == "true" { - return []string{ - "true", - } - } - return []string{ - "false", +func (m *PodMutator) IsReady(_ *http.Request) error { + if m.ready { + return nil } + return errors.New("pod mutator is not ready") } diff --git a/webhooks/pod_webhook_component_test.go b/webhooks/pod_webhook_component_test.go deleted file mode 100644 index 9c8f7b6e2..000000000 --- a/webhooks/pod_webhook_component_test.go +++ /dev/null @@ -1,841 +0,0 @@ -package webhooks - -import ( - "context" - "encoding/json" - "fmt" - "reflect" - "testing" - "time" - - jsonpatch "github.com/evanphx/json-patch/v5" - "github.com/go-logr/logr/testr" - "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - corev1alpha2 "github.com/open-feature/open-feature-operator/apis/core/v1alpha2" - corev1alpha3 "github.com/open-feature/open-feature-operator/apis/core/v1alpha3" - controllercommon "github.com/open-feature/open-feature-operator/controllers/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - admissionv1 "k8s.io/api/admission/v1" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/rbac/v1" - "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" - errors2 "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -var ( - k8sClient client.Client - testCtx context.Context - mutator *PodMutator -) - -const ( - mutatePodNamespace = "test-mutate-pod" - defaultPodName = "test-pod" - defaultPodServiceAccountName = "test-pod-service-account" - featureFlagConfigurationName = "test-feature-flag-configuration" - featureFlagConfigurationName2 = "test-feature-flag-configuration-2" - flagSourceConfigurationName = "test-flag-source-configuration" - flagSourceConfigurationName2 = "test-flag-source-configuration-2" - flagSourceConfigurationName3 = "test-flag-source-configuration-3" - flagSourceConfigGrpc = "test-flag-source-grpc" - existingPod1Name = "existing-pod-1" - existingPod1ServiceAccountName = "existing-pod-1-service-account" - existingPod2Name = "existing-pod-2" - existingPod2ServiceAccountName = "existing-pod-2-service-account" - featureFlagConfigurationNamespace = "test-validate-featureflagconfiguration" - featureFlagSpec = ` - { - "flags": { - "new-welcome-message": { - "state": "ENABLED", - "variants": { - "on": true, - "off": false - }, - "defaultVariant": "on" - } - } - }` -) - -func TestPodMutationWebhook_Component(t *testing.T) { - setupTests(t) - t.Run("should backfill role binding subjects when annotated pods already exist in the cluster", func(t *testing.T) { - // this integration test confirms the proper execution of the podMutator.BackfillPermissions method - // this method is responsible for backfilling the subjects of the open-feature-operator-flagd-kubernetes-sync - // cluster role binding, for previously existing pods on startup - // a retry is required on this test as the backfilling occurs asynchronously - var finalError error - for i := 0; i < 3; i++ { - pod1 := getPod(existingPod1Name, t) - pod2 := getPod(existingPod2Name, t) - - handleMutation(t, pod1) - handleMutation(t, pod2) - - rb := getRoleBinding(clusterRoleBindingName, t) - - unexpectedServiceAccount := "" - for _, subject := range rb.Subjects { - if !reflect.DeepEqual(subject, v1.Subject{ - Kind: "ServiceAccount", - APIGroup: "", - Name: existingPod1ServiceAccountName, - Namespace: mutatePodNamespace, - }) && - !reflect.DeepEqual(subject, v1.Subject{ - Kind: "ServiceAccount", - APIGroup: "", - Name: existingPod2ServiceAccountName, - Namespace: mutatePodNamespace, - }) { - unexpectedServiceAccount = subject.Name - } - } - if unexpectedServiceAccount != "" { - finalError = fmt.Errorf("unexpected subject found in role binding, name: %s", unexpectedServiceAccount) - time.Sleep(1 * time.Second) - continue - } - finalError = nil - break - } - require.Nil(t, finalError) - }) - - t.Run("should update cluster role binding's subjects", func(t *testing.T) { - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - }) - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - - handleMutation(t, pod) - - crb := &v1.ClusterRoleBinding{} - err = k8sClient.Get(testCtx, client.ObjectKey{Name: clusterRoleBindingName}, crb) - require.Nil(t, err) - - require.Contains(t, crb.Subjects, v1.Subject{ - Kind: "ServiceAccount", - APIGroup: "", - Name: defaultPodServiceAccountName, - Namespace: mutatePodNamespace, - }) - }) - - t.Run("should create flagd sidecar", func(t *testing.T) { - flagConfig, _ := v1alpha1.NewFlagSourceConfigurationSpec() - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - }) - err := k8sClient.Create(testCtx, pod) - defer podMutationWebhookCleanup(t) - require.Nil(t, err) - pod = handleMutation(t, pod) - require.Equal(t, "true", pod.Annotations["openfeature.dev/allowkubernetessync"]) - require.Equal(t, 2, len(pod.Spec.Containers)) - require.Equal(t, pod.Spec.Containers[1].Name, "flagd") - require.Equal(t, pod.Spec.Containers[1].Image, fmt.Sprintf("%s:%s", flagConfig.Image, flagConfig.Tag)) - require.Equal(t, pod.Spec.Containers[1].Args, []string{ - "start", "--sources", "[{\"uri\":\"test-mutate-pod/test-feature-flag-configuration\",\"provider\":\"kubernetes\"}]", - }) - require.Equal(t, pod.Spec.Containers[1].ImagePullPolicy, FlagDImagePullPolicy) - require.Equal(t, pod.Spec.Containers[1].Env, []corev1.EnvVar{ - {Name: "FLAGD_LOG_LEVEL", Value: "dev"}, - }) - - require.Equal(t, []corev1.ContainerPort{ - { - Name: "metrics", - ContainerPort: 8014, - }, - }, pod.Spec.Containers[1].Ports) - - // Validate probes. Default config will set them - liveness := pod.Spec.Containers[1].LivenessProbe - require.NotNil(t, liveness) - require.Equal(t, ProbeLiveness, liveness.HTTPGet.Path) - - readiness := pod.Spec.Containers[1].ReadinessProbe - require.NotNil(t, readiness) - require.Equal(t, ProbeReadiness, readiness.HTTPGet.Path) - - }) - - t.Run("should create flagd sidecar even if openfeature.dev/featureflagconfiguration annotation isn't present", func(t *testing.T) { - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - }) - - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - pod = handleMutation(t, pod) - - require.Equal(t, 2, len(pod.Spec.Containers)) - }) - - t.Run("should not create flagd sidecar if openfeature.dev annotation is disabled", func(t *testing.T) { - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "disabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - }) - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - - pod = handleMutation(t, pod) - - require.Equal(t, len(pod.Spec.Containers), 1) - - }) - - t.Run("should fail if pod has no owner references", func(t *testing.T) { - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - }) - pod.OwnerReferences = nil - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - handleError(t, pod) - }) - - t.Run("should fail if service account not found", func(t *testing.T) { - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - }) - pod.Spec.ServiceAccountName = "foo" - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - handleError(t, pod) - }) - - t.Run("should create config map if sync provider is filepath", func(t *testing.T) { - ffConfig := &v1alpha1.FeatureFlagConfiguration{} - err := k8sClient.Get( - testCtx, client.ObjectKey{Name: featureFlagConfigurationName, Namespace: mutatePodNamespace}, ffConfig, - ) - require.Nil(t, err) - - ffConfig.Spec = v1alpha1.FeatureFlagConfigurationSpec{ - SyncProvider: &v1alpha1.FeatureFlagSyncProvider{ - Name: string(v1alpha1.SyncProviderFilepath), - }, - } - err = k8sClient.Update(testCtx, ffConfig) - require.Nil(t, err) - - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - }) - err = k8sClient.Create(testCtx, pod) - require.Nil(t, err) - - defer func() { - podMutationWebhookCleanup(t) - // reset FeatureFlagConfiguration - ffConfig.Spec.SyncProvider = nil - err = k8sClient.Update(testCtx, ffConfig) - require.Nil(t, err) - }() - - handleMutation(t, pod) - - cm := &corev1.ConfigMap{} - err = k8sClient.Get(testCtx, client.ObjectKey{ - Name: featureFlagConfigurationName, - Namespace: mutatePodNamespace, - }, cm) - require.Nil(t, err) - - require.Equal(t, cm.Name, featureFlagConfigurationName) - require.Equal(t, cm.Namespace, mutatePodNamespace) - require.EqualValues(t, map[string]string{ - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): featureFlagConfigurationName, - }, cm.Annotations) - require.Equal(t, 2, len(cm.OwnerReferences)) - - require.Equal(t, cm.Data, map[string]string{ - fmt.Sprintf("%s_%s.flagd.json", mutatePodNamespace, featureFlagConfigurationName): ffConfig.Spec.FeatureFlagSpec, - }) - - }) - - t.Run("should not panic if flagDSpec isn't provided", func(t *testing.T) { - ffConfigName := "feature-flag-configuration-panic-test" - ffConfig := &v1alpha1.FeatureFlagConfiguration{} - ffConfig.Namespace = mutatePodNamespace - ffConfig.Name = ffConfigName - ffConfig.Spec.FeatureFlagSpec = featureFlagSpec - err := k8sClient.Create(testCtx, ffConfig) - require.Nil(t, err) - - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, ffConfigName), - }) - err = k8sClient.Create(testCtx, pod) - require.Nil(t, err) - - podMutationWebhookCleanup(t) - err = k8sClient.Delete(testCtx, ffConfig, client.GracePeriodSeconds(0)) - require.Nil(t, err) - }) - - t.Run(`should create flagd sidecar if openfeature.dev/enabled annotation is "true"`, func(t *testing.T) { - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation): "true", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - }) - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - - pod = handleMutation(t, pod) - - require.Equal(t, 2, len(pod.Spec.Containers)) - - }) - - t.Run(`should only write non default flagsourceconfiguration env vars to the flagd container`, func(t *testing.T) { - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - "openfeature.dev/flagsourceconfiguration": fmt.Sprintf("%s/%s", mutatePodNamespace, flagSourceConfigurationName), - }) - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - - pod = handleMutation(t, pod) - require.Equal(t, []corev1.EnvVar{ - {Name: "FLAGD_METRICS_PORT", Value: "8081"}, - {Name: "FLAGD_PORT", Value: "8080"}, - {Name: "FLAGD_EVALUATOR", Value: "yaml"}, - {Name: "FLAGD_SOCKET_PATH", Value: "/tmp/flag-source.sock"}, - {Name: "FLAGD_LOG_FORMAT", Value: "console"}, - }, - pod.Spec.Containers[1].Env) - - }) - - t.Run(`should use env var configuration to overwrite flagsourceconfiguration defaults`, func(t *testing.T) { - t.Setenv(v1alpha1.SidecarEnvVarPrefix, "MY_SIDECAR") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarMetricPortEnvVar), "10") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarPortEnvVar), "20") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarSocketPathEnvVar), "socket") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarEvaluatorEnvVar), "evaluator") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarImageEnvVar), "image") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarVersionEnvVar), "version") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarDefaultSyncProviderEnvVar), "filepath") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarProviderArgsEnvVar), "key=value,key2=value2") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarLogFormatEnvVar), "yaml") - - // Override probes - disabled - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarProbesEnabledVar), "false") - - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - }) - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - - pod = handleMutation(t, pod) - require.Equal(t, pod.Spec.Containers[1].Env, []corev1.EnvVar{ - {Name: "MY_SIDECAR_METRICS_PORT", Value: "10"}, - {Name: "MY_SIDECAR_PORT", Value: "20"}, - {Name: "MY_SIDECAR_EVALUATOR", Value: "evaluator"}, - {Name: "MY_SIDECAR_SOCKET_PATH", Value: "socket"}, - {Name: "MY_SIDECAR_LOG_FORMAT", Value: "yaml"}, - }) - require.Equal(t, pod.Spec.Containers[1].Image, "image:version") - require.Equal(t, pod.Spec.Containers[1].Args, []string{ - "start", - SourceConfigParam, - "[{\"uri\":\"/etc/flagd/test-mutate-pod_test-feature-flag-configuration/test-mutate-pod_test-feature-flag-configuration.flagd.json\",\"provider\":\"file\"}]", - "--sync-provider-args", - "key=value", - "--sync-provider-args", - "key2=value2", - }) - - // Validate probes - disabled - require.Nil(t, pod.Spec.Containers[1].LivenessProbe) - require.Nil(t, pod.Spec.Containers[1].ReadinessProbe) - - }) - - t.Run(`should overwrite env var configuration with flagsourceconfiguration values`, func(t *testing.T) { - t.Setenv(v1alpha1.SidecarEnvVarPrefix, "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarMetricPortEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarPortEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarSocketPathEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarEvaluatorEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarImageEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarVersionEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarDefaultSyncProviderEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarProviderArgsEnvVar), "key=value,key2=value2") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarLogFormatEnvVar), "") - - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - "openfeature.dev/flagsourceconfiguration": fmt.Sprintf("%s/%s", mutatePodNamespace, flagSourceConfigurationName), - }) - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - - pod = handleMutation(t, pod) - require.Equal(t, pod.Spec.Containers[1].Env, []corev1.EnvVar{ - {Name: "FLAGD_METRICS_PORT", Value: "8081"}, - {Name: "FLAGD_PORT", Value: "8080"}, - {Name: "FLAGD_EVALUATOR", Value: "yaml"}, - {Name: "FLAGD_SOCKET_PATH", Value: "/tmp/flag-source.sock"}, - {Name: "FLAGD_LOG_FORMAT", Value: "console"}, - }) - require.Equal(t, pod.Spec.Containers[1].Image, "new-image:latest") - require.Equal(t, pod.Spec.Containers[1].Args, []string{ - "start", - SourceConfigParam, - "[{\"uri\":\"not-real.com\",\"provider\":\"http\"}]", - "--sync-provider-args", - "key=value", - "--sync-provider-args", - "key2=value2", - "--sync-provider-args", - "key3=val3", - }) - }) - - t.Run("should create flagd sidecar using flagsourceconfiguration", func(t *testing.T) { - t.Setenv(v1alpha1.SidecarEnvVarPrefix, "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarMetricPortEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarPortEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarSocketPathEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarEvaluatorEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarImageEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarVersionEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarDefaultSyncProviderEnvVar), "") - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarProviderArgsEnvVar), "") - flagConfig, _ := v1alpha1.NewFlagSourceConfigurationSpec() - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation): "true", - "openfeature.dev/flagsourceconfiguration": fmt.Sprintf("%s/%s", mutatePodNamespace, flagSourceConfigurationName2), - }) - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - - pod = handleMutation(t, pod) - require.Equal(t, pod.Annotations["openfeature.dev/allowkubernetessync"], "true") - require.Equal(t, len(pod.Spec.Containers), 2) - require.Equal(t, pod.Spec.Containers[1].Name, "flagd") - require.Equal(t, pod.Spec.Containers[1].Image, fmt.Sprintf("%s:%s", flagConfig.Image, flagConfig.Tag)) - require.Equal(t, pod.Spec.Containers[1].Args, []string{ - "start", - SourceConfigParam, - "[{\"uri\":\"test-mutate-pod/test-feature-flag-configuration\",\"provider\":\"kubernetes\"}," + - "{\"uri\":\"/etc/flagd/test-mutate-pod_test-feature-flag-configuration-2/test-mutate-pod_test-feature-flag-configuration-2.flagd.json\",\"provider\":\"file\"}]", - }) - require.Equal(t, pod.Spec.Containers[1].ImagePullPolicy, FlagDImagePullPolicy) - require.Equal(t, pod.Spec.Containers[1].Ports, []corev1.ContainerPort{ - { - Name: "metrics", - ContainerPort: 8014, - }, - }) - - }) - - t.Run("should not create flagd sidecar if flagsourceconfiguration does not exist", func(t *testing.T) { - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - "openfeature.dev/flagsourceconfiguration": "im-not-real", - }) - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - handleError(t, pod) - }) - - t.Run("should not create flagd sidecar if flagsourceconfiguration contains a source that does not exist", func(t *testing.T) { - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - "openfeature.dev/flagsourceconfiguration": fmt.Sprintf("%s/%s", mutatePodNamespace, flagSourceConfigurationName3), - }) - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - handleError(t, pod) - }) - - t.Run(`should use defaultSyncProvider if one isn't provided`, func(t *testing.T) { - t.Setenv(fmt.Sprintf("%s_%s", v1alpha1.InputConfigurationEnvVarPrefix, v1alpha1.SidecarDefaultSyncProviderEnvVar), "filepath") - - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FlagSourceConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, flagSourceConfigurationName2), - }) - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - - pod = handleMutation(t, pod) - require.Equal(t, pod.Spec.Containers[1].Args, []string{ - "start", - SourceConfigParam, - "[{\"uri\":\"/etc/flagd/test-mutate-pod_test-feature-flag-configuration/test-mutate-pod_test-feature-flag-configuration.flagd.json\",\"provider\":\"file\"}," + - "{\"uri\":\"/etc/flagd/test-mutate-pod_test-feature-flag-configuration-2/test-mutate-pod_test-feature-flag-configuration-2.flagd.json\",\"provider\":\"file\"}]", - }) - - }) - - t.Run("should create a valid grpc source configuration", func(t *testing.T) { - pod := testPod(defaultPodName, defaultPodServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FlagSourceConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, flagSourceConfigGrpc), - }) - err := k8sClient.Create(testCtx, pod) - require.Nil(t, err) - defer podMutationWebhookCleanup(t) - - pod = handleMutation(t, pod) - require.Equal(t, pod.Spec.Containers[1].Args, []string{ - "start", - SourceConfigParam, - "[{\"uri\":\"grpc-service:9090\",\"provider\":\"grpc\",\"certPath\":\"/tmp/certs\",\"tls\":true,\"providerID\":\"myapp\",\"selector\":\"source=database\"}]", - }) - }) -} - -func handleError(t *testing.T, pod *corev1.Pod) { - _, res := triggerHandler(t, pod) - require.False(t, res.Allowed) -} - -// calls handle of the webhook and returns the mutated pod according to the resulting patch -func handleMutation(t *testing.T, pod *corev1.Pod) *corev1.Pod { - - rawPod, res := triggerHandler(t, pod) - - data, err := json.Marshal(res.Patches) - assert.Nil(t, err) - - patch, err := jsonpatch.DecodePatch(data) - assert.Nil(t, err) - - patchedPod, err := patch.Apply(rawPod) - assert.Nil(t, err) - - newPod := &corev1.Pod{} - err = json.Unmarshal(patchedPod, newPod) - assert.Nil(t, err) - - return newPod -} - -func triggerHandler(t *testing.T, pod *corev1.Pod) ([]byte, admission.Response) { - rawPod, err := json.Marshal(pod) - require.Nil(t, err) - req := admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - UID: pod.UID, - Object: runtime.RawExtension{ - Raw: rawPod, - Object: pod, - }, - }, - } - - res := mutator.Handle(context.TODO(), req) - return rawPod, res -} - -func setupTests(t *testing.T) { - - utilruntime.Must(clientgoscheme.AddToScheme(scheme.Scheme)) - utilruntime.Must(v1alpha1.AddToScheme(scheme.Scheme)) - utilruntime.Must(corev1alpha2.AddToScheme(scheme.Scheme)) - utilruntime.Must(corev1alpha3.AddToScheme(scheme.Scheme)) - - annotationsSyncIndexer := func(obj client.Object) []string { - res := obj.GetAnnotations()[fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, AllowKubernetesSyncAnnotation)] - return []string{res} - } - - featureflagIndexer := func(obj client.Object) []string { - res := obj.GetAnnotations()["openfeature.dev/featureflagconfiguration"] - return []string{res} - } - enabledIndexer := func(obj client.Object) []string { - res := obj.GetAnnotations()["openfeature.dev/enabled"] - return []string{res} - } - - k8sClient = fake.NewClientBuilder(). - WithScheme(scheme.Scheme). - WithIndex( - &corev1.Pod{}, - "metadata.annotations.openfeature.dev/allowkubernetessync", - annotationsSyncIndexer). - WithIndex( - &corev1.Pod{}, - "metadata.annotations.openfeature.dev/featureflagconfiguration", - featureflagIndexer). - WithIndex( - &corev1.Pod{}, - "metadata.annotations.openfeature.dev/enabled", - enabledIndexer). - Build() - - decoder, err := admission.NewDecoder(scheme.Scheme) - require.Nil(t, err) - - setupValidateFeatureFlagConfigurationResources(t) - setupPreviouslyExistingPods(t) - setupMutatePodResources(t) - - mutator = &PodMutator{ - Client: k8sClient, - decoder: decoder, - Log: testr.New(t), - FlagdInjector: &controllercommon.FlagdContainerInjector{ - Client: k8sClient, - Logger: testr.New(t), - FlagDResourceRequirements: corev1.ResourceRequirements{}, - }, - ready: false, - } - -} - -func setupValidateFeatureFlagConfigurationResources(t *testing.T) { - ns := &corev1.Namespace{} - ns.Name = featureFlagConfigurationNamespace - err := k8sClient.Create(testCtx, ns) - require.Nil(t, err) -} - -// // Sets up environment to simulate an upgrade, with an existing pod already in the cluster -func setupPreviouslyExistingPods(t *testing.T) { - ns := &corev1.Namespace{} - ns.Name = mutatePodNamespace - err := k8sClient.Create(testCtx, ns) - require.Nil(t, err) - - svcAccount := &corev1.ServiceAccount{} - svcAccount.Namespace = mutatePodNamespace - svcAccount.Name = existingPod1ServiceAccountName - err = k8sClient.Create(testCtx, svcAccount) - require.Nil(t, err) - - svcAccount = &corev1.ServiceAccount{} - svcAccount.Namespace = mutatePodNamespace - svcAccount.Name = existingPod2ServiceAccountName - err = k8sClient.Create(testCtx, svcAccount) - require.Nil(t, err) - - clusterRoleBinding := &v1.ClusterRoleBinding{} - clusterRoleBinding.Name = clusterRoleBindingName - clusterRoleBinding.APIVersion = "rbac.authorization.k8s.io/v1" - clusterRoleBinding.RoleRef = v1.RoleRef{ - APIGroup: "", - Kind: "ClusterRole", - Name: clusterRoleBindingName, - } - err = k8sClient.Create(testCtx, clusterRoleBinding) - require.Nil(t, err) - - existingPod := testPod(existingPod1Name, existingPod1ServiceAccountName, map[string]string{ - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation): "true", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, AllowKubernetesSyncAnnotation): "true", - }) - err = k8sClient.Create(testCtx, existingPod) - require.Nil(t, err) - - existingPod = testPod(existingPod2Name, existingPod2ServiceAccountName, map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, AllowKubernetesSyncAnnotation): "true", - }) - err = k8sClient.Create(testCtx, existingPod) - require.Nil(t, err) -} - -func setupMutatePodResources(t *testing.T) { - svcAccount := &corev1.ServiceAccount{} - svcAccount.Namespace = mutatePodNamespace - svcAccount.Name = defaultPodServiceAccountName - err := k8sClient.Create(testCtx, svcAccount) - require.Nil(t, err) - - ffConfig := &v1alpha1.FeatureFlagConfiguration{} - ffConfig.Namespace = mutatePodNamespace - ffConfig.Name = featureFlagConfigurationName - ffConfig.Spec.FlagDSpec = &v1alpha1.FlagDSpec{Envs: []corev1.EnvVar{ - {Name: "LOG_LEVEL", Value: "dev"}, - }} - ffConfig.Spec.FeatureFlagSpec = featureFlagSpec - err = k8sClient.Create(testCtx, ffConfig) - require.Nil(t, err) - - fsConfig := &v1alpha1.FlagSourceConfiguration{} - fsConfig.Namespace = mutatePodNamespace - fsConfig.Name = flagSourceConfigurationName - fsConfig.Spec.Port = 8080 - fsConfig.Spec.Evaluator = "yaml" - fsConfig.Spec.Image = "new-image" - fsConfig.Spec.Tag = "latest" - fsConfig.Spec.MetricsPort = 8081 - fsConfig.Spec.SocketPath = "/tmp/flag-source.sock" - fsConfig.Spec.SyncProviderArgs = []string{ - "key3=val3", - } - fsConfig.Spec.LogFormat = "console" - fsConfig.Spec.Sources = []v1alpha1.Source{ - { - Source: "not-real.com", - Provider: "http", - }, - } - err = k8sClient.Create(testCtx, fsConfig) - require.Nil(t, err) - - ffConfig2 := &v1alpha1.FeatureFlagConfiguration{} - ffConfig2.Namespace = mutatePodNamespace - ffConfig2.Name = featureFlagConfigurationName2 - ffConfig2.Spec.FeatureFlagSpec = featureFlagSpec - err = k8sClient.Create(testCtx, ffConfig2) - require.Nil(t, err) - - fsConfig2 := &v1alpha1.FlagSourceConfiguration{} - fsConfig2.Namespace = mutatePodNamespace - fsConfig2.Name = flagSourceConfigurationName2 - fsConfig2.Spec.Sources = []v1alpha1.Source{ - { - Source: fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - }, - { - Source: fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName2), - Provider: v1alpha1.SyncProviderFilepath, - }, - } - err = k8sClient.Create(testCtx, fsConfig2) - require.Nil(t, err) - - fsConfig3 := &v1alpha1.FlagSourceConfiguration{} - fsConfig3.Namespace = mutatePodNamespace - fsConfig3.Name = flagSourceConfigurationName3 - fsConfig3.Spec.Sources = []v1alpha1.Source{ - { - Source: fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName2), - Provider: v1alpha1.SyncProviderKubernetes, - }, - { - Source: "i don't exist", - Provider: v1alpha1.SyncProviderFilepath, - }, - } - err = k8sClient.Create(testCtx, fsConfig3) - require.Nil(t, err) - - fsConfigGrpc := &v1alpha1.FlagSourceConfiguration{} - fsConfigGrpc.Namespace = mutatePodNamespace - fsConfigGrpc.Name = flagSourceConfigGrpc - fsConfigGrpc.Spec.Sources = []v1alpha1.Source{ - { - Source: "grpc-service:9090", - Provider: v1alpha1.SyncProviderGrpc, - TLS: true, - ProviderID: "myapp", - Selector: "source=database", - CertPath: "/tmp/certs", - }, - } - err = k8sClient.Create(testCtx, fsConfigGrpc) - require.Nil(t, err) -} - -func testPod(podName string, serviceAccountName string, annotations map[string]string) *corev1.Pod { - pod := &corev1.Pod{} - pod.Namespace = mutatePodNamespace - pod.Name = podName - pod.Annotations = annotations - - pod.Spec.Containers = []corev1.Container{ - { - Name: "container1", - Image: "ubuntu", - }, - } - pod.Spec.ServiceAccountName = serviceAccountName - - // In reality something like a Deployment would take ownership of pod creation. - // A limitation of envtest is that inbuilt kubernetes controllers like deployment controllers aren't available. - // Below simulates a pod that has ownership. - pod.OwnerReferences = []metav1.OwnerReference{ - { - Name: "simulated-owner", - Kind: "deployment", - APIVersion: "v1", - UID: "1f08bbbf-edb4-452a-9ffd-1898dd24c5b8", - }, - } - return pod -} - -func getPod(podName string, t *testing.T) *corev1.Pod { - pod := &corev1.Pod{} - name := types.NamespacedName{ - Namespace: mutatePodNamespace, - Name: podName, - } - err := k8sClient.Get(testCtx, name, pod) - require.Nil(t, err) - return pod -} - -func getRoleBinding(roleBindingName string, t *testing.T) *v1.ClusterRoleBinding { - roleBinding := &v1.ClusterRoleBinding{} - name := types.NamespacedName{ - Name: roleBindingName, - } - err := k8sClient.Get(testCtx, name, roleBinding) - require.Nil(t, err) - return roleBinding -} - -func podMutationWebhookCleanup(t *testing.T) { - pod := &corev1.Pod{} - pod.Namespace = mutatePodNamespace - pod.Name = defaultPodName - err := k8sClient.Delete(testCtx, pod, client.GracePeriodSeconds(0)) - require.Nil(t, err) - require.Eventually(t, func() bool { - err = k8sClient.Get(testCtx, types.NamespacedName{ - Name: defaultPodName, Namespace: mutatePodNamespace, - }, pod) - return errors2.IsNotFound(err) - }, time.Second, 10*time.Millisecond) -} diff --git a/webhooks/pod_webhook_deprecated.go b/webhooks/pod_webhook_deprecated.go deleted file mode 100644 index ce0b5e8a0..000000000 --- a/webhooks/pod_webhook_deprecated.go +++ /dev/null @@ -1,58 +0,0 @@ -package webhooks - -import ( - "context" - "fmt" - "reflect" - - v1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - "github.com/open-feature/open-feature-operator/pkg/utils" -) - -//nolint:gocyclo -func (m *PodMutator) handleFeatureFlagConfigurationAnnotation(ctx context.Context, fcConfig *v1alpha1.FlagSourceConfigurationSpec, ffconfigAnnotation string, defaultNamespace string) error { - for _, ffName := range parseList(ffconfigAnnotation) { - ns, name := utils.ParseAnnotation(ffName, defaultNamespace) - fsConfig := m.getFeatureFlag(ctx, ns, name) - if reflect.DeepEqual(fsConfig, v1alpha1.FeatureFlagConfiguration{}) { - return fmt.Errorf("FeatureFlagConfiguration %s not found", ffName) - } - if fsConfig.Spec.FlagDSpec != nil { - if len(fsConfig.Spec.FlagDSpec.Envs) > 0 { - fcConfig.EnvVars = append(fsConfig.Spec.FlagDSpec.Envs, fcConfig.EnvVars...) - } - if fsConfig.Spec.FlagDSpec.MetricsPort != 0 && fcConfig.MetricsPort == v1alpha1.DefaultMetricPort { - fcConfig.MetricsPort = fsConfig.Spec.FlagDSpec.MetricsPort - } - } - switch { - case fsConfig.Spec.SyncProvider == nil: - fcConfig.Sources = append(fcConfig.Sources, v1alpha1.Source{ - Provider: fcConfig.DefaultSyncProvider, - Source: ffName, - }) - case v1alpha1.SyncProviderType(fsConfig.Spec.SyncProvider.Name).IsKubernetes(): - fcConfig.Sources = append(fcConfig.Sources, v1alpha1.Source{ - Provider: v1alpha1.SyncProviderKubernetes, - Source: ffName, - }) - case v1alpha1.SyncProviderType(fsConfig.Spec.SyncProvider.Name).IsFilepath(): - fcConfig.Sources = append(fcConfig.Sources, v1alpha1.Source{ - Provider: v1alpha1.SyncProviderFilepath, - Source: ffName, - }) - case v1alpha1.SyncProviderType(fsConfig.Spec.SyncProvider.Name).IsHttp(): - if fsConfig.Spec.SyncProvider.HttpSyncConfiguration == nil { - return fmt.Errorf("FeatureFlagConfiguration %s is missing HttpSyncConfiguration", ffName) - } - fcConfig.Sources = append(fcConfig.Sources, v1alpha1.Source{ - Provider: v1alpha1.SyncProviderHttp, - Source: fsConfig.Spec.SyncProvider.HttpSyncConfiguration.Target, - HttpSyncBearerToken: fsConfig.Spec.SyncProvider.HttpSyncConfiguration.BearerToken, - }) - default: - return fmt.Errorf("FeatureFlagConfiguration %s configuration is unrecognized", ffName) - } - } - return nil -} diff --git a/webhooks/pod_webhook_test.go b/webhooks/pod_webhook_test.go index b517eb4fd..ed3e2b38c 100644 --- a/webhooks/pod_webhook_test.go +++ b/webhooks/pod_webhook_test.go @@ -11,12 +11,10 @@ import ( "github.com/go-logr/logr/testr" "github.com/golang/mock/gomock" - "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" - "github.com/open-feature/open-feature-operator/apis/core/v1alpha2" - "github.com/open-feature/open-feature-operator/apis/core/v1alpha3" - "github.com/open-feature/open-feature-operator/controllers/common/constant" - commonmock "github.com/open-feature/open-feature-operator/controllers/common/mock" - "github.com/open-feature/open-feature-operator/pkg/utils" + api "github.com/open-feature/open-feature-operator/apis/core/v1beta1" + apicommon "github.com/open-feature/open-feature-operator/apis/core/v1beta1/common" + "github.com/open-feature/open-feature-operator/common/constant" + flagdinjectorfake "github.com/open-feature/open-feature-operator/common/flagdinjector/fake" "github.com/stretchr/testify/require" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" @@ -31,35 +29,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) -func TestOpenFeatureEnabledAnnotationIndex(t *testing.T) { - - tests := []struct { - name string - o client.Object - want []string - }{ - { - name: "no annotations", - o: &corev1.Pod{}, - want: []string{"false"}, - }, { - name: "annotated wrong", - o: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"test/ann": "nope", "openfeature.dev/allowkubernetessync": "false"}}}, - want: []string{"false"}, - }, { - name: "annotated with enabled index", - o: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"openfeature.dev/allowkubernetessync": "true"}}}, - want: []string{"true"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := OpenFeatureEnabledAnnotationIndex(tt.o); !reflect.DeepEqual(got, tt.want) { - t.Errorf("OpenFeatureEnabledAnnotationIndex() = %v, want %v", got, tt.want) - } - }) - } -} +const ( + mutatePodNamespace = "test-mutate-pod" + defaultPodServiceAccountName = "test-pod-service-account" + featureFlagSourceName = "test-feature-flag-source" +) func TestPodMutator_BackfillPermissions(t *testing.T) { const ( @@ -72,7 +46,7 @@ func TestPodMutator_BackfillPermissions(t *testing.T) { name string mutator *PodMutator wantErr bool - setup func(injector *commonmock.MockIFlagdContainerInjector) + setup func(injector *flagdinjectorfake.MockFlagdContainerInjector) }{ { name: "no annotated pod", @@ -94,14 +68,14 @@ func TestPodMutator_BackfillPermissions(t *testing.T) { Name: pod, Namespace: ns, Annotations: map[string]string{ - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation): "true", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, AllowKubernetesSyncAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.EnabledAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.FeatureFlagSourceAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagSourceName), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.AllowKubernetesSyncAnnotation): "true", }}, }, ), }, - setup: func(injector *commonmock.MockIFlagdContainerInjector) { + setup: func(injector *flagdinjectorfake.MockFlagdContainerInjector) { injector.EXPECT().EnableClusterRoleBinding( gomock.Any(), ns, @@ -120,9 +94,9 @@ func TestPodMutator_BackfillPermissions(t *testing.T) { Name: pod + "-1", Namespace: ns, Annotations: map[string]string{ - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation): "true", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, AllowKubernetesSyncAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.EnabledAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.FeatureFlagSourceAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagSourceName), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.AllowKubernetesSyncAnnotation): "true", }}, }, &corev1.Pod{ @@ -130,14 +104,14 @@ func TestPodMutator_BackfillPermissions(t *testing.T) { Name: pod + "-2", Namespace: ns, Annotations: map[string]string{ - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation): "true", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, AllowKubernetesSyncAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.EnabledAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.FeatureFlagSourceAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagSourceName), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.AllowKubernetesSyncAnnotation): "true", }}, }, ), }, - setup: func(injector *commonmock.MockIFlagdContainerInjector) { + setup: func(injector *flagdinjectorfake.MockFlagdContainerInjector) { // make the mock return an error - in this case we still expect the number of invocations // to match the number of pods injector.EXPECT().EnableClusterRoleBinding( @@ -158,9 +132,9 @@ func TestPodMutator_BackfillPermissions(t *testing.T) { Name: pod, Namespace: ns, Annotations: map[string]string{ - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation): "true", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, AllowKubernetesSyncAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.EnabledAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.FeatureFlagSourceAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagSourceName), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.AllowKubernetesSyncAnnotation): "true", }}, Spec: corev1.PodSpec{ServiceAccountName: "my-service-account"}, }, @@ -169,9 +143,9 @@ func TestPodMutator_BackfillPermissions(t *testing.T) { Name: name, Namespace: ns, Annotations: map[string]string{ - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation): "true", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, AllowKubernetesSyncAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.EnabledAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.FeatureFlagSourceAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagSourceName), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.AllowKubernetesSyncAnnotation): "true", }}, }, &rbac.ClusterRoleBinding{ @@ -181,7 +155,7 @@ func TestPodMutator_BackfillPermissions(t *testing.T) { }, ), }, - setup: func(injector *commonmock.MockIFlagdContainerInjector) { + setup: func(injector *flagdinjectorfake.MockFlagdContainerInjector) { injector.EXPECT().EnableClusterRoleBinding(context.Background(), ns, "my-service-account").Times(1) }, wantErr: false, @@ -196,9 +170,9 @@ func TestPodMutator_BackfillPermissions(t *testing.T) { Name: pod, Namespace: ns, Annotations: map[string]string{ - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation): "true", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, AllowKubernetesSyncAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.EnabledAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.FeatureFlagSourceAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagSourceName), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.AllowKubernetesSyncAnnotation): "true", }}, }, &corev1.ServiceAccount{ @@ -206,9 +180,9 @@ func TestPodMutator_BackfillPermissions(t *testing.T) { Name: name, Namespace: ns, Annotations: map[string]string{ - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation): "true", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, AllowKubernetesSyncAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.EnabledAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.FeatureFlagSourceAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagSourceName), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.AllowKubernetesSyncAnnotation): "true", }}, }, &rbac.ClusterRoleBinding{ @@ -226,7 +200,7 @@ func TestPodMutator_BackfillPermissions(t *testing.T) { ), }, wantErr: false, - setup: func(injector *commonmock.MockIFlagdContainerInjector) { + setup: func(injector *flagdinjectorfake.MockFlagdContainerInjector) { injector.EXPECT().EnableClusterRoleBinding(context.Background(), ns, "").Times(1) }, }, @@ -236,7 +210,7 @@ func TestPodMutator_BackfillPermissions(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) - mockInjector := commonmock.NewMockIFlagdContainerInjector(ctrl) + mockInjector := flagdinjectorfake.NewMockFlagdContainerInjector(ctrl) if tt.setup != nil { tt.setup(mockInjector) @@ -263,8 +237,8 @@ func TestPodMutator_Handle(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "myAnnotatedPod", Annotations: map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.EnabledAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.FeatureFlagSourceAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagSourceName), }, }, }) @@ -275,8 +249,8 @@ func TestPodMutator_Handle(t *testing.T) { Name: "myAnnotatedPod", Namespace: mutatePodNamespace, Annotations: map[string]string{ - OpenFeatureAnnotationPrefix: "enabled", - fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, FeatureFlagConfigurationAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagConfigurationName), + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.EnabledAnnotation): "true", + fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.FeatureFlagSourceAnnotation): fmt.Sprintf("%s/%s", mutatePodNamespace, featureFlagSourceName), }, OwnerReferences: []metav1.OwnerReference{{UID: "123"}}, }, @@ -291,7 +265,7 @@ func TestPodMutator_Handle(t *testing.T) { req admission.Request wantCode int32 allow bool - setup func(mockInjector *commonmock.MockIFlagdContainerInjector) + setup func(mockInjector *flagdinjectorfake.MockFlagdContainerInjector) }{ { name: "successful request pod not annotated", @@ -331,20 +305,21 @@ func TestPodMutator_Handle(t *testing.T) { }, }, wantCode: http.StatusForbidden, + allow: false, }, { name: "forbidden request pod annotated with owner, but cluster role binding cannot be enabled", mutator: &PodMutator{ Client: NewClient(false, - &v1alpha1.FeatureFlagConfiguration{ + &api.FeatureFlagSource{ ObjectMeta: metav1.ObjectMeta{ - Name: featureFlagConfigurationName, + Name: featureFlagSourceName, Namespace: mutatePodNamespace, }, - Spec: v1alpha1.FeatureFlagConfigurationSpec{ - FlagDSpec: &v1alpha1.FlagDSpec{Envs: []corev1.EnvVar{ - {Name: "LOG_LEVEL", Value: "dev"}, - }}, + Spec: api.FeatureFlagSourceSpec{ + Sources: []api.Source{ + {Provider: apicommon.SyncProviderKubernetes}, + }, }, }, ), @@ -361,7 +336,7 @@ func TestPodMutator_Handle(t *testing.T) { }, }, }, - setup: func(mockInjector *commonmock.MockIFlagdContainerInjector) { + setup: func(mockInjector *flagdinjectorfake.MockFlagdContainerInjector) { mockInjector.EXPECT(). EnableClusterRoleBinding( gomock.Any(), @@ -370,21 +345,18 @@ func TestPodMutator_Handle(t *testing.T) { ).Return(errors.New("error")).Times(1) }, wantCode: http.StatusForbidden, + allow: false, }, { name: "forbidden request pod annotated with owner, but flagd proxy is not ready", mutator: &PodMutator{ Client: NewClient(false, - &v1alpha1.FeatureFlagConfiguration{ + &api.FeatureFlagSource{ ObjectMeta: metav1.ObjectMeta{ - Name: featureFlagConfigurationName, + Name: featureFlagSourceName, Namespace: mutatePodNamespace, }, - Spec: v1alpha1.FeatureFlagConfigurationSpec{ - FlagDSpec: &v1alpha1.FlagDSpec{Envs: []corev1.EnvVar{ - {Name: "LOG_LEVEL", Value: "dev"}, - }}, - }, + Spec: api.FeatureFlagSourceSpec{}, }, ), decoder: decoder, @@ -400,26 +372,19 @@ func TestPodMutator_Handle(t *testing.T) { }, }, }, - setup: func(mockInjector *commonmock.MockIFlagdContainerInjector) { - mockInjector.EXPECT(). - EnableClusterRoleBinding( - gomock.Any(), - antPod.Namespace, - antPod.Spec.ServiceAccountName, - ).Return(nil).Times(1) - + setup: func(mockInjector *flagdinjectorfake.MockFlagdContainerInjector) { mockInjector.EXPECT(). InjectFlagd( gomock.Any(), gomock.AssignableToTypeOf(&antPod.ObjectMeta), gomock.AssignableToTypeOf(&antPod.Spec), - gomock.AssignableToTypeOf(&v1alpha1.FlagSourceConfigurationSpec{}), + gomock.AssignableToTypeOf(&api.FeatureFlagSourceSpec{}), ).Return(constant.ErrFlagdProxyNotReady).Times(1) }, wantCode: http.StatusForbidden, }, { - name: "forbidden request pod annotated with owner, but feature flag configuration is not available", + name: "forbidden request pod annotated with owner, but FeatureFlagSource is not available", mutator: &PodMutator{ Client: NewClient(false), decoder: decoder, @@ -435,23 +400,7 @@ func TestPodMutator_Handle(t *testing.T) { }, }, }, - setup: func(mockInjector *commonmock.MockIFlagdContainerInjector) { - mockInjector.EXPECT(). - EnableClusterRoleBinding( - gomock.Any(), - antPod.Namespace, - antPod.Spec.ServiceAccountName, - ).Return(nil).Times(1) - - mockInjector.EXPECT(). - InjectFlagd( - gomock.Any(), - gomock.AssignableToTypeOf(&antPod.ObjectMeta), - gomock.AssignableToTypeOf(&antPod.Spec), - gomock.AssignableToTypeOf(&v1alpha1.FlagSourceConfigurationSpec{}), - ).Return(constant.ErrFlagdProxyNotReady).Times(1) - }, - wantCode: http.StatusInternalServerError, + wantCode: http.StatusNotFound, }, { name: "happy path: request pod annotated configured for env var", @@ -465,20 +414,16 @@ func TestPodMutator_Handle(t *testing.T) { }, }, &rbac.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{Name: clusterRoleBindingName}, + ObjectMeta: metav1.ObjectMeta{Name: constant.ClusterRoleBindingName}, Subjects: nil, RoleRef: rbac.RoleRef{}, }, - &v1alpha1.FeatureFlagConfiguration{ + &api.FeatureFlagSource{ ObjectMeta: metav1.ObjectMeta{ - Name: featureFlagConfigurationName, + Name: featureFlagSourceName, Namespace: mutatePodNamespace, }, - Spec: v1alpha1.FeatureFlagConfigurationSpec{ - FlagDSpec: &v1alpha1.FlagDSpec{Envs: []corev1.EnvVar{ - {Name: "LOG_LEVEL", Value: "dev"}, - }}, - }, + Spec: api.FeatureFlagSourceSpec{}, }, ), decoder: decoder, @@ -493,20 +438,13 @@ func TestPodMutator_Handle(t *testing.T) { }, }, }, - setup: func(mockInjector *commonmock.MockIFlagdContainerInjector) { - mockInjector.EXPECT(). - EnableClusterRoleBinding( - gomock.Any(), - antPod.Namespace, - antPod.Spec.ServiceAccountName, - ).Return(nil).Times(1) - + setup: func(mockInjector *flagdinjectorfake.MockFlagdContainerInjector) { mockInjector.EXPECT(). InjectFlagd( gomock.Any(), gomock.AssignableToTypeOf(&antPod.ObjectMeta), gomock.AssignableToTypeOf(&antPod.Spec), - gomock.AssignableToTypeOf(&v1alpha1.FlagSourceConfigurationSpec{}), + gomock.AssignableToTypeOf(&api.FeatureFlagSourceSpec{}), ).Return(nil).Times(1) }, allow: true, @@ -535,7 +473,7 @@ func TestPodMutator_Handle(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) - mockFlagdInjector := commonmock.NewMockIFlagdContainerInjector(ctrl) + mockFlagdInjector := flagdinjectorfake.NewMockFlagdContainerInjector(ctrl) m := tt.mutator @@ -555,127 +493,17 @@ func TestPodMutator_Handle(t *testing.T) { } } -func TestPodMutator_checkOFEnabled(t *testing.T) { - - tests := []struct { - name string - mutator PodMutator - annotations map[string]string - want bool - }{ - { - name: "deprecated enabled", - mutator: PodMutator{ - Log: testr.New(t), - }, - annotations: map[string]string{OpenFeatureAnnotationPrefix: "enabled"}, - want: true, - }, - { - name: "enabled", - mutator: PodMutator{ - Log: testr.New(t), - }, - annotations: map[string]string{fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation): "true"}, - want: true, - }, { - name: "disabled", - mutator: PodMutator{ - Log: testr.New(t), - }, - annotations: map[string]string{fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, EnabledAnnotation): "false"}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := &tt.mutator - if got := m.checkOFEnabled(tt.annotations); got != tt.want { - t.Errorf("checkOFEnabled() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_parseAnnotation(t *testing.T) { - tests := []struct { - name string - s string - defaultNs string - wantNs string - want string - }{ - { - name: "no namespace", - s: "test", - defaultNs: "ofo", - wantNs: "ofo", - want: "test", - }, - { - name: "namespace", - s: "myns/test", - defaultNs: "ofo", - wantNs: "myns", - want: "test", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, got1 := utils.ParseAnnotation(tt.s, tt.defaultNs) - if got != tt.wantNs { - t.Errorf("parseAnnotation() got = %v, want %v", got, tt.wantNs) - } - if got1 != tt.want { - t.Errorf("parseAnnotation() got1 = %v, want %v", got1, tt.want) - } - }) - } -} - -func Test_parseList(t *testing.T) { - - tests := []struct { - name string - s string - want []string - }{ - { - name: "empty string", - s: "", - want: []string{}, - }, { - name: "nice list with spaces", - s: "annotation1, annotation2, annotation4 , annotation3,", - want: []string{"annotation1", "annotation2", "annotation4", "annotation3"}, - }, { - name: "list with no spaces", - s: "annotation1, annotation2,annotation4, annotation3", - want: []string{"annotation1", "annotation2", "annotation4", "annotation3"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := parseList(tt.s); !reflect.DeepEqual(got, tt.want) { - t.Errorf("parseList() = %v, want %v", got, tt.want) - } - }) - } -} - func NewClient(withIndexes bool, objs ...client.Object) client.Client { utilruntime.Must(clientgoscheme.AddToScheme(scheme.Scheme)) - utilruntime.Must(v1alpha1.AddToScheme(scheme.Scheme)) - utilruntime.Must(v1alpha2.AddToScheme(scheme.Scheme)) - utilruntime.Must(v1alpha3.AddToScheme(scheme.Scheme)) + utilruntime.Must(api.AddToScheme(scheme.Scheme)) annotationsSyncIndexer := func(obj client.Object) []string { - res := obj.GetAnnotations()[fmt.Sprintf("%s/%s", OpenFeatureAnnotationPrefix, AllowKubernetesSyncAnnotation)] + res := obj.GetAnnotations()[fmt.Sprintf("%s/%s", constant.OpenFeatureAnnotationPrefix, constant.AllowKubernetesSyncAnnotation)] return []string{res} } featureflagIndexer := func(obj client.Object) []string { - res := obj.GetAnnotations()["openfeature.dev/featureflagconfiguration"] + res := obj.GetAnnotations()["openfeature.dev/featureflag"] return []string{res} } @@ -691,9 +519,22 @@ func NewClient(withIndexes bool, objs ...client.Object) client.Client { annotationsSyncIndexer). WithIndex( &corev1.Pod{}, - "metadata.annotations.openfeature.dev/featureflagconfiguration", + "metadata.annotations.openfeature.dev/featureflag", featureflagIndexer). Build() } return fakeClient.Build() } + +func TestPodMutator_IsReady(t *testing.T) { + + podMutator := PodMutator{ + ready: true, + } + + require.Nil(t, podMutator.IsReady(nil)) + + podMutator.ready = false + + require.NotNil(t, podMutator.IsReady(nil)) +}