From 116f97f061fddc90bf5d479f7fd2bb77ea0fc73b Mon Sep 17 00:00:00 2001 From: jose-fully-ported <141160579+jose-fully-ported@users.noreply.github.com> Date: Thu, 14 Sep 2023 16:49:52 -0400 Subject: [PATCH] POR-1503: allow fallback to the database for feature flags (#3575) --- api/server/shared/config/env/envconfs.go | 2 + api/server/shared/config/loader/loader.go | 2 +- cmd/migrate/main.go | 2 +- internal/features/launch_darkly.go | 48 ++++++++++++++++++++--- internal/models/project.go | 32 +++++++++++++++ provisioner/server/config/config.go | 5 ++- zarf/helm/.serverenv | 1 + 7 files changed, 83 insertions(+), 9 deletions(-) diff --git a/api/server/shared/config/env/envconfs.go b/api/server/shared/config/env/envconfs.go index c9837dd0d5..ad3bff0c97 100644 --- a/api/server/shared/config/env/envconfs.go +++ b/api/server/shared/config/env/envconfs.go @@ -55,6 +55,8 @@ type ServerConf struct { GoogleClientSecret string `env:"GOOGLE_CLIENT_SECRET"` GoogleRestrictedDomain string `env:"GOOGLE_RESTRICTED_DOMAIN"` + // FeatureFlagClient controls which client to use (database or launch_darkly) + FeatureFlagClient string `env:"FEATURE_FLAG_CLIENT,default=launch_darkly"` LaunchDarklySDKKey string `env:"LAUNCHDARKLY_SDK_KEY"` SendgridAPIKey string `env:"SENDGRID_API_KEY"` diff --git a/api/server/shared/config/loader/loader.go b/api/server/shared/config/loader/loader.go index da0e1bb830..25a634019c 100644 --- a/api/server/shared/config/loader/loader.go +++ b/api/server/shared/config/loader/loader.go @@ -244,7 +244,7 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) { sc.GithubAppSecret = append(sc.GithubAppSecret, secret...) } - launchDarklyClient, err := features.GetClient(envConf.ServerConf.LaunchDarklySDKKey) + launchDarklyClient, err := features.GetClient(envConf.ServerConf.FeatureFlagClient, envConf.ServerConf.LaunchDarklySDKKey) if err != nil { return nil, fmt.Errorf("could not create launch darkly client: %s", err) } diff --git a/cmd/migrate/main.go b/cmd/migrate/main.go index 3f189ed084..2d52e4dfff 100644 --- a/cmd/migrate/main.go +++ b/cmd/migrate/main.go @@ -30,7 +30,7 @@ func main() { return } - launchDarklyClient, err := features.GetClient(envConf.ServerConf.LaunchDarklySDKKey) + launchDarklyClient, err := features.GetClient(envConf.ServerConf.FeatureFlagClient, envConf.ServerConf.LaunchDarklySDKKey) if err != nil { logger.Fatal().Err(err).Msg("could not load launch darkly client") return diff --git a/internal/features/launch_darkly.go b/internal/features/launch_darkly.go index 2dbc59089c..64cbe0411c 100644 --- a/internal/features/launch_darkly.go +++ b/internal/features/launch_darkly.go @@ -11,7 +11,8 @@ import ( // Client is a struct wrapper around the launchdarkly client type Client struct { - Client LDClient + Client LDClient + useDatabase bool } // LDClient is an interface that allows us to mock @@ -33,20 +34,55 @@ func (c Client) BoolVariation(field string, context ldcontext.Context, defaultVa return c.Client.BoolVariation(field, context, defaultValue) } +// UseDatabase returns whether we should force using the database for feature flags +// +// The initial implementation of feature flags stored flags in +// table-specific columns, making it so we need to use a nasty hack to +// fetch the correct value for on-prem deployments. A proper refactor would +// be to introduce a migration that moved these flags to a feature flags +// table that we could implement a proper WrappedClient for, but a shortcut +// is taken here in order to fix this sooner and give us time for a proper +// refactor. +func (c Client) UseDatabase() bool { + return c.useDatabase +} + // GetClient retrieves a Client for interacting with LaunchDarkly -func GetClient(launchDarklySDKKey string) (*Client, error) { +func GetClient(featureFlagClient string, launchDarklySDKKey string) (*Client, error) { + validClients := map[string]bool{ + "database": true, + "launch_darkly": true, + } + + if !validClients[featureFlagClient] { + return &Client{}, fmt.Errorf("failed to create new feature flag client: invalid feature flag client specified") + } + + if featureFlagClient == "database" { + return &Client{ + useDatabase: true, + }, nil + } + + if launchDarklySDKKey == "" { + return &Client{}, fmt.Errorf("failed to create new feature flag client: missing launch_darkly sdk key") + } + ldClient, err := ld.MakeClient(launchDarklySDKKey, 5*time.Second) if err != nil { - return &Client{}, fmt.Errorf("failed to create new launchdarkly client: %w", err) + return &Client{}, fmt.Errorf("failed to create new feature flag client: %w", err) } if ldClient == nil { - return &Client{}, errors.New("failed to create new launchdarkly client: invalid config") + return &Client{}, errors.New("failed to create new feature flag client: invalid config") } if !ldClient.Initialized() { - return &Client{}, errors.New("failed to create new launchdarkly client: sdk failed to initialize") + return &Client{}, errors.New("failed to create new feature flag client: sdk failed to initialize") } - return &Client{ldClient}, nil + return &Client{ + Client: ldClient, + useDatabase: false, + }, nil } diff --git a/internal/models/project.go b/internal/models/project.go index 334c7bbe3b..59a70a4abf 100644 --- a/internal/models/project.go +++ b/internal/models/project.go @@ -168,6 +168,38 @@ type Project struct { // GetFeatureFlag calls launchdarkly for the specified flag // and returns the configured value func (p *Project) GetFeatureFlag(flagName FeatureFlagLabel, launchDarklyClient *features.Client) bool { + if launchDarklyClient.UseDatabase() { + // case switch things + switch flagName { + case "api_tokens_enabled": + return p.APITokensEnabled + case "azure_enabled": + return p.AzureEnabled + case "capi_provisioner_enabled": + return p.CapiProvisionerEnabled + case "enable_reprovision": + return p.EnableReprovision + case "full_add_ons": + return p.FullAddOns + case "helm_values_enabled": + return p.HelmValuesEnabled + case "managed_infra_enabled": + return p.ManagedInfraEnabled + case "multi_cluster": + return p.MultiCluster + case "preview_envs_enabled": + return p.PreviewEnvsEnabled + case "rds_databases_enabled": + return p.RDSDatabasesEnabled + case "simplified_view_enabled": + return p.SimplifiedViewEnabled + case "stacks_enabled": + return p.StacksEnabled + case "validate_apply_v2": + return p.ValidateApplyV2 + } + } + projectID := p.ID projectName := p.Name ldContext := getProjectContext(projectID, projectName) diff --git a/provisioner/server/config/config.go b/provisioner/server/config/config.go index 6a79fd24b0..b20cce3ae4 100644 --- a/provisioner/server/config/config.go +++ b/provisioner/server/config/config.go @@ -126,6 +126,9 @@ type ProvisionerConf struct { // Client key for segment to report provisioning events SegmentClientKey string `env:"SEGMENT_CLIENT_KEY"` + // FeatureFlagClient controls which client to use (database or launch_darkly) + FeatureFlagClient string `env:"FEATURE_FLAG_CLIENT,default=launch_darkly"` + // Launch Darkly SDK key LaunchDarklySDKKey string `env:"LAUNCHDARKLY_SDK_KEY"` } @@ -172,7 +175,7 @@ func GetConfig(envConf *EnvConf) (*Config, error) { res.Repo = gorm.NewRepository(db, &key, InstanceCredentialBackend) - launchDarklyClient, err := features.GetClient(envConf.LaunchDarklySDKKey) + launchDarklyClient, err := features.GetClient(envConf.FeatureFlagClient, envConf.LaunchDarklySDKKey) if err != nil { return nil, fmt.Errorf("could not create launch darkly client: %s", err) } diff --git a/zarf/helm/.serverenv b/zarf/helm/.serverenv index f5f7276520..22a7af2148 100644 --- a/zarf/helm/.serverenv +++ b/zarf/helm/.serverenv @@ -34,6 +34,7 @@ GITHUB_APP_SECRET_PATH= # LAUNCHDARKLY_SDK_KEY is used to interact with the launchdarkly api # Retrieve your SDK key from https://app.launchdarkly.com/settings/projects/dev/environments +FEATURE_FLAG_CLIENT=launch_darkly LAUNCHDARKLY_SDK_KEY= # Optional parameters