Skip to content

Commit 755e43b

Browse files
committed
fix: lazy featuregate init in callers
1 parent 7f591c2 commit 755e43b

File tree

5 files changed

+27
-20
lines changed

5 files changed

+27
-20
lines changed

cmd/webhook/main_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,11 +413,11 @@ func TestResourceClaimValidatingWebhook(t *testing.T) {
413413

414414
for name, test := range tests {
415415
t.Run(name, func(t *testing.T) {
416-
err := featuregates.FeatureGates.SetEmulationVersion(utilversion.MajorMinor(999, 999))
416+
err := featuregates.GetFeatureGates().SetEmulationVersion(utilversion.MajorMinor(999, 999))
417417
require.NoError(t, err)
418418

419419
if test.featureGates != nil {
420-
err := featuregates.FeatureGates.SetFromMap(test.featureGates)
420+
err := featuregates.GetFeatureGates().SetFromMap(test.featureGates)
421421
require.NoError(t, err)
422422
}
423423

pkg/featuregates/featuregates.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package featuregates
1818

1919
import (
2020
"strings"
21+
"sync"
2122

2223
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2324
"k8s.io/apimachinery/pkg/util/version"
@@ -38,10 +39,6 @@ const (
3839
IMEXDaemonsWithDNSNames featuregate.Feature = "IMEXDaemonsWithDNSNames"
3940
)
4041

41-
// FeatureGates is a singleton representing the set of all feature gates and their values.
42-
// It contains both project-specific feature gates and standard Kubernetes logging feature gates.
43-
var FeatureGates featuregate.MutableVersionedFeatureGate
44-
4542
// defaultFeatureGates contains the default settings for all project-specific feature gates.
4643
// These will be registered with the standard Kubernetes feature gate system.
4744
var defaultFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{
@@ -68,9 +65,19 @@ var defaultFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{
6865
},
6966
}
7067

71-
// init instantiates and sets the singleton 'FeatureGates' variable with newFeatureGates().
72-
func init() {
73-
FeatureGates = newFeatureGates(parseProjectVersion())
68+
var (
69+
featureGatesOnce sync.Once
70+
featureGates featuregate.MutableVersionedFeatureGate
71+
)
72+
73+
// GetFeatureGates instantiates and returns the package-level singleton representing
74+
// the set of all feature gates and their values.
75+
// It contains both project-specific feature gates and standard Kubernetes logging feature gates.
76+
func GetFeatureGates() featuregate.MutableVersionedFeatureGate {
77+
featureGatesOnce.Do(func() {
78+
featureGates = newFeatureGates(parseProjectVersion())
79+
})
80+
return featureGates
7481
}
7582

7683
// parseProjectVersion parses the project version string and returns major.minor version.
@@ -106,21 +113,21 @@ func newFeatureGates(version *version.Version) featuregate.MutableVersionedFeatu
106113
// Enabled returns true if the specified feature gate is enabled in the global FeatureGates singleton.
107114
// This is a convenience function that uses the global feature gate registry.
108115
func Enabled(feature featuregate.Feature) bool {
109-
return FeatureGates.Enabled(feature)
116+
return GetFeatureGates().Enabled(feature)
110117
}
111118

112119
// KnownFeatures returns a list of known feature gates with their descriptions.
113120
func KnownFeatures() []string {
114-
return FeatureGates.KnownFeatures()
121+
return GetFeatureGates().KnownFeatures()
115122
}
116123

117124
// ToMap returns all known feature gates as a map[string]bool suitable for
118125
// template rendering (e.g., {"FeatureA": true, "FeatureB": false}).
119126
// Returns an empty map if no feature gates are configured.
120127
func ToMap() map[string]bool {
121128
result := make(map[string]bool)
122-
for feature := range FeatureGates.GetAll() {
123-
result[string(feature)] = FeatureGates.Enabled(feature)
129+
for feature := range GetFeatureGates().GetAll() {
130+
result[string(feature)] = GetFeatureGates().Enabled(feature)
124131
}
125132
return result
126133
}

pkg/flags/featuregates.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func (f *FeatureGateConfig) Flags() []cli.Flag {
4343
Name: "feature-gates",
4444
Usage: "A set of key=value pairs that describe feature gates for alpha/experimental features. " +
4545
"Options are:\n " + strings.Join(featuregates.KnownFeatures(), "\n "),
46-
Value: featuregates.FeatureGates.(pflag.Value), //nolint:forcetypeassert // No need for type check: FeatureGates is a *featuregate.featureGate, which implements pflag.Value.
46+
Value: featuregates.GetFeatureGates().(pflag.Value), //nolint:forcetypeassert // No need for type check: FeatureGates is a *featuregate.featureGate, which implements pflag.Value.
4747
})
4848

4949
var flags []cli.Flag

pkg/flags/featuregates_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ var (
4444

4545
func TestFeatureGateConfigBasicFunctionality(t *testing.T) {
4646
// Test that the config provides basic feature gate functionality
47-
fg := featuregates.FeatureGates
47+
fg := featuregates.GetFeatureGates()
4848

4949
// Test that the feature gate is functional
5050
require.NotNil(t, fg, "FeatureGate should not be nil")
@@ -93,7 +93,7 @@ func TestFeatureGateConfigSeparationOfConcerns(t *testing.T) {
9393
func TestFeatureGateConfigSetFromMap(t *testing.T) {
9494
t.Run("EnableDisabledFeature", func(t *testing.T) {
9595
// Test that we can enable a disabled feature
96-
fg := featuregates.FeatureGates
96+
fg := featuregates.GetFeatureGates()
9797

9898
// Test enabling a disabled feature - we know AllAlpha starts as false
9999
require.False(t, featuregates.Enabled(testValidDisabledFeature), "AllAlpha feature should start as false")
@@ -116,7 +116,7 @@ func TestFeatureGateConfigSetFromMap(t *testing.T) {
116116

117117
t.Run("DisableEnabledFeature", func(t *testing.T) {
118118
// Test that we can disable an enabled feature
119-
fg := featuregates.FeatureGates
119+
fg := featuregates.GetFeatureGates()
120120

121121
// First enable AllBeta so we can test disabling it
122122
err := fg.SetFromMap(map[string]bool{
@@ -145,7 +145,7 @@ func TestFeatureGateConfigSetFromMap(t *testing.T) {
145145
}
146146

147147
func TestFeatureGateConfigKnownFeatures(t *testing.T) {
148-
fg := featuregates.FeatureGates
148+
fg := featuregates.GetFeatureGates()
149149

150150
knownFeatures := fg.KnownFeatures()
151151
require.NotEmpty(t, knownFeatures, "Should have some known features")
@@ -163,7 +163,7 @@ func TestFeatureGateConfigKnownFeatures(t *testing.T) {
163163
}
164164

165165
func TestFeatureGateConfigErrorHandling(t *testing.T) {
166-
fg := featuregates.FeatureGates
166+
fg := featuregates.GetFeatureGates()
167167

168168
// Test error handling with invalid features
169169
err := fg.SetFromMap(map[string]bool{

pkg/flags/logging.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func NewLoggingConfig() *LoggingConfig {
4343
// line flags and before running any code which emits log entries.
4444
// It uses the global feature gate singleton.
4545
func (l *LoggingConfig) Apply() error {
46-
return logsapi.ValidateAndApply(l.config, featuregates.FeatureGates)
46+
return logsapi.ValidateAndApply(l.config, featuregates.GetFeatureGates())
4747
}
4848

4949
// Flags returns the flags for logging configuration (NOT including feature gates).

0 commit comments

Comments
 (0)