// Copyright 2025 The frp 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" "strings" "sync" "sync/atomic" ) // Feature represents a feature gate name type Feature string // FeatureStage represents the maturity level of a feature type FeatureStage string const ( // Alpha means the feature is experimental and disabled by default Alpha FeatureStage = "ALPHA" // Beta means the feature is more stable but still might change and is disabled by default Beta FeatureStage = "BETA" // GA means the feature is generally available and enabled by default GA FeatureStage = "" ) // FeatureSpec describes a feature and its properties type FeatureSpec struct { // Default is the default enablement state for the feature Default bool // LockToDefault indicates the feature cannot be changed from its default LockToDefault bool // Stage indicates the maturity level of the feature Stage FeatureStage } // Define all available features here var ( VirtualNet = Feature("VirtualNet") ) // defaultFeatures defines default features with their specifications var defaultFeatures = map[Feature]FeatureSpec{ // Actual features VirtualNet: {Default: false, Stage: Alpha}, } // 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 known features KnownFeatures() []string } // MutableFeatureGate allows for dynamic feature gate configuration type MutableFeatureGate interface { FeatureGate // SetFromMap sets feature gate values from a map[string]bool SetFromMap(m map[string]bool) error // Add adds features to the feature gate Add(features map[Feature]FeatureSpec) error // String returns a string representing the feature gate configuration String() string } // featureGate implements the FeatureGate and MutableFeatureGate interfaces type featureGate struct { // 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 once the feature gates are considered immutable closed bool } // NewFeatureGate creates a new feature gate with the default features func NewFeatureGate() MutableFeatureGate { known := map[Feature]FeatureSpec{} for k, v := range defaultFeatures { known[k] = v } f := &featureGate{} f.known.Store(known) f.enabled.Store(map[Feature]bool{}) return f } // SetFromMap sets feature gate values from a map[string]bool 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 } // Apply the new settings 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 } // Persist the changes f.known.Store(known) f.enabled.Store(enabled) return nil } // Add adds features to the feature gate func (f *featureGate) Add(features map[Feature]FeatureSpec) error { f.lock.Lock() defer f.lock.Unlock() if f.closed { return fmt.Errorf("cannot add feature gates after the feature gate is closed") } // Copy existing state known := map[Feature]FeatureSpec{} for k, v := range f.known.Load().(map[Feature]FeatureSpec) { known[k] = v } // Add new features 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 changes f.known.Store(known) 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, ",") } // Enabled returns true if the key is enabled 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 } return false } // KnownFeatures returns a slice of strings describing the FeatureGate's known features // GA features are hidden from the list func (f *featureGate) KnownFeatures() []string { knownFeatures := f.known.Load().(map[Feature]FeatureSpec) known := make([]string, 0, len(knownFeatures)) for k, v := range knownFeatures { if v.Stage == GA { continue } known = append(known, fmt.Sprintf("%s=true|false (%s - default=%t)", k, v.Stage, v.Default)) } sort.Strings(known) return known } // Default feature gates instance var DefaultFeatureGates = NewFeatureGate() // Enabled checks if a feature is enabled in the default feature gates func Enabled(name Feature) bool { return DefaultFeatureGates.Enabled(name) } // SetFromMap sets feature gate values from a map in the default feature gates func SetFromMap(featureMap map[string]bool) error { return DefaultFeatureGates.SetFromMap(featureMap) }