Skip to content

Commit

Permalink
feat: aligned environment variables application with flagd provider s…
Browse files Browse the repository at this point in the history
…pec (#119)

Signed-off-by: Skye Gill <[email protected]>
  • Loading branch information
skyerus authored Feb 21, 2023
1 parent eedb577 commit 5ee1f2c
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 41 deletions.
25 changes: 12 additions & 13 deletions providers/flagd/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# Flagd Provider

![Experimental](https://img.shields.io/badge/experimental-breaking%20changes%20allowed-yellow)
![Alpha](https://img.shields.io/badge/alpha-release-red)

[Flagd](https://github.com/open-feature/flagd) is a simple command line tool for fetching and presenting feature flags to services. It is designed to conform to OpenFeature schema for flag definitions. This repository and package provides the client side code for interacting with it via the [OpenFeature SDK](https://github.com/open-feature/go-sdk).

## Setup
Expand Down Expand Up @@ -44,16 +41,18 @@ func main() {
```

### Using flagd.FromEnv()
By default the flagd provider will not read environment variables to set its own configuration, however, if the `flagd.FromEnv()` option is set as an argument for the `flagd.NewProvider()` method, then the following table of environment variables are applicable.

| Option name | Environment variable name | Type | Default |
|-----------------|---------------------------| ------- |-----------|
| host | FLAGD_HOST | string | localhost |
| port | FLAGD_PORT | number | 8013 |
| tls | FLAGD_TLS | boolean | false |
| socketPath | FLAGD_SOCKET_PATH | string | |
| certPath | FLAGD_SERVER_CERT_PATH | string | |
| cachingDisabled | FLAGD_CACHING_DISABLED | boolean | false |
By default the flagd provider will read non-empty environment variables to set its own configuration with the lowest priority. Use the `flagd.FromEnv()` option as an argument for the `flagd.NewProvider()` method to give environment variables a higher priority.

| Option name | Environment variable name | Type | Options | Default |
|-----------------------|--------------------------------|-----------|--------------|-----------|
| host | FLAGD_HOST | string | | localhost |
| port | FLAGD_PORT | number | | 8013 |
| tls | FLAGD_TLS | boolean | | false |
| socketPath | FLAGD_SOCKET_PATH | string | | |
| certPath | FLAGD_SERVER_CERT_PATH | string | | |
| cache | FLAGD_CACHE | string | lru,disabled | lru |
| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | | 1000 |
| maxEventStreamRetries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | | 5 |

In the event that another configuration option is passed to the `flagd.NewProvider()` method, such as `flagd.WithPort(8013)` then priority is decided by the order in which the options are passed to the constructor from lowest to highest priority.

Expand Down
117 changes: 89 additions & 28 deletions providers/flagd/pkg/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
schemaV1 "buf.build/gen/go/open-feature/flagd/protocolbuffers/go/schema/v1"
"context"
"errors"
"fmt"
"github.com/go-logr/logr"
lru "github.com/hashicorp/golang-lru/v2"
flagdModels "github.com/open-feature/flagd/pkg/model"
Expand All @@ -17,21 +18,32 @@ import (
"strconv"
)

// naming and defaults follow https://github.com/open-feature/flagd/blob/main/docs/other_resources/creating_providers.md?plain=1#L117
const (
defaultLRUCacheSize int = 1000
flagdHostEnvironmentVariableName = "FLAGD_HOST"
flagdPortEnvironmentVariableName = "FLAGD_PORT"
flagdTLSEnvironmentVariableName = "FLAGD_TLS"
flagdSocketPathEnvironmentVariableName = "FLAGD_SOCKET_PATH"
flagdServerCertPathEnvironmentVariableName = "FLAGD_SERVER_CERT_PATH"
flagdCachingDisabledEnvironmentVariableName = "FLAGD_CACHING_DISABLED"
defaultMaxCacheSize int = 1000
defaultPort = 8013
defaultMaxEventStreamRetries = 5
defaultTLS bool = false
defaultCache string = "lru"
defaultHost = "localhost"
flagdHostEnvironmentVariableName = "FLAGD_HOST"
flagdPortEnvironmentVariableName = "FLAGD_PORT"
flagdTLSEnvironmentVariableName = "FLAGD_TLS"
flagdSocketPathEnvironmentVariableName = "FLAGD_SOCKET_PATH"
flagdServerCertPathEnvironmentVariableName = "FLAGD_SERVER_CERT_PATH"
flagdCacheEnvironmentVariableName = "FLAGD_CACHE"
flagdMaxCacheSizeEnvironmentVariableName = "FLAGD_MAX_CACHE_SIZE"
flagdMaxEventStreamRetriesEnvironmentVariableName = "FLAGD_MAX_EVENT_STREAM_RETRIES"
cacheDisabledValue = "disabled"
cacheLRUValue = "lru"
)

type Provider struct {
ctx context.Context
service service.IService
cacheEnabled bool
cache Cache[string, interface{}]
maxCacheSize int
providerConfiguration *ProviderConfiguration
eventStreamConnectionMaxAttempts int
isReady chan struct{}
Expand All @@ -52,16 +64,15 @@ func NewProvider(opts ...ProviderOption) *Provider {
ctx: context.Background(),
// providerConfiguration maintains its default values, to ensure that the FromEnv option does not overwrite any explicitly set
// values (default values are then set after the options are run via applyDefaults())
providerConfiguration: &ProviderConfiguration{},
eventStreamConnectionMaxAttempts: 5,
isReady: make(chan struct{}),
logger: logr.New(logger.Logger{}),
providerConfiguration: &ProviderConfiguration{},
isReady: make(chan struct{}),
logger: logr.New(logger.Logger{}),
}
WithLRUCache(defaultLRUCacheSize)(provider)
for _, opt := range opts {
provider.applyDefaults() // defaults have the lowest priority
FromEnv()(provider) // env variables have higher priority than defaults
for _, opt := range opts { // explicitly declared options have the highest priority
opt(provider)
}
provider.applyDefaults()
provider.service = service.NewService(&service.Client{
ServiceConfiguration: &service.ServiceConfiguration{
Host: provider.providerConfiguration.Host,
Expand All @@ -82,12 +93,12 @@ func NewProvider(opts ...ProviderOption) *Provider {
}

func (p *Provider) applyDefaults() {
if p.providerConfiguration.Host == "" {
p.providerConfiguration.Host = "localhost"
}
if p.providerConfiguration.Port == 0 {
p.providerConfiguration.Port = 8013
}
p.providerConfiguration.Host = defaultHost
p.providerConfiguration.Port = defaultPort
p.providerConfiguration.TLSEnabled = defaultTLS
p.eventStreamConnectionMaxAttempts = defaultMaxEventStreamRetries
p.maxCacheSize = defaultMaxCacheSize
p.withCache(defaultCache)
}

// WithSocketPath overrides the default hostname and port, a unix socket connection is made to flagd instead
Expand All @@ -97,14 +108,18 @@ func WithSocketPath(socketPath string) ProviderOption {
}
}

// FromEnv sets the provider configuration from environment variables: FLAGD_HOST, FLAGD_PORT, FLAGD_SERVICE_PROVIDER, FLAGD_SERVER_CERT_PATH & FLAGD_CACHING_DISABLED
// FromEnv sets the provider configuration from environment variables (if set) as defined https://github.com/open-feature/flagd/blob/main/docs/other_resources/creating_providers.md?plain=1#L117
func FromEnv() ProviderOption {
return func(p *Provider) {
portS := os.Getenv(flagdPortEnvironmentVariableName)
if portS != "" {
port, err := strconv.Atoi(portS)
if err != nil {
p.logger.Error(err, "invalid env config for FLAGD_PORT provided, using default value")
p.logger.Error(err,
fmt.Sprintf(
"invalid env config for %s provided, using default value: %d",
flagdPortEnvironmentVariableName, defaultPort,
))
} else {
p.providerConfiguration.Port = uint16(port)
}
Expand All @@ -120,22 +135,65 @@ func FromEnv() ProviderOption {
p.providerConfiguration.SocketPath = socketPath
}

cachingDisabled := os.Getenv(flagdCachingDisabledEnvironmentVariableName)
if cachingDisabled == "true" {
WithoutCache()(p)
}

certificatePath := os.Getenv(flagdServerCertPathEnvironmentVariableName)
if certificatePath != "" || os.Getenv(flagdTLSEnvironmentVariableName) == "true" {
WithTLS(certificatePath)(p)
}

maxCacheSizeS := os.Getenv(flagdMaxCacheSizeEnvironmentVariableName)
if maxCacheSizeS != "" {
maxCacheSizeFromEnv, err := strconv.Atoi(maxCacheSizeS)
if err != nil {
p.logger.Error(err,
fmt.Sprintf("invalid env config for %s provided, using default value: %d",
flagdMaxCacheSizeEnvironmentVariableName, defaultMaxCacheSize,
))
} else {
p.maxCacheSize = maxCacheSizeFromEnv
}
}

if cacheValue := os.Getenv(flagdCacheEnvironmentVariableName); cacheValue != "" {
if ok := p.withCache(cacheValue); !ok {
p.logger.Error(fmt.Errorf("%s is invalid", cacheValue),
fmt.Sprintf("invalid env config for %s provided, using default value: %s",
flagdCacheEnvironmentVariableName, defaultCache,
))
}
}

maxEventStreamRetriesS := os.Getenv(flagdMaxEventStreamRetriesEnvironmentVariableName)
if maxEventStreamRetriesS != "" {
maxEventStreamRetries, err := strconv.Atoi(maxEventStreamRetriesS)
if err != nil {
p.logger.Error(err,
fmt.Sprintf("invalid env config for %s provided, using default value: %d",
flagdMaxEventStreamRetriesEnvironmentVariableName, defaultMaxEventStreamRetries))
} else {
p.eventStreamConnectionMaxAttempts = maxEventStreamRetries
}
}
}
}

func (p *Provider) withCache(cache string) bool {
switch cache {
case cacheDisabledValue:
WithoutCache()(p)
case cacheLRUValue:
WithLRUCache(p.maxCacheSize)
default:
return false
}

return true
}

// WithCertificatePath specifies the location of the certificate to be used in the gRPC dial credentials. If certificate loading fails insecure credentials will be used instead
func WithCertificatePath(path string) ProviderOption {
return func(p *Provider) {
p.providerConfiguration.CertificatePath = path
p.providerConfiguration.TLSEnabled = true
}
}

Expand Down Expand Up @@ -175,7 +233,10 @@ func WithBasicInMemoryCache() ProviderOption {
// least recently used entry.
func WithLRUCache(size int) ProviderOption {
return func(p *Provider) {
c, err := lru.New[string, interface{}](size)
if size != 0 {
p.maxCacheSize = size
}
c, err := lru.New[string, interface{}](p.maxCacheSize)
if err != nil {
p.logger.Error(err, "init lru cache")
return
Expand Down

0 comments on commit 5ee1f2c

Please sign in to comment.