diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 046871b52..d1852cf50 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -4,7 +4,7 @@ resources: # if your manager will use a service account that exists at # runtime. Be sure to update RoleBinding and ClusterRoleBinding # subjects if changing service account names. -- service_account_kube_flagd_proxy.yaml +- service_account_flagd_proxy.yaml - service_account_manager.yaml - role.yaml - role_binding.yaml diff --git a/config/rbac/service_account_kube_flagd_proxy.yaml b/config/rbac/service_account_flagd_proxy.yaml similarity index 100% rename from config/rbac/service_account_kube_flagd_proxy.yaml rename to config/rbac/service_account_flagd_proxy.yaml diff --git a/controllers/core/flagsourceconfiguration/controller.go b/controllers/core/flagsourceconfiguration/controller.go index b895ced1f..66a9f65b5 100644 --- a/controllers/core/flagsourceconfiguration/controller.go +++ b/controllers/core/flagsourceconfiguration/controller.go @@ -35,25 +35,6 @@ import ( corev1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1" ) -const ( - FlagdProxyDeploymentName = "flagd-proxy" - FlagdProxyServiceAccountName = "open-feature-operator-flagd-proxy" - FlagdProxyServiceName = "flagd-proxy-svc" - - 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" - defaultFlagdProxyImage = "ghcr.io/open-feature/flagd-proxy" - defaultFlagdProxyTag = "v0.2.1" //FLAGD_PROXY_TAG_RENOVATE - defaultFlagdProxyPort = 8015 - defaultFlagdProxyMetricsPort = 8016 - defaultFlagdProxyDebugLogging = false - defaultFlagdProxyNamespace = "open-feature-operator-system" -) - // FlagSourceConfigurationReconciler reconciles a FlagSourceConfiguration object type FlagSourceConfigurationReconciler struct { client.Client @@ -92,7 +73,7 @@ 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); err != nil { + if err := r.FlagdProxy.handleFlagdProxy(ctx, fsConfig); err != nil { r.Log.Error(err, "error handling the flagd-proxy deployment") } break diff --git a/controllers/core/flagsourceconfiguration/flagd-proxy.go b/controllers/core/flagsourceconfiguration/flagd-proxy.go new file mode 100644 index 000000000..d2caa1000 --- /dev/null +++ b/controllers/core/flagsourceconfiguration/flagd-proxy.go @@ -0,0 +1,251 @@ +package flagsourceconfiguration + +import ( + "context" + "fmt" + "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" + appsV1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + ManagedByAnnotationValue = "open-feature-operator" + FlagdProxyDeploymentName = "flagd-proxy" + FlagdProxyServiceAccountName = "open-feature-operator-flagd-proxy" + FlagdProxyServiceName = "flagd-proxy-svc" + + 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" + defaultFlagdProxyImage = "ghcr.io/open-feature/flagd-proxy" + defaultFlagdProxyTag = "v0.2.0" //FLAGD_PROXY_TAG_RENOVATE + defaultFlagdProxyPort = 8015 + defaultFlagdProxyMetricsPort = 8016 + defaultFlagdProxyDebugLogging = false + defaultFlagdProxyNamespace = "open-feature-operator-system" + operatorDeploymentName = "open-feature-operator-controller-manager" +) + +type FlagdProxyHandler struct { + client.Client + config *FlagdProxyConfiguration + Log logr.Logger +} + +type FlagdProxyConfiguration struct { + Port int + MetricsPort int + DebugLogging bool + Image string + Tag string + Namespace string + OperatorDeploymentName string +} + +func NewFlagdProxyConfiguration() (*FlagdProxyConfiguration, error) { + config := &FlagdProxyConfiguration{ + Image: defaultFlagdProxyImage, + Tag: defaultFlagdProxyTag, + Namespace: defaultFlagdProxyNamespace, + OperatorDeploymentName: operatorDeploymentName, + } + ns, ok := os.LookupEnv(envVarPodNamespace) + if ok { + config.Namespace = ns + } + kpi, ok := os.LookupEnv(envVarProxyImage) + if ok { + config.Image = kpi + } + kpt, ok := os.LookupEnv(envVarProxyTag) + if ok { + config.Tag = kpt + } + port, err := utils.GetIntEnvVar(envVarProxyPort, defaultFlagdProxyPort) + if err != nil { + return config, err + } + config.Port = port + + metricsPort, err := utils.GetIntEnvVar(envVarProxyMetricsPort, defaultFlagdProxyMetricsPort) + if err != nil { + return config, err + } + config.MetricsPort = metricsPort + + kpDebugLogging, err := utils.GetBoolEnvVar(envVarProxyDebugLogging, defaultFlagdProxyDebugLogging) + if err != nil { + return config, err + } + config.DebugLogging = kpDebugLogging + + return config, nil +} + +func NewFlagdProxyHandler(config *FlagdProxyConfiguration, client client.Client, logger logr.Logger) *FlagdProxyHandler { + return &FlagdProxyHandler{ + config: config, + Client: client, + Log: logger, + } +} + +func (f *FlagdProxyHandler) Config() *FlagdProxyConfiguration { + return f.config +} + +func (f *FlagdProxyHandler) handleFlagdProxy(ctx context.Context, flagSourceConfiguration *corev1alpha1.FlagSourceConfiguration) error { + exists, err := f.doesFlagdProxyExist(ctx) + if err != nil { + return err + } + if !exists { + return f.deployFlagdProxy(ctx) + } + return nil +} + +func (f *FlagdProxyHandler) deployFlagdProxy(ctx context.Context) error { + ownerReferences := []metav1.OwnerReference{} + ownerReference, err := f.getOwnerReference(ctx) + if err != nil { + f.Log.Error(err, "unable to create owner reference for open-feature-operator, not appending") + } else { + ownerReferences = append(ownerReferences, ownerReference) + } + + f.Log.Info("deploying the flagd-proxy") + if err := f.Client.Create(ctx, f.newFlagdProxyManifest(ownerReferences)); err != nil && !errors.IsAlreadyExists(err) { + return err + } + f.Log.Info("deploying the flagd-proxy service") + if err := f.Client.Create(ctx, f.newFlagdProxyServiceManifest(ownerReferences)); err != nil && !errors.IsAlreadyExists(err) { + return err + } + return nil +} + +func (f *FlagdProxyHandler) newFlagdProxyServiceManifest(ownerReferences []metav1.OwnerReference) *corev1.Service { + return &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: FlagdProxyServiceName, + Namespace: f.config.Namespace, + OwnerReferences: ownerReferences, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{ + "app.kubernetes.io/name": FlagdProxyDeploymentName, + "app.kubernetes.io/managed-by": ManagedByAnnotationValue, + }, + Ports: []corev1.ServicePort{ + { + Name: "flagd-proxy", + Port: int32(f.config.Port), + TargetPort: intstr.FromInt(f.config.Port), + }, + }, + }, + } +} + +func (f *FlagdProxyHandler) newFlagdProxyManifest(ownerReferences []metav1.OwnerReference) *appsV1.Deployment { + replicas := int32(1) + args := []string{ + "start", + "--metrics-port", + fmt.Sprintf("%d", f.config.MetricsPort), + } + if f.config.DebugLogging { + args = append(args, "--debug") + } + return &appsV1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: FlagdProxyDeploymentName, + Namespace: f.config.Namespace, + Labels: map[string]string{ + "app": FlagdProxyDeploymentName, + "app.kubernetes.io/managed-by": ManagedByAnnotationValue, + "app.kubernetes.io/version": f.config.Tag, + }, + OwnerReferences: ownerReferences, + }, + Spec: appsV1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": FlagdProxyDeploymentName, + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app": FlagdProxyDeploymentName, + "app.kubernetes.io/name": FlagdProxyDeploymentName, + "app.kubernetes.io/managed-by": ManagedByAnnotationValue, + "app.kubernetes.io/version": f.config.Tag, + }, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: FlagdProxyServiceAccountName, + Containers: []corev1.Container{ + { + Image: fmt.Sprintf("%s:%s", f.config.Image, f.config.Tag), + Name: FlagdProxyDeploymentName, + Ports: []corev1.ContainerPort{ + { + Name: "port", + ContainerPort: int32(f.config.Port), + }, + { + Name: "metrics-port", + ContainerPort: int32(f.config.MetricsPort), + }, + }, + Args: args, + }, + }, + }, + }, + }, + } +} + +func (f *FlagdProxyHandler) doesFlagdProxyExist(ctx context.Context) (bool, error) { + d := &appsV1.Deployment{} + err := f.Client.Get(ctx, client.ObjectKey{Name: FlagdProxyDeploymentName, Namespace: f.config.Namespace}, d) + if err != nil { + if errors.IsNotFound(err) { + // does not exist, is not ready, no error + return false, nil + } + // does not exist, is not ready, is in error + return false, err + } + // exists, at least one replica ready, no error + return true, nil +} + +func (f *FlagdProxyHandler) getOwnerReference(ctx context.Context) (metav1.OwnerReference, error) { + d := &appsV1.Deployment{} + if err := f.Client.Get(ctx, client.ObjectKey{Name: f.config.OperatorDeploymentName, Namespace: f.config.Namespace}, d); err != nil { + return metav1.OwnerReference{}, fmt.Errorf("unable to fetch operator deployment to create owner reference: %w", err) + } + return metav1.OwnerReference{ + UID: d.GetUID(), + Name: d.GetName(), + APIVersion: d.APIVersion, + Kind: d.Kind, + }, nil + +} diff --git a/controllers/core/flagsourceconfiguration/kube-flagd-proxy.go b/controllers/core/flagsourceconfiguration/kube-flagd-proxy.go deleted file mode 100644 index 76dce4341..000000000 --- a/controllers/core/flagsourceconfiguration/kube-flagd-proxy.go +++ /dev/null @@ -1,209 +0,0 @@ -package flagsourceconfiguration - -import ( - "context" - "fmt" - "os" - - "github.com/go-logr/logr" - "github.com/open-feature/open-feature-operator/pkg/utils" - appsV1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - ManagedByAnnotationValue = "open-feature-operator" -) - -type FlagdProxyHandler struct { - client.Client - config *FlagdProxyConfiguration - Log logr.Logger -} - -type FlagdProxyConfiguration struct { - Port int - MetricsPort int - DebugLogging bool - Image string - Tag string - Namespace string -} - -func NewFlagdProxyConfiguration() (*FlagdProxyConfiguration, error) { - config := &FlagdProxyConfiguration{ - Image: defaultFlagdProxyImage, - Tag: defaultFlagdProxyTag, - Namespace: defaultFlagdProxyNamespace, - } - ns, ok := os.LookupEnv(envVarPodNamespace) - if ok { - config.Namespace = ns - } - kpi, ok := os.LookupEnv(envVarProxyImage) - if ok { - config.Image = kpi - } - kpt, ok := os.LookupEnv(envVarProxyTag) - if ok { - config.Tag = kpt - } - port, err := utils.GetIntEnvVar(envVarProxyPort, defaultFlagdProxyPort) - if err != nil { - return config, err - } - config.Port = port - - metricsPort, err := utils.GetIntEnvVar(envVarProxyMetricsPort, defaultFlagdProxyMetricsPort) - if err != nil { - return config, err - } - config.MetricsPort = metricsPort - - kpDebugLogging, err := utils.GetBoolEnvVar(envVarProxyDebugLogging, defaultFlagdProxyDebugLogging) - if err != nil { - return config, err - } - config.DebugLogging = kpDebugLogging - - return config, nil -} - -func NewFlagdProxyHandler(config *FlagdProxyConfiguration, client client.Client, logger logr.Logger) *FlagdProxyHandler { - return &FlagdProxyHandler{ - config: config, - Client: client, - Log: logger, - } -} - -func (k *FlagdProxyHandler) Config() *FlagdProxyConfiguration { - return k.config -} - -func (k *FlagdProxyHandler) handleFlagdProxy(ctx context.Context) error { - exists, err := k.doesFlagdProxyExist(ctx) - if err != nil { - return err - } - if !exists { - return k.deployFlagdProxy(ctx) - } - return nil -} - -func (k *FlagdProxyHandler) deployFlagdProxy(ctx context.Context) error { - k.Log.Info("deploying the flagd-proxy") - if err := k.Client.Create(ctx, k.newFlagdProxyManifest()); err != nil && !errors.IsAlreadyExists(err) { - return err - } - k.Log.Info("deploying the flagd-proxy service") - if err := k.Client.Create(ctx, k.newFlagdProxyServiceManifest()); err != nil && !errors.IsAlreadyExists(err) { - return err - } - return nil -} - -func (k *FlagdProxyHandler) newFlagdProxyServiceManifest() *corev1.Service { - return &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: FlagdProxyServiceName, - Namespace: k.config.Namespace, - }, - Spec: corev1.ServiceSpec{ - Selector: map[string]string{ - "app.kubernetes.io/name": FlagdProxyDeploymentName, - "app.kubernetes.io/managed-by": ManagedByAnnotationValue, - }, - Ports: []corev1.ServicePort{ - { - Name: "flagd-proxy", - Port: int32(k.config.Port), - TargetPort: intstr.FromInt(k.config.Port), - }, - }, - }, - } -} - -func (k *FlagdProxyHandler) newFlagdProxyManifest() *appsV1.Deployment { - replicas := int32(1) - args := []string{ - "start", - "--metrics-port", - fmt.Sprintf("%d", k.config.MetricsPort), - } - if k.config.DebugLogging { - args = append(args, "--debug") - } - return &appsV1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: FlagdProxyDeploymentName, - Namespace: k.config.Namespace, - Labels: map[string]string{ - "app": FlagdProxyDeploymentName, - "app.kubernetes.io/managed-by": ManagedByAnnotationValue, - "app.kubernetes.io/version": k.config.Tag, - }, - }, - Spec: appsV1.DeploymentSpec{ - Replicas: &replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": FlagdProxyDeploymentName, - }, - }, - - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app": FlagdProxyDeploymentName, - "app.kubernetes.io/name": FlagdProxyDeploymentName, - "app.kubernetes.io/managed-by": ManagedByAnnotationValue, - "app.kubernetes.io/version": k.config.Tag, - }, - }, - Spec: corev1.PodSpec{ - ServiceAccountName: FlagdProxyServiceAccountName, - Containers: []corev1.Container{ - { - Image: fmt.Sprintf("%s:%s", k.config.Image, k.config.Tag), - Name: FlagdProxyDeploymentName, - Ports: []corev1.ContainerPort{ - { - Name: "port", - ContainerPort: int32(k.config.Port), - }, - { - Name: "metrics-port", - ContainerPort: int32(k.config.MetricsPort), - }, - }, - Args: args, - }, - }, - }, - }, - }, - } -} - -func (r *FlagdProxyHandler) doesFlagdProxyExist(ctx context.Context) (bool, error) { - r.Client.Scheme() - d := appsV1.Deployment{} - err := r.Client.Get(ctx, client.ObjectKey{Name: FlagdProxyDeploymentName, Namespace: r.config.Namespace}, &d) - if err != nil { - if errors.IsNotFound(err) { - // does not exist, is not ready, no error - return false, nil - } - // does not exist, is not ready, is in error - return false, err - } - // exists, at least one replica ready, no error - return true, nil -} diff --git a/docs/README.md b/docs/README.md index 65a41b95f..8c84aafd0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,4 +22,4 @@ Configuration of the deployed sidecars is handled through the `FeatureFlagConfig - [Architecture](./architecture.md) - [Permissions](./permissions.md) - [Development Notes](./development_notes.md) -- [flagd Kube Proxy](./kube_flagd_proxy.md) \ No newline at end of file +- [flagd Kube Proxy](./flagd_proxy.md) \ No newline at end of file diff --git a/docs/flag_source_configuration.md b/docs/flag_source_configuration.md index 3e86d593b..a89513bb0 100644 --- a/docs/flag_source_configuration.md +++ b/docs/flag_source_configuration.md @@ -69,7 +69,7 @@ The relevant `FlagSourceConfigurations` are passed to the operator by setting th | ProviderID | Defines the identifier for grpc connection. Has no effect on other `Provider` types | optional `string` | | Selector | Defines the flag configuration selection criteria for grpc connection. Has no effect on other `Provider` types | optional `string` | -> The flagd-proxy provider type is experimental, documentation can be found [here](./kube_flagd_proxy.md) +> The flagd-proxy provider type is experimental, documentation can be found [here](./flagd_proxy.md) ## Configuration Merging diff --git a/docs/kube_flagd_proxy.md b/docs/flagd_proxy.md similarity index 91% rename from docs/kube_flagd_proxy.md rename to docs/flagd_proxy.md index 61c6e9960..39d63688f 100644 --- a/docs/kube_flagd_proxy.md +++ b/docs/flagd_proxy.md @@ -51,3 +51,7 @@ The current implementation of the `flagd-proxy` allows for a set of basic config | FLAGD_PROXY_PORT | Allows the default port of `8015` to eb overwritten | | FLAGD_PROXY_METRICS_PORT | Allows the default metrics port of `8016` to eb overwritten | | FLAGD_PROXY_DEBUG_LOGGING | Defaults to `"false"`, allows for the `--debug` flag to be set on the `flagd-proxy` container | + +## Resource Ownership + +On deployment the `flagd-proxy` `Deployment` will be configured with the `open-feature-operator-controller-manager` `Deployment` as its owner resource. As such the `flagd-proxy` and its associated `Service` will be garbage collected when the operator is uninstalled. \ No newline at end of file diff --git a/renovate.json b/renovate.json index 1659d32c5..bf2b762e0 100644 --- a/renovate.json +++ b/renovate.json @@ -69,7 +69,7 @@ ] }, { - "fileMatch": ["^controllers/core/flagsourceconfiguration/controller.go$"], + "fileMatch": ["^controllers/core/flagsourceconfiguration/flagd-proxy.go$"], "matchStrings": ["\"(?.*?)\" \\/\\/FLAGD_PROXY_TAG_RENOVATE"], "depNameTemplate": "open-feature/flagd", "datasourceTemplate": "github-releases",