@@ -24,7 +24,6 @@ import (
24
24
"sort"
25
25
"strconv"
26
26
"strings"
27
- "sync"
28
27
29
28
"github.com/spf13/pflag"
30
29
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -36,7 +35,8 @@ import (
36
35
// GateSet is a list of feature gates. All Gates in a GateSet must have unique
37
36
// names.
38
37
type GateSet struct {
39
- gateMap map [string ]* Gate
38
+ gateMap map [string ]* Gate
39
+ providers []GateProvider
40
40
}
41
41
42
42
// Add adds the specified Gate to the GateSet.
@@ -54,16 +54,23 @@ func (gs *GateSet) Add(gate *Gate) *Gate {
54
54
//FIXME: what should we do?
55
55
}
56
56
gs .gateMap [gate .Name ] = gate
57
+ gate .set = gs
57
58
return gate
58
59
}
59
60
60
- // Merge adds all of the Gates in other to this GateSet.
61
+ // Merge moves all of the Gates in other to this GateSet.
61
62
func (gs * GateSet ) Merge (other * GateSet ) {
62
63
for _ , v := range other .gateMap {
63
64
gs .Add (v )
65
+ //FIXME: remove from other?
64
66
}
65
67
}
66
68
69
+ type GateProvider interface {
70
+ //FIXME: comments, (val, found) return
71
+ Get (name string ) (bool , bool )
72
+ }
73
+
67
74
const (
68
75
// allAlphaGate is a global toggle for alpha features. Per-feature key
69
76
// values override the default set by allAlphaGate. Examples:
@@ -78,12 +85,11 @@ const (
78
85
allBetaGate = "AllBeta"
79
86
)
80
87
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 ) {
87
93
helpStrings := []string {
88
94
fmt .Sprintf ("%s=true|false (%s - default=%t)" , allAlphaGate , Alpha , false ),
89
95
fmt .Sprintf ("%s=true|false (%s - default=%t)" , allBetaGate , Beta , false ),
@@ -94,14 +100,31 @@ func (gs *GateSet) AddFlags(flagName string, fs *pflag.FlagSet) {
94
100
sort .Strings (helpStrings )
95
101
96
102
//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 , "" +
98
105
"A set of key=value pairs that describe feature gates for alpha/experimental features. " +
99
106
"Options are:\n " + strings .Join (helpStrings , "\n " ))
107
+
108
+ gs .providers = append (gs .providers , fv )
100
109
}
101
110
102
- // flagValue implements pflag.Value in terms of a GateSet .
111
+ // flagValue implements pflag.Value and GateProvider .
103
112
type 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
105
128
}
106
129
107
130
// Set parses a string of the form "key1=value1,key2=value2,..." into a
@@ -124,43 +147,42 @@ func (fv *flagValue) Set(value string) error {
124
147
}
125
148
m [k ] = boolValue
126
149
}
127
- return fv .setFromMap (m )
128
- }
129
150
130
- func (fv * flagValue ) setFromMap (m map [string ]bool ) error {
131
151
for name , enabled := range m {
132
152
switch {
133
153
case name == allAlphaGate :
134
154
for _ , gate := range fv .gates .gateMap {
135
155
if gate .Release == Alpha {
136
- gate . Set ( enabled )
156
+ fv . setOne ( gate . Name , enabled )
137
157
}
138
158
}
139
159
continue
140
160
case name == allBetaGate :
141
161
for _ , gate := range fv .gates .gateMap {
142
162
if gate .Release == Beta {
143
- gate . Set ( enabled )
163
+ fv . setOne ( gate . Name , enabled )
144
164
}
145
165
}
146
166
continue
147
167
}
148
168
169
+ // FIXME: do we need this? Env won't fail with error unless we scan all vars
149
170
gate , exists := fv .gates .gateMap [name ]
150
171
if ! exists {
151
172
return fmt .Errorf ("unrecognized feature gate: %q" , name )
152
173
}
153
174
if gate .LockToDefault && gate .Default != enabled {
154
175
return fmt .Errorf ("cannot set feature gate %q to %t, feature is locked to %t" , name , enabled , gate .Default )
155
176
}
156
- gate . Set ( enabled )
177
+ fv . setOne ( gate . Name , enabled )
157
178
}
158
179
159
180
return nil
160
181
}
161
182
162
183
// String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,...".
163
184
func (fv * flagValue ) String () string {
185
+ //FIXME: should this list all gates or just ones that are set?
164
186
pairs := []string {}
165
187
for _ , gate := range fv .gates .gateMap {
166
188
pairs = append (pairs , fmt .Sprintf ("%s=%t" , gate .Name , Enabled (gate )))
@@ -176,14 +198,76 @@ func (fv *flagValue) Type() string {
176
198
return "mapStringBool"
177
199
}
178
200
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
+
179
263
// Gate is a single feature gate definition.
180
264
type 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
187
271
}
188
272
189
273
// StabilityLevel indicates which phase of the feature lifecycle a given gate is
@@ -198,30 +282,9 @@ const (
198
282
)
199
283
200
284
// Set sets the value of this gate.
285
+ // FIXME: comments regardless of other gate config sources
201
286
func (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
225
288
}
226
289
227
290
// MergeGateSets combines a list of GateSets into a new GateSet.
@@ -236,11 +299,21 @@ func MergeGateSets(gatesets ...*GateSet) *GateSet {
236
299
// FIXME: This could be a function here or a method on Gate, but let's not do both?
237
300
// Enabled returns whether the specified Gate is enabled.
238
301
func 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 {
241
308
return g .Default
242
309
}
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
244
317
}
245
318
246
319
// testHelper is the locally-used subset of testing.T needed in SetForTesting.
@@ -254,10 +327,10 @@ type testHelper interface {
254
327
func SetForTesting (g * Gate , t testHelper , enabled bool ) func () {
255
328
t .Helper ()
256
329
257
- prev := g .enabled
258
- g .enabled = & enabled
330
+ prev := g .forcedValue
331
+ g .forcedValue = & enabled
259
332
260
333
return func () {
261
- g .enabled = prev
334
+ g .forcedValue = prev
262
335
}
263
336
}
0 commit comments