Skip to content

Commit 3758fc6

Browse files
committed
Testing: structured arguments
This should make it a bit easier to manage argument overrides than the set of templates we have now, especially while we wait for componentconfig to be everywhere. Arguments can be defaulted, and those defaults can be overriden or appended to, and then finally render to a slice of strings to be passed as arguments and such. This should make it easier to configure the API server without needing to splat out the existing default arguments, maybe splice some stuff out, etc. For example: ```go apiServer.Configure(). Disable("insecure-port"). Append("vmodule", "httplog.go=10"). Set("v", "5") ```
1 parent 72fc2c6 commit 3758fc6

File tree

2 files changed

+425
-0
lines changed

2 files changed

+425
-0
lines changed

pkg/internal/testing/process/arguments.go

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
)
77

88
// RenderTemplates returns an []string to render the templates
9+
//
10+
// Deprecated: this should be removed when we remove APIServer.Args.
911
func RenderTemplates(argTemplates []string, data interface{}) (args []string, err error) {
1012
var t *template.Template
1113

@@ -27,3 +29,216 @@ func RenderTemplates(argTemplates []string, data interface{}) (args []string, er
2729

2830
return
2931
}
32+
33+
// EmptyArguments constructs an empty set of flags with no defaults.
34+
func EmptyArguments() *Arguments {
35+
return &Arguments{
36+
values: make(map[string]Arg),
37+
}
38+
}
39+
40+
// Arguments are structured, overridable arguments.
41+
// Each Arguments object contains some set of default arguments, which may
42+
// be appended to, or overridden.
43+
//
44+
// When ready, you can serialize them to pass to exec.Command and friends using
45+
// AsStrings.
46+
//
47+
// All flag-setting methods return the *same* instance of Arguments so that you
48+
// can chain calls.
49+
type Arguments struct {
50+
// values contains the user-set values for the arguments.
51+
// `values[key] = dontPass` means "don't pass this flag"
52+
// `values[key] = passAsName` means "pass this flag without args like --key`
53+
// `values[key] = []string{a, b, c}` means "--key=a --key=b --key=c`
54+
// any values not explicitly set here will be copied from defaults on final rendering.
55+
values map[string]Arg
56+
}
57+
58+
// Arg is an argument that has one or more values,
59+
// and optionally falls back to default values.
60+
type Arg interface {
61+
// Append adds new values to this argument, returning
62+
// a new instance contain the new value. The intermediate
63+
// argument should generally be assumed to be consumed.
64+
Append(vals ...string) Arg
65+
// Get returns the full set of values, optionally including
66+
// the passed in defaults. If it returns nil, this will be
67+
// skipped. If it returns a non-nil empty slice, it'll be
68+
// assumed that the argument should be passed as name-only.
69+
Get(defaults []string) []string
70+
}
71+
72+
type userArg []string
73+
74+
func (a userArg) Append(vals ...string) Arg {
75+
return userArg(append(a, vals...))
76+
}
77+
func (a userArg) Get(_ []string) []string {
78+
return []string(a)
79+
}
80+
81+
type defaultedArg []string
82+
83+
func (a defaultedArg) Append(vals ...string) Arg {
84+
return defaultedArg(append(a, vals...))
85+
}
86+
func (a defaultedArg) Get(defaults []string) []string {
87+
res := append([]string(nil), defaults...)
88+
return append(res, a...)
89+
}
90+
91+
type dontPassArg struct{}
92+
93+
func (a dontPassArg) Append(vals ...string) Arg {
94+
return userArg(vals)
95+
}
96+
func (dontPassArg) Get(_ []string) []string {
97+
return nil
98+
}
99+
100+
type passAsNameArg struct{}
101+
102+
func (a passAsNameArg) Append(_ ...string) Arg {
103+
return passAsNameArg{}
104+
}
105+
func (passAsNameArg) Get(_ []string) []string {
106+
return []string{}
107+
}
108+
109+
var (
110+
// DontPass indicates that the given argument will not actually be
111+
// rendered.
112+
DontPass Arg = dontPassArg{}
113+
// PassAsName indicates that the given flag will be passed as `--key`
114+
// without any value.
115+
PassAsName Arg = passAsNameArg{}
116+
)
117+
118+
// AsStrings serializes this set of arguments to a slice of strings appropriate
119+
// for passing to exec.Command and friends, making use of the given defaults
120+
// as indicated for each particular argument.
121+
//
122+
// - Any flag in defaults that's not in Arguments will be present in the output
123+
// - Any flag that's present in Arguments will be passed the corresponding
124+
// defaults to do with as it will (ignore, append-to, suppress, etc).
125+
func (a *Arguments) AsStrings(defaults map[string][]string) []string {
126+
// sort for deterministic ordering
127+
keysInOrder := make([]string, 0, len(defaults)+len(a.values))
128+
for key := range defaults {
129+
if _, userSet := a.values[key]; userSet {
130+
continue
131+
}
132+
keysInOrder = append(keysInOrder, key)
133+
}
134+
for key := range a.values {
135+
keysInOrder = append(keysInOrder, key)
136+
}
137+
sort.Strings(keysInOrder)
138+
139+
var res []string
140+
for _, key := range keysInOrder {
141+
vals := a.Get(key).Get(defaults[key])
142+
switch {
143+
case vals == nil: // don't pass
144+
continue
145+
case len(vals) == 0: // pass as name
146+
res = append(res, "--"+key)
147+
default:
148+
for _, val := range vals {
149+
res = append(res, "--"+key+"="+val)
150+
}
151+
}
152+
}
153+
154+
return res
155+
}
156+
157+
// Get returns the value of the given flag. If nil,
158+
// it will not be passed in AsString, otherwise:
159+
//
160+
// len == 0 --> `--key`, len > 0 --> `--key=val1 --key=val2 ...`
161+
func (a *Arguments) Get(key string) Arg {
162+
if vals, ok := a.values[key]; ok {
163+
return vals
164+
}
165+
return defaultedArg(nil)
166+
}
167+
168+
// Enable configures the given key to be passed as a "name-only" flag,
169+
// like, `--key`.
170+
func (a *Arguments) Enable(key string) *Arguments {
171+
a.values[key] = PassAsName
172+
return a
173+
}
174+
175+
// Disable prevents this flag from be passed.
176+
func (a *Arguments) Disable(key string) *Arguments {
177+
a.values[key] = DontPass
178+
return a
179+
}
180+
181+
// Append adds additional values to this flag. If this flag has
182+
// yet to be set, initial values will include defaults. If you want
183+
// to intentionally ignore defaults/start from scratch, call AppendNoDefaults.
184+
//
185+
// Multiple values will look like `--key=value1 --key=value2 ...`.
186+
func (a *Arguments) Append(key string, values ...string) *Arguments {
187+
vals, present := a.values[key]
188+
if !present {
189+
vals = defaultedArg{}
190+
}
191+
a.values[key] = vals.Append(values...)
192+
return a
193+
}
194+
195+
// AppendNoDefaults adds additional values to this flag. However,
196+
// unlike Append, it will *not* copy values from defaults.
197+
func (a *Arguments) AppendNoDefaults(key string, values ...string) *Arguments {
198+
vals, present := a.values[key]
199+
if !present {
200+
vals = userArg{}
201+
}
202+
a.values[key] = vals.Append(values...)
203+
return a
204+
}
205+
206+
// Set resets the given flag to the specified values, ignoring any existing
207+
// values or defaults.
208+
func (a *Arguments) Set(key string, values ...string) *Arguments {
209+
a.values[key] = userArg(values)
210+
return a
211+
}
212+
213+
// SetRaw sets the given flag to the given Arg value directly. Use this if
214+
// you need to do some complicated deferred logic or something.
215+
//
216+
// Otherwise behaves like Set.
217+
func (a *Arguments) SetRaw(key string, val Arg) *Arguments {
218+
a.values[key] = val
219+
return a
220+
}
221+
222+
// FuncArg is a basic implementation of Arg that can be used for custom argument logic,
223+
// like pulling values out of APIServer, or dynamically calculating values just before
224+
// launch.
225+
//
226+
// The given function will be mapped directly to Arg#Get, and will generally be
227+
// used in conjunction with SetRaw. For example, to set `--some-flag` to the
228+
// API server's CertDir, you could do:
229+
//
230+
// server.Configure().SetRaw("--some-flag", FuncArg(func(defaults []string) []string {
231+
// return []string{server.CertDir}
232+
// }))
233+
//
234+
// FuncArg ignores Appends; if you need to support appending values too, consider implementing
235+
// Arg directly.
236+
type FuncArg func([]string) []string
237+
238+
// Append is a no-op for FuncArg, and just returns itself.
239+
func (a FuncArg) Append(vals ...string) Arg { return a }
240+
241+
// Get delegates functionality to the FuncArg function itself.
242+
func (a FuncArg) Get(defaults []string) []string {
243+
return a(defaults)
244+
}

0 commit comments

Comments
 (0)