@@ -24,7 +24,6 @@ import (
2424 "sort"
2525 "strconv"
2626 "strings"
27- "sync"
2827
2928 "github.com/spf13/pflag"
3029 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -36,7 +35,8 @@ import (
3635// GateSet is a list of feature gates. All Gates in a GateSet must have unique
3736// names.
3837type GateSet struct {
39- gateMap map [string ]* Gate
38+ gateMap map [string ]* Gate
39+ providers []GateProvider
4040}
4141
4242// Add adds the specified Gate to the GateSet.
@@ -54,16 +54,23 @@ func (gs *GateSet) Add(gate *Gate) *Gate {
5454 //FIXME: what should we do?
5555 }
5656 gs .gateMap [gate .Name ] = gate
57+ gate .set = gs
5758 return gate
5859}
5960
60- // Merge adds all of the Gates in other to this GateSet.
61+ // Merge moves all of the Gates in other to this GateSet.
6162func (gs * GateSet ) Merge (other * GateSet ) {
6263 for _ , v := range other .gateMap {
6364 gs .Add (v )
65+ //FIXME: remove from other?
6466 }
6567}
6668
69+ type GateProvider interface {
70+ //FIXME: comments, (val, found) return
71+ Get (name string ) (bool , bool )
72+ }
73+
6774const (
6875 // allAlphaGate is a global toggle for alpha features. Per-feature key
6976 // values override the default set by allAlphaGate. Examples:
@@ -78,12 +85,11 @@ const (
7885 allBetaGate = "AllBeta"
7986)
8087
81- // AddFlags adds gate-related flags to the specified FlagSet.
82- // FIXME: how are gates exposed today?
83- func (gs * GateSet ) AddFlags (flagName string , fs * pflag.FlagSet ) {
84- for _ , gate := range gs .gateMap {
85- gate .readEnvVar ()
86- }
88+ // FIXME: comments
89+ // FIXME: We could move all the flag and env stuff out of this lib, and make
90+ // FIXME: callers do it, but it's considerably less useful.
91+ // FIXME: merge with existing gates flag and config
92+ func (gs * GateSet ) EnablePFlagControl (flagName string , fs * pflag.FlagSet ) {
8793 helpStrings := []string {
8894 fmt .Sprintf ("%s=true|false (%s - default=%t)" , allAlphaGate , Alpha , false ),
8995 fmt .Sprintf ("%s=true|false (%s - default=%t)" , allBetaGate , Beta , false ),
@@ -94,14 +100,31 @@ func (gs *GateSet) AddFlags(flagName string, fs *pflag.FlagSet) {
94100 sort .Strings (helpStrings )
95101
96102 //FIXME: nicer to have a --list-feature-gates flag?
97- fs .Var (& flagValue {gs }, flagName , "" +
103+ fv := & flagValue {gates : gs }
104+ fs .Var (fv , flagName , "" +
98105 "A set of key=value pairs that describe feature gates for alpha/experimental features. " +
99106 "Options are:\n " + strings .Join (helpStrings , "\n " ))
107+
108+ gs .providers = append (gs .providers , fv )
100109}
101110
102- // flagValue implements pflag.Value in terms of a GateSet .
111+ // flagValue implements pflag.Value and GateProvider .
103112type flagValue struct {
104- gates * GateSet
113+ gates * GateSet
114+ values map [string ]bool
115+ }
116+
117+ // Implement GateProvider.
118+ func (fv * flagValue ) Get (name string ) (bool , bool ) {
119+ val , found := fv .values [name ]
120+ return val , found
121+ }
122+
123+ func (fv * flagValue ) setOne (k string , v bool ) {
124+ if fv .values == nil {
125+ fv .values = map [string ]bool {}
126+ }
127+ fv .values [k ] = v
105128}
106129
107130// Set parses a string of the form "key1=value1,key2=value2,..." into a
@@ -124,43 +147,42 @@ func (fv *flagValue) Set(value string) error {
124147 }
125148 m [k ] = boolValue
126149 }
127- return fv .setFromMap (m )
128- }
129150
130- func (fv * flagValue ) setFromMap (m map [string ]bool ) error {
131151 for name , enabled := range m {
132152 switch {
133153 case name == allAlphaGate :
134154 for _ , gate := range fv .gates .gateMap {
135155 if gate .Release == Alpha {
136- gate . Set ( enabled )
156+ fv . setOne ( gate . Name , enabled )
137157 }
138158 }
139159 continue
140160 case name == allBetaGate :
141161 for _ , gate := range fv .gates .gateMap {
142162 if gate .Release == Beta {
143- gate . Set ( enabled )
163+ fv . setOne ( gate . Name , enabled )
144164 }
145165 }
146166 continue
147167 }
148168
169+ // FIXME: do we need this? Env won't fail with error unless we scan all vars
149170 gate , exists := fv .gates .gateMap [name ]
150171 if ! exists {
151172 return fmt .Errorf ("unrecognized feature gate: %q" , name )
152173 }
153174 if gate .LockToDefault && gate .Default != enabled {
154175 return fmt .Errorf ("cannot set feature gate %q to %t, feature is locked to %t" , name , enabled , gate .Default )
155176 }
156- gate . Set ( enabled )
177+ fv . setOne ( gate . Name , enabled )
157178 }
158179
159180 return nil
160181}
161182
162183// String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,...".
163184func (fv * flagValue ) String () string {
185+ //FIXME: should this list all gates or just ones that are set?
164186 pairs := []string {}
165187 for _ , gate := range fv .gates .gateMap {
166188 pairs = append (pairs , fmt .Sprintf ("%s=%t" , gate .Name , Enabled (gate )))
@@ -176,14 +198,76 @@ func (fv *flagValue) Type() string {
176198 return "mapStringBool"
177199}
178200
201+ func (gs * GateSet ) EnableEnvControl (prefix string ) {
202+ //FIXME: make sure all env vars with this prefix are known gates?
203+ gs .providers = append (gs .providers , & envProvider {prefix : prefix })
204+ }
205+
206+ // envProvider implements GateProvider.
207+ type envProvider struct {
208+ prefix string
209+ values map [string ]bool
210+ }
211+
212+ // Implement GateProvider.
213+ func (ep * envProvider ) Get (name string ) (bool , bool ) {
214+ if ep .values == nil {
215+ ep .values = map [string ]bool {}
216+ }
217+ key := fmt .Sprintf ("%s%s" , ep .prefix , name )
218+ strVal , found := os .LookupEnv (key )
219+ if ! found {
220+ return false , false
221+ }
222+
223+ boolVal , err := strconv .ParseBool (strVal )
224+ if err != nil {
225+ //FIXME: blech
226+ utilruntime .HandleError (fmt .Errorf ("cannot set feature gate %q to %q: %v" , key , strVal , err ))
227+ }
228+ /* FIXME: check if gate is allowed to be overridden?
229+ if g.LockToDefault {
230+ if boolVal != g.Default {
231+ utilruntime.HandleError(fmt.Errorf("cannot set feature gate %q to %q: feature is locked to %t", feature, strVal, g.Default))
232+ }
233+ }
234+ */
235+ return boolVal , true
236+ }
237+
238+ // FIXME: comments
239+ func (gs * GateSet ) EnableExternalControl (provider GateProvider ) {
240+ gs .providers = append (gs .providers , provider )
241+ }
242+
243+ // FIXME: comments
244+ func (gs * GateSet ) Enabled (name string ) (enabled bool , found bool ) {
245+ foundAny := false
246+ for _ , provider := range gs .providers {
247+ en , found := provider .Get (name )
248+ if ! found {
249+ continue
250+ }
251+ foundAny = true
252+ if en {
253+ return true , true
254+ }
255+ }
256+ if foundAny {
257+ // We didn't early-return so we must have found a `false`
258+ return false , true
259+ }
260+ return false , false
261+ }
262+
179263// Gate is a single feature gate definition.
180264type Gate struct {
181- Name string
182- Default bool
183- Release StabilityLevel
184- LockToDefault bool
185- enabled * bool
186- readEnvVarsOnce sync. Once
265+ Name string
266+ Default bool
267+ Release StabilityLevel
268+ LockToDefault bool
269+ forcedValue * bool
270+ set * GateSet
187271}
188272
189273// StabilityLevel indicates which phase of the feature lifecycle a given gate is
@@ -198,30 +282,9 @@ const (
198282)
199283
200284// Set sets the value of this gate.
285+ // FIXME: comments regardless of other gate config sources
201286func (g * Gate ) Set (val bool ) {
202- g .enabled = & val
203- }
204-
205- // readEnvVar looks for an environment variable for this gate and (if found)
206-
207- func (g * Gate ) readEnvVar () {
208- g .readEnvVarsOnce .Do (func () {
209- feature := g .Name
210- strVal , found := os .LookupEnv (fmt .Sprintf ("KUBE_FEATURE_%s" , feature ))
211- if ! found {
212- return
213- }
214-
215- if boolVal , err := strconv .ParseBool (strVal ); err != nil {
216- utilruntime .HandleError (fmt .Errorf ("cannot set feature gate %q to %q: %v" , feature , strVal , err ))
217- } else if g .LockToDefault {
218- if boolVal != g .Default {
219- utilruntime .HandleError (fmt .Errorf ("cannot set feature gate %q to %q: feature is locked to %t" , feature , strVal , g .Default ))
220- }
221- } else {
222- g .Set (boolVal )
223- }
224- })
287+ g .forcedValue = & val
225288}
226289
227290// MergeGateSets combines a list of GateSets into a new GateSet.
@@ -236,11 +299,21 @@ func MergeGateSets(gatesets ...*GateSet) *GateSet {
236299// FIXME: This could be a function here or a method on Gate, but let's not do both?
237300// Enabled returns whether the specified Gate is enabled.
238301func Enabled (g * Gate ) bool {
239- g .readEnvVar ()
240- if g .enabled == nil {
302+ // If some code called Set(), we always respect that.
303+ if g .forcedValue != nil {
304+ return * (g .forcedValue )
305+ }
306+ // If the value is locked or this gate is not in a set, use the default.
307+ if g .LockToDefault || g .set == nil {
241308 return g .Default
242309 }
243- return * g .enabled
310+ // Otherwise let the set's configured providers have an opinion.
311+ en , found := g .set .Enabled (g .Name )
312+ if found {
313+ return en
314+ }
315+ // Otherwise, use the default.
316+ return g .Default
244317}
245318
246319// testHelper is the locally-used subset of testing.T needed in SetForTesting.
@@ -254,10 +327,10 @@ type testHelper interface {
254327func SetForTesting (g * Gate , t testHelper , enabled bool ) func () {
255328 t .Helper ()
256329
257- prev := g .enabled
258- g .enabled = & enabled
330+ prev := g .forcedValue
331+ g .forcedValue = & enabled
259332
260333 return func () {
261- g .enabled = prev
334+ g .forcedValue = prev
262335 }
263336}
0 commit comments