Skip to content

✨ Log catalogd feature gate states #1930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/catalogd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ func validateConfig(cfg *config) error {
}

func run(ctx context.Context) error {
// log startup message and feature gate status
setupLog.Info("starting up catalogd", "version info", version.String())
features.LogFeatureGateStates(setupLog, features.CatalogdFeatureGate)
authFilePath := filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s.json", authFilePrefix, apimachineryrand.String(8)))

protocol := "http://"
Expand Down
8 changes: 8 additions & 0 deletions internal/catalogd/features/features.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package features

import (
"github.com/go-logr/logr"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/component-base/featuregate"

fgutil "github.com/operator-framework/operator-controller/internal/shared/util/featuregates"
)

const (
Expand All @@ -18,3 +21,8 @@ var CatalogdFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureG
func init() {
utilruntime.Must(CatalogdFeatureGate.Add(catalogdFeatureGates))
}

// LogFeatureGateStates logs the state of all known feature gates for catalogd
func LogFeatureGateStates(log logr.Logger, fg featuregate.FeatureGate) {
fgutil.LogFeatureGateStates(log, "catalogd feature gate status", fg, catalogdFeatureGates)
}
19 changes: 3 additions & 16 deletions internal/operator-controller/features/features.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package features

import (
"sort"

"github.com/go-logr/logr"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/component-base/featuregate"

fgutil "github.com/operator-framework/operator-controller/internal/shared/util/featuregates"
)

const (
Expand Down Expand Up @@ -42,18 +42,5 @@ func init() {

// LogFeatureGateStates logs the state of all known feature gates.
func LogFeatureGateStates(log logr.Logger, fg featuregate.FeatureGate) {
// Sort the keys for consistent logging order
featureKeys := make([]featuregate.Feature, 0, len(operatorControllerFeatureGates))
for k := range operatorControllerFeatureGates {
featureKeys = append(featureKeys, k)
}
sort.Slice(featureKeys, func(i, j int) bool {
return string(featureKeys[i]) < string(featureKeys[j]) // Sort by string representation
})

featurePairs := make([]interface{}, 0, len(featureKeys))
for _, feature := range featureKeys {
featurePairs = append(featurePairs, feature, fg.Enabled(feature))
}
log.Info("feature gate status", featurePairs...)
fgutil.LogFeatureGateStates(log, "feature gate status", fg, operatorControllerFeatureGates)
}
29 changes: 29 additions & 0 deletions internal/shared/util/featuregates/logging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package featuregates

import (
"sort"

"github.com/go-logr/logr"
"k8s.io/component-base/featuregate"
)

// LogFeatureGateStates logs a sorted list of features and their enabled state
// message is the log message under which to record the feature states
// fg is the feature gate instance, and featureDefs is the map of feature specs
func LogFeatureGateStates(log logr.Logger, message string, fg featuregate.FeatureGate, featureDefs map[featuregate.Feature]featuregate.FeatureSpec) {
// Collect and sort feature keys for deterministic ordering
keys := make([]featuregate.Feature, 0, len(featureDefs))
for f := range featureDefs {
keys = append(keys, f)
}
sort.Slice(keys, func(i, j int) bool {
return string(keys[i]) < string(keys[j])
})

// Build key/value pairs for logging
pairs := make([]interface{}, 0, len(keys)*2)
for _, f := range keys {
pairs = append(pairs, f, fg.Enabled(f))
}
log.Info(message, pairs...)
}
78 changes: 78 additions & 0 deletions internal/shared/util/featuregates/logging_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package featuregates_test

import (
"testing"

"github.com/go-logr/logr"
"github.com/stretchr/testify/require"
"k8s.io/component-base/featuregate"

"github.com/operator-framework/operator-controller/internal/shared/util/featuregates"
)

// fakeSink implements logr.LogSink, capturing Info calls for testing
type fakeSink struct {
level int
msg string
keysAndValues []interface{}
}

// Init is part of logr.LogSink
func (f *fakeSink) Init(info logr.RuntimeInfo) {}

// Enabled is part of logr.LogSink
func (f *fakeSink) Enabled(level int) bool { return true }

// Info captures the log level, message, and key/value pairs
func (f *fakeSink) Info(level int, msg string, keysAndValues ...interface{}) {
f.level = level
f.msg = msg
f.keysAndValues = append([]interface{}{}, keysAndValues...)
}

// Error is part of logr.LogSink; not used in this test
func (f *fakeSink) Error(err error, msg string, keysAndValues ...interface{}) {}

// WithValues returns a sink with additional values; for testing, return self
func (f *fakeSink) WithValues(keysAndValues ...interface{}) logr.LogSink { return f }

// WithName returns a sink with a new name; for testing, return self
func (f *fakeSink) WithName(name string) logr.LogSink { return f }

// TestLogFeatureGateStates verifies that LogFeatureGateStates logs features
// sorted alphabetically with their enabled state
func TestLogFeatureGateStates(t *testing.T) {
// Define a set of feature specs with default states
defs := map[featuregate.Feature]featuregate.FeatureSpec{
"AFeature": {Default: false},
"BFeature": {Default: true},
"CFeature": {Default: false},
}

// create a mutable gate and register our definitions
gate := featuregate.NewFeatureGate()
require.NoError(t, gate.Add(defs))

// override CFeature to true.
require.NoError(t, gate.SetFromMap(map[string]bool{
"CFeature": true,
}))

// prepare a fake sink and logger
sink := &fakeSink{}
logger := logr.New(sink)

// log the feature states
featuregates.LogFeatureGateStates(logger, "feature states", gate, defs)

// verify the message
require.Equal(t, "feature states", sink.msg)

// Expect keys sorted: AFeature, BFeature, CFeature
want := []interface{}{
featuregate.Feature("AFeature"), false,
featuregate.Feature("BFeature"), true,
featuregate.Feature("CFeature"), true,
}
require.Equal(t, want, sink.keysAndValues)
}
Loading