Skip to content

Commit a7fc7ec

Browse files
authored
Improvement/argument handling (#127)
* finalize cli argument wizard * add error handler for init() * move detect to utils * Update tests * Update README
1 parent 0b914f5 commit a7fc7ec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+444
-310
lines changed

README.md

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ Inspired by [NETWAYS/icinga2-diagnostics](https://github.com/Icinga/icinga2-diag
1616

1717
## Usage
1818

19+
`$ support-collector`
20+
21+
The CLI wizard will guide you through the possible arguments after calling the command.
22+
If you prefer to pass the arguments in the command call, use `--non-interactive` and pass the arguments as described in the documentation.
23+
24+
`--hide`, `--command-timeout` can only be used as CLI argument.
25+
26+
You can also combine your CLI arguments and the wizard. All arguments you pass from the CLI will be given into the wizard.
27+
28+
---
29+
1930
> **WARNING:** Some passwords or secrets are automatically removed, but this no guarantee, so be careful what you share!
2031
2132
The `--hide` flag can be used multiple times to hide sensitive data, it supports regular expressions.
@@ -29,23 +40,25 @@ By default, we collect all we can find. You can control this by only enabling ce
2940

3041
If you want to see what is collected, add `--verbose`
3142

32-
To collect advanced data for module `Icinga 2`, you can use the Icinga 2 API to collect data from all endpoints provided.
33-
The API requests are performed with a global API user you have to create yourself. Just create that user in a global zone like 'director-global'
34-
35-
36-
| Short | Long | Description |
37-
|:-----:|:------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
38-
| -o | --output | Output file for the zip content (default: current directory and named like '$HOSTNAME'-netways-support-$TIMESTAMP.zip) |
39-
| | --nodetails | Disable detailed collection including logs and more |
40-
| | --enable | List of enabled modules (default: all) |
41-
| | --disable | List of disabled modules (default: none) |
42-
| | --hide | List of keywords to obfuscate. Can be used multiple times |
43-
| | --command-timeout | Timeout for command execution in modules (default: 1m0s) |
44-
| | --icinga2-api-user | Username of global Icinga 2 API user to collect data about Icinga 2 Infrastructure |
45-
| | --icinga2-api-pass | Password for global Icinga 2 API user to collect data about Icinga 2 Infrastructure |
46-
| | --icinga2-api-endpoints | List of Icinga 2 API Endpoints (including port) to collect data from. FQDN or IP address must be reachable. (Example: i2-master01.local:5665) |
47-
| -v | --verbose | Enable verbose logging |
48-
| -V | --version | Print version and exit |
43+
To collect advanced data for module `Icinga 2`, you can use the Icinga 2 API to collect data from all endpoints
44+
provided.
45+
The API requests are performed with a global API user you have to create yourself. Just create that user in a global
46+
zone like 'director-global' to sync it to all endpoints
47+
48+
| Short | Long | Description |
49+
|:-----:|:------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
50+
| -o | --output | Output file for the zip content (default: current directory and named like '\$HOSTNAME'-netways-support-\$TIMESTAMP.zip) |
51+
| | --non-interactive | Disable the interactive CLI wizard |
52+
| | --no-details | Disable detailed collection including logs and more |
53+
| | --enable | List of enabled modules (default: all) |
54+
| | --disable | List of disabled modules (default: none) |
55+
| | --hide | List of keywords to obfuscate. Can be used multiple times |
56+
| | --command-timeout | Timeout for command execution in modules (default: 1m0s) |
57+
| | --icinga2-api-user | Username of global Icinga 2 API user to collect data about Icinga 2 Infrastructure (Optional and only for module `icinga2`) |
58+
| | --icinga2-api-pass | Password for global Icinga 2 API user to collect data about Icinga 2 Infrastructure (Optional and only for module `icinga2`) |
59+
| | --icinga2-api-endpoints | List of Icinga 2 API Endpoints (including port) to collect data from. FQDN or IP address must be reachable. (Example: i2-master01.local:5665) (Optional and only for module `icinga2`) |
60+
| -v | --verbose | Enable verbose logging |
61+
| -V | --version | Print version and exit |
4962

5063
## Modules
5164

go.mod

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,5 @@ require (
1616
github.com/davecgh/go-spew v1.1.1 // indirect
1717
github.com/mattn/go-isatty v0.0.16 // indirect
1818
github.com/pmezard/go-difflib v1.0.0 // indirect
19-
golang.org/x/mod v0.16.0 // indirect
2019
golang.org/x/sys v0.18.0 // indirect
21-
golang.org/x/tools v0.19.0 // indirect
2220
)

go.sum

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
1919
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
2020
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
2121
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
22-
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
23-
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
2422
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
25-
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
2623
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2724
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
2825
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
29-
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
30-
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
3126
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
3227
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3328
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/arguments/arguments.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package arguments
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
flag "github.com/spf13/pflag"
7+
"os"
8+
"strings"
9+
)
10+
11+
var (
12+
NonInteractive bool
13+
validBooleanInputs = map[string]bool{
14+
"y": true,
15+
"yes": true,
16+
"n": false,
17+
"no": false,
18+
"true": true,
19+
"false": false,
20+
}
21+
)
22+
23+
const interactiveHelpText = `Welcome to the support-collector argument wizard!
24+
We will guide you through all required details.
25+
26+
Available modules are: %s`
27+
28+
type Argument struct {
29+
Name string
30+
InputFunction func()
31+
Dependency func() bool
32+
}
33+
34+
type Handler struct {
35+
scanner *bufio.Scanner
36+
arguments []Argument
37+
}
38+
39+
// New creates a new Handler object
40+
func New() Handler {
41+
return Handler{
42+
scanner: bufio.NewScanner(os.Stdin),
43+
}
44+
}
45+
46+
func (args *Handler) CollectArgsFromStdin(availableModules string) []error {
47+
fmt.Printf(interactiveHelpText+"\n\n", availableModules)
48+
49+
errors := make([]error, 0, len(args.arguments))
50+
51+
for _, argument := range args.arguments {
52+
if argument.Dependency == nil {
53+
argument.InputFunction()
54+
continue
55+
}
56+
57+
if ok := argument.Dependency(); ok {
58+
argument.InputFunction()
59+
continue
60+
}
61+
62+
errors = append(errors, fmt.Errorf("argument '%s' is not matching the needed depenency. Skipping... ", argument.Name))
63+
}
64+
65+
fmt.Print("\nInteractive wizard finished. Starting...\n\n")
66+
67+
return errors
68+
}
69+
70+
func (args *Handler) NewPromptStringVar(callback *string, name, defaultValue, usage string, required bool, dependency func() bool) {
71+
flag.StringVar(callback, name, defaultValue, usage)
72+
73+
args.arguments = append(args.arguments, Argument{
74+
Name: name,
75+
InputFunction: func() {
76+
if *callback != "" {
77+
defaultValue = *callback
78+
}
79+
80+
args.newStringPrompt(callback, defaultValue, usage, required)
81+
},
82+
Dependency: dependency,
83+
})
84+
}
85+
86+
func (args *Handler) NewPromptStringSliceVar(callback *[]string, name string, defaultValue []string, usage string, required bool, dependency func() bool) {
87+
flag.StringSliceVar(callback, name, defaultValue, usage)
88+
89+
args.arguments = append(args.arguments, Argument{
90+
Name: name,
91+
InputFunction: func() {
92+
if len(*callback) > 0 {
93+
defaultValue = *callback
94+
}
95+
96+
var input string
97+
98+
args.newStringPrompt(&input, strings.Join(defaultValue, ","), usage, required)
99+
*callback = strings.Split(input, ",")
100+
},
101+
Dependency: dependency,
102+
})
103+
}
104+
105+
func (args *Handler) NewPromptBoolVar(callback *bool, name string, defaultValue bool, usage string, dependency func() bool) {
106+
flag.BoolVar(callback, name, defaultValue, usage)
107+
108+
args.arguments = append(args.arguments, Argument{
109+
Name: name,
110+
InputFunction: func() {
111+
args.newBoolPrompt(callback, defaultValue, usage)
112+
},
113+
Dependency: dependency,
114+
})
115+
}
116+
117+
func (args *Handler) newStringPrompt(callback *string, defaultValue, usage string, required bool) {
118+
for {
119+
fmt.Printf("%s - (Preselection: '%s'): ", usage, defaultValue)
120+
121+
if args.scanner.Scan() {
122+
input := args.scanner.Text()
123+
124+
switch {
125+
case input != "":
126+
*callback = input
127+
return
128+
case input == "" && defaultValue != "":
129+
*callback = defaultValue
130+
return
131+
case input == "" && !required:
132+
return
133+
}
134+
} else {
135+
if err := args.scanner.Err(); err != nil {
136+
_, _ = fmt.Fprintln(os.Stderr, "reading standard input:", err)
137+
return
138+
}
139+
}
140+
}
141+
}
142+
143+
func (args *Handler) newBoolPrompt(callback *bool, defaultValue bool, usage string) {
144+
for {
145+
fmt.Printf("%s [y/n] - (Preselection: '%t'): ", usage, defaultValue)
146+
147+
if args.scanner.Scan() {
148+
input := strings.ToLower(args.scanner.Text())
149+
150+
if input != "" && isValidBoolString(input) {
151+
*callback = validBooleanInputs[input]
152+
break
153+
} else if input == "" {
154+
*callback = defaultValue
155+
break
156+
}
157+
}
158+
}
159+
}
160+
161+
func isValidBoolString(input string) bool {
162+
if _, ok := validBooleanInputs[input]; !ok {
163+
return false
164+
}
165+
166+
return true
167+
}

internal/util/module.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package util
2+
3+
import "os"
4+
5+
// ModuleExists checks if module is installed based on given relevant paths
6+
func ModuleExists(paths []string) bool {
7+
for _, path := range paths {
8+
_, err := os.Stat(path)
9+
if err == nil {
10+
return true
11+
}
12+
}
13+
14+
return false
15+
}

0 commit comments

Comments
 (0)