diff --git a/core/pkg/runtime/from_config.go b/core/pkg/runtime/from_config.go index 80d666d71..bef2cb552 100644 --- a/core/pkg/runtime/from_config.go +++ b/core/pkg/runtime/from_config.go @@ -9,6 +9,8 @@ import ( msync "sync" "time" + "github.com/open-feature/flagd/core/pkg/sync/grpc/credentials" + "github.com/open-feature/flagd/core/pkg/service" "go.opentelemetry.io/otel/exporters/prometheus" @@ -21,7 +23,6 @@ import ( "github.com/open-feature/flagd/core/pkg/sync" "github.com/open-feature/flagd/core/pkg/sync/file" "github.com/open-feature/flagd/core/pkg/sync/grpc" - "github.com/open-feature/flagd/core/pkg/sync/grpc/credentials" httpSync "github.com/open-feature/flagd/core/pkg/sync/http" "github.com/open-feature/flagd/core/pkg/sync/kubernetes" "github.com/robfig/cron" @@ -53,6 +54,7 @@ type SourceConfig struct { BearerToken string `json:"bearerToken,omitempty"` CertPath string `json:"certPath,omitempty"` + TLS bool `json:"tls,omitempty"` ProviderID string `json:"providerID,omitempty"` Selector string `json:"selector,omitempty"` } @@ -162,10 +164,11 @@ func NewGRPC(config SourceConfig, logger *logger.Logger) *grpc.Sync { zap.String("component", "sync"), zap.String("sync", "grpc"), ), + CredentialBuilder: &credentials.CredentialBuilder{}, CertPath: config.CertPath, ProviderID: config.ProviderID, + Secure: config.TLS, Selector: config.Selector, - CredentialBuilder: &credentials.CredentialBuilder{}, } } @@ -218,24 +221,19 @@ func ParseSources(sourcesFlag string) ([]SourceConfig, error) { if err := json.Unmarshal([]byte(sourcesFlag), &syncProvidersParsed); err != nil { return syncProvidersParsed, fmt.Errorf("unable to parse sync providers: %w", err) } - for i, sp := range syncProvidersParsed { + for _, sp := range syncProvidersParsed { if sp.URI == "" { return syncProvidersParsed, errors.New("sync provider argument parse: uri is a required field") } if sp.Provider == "" { return syncProvidersParsed, errors.New("sync provider argument parse: provider is a required field") } - switch uriB := []byte(sp.URI); { - case regFile.Match(uriB): - syncProvidersParsed[i].URI = regFile.ReplaceAllString(syncProvidersParsed[i].URI, "") - case regCrd.Match(uriB): - syncProvidersParsed[i].URI = regCrd.ReplaceAllString(syncProvidersParsed[i].URI, "") - } } return syncProvidersParsed, nil } -// ParseSyncProviderURIs uri flag based sync sources to SourceConfig array. Replaces uri prefixes where necessary +// ParseSyncProviderURIs uri flag based sync sources to SourceConfig array. Replaces uri prefixes where necessary to +// derive SourceConfig func ParseSyncProviderURIs(uris []string) ([]SourceConfig, error) { syncProvidersParsed := []SourceConfig{} @@ -256,10 +254,16 @@ func ParseSyncProviderURIs(uris []string) ([]SourceConfig, error) { URI: uri, Provider: syncProviderHTTP, }) - case regGRPC.Match(uriB), regGRPCSecure.Match(uriB): + case regGRPC.Match(uriB): syncProvidersParsed = append(syncProvidersParsed, SourceConfig{ - URI: uri, + URI: regGRPC.ReplaceAllString(uri, ""), + Provider: syncProviderGrpc, + }) + case regGRPCSecure.Match(uriB): + syncProvidersParsed = append(syncProvidersParsed, SourceConfig{ + URI: regGRPCSecure.ReplaceAllString(uri, ""), Provider: syncProviderGrpc, + TLS: true, }) default: return syncProvidersParsed, fmt.Errorf("invalid sync uri argument: %s, must start with 'file:', "+ diff --git a/core/pkg/runtime/from_config_test.go b/core/pkg/runtime/from_config_test.go index 21baa476d..c83b2eb1a 100644 --- a/core/pkg/runtime/from_config_test.go +++ b/core/pkg/runtime/from_config_test.go @@ -19,7 +19,7 @@ func TestParseSource(t *testing.T) { out: []SourceConfig{ { URI: "config/samples/example_flags.json", - Provider: "file", + Provider: syncProviderFile, }, }, }, @@ -34,55 +34,62 @@ func TestParseSource(t *testing.T) { out: []SourceConfig{ { URI: "config/samples/example_flags.json", - Provider: "file", + Provider: syncProviderFile, }, { URI: "http://test.com", - Provider: "http", + Provider: syncProviderHTTP, BearerToken: ":)", }, { URI: "host:port", - Provider: "grpc", + Provider: syncProviderGrpc, }, { URI: "default/my-crd", - Provider: "kubernetes", + Provider: syncProviderKubernetes, }, }, }, "multiple-syncs-with-options": { - in: `[ - {"uri":"file:config/samples/example_flags.json","provider":"file"}, - {"uri":"https://test.com","provider":"http","bearerToken":":)"}, - {"uri":"host:port","provider":"grpc"}, - {"uri":"host:port","provider":"grpcs","providerID":"appA","selector":"source=database"}, - {"uri":"core.openfeature.dev/namespace/my-crd","provider":"kubernetes"} - ]`, + in: `[{"uri":"config/samples/example_flags.json","provider":"file"}, + {"uri":"http://my-flag-source.json","provider":"http","bearerToken":"bearer-dji34ld2l"}, + {"uri":"https://secure-remote","provider":"http","bearerToken":"bearer-dji34ld2l"}, + {"uri":"default/my-flag-config","provider":"kubernetes"}, + {"uri":"grpc-source:8080","provider":"grpc"}, + {"uri":"my-flag-source:8080","provider":"grpc", "tls":true, "certPath": "/certs/ca.cert", "providerID": "flagd-weatherapp-sidecar", "selector": "source=database,app=weatherapp"}] + `, expectErr: false, out: []SourceConfig{ { URI: "config/samples/example_flags.json", - Provider: "file", + Provider: syncProviderFile, }, { - URI: "https://test.com", - Provider: "http", - BearerToken: ":)", + URI: "http://my-flag-source.json", + Provider: syncProviderHTTP, + BearerToken: "bearer-dji34ld2l", }, { - URI: "host:port", - Provider: "grpc", + URI: "https://secure-remote", + Provider: syncProviderHTTP, + BearerToken: "bearer-dji34ld2l", }, { - URI: "host:port", - Provider: "grpcs", - ProviderID: "appA", - Selector: "source=database", + URI: "default/my-flag-config", + Provider: syncProviderKubernetes, }, { - URI: "namespace/my-crd", - Provider: "kubernetes", + URI: "grpc-source:8080", + Provider: syncProviderGrpc, + }, + { + URI: "my-flag-source:8080", + Provider: syncProviderGrpc, + TLS: true, + CertPath: "/certs/ca.cert", + ProviderID: "flagd-weatherapp-sidecar", + Selector: "source=database,app=weatherapp", }, }, }, @@ -138,6 +145,7 @@ func TestParseSyncProviderURIs(t *testing.T) { "file:my-file.json", "https://test.com", "grpc://host:port", + "grpcs://secure-grpc", "core.openfeature.dev/default/my-crd", }, expectErr: false, @@ -151,8 +159,14 @@ func TestParseSyncProviderURIs(t *testing.T) { Provider: "http", }, { - URI: "grpc://host:port", + URI: "host:port", + Provider: "grpc", + TLS: false, + }, + { + URI: "secure-grpc", Provider: "grpc", + TLS: true, }, { URI: "default/my-crd", diff --git a/core/pkg/sync/grpc/credentials/builder.go b/core/pkg/sync/grpc/credentials/builder.go index 0ed99a704..86cf49cb3 100644 --- a/core/pkg/sync/grpc/credentials/builder.go +++ b/core/pkg/sync/grpc/credentials/builder.go @@ -5,35 +5,29 @@ import ( "crypto/x509" "fmt" "os" - "strings" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" ) -const ( - // Prefix for GRPC URL inputs. GRPC does not define a standard prefix. This prefix helps to differentiate remote - // URLs for REST APIs (i.e - HTTP) from GRPC endpoints. - Prefix = "grpc://" - PrefixSecure = "grpcs://" - - tlsVersion = tls.VersionTLS12 -) +const tlsVersion = tls.VersionTLS12 type Builder interface { - Build(string, string) (credentials.TransportCredentials, error) + Build(secure bool, certPath string) (credentials.TransportCredentials, error) } type CredentialBuilder struct{} // Build is a helper to build grpc credentials.TransportCredentials based on source and cert path -func (cb *CredentialBuilder) Build(source string, certPath string) (credentials.TransportCredentials, error) { - if strings.Contains(source, Prefix) { - return insecure.NewCredentials(), nil - } +func (cb *CredentialBuilder) Build(secure bool, certPath string) (credentials.TransportCredentials, error) { + if !secure { + // check if certificate is set & make this an error so that we do not establish an unwanted insecure connection + if certPath != "" { + return nil, fmt.Errorf("provided a non empty certificate %s, but requested an insecure connection."+ + " Please check configurations of the grpc sync source", certPath) + } - if !strings.Contains(source, PrefixSecure) { - return nil, fmt.Errorf("invalid source. grpc source must contain prefix %s or %s", Prefix, PrefixSecure) + return insecure.NewCredentials(), nil } if certPath == "" { diff --git a/core/pkg/sync/grpc/credentials/builder_test.go b/core/pkg/sync/grpc/credentials/builder_test.go index f1f2b0374..b5304cb85 100644 --- a/core/pkg/sync/grpc/credentials/builder_test.go +++ b/core/pkg/sync/grpc/credentials/builder_test.go @@ -64,51 +64,52 @@ func TestCredentialBuilder_Build(t *testing.T) { tests := []struct { name string - source string certPath string + secure bool expectSecProto string error bool }{ { name: "Insecure source results in insecure connection", - source: Prefix + "some.domain", + secure: false, certPath: "", expectSecProto: insecure, }, { name: "Secure source results in secure connection", - source: PrefixSecure + "some.domain", certPath: validCertFile, + secure: true, expectSecProto: tls, }, { name: "Secure source with no certificate results in a secure connection", - source: PrefixSecure + "some.domain", + secure: true, expectSecProto: tls, }, { name: "Invalid cert path results in an error", - source: PrefixSecure + "some.domain", + secure: true, certPath: "invalid/path", error: true, }, { name: "Invalid certificate results in an error", - source: PrefixSecure + "some.domain", + secure: true, certPath: invalidCertFile, error: true, }, { - name: "Invalid prefix results in an error", - source: "http://some.domain", - error: true, + name: "Prevent insecure if certificate path is set - configuration check", + secure: false, + certPath: validCertFile, + error: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { builder := CredentialBuilder{} - tCred, err := builder.Build(test.source, test.certPath) + tCred, err := builder.Build(test.secure, test.certPath) if test.error { if err == nil { diff --git a/core/pkg/sync/grpc/credentials/mock/builder.go b/core/pkg/sync/grpc/credentials/mock/builder.go index 933a926a6..ffa857f8b 100644 --- a/core/pkg/sync/grpc/credentials/mock/builder.go +++ b/core/pkg/sync/grpc/credentials/mock/builder.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: core/pkg/sync/grpc/credentials/builder.go +// Source: pkg/sync/grpc/credentials/builder.go // Package credendialsmock is a generated GoMock package. package credendialsmock @@ -35,16 +35,16 @@ func (m *MockBuilder) EXPECT() *MockBuilderMockRecorder { } // Build mocks base method. -func (m *MockBuilder) Build(arg0, arg1 string) (credentials.TransportCredentials, error) { +func (m *MockBuilder) Build(secure bool, certPath string) (credentials.TransportCredentials, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Build", arg0, arg1) + ret := m.ctrl.Call(m, "Build", secure, certPath) ret0, _ := ret[0].(credentials.TransportCredentials) ret1, _ := ret[1].(error) return ret0, ret1 } // Build indicates an expected call of Build. -func (mr *MockBuilderMockRecorder) Build(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockBuilderMockRecorder) Build(secure, certPath interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockBuilder)(nil).Build), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockBuilder)(nil).Build), secure, certPath) } diff --git a/core/pkg/sync/grpc/grpc_sync.go b/core/pkg/sync/grpc/grpc_sync.go index 4b29b4f69..0ea629730 100644 --- a/core/pkg/sync/grpc/grpc_sync.go +++ b/core/pkg/sync/grpc/grpc_sync.go @@ -4,11 +4,10 @@ import ( "context" "fmt" "math" - "strings" msync "sync" "time" - credentials2 "github.com/open-feature/flagd/core/pkg/sync/grpc/credentials" + grpccredential "github.com/open-feature/flagd/core/pkg/sync/grpc/credentials" "buf.build/gen/go/open-feature/flagd/grpc/go/sync/v1/syncv1grpc" v1 "buf.build/gen/go/open-feature/flagd/protocolbuffers/go/sync/v1" @@ -44,31 +43,27 @@ type FlagSyncServiceClientResponse interface { var once msync.Once type Sync struct { - URI string - ProviderID string - Selector string CertPath string + CredentialBuilder grpccredential.Builder Logger *logger.Logger - CredentialBuilder credentials2.Builder + ProviderID string + Secure bool + Selector string + URI string client FlagSyncServiceClient ready bool } func (g *Sync) Init(ctx context.Context) error { - tCredentials, err := g.CredentialBuilder.Build(g.URI, g.CertPath) + tCredentials, err := g.CredentialBuilder.Build(g.Secure, g.CertPath) if err != nil { g.Logger.Error(fmt.Sprintf("error building transport credentials: %s", err.Error())) return err } - target, ok := sourceToGRPCTarget(g.URI) - if !ok { - return fmt.Errorf("invalid grpc source: %s", g.URI) - } - // Derive reusable client connection - rpcCon, err := grpc.DialContext(ctx, target, grpc.WithTransportCredentials(tCredentials)) + rpcCon, err := grpc.DialContext(ctx, g.URI, grpc.WithTransportCredentials(tCredentials)) if err != nil { g.Logger.Error(fmt.Sprintf("error initiating grpc client connection: %s", err.Error())) return err @@ -223,26 +218,3 @@ func (g *Sync) handleFlagSync(stream syncv1grpc.FlagSyncService_SyncFlagsClient, } } } - -// sourceToGRPCTarget is a helper to derive GRPC target from a provided URL -// For example, function returns the target localhost:9090 for the input grpc://localhost:9090 -func sourceToGRPCTarget(url string) (string, bool) { - var separator string - - switch { - case strings.Contains(url, Prefix): - separator = Prefix - case strings.Contains(url, PrefixSecure): - separator = PrefixSecure - default: - return "", false - } - - index := strings.Split(url, separator) - - if len(index) == 2 && len(index[1]) != 0 { - return index[1], true - } - - return "", false -} diff --git a/core/pkg/sync/grpc/grpc_sync_test.go b/core/pkg/sync/grpc/grpc_sync_test.go index 52eab7ab4..8815c738a 100644 --- a/core/pkg/sync/grpc/grpc_sync_test.go +++ b/core/pkg/sync/grpc/grpc_sync_test.go @@ -28,62 +28,43 @@ import ( "google.golang.org/grpc/test/bufconn" ) -func Test_Init(t *testing.T) { +func Test_InitWithMockCredentialBuilder(t *testing.T) { tests := []struct { - name string - target string - err error - returnedCredentials credentials.TransportCredentials - shouldError bool + name string + mockCredentials credentials.TransportCredentials + mockCredentialBuilderError error + shouldError bool }{ { - name: "happy path", - target: "grpc://localBufCon", - err: nil, - returnedCredentials: insecure.NewCredentials(), - shouldError: false, + name: "Initializer - happy path", + mockCredentialBuilderError: nil, + mockCredentials: insecure.NewCredentials(), + shouldError: false, }, { - name: "invalid grpc source", - target: "localBufCon", - err: nil, - returnedCredentials: insecure.NewCredentials(), - shouldError: true, + name: "Initializer fails on nil credentials", + mockCredentialBuilderError: nil, + mockCredentials: nil, + shouldError: true, }, { - name: "nil credentials", - target: "grpc://localBufCon", - err: nil, - returnedCredentials: nil, - shouldError: true, - }, - { - name: "could not create transport credentials", - target: "grpc://localBufCon", - err: errors.New("could not create transport credentials"), - returnedCredentials: nil, - shouldError: true, + name: "Initializer handles credential builder error", + mockCredentialBuilderError: errors.New("could not create transport credentials"), + mockCredentials: nil, + shouldError: true, }, } for _, test := range tests { - bufCon := bufconn.Listen(5) - - bufServer := bufferedServer{ - listener: bufCon, - } - - // start server - go serve(&bufServer) - mockCtrl := gomock.NewController(t) mockCredentialBulder := credendialsmock.NewMockBuilder(mockCtrl) - mockCredentialBulder.EXPECT().Build(gomock.Any(), gomock.Any()).Return(test.returnedCredentials, test.err) + mockCredentialBulder.EXPECT(). + Build(gomock.Any(), gomock.Any()). + Return(test.mockCredentials, test.mockCredentialBuilderError) grpcSync := Sync{ - URI: test.target, - ProviderID: "", + URI: "grpc-target", Logger: logger.NewLogger(nil, false), CredentialBuilder: mockCredentialBulder, } @@ -91,11 +72,10 @@ func Test_Init(t *testing.T) { err := grpcSync.Init(context.Background()) if test.shouldError { - require.NotNil(t, err) - require.Nil(t, grpcSync.client) + require.NotNilf(t, err, "%s: expected an error", test.name) } else { - require.Nil(t, err) - require.NotNil(t, grpcSync.client) + require.Nilf(t, err, "%s: expected no error, but got non nil error", test.name) + require.NotNilf(t, grpcSync.client, "%s: expected client to be initialized", test.name) } } } @@ -191,65 +171,6 @@ func Test_ReSyncTests(t *testing.T) { } } -func TestSourceToGRPCTarget(t *testing.T) { - tests := []struct { - name string - url string - want string - ok bool - }{ - { - name: "With Prefix", - url: "grpc://test.com/endpoint", - want: "test.com/endpoint", - ok: true, - }, - { - name: "With secure Prefix", - url: "grpcs://test.com/endpoint", - want: "test.com/endpoint", - ok: true, - }, - { - name: "Empty is error", - url: "", - want: "", - ok: false, - }, - { - name: "Invalid is error", - url: "https://test.com/endpoint", - want: "", - ok: false, - }, - { - name: "Prefix is not enough I", - url: Prefix, - want: "", - ok: false, - }, - { - name: "Prefix is not enough II", - url: PrefixSecure, - want: "", - ok: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, ok := sourceToGRPCTarget(tt.url) - - if tt.ok != ok { - t.Errorf("URLToGRPCTarget() returned = %v, want %v", ok, tt.ok) - } - - if got != tt.want { - t.Errorf("URLToGRPCTarget() returned = %v, want %v", got, tt.want) - } - }) - } -} - func TestSync_BasicFlagSyncStates(t *testing.T) { grpcSyncImpl := Sync{ URI: "grpc://test", diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 70ec3060f..a30ad2d90 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -1,35 +1,43 @@ # Configuration -`flagd` supports configuration via config file, environment variables and flags. -In cases of conflict, flags have the -highest priority, followed by environment variables and finally config file. + +* [Configuration](#configuration) + * [Sync providers](#sync-providers) + * [Kubernetes provider](#kubernetes-provider) + * [Filepath provider](#filepath-provider) + * [Remote provider](#remote-provider) + * [GRPC provider](#grpc-provider) + * [Sync provider configurations](#sync-provider-configurations) + * [URI patterns](#uri-patterns) + * [Source Configuration](#source-configuration) + -Supported flags are documented (auto-generated) [here](./flagd_start.md). +`flagd` supports configuration via config file, environment variables and start-up flags. In cases of a conflict, +start-up flags have the highest priority, followed by environment variables and config file. -Environment variable keys are uppercased, prefixed with `FLAGD_` and all `-` are replaced with `_`. -For example, -`sync-provider-args` in environment variable form is `FLAGD_SYNC_PROVIDER_ARGS`. +Supported start-up flags are documented (auto-generated) [here](./flagd_start.md). -Config file expects the keys to have the exact naming as the flags. +Environment variable keys are uppercase, prefixed with `FLAGD_` and all `-` are replaced with `_`. For example, +`metrics-port` flag in environment variable form is `FLAGD_METRICS_PORT`. -## URI patterns +The config file expects the keys to have the exact naming as startup-flags flags. -Any URI passed to flagd via the `--uri` flag must follow one of the 4 following patterns to ensure that it is passed to the correct implementation: +## Sync providers -| Sync | Pattern | Example | -|------------|---------------------------------------|---------------------------------------| -| Kubernetes | `core.openfeature.dev/namespace/name` | `core.openfeature.dev/default/my-crd` | -| Filepath | `file:path/to/my/flag` | `file:etc/flagd/my-flags.json` | -| Remote | `http(s)://flag-source-url` | `https://my-flags.com/flags` | -| Grpc | `grpc(s)://flag-source-url` | `grpc://my-flags-server` | +Sync providers are a core part of flagd; they are the abstraction that enables different sources for feature flag configurations. +flagd currently support the following sync providers: -## Customising sync providers - -Custom sync providers can be used to provide flag evaluation logic. +* [Kubernetes provider](#kubernetes-provider) +* [Filepath Configuration](#filepath-provider) +* [Remote Configuration](#remote-provider) +* [GRPC Configuration](#grpc-provider) ### Kubernetes provider -The Kubernetes 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) spec. +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) +spec. To use an existing FeatureFlagConfiguration custom resource, start flagD with the following command: @@ -37,51 +45,122 @@ To use an existing FeatureFlagConfiguration custom resource, start flagD with th flagd start --uri core.openfeature.dev/default/my_example ``` -## Source Configuration +In this example, `default/my_example` expected to be a valid FeatureFlagConfiguration resource, where `default` is the +namespace and `my_example` being the resource name. + +### Filepath provider + +The file path sync provider reads and watch the source file for updates(ex:- changes and deletions). + +```shell +flagd start --uri file:etc/featureflags.json +``` + +In this example, `etc/featureflags.json` is a valid feature flag configuration file accessible by the flagd runtime. + +### Remote provider + +The HTTP sync provider fetch flags from a remote source and periodically poll the source for flag configuration updates. + +```shell +flagd start --uri https://my-flag-source.json +``` + +In this example, `https://my-flag-source.json` is a remote endpoint responding valid feature flag configurations when +invoked with **HTTP GET** request. + +### GRPC provider + +The GRPC sync provider stream flag configurations from a grpc sync provider implementation. This stream connection is ruled +by +the [sync service protobuf definition](https://github.com/open-feature/schemas/blob/main/protobuf/sync/v1/sync_service.proto). + +```shell +flagd start --uri grpc://grpc-sync-source +``` + +In this example, `grpc-sync-source` is a grpc target implementing flagd protobuf definition. + +There are two mechanisms to provide configurations of sync providers, + +* [URI patterns](#uri-patterns) +* [Source Configuration](#source-configuration) + +## Sync provider configurations + +### URI patterns + +Any URI passed to flagd via the `--uri` flag must follow one of the 4 following patterns with prefixes to ensure that +it is passed to the correct implementation: + +| Sync | Prefix | Example | +|------------|------------------------|---------------------------------------| +| Kubernetes | `core.openfeature.dev` | `core.openfeature.dev/default/my-crd` | +| Filepath | `file:` | `file:etc/flagd/my-flags.json` | +| Remote | `http(s)://` | `https://my-flags.com/flags` | +| Grpc | `grpc(s)://` | `grpc://my-flags-server` | + +### Source Configuration While a URI may be passed to flagd via the `--uri` flag, some implementations may require further configurations. In these cases the `--sources` flag should be used. -The flag takes a string argument, which should be a JSON representation of an array of `SourceConfig` objects. -Alternatively, these configurations should be passed to -flagd via config file, specified using the `--config` flag. -| Field | Type | Note | -|-------------|------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| -| uri | required `string` | | -| provider | required `string` (`file`, `kubernetes`, `http` or `grpc`) | | -| bearerToken | optional `string` | Used for http sync | -| providerID | optional `string` | Value binds to grpc connection's providerID field. GRPC server implementations may use this to identify connecting flagd instance | -| selector | optional `string` | Value binds to grpc connection's selector field. GRPC server implementations may use this to filter flag configurations | -| certPath | optional `string` | Used for grpcs sync when TLS certificate is needed. If not provided, system certificates will be used for TLS connection | +The flagd accepts a string argument, which should be a JSON representation of an array of `SourceConfig` objects. + +Alternatively, these configurations can be passed to flagd via config file, specified using the `--config` flag. + +| Field | Type | Note | +|-------------|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| uri | required `string` | Flag configuration source of the provider | +| provider | required `string` | Provider type - `file`, `kubernetes`, `http` or `grpc` | +| bearerToken | optional `string` | Used for http sync and token get appended to `Authorization` header with [bearer schema](https://www.rfc-editor.org/rfc/rfc6750#section-2.1) | +| tls | optional `boolean` | Enable/Disable secure TLS connectivity. Currently used only by GRPC sync. Default(ex:- if unset) is false, which will use an insecure connection | +| providerID | optional `string` | Value binds to grpc connection's providerID field. GRPC server implementations may use this to identify connecting flagd instance | +| selector | optional `string` | Value binds to grpc connection's selector field. GRPC server implementations may use this to filter flag configurations | +| certPath | optional `string` | Used for grpcs sync when TLS certificate is needed. If not provided, system certificates will be used for TLS connection | -The `uri` field values do not need to follow the [URI patterns](#uri-patterns), the provider type is instead derived from the provider field. -If the prefix is supplied, it will be removed on startup without error. +The `uri` field values **do not** follow the [URI patterns](#uri-patterns). The provider type is instead derived +from the `provider` field. Only exception is the remote provider where `http(s)://` is expected by default. Incorrect +URIs will result in a flagd start-up failure with errors from the respective sync provider implementation. -Example start command using a filepath sync provider and the equivalent config file definition: +Given below are example sync providers, startup command and equivalent config file definition: + +Sync providers, + +* `file` - config/samples/example_flags.json +* `http` - +* `kubernetes` - default/my-flag-config +* `grpc`(insecure) - grpc-source:8080 +* `grpc`(secure) - my-flag-source:8080 + +Startup command, ```sh -./bin/flagd start --sources='[{"uri":"config/samples/example_flags.json","provider":"file"},{"uri":"http://my-flag-source.json","provider":"http","bearerToken":"bearer-dji34ld2l"}]{"uri":"default/my-flag-config","provider":"kubernetes"},{"uri":"grpc://my-flag-source:8080","provider":"grpc"}' +./bin/flagd start +--sources='[{"uri":"config/samples/example_flags.json","provider":"file"}, + {"uri":"http://my-flag-source.json","provider":"http","bearerToken":"bearer-dji34ld2l"}, + {"uri":"default/my-flag-config","provider":"kubernetes"}, + {"uri":"grpc-source:8080","provider":"grpc"}, + {"uri":"my-flag-source:8080","provider":"grpc", "certPath": "/certs/ca.cert", "tls": true, "providerID": "flagd-weatherapp-sidecar", "selector": "source=database,app=weatherapp"}]' ``` +Configuration file, + ```yaml sources: -- uri: config/samples/example_flags.json - provider: file -- uri: http://my-flag-source.json - provider: http - bearerToken: bearer-dji34ld2l -- uri: default/my-flag-config - provider: kubernetes -- uri: http://my-flag-source.json - provider: kubernetes -- uri: grpc://my-flag-source:8080 - provider: grpc -- uri: grpcs://my-flag-source:8080 - provider: grpc - certPath: /certs/ca.cert -- uri: grpcs://my-flag-source:8080 - provider: grpc - certPath: /certs/ca.cert - providerID: flagd-weatherapp-sidecar - selector: 'source=database,app=weatherapp' + - uri: config/samples/example_flags.json + provider: file + - uri: http://my-flag-source.json + provider: http + bearerToken: bearer-dji34ld2l + - uri: default/my-flag-config + provider: kubernetes + - uri: my-flag-source:8080 + provider: grpc + - uri: my-flag-source:8080 + provider: grpc + certPath: /certs/ca.cert + tls: true + providerID: flagd-weatherapp-sidecar + selector: 'source=database,app=weatherapp' ```