diff --git a/go.mod b/go.mod index af00404..778016a 100644 --- a/go.mod +++ b/go.mod @@ -14,9 +14,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/cobra v1.8.1 // indirect golang.org/x/sys v0.18.0 // indirect ) diff --git a/go.sum b/go.sum index 39b427d..cf0d2e3 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,8 @@ github.com/Showmax/go-fqdn v1.0.0 h1:0rG5IbmVliNT5O19Mfuvna9LL7zlHyRfsSvBPZmF9tM= github.com/Showmax/go-fqdn v1.0.0/go.mod h1:SfrFBzmDCtCGrnHhoDjuvFnKsWjEQX/Q9ARZvOrJAko= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= @@ -14,11 +11,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/internal/arguments/arguments.go b/internal/arguments/arguments.go index 35a6358..1b88f4d 100644 --- a/internal/arguments/arguments.go +++ b/internal/arguments/arguments.go @@ -11,70 +11,110 @@ import ( var ( NonInteractive bool validBooleanInputs = map[string]bool{ - "y": true, - "yes": true, - "n": false, - "no": false, + "y": true, + "yes": true, + "n": false, + "no": false, + "true": true, + "false": false, } ) +const interactiveHelpText = `Welcome to the support-collector argument wizard! +We will guide you through all required details. + +Available modules are: %s` + +type Argument struct { + Name string + InputFunction func() + Dependency func() bool +} + type Handler struct { - scanner *bufio.Scanner - prompts []func() + scanner *bufio.Scanner + arguments []Argument } -func NewHandler() Handler { +// New creates a new Handler object +func New() Handler { return Handler{ scanner: bufio.NewScanner(os.Stdin), } } -func (h *Handler) ReadArgumentsFromStdin() { - for _, prompt := range h.prompts { - prompt() +func (args *Handler) CollectArgsFromStdin(availableModules string) { + fmt.Printf(interactiveHelpText+"\n\n", availableModules) + + var errors []error + + for _, argument := range args.arguments { + if argument.Dependency == nil { + argument.InputFunction() + continue + } + + if ok := argument.Dependency(); ok { + argument.InputFunction() + continue + } + + errors = append(errors, fmt.Errorf("%s is not matching the needed depenency", argument.Name)) } } -func (h *Handler) NewPromptStringVar(callback *string, name, defaultValue, usage string, required bool) { +func (args *Handler) NewPromptStringVar(callback *string, name, defaultValue, usage string, required bool, dependency func() bool) { flag.StringVar(callback, name, defaultValue, usage) - h.prompts = append(h.prompts, func() { - if *callback != "" { - defaultValue = *callback - } + args.arguments = append(args.arguments, Argument{ + Name: name, + InputFunction: func() { + if *callback != "" { + defaultValue = *callback + } - h.newStringPrompt(callback, defaultValue, usage, required) + args.newStringPrompt(callback, defaultValue, usage, required) + }, + Dependency: dependency, }) } -func (h *Handler) NewPromptStringSliceVar(callback *[]string, name string, defaultValue []string, usage string, required bool) { +func (args *Handler) NewPromptStringSliceVar(callback *[]string, name string, defaultValue []string, usage string, required bool, dependency func() bool) { flag.StringSliceVar(callback, name, defaultValue, usage) - h.prompts = append(h.prompts, func() { - if len(*callback) > 0 { - defaultValue = *callback - } + args.arguments = append(args.arguments, Argument{ + Name: name, + InputFunction: func() { + if len(*callback) > 0 { + defaultValue = *callback + } - var input string + var input string - h.newStringPrompt(&input, strings.Join(defaultValue, ","), usage, required) - *callback = strings.Split(input, ",") + args.newStringPrompt(&input, strings.Join(defaultValue, ","), usage, required) + *callback = strings.Split(input, ",") + }, + Dependency: dependency, }) } -func (h *Handler) NewPromptBoolVar(callback *bool, name string, defaultValue bool, usage string) { +func (args *Handler) NewPromptBoolVar(callback *bool, name string, defaultValue bool, usage string, dependency func() bool) { flag.BoolVar(callback, name, defaultValue, usage) - h.prompts = append(h.prompts, func() { - h.newBoolPrompt(callback, defaultValue, usage) + args.arguments = append(args.arguments, Argument{ + Name: name, + InputFunction: func() { + args.newBoolPrompt(callback, defaultValue, usage) + }, + Dependency: dependency, }) } -func (h *Handler) newStringPrompt(callback *string, defaultValue, usage string, required bool) { +func (args *Handler) newStringPrompt(callback *string, defaultValue, usage string, required bool) { for { fmt.Printf("%s - (Preselection: '%s'): ", usage, defaultValue) - if h.scanner.Scan() { - input := h.scanner.Text() + if args.scanner.Scan() { + input := args.scanner.Text() if input != "" { *callback = input break @@ -85,7 +125,7 @@ func (h *Handler) newStringPrompt(callback *string, defaultValue, usage string, break } } else { - if err := h.scanner.Err(); err != nil { + if err := args.scanner.Err(); err != nil { _, _ = fmt.Fprintln(os.Stderr, "reading standard input:", err) break } @@ -95,12 +135,12 @@ func (h *Handler) newStringPrompt(callback *string, defaultValue, usage string, return } -func (h *Handler) newBoolPrompt(callback *bool, defaultValue bool, usage string) { +func (args *Handler) newBoolPrompt(callback *bool, defaultValue bool, usage string) { for { fmt.Printf("%s [y/n] - (Preselection: '%t'): ", usage, defaultValue) - if h.scanner.Scan() { - input := strings.ToLower(h.scanner.Text()) + if args.scanner.Scan() { + input := strings.ToLower(args.scanner.Text()) if input != "" && isValidBoolString(input) { *callback = validBooleanInputs[input] diff --git a/main.go b/main.go index f8b2c28..4fd05cb 100644 --- a/main.go +++ b/main.go @@ -120,26 +120,24 @@ func init() { // Set locale to C, to avoid translations in command output _ = os.Setenv("LANG", "C") - args := arguments.NewHandler() + args := arguments.New() // General arguments without interactive prompt - flag.BoolVar(&arguments.NonInteractive, "nonInteractive", false, "Enable non-interactive mode") + flag.BoolVar(&arguments.NonInteractive, "non-interactive", false, "Enable non-interactive mode") flag.BoolVar(&printVersion, "version", false, "Print version and exit") flag.BoolVar(&verbose, "verbose", false, "Enable verbose logging") - - // TODO - //flag.DurationVar(&commandTimeout, "command-timeout", commandTimeout, "Timeout for command execution in modules") + flag.DurationVar(&commandTimeout, "command-timeout", commandTimeout, "Timeout for command execution in modules") // Run specific arguments - args.NewPromptStringVar(&outputFile, "output", buildFileName(), "Output file for the ZIP content", true) - args.NewPromptStringSliceVar(&enabledModules, "enable", moduleOrder, "Comma separated list of enabled modules", false) - args.NewPromptStringSliceVar(&disabledModules, "disable", []string{}, "Comma separated list of disabled modules", false) - args.NewPromptBoolVar(&noDetailedCollection, "nodetails", false, "Disable detailed collection including logs and more") + args.NewPromptStringVar(&outputFile, "output", buildFileName(), "Filename for resulting zip", true, nil) + args.NewPromptStringSliceVar(&enabledModules, "enable", moduleOrder, "Enabled modules for collection (comma seperated)", false, nil) + args.NewPromptStringSliceVar(&disabledModules, "disable", []string{}, "Explicit disabled modules for collection (comma seperated)", false, nil) + args.NewPromptBoolVar(&noDetailedCollection, "no-details", false, "Disable detailed collection including logs and more", nil) // Icinga 2 specific arguments - args.NewPromptStringVar(&icinga2.APICred.Username, "icinga2-api-user", "", "Username of global Icinga 2 API user to collect data about Icinga 2 Infrastructure", false) - args.NewPromptStringVar(&icinga2.APICred.Password, "icinga2-api-pass", "", "Password for global Icinga 2 API user to collect data about Icinga 2 Infrastructure", false) - args.NewPromptStringSliceVar(&icinga2.APIEndpoints, "icinga2-api-endpoints", []string{}, "Comma separated list of Icinga 2 API Endpoints (including port) to collect data from. FQDN or IP address must be reachable. (Example: i2-master01.local:5665)", false) + args.NewPromptStringVar(&icinga2.APICred.Username, "icinga2-api-user", "", "Username of global Icinga 2 API user to collect data about Icinga 2 Infrastructure", false, icinga2Enabled) + args.NewPromptStringVar(&icinga2.APICred.Password, "icinga2-api-pass", "", "Password for global Icinga 2 API user to collect data about Icinga 2 Infrastructure", false, icinga2Enabled) + args.NewPromptStringSliceVar(&icinga2.APIEndpoints, "icinga2-api-endpoints", []string{}, "Comma separated list of Icinga 2 API Endpoints (including port) to collect data from. FQDN or IP address must be reachable. (Example: i2-master01.local:5665)", false, icinga2Enabled) flag.CommandLine.SortFlags = false @@ -152,6 +150,7 @@ func init() { flag.PrintDefaults() } + // Parse flags from command-line flag.Parse() if printVersion { @@ -159,8 +158,9 @@ func init() { os.Exit(0) } + // Start interactive wizard if interactive is enabled if !arguments.NonInteractive { - args.ReadArgumentsFromStdin() + args.CollectArgsFromStdin(strings.Join(moduleOrder, ",")) } // Verify enabled modules @@ -169,10 +169,6 @@ func init() { logrus.Fatal("Unknown module to enable: ", name) } } - - fmt.Println(noDetailedCollection) - - os.Exit(1) } // buildFileName returns a filename to store the output of support collector. @@ -180,8 +176,20 @@ func buildFileName() string { return FilePrefix + "_" + util.GetHostnameWithoutDomain() + "_" + time.Now().Format("20060102-1504") + ".zip" } +func icinga2Enabled() bool { + for _, name := range enabledModules { + if name == "icinga2" { + return true + } + } + + return false +} + func main() { + // Initialize new collection c, closeCollection := NewCollection(outputFile) + // Close collection defer closeCollection()