k8s.io/client-go@v0.31.1/features/envvar.go (about) 1 /* 2 Copyright 2024 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package features 18 19 import ( 20 "fmt" 21 "os" 22 "strconv" 23 "sync" 24 "sync/atomic" 25 26 "k8s.io/apimachinery/pkg/util/naming" 27 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 28 "k8s.io/klog/v2" 29 ) 30 31 // internalPackages are packages that ignored when creating a name for featureGates. These packages are in the common 32 // call chains, so they'd be unhelpful as names. 33 var internalPackages = []string{"k8s.io/client-go/features/envvar.go"} 34 35 var _ Gates = &envVarFeatureGates{} 36 37 // newEnvVarFeatureGates creates a feature gate that allows for registration 38 // of features and checking if the features are enabled. 39 // 40 // On the first call to Enabled, the effective state of all known features is loaded from 41 // environment variables. The environment variable read for a given feature is formed by 42 // concatenating the prefix "KUBE_FEATURE_" with the feature's name. 43 // 44 // For example, if you have a feature named "MyFeature" 45 // setting an environmental variable "KUBE_FEATURE_MyFeature" 46 // will allow you to configure the state of that feature. 47 // 48 // Please note that environmental variables can only be set to the boolean value. 49 // Incorrect values will be ignored and logged. 50 // 51 // Features can also be set directly via the Set method. 52 // In that case, these features take precedence over 53 // features set via environmental variables. 54 func newEnvVarFeatureGates(features map[Feature]FeatureSpec) *envVarFeatureGates { 55 known := map[Feature]FeatureSpec{} 56 for name, spec := range features { 57 known[name] = spec 58 } 59 60 fg := &envVarFeatureGates{ 61 callSiteName: naming.GetNameFromCallsite(internalPackages...), 62 known: known, 63 } 64 fg.enabledViaEnvVar.Store(map[Feature]bool{}) 65 fg.enabledViaSetMethod = map[Feature]bool{} 66 67 return fg 68 } 69 70 // envVarFeatureGates implements Gates and allows for feature registration. 71 type envVarFeatureGates struct { 72 // callSiteName holds the name of the file 73 // that created this instance 74 callSiteName string 75 76 // readEnvVarsOnce guards reading environmental variables 77 readEnvVarsOnce sync.Once 78 79 // known holds known feature gates 80 known map[Feature]FeatureSpec 81 82 // enabledViaEnvVar holds a map[Feature]bool 83 // with values explicitly set via env var 84 enabledViaEnvVar atomic.Value 85 86 // lockEnabledViaSetMethod protects enabledViaSetMethod 87 lockEnabledViaSetMethod sync.RWMutex 88 89 // enabledViaSetMethod holds values explicitly set 90 // via Set method, features stored in this map take 91 // precedence over features stored in enabledViaEnvVar 92 enabledViaSetMethod map[Feature]bool 93 94 // readEnvVars holds the boolean value which 95 // indicates whether readEnvVarsOnce has been called. 96 readEnvVars atomic.Bool 97 } 98 99 // Enabled returns true if the key is enabled. If the key is not known, this call will panic. 100 func (f *envVarFeatureGates) Enabled(key Feature) bool { 101 if v, ok := f.wasFeatureEnabledViaSetMethod(key); ok { 102 // ensue that the state of all known features 103 // is loaded from environment variables 104 // on the first call to Enabled method. 105 if !f.hasAlreadyReadEnvVar() { 106 _ = f.getEnabledMapFromEnvVar() 107 } 108 return v 109 } 110 if v, ok := f.getEnabledMapFromEnvVar()[key]; ok { 111 return v 112 } 113 if v, ok := f.known[key]; ok { 114 return v.Default 115 } 116 panic(fmt.Errorf("feature %q is not registered in FeatureGates %q", key, f.callSiteName)) 117 } 118 119 // Set sets the given feature to the given value. 120 // 121 // Features set via this method take precedence over 122 // the features set via environment variables. 123 func (f *envVarFeatureGates) Set(featureName Feature, featureValue bool) error { 124 feature, ok := f.known[featureName] 125 if !ok { 126 return fmt.Errorf("feature %q is not registered in FeatureGates %q", featureName, f.callSiteName) 127 } 128 if feature.LockToDefault && feature.Default != featureValue { 129 return fmt.Errorf("cannot set feature gate %q to %v, feature is locked to %v", featureName, featureValue, feature.Default) 130 } 131 132 f.lockEnabledViaSetMethod.Lock() 133 defer f.lockEnabledViaSetMethod.Unlock() 134 f.enabledViaSetMethod[featureName] = featureValue 135 136 return nil 137 } 138 139 // getEnabledMapFromEnvVar will fill the enabled map on the first call. 140 // This is the only time a known feature can be set to a value 141 // read from the corresponding environmental variable. 142 func (f *envVarFeatureGates) getEnabledMapFromEnvVar() map[Feature]bool { 143 f.readEnvVarsOnce.Do(func() { 144 featureGatesState := map[Feature]bool{} 145 for feature, featureSpec := range f.known { 146 featureState, featureStateSet := os.LookupEnv(fmt.Sprintf("KUBE_FEATURE_%s", feature)) 147 if !featureStateSet { 148 continue 149 } 150 boolVal, boolErr := strconv.ParseBool(featureState) 151 switch { 152 case boolErr != nil: 153 utilruntime.HandleError(fmt.Errorf("cannot set feature gate %q to %q, due to %v", feature, featureState, boolErr)) 154 case featureSpec.LockToDefault: 155 if boolVal != featureSpec.Default { 156 utilruntime.HandleError(fmt.Errorf("cannot set feature gate %q to %q, feature is locked to %v", feature, featureState, featureSpec.Default)) 157 break 158 } 159 featureGatesState[feature] = featureSpec.Default 160 default: 161 featureGatesState[feature] = boolVal 162 } 163 } 164 f.enabledViaEnvVar.Store(featureGatesState) 165 f.readEnvVars.Store(true) 166 167 for feature, featureSpec := range f.known { 168 if featureState, ok := featureGatesState[feature]; ok { 169 klog.V(1).InfoS("Feature gate updated state", "feature", feature, "enabled", featureState) 170 continue 171 } 172 klog.V(1).InfoS("Feature gate default state", "feature", feature, "enabled", featureSpec.Default) 173 } 174 }) 175 return f.enabledViaEnvVar.Load().(map[Feature]bool) 176 } 177 178 func (f *envVarFeatureGates) wasFeatureEnabledViaSetMethod(key Feature) (bool, bool) { 179 f.lockEnabledViaSetMethod.RLock() 180 defer f.lockEnabledViaSetMethod.RUnlock() 181 182 value, found := f.enabledViaSetMethod[key] 183 return value, found 184 } 185 186 func (f *envVarFeatureGates) hasAlreadyReadEnvVar() bool { 187 return f.readEnvVars.Load() 188 }