Skip to content

Commit

Permalink
feat: auto-upgrade flagd-proxy with OFO upgrades (#596)
Browse files Browse the repository at this point in the history
Signed-off-by: odubajDT <[email protected]>
Signed-off-by: odubajDT <[email protected]>
Co-authored-by: Giovanni Liva <[email protected]>
  • Loading branch information
odubajDT and thisthat authored Jan 24, 2024
1 parent f3f9427 commit 3271f33
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 42 deletions.
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ make build-deploy-operator TAG=myTag RELEASE_REGISTRY=docker.io/user1 RELEASE_NA
Which will result in building the operator image `docker.io/user1/myImgName:myTag`, uploading it to your image registry
and deploying to your cluster. Please be aware that it is using the cluster your current kube-context is pointing to.

**Note:** All bash variables are optional, the default values are set and will result in an image `ghcr.io/openfeature/operator:latest`
> [!NOTE]
> All bash variables are optional, the default values are set and will result in an image `ghcr.io/openfeature/operator:latest`
### Autogenerated Documentation

Expand Down
7 changes: 7 additions & 0 deletions chart/open-feature-operator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ helm repo update
helm upgrade --install open-feature-operator openfeature/open-feature-operator
```

> [!NOTE]
> If you upgrade to OFO `v0.5.4` or higher while using a `flagd-proxy` provider, the instance of
`flagd-proxy` will be automatically upgraded to the latest supported version by the `open-feature-operator`.
The upgrade of `flagd-proxy` will also consider your current `FeatureFlagSource` configuration and adapt
the `flagd-proxy` Deployment accordingly.
If you are upgrading OFO to `v0.5.3` or lower, `flagd-proxy` (if present) won't be upgraded automatically.

#### Upgrade CRDs

CRDs are not upgraded automatically with helm (https://helm.sh/docs/chart_best_practices/custom_resource_definitions/).
Expand Down
98 changes: 69 additions & 29 deletions common/flagdproxy/flagdproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package flagdproxy
import (
"context"
"fmt"
"reflect"

"github.com/go-logr/logr"
"github.com/open-feature/open-feature-operator/common/types"
Expand All @@ -28,6 +29,8 @@ type FlagdProxyHandler struct {
Log logr.Logger
}

type CreateUpdateFunc func(ctx context.Context, obj client.Object) error

type FlagdProxyConfiguration struct {
Port int
ManagementPort int
Expand Down Expand Up @@ -62,43 +65,58 @@ func (f *FlagdProxyHandler) Config() *FlagdProxyConfiguration {
return f.config
}

func (f *FlagdProxyHandler) createObject(ctx context.Context, obj client.Object) error {
return f.Client.Create(ctx, obj)
}

func (f *FlagdProxyHandler) updateObject(ctx context.Context, obj client.Object) error {
return f.Client.Update(ctx, obj)
}

func (f *FlagdProxyHandler) HandleFlagdProxy(ctx context.Context) error {
exists, err := f.doesFlagdProxyExist(ctx)
exists, deployment, 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)
return err
}
newDeployment := f.newFlagdProxyManifest(ownerReference)
newService := f.newFlagdProxyServiceManifest(ownerReference)

if !exists {
f.Log.Info("flagd-proxy Deployment does not exist, creating")
return f.deployFlagdProxy(ctx, f.createObject, newDeployment, newService)
}
// flagd-proxy exists, need to check if we should update it
if f.shouldUpdateFlagdProxy(deployment, newDeployment) {
f.Log.Info("flagd-proxy Deployment out of sync, updating")
return f.deployFlagdProxy(ctx, f.updateObject, newDeployment, newService)
}
f.Log.Info("flagd-proxy Deployment up-to-date")
return nil
}

func (f *FlagdProxyHandler) deployFlagdProxy(ctx context.Context, createUpdateFunc CreateUpdateFunc, deployment *appsV1.Deployment, service *corev1.Service) error {
f.Log.Info("deploying the flagd-proxy")
if err := f.Client.Create(ctx, f.newFlagdProxyManifest(ownerReferences)); err != nil && !errors.IsAlreadyExists(err) {
if err := createUpdateFunc(ctx, deployment); 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) {
if err := createUpdateFunc(ctx, service); err != nil && !errors.IsAlreadyExists(err) {
return err
}
return nil
}

func (f *FlagdProxyHandler) newFlagdProxyServiceManifest(ownerReferences []metav1.OwnerReference) *corev1.Service {
func (f *FlagdProxyHandler) newFlagdProxyServiceManifest(ownerReference *metav1.OwnerReference) *corev1.Service {
return &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: FlagdProxyServiceName,
Namespace: f.config.Namespace,
OwnerReferences: ownerReferences,
OwnerReferences: []metav1.OwnerReference{*ownerReference},
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
Expand All @@ -116,7 +134,7 @@ func (f *FlagdProxyHandler) newFlagdProxyServiceManifest(ownerReferences []metav
}
}

func (f *FlagdProxyHandler) newFlagdProxyManifest(ownerReferences []metav1.OwnerReference) *appsV1.Deployment {
func (f *FlagdProxyHandler) newFlagdProxyManifest(ownerReference *metav1.OwnerReference) *appsV1.Deployment {
replicas := int32(1)
args := []string{
"start",
Expand All @@ -135,7 +153,7 @@ func (f *FlagdProxyHandler) newFlagdProxyManifest(ownerReferences []metav1.Owner
"app.kubernetes.io/managed-by": ManagedByAnnotationValue,
"app.kubernetes.io/version": f.config.Tag,
},
OwnerReferences: ownerReferences,
OwnerReferences: []metav1.OwnerReference{*ownerReference},
},
Spec: appsV1.DeploymentSpec{
Replicas: &replicas,
Expand Down Expand Up @@ -178,31 +196,53 @@ func (f *FlagdProxyHandler) newFlagdProxyManifest(ownerReferences []metav1.Owner
}
}

func (f *FlagdProxyHandler) doesFlagdProxyExist(ctx context.Context) (bool, error) {
func (f *FlagdProxyHandler) doesFlagdProxyExist(ctx context.Context) (bool, *appsV1.Deployment, 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
return false, nil, nil
}
// does not exist, is not ready, is in error
return false, err
return false, nil, err
}
// exists, at least one replica ready, no error
return true, nil
return true, d, nil
}

func (f *FlagdProxyHandler) getOwnerReference(ctx context.Context) (metav1.OwnerReference, error) {
func (f *FlagdProxyHandler) shouldUpdateFlagdProxy(old, new *appsV1.Deployment) bool {
if !isDeployedByOFO(old) {
f.Log.Info("flagd-proxy Deployment not managed by OFO")
return false
}
return !reflect.DeepEqual(old.Spec, new.Spec)
}

func (f *FlagdProxyHandler) getOperatorDeployment(ctx context.Context) (*appsV1.Deployment, 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 nil, fmt.Errorf("unable to fetch operator deployment: %w", err)
}
return metav1.OwnerReference{
UID: d.GetUID(),
Name: d.GetName(),
APIVersion: d.APIVersion,
Kind: d.Kind,
return d, nil

}

func (f *FlagdProxyHandler) getOwnerReference(ctx context.Context) (*metav1.OwnerReference, error) {
operatorDeployment, err := f.getOperatorDeployment(ctx)
if err != nil {
f.Log.Error(err, "unable to create owner reference for open-feature-operator")
return nil, err
}

return &metav1.OwnerReference{
UID: operatorDeployment.GetUID(),
Name: operatorDeployment.GetName(),
APIVersion: operatorDeployment.APIVersion,
Kind: operatorDeployment.Kind,
}, nil
}

func isDeployedByOFO(d *appsV1.Deployment) bool {
val, ok := d.Labels["app.kubernetes.io/managed-by"]
return ok && val == ManagedByAnnotationValue
}
Loading

0 comments on commit 3271f33

Please sign in to comment.