Welcome to mirror list, hosted at ThFree Co, Russian Federation.

featureflag.go « featureflag « metadata « internal - gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 70f90d2d95e9675b8d8e31c2c4842ed9312d1575 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package featureflag

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"gitlab.com/gitlab-org/gitaly/v15/internal/helper/env"
	"google.golang.org/grpc/metadata"
)

var (
	// EnableAllFeatureFlagsEnvVar will cause Gitaly to treat all feature flags as
	// enabled in case its value is set to `true`. Only used for testing purposes.
	EnableAllFeatureFlagsEnvVar = "GITALY_TESTING_ENABLE_ALL_FEATURE_FLAGS"

	// featureFlagsOverride allows to enable all feature flags with a
	// single environment variable. If the value of
	// GITALY_TESTING_ENABLE_ALL_FEATURE_FLAGS is set to "true", then all
	// feature flags will be enabled. This is only used for testing
	// purposes such that we can run integration tests with feature flags.
	featureFlagsOverride, _ = env.GetBool(EnableAllFeatureFlagsEnvVar, false)

	flagChecks = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Name: "gitaly_feature_flag_checks_total",
			Help: "Number of enabled/disabled checks for Gitaly server side feature flags",
		},
		[]string{"flag", "enabled"},
	)

	// All is the list of all registered feature flags.
	All = []FeatureFlag{}
)

const explicitFeatureFlagKey = "require_explicit_feature_flag_checks"

func injectIntoIncomingAndOutgoingContext(ctx context.Context, key string, enabled bool) context.Context {
	incomingMD, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		incomingMD = metadata.New(map[string]string{})
	}

	incomingMD.Set(key, strconv.FormatBool(enabled))

	ctx = metadata.NewIncomingContext(ctx, incomingMD)

	return metadata.AppendToOutgoingContext(ctx, key, strconv.FormatBool(enabled))
}

// ContextWithExplicitFeatureFlags marks the context such that all feature flags which are checked
// must have been explicitly set in that context. If a feature flag wasn't set to an explicit value,
// then checking this feature flag will panic. This is not for use in production systems, but is
// intended for tests to verify that we test each feature flag properly.
func ContextWithExplicitFeatureFlags(ctx context.Context) context.Context {
	return injectIntoIncomingAndOutgoingContext(ctx, explicitFeatureFlagKey, true)
}

// FeatureFlag gates the implementation of new or changed functionality.
type FeatureFlag struct {
	// Name is the name of the feature flag.
	Name string `json:"name"`
	// OnByDefault is the default value if the feature flag is not explicitly set in
	// the incoming context.
	OnByDefault bool `json:"on_by_default"`
}

// NewFeatureFlag creates a new feature flag and adds it to the array of all existing feature flags.
// The name must be of the format `some_feature_flag`. Accepts a version and rollout issue URL as
// input that are not used for anything but only for the sake of linking to the feature flag rollout
// issue in the Gitaly project.
func NewFeatureFlag(name, version, rolloutIssueURL string, onByDefault bool) FeatureFlag {
	featureFlag := FeatureFlag{
		Name:        name,
		OnByDefault: onByDefault,
	}
	All = append(All, featureFlag)
	return featureFlag
}

// IsEnabled checks if the feature flag is enabled for the passed context.
// Only returns true if the metadata for the feature flag is set to "true"
func (ff FeatureFlag) IsEnabled(ctx context.Context) bool {
	if featureFlagsOverride {
		return true
	}

	val, ok := ff.valueFromContext(ctx)
	if !ok {
		if md, ok := metadata.FromIncomingContext(ctx); ok {
			if _, ok := md[explicitFeatureFlagKey]; ok {
				panic(fmt.Sprintf("checking for feature %q without use of feature sets", ff.Name))
			}
		}

		return ff.OnByDefault
	}

	enabled := val == "true"

	flagChecks.WithLabelValues(ff.Name, strconv.FormatBool(enabled)).Inc()

	return enabled
}

// IsDisabled determines whether the feature flag is disabled in the incoming context.
func (ff FeatureFlag) IsDisabled(ctx context.Context) bool {
	return !ff.IsEnabled(ctx)
}

// MetadataKey returns the key of the feature flag as it is present in the metadata map.
func (ff FeatureFlag) MetadataKey() string {
	return ffPrefix + strings.ReplaceAll(ff.Name, "_", "-")
}

func (ff FeatureFlag) valueFromContext(ctx context.Context) (string, bool) {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return "", false
	}

	val, ok := md[ff.MetadataKey()]
	if !ok {
		return "", false
	}

	if len(val) == 0 {
		return "", false
	}

	return val[0], true
}