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  }