Skip to content

Commit

Permalink
refactor to share funcs
Browse files Browse the repository at this point in the history
Signed-off-by: Skye Gill <[email protected]>
  • Loading branch information
skyerus committed Mar 17, 2023
1 parent d561b58 commit ea9cd96
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 335 deletions.
4 changes: 2 additions & 2 deletions apis/core/v1alpha1/flagsourceconfiguration_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ const (
defaultSocketPath string = ""
defaultEvaluator string = "json"
defaultImage string = "ghcr.io/open-feature/flagd"
// v0.4.1` is replaced in the `update-flagd` Makefile target
defaultTag string = "v0.4.1"
// INPUT_FLAGD_VERSION` is replaced in the `update-flagd` Makefile target
defaultTag string = "INPUT_FLAGD_VERSION"
defaultLogFormat string = "json"
defaultProbesEnabled bool = true
SyncProviderKubernetes SyncProviderType = "kubernetes"
Expand Down
162 changes: 47 additions & 115 deletions controllers/clientsideconfiguration_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import (
"fmt"
"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"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -84,6 +84,14 @@ func (r *ClientSideConfigurationReconciler) Reconcile(ctx context.Context, req c
return r.finishReconcile(err, false)
}
ns := csconf.Namespace
csconfOwnerReferences := []metav1.OwnerReference{
{
Kind: csconf.Kind,
Name: csconf.Name,
UID: csconf.UID,
Controller: utils.FalseVal(),
},
}

// check for existing client side deployment
deployment := &appsV1.Deployment{}
Expand Down Expand Up @@ -138,6 +146,7 @@ func (r *ClientSideConfigurationReconciler) Reconcile(ctx context.Context, req c
} else {
svc.Name = clientSideServiceName
svc.Namespace = ns
svc.OwnerReferences = csconfOwnerReferences
svc.Spec.Selector = map[string]string{
"app": clientSideAppName,
}
Expand Down Expand Up @@ -170,70 +179,47 @@ func (r *ClientSideConfigurationReconciler) Reconcile(ctx context.Context, req c
}
}

// create gateway if it doesn't exist, update if it does
// update existing gateway
namespacesFromSame := gatewayv1beta1.NamespacesFromSame
hostname := gatewayv1beta1.Hostname(csconf.Spec.HTTPRouteHostname)
gateway := &gatewayv1beta1.Gateway{}
if err := r.Client.Get(
ctx, client.ObjectKey{Namespace: ns, Name: csconf.Spec.GatewayName}, gateway,
); err != nil {
if !errors.IsNotFound(err) {
r.Log.Error(err,
fmt.Sprintf("Failed to get the gateway %s/%s", ns, csconf.Spec.GatewayName))
return r.finishReconcile(err, false)
}
gateway.Name = csconf.Spec.GatewayName
gateway.Namespace = ns
gateway.Spec.GatewayClassName = gatewayv1beta1.ObjectName(csconf.Spec.GatewayClassName)
gateway.Spec.Listeners = []gatewayv1beta1.Listener{
{
Name: clientSideGatewayListenerName,
Hostname: &hostname,
Protocol: gatewayv1beta1.HTTPProtocolType,
Port: gatewayv1beta1.PortNumber(csconf.Spec.GatewayListenerPort),
AllowedRoutes: &gatewayv1beta1.AllowedRoutes{
Namespaces: &gatewayv1beta1.RouteNamespaces{
From: &namespacesFromSame,
},
},
},
}
r.Log.Error(err,
fmt.Sprintf("Failed to get the gateway %s/%s", ns, csconf.Spec.GatewayName))
return r.finishReconcile(err, false)
}

if err := r.Client.Create(ctx, gateway); err != nil {
r.Log.Error(err, "Failed to create gateway")
return r.finishReconcile(nil, false)
}
} else {
gateway.Spec.GatewayClassName = gatewayv1beta1.ObjectName(csconf.Spec.GatewayClassName)
listener := gatewayv1beta1.Listener{
Name: clientSideGatewayListenerName,
Hostname: &hostname,
Protocol: gatewayv1beta1.HTTPProtocolType,
Port: gatewayv1beta1.PortNumber(csconf.Spec.GatewayListenerPort),
AllowedRoutes: &gatewayv1beta1.AllowedRoutes{
Namespaces: &gatewayv1beta1.RouteNamespaces{
From: &namespacesFromSame,
},
gateway.Spec.GatewayClassName = gatewayv1beta1.ObjectName(csconf.Spec.GatewayClassName)
listener := gatewayv1beta1.Listener{
Name: clientSideGatewayListenerName,
Hostname: &hostname,
Protocol: gatewayv1beta1.HTTPProtocolType,
Port: gatewayv1beta1.PortNumber(csconf.Spec.GatewayListenerPort),
AllowedRoutes: &gatewayv1beta1.AllowedRoutes{
Namespaces: &gatewayv1beta1.RouteNamespaces{
From: &namespacesFromSame,
},
}
},
}

listenerExists := false
for i := 0; i < len(gateway.Spec.Listeners); i++ {
if gateway.Spec.Listeners[i].Name == clientSideGatewayListenerName {
gateway.Spec.Listeners[i] = listener
listenerExists = true
break
}
listenerExists := false
for i := 0; i < len(gateway.Spec.Listeners); i++ {
if gateway.Spec.Listeners[i].Name == clientSideGatewayListenerName {
gateway.Spec.Listeners[i] = listener
listenerExists = true
break
}
}

if !listenerExists {
gateway.Spec.Listeners = append(gateway.Spec.Listeners, listener)
}
if !listenerExists {
gateway.Spec.Listeners = append(gateway.Spec.Listeners, listener)
}

if err := r.Client.Update(ctx, gateway); err != nil {
r.Log.Error(err, "Failed to update gateway")
return r.finishReconcile(nil, false)
}
if err := r.Client.Update(ctx, gateway); err != nil {
r.Log.Error(err, "Failed to update gateway")
return r.finishReconcile(nil, false)
}

// create gateway http route if it doesn't exist
Expand All @@ -251,6 +237,7 @@ func (r *ClientSideConfigurationReconciler) Reconcile(ctx context.Context, req c
} else {
httpRoute.Name = csconf.Spec.HTTPRouteName
httpRoute.Namespace = ns
httpRoute.OwnerReferences = csconfOwnerReferences
httpRoute.Spec.ParentRefs = []gatewayv1beta1.ParentReference{
{
Name: gatewayv1beta1.ObjectName(csconf.Spec.GatewayName),
Expand Down Expand Up @@ -300,22 +287,14 @@ func (r *ClientSideConfigurationReconciler) Reconcile(ctx context.Context, req c
// TODO resource limits
}

for _, source := range fsConfigSpec.Sources {
if source.Provider == "" {
source.Provider = fsConfigSpec.DefaultSyncProvider
}
switch {
case source.Provider.IsKubernetes():
if err := r.handleKubernetesProvider(ctx, ns, csconf.Spec.ServiceAccountName, &flagdContainer, source); err != nil {
r.Log.Error(err, "Failed to handle kubernetes provider")
return r.finishReconcile(nil, false)
}
default:
r.Log.Error(fmt.Errorf("%s", source.Provider), "Unsupported source")
return r.finishReconcile(nil, false)
}
if err := HandleSourcesProviders(ctx, r.Log, r.Client, fsConfigSpec, ns, csconf.Spec.ServiceAccountName,
csconfOwnerReferences, &deployment.Spec.Template.Spec, deployment.Spec.Template.ObjectMeta, &flagdContainer,
); err != nil {
r.Log.Error(err, "handle source providers")
return r.finishReconcile(nil, false)
}

deployment.OwnerReferences = csconfOwnerReferences
deployment.Spec.Template.Spec.ServiceAccountName = csconf.Spec.ServiceAccountName
labels := map[string]string{
"app": clientSideAppName,
Expand All @@ -333,61 +312,14 @@ func (r *ClientSideConfigurationReconciler) Reconcile(ctx context.Context, req c
return r.finishReconcile(nil, false)
}

func (r *ClientSideConfigurationReconciler) enableClusterRoleBinding(ctx context.Context, namespace, serviceAccountName string) error {
serviceAccount := client.ObjectKey{
Name: serviceAccountName,
Namespace: namespace,
}
if serviceAccountName == "" {
serviceAccount.Name = "default"
}
// Check if the service account exists
r.Log.V(1).Info(fmt.Sprintf("Fetching serviceAccount: %s/%s", serviceAccount.Namespace, serviceAccount.Name))
sa := corev1.ServiceAccount{}
if err := r.Client.Get(ctx, serviceAccount, &sa); err != nil {
r.Log.V(1).Info(fmt.Sprintf("ServiceAccount not found: %s/%s", serviceAccount.Namespace, serviceAccount.Name))
return err
}
r.Log.V(1).Info(fmt.Sprintf("Fetching clusterrolebinding: %s", clusterRoleBindingName))
// Fetch service account if it exists
crb := rbacv1.ClusterRoleBinding{}
if err := r.Client.Get(ctx, client.ObjectKey{Name: clusterRoleBindingName}, &crb); errors.IsNotFound(err) {
r.Log.V(1).Info(fmt.Sprintf("ClusterRoleBinding not found: %s", clusterRoleBindingName))
return err
}
found := false
for _, subject := range crb.Subjects {
if subject.Kind == "ServiceAccount" && subject.Name == serviceAccount.Name && subject.Namespace == serviceAccount.Namespace {
r.Log.V(1).Info(fmt.Sprintf("ClusterRoleBinding already exists for service account: %s/%s", serviceAccount.Namespace, serviceAccount.Name))
found = true
}
}
if !found {
r.Log.V(1).Info(fmt.Sprintf("Updating ClusterRoleBinding %s for service account: %s/%s", crb.Name,
serviceAccount.Namespace, serviceAccount.Name))
crb.Subjects = append(crb.Subjects, rbacv1.Subject{
Kind: "ServiceAccount",
Name: serviceAccount.Name,
Namespace: serviceAccount.Namespace,
})
if err := r.Client.Update(ctx, &crb); err != nil {
r.Log.V(1).Info(fmt.Sprintf("Failed to update ClusterRoleBinding: %s", err.Error()))
return err
}
}
r.Log.V(1).Info(fmt.Sprintf("Updated ClusterRoleBinding: %s", crb.Name))

return nil
}

func (r *ClientSideConfigurationReconciler) handleKubernetesProvider(ctx context.Context, namespace, serviceAccountName string, container *corev1.Container, source corev1alpha1.Source) error {
ns, n := parseAnnotation(source.Source, namespace)
// ensure that the FeatureFlagConfiguration exists
ff := r.getFeatureFlag(ctx, ns, n)
if ff.Name == "" {
return fmt.Errorf("feature flag configuration %s/%s not found", ns, n)
}
if err := r.enableClusterRoleBinding(ctx, namespace, serviceAccountName); err != nil {
if err := EnableClusterRoleBinding(ctx, r.Log, r.Client, namespace, serviceAccountName); err != nil {
return fmt.Errorf("enableClusterRoleBinding: %w", err)
}
// append args
Expand Down
30 changes: 30 additions & 0 deletions controllers/configmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package controllers

import (
"context"
"fmt"
"github.com/go-logr/logr"
"github.com/open-feature/open-feature-operator/apis/core/v1alpha1"
"github.com/open-feature/open-feature-operator/pkg/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func CreateConfigMap(
ctx context.Context, log logr.Logger, c client.Client, namespace string, name string, ownerReferences []metav1.OwnerReference,
) error {
log.V(1).Info(fmt.Sprintf("Creating configmap %s", name))
references := []metav1.OwnerReference{
ownerReferences[0],
}
references[0].Controller = utils.FalseVal()
ff := FeatureFlag(ctx, c, namespace, name)
if ff.Name == "" {
return fmt.Errorf("feature flag configuration %s/%s not found", namespace, name)
}
references = append(references, v1alpha1.GetFfReference(&ff))

cm := v1alpha1.GenerateFfConfigMap(name, namespace, references, ff.Spec)

return c.Create(ctx, &cm)
}
16 changes: 16 additions & 0 deletions controllers/featureflag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package controllers

import (
"context"
"github.com/open-feature/open-feature-operator/apis/core/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func FeatureFlag(ctx context.Context, c client.Client, namespace string, name string) v1alpha1.FeatureFlagConfiguration {
ffConfig := v1alpha1.FeatureFlagConfiguration{}
if err := c.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, &ffConfig); errors.IsNotFound(err) {
return v1alpha1.FeatureFlagConfiguration{}
}
return ffConfig
}
17 changes: 17 additions & 0 deletions controllers/owner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package controllers

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// SharedOwnership returns true if any of the owner references match in the given slices
func SharedOwnership(ownerReferences1, ownerReferences2 []metav1.OwnerReference) bool {
for _, owner1 := range ownerReferences1 {
for _, owner2 := range ownerReferences2 {
if owner1.UID == owner2.UID {
return true
}
}
}
return false
}
11 changes: 11 additions & 0 deletions controllers/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package controllers

import "strings"

func ParseAnnotation(s string, defaultNs string) (string, string) {
ss := strings.Split(s, "/")
if len(ss) == 2 {
return ss[0], ss[1]
}
return defaultNs, s
}
Loading

0 comments on commit ea9cd96

Please sign in to comment.