diff --git a/cmd/gardener-extension-provider-openstack/app/app.go b/cmd/gardener-extension-provider-openstack/app/app.go index 9b5d271c4..3d7b7aee3 100644 --- a/cmd/gardener-extension-provider-openstack/app/app.go +++ b/cmd/gardener-extension-provider-openstack/app/app.go @@ -31,6 +31,7 @@ import ( "github.com/gardener/gardener-extension-provider-openstack/pkg/controller/healthcheck" openstackinfrastructure "github.com/gardener/gardener-extension-provider-openstack/pkg/controller/infrastructure" openstackworker "github.com/gardener/gardener-extension-provider-openstack/pkg/controller/worker" + "github.com/gardener/gardener-extension-provider-openstack/pkg/features" "github.com/gardener/gardener-extension-provider-openstack/pkg/openstack" openstackcontrolplaneexposure "github.com/gardener/gardener-extension-provider-openstack/pkg/webhook/controlplaneexposure" @@ -168,6 +169,10 @@ func NewControllerManagerCommand(ctx context.Context) *cobra.Command { return fmt.Errorf("invalid controller config: %w", err) } + if err := features.ExtensionFeatureGate.SetFromMap(configFileOpts.Completed().Config.FeatureGates); err != nil { + return err + } + util.ApplyClientConnectionConfigurationToRESTConfig(configFileOpts.Completed().Config.ClientConnection, restOpts.Completed().Config) if workerReconcileOpts.Completed().DeployCRDs { diff --git a/cmd/gardener-extension-provider-openstack/main.go b/cmd/gardener-extension-provider-openstack/main.go index a663152c9..807bd7ff8 100644 --- a/cmd/gardener-extension-provider-openstack/main.go +++ b/cmd/gardener-extension-provider-openstack/main.go @@ -18,6 +18,7 @@ import ( "os" "github.com/gardener/gardener-extension-provider-openstack/cmd/gardener-extension-provider-openstack/app" + "github.com/gardener/gardener-extension-provider-openstack/pkg/features" "github.com/gardener/gardener/pkg/logger" runtimelog "sigs.k8s.io/controller-runtime/pkg/log" @@ -25,6 +26,7 @@ import ( ) func main() { + features.RegisterExtensionFeatureGate() runtimelog.SetLogger(logger.ZapLogger(false)) cmd := app.NewControllerManagerCommand(signals.SetupSignalHandler()) diff --git a/hack/api-reference/config.md b/hack/api-reference/config.md index 5341ae480..e93c9d6b4 100644 --- a/hack/api-reference/config.md +++ b/hack/api-reference/config.md @@ -112,6 +112,20 @@ ApplicationCredentialConfig
ApplicationCrednentialConfig defines the configuration for managed application credentials.
+featureGates
+
+map[string]bool
+
+FeatureGates is a map of feature names to bools that enable +or disable alpha/experimental features. +Default: nil
+enabled
-
-bool
-
-Enabled indicate if managed application credentials should be used.
-lifetime
diff --git a/pkg/apis/config/types.go b/pkg/apis/config/types.go
index b20204641..8ecbbf278 100644
--- a/pkg/apis/config/types.go
+++ b/pkg/apis/config/types.go
@@ -39,6 +39,10 @@ type ControllerConfiguration struct {
BastionConfig *BastionConfig
// ApplicationCrednentialConfig defines the configuration for managed application credentials.
ApplicationCredentialConfig *ApplicationCredentialConfig
+ // FeatureGates is a map of feature names to bools that enable
+ // or disable alpha/experimental features.
+ // Default: nil
+ FeatureGates map[string]bool
}
// ETCD is an etcd configuration.
@@ -73,8 +77,6 @@ type BastionConfig struct {
// ApplicationCredentialConfig defines the configuration for managed application credentials.
type ApplicationCredentialConfig struct {
- // Enabled indicate if managed application credentials should be used.
- Enabled bool
// Lifetime define how long a managed application credentials are valid.
// Once the creation time + lifetime of an application credential is expired
// it will be renewed once it is next reconciled.
diff --git a/pkg/apis/config/v1alpha1/types.go b/pkg/apis/config/v1alpha1/types.go
index aec48e4f8..0ea5824f3 100644
--- a/pkg/apis/config/v1alpha1/types.go
+++ b/pkg/apis/config/v1alpha1/types.go
@@ -44,6 +44,11 @@ type ControllerConfiguration struct {
// ApplicationCrednentialConfig defines the configuration for managed application credentials.
// +optional
ApplicationCredentialConfig *ApplicationCredentialConfig `json:"managedApplicationCredential,omitempty"`
+ // FeatureGates is a map of feature names to bools that enable
+ // or disable alpha/experimental features.
+ // Default: nil
+ // +optional
+ FeatureGates map[string]bool `json:"featureGates,omitempty"`
}
// ETCD is an etcd configuration.
@@ -81,9 +86,6 @@ type BastionConfig struct {
// ApplicationCredentialConfig defines the configuration for managed application credentials.
type ApplicationCredentialConfig struct {
- // Enabled indicate if managed application credentials should be used.
- // +optional
- Enabled bool `json:"enabled,omitempty"`
// Lifetime define how long a managed application credentials are valid.
// Once the creation time + lifetime of an application credential is expired
// it will be renewed once it is next reconciled.
diff --git a/pkg/apis/config/v1alpha1/zz_generated.conversion.go b/pkg/apis/config/v1alpha1/zz_generated.conversion.go
index da7af497a..a5e9bad08 100644
--- a/pkg/apis/config/v1alpha1/zz_generated.conversion.go
+++ b/pkg/apis/config/v1alpha1/zz_generated.conversion.go
@@ -106,7 +106,6 @@ func RegisterConversions(s *runtime.Scheme) error {
}
func autoConvert_v1alpha1_ApplicationCredentialConfig_To_config_ApplicationCredentialConfig(in *ApplicationCredentialConfig, out *config.ApplicationCredentialConfig, s conversion.Scope) error {
- out.Enabled = in.Enabled
out.Lifetime = (*v1.Duration)(unsafe.Pointer(in.Lifetime))
out.OpenstackExpirationPeriod = (*v1.Duration)(unsafe.Pointer(in.OpenstackExpirationPeriod))
out.RenewThreshold = (*v1.Duration)(unsafe.Pointer(in.RenewThreshold))
@@ -119,7 +118,6 @@ func Convert_v1alpha1_ApplicationCredentialConfig_To_config_ApplicationCredentia
}
func autoConvert_config_ApplicationCredentialConfig_To_v1alpha1_ApplicationCredentialConfig(in *config.ApplicationCredentialConfig, out *ApplicationCredentialConfig, s conversion.Scope) error {
- out.Enabled = in.Enabled
out.Lifetime = (*v1.Duration)(unsafe.Pointer(in.Lifetime))
out.OpenstackExpirationPeriod = (*v1.Duration)(unsafe.Pointer(in.OpenstackExpirationPeriod))
out.RenewThreshold = (*v1.Duration)(unsafe.Pointer(in.RenewThreshold))
@@ -161,6 +159,7 @@ func autoConvert_v1alpha1_ControllerConfiguration_To_config_ControllerConfigurat
out.HealthCheckConfig = (*healthcheckconfig.HealthCheckConfig)(unsafe.Pointer(in.HealthCheckConfig))
out.BastionConfig = (*config.BastionConfig)(unsafe.Pointer(in.BastionConfig))
out.ApplicationCredentialConfig = (*config.ApplicationCredentialConfig)(unsafe.Pointer(in.ApplicationCredentialConfig))
+ out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
return nil
}
@@ -177,6 +176,7 @@ func autoConvert_config_ControllerConfiguration_To_v1alpha1_ControllerConfigurat
out.HealthCheckConfig = (*healthcheckconfigv1alpha1.HealthCheckConfig)(unsafe.Pointer(in.HealthCheckConfig))
out.BastionConfig = (*BastionConfig)(unsafe.Pointer(in.BastionConfig))
out.ApplicationCredentialConfig = (*ApplicationCredentialConfig)(unsafe.Pointer(in.ApplicationCredentialConfig))
+ out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
return nil
}
diff --git a/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go
index bae3f72fa..18fdde2e0 100644
--- a/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go
@@ -100,6 +100,13 @@ func (in *ControllerConfiguration) DeepCopyInto(out *ControllerConfiguration) {
*out = new(ApplicationCredentialConfig)
(*in).DeepCopyInto(*out)
}
+ if in.FeatureGates != nil {
+ in, out := &in.FeatureGates, &out.FeatureGates
+ *out = make(map[string]bool, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
return
}
diff --git a/pkg/apis/config/validation/config.go b/pkg/apis/config/validation/config.go
index 483058603..69cf76aff 100644
--- a/pkg/apis/config/validation/config.go
+++ b/pkg/apis/config/validation/config.go
@@ -37,10 +37,6 @@ func ValidateControllerConfig(cfg *config.ControllerConfiguration) error {
func validateApplicationCredentialConfig(cfg *config.ApplicationCredentialConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
- if !cfg.Enabled {
- return allErrs
- }
-
if cfg.Lifetime.Duration > cfg.OpenstackExpirationPeriod.Duration {
allErrs = append(allErrs, field.Invalid(fldPath.Child("lifetime"), cfg.Lifetime.Duration, "application credential lifetime cannot be lower than the openstack layer expiration time"))
}
diff --git a/pkg/apis/config/validation/config_test.go b/pkg/apis/config/validation/config_test.go
index 7932f99ea..efbe69094 100644
--- a/pkg/apis/config/validation/config_test.go
+++ b/pkg/apis/config/validation/config_test.go
@@ -35,25 +35,15 @@ var _ = Describe("ControllerConfig validation", func() {
Entry("should pass with valid application credential config",
&config.ControllerConfiguration{
ApplicationCredentialConfig: &config.ApplicationCredentialConfig{
- Enabled: true,
Lifetime: &metav1.Duration{Duration: 24 * time.Hour},
OpenstackExpirationPeriod: &metav1.Duration{Duration: 72 * time.Hour},
},
},
BeNil(),
),
- Entry("should pass when application credential usage is disabled",
- &config.ControllerConfiguration{
- ApplicationCredentialConfig: &config.ApplicationCredentialConfig{
- Enabled: false,
- },
- },
- BeNil(),
- ),
Entry("should forbid application credential lifetime larger than openstack expiration time",
&config.ControllerConfiguration{
ApplicationCredentialConfig: &config.ApplicationCredentialConfig{
- Enabled: true,
Lifetime: &metav1.Duration{Duration: 73 * time.Hour},
OpenstackExpirationPeriod: &metav1.Duration{Duration: 72 * time.Hour},
},
diff --git a/pkg/apis/config/zz_generated.deepcopy.go b/pkg/apis/config/zz_generated.deepcopy.go
index d89411a03..c1d0aa4f0 100644
--- a/pkg/apis/config/zz_generated.deepcopy.go
+++ b/pkg/apis/config/zz_generated.deepcopy.go
@@ -100,6 +100,13 @@ func (in *ControllerConfiguration) DeepCopyInto(out *ControllerConfiguration) {
*out = new(ApplicationCredentialConfig)
(*in).DeepCopyInto(*out)
}
+ if in.FeatureGates != nil {
+ in, out := &in.FeatureGates, &out.FeatureGates
+ *out = make(map[string]bool, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
return
}
diff --git a/pkg/features/features.go b/pkg/features/features.go
new file mode 100644
index 000000000..a12649cbb
--- /dev/null
+++ b/pkg/features/features.go
@@ -0,0 +1,37 @@
+// Copyright (c) 2022 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file
+//
+// 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 (
+ "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/component-base/featuregate"
+)
+
+const (
+ // ManagedApplicationCredential allows to manage application credentials for Shoots
+ // to interact with the Openstack layer on behalf of an Openstack user.
+ // owner @dkistner
+ // alpha: v1.27.0
+ ManagedApplicationCredential featuregate.Feature = "ManagedApplicationCredential"
+)
+
+var ExtensionFeatureGate = featuregate.NewFeatureGate()
+
+// RegisterExtensionFeatureGate registers features to the extension feature gate.
+func RegisterExtensionFeatureGate() {
+ runtime.Must(ExtensionFeatureGate.Add(map[featuregate.Feature]featuregate.FeatureSpec{
+ ManagedApplicationCredential: {Default: false, PreRelease: featuregate.Alpha},
+ }))
+}
diff --git a/pkg/internal/managedappcredential/manager_ensure.go b/pkg/internal/managedappcredential/manager_ensure.go
index d2bac5f22..7edc0392f 100644
--- a/pkg/internal/managedappcredential/manager_ensure.go
+++ b/pkg/internal/managedappcredential/manager_ensure.go
@@ -19,6 +19,7 @@ import (
"fmt"
"time"
+ "github.com/gardener/gardener-extension-provider-openstack/pkg/features"
"github.com/gardener/gardener-extension-provider-openstack/pkg/openstack"
openstackclient "github.com/gardener/gardener-extension-provider-openstack/pkg/openstack/client"
"github.com/gardener/gardener/pkg/utils"
@@ -76,7 +77,7 @@ func (m *Manager) Ensure(ctx context.Context, credentials *openstack.Credentials
// In case the application credential usage is disabled or the new parent user
// itself is an appplication, it is tried to clean up old application credentials before aborting.
- if !m.config.Enabled || newParentUser.isApplicationCredential() {
+ if newParentUser.isApplicationCredential() || !features.ExtensionFeatureGate.Enabled(features.ManagedApplicationCredential) {
if oldParentUserUsable {
if err := m.runGarbageCollection(ctx, oldParentUser, nil); err != nil {
return err
diff --git a/vendor/k8s.io/component-base/featuregate/OWNERS b/vendor/k8s.io/component-base/featuregate/OWNERS
new file mode 100644
index 000000000..48e811bf4
--- /dev/null
+++ b/vendor/k8s.io/component-base/featuregate/OWNERS
@@ -0,0 +1,17 @@
+# See the OWNERS docs at https://go.k8s.io/owners
+
+# Currently assigned to api-approvers since feature gates are the API
+# for enabling/disabling other APIs.
+
+# Disable inheritance as this is an api owners file
+options:
+ no_parent_owners: true
+approvers:
+- api-approvers
+reviewers:
+- api-reviewers
+
+labels:
+- kind/api-change
+- sig/api-machinery
+- sig/cluster-lifecycle
diff --git a/vendor/k8s.io/component-base/featuregate/feature_gate.go b/vendor/k8s.io/component-base/featuregate/feature_gate.go
new file mode 100644
index 000000000..c7166d80b
--- /dev/null
+++ b/vendor/k8s.io/component-base/featuregate/feature_gate.go
@@ -0,0 +1,375 @@
+/*
+Copyright 2016 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 featuregate
+
+import (
+ "fmt"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "sync/atomic"
+
+ "github.com/spf13/pflag"
+
+ "k8s.io/apimachinery/pkg/util/naming"
+ "k8s.io/klog/v2"
+)
+
+type Feature string
+
+const (
+ flagName = "feature-gates"
+
+ // allAlphaGate is a global toggle for alpha features. Per-feature key
+ // values override the default set by allAlphaGate. Examples:
+ // AllAlpha=false,NewFeature=true will result in newFeature=true
+ // AllAlpha=true,NewFeature=false will result in newFeature=false
+ allAlphaGate Feature = "AllAlpha"
+
+ // allBetaGate is a global toggle for beta features. Per-feature key
+ // values override the default set by allBetaGate. Examples:
+ // AllBeta=false,NewFeature=true will result in NewFeature=true
+ // AllBeta=true,NewFeature=false will result in NewFeature=false
+ allBetaGate Feature = "AllBeta"
+)
+
+var (
+ // The generic features.
+ defaultFeatures = map[Feature]FeatureSpec{
+ allAlphaGate: {Default: false, PreRelease: Alpha},
+ allBetaGate: {Default: false, PreRelease: Beta},
+ }
+
+ // Special handling for a few gates.
+ specialFeatures = map[Feature]func(known map[Feature]FeatureSpec, enabled map[Feature]bool, val bool){
+ allAlphaGate: setUnsetAlphaGates,
+ allBetaGate: setUnsetBetaGates,
+ }
+)
+
+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
+}
+
+type prerelease string
+
+const (
+ // Values for PreRelease.
+ Alpha = prerelease("ALPHA")
+ Beta = prerelease("BETA")
+ GA = prerelease("")
+
+ // Deprecated
+ Deprecated = prerelease("DEPRECATED")
+)
+
+// FeatureGate indicates whether a given feature is enabled or not
+type FeatureGate interface {
+ // Enabled returns true if the key is enabled.
+ Enabled(key Feature) bool
+ // KnownFeatures returns a slice of strings describing the FeatureGate's known features.
+ KnownFeatures() []string
+ // DeepCopy returns a deep copy of the FeatureGate object, such that gates can be
+ // set on the copy without mutating the original. This is useful for validating
+ // config against potential feature gate changes before committing those changes.
+ DeepCopy() MutableFeatureGate
+}
+
+// MutableFeatureGate parses and stores flag gates for known features from
+// a string like feature1=true,feature2=false,...
+type MutableFeatureGate interface {
+ FeatureGate
+
+ // AddFlag adds a flag for setting global feature gates to the specified FlagSet.
+ AddFlag(fs *pflag.FlagSet)
+ // Set parses and stores flag gates for known features
+ // from a string like feature1=true,feature2=false,...
+ Set(value string) error
+ // SetFromMap stores flag gates for known features from a map[string]bool or returns an error
+ SetFromMap(m map[string]bool) error
+ // Add adds features to the featureGate.
+ Add(features map[Feature]FeatureSpec) error
+ // GetAll returns a copy of the map of known feature names to feature specs.
+ GetAll() map[Feature]FeatureSpec
+}
+
+// featureGate implements FeatureGate as well as pflag.Value for flag parsing.
+type featureGate struct {
+ featureGateName string
+
+ special map[Feature]func(map[Feature]FeatureSpec, map[Feature]bool, bool)
+
+ // lock guards writes to known, enabled, and reads/writes of closed
+ lock sync.Mutex
+ // known holds a map[Feature]FeatureSpec
+ known *atomic.Value
+ // enabled holds a map[Feature]bool
+ enabled *atomic.Value
+ // closed is set to true when AddFlag is called, and prevents subsequent calls to Add
+ closed bool
+}
+
+func setUnsetAlphaGates(known map[Feature]FeatureSpec, enabled map[Feature]bool, val bool) {
+ for k, v := range known {
+ if v.PreRelease == Alpha {
+ if _, found := enabled[k]; !found {
+ enabled[k] = val
+ }
+ }
+ }
+}
+
+func setUnsetBetaGates(known map[Feature]FeatureSpec, enabled map[Feature]bool, val bool) {
+ for k, v := range known {
+ if v.PreRelease == Beta {
+ if _, found := enabled[k]; !found {
+ enabled[k] = val
+ }
+ }
+ }
+}
+
+// Set, String, and Type implement pflag.Value
+var _ pflag.Value = &featureGate{}
+
+// internalPackages are packages that ignored when creating a name for featureGates. These packages are in the common
+// call chains, so they'd be unhelpful as names.
+var internalPackages = []string{"k8s.io/component-base/featuregate/feature_gate.go"}
+
+func NewFeatureGate() *featureGate {
+ known := map[Feature]FeatureSpec{}
+ for k, v := range defaultFeatures {
+ known[k] = v
+ }
+
+ knownValue := &atomic.Value{}
+ knownValue.Store(known)
+
+ enabled := map[Feature]bool{}
+ enabledValue := &atomic.Value{}
+ enabledValue.Store(enabled)
+
+ f := &featureGate{
+ featureGateName: naming.GetNameFromCallsite(internalPackages...),
+ known: knownValue,
+ special: specialFeatures,
+ enabled: enabledValue,
+ }
+ return f
+}
+
+// Set parses a string of the form "key1=value1,key2=value2,..." into a
+// map[string]bool of known keys or returns an error.
+func (f *featureGate) Set(value string) error {
+ m := make(map[string]bool)
+ for _, s := range strings.Split(value, ",") {
+ if len(s) == 0 {
+ continue
+ }
+ arr := strings.SplitN(s, "=", 2)
+ k := strings.TrimSpace(arr[0])
+ if len(arr) != 2 {
+ return fmt.Errorf("missing bool value for %s", k)
+ }
+ v := strings.TrimSpace(arr[1])
+ boolValue, err := strconv.ParseBool(v)
+ if err != nil {
+ return fmt.Errorf("invalid value of %s=%s, err: %v", k, v, err)
+ }
+ m[k] = boolValue
+ }
+ return f.SetFromMap(m)
+}
+
+// SetFromMap stores flag gates for known features from a map[string]bool or returns an error
+func (f *featureGate) SetFromMap(m map[string]bool) error {
+ f.lock.Lock()
+ defer f.lock.Unlock()
+
+ // Copy existing state
+ known := map[Feature]FeatureSpec{}
+ for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
+ known[k] = v
+ }
+ enabled := map[Feature]bool{}
+ for k, v := range f.enabled.Load().(map[Feature]bool) {
+ enabled[k] = v
+ }
+
+ for k, v := range m {
+ k := Feature(k)
+ featureSpec, ok := known[k]
+ if !ok {
+ return fmt.Errorf("unrecognized feature gate: %s", k)
+ }
+ if featureSpec.LockToDefault && featureSpec.Default != v {
+ return fmt.Errorf("cannot set feature gate %v to %v, feature is locked to %v", k, v, featureSpec.Default)
+ }
+ enabled[k] = v
+ // Handle "special" features like "all alpha gates"
+ if fn, found := f.special[k]; found {
+ fn(known, enabled, v)
+ }
+
+ if featureSpec.PreRelease == Deprecated {
+ klog.Warningf("Setting deprecated feature gate %s=%t. It will be removed in a future release.", k, v)
+ } else if featureSpec.PreRelease == GA {
+ klog.Warningf("Setting GA feature gate %s=%t. It will be removed in a future release.", k, v)
+ }
+ }
+
+ // Persist changes
+ f.known.Store(known)
+ f.enabled.Store(enabled)
+
+ klog.V(1).Infof("feature gates: %v", f.enabled)
+ return nil
+}
+
+// String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,...".
+func (f *featureGate) String() string {
+ pairs := []string{}
+ for k, v := range f.enabled.Load().(map[Feature]bool) {
+ pairs = append(pairs, fmt.Sprintf("%s=%t", k, v))
+ }
+ sort.Strings(pairs)
+ return strings.Join(pairs, ",")
+}
+
+func (f *featureGate) Type() string {
+ return "mapStringBool"
+}
+
+// Add adds features to the featureGate.
+func (f *featureGate) Add(features map[Feature]FeatureSpec) error {
+ f.lock.Lock()
+ defer f.lock.Unlock()
+
+ if f.closed {
+ return fmt.Errorf("cannot add a feature gate after adding it to the flag set")
+ }
+
+ // Copy existing state
+ known := map[Feature]FeatureSpec{}
+ for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
+ known[k] = v
+ }
+
+ for name, spec := range features {
+ if existingSpec, found := known[name]; found {
+ if existingSpec == spec {
+ continue
+ }
+ return fmt.Errorf("feature gate %q with different spec already exists: %v", name, existingSpec)
+ }
+
+ known[name] = spec
+ }
+
+ // Persist updated state
+ f.known.Store(known)
+
+ return nil
+}
+
+// GetAll returns a copy of the map of known feature names to feature specs.
+func (f *featureGate) GetAll() map[Feature]FeatureSpec {
+ retval := map[Feature]FeatureSpec{}
+ for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
+ retval[k] = v
+ }
+ return retval
+}
+
+// Enabled returns true if the key is enabled. If the key is not known, this call will panic.
+func (f *featureGate) Enabled(key Feature) bool {
+ if v, ok := f.enabled.Load().(map[Feature]bool)[key]; ok {
+ return v
+ }
+ if v, ok := f.known.Load().(map[Feature]FeatureSpec)[key]; ok {
+ return v.Default
+ }
+
+ panic(fmt.Errorf("feature %q is not registered in FeatureGate %q", key, f.featureGateName))
+}
+
+// AddFlag adds a flag for setting global feature gates to the specified FlagSet.
+func (f *featureGate) AddFlag(fs *pflag.FlagSet) {
+ f.lock.Lock()
+ // TODO(mtaufen): Shouldn't we just close it on the first Set/SetFromMap instead?
+ // Not all components expose a feature gates flag using this AddFlag method, and
+ // in the future, all components will completely stop exposing a feature gates flag,
+ // in favor of componentconfig.
+ f.closed = true
+ f.lock.Unlock()
+
+ known := f.KnownFeatures()
+ fs.Var(f, flagName, ""+
+ "A set of key=value pairs that describe feature gates for alpha/experimental features. "+
+ "Options are:\n"+strings.Join(known, "\n"))
+}
+
+// KnownFeatures returns a slice of strings describing the FeatureGate's known features.
+// Deprecated and GA features are hidden from the list.
+func (f *featureGate) KnownFeatures() []string {
+ var known []string
+ for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
+ if v.PreRelease == GA || v.PreRelease == Deprecated {
+ continue
+ }
+ known = append(known, fmt.Sprintf("%s=true|false (%s - default=%t)", k, v.PreRelease, v.Default))
+ }
+ sort.Strings(known)
+ return known
+}
+
+// DeepCopy returns a deep copy of the FeatureGate object, such that gates can be
+// set on the copy without mutating the original. This is useful for validating
+// config against potential feature gate changes before committing those changes.
+func (f *featureGate) DeepCopy() MutableFeatureGate {
+ // Copy existing state.
+ known := map[Feature]FeatureSpec{}
+ for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
+ known[k] = v
+ }
+ enabled := map[Feature]bool{}
+ for k, v := range f.enabled.Load().(map[Feature]bool) {
+ enabled[k] = v
+ }
+
+ // Store copied state in new atomics.
+ knownValue := &atomic.Value{}
+ knownValue.Store(known)
+ enabledValue := &atomic.Value{}
+ enabledValue.Store(enabled)
+
+ // Construct a new featureGate around the copied state.
+ // Note that specialFeatures is treated as immutable by convention,
+ // and we maintain the value of f.closed across the copy.
+ return &featureGate{
+ special: specialFeatures,
+ known: knownValue,
+ enabled: enabledValue,
+ closed: f.closed,
+ }
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 7e0191fb6..2f81097cd 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1008,6 +1008,7 @@ k8s.io/code-generator/third_party/forked/golang/reflect
## explicit; go 1.16
k8s.io/component-base/config
k8s.io/component-base/config/v1alpha1
+k8s.io/component-base/featuregate
k8s.io/component-base/version
k8s.io/component-base/version/verflag
# k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c