feat!: support OFO v1beta1 API (#997)

BREAKING CHANGE: OFO APIs were updated to version v1beta1, since they are more stable now. Resources of the alpha versions are no longer supported in flagd or flagd-proxy.
This commit is contained in:
odubajDT 2023-11-14 07:35:45 +01:00 committed by GitHub
parent 5b82d06eb7
commit bb6f5bf0fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 136 additions and 115 deletions

View File

@ -11,7 +11,7 @@ require (
github.com/diegoholiveira/jsonlogic/v3 v3.3.2
github.com/fsnotify/fsnotify v1.7.0
github.com/golang/mock v1.6.0
github.com/open-feature/open-feature-operator v0.2.36
github.com/open-feature/open-feature-operator v0.2.37-0.20231108054703-a97d336468d5
github.com/open-feature/schemas v0.2.8
github.com/prometheus/client_golang v1.17.0
github.com/robfig/cron v1.2.0
@ -61,7 +61,6 @@ require (
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-task/slim-sprig v2.20.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
@ -95,15 +94,12 @@ require (
golang.org/x/term v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/api v0.28.3 // indirect
k8s.io/apiextensions-apiserver v0.28.3 // indirect
k8s.io/component-base v0.28.3 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect

View File

@ -488,8 +488,6 @@ github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -638,8 +636,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/open-feature/open-feature-operator v0.2.36 h1:dzyZh9JSIRvXkfpM9ynYplNk7vjQFLs9sd5aHhF48z4=
github.com/open-feature/open-feature-operator v0.2.36/go.mod h1:nM7T4oGQukeGmcAFkQm0uwt8WFdDb5hYPjXkm7pHhX4=
github.com/open-feature/open-feature-operator v0.2.37-0.20231108054703-a97d336468d5 h1:ONgFdsDH0uAS3qrmZEQjYqFtqKw/MCkbbQSE33bGBsU=
github.com/open-feature/open-feature-operator v0.2.37-0.20231108054703-a97d336468d5/go.mod h1:nM7T4oGQukeGmcAFkQm0uwt8WFdDb5hYPjXkm7pHhX4=
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/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -1054,7 +1052,6 @@ golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNq
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@ -1315,13 +1312,10 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM=
k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc=
k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08=
k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc=
k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A=
k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8=
k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4=
k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo=
k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI=
k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8=
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=

View File

@ -2,6 +2,7 @@ package kubernetes
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
@ -10,7 +11,7 @@ import (
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/sync"
"github.com/open-feature/open-feature-operator/apis/core/v1alpha1"
"github.com/open-feature/open-feature-operator/apis/core/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
@ -23,9 +24,9 @@ import (
)
var (
resyncPeriod = 1 * time.Minute
apiVersion = fmt.Sprintf("%s/%s", v1alpha1.GroupVersion.Group, v1alpha1.GroupVersion.Version)
featureFlagConfigurationResource = v1alpha1.GroupVersion.WithResource("featureflagconfigurations")
resyncPeriod = 1 * time.Minute
apiVersion = fmt.Sprintf("%s/%s", v1beta1.GroupVersion.Group, v1beta1.GroupVersion.Version)
featureFlagResource = v1beta1.GroupVersion.WithResource("featureflags")
)
type Sync struct {
@ -89,15 +90,15 @@ func (k *Sync) Init(_ context.Context) error {
return fmt.Errorf("unable to parse uri %s: %w", k.URI, err)
}
if err := v1alpha1.AddToScheme(scheme.Scheme); err != nil {
return fmt.Errorf("unable to v1alpha1 types to scheme: %w", err)
if err := v1beta1.AddToScheme(scheme.Scheme); err != nil {
return fmt.Errorf("unable to v1beta1 types to scheme: %w", err)
}
// The created informer will not do resyncs if the given defaultEventHandlerResyncPeriod is zero.
// For more details on resync implications refer to tools/cache/shared_informer.go
factory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(k.dynamicClient, resyncPeriod, k.namespace, nil)
k.informer = factory.ForResource(featureFlagConfigurationResource).Informer()
k.informer = factory.ForResource(featureFlagResource).Informer()
return nil
}
@ -191,21 +192,21 @@ func (k *Sync) fetch(ctx context.Context) (string, error) {
}
k.logger.Debug(fmt.Sprintf("resource %s served from the informer cache", k.URI))
return configuration.Spec.FeatureFlagSpec, nil
return marshallFeatureFlagSpec(configuration)
}
// fallback to API access - this is an informer cache miss. Could happen at the startup where cache is not filled
var ff v1alpha1.FeatureFlagConfiguration
var ff v1beta1.FeatureFlag
err = k.readClient.Get(ctx, client.ObjectKey{
Name: k.crdName,
Namespace: k.namespace,
}, &ff)
if err != nil {
return "", fmt.Errorf("unable to fetch FeatureFlagConfiguration %s/%s: %w", k.namespace, k.crdName, err)
return "", fmt.Errorf("unable to fetch FeatureFlag %s/%s: %w", k.namespace, k.crdName, err)
}
k.logger.Debug(fmt.Sprintf("resource %s served from API server", k.URI))
return ff.Spec.FeatureFlagSpec, nil
return marshallFeatureFlagSpec(&ff)
}
func (k *Sync) notify(ctx context.Context, c chan<- INotify) {
@ -299,16 +300,16 @@ func updateFuncHandler(oldObj interface{}, newObj interface{}, object client.Obj
}
// toFFCfg attempts to covert unstructured payload to configurations
func toFFCfg(object interface{}) (*v1alpha1.FeatureFlagConfiguration, error) {
func toFFCfg(object interface{}) (*v1beta1.FeatureFlag, error) {
u, ok := object.(*unstructured.Unstructured)
if !ok {
return nil, fmt.Errorf("provided value is not of type *unstructured.Unstructured")
}
var ffObj v1alpha1.FeatureFlagConfiguration
var ffObj v1beta1.FeatureFlag
err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &ffObj)
if err != nil {
return nil, fmt.Errorf("unable to convert unstructured to v1alpha1.FeatureFlagConfiguration: %w", err)
return nil, fmt.Errorf("unable to convert unstructured to v1beta1.FeatureFlag: %w", err)
}
return &ffObj, nil
@ -349,3 +350,11 @@ func k8sClusterConfig() (*rest.Config, error) {
return clusterConfig, nil
}
func marshallFeatureFlagSpec(ff *v1beta1.FeatureFlag) (string, error) {
b, err := json.Marshal(ff.Spec.FlagSpec)
if err != nil {
return "", fmt.Errorf("failed to marshall FlagSpec: %s", err.Error())
}
return string(b), nil
}

View File

@ -12,7 +12,8 @@ import (
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/sync"
"github.com/open-feature/open-feature-operator/apis/core/v1alpha1"
"github.com/open-feature/open-feature-operator/apis/core/v1beta1"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -26,7 +27,7 @@ import (
)
var Metadata = v1.TypeMeta{
Kind: "FeatureFlagConfiguration",
Kind: "FeatureFlag",
APIVersion: apiVersion,
}
@ -74,14 +75,14 @@ func Test_parseURI(t *testing.T) {
}
func Test_toFFCfg(t *testing.T) {
validFFCfg := v1alpha1.FeatureFlagConfiguration{
validFFCfg := v1beta1.FeatureFlag{
TypeMeta: Metadata,
}
tests := []struct {
name string
input interface{}
want *v1alpha1.FeatureFlagConfiguration
want *v1beta1.FeatureFlag
wantErr bool
}{
{
@ -120,7 +121,7 @@ func Test_commonHandler(t *testing.T) {
cfgNs := "resourceNS"
cfgName := "resourceName"
validFFCfg := v1alpha1.FeatureFlagConfiguration{
validFFCfg := v1beta1.FeatureFlag{
TypeMeta: Metadata,
ObjectMeta: v1.ObjectMeta{
Namespace: cfgNs,
@ -166,9 +167,9 @@ func Test_commonHandler(t *testing.T) {
{
name: "simple error - API mismatch",
args: args{
obj: toUnstructured(t, v1alpha1.FeatureFlagConfiguration{
obj: toUnstructured(t, v1beta1.FeatureFlag{
TypeMeta: v1.TypeMeta{
Kind: "FeatureFlagConfiguration",
Kind: "FeatureFlag",
APIVersion: "someAPIVersion",
},
}),
@ -227,7 +228,7 @@ func Test_updateFuncHandler(t *testing.T) {
cfgNs := "resourceNS"
cfgName := "resourceName"
validFFCfgOld := v1alpha1.FeatureFlagConfiguration{
validFFCfgOld := v1beta1.FeatureFlag{
TypeMeta: Metadata,
ObjectMeta: v1.ObjectMeta{
Namespace: cfgNs,
@ -293,9 +294,9 @@ func Test_updateFuncHandler(t *testing.T) {
name: "Simple error - API version mismatch new object",
args: args{
oldObj: toUnstructured(t, validFFCfgOld),
newObj: toUnstructured(t, v1alpha1.FeatureFlagConfiguration{
newObj: toUnstructured(t, v1beta1.FeatureFlag{
TypeMeta: v1.TypeMeta{
Kind: "FeatureFlagConfiguration",
Kind: "FeatureFlag",
APIVersion: "someAPIVersion",
},
}),
@ -310,9 +311,9 @@ func Test_updateFuncHandler(t *testing.T) {
{
name: "Simple error - API version mismatch old object",
args: args{
oldObj: toUnstructured(t, v1alpha1.FeatureFlagConfiguration{
oldObj: toUnstructured(t, v1beta1.FeatureFlag{
TypeMeta: v1.TypeMeta{
Kind: "FeatureFlagConfiguration",
Kind: "FeatureFlag",
APIVersion: "someAPIVersion",
},
}),
@ -369,23 +370,25 @@ func Test_updateFuncHandler(t *testing.T) {
}
func TestSync_fetch(t *testing.T) {
flagSpec := "fakeFlagSpec"
flagSpec := v1beta1.FlagSpec{
Flags: map[string]v1beta1.Flag{},
}
validCfg := v1alpha1.FeatureFlagConfiguration{
validCfg := v1beta1.FeatureFlag{
TypeMeta: Metadata,
ObjectMeta: v1.ObjectMeta{
Namespace: "resourceNS",
Name: "resourceName",
ResourceVersion: "v1",
},
Spec: v1alpha1.FeatureFlagConfigurationSpec{
FeatureFlagSpec: flagSpec,
Spec: v1beta1.FeatureFlagSpec{
FlagSpec: flagSpec,
},
}
type args struct {
InformerGetFunc func(key string) (item interface{}, exists bool, err error)
ClientResponse v1alpha1.FeatureFlagConfiguration
ClientResponse v1beta1.FeatureFlag
ClientError error
}
@ -403,7 +406,7 @@ func TestSync_fetch(t *testing.T) {
},
},
wantErr: false,
want: flagSpec,
want: `{"flags":{}}`,
},
{
name: "Scenario - get from API if informer cache miss",
@ -414,7 +417,7 @@ func TestSync_fetch(t *testing.T) {
ClientResponse: validCfg,
},
wantErr: false,
want: flagSpec,
want: `{"flags":{}}`,
},
{
name: "Scenario - error for informer cache read error",
@ -468,18 +471,20 @@ func TestSync_fetch(t *testing.T) {
}
func TestSync_watcher(t *testing.T) {
flagSpec := "fakeFlagSpec"
flagSpec := v1beta1.FeatureFlagSpec{
FlagSpec: v1beta1.FlagSpec{
Flags: map[string]v1beta1.Flag{},
},
}
validCfg := v1alpha1.FeatureFlagConfiguration{
validCfg := v1beta1.FeatureFlag{
TypeMeta: Metadata,
ObjectMeta: v1.ObjectMeta{
Namespace: "resourceNS",
Name: "resourceName",
ResourceVersion: "v1",
},
Spec: v1alpha1.FeatureFlagConfigurationSpec{
FeatureFlagSpec: flagSpec,
},
Spec: flagSpec,
}
type args struct {
@ -494,7 +499,7 @@ func TestSync_watcher(t *testing.T) {
}{
{
name: "scenario - create event",
want: flagSpec,
want: `{"flags":{}}`,
args: args{
InformerGetFunc: func(key string) (item interface{}, exists bool, err error) {
return toUnstructured(t, validCfg), true, nil
@ -508,7 +513,7 @@ func TestSync_watcher(t *testing.T) {
},
{
name: "scenario - modify event",
want: flagSpec,
want: `{"flags":{}}`,
args: args{
InformerGetFunc: func(key string) (item interface{}, exists bool, err error) {
return toUnstructured(t, validCfg), true, nil
@ -602,7 +607,7 @@ func TestSync_ReSync(t *testing.T) {
ff := &unstructured.Unstructured{}
ff.SetUnstructuredContent(getCFG(name, ns))
fakeDynamicClient := fake.NewSimpleDynamicClient(s, ff)
validFFCfg := &v1alpha1.FeatureFlagConfiguration{
validFFCfg := &v1beta1.FeatureFlag{
TypeMeta: Metadata,
ObjectMeta: v1.ObjectMeta{
Name: name,
@ -727,7 +732,7 @@ func TestNotify(t *testing.T) {
"empty": "",
}
ff.SetUnstructuredContent(cfg)
_, err = fc.Resource(featureFlagConfigurationResource).Namespace(ns).UpdateStatus(context.TODO(), ff, v1.UpdateOptions{})
_, err = fc.Resource(featureFlagResource).Namespace(ns).UpdateStatus(context.TODO(), ff, v1.UpdateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -740,7 +745,7 @@ func TestNotify(t *testing.T) {
old["resourceVersion"] = "newVersion"
cfg["metadata"] = old
ff.SetUnstructuredContent(cfg)
_, err = fc.Resource(featureFlagConfigurationResource).Namespace(ns).UpdateStatus(context.TODO(), ff, v1.UpdateOptions{})
_, err = fc.Resource(featureFlagResource).Namespace(ns).UpdateStatus(context.TODO(), ff, v1.UpdateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -749,7 +754,7 @@ func TestNotify(t *testing.T) {
t.Errorf("Expected message %v, got %v", DefaultEventTypeModify, msg)
}
// delete
err = fc.Resource(featureFlagConfigurationResource).Namespace(ns).Delete(context.TODO(), name, v1.DeleteOptions{})
err = fc.Resource(featureFlagResource).Namespace(ns).Delete(context.TODO(), name, v1.DeleteOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -763,7 +768,7 @@ func TestNotify(t *testing.T) {
"featureFlagSpec": int64(12), // we expect string here
}
ff.SetUnstructuredContent(cfg)
_, err = fc.Resource(featureFlagConfigurationResource).Namespace(ns).Create(context.TODO(), ff, v1.CreateOptions{})
_, err = fc.Resource(featureFlagResource).Namespace(ns).Create(context.TODO(), ff, v1.CreateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -771,11 +776,11 @@ func TestNotify(t *testing.T) {
"bump": "1",
}
ff.SetUnstructuredContent(cfg)
_, err = fc.Resource(featureFlagConfigurationResource).Namespace(ns).UpdateStatus(context.TODO(), ff, v1.UpdateOptions{})
_, err = fc.Resource(featureFlagResource).Namespace(ns).UpdateStatus(context.TODO(), ff, v1.UpdateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
err = fc.Resource(featureFlagConfigurationResource).Namespace(ns).Delete(context.TODO(), name, v1.DeleteOptions{})
err = fc.Resource(featureFlagResource).Namespace(ns).Delete(context.TODO(), name, v1.DeleteOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -839,14 +844,14 @@ func Test_NewK8sSync(t *testing.T) {
}
func newFakeReadClient(objs ...client.Object) client.Client {
_ = v1alpha1.AddToScheme(scheme.Scheme)
_ = v1beta1.AddToScheme(scheme.Scheme)
return fakeClient.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(objs...).Build()
}
func getCFG(name, namespace string) map[string]interface{} {
return map[string]interface{}{
"apiVersion": "core.openfeature.dev/v1alpha1",
"kind": "FeatureFlagConfiguration",
"apiVersion": "core.openfeature.dev/v1beta1",
"kind": "FeatureFlag",
"metadata": map[string]interface{}{
"name": name,
"namespace": namespace,
@ -879,7 +884,7 @@ type MockClient struct {
client.Reader
clientErr error
getResponse v1alpha1.FeatureFlagConfiguration
getResponse v1beta1.FeatureFlag
}
func (m MockClient) Get(_ context.Context, _ client.ObjectKey, obj client.Object, _ ...client.GetOption) error {
@ -889,9 +894,9 @@ func (m MockClient) Get(_ context.Context, _ client.ObjectKey, obj client.Object
}
// else try returning response
cfg, ok := obj.(*v1alpha1.FeatureFlagConfiguration)
cfg, ok := obj.(*v1beta1.FeatureFlag)
if !ok {
return errors.New("must contain a pointer typed v1alpha1.FeatureFlagConfiguration")
return errors.New("must contain a pointer typed v1beta1.FeatureFlag")
}
*cfg = m.getResponse
@ -908,3 +913,20 @@ type MockInformer struct {
func (m MockInformer) GetStore() cache.Store {
return &m.fakeStore
}
func TestMeasure(t *testing.T) {
res, err := marshallFeatureFlagSpec(&v1beta1.FeatureFlag{
Spec: v1beta1.FeatureFlagSpec{
FlagSpec: v1beta1.FlagSpec{
Flags: map[string]v1beta1.Flag{
"flag": {
DefaultVariant: "kubernetes",
},
},
},
},
})
require.Nil(t, err)
require.Equal(t, "{\"flags\":{\"flag\":{\"state\":\"\",\"variants\":null,\"defaultVariant\":\"kubernetes\"}}}", res)
}

View File

@ -50,18 +50,18 @@ See [sync source](../reference/sync-configuration.md#source-configuration) confi
### Kubernetes sync
The Kubernetes sync provider allows flagd to connect to a Kubernetes cluster and evaluate flags against a specified
FeatureFlagConfiguration resource as defined within
the [open-feature-operator](https://github.com/open-feature/open-feature-operator/blob/main/apis/core/v1alpha1/featureflagconfiguration_types.go)
FeatureFlag resource as defined within
the [open-feature-operator](https://github.com/open-feature/open-feature-operator/blob/main/apis/core/v1beta1/featureflag_types.go)
spec.
This configuration is best used in conjunction with the [OpenFeature Operator](https://github.com/open-feature/open-feature-operator).
To use an existing FeatureFlagConfiguration custom resource, start flagd with the following command:
To use an existing FeatureFlag custom resource, start flagd with the following command:
```shell
flagd start --uri core.openfeature.dev/default/my_example
```
In this example, `default/my_example` expected to be a valid FeatureFlagConfiguration resource, where `default` is the
In this example, `default/my_example` expected to be a valid FeatureFlag resource, where `default` is the
namespace and `my_example` being the resource name.
See [sync source](../reference/sync-configuration.md#source-configuration) configuration for details.

View File

@ -26,7 +26,7 @@ flagd start [flags]
-s, --sources string JSON representation of an array of SourceConfig objects. This object contains 2 required fields, uri (string) and provider (string). Documentation for this object: https://github.com/open-feature/flagd/blob/main/docs/configuration/configuration.md#sync-provider-customisation
-y, --sync-provider string DEPRECATED: Set a sync provider e.g. filepath or remote
-a, --sync-provider-args stringToString DEPRECATED: Sync provider arguments as key values separated by = (default [])
-f, --uri .yaml/.yml/.json Set a sync provider uri to read data from, this can be a filepath,url (http and grpc) or FeatureFlagConfiguration. When flag keys are duplicated across multiple providers the merge priority follows the index of the flag arguments, as such flags from the uri at index 0 take the lowest precedence, with duplicated keys being overwritten by those from the uri at index 1. Please note that if you are using filepath, flagd only supports files with .yaml/.yml/.json extension.
-f, --uri .yaml/.yml/.json Set a sync provider uri to read data from, this can be a filepath, URL (HTTP and gRPC) or FeatureFlag custom resource. When flag keys are duplicated across multiple providers the merge priority follows the index of the flag arguments, as such flags from the uri at index 0 take the lowest precedence, with duplicated keys being overwritten by those from the uri at index 1. Please note that if you are using filepath, flagd only supports files with .yaml/.yml/.json extension.
```
### Options inherited from parent commands

View File

@ -1,12 +1,12 @@
# Feature Flag Configuration
The `FeatureFlagConfiguration` version `v1alpha2` CRD defines a CR with the following example structure:
The `FeatureFlag` version `v1beta1` CRD defines a CR with the following example structure:
```yaml
apiVersion: core.openfeature.dev/v1alpha2
kind: FeatureFlagConfiguration
apiVersion: core.openfeature.dev/v1beta1
kind: FeatureFlag
metadata:
name: featureflagconfiguration-sample
name: featureflag-sample
spec:
featureFlagSpec:
flags:

View File

@ -1,23 +1,23 @@
# FlagSourceConfigurations
# FeatureFlagSources
`FlagSourceConfiguration` support multiple flag sources. Sources are configured as a list and given below are supported sources and their configurations,
`FeatureFlagSource` support multiple flag sources. Sources are configured as a list and given below are supported sources and their configurations,
## kubernetes aka `FeatureFlagConfiguration`
## kubernetes aka `FeatureFlag`
This is `FeatureFlagConfiguration` custom resource backed flagd feature flag definition.
Read more on the custom resource at the dedicated documentation of [FeatureFlagConfiguration](https://github.com/open-feature/open-feature-operator/blob/main/docs/feature_flag_configuration.md)
This is `FeatureFlag` custom resource backed flagd feature flag definition.
Read more on the custom resource at the dedicated documentation of [FeatureFlag](https://github.com/open-feature/open-feature-operator/blob/main/docs/feature_flag_configuration.md)
To refer this custom resource in `FlagSourceConfiguration`, provider type `kubernetes` is used as below example,
To refer this custom resource in `FlagSource`, provider type `kubernetes` is used as below example,
```yaml
sources:
- source: flags/sample-flags # FeatureFlagConfiguration - namespace/custom_resource_name
provider: kubernetes # kubernetes flag source backed by FeatureFlagConfiguration custom resource
- source: flags/sample-flags # FeatureFlag - namespace/custom_resource_name
provider: kubernetes # kubernetes flag source backed by FeatureFlag custom resource
```
## flagd-proxy
`flagd-proxy` is an alternative to direct resource access on `FeatureFlagConfiguration` custom resources.
`flagd-proxy` is an alternative to direct resource access on `FeatureFlag` custom resources.
This source type is useful when there is a need for restricting workload permissions and/or to reduce k8s API load.
Read more about proxy approach to access kubernetes resources: [flagd-proxy](https://github.com/open-feature/open-feature-operator/blob/main/docs/flagd_proxy.md)
@ -60,7 +60,7 @@ sources:
## Sidecar configurations
`FlagSourceConfiguration` further allows to provide configurations to the injected flagd sidecar.
`FeatureFlagSource` further allows to provide configurations to the injected flagd sidecar.
Table given below is non-exhaustive list of overriding options,
| Configuration | Explanation | Default |
@ -82,16 +82,16 @@ If no namespace is provided, it is assumed that the CR is within the same namesp
namespace: test-ns
annotations:
openfeature.dev/enabled: "true"
openfeature.dev/flagsourceconfiguration: "config-A, test-ns-2/config-B"
openfeature.dev/featureflagsource: "config-A, test-ns-2/config-B"
```
In this example, 2 CRs are being used to configure the injected container (by default the operator uses the `flagd:main` image), `config-A` (which is assumed to be in the namespace `test-ns`) and `config-B` from the `test-ns-2` namespace, with `config-B` taking precedence in the configuration merge.
The `FlagSourceConfiguration` version `v1alpha3` CRD defines a CR with the following example structure, the documentation for this CRD can be found [here](https://github.com/open-feature/open-feature-operator/blob/main/docs/crds.md#flagsourceconfiguration):
The `FeatureFlagSource` version `v1beta1` CRD defines a CR with the following example structure, the documentation for this CRD can be found [here](https://github.com/open-feature/open-feature-operator/blob/main/docs/crds.md#featureflagsource):
```yaml
apiVersion: core.openfeature.dev/v1alpha3
kind: FlagSourceConfiguration
apiVersion: core.openfeature.dev/v1beta1
kind: FeatureFlagSource
metadata:
name: flag-source-sample
spec:
@ -114,15 +114,15 @@ spec:
debugLogging: false
```
The relevant `FlagSourceConfigurations` are passed to the operator by setting the `openfeature.dev/flagsourceconfiguration` annotation, and is responsible for providing the full configuration of the injected sidecar.
The relevant `FeatureFlagSources` are passed to the operator by setting the `openfeature.dev/featureflagsource` annotation, and is responsible for providing the full configuration of the injected sidecar.
## Configuration Merging
When multiple `FlagSourceConfigurations` are provided, the configurations are merged. The last `CR` takes precedence over the first, with any configuration from the deprecated `FlagDSpec` field of the `FeatureFlagConfiguration` CRD taking the lowest priority.
When multiple `FeatureFlagSources` are provided, the configurations are merged. The last `CR` takes precedence over the first, with any configuration from the deprecated `FlagDSpec` field of the `FeatureFlag` CRD taking the lowest priority.
```mermaid
flowchart LR
FlagSourceConfiguration-values -->|highest priority| environment-variables -->|lowest priority| defaults
FeatureFlagSource-values -->|highest priority| environment-variables -->|lowest priority| defaults
```
An example of this behavior:
@ -131,14 +131,14 @@ An example of this behavior:
metadata:
annotations:
openfeature.dev/enabled: "true"
openfeature.dev/flagsourceconfiguration:"config-A, config-B"
openfeature.dev/featureflagsource:"config-A, config-B"
```
Config-A:
```yaml
apiVersion: core.openfeature.dev/v1alpha2
kind: FlagSourceConfiguration
apiVersion: core.openfeature.dev/v1beta1
kind: FeatureFlagSource
metadata:
name: config-A
spec:
@ -149,8 +149,8 @@ spec:
Config-B:
```yaml
apiVersion: core.openfeature.dev/v1alpha2
kind: FlagSourceConfiguration
apiVersion: core.openfeature.dev/v1beta1
kind: FeatureFlagSource
metadata:
name: config-B
spec:

View File

@ -27,14 +27,14 @@ Create a namespace to house your flags:
kubectl create namespace flags
```
Next, define your feature flag(s) using the [FeatureFlagConfiguration](./crds/featureflagconfiguration.md) custom resource definition (CRD).
Next, define your feature flag(s) using the [FeatureFlag](./crds/featureflag.md) custom resource definition (CRD).
This example specifies one flag called `foo` which has two variants `bar` and `baz`. The `defaultVariant` is `bar`.
```bash
kubectl apply -n flags -f - <<EOF
apiVersion: core.openfeature.dev/v1alpha2
kind: FeatureFlagConfiguration
apiVersion: core.openfeature.dev/v1beta1
kind: FeatureFlag
metadata:
name: sample-flags
spec:
@ -52,7 +52,7 @@ EOF
Next, tell the OpenFeature operator where to find flags.
Do so by creating a [FlagSourceConfiguration](./crds/flagsourceconfiguration.md) CRD.
Do so by creating a [FeatureFlagSource](./crds/featureflagsource.md) CRD.
This example specifies that the CRD called `sample-flags` (created above) can be found in the `flags` namespace and that the provider is `kubernetes`.
@ -60,10 +60,10 @@ The `port` parameter defines the port on which the flagd API will be made availa
```bash
kubectl apply -n flags -f - <<EOF
apiVersion: core.openfeature.dev/v1alpha3
kind: FlagSourceConfiguration
apiVersion: core.openfeature.dev/v1beta1
kind: FeatureFlagSource
metadata:
name: flag-source-configuration
name: feature-flag-source
spec:
sources:
- source: flags/sample-flags
@ -77,11 +77,11 @@ EOF
The operator looks for `Deployment` objects annotated with particular annotations.
- `openfeature.dev/enabled: "true"` enables this deployment for flagd
- `openfeature.dev/flagsourceconfiguration: "flags/flag-source-configuration"` makes the given feature flag sources available to this deployment
- `openfeature.dev/featureflagsource: "flags/feature-flag-source"` makes the given feature flag sources available to this deployment
When these two annotations are added, the OpenFeature operator will inject a sidecar into your workload.
flagd will then be available via `http://localhost` the port specified in the `FlagSourceConfiguration` (e.g. `8080`)
flagd will then be available via `http://localhost` the port specified in the `FeatureFlagSource` (e.g. `8080`)
Your Deployment YAML might look like this:
@ -102,7 +102,7 @@ spec:
annotations:
# here are the annotations for OpenFeature Operator
openfeature.dev/enabled: "true"
openfeature.dev/flagsourceconfiguration: "flags/flag-source-configuration"
openfeature.dev/featureflagsource: "flags/feature-flag-source"
spec:
containers:
- name: busybox

View File

@ -21,7 +21,7 @@ in-process flagd provider via a gRPC client connection to a sync server, such as
An implementation of an in-process flagd-provider must accept the following environment variables which determine the sync source:
- `FLAGD_SOURCE_URI`: The URI identifying the sync source. Depending on the sync provider type, this can be the URI of a gRPC service providing the `sync` API required by the in-process flagd provider, or the name of a [core.openfeature.dev/v1alpha2.FeatureFlagConfiguration](https://github.com/open-feature/open-feature-operator/blob/main/docs/crds.md#featureflagconfiguration-1) Custom Resource containing the flag definition.
- `FLAGD_SOURCE_URI`: The URI identifying the sync source. Depending on the sync provider type, this can be the URI of a gRPC service providing the `sync` API required by the in-process flagd provider, or the name of a [core.openfeature.dev/v1beta1.FeatureFlag](https://github.com/open-feature/open-feature-operator/blob/main/docs/crds.md#featureflag-1) Custom Resource containing the flag definition.
- `FLAGD_SOURCE_PROVIDER_TYPE`: The type of the provider. E.g. `grpc` or `kubernetes`.
- `FLAGD_SOURCE_SELECTOR`: Optional selector for the feature flag definition of interest. This is used as a `selector` for the flagd-proxie's sync API to identify a flag definition within a collection of feature flag definitions.

View File

@ -2,7 +2,7 @@
[![experimental](http://badges.github.io/stability-badges/dist/experimental.svg)](http://github.com/badges/stability-badges)
The kube flagd proxy acts as a pub sub for deployed flagd sidecar containers to subscribe to change events in FeatureFlagConfiguration CRs.
The kube flagd proxy acts as a pub sub for deployed flagd sidecar containers to subscribe to change events in FeatureFlag CRs.
<!-- markdownlint-disable MD033 -->
<p align="center">
<img src="../images/flagd-proxy.png" width="650">
@ -40,12 +40,12 @@ spec:
- '[{"uri":"grpc://flagd-proxy-svc.flagd-proxy.svc.cluster.local:8015","provider":"grpc","selector":"core.openfeature.dev/NAMESPACE/NAME"}]'
- --debug
---
apiVersion: core.openfeature.dev/v1alpha2
kind: FeatureFlagConfiguration
apiVersion: core.openfeature.dev/v1beta1
kind: FeatureFlag
metadata:
name: end-to-end
spec:
featureFlagSpec:
flagSpec:
flags:
color:
state: ENABLED

View File

@ -54,7 +54,7 @@ func init() {
"a", nil, "DEPRECATED: Sync provider arguments as key values separated by =")
flags.StringSliceP(
uriFlagName, "f", []string{}, "Set a sync provider uri to read data from, this can be a filepath,"+
"url (http and grpc) or FeatureFlagConfiguration. When flag keys are duplicated across multiple providers the "+
" URL (HTTP and gRPC) or FeatureFlag custom resource. When flag keys are duplicated across multiple providers the "+
"merge priority follows the index of the flag arguments, as such flags from the uri at index 0 take the "+
"lowest precedence, with duplicated keys being overwritten by those from the uri at index 1. "+
"Please note that if you are using filepath, flagd only supports files with `.yaml/.yml/.json` extension.",

View File

@ -92,8 +92,8 @@ nav:
- 'String Comparison Specification': 'reference/specifications/custom-operations/string-comparison-operation-spec.md'
- 'OpenFeature Operator':
- 'Installation': 'reference/openfeature-operator/installation.md'
- 'FeatureFlagConfiguration': 'reference/openfeature-operator/crds/featureflagconfiguration.md'
- 'FlagSourceConfiguration': 'reference/openfeature-operator/crds/flagsourceconfiguration.md'
- 'FeatureFlag': 'reference/openfeature-operator/crds/featureflag.md'
- 'FeatureFlagSource': 'reference/openfeature-operator/crds/featureflagsource.md'
- 'Naming': 'reference/naming.md'
- 'FAQ': 'faq.md'
- 'Troubleshooting': 'troubleshooting.md'