Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/config/features/features.go
4096 views
1
// Package features enables a way to encode enabled features in a
2
// flag.FlagSet.
3
package features
4
5
import (
6
"flag"
7
"fmt"
8
"sort"
9
"strings"
10
)
11
12
// Feature is an experimental feature. Features are case-insensitive.
13
type Feature string
14
15
const setFlagName = "enable-features"
16
17
// Register sets a flag in fs to track enabled features. The list of possible
18
// features is enumerated by ff. ff must contain a unique set of case-insensitive
19
// features. Register will panic if ff is invalid.
20
func Register(fs *flag.FlagSet, ff []Feature) {
21
var (
22
cache = make(map[Feature]struct{}, len(ff))
23
names = make([]string, len(ff))
24
)
25
for i, f := range ff {
26
normalized := normalize(f)
27
if _, found := cache[normalized]; found {
28
panic(fmt.Sprintf("case-insensitive feature %q registered twice", normalized))
29
}
30
cache[normalized] = struct{}{}
31
names[i] = string(normalized)
32
}
33
34
help := fmt.Sprintf("Comma-delimited list of features to enable. Valid values: %s", strings.Join(names, ", "))
35
36
s := set{valid: cache, validString: strings.Join(names, ", ")}
37
fs.Var(&s, setFlagName, help)
38
}
39
40
func normalize(f Feature) Feature {
41
return Feature(strings.ToLower(string(f)))
42
}
43
44
// Enabled returns true if a feature is enabled. Enable will panic if fs has
45
// not been passed to Register or name is an unknown feature.
46
func Enabled(fs *flag.FlagSet, name Feature) bool {
47
name = normalize(name)
48
49
f := fs.Lookup(setFlagName)
50
if f == nil {
51
panic("feature flag not registered to fs")
52
}
53
s, ok := f.Value.(*set)
54
if !ok {
55
panic("registered feature flag not appropriate type")
56
}
57
58
if _, valid := s.valid[name]; !valid {
59
panic(fmt.Sprintf("unknown feature %q", name))
60
}
61
_, enabled := s.enabled[name]
62
return enabled
63
}
64
65
// Dependency marks a Flag as depending on a specific feature being enabled.
66
type Dependency struct {
67
// Flag must be a flag name from a FlagSet.
68
Flag string
69
// Feature which must be enabled for Flag to be provided at the command line.
70
Feature Feature
71
}
72
73
// Validate returns an error if any flags from deps were used without the
74
// corresponding feature being enabled.
75
//
76
// If deps references a flag that is not in fs, Validate will panic.
77
func Validate(fs *flag.FlagSet, deps []Dependency) error {
78
depLookup := make(map[string]Dependency, len(deps))
79
80
for _, dep := range deps {
81
if fs.Lookup(dep.Flag) == nil {
82
panic(fmt.Sprintf("flag %q does not exist in fs", dep.Flag))
83
}
84
depLookup[dep.Flag] = dep
85
86
// Ensure that the feature also exists. We ignore the result here;
87
// we just want to propagate the panic behavior.
88
_ = Enabled(fs, dep.Feature)
89
}
90
91
var err error
92
93
// Iterate over all the flags that were passed at the command line.
94
// Flags that were passed and are present in deps MUST also have their
95
// corresponding feature enabled.
96
fs.Visit(func(f *flag.Flag) {
97
// If we have an error to return, stop iterating.
98
if err != nil {
99
return
100
}
101
102
dep, ok := depLookup[f.Name]
103
if !ok {
104
return
105
}
106
107
// Flag was provided and exists in deps.
108
if !Enabled(fs, dep.Feature) {
109
err = fmt.Errorf("flag %q requires feature %q to be provided in --%s", f.Name, dep.Feature, setFlagName)
110
}
111
})
112
113
return err
114
}
115
116
// GetAllEnabled returns the list of all enabled features
117
func GetAllEnabled(fs *flag.FlagSet) []string {
118
f := fs.Lookup(setFlagName)
119
if f == nil {
120
panic("feature flag not registered to fs")
121
}
122
s, ok := f.Value.(*set)
123
if !ok {
124
panic("registered feature flag not appropriate type")
125
}
126
var enabled []string
127
for feature := range s.enabled {
128
enabled = append(enabled, string(feature))
129
}
130
return enabled
131
}
132
133
// set implements flag.Value and holds the set of enabled features.
134
// set should be provided to a flag.FlagSet with:
135
//
136
// var s features.set
137
// fs.Var(&s, features.SetFlag, "")
138
type set struct {
139
valid map[Feature]struct{}
140
validString string // Comma-delimited list of acceptable values
141
142
enabled map[Feature]struct{}
143
}
144
145
// Set implements flag.Value.
146
func (s *set) String() string {
147
res := make([]string, 0, len(s.enabled))
148
for k := range s.enabled {
149
res = append(res, string(k))
150
}
151
sort.Strings(res)
152
return strings.Join(res, ",")
153
}
154
155
// Set implements flag.Value.
156
func (s *set) Set(in string) error {
157
slice := strings.Split(in, ",")
158
159
m := make(map[Feature]struct{}, len(slice))
160
for _, input := range slice {
161
f := normalize(Feature(input))
162
if _, valid := s.valid[f]; !valid {
163
return fmt.Errorf("unknown feature %q. possible options: %s", f, s.validString)
164
} else if _, ok := m[f]; ok {
165
return fmt.Errorf("%q already set", f)
166
}
167
m[f] = struct{}{}
168
}
169
170
s.enabled = m
171
return nil
172
}
173
174