Skip to content

Commit ddfdfdf

Browse files
committed
Testing: use structured arguments
This converts the internal testing package to use structured arguments, introducing a new helper to preserve compat when specifying legacy args.
1 parent 3758fc6 commit ddfdfdf

File tree

4 files changed

+283
-19
lines changed

4 files changed

+283
-19
lines changed

pkg/internal/testing/controlplane/apiserver.go

+54-8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ type APIServer struct {
4444
//
4545
// If not specified, the minimal set of arguments to run the APIServer will
4646
// be used.
47+
//
48+
// They will be loaded into the same argument set as Configure. Each flag
49+
// will be Append-ed to the configured arguments just before launch.
50+
//
51+
// Deprecated: use Configure instead.
4752
Args []string
4853

4954
// CertDir is a path to a directory containing whatever certificates the
@@ -72,20 +77,34 @@ type APIServer struct {
7277
Err io.Writer
7378

7479
processState *process.State
80+
81+
// args contains the structured arguments to use for running the API server
82+
// Lazily initialized by .Configure(), Defaulted eventually with .defaultArgs()
83+
args *process.Arguments
84+
}
85+
86+
// Configure returns Arguments that may be used to customize the
87+
// flags used to launch the API server. A set of defaults will
88+
// be applied underneath.
89+
func (s *APIServer) Configure() *process.Arguments {
90+
if s.args == nil {
91+
s.args = process.EmptyArguments()
92+
}
93+
return s.args
7594
}
7695

7796
// Start starts the apiserver, waits for it to come up, and returns an error,
7897
// if occurred.
7998
func (s *APIServer) Start() error {
8099
if s.processState == nil {
81-
if err := s.setState(); err != nil {
100+
if err := s.setProcessState(); err != nil {
82101
return err
83102
}
84103
}
85104
return s.processState.Start(s.Out, s.Err)
86105
}
87106

88-
func (s *APIServer) setState() error {
107+
func (s *APIServer) setProcessState() error {
89108
if s.EtcdURL == nil {
90109
return fmt.Errorf("expected EtcdURL to be configured")
91110
}
@@ -133,15 +152,42 @@ func (s *APIServer) setState() error {
133152
return err
134153
}
135154

136-
args := s.Args
137-
if len(args) == 0 {
138-
args = APIServerDefaultArgs
139-
}
140-
141-
s.processState.Args, err = process.RenderTemplates(args, s)
155+
s.processState.Args, err = process.TemplateAndArguments(s.Args, s.Configure(), process.TemplateDefaults{
156+
Data: s,
157+
Defaults: s.defaultArgs(),
158+
// as per kubernetes-sigs/controller-runtime#641, we need this (we
159+
// probably need other stuff too, but this is the only thing that was
160+
// previously considered a "minimal default")
161+
MinimalDefaults: map[string][]string{
162+
"service-cluster-ip-range": []string{"10.0.0.0/24"},
163+
},
164+
})
142165
return err
143166
}
144167

168+
func (s *APIServer) defaultArgs() map[string][]string {
169+
args := map[string][]string{
170+
"advertise-address": []string{"127.0.0.1"},
171+
"service-cluster-ip-range": []string{"10.0.0.0/24"},
172+
"allow-privileged": []string{"true"},
173+
// we're keeping this disabled because if enabled, default SA is
174+
// missing which would force all tests to create one in normal
175+
// apiserver operation this SA is created by controller, but that is
176+
// not run in integration environment
177+
"disable-admission-plugins": []string{"ServiceAccount"},
178+
"cert-dir": []string{s.CertDir},
179+
"secure-port": []string{strconv.Itoa(s.SecurePort)},
180+
}
181+
if s.EtcdURL != nil {
182+
args["etcd-servers"] = []string{s.EtcdURL.String()}
183+
}
184+
if s.URL != nil {
185+
args["insecure-port"] = []string{s.URL.Port()}
186+
args["insecure-bind-address"] = []string{s.URL.Hostname()}
187+
}
188+
return args
189+
}
190+
145191
func (s *APIServer) populateAPIServerCerts() error {
146192
_, statErr := os.Stat(filepath.Join(s.CertDir, "apiserver.crt"))
147193
if !os.IsNotExist(statErr) {

pkg/internal/testing/controlplane/etcd.go

+38-10
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ type Etcd struct {
3636
//
3737
// If not specified, the minimal set of arguments to run the Etcd will be
3838
// used.
39+
//
40+
// They will be loaded into the same argument set as Configure. Each flag
41+
// will be Append-ed to the configured arguments just before launch.
42+
//
43+
// Deprecated: use Configure instead.
3944
Args []string
4045

4146
// DataDir is a path to a directory in which etcd can store its state.
@@ -59,22 +64,24 @@ type Etcd struct {
5964

6065
// processState contains the actual details about this running process
6166
processState *process.State
67+
68+
// args contains the structured arguments to use for running etcd.
69+
// Lazily initialized by .Configure(), Defaulted eventually with .defaultArgs()
70+
args *process.Arguments
6271
}
6372

6473
// Start starts the etcd, waits for it to come up, and returns an error, if one
6574
// occoured.
6675
func (e *Etcd) Start() error {
6776
if e.processState == nil {
68-
if err := e.setState(); err != nil {
77+
if err := e.setProcessState(); err != nil {
6978
return err
7079
}
7180
}
7281
return e.processState.Start(e.Out, e.Err)
7382
}
7483

75-
func (e *Etcd) setState() error {
76-
var err error
77-
84+
func (e *Etcd) setProcessState() error {
7885
e.processState = &process.State{
7986
Dir: e.DataDir,
8087
Path: e.Path,
@@ -106,12 +113,11 @@ func (e *Etcd) setState() error {
106113
e.StartTimeout = e.processState.StartTimeout
107114
e.StopTimeout = e.processState.StopTimeout
108115

109-
args := e.Args
110-
if len(args) == 0 {
111-
args = EtcdDefaultArgs
112-
}
113-
114-
e.processState.Args, err = process.RenderTemplates(args, e)
116+
var err error
117+
e.processState.Args, err = process.TemplateAndArguments(e.Args, e.Configure(), process.TemplateDefaults{
118+
Data: e,
119+
Defaults: e.defaultArgs(),
120+
})
115121
return err
116122
}
117123

@@ -121,6 +127,28 @@ func (e *Etcd) Stop() error {
121127
return e.processState.Stop()
122128
}
123129

130+
func (e *Etcd) defaultArgs() map[string][]string {
131+
args := map[string][]string{
132+
"listen-peer-urls": []string{"http://localhost:0"},
133+
"data-dir": []string{e.DataDir},
134+
}
135+
if e.URL != nil {
136+
args["advertise-client-urls"] = []string{e.URL.String()}
137+
args["listen-client-urls"] = []string{e.URL.String()}
138+
}
139+
return args
140+
}
141+
142+
// Configure returns Arguments that may be used to customize the
143+
// flags used to launch etcd. A set of defaults will
144+
// be applied underneath.
145+
func (e *Etcd) Configure() *process.Arguments {
146+
if e.args == nil {
147+
e.args = process.EmptyArguments()
148+
}
149+
return e.args
150+
}
151+
124152
// EtcdDefaultArgs exposes the default args for Etcd so that you
125153
// can use those to append your own additional arguments.
126154
var EtcdDefaultArgs = []string{

pkg/internal/testing/process/arguments.go

+78-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package process
33
import (
44
"bytes"
55
"html/template"
6+
"sort"
7+
"strings"
68
)
79

810
// RenderTemplates returns an []string to render the templates
911
//
10-
// Deprecated: this should be removed when we remove APIServer.Args.
12+
// Deprecated: will be removed in favor of Arguments
1113
func RenderTemplates(argTemplates []string, data interface{}) (args []string, err error) {
1214
var t *template.Template
1315

@@ -30,6 +32,81 @@ func RenderTemplates(argTemplates []string, data interface{}) (args []string, er
3032
return
3133
}
3234

35+
// SliceToArguments converts a slice of arguments to structured arguments,
36+
// appending each argument that starts with `--` and contains an `=` to the
37+
// argument set, returning the rest.
38+
//
39+
// Deprecated: will be removed when RenderTemplates is removed
40+
func SliceToArguments(sliceArgs []string, args *Arguments) []string {
41+
var rest []string
42+
for i, arg := range sliceArgs {
43+
if arg == "--" {
44+
rest = append(rest, sliceArgs[i:]...)
45+
return rest
46+
}
47+
// skip non-flag arguments, skip arguments w/o equals because we
48+
// can't tell if the next argument should take a value
49+
if !strings.HasPrefix(arg, "--") || !strings.Contains(arg, "=") {
50+
rest = append(rest, arg)
51+
continue
52+
}
53+
54+
parts := strings.SplitN(arg[2:], "=", 2)
55+
name := parts[0]
56+
val := parts[1]
57+
58+
args.AppendNoDefaults(name, val)
59+
}
60+
61+
return rest
62+
}
63+
64+
// TemplateDefaults specifies defaults to be used for joining structured arguments with templates.
65+
//
66+
// Deprecated: will be removed when RenderTemplates is removed
67+
type TemplateDefaults struct {
68+
// Data will be used to render the template.
69+
Data interface{}
70+
// Defaults will be used to default structured arguments if no template is passed.
71+
Defaults map[string][]string
72+
// MinimalDefaults will be used to default structured arguments if a template is passed.
73+
// Use this for flags which *must* be present.
74+
MinimalDefaults map[string][]string // for api server service-cluster-ip-range
75+
}
76+
77+
// TemplateAndArguments joins structured arguments and non-structured arguments, preserving existing
78+
// behavior. Namely:
79+
//
80+
// 1. if templ has len > 0, it will be rendered against data
81+
// 2. the rendered template values that look like `--foo=bar` will be split
82+
// and appended to args, the rest will be kept around
83+
// 3. the given args will be rendered as string form. If a template is given,
84+
// no defaults will be used, otherwise defaults will be used
85+
// 4. a result of [args..., rest...] will be returned
86+
//
87+
// Deprecated: will be removed when RenderTemplates is removed
88+
func TemplateAndArguments(templ []string, args *Arguments, data TemplateDefaults) ([]string, error) {
89+
if len(templ) == 0 { // 3 & 4 (no template case)
90+
return args.AsStrings(data.Defaults), nil
91+
}
92+
93+
// 1: render the template
94+
rendered, err := RenderTemplates(templ, data.Data)
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
// 2: filter out structured args and add them to args
100+
rest := SliceToArguments(rendered, args)
101+
102+
// 3 (template case): render structured args, no defaults (matching the
103+
// legacy case where if Args was specified, no defaults were used)
104+
res := args.AsStrings(data.MinimalDefaults)
105+
106+
// 4: return the rendered structured args + all non-structured args
107+
return append(res, rest...), nil
108+
}
109+
33110
// EmptyArguments constructs an empty set of flags with no defaults.
34111
func EmptyArguments() *Arguments {
35112
return &Arguments{

0 commit comments

Comments
 (0)