diff options
author | Patrick Steinhardt <psteinhardt@gitlab.com> | 2022-06-30 15:16:12 +0300 |
---|---|---|
committer | Patrick Steinhardt <psteinhardt@gitlab.com> | 2022-07-06 09:12:53 +0300 |
commit | d460daf3ab1a1b11587a362e5b796d13b57947b1 (patch) | |
tree | d61d68741f01ae3bc4e679dd8c7e3b398f3f14ca | |
parent | dc6452d0da11be7750572bea9515b247d1659ffd (diff) |
featureflag: Add constructor from metadata key
Extract the logic to construct a feature flag from its gRPC metadata key
from `FromContext()` into a new `FromMetadataKey()` function. This will
be used at a later point.
-rw-r--r-- | internal/metadata/featureflag/context.go | 19 | ||||
-rw-r--r-- | internal/metadata/featureflag/featureflag.go | 29 | ||||
-rw-r--r-- | internal/metadata/featureflag/featureflag_test.go | 62 |
3 files changed, 94 insertions, 16 deletions
diff --git a/internal/metadata/featureflag/context.go b/internal/metadata/featureflag/context.go index ca9b40cb2..25f269d60 100644 --- a/internal/metadata/featureflag/context.go +++ b/internal/metadata/featureflag/context.go @@ -10,9 +10,6 @@ import ( ) const ( - // ffPrefix is the prefix used for Gitaly-scoped feature flags. - ffPrefix = "gitaly-feature-" - // explicitFeatureFlagKey is used by ContextWithExplicitFeatureFlags to mark a context as // requiring all feature flags to have been explicitly defined. explicitFeatureFlagKey = "require_explicit_feature_flag_checks" @@ -99,19 +96,9 @@ func FromContext(ctx context.Context) map[FeatureFlag]bool { flags := map[FeatureFlag]bool{} for rawName, value := range rawFlags { - flagName := strings.TrimPrefix(rawName, ffPrefix) - flagName = strings.ReplaceAll(flagName, "-", "_") - - // Try to look up the feature flag definition. If we don't know this flag, we - // instead return a manually constructed feature flag that we pretend to be off by - // default. We cannot ignore any unknown feature flags though given that we may - // want to pass them down to other systems that may know the definition. - flag, ok := flagsByName[flagName] - if !ok { - flag = FeatureFlag{ - Name: flagName, - OnByDefault: false, - } + flag, err := FromMetadataKey(rawName) + if err != nil { + continue } flags[flag] = value == "true" diff --git a/internal/metadata/featureflag/featureflag.go b/internal/metadata/featureflag/featureflag.go index 60d0d0530..e2c91568f 100644 --- a/internal/metadata/featureflag/featureflag.go +++ b/internal/metadata/featureflag/featureflag.go @@ -36,6 +36,11 @@ var ( flagsByName = map[string]FeatureFlag{} ) +const ( + // ffPrefix is the prefix used for Gitaly-scoped feature flags. + ffPrefix = "gitaly-feature-" +) + // DefinedFlags returns the set of feature flags that have been explicitly defined. func DefinedFlags() []FeatureFlag { flags := make([]FeatureFlag, 0, len(flagsByName)) @@ -79,6 +84,30 @@ func NewFeatureFlag(name, version, rolloutIssueURL string, onByDefault bool) Fea return featureFlag } +// FromMetadataKey parses the given gRPC metadata key into a Gitaly feature flag and performs the +// necessary conversions. Returns an error in case the metadata does not refer to a feature flag. +// +// This function tries to look up the default value via our set of flag definitions. In case the +// flag definition is unknown to Gitaly it assumes a default value of `false`. +func FromMetadataKey(metadataKey string) (FeatureFlag, error) { + if !strings.HasPrefix(metadataKey, ffPrefix) { + return FeatureFlag{}, fmt.Errorf("not a feature flag: %q", metadataKey) + } + + flagName := strings.TrimPrefix(metadataKey, ffPrefix) + flagName = strings.ReplaceAll(flagName, "-", "_") + + flag, ok := flagsByName[flagName] + if !ok { + flag = FeatureFlag{ + Name: flagName, + OnByDefault: false, + } + } + + return flag, nil +} + // FormatWithValue converts the feature flag into a string with the given state. Note that this // function uses the feature flag name and not the raw metadata key as used in gRPC metadata. func (ff FeatureFlag) FormatWithValue(enabled bool) string { diff --git a/internal/metadata/featureflag/featureflag_test.go b/internal/metadata/featureflag/featureflag_test.go index 8944e21fd..7a5d5915b 100644 --- a/internal/metadata/featureflag/featureflag_test.go +++ b/internal/metadata/featureflag/featureflag_test.go @@ -1,12 +1,74 @@ package featureflag import ( + "fmt" "testing" "github.com/stretchr/testify/require" "google.golang.org/grpc/metadata" ) +func TestFeatureFlag_FromMetadataKey(t *testing.T) { + defer func(old map[string]FeatureFlag) { + flagsByName = old + }(flagsByName) + + defaultEnabled := FeatureFlag{ + Name: "default_enabled", + OnByDefault: true, + } + defaultDisabled := FeatureFlag{ + Name: "default_disabled", + OnByDefault: false, + } + + flagsByName = map[string]FeatureFlag{ + defaultEnabled.Name: defaultEnabled, + defaultDisabled.Name: defaultDisabled, + } + + for _, tc := range []struct { + desc string + metadataKey string + expectedErr error + expectedFlag FeatureFlag + }{ + { + desc: "empty key", + metadataKey: "", + expectedErr: fmt.Errorf("not a feature flag: \"\""), + }, + { + desc: "invalid prefix", + metadataKey: "invalid-prefix", + expectedErr: fmt.Errorf("not a feature flag: \"invalid-prefix\""), + }, + { + desc: "default enabled flag", + metadataKey: defaultEnabled.MetadataKey(), + expectedFlag: defaultEnabled, + }, + { + desc: "default disabled flag", + metadataKey: defaultDisabled.MetadataKey(), + expectedFlag: defaultDisabled, + }, + { + desc: "undefined flag", + metadataKey: "gitaly-feature-not-defined", + expectedFlag: FeatureFlag{ + Name: "not_defined", + }, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + flag, err := FromMetadataKey(tc.metadataKey) + require.Equal(t, tc.expectedErr, err) + require.Equal(t, tc.expectedFlag, flag) + }) + } +} + func TestFeatureFlag_enabled(t *testing.T) { for _, tc := range []struct { desc string |