| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- /*
- Copyright 2024 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package features
- import (
- "errors"
- "sync/atomic"
- utilruntime "k8s.io/apimachinery/pkg/util/runtime"
- "k8s.io/apimachinery/pkg/util/version"
- )
- // NOTE: types Feature, FeatureSpec, prerelease (and its values)
- // were duplicated from the component-base repository
- //
- // for more information please refer to https://docs.google.com/document/d/1g9BGCRw-7ucUxO6OtCWbb3lfzUGA_uU9178wLdXAIfs
- const (
- // Values for PreRelease.
- Alpha = prerelease("ALPHA")
- Beta = prerelease("BETA")
- GA = prerelease("")
- // Deprecated
- Deprecated = prerelease("DEPRECATED")
- )
- type prerelease string
- type Feature string
- type FeatureSpec struct {
- // Default is the default enablement state for the feature
- Default bool
- // LockToDefault indicates that the feature is locked to its default and cannot be changed
- LockToDefault bool
- // PreRelease indicates the maturity level of the feature
- PreRelease prerelease
- // Version indicates the earliest version from which this FeatureSpec is valid.
- // If multiple FeatureSpecs exist for a Feature, the one with the highest version that is less
- // than or equal to the effective version of the component is used.
- Version *version.Version
- }
- type VersionedSpecs []FeatureSpec
- // Gates indicates whether a given feature is enabled or not.
- type Gates interface {
- // Enabled returns true if the key is enabled.
- Enabled(key Feature) bool
- }
- // Registry represents an external feature gates registry.
- type Registry interface {
- // Add adds existing feature gates to the provided registry.
- //
- // As of today, this method is used by AddFeaturesToExistingFeatureGates and
- // ReplaceFeatureGates to take control of the features exposed by this library.
- Add(map[Feature]FeatureSpec) error
- }
- // VersionedRegistry represents an external versioned feature gates registry.
- type VersionedRegistry interface {
- // AddVersioned adds existing versioned feature gates to the provided registry.
- //
- // As of today, this method is used by AddVersionedFeaturesToExistingFeatureGates and
- // ReplaceFeatureGates to take control of the features exposed by this library.
- AddVersioned(in map[Feature]VersionedSpecs) error
- }
- // FeatureGates returns the feature gates exposed by this library.
- //
- // By default, only the default features gates will be returned.
- // The default implementation allows controlling the features
- // via environmental variables.
- // For example, if you have a feature named "MyFeature"
- // setting an environmental variable "KUBE_FEATURE_MyFeature"
- // will allow you to configure the state of that feature.
- //
- // Please note that the actual set of the feature gates
- // might be overwritten by calling ReplaceFeatureGates method.
- func FeatureGates() Gates {
- return featureGates.Load().(*featureGatesWrapper).Gates
- }
- // AddFeaturesToExistingFeatureGates adds the default feature gates to the provided registry.
- // Usually this function is combined with ReplaceFeatureGates to take control of the
- // features exposed by this library.
- func AddFeaturesToExistingFeatureGates(registry Registry) error {
- return registry.Add(unversionedFeatureGates(defaultVersionedKubernetesFeatureGates))
- }
- // AddFeaturesToExistingFeatureGates adds the default versioned feature gates to the provided registry.
- // Usually this function is combined with ReplaceFeatureGates to take control of the
- // features exposed by this library.
- // Generally only used by k/k.
- func AddVersionedFeaturesToExistingFeatureGates(registry VersionedRegistry) error {
- return registry.AddVersioned(defaultVersionedKubernetesFeatureGates)
- }
- // ReplaceFeatureGates overwrites the default implementation of the feature gates
- // used by this library.
- //
- // Useful for binaries that would like to have full control of the features
- // exposed by this library, such as allowing consumers of a binary
- // to interact with the features via a command line flag.
- //
- // For example:
- //
- // // first, register client-go's features to your registry.
- // clientgofeaturegate.AddFeaturesToExistingFeatureGates(utilfeature.DefaultMutableFeatureGate)
- // // then replace client-go's feature gates implementation with your implementation
- // clientgofeaturegate.ReplaceFeatureGates(utilfeature.DefaultMutableFeatureGate)
- func ReplaceFeatureGates(newFeatureGates Gates) {
- if replaceFeatureGatesWithWarningIndicator(newFeatureGates) {
- utilruntime.HandleError(errors.New("the default feature gates implementation has already been used and now it's being overwritten. This might lead to unexpected behaviour. Check your initialization order"))
- }
- }
- func replaceFeatureGatesWithWarningIndicator(newFeatureGates Gates) bool {
- shouldProduceWarning := false
- if defaultFeatureGates, ok := FeatureGates().(*envVarFeatureGates); ok {
- if defaultFeatureGates.hasAlreadyReadEnvVar() {
- shouldProduceWarning = true
- }
- }
- wrappedFeatureGates := &featureGatesWrapper{newFeatureGates}
- featureGates.Store(wrappedFeatureGates)
- return shouldProduceWarning
- }
- // unversionedFeatureGates takes the latest entry from the VersionedSpecs of each feature, and clears out the version information,
- // so that the result can be used with an unversioned feature gate.
- func unversionedFeatureGates(featureGates map[Feature]VersionedSpecs) map[Feature]FeatureSpec {
- unversioned := map[Feature]FeatureSpec{}
- for feature, specs := range featureGates {
- if len(specs) == 0 {
- continue
- }
- latestSpec := specs[len(specs)-1]
- latestSpec.Version = nil // Clear version information.
- unversioned[feature] = latestSpec
- }
- return unversioned
- }
- func init() {
- envVarGates := newEnvVarFeatureGates(unversionedFeatureGates(defaultVersionedKubernetesFeatureGates))
- wrappedFeatureGates := &featureGatesWrapper{envVarGates}
- featureGates.Store(wrappedFeatureGates)
- }
- // featureGatesWrapper a thin wrapper to satisfy featureGates variable (atomic.Value).
- // That is, all calls to Store for a given Value must use values of the same concrete type.
- type featureGatesWrapper struct {
- Gates
- }
- var (
- // featureGates is a shared global FeatureGates.
- //
- // Top-level commands/options setup that needs to modify this feature gates
- // should use AddFeaturesToExistingFeatureGates followed by ReplaceFeatureGates.
- featureGates = &atomic.Value{}
- )
|