Skip to content

Commit 086a89e

Browse files
committed
diagnostics: enable per-diagnostic parameters
Adds the ability to specify parameters for individual diagnostics on the command line (without proliferating flags). Addresses #14640
1 parent 47aa2c1 commit 086a89e

File tree

9 files changed

+156
-5
lines changed

9 files changed

+156
-5
lines changed

contrib/completions/bash/oadm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2065,6 +2065,8 @@ _oadm_diagnostics()
20652065
local_nonpersistent_flags+=("--images=")
20662066
flags+=("--latest-images")
20672067
local_nonpersistent_flags+=("--latest-images")
2068+
flags+=("--list-params")
2069+
local_nonpersistent_flags+=("--list-params")
20682070
flags+=("--loglevel=")
20692071
local_nonpersistent_flags+=("--loglevel=")
20702072
flags+=("--logspec=")

contrib/completions/bash/oc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,6 +2230,8 @@ _oc_adm_diagnostics()
22302230
local_nonpersistent_flags+=("--images=")
22312231
flags+=("--latest-images")
22322232
local_nonpersistent_flags+=("--latest-images")
2233+
flags+=("--list-params")
2234+
local_nonpersistent_flags+=("--list-params")
22332235
flags+=("--loglevel=")
22342236
local_nonpersistent_flags+=("--loglevel=")
22352237
flags+=("--logspec=")

contrib/completions/bash/openshift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2065,6 +2065,8 @@ _openshift_admin_diagnostics()
20652065
local_nonpersistent_flags+=("--images=")
20662066
flags+=("--latest-images")
20672067
local_nonpersistent_flags+=("--latest-images")
2068+
flags+=("--list-params")
2069+
local_nonpersistent_flags+=("--list-params")
20682070
flags+=("--loglevel=")
20692071
local_nonpersistent_flags+=("--loglevel=")
20702072
flags+=("--logspec=")
@@ -7488,6 +7490,8 @@ _openshift_cli_adm_diagnostics()
74887490
local_nonpersistent_flags+=("--images=")
74897491
flags+=("--latest-images")
74907492
local_nonpersistent_flags+=("--latest-images")
7493+
flags+=("--list-params")
7494+
local_nonpersistent_flags+=("--list-params")
74917495
flags+=("--loglevel=")
74927496
local_nonpersistent_flags+=("--loglevel=")
74937497
flags+=("--logspec=")

contrib/completions/zsh/oadm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2214,6 +2214,8 @@ _oadm_diagnostics()
22142214
local_nonpersistent_flags+=("--images=")
22152215
flags+=("--latest-images")
22162216
local_nonpersistent_flags+=("--latest-images")
2217+
flags+=("--list-params")
2218+
local_nonpersistent_flags+=("--list-params")
22172219
flags+=("--loglevel=")
22182220
local_nonpersistent_flags+=("--loglevel=")
22192221
flags+=("--logspec=")

contrib/completions/zsh/oc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2379,6 +2379,8 @@ _oc_adm_diagnostics()
23792379
local_nonpersistent_flags+=("--images=")
23802380
flags+=("--latest-images")
23812381
local_nonpersistent_flags+=("--latest-images")
2382+
flags+=("--list-params")
2383+
local_nonpersistent_flags+=("--list-params")
23822384
flags+=("--loglevel=")
23832385
local_nonpersistent_flags+=("--loglevel=")
23842386
flags+=("--logspec=")

contrib/completions/zsh/openshift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2214,6 +2214,8 @@ _openshift_admin_diagnostics()
22142214
local_nonpersistent_flags+=("--images=")
22152215
flags+=("--latest-images")
22162216
local_nonpersistent_flags+=("--latest-images")
2217+
flags+=("--list-params")
2218+
local_nonpersistent_flags+=("--list-params")
22172219
flags+=("--loglevel=")
22182220
local_nonpersistent_flags+=("--loglevel=")
22192221
flags+=("--logspec=")
@@ -7637,6 +7639,8 @@ _openshift_cli_adm_diagnostics()
76377639
local_nonpersistent_flags+=("--images=")
76387640
flags+=("--latest-images")
76397641
local_nonpersistent_flags+=("--latest-images")
7642+
flags+=("--list-params")
7643+
local_nonpersistent_flags+=("--list-params")
76407644
flags+=("--loglevel=")
76417645
local_nonpersistent_flags+=("--loglevel=")
76427646
flags+=("--logspec=")

pkg/diagnostics/types/diagnostic.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ type Diagnostic interface {
2424
Check() DiagnosticResult
2525
}
2626

27+
// ParameterizedDiagnostic is a Diagnostic that can accept arbitrary parameters specifically for it.
28+
// AvailableParameters is used to describe or validate the parameters given on the command line.
29+
// Then SetParameters is called with those parameters.
30+
type ParameterizedDiagnostic interface {
31+
Diagnostic
32+
AvailableParameters() map[string]string
33+
SetParameters(map[string]string) error
34+
}
35+
2736
// DiagnosticResult provides a result object for diagnostics, accumulating the messages and errors
2837
// that the diagnostic generates as it runs.
2938
type DiagnosticResult interface {

pkg/oc/admin/diagnostics/diagnostics.go

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ import (
3333
type DiagnosticsOptions struct {
3434
// list of diagnostic names to limit what is run
3535
RequestedDiagnostics []string
36+
// parameters to specify diagnostic-specific options (name => map of params)
37+
Parameters map[string][]string
38+
// after building diagnostics, list their parameters and exit
39+
ListParameters bool
40+
3641
// specify locations of host config files
3742
MasterConfigLocation string
3843
NodeConfigLocation string
@@ -102,9 +107,13 @@ var (
102107
* If a client config file is not found, client and cluster diagnostics
103108
are skipped.
104109
105-
Diagnostics may be individually run by passing diagnostic name as arguments.
110+
Diagnostics may be individually run by passing diagnostic name(s) as arguments.
111+
112+
%[1]s DiagnosticName
113+
114+
Diagnostic parameters may be given values similarly:
106115
107-
%[1]s <DiagnosticName>
116+
%[1]s DiagnosticName.parameter=value
108117
109118
The available diagnostic names are: %[2]s.`)
110119
)
@@ -113,6 +122,7 @@ var (
113122
func NewCmdDiagnostics(name string, fullName string, out io.Writer) *cobra.Command {
114123
o := &DiagnosticsOptions{
115124
RequestedDiagnostics: []string{},
125+
Parameters: map[string][]string{},
116126
LogOptions: &log.LoggerOptions{Out: out},
117127
ImageTemplate: variable.NewDefaultImageTemplate(),
118128
NetworkOptions: &NetworkDiagnosticsOptions{},
@@ -141,6 +151,7 @@ func NewCmdDiagnostics(name string, fullName string, out io.Writer) *cobra.Comma
141151

142152
o.ClientFlags = flag.NewFlagSet("client", flag.ContinueOnError) // hide the extensive set of client flags
143153
o.Factory = osclientcmd.New(o.ClientFlags) // that would otherwise be added to this command
154+
cmd.Flags().BoolVar(&o.ListParameters, options.FlagListParametersName, false, "List parameters for specified diagnostic(s) and exit")
144155
cmd.Flags().AddFlag(o.ClientFlags.Lookup(config.OpenShiftConfigFlagName))
145156
cmd.Flags().AddFlag(o.ClientFlags.Lookup("context")) // TODO: find k8s constant
146157
cmd.Flags().StringVar(&o.ClientClusterContext, options.FlagClusterContextName, "", "Client context to use for cluster administrator")
@@ -182,6 +193,13 @@ func (o *DiagnosticsOptions) Complete(args []string) error {
182193
}
183194
}
184195

196+
if err = o.completeNetworkOptions(); err != nil {
197+
return err
198+
}
199+
return o.processArgs(args)
200+
}
201+
202+
func (o *DiagnosticsOptions) completeNetworkOptions() error {
185203
if len(o.NetworkOptions.LogDir) == 0 {
186204
o.NetworkOptions.LogDir = netutil.NetworkDiagDefaultLogDir
187205
} else {
@@ -208,10 +226,39 @@ func (o *DiagnosticsOptions) Complete(args []string) error {
208226
if kvalidation.IsValidPortNum(o.NetworkOptions.TestPodPort) != nil {
209227
return fmt.Errorf("invalid port for network diagnostic test pod. Must be in the range 1 to 65535.")
210228
}
229+
return nil
230+
}
211231

212-
o.RequestedDiagnostics = append(o.RequestedDiagnostics, args...)
213-
if len(o.RequestedDiagnostics) == 0 {
214-
o.RequestedDiagnostics = availableDiagnostics().Difference(defaultSkipDiagnostics()).List()
232+
// Sort out the diagnostic names and any of their parameters.
233+
// oc adm diagnostics # all standard diagnostics
234+
// oc adm diagnostics Foo # only Foo
235+
// oc adm diagnostics Foo Foo Foo # only Foo (and only once)
236+
// oc adm diagnostics Foo.bar=baz # all standard diagnostics and Foo, with param for Foo
237+
// oc adm diagnostics Foo Foo.bar=baz # only Foo with param
238+
// oc adm diagnostics Foo Spam.eggs=ugh # only Foo and Spam with param
239+
func (o *DiagnosticsOptions) processArgs(args []string) error {
240+
explicitDiagnostics := map[string]bool{} // names by themselves, not from a parameter
241+
requestedDiagnostics := map[string]bool{} // names by themselves or from a parameter
242+
for _, arg := range args {
243+
// if arg includes a . then it is a param; otherwise just a name
244+
argsplit := strings.SplitN(arg, ".", 2)
245+
name := argsplit[0]
246+
if len(argsplit) == 1 { // name by itself
247+
explicitDiagnostics[name] = true
248+
requestedDiagnostics[name] = true
249+
} else { // name.parameter
250+
requestedDiagnostics[name] = true // note, not counted as explicit
251+
o.Parameters[name] = append(o.Parameters[name], argsplit[1])
252+
}
253+
}
254+
if len(explicitDiagnostics) == 0 {
255+
// if none were explicitly given, include all available (except default skipped)
256+
for _, name := range availableDiagnostics().Difference(defaultSkipDiagnostics()).List() {
257+
requestedDiagnostics[name] = true
258+
}
259+
}
260+
for name := range requestedDiagnostics {
261+
o.RequestedDiagnostics = append(o.RequestedDiagnostics, name)
215262
}
216263

217264
return nil
@@ -320,6 +367,27 @@ func (o DiagnosticsOptions) RunDiagnostics() (bool, error, int, int) {
320367
if err != nil {
321368
errors = append(errors, err)
322369
}
370+
371+
// this has to run after the diagnostics are built to know what params they take
372+
if o.ListParameters {
373+
paramList := []string{}
374+
for _, diag := range diagnostics {
375+
if pd, ok := diag.(types.ParameterizedDiagnostic); ok {
376+
paramList = append(paramList, listDiagnosticParameters(pd)...)
377+
}
378+
}
379+
if len(paramList) > 0 {
380+
o.Logger.Info("CED3021", strings.Join(paramList, "\n"))
381+
} else {
382+
o.Logger.Info("CED3022", "There are no parameters for the selected diagnostics.")
383+
}
384+
os.Exit(0)
385+
}
386+
// log parameter errors as user errors and fail
387+
if err := o.parameterizeDiagnostics(diagnostics); err != nil {
388+
failed = true
389+
o.Logger.Error("CED3023", err.Error())
390+
}
323391
}()
324392

325393
if failed {
@@ -332,6 +400,63 @@ func (o DiagnosticsOptions) RunDiagnostics() (bool, error, int, int) {
332400
return failed, err, numWarnings, numErrors
333401
}
334402

403+
func (o DiagnosticsOptions) parameterizeDiagnostics(diagnostics []types.Diagnostic) error {
404+
errors := []string{}
405+
for _, diag := range diagnostics {
406+
if pd, ok := diag.(types.ParameterizedDiagnostic); ok {
407+
available := pd.AvailableParameters()
408+
paramList := o.Parameters[pd.Name()]
409+
paramMap := map[string]string{}
410+
diagErrors := []string{}
411+
for _, param := range paramList {
412+
if split := strings.SplitN(param, "=", 2); len(split) == 2 {
413+
name := split[0]
414+
if available[name] == "" {
415+
diagErrors = append(diagErrors, fmt.Sprintf("Diagnostic %s has no parameter '%s'.", pd.Name(), name))
416+
continue
417+
}
418+
paramMap[name] = split[1]
419+
} else { // Diagnostic.foo with no =value
420+
diagErrors = append(errors, fmt.Sprintf("Diagnostic parameter sets no value: %s.%s", pd.Name(), param))
421+
}
422+
423+
}
424+
if len(diagErrors) > 0 {
425+
errors = append(errors, diagErrors...)
426+
errors = append(errors, listDiagnosticParameters(pd)...)
427+
continue
428+
}
429+
430+
if err := pd.SetParameters(paramMap); err != nil {
431+
errors = append(errors, fmt.Sprintf("Error setting %s parameters: %v", pd.Name(), err))
432+
errors = append(errors, listDiagnosticParameters(pd)...)
433+
continue
434+
}
435+
} else {
436+
if o.Parameters[diag.Name()] != nil {
437+
errors = append(errors, fmt.Sprintf("Diagnostic %s accepts no parameters.", diag.Name()))
438+
continue
439+
}
440+
}
441+
}
442+
if len(errors) == 0 {
443+
return nil
444+
}
445+
return fmt.Errorf("Error with diagnostic parameters:\n%s", strings.Join(errors, "\n"))
446+
}
447+
448+
func listDiagnosticParameters(pd types.ParameterizedDiagnostic) []string {
449+
params := pd.AvailableParameters()
450+
paramlist := []string{fmt.Sprintf("Valid parameters for %s are:", pd.Name())}
451+
if params == nil {
452+
return append(paramlist, " None")
453+
}
454+
for _, key := range sets.StringKeySet(params).List() {
455+
paramlist = append(paramlist, fmt.Sprintf(" %s: %s", key, params[key]))
456+
}
457+
return paramlist
458+
}
459+
335460
// Run performs the actual execution of diagnostics once they're built.
336461
func (o DiagnosticsOptions) Run(diagnostics []types.Diagnostic) (bool, error, int, int) {
337462
warnCount := 0

pkg/oc/admin/diagnostics/options/flaginfo.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const (
5353
FlagImageTemplateName = "images"
5454
FlagLatestImageName = "latest-images"
5555
FlagPreventModificationName = "prevent-modification"
56+
FlagListParametersName = "list-params"
5657

5758
FlagNetworkDiagLogDir = "network-logdir"
5859
FlagNetworkDiagPodImage = "network-pod-image"

0 commit comments

Comments
 (0)