Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvement/argument handling #127

Merged
merged 7 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 30 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ Inspired by [NETWAYS/icinga2-diagnostics](https://github.com/Icinga/icinga2-diag

## Usage

`$ support-collector`

The CLI wizard will guide you through the possible arguments after calling the command.
If you prefer to pass the arguments in the command call, use `--non-interactive` and pass the arguments as described in the documentation.

`--hide`, `--command-timeout` can only be used as CLI argument.

You can also combine your CLI arguments and the wizard. All arguments you pass from the CLI will be given into the wizard.

---

> **WARNING:** Some passwords or secrets are automatically removed, but this no guarantee, so be careful what you share!

The `--hide` flag can be used multiple times to hide sensitive data, it supports regular expressions.
Expand All @@ -26,23 +37,25 @@ By default, we collect all we can find. You can control this by only enabling ce

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

To collect advanced data for module `Icinga 2`, you can use the Icinga 2 API to collect data from all endpoints provided.
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'


| Short | Long | Description |
|:-----:|:------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| -o | --output | Output file for the zip content (default: current directory and named like '$HOSTNAME'-netways-support-$TIMESTAMP.zip) |
| | --nodetails | Disable detailed collection including logs and more |
| | --enable | List of enabled modules (default: all) |
| | --disable | List of disabled modules (default: none) |
| | --hide | List of keywords to obfuscate. Can be used multiple times |
| | --command-timeout | Timeout for command execution in modules (default: 1m0s) |
| | --icinga2-api-user | Username of global Icinga 2 API user to collect data about Icinga 2 Infrastructure |
| | --icinga2-api-pass | Password for global Icinga 2 API user to collect data about Icinga 2 Infrastructure |
| | --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) |
| -v | --verbose | Enable verbose logging |
| -V | --version | Print version and exit |
To collect advanced data for module `Icinga 2`, you can use the Icinga 2 API to collect data from all endpoints
provided.
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' to sync it to all endpoints

| Short | Long | Description |
|:-----:|:------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| -o | --output | Output file for the zip content (default: current directory and named like '\$HOSTNAME'-netways-support-\$TIMESTAMP.zip) |
| | --non-interactive | Disable the interactive CLI wizard |
| | --no-details | Disable detailed collection including logs and more |
| | --enable | List of enabled modules (default: all) |
| | --disable | List of disabled modules (default: none) |
| | --hide | List of keywords to obfuscate. Can be used multiple times |
| | --command-timeout | Timeout for command execution in modules (default: 1m0s) |
| | --icinga2-api-user | Username of global Icinga 2 API user to collect data about Icinga 2 Infrastructure (Optional and only for module `icinga2`) |
| | --icinga2-api-pass | Password for global Icinga 2 API user to collect data about Icinga 2 Infrastructure (Optional and only for module `icinga2`) |
| | --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`) |
| -v | --verbose | Enable verbose logging |
| -V | --version | Print version and exit |

## Modules

Expand Down
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,5 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/tools v0.19.0 // indirect
)
5 changes: 0 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
167 changes: 167 additions & 0 deletions internal/arguments/arguments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package arguments

import (
"bufio"
"fmt"
flag "github.com/spf13/pflag"
"os"
"strings"
)

var (
NonInteractive bool
validBooleanInputs = map[string]bool{
"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
arguments []Argument
}

// New creates a new Handler object
func New() Handler {
return Handler{
scanner: bufio.NewScanner(os.Stdin),
}
}

func (args *Handler) CollectArgsFromStdin(availableModules string) []error {
fmt.Printf(interactiveHelpText+"\n\n", availableModules)

errors := make([]error, 0, len(args.arguments))

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("argument '%s' is not matching the needed depenency. Skipping... ", argument.Name))
}

fmt.Print("\nInteractive wizard finished. Starting...\n\n")

return errors
}

func (args *Handler) NewPromptStringVar(callback *string, name, defaultValue, usage string, required bool, dependency func() bool) {
flag.StringVar(callback, name, defaultValue, usage)

args.arguments = append(args.arguments, Argument{
Name: name,
InputFunction: func() {
if *callback != "" {
defaultValue = *callback
}

args.newStringPrompt(callback, defaultValue, usage, required)
},
Dependency: dependency,
})
}

func (args *Handler) NewPromptStringSliceVar(callback *[]string, name string, defaultValue []string, usage string, required bool, dependency func() bool) {
flag.StringSliceVar(callback, name, defaultValue, usage)

args.arguments = append(args.arguments, Argument{
Name: name,
InputFunction: func() {
if len(*callback) > 0 {
defaultValue = *callback
}

var input string

args.newStringPrompt(&input, strings.Join(defaultValue, ","), usage, required)
*callback = strings.Split(input, ",")
},
Dependency: dependency,
})
}

func (args *Handler) NewPromptBoolVar(callback *bool, name string, defaultValue bool, usage string, dependency func() bool) {
flag.BoolVar(callback, name, defaultValue, usage)

args.arguments = append(args.arguments, Argument{
Name: name,
InputFunction: func() {
args.newBoolPrompt(callback, defaultValue, usage)
},
Dependency: dependency,
})
}

func (args *Handler) newStringPrompt(callback *string, defaultValue, usage string, required bool) {
for {
fmt.Printf("%s - (Preselection: '%s'): ", usage, defaultValue)

if args.scanner.Scan() {
input := args.scanner.Text()

switch {
case input != "":
*callback = input
return
case input == "" && defaultValue != "":
*callback = defaultValue
return
case input == "" && !required:
return
}
} else {
if err := args.scanner.Err(); err != nil {
_, _ = fmt.Fprintln(os.Stderr, "reading standard input:", err)
return
}
}
}
}

func (args *Handler) newBoolPrompt(callback *bool, defaultValue bool, usage string) {
for {
fmt.Printf("%s [y/n] - (Preselection: '%t'): ", usage, defaultValue)

if args.scanner.Scan() {
input := strings.ToLower(args.scanner.Text())

if input != "" && isValidBoolString(input) {
*callback = validBooleanInputs[input]
break
} else if input == "" {
*callback = defaultValue
break
}
}
}
}

func isValidBoolString(input string) bool {
if _, ok := validBooleanInputs[input]; !ok {
return false
}

return true
}
15 changes: 15 additions & 0 deletions internal/util/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package util

import "os"

// ModuleExists checks if module is installed based on given relevant paths
func ModuleExists(paths []string) bool {
for _, path := range paths {
_, err := os.Stat(path)
if err == nil {
return true
}
}

return false
}
Loading