Skip to content

Commit

Permalink
finalize cli argument wizard
Browse files Browse the repository at this point in the history
  • Loading branch information
tbauriedel committed Aug 28, 2024
1 parent d0a6164 commit cd4db20
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 59 deletions.
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
6 changes: 0 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand All @@ -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=
Expand Down
108 changes: 74 additions & 34 deletions internal/arguments/arguments.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand All @@ -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]
Expand Down
42 changes: 25 additions & 17 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -152,15 +150,17 @@ func init() {
flag.PrintDefaults()
}

// Parse flags from command-line
flag.Parse()

if printVersion {
fmt.Println(Product, "version", getBuildInfo()) //nolint:forbidigo
os.Exit(0)
}

// Start interactive wizard if interactive is enabled
if !arguments.NonInteractive {
args.ReadArgumentsFromStdin()
args.CollectArgsFromStdin(strings.Join(moduleOrder, ","))
}

// Verify enabled modules
Expand All @@ -169,19 +169,27 @@ 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.
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()

Expand Down

0 comments on commit cd4db20

Please sign in to comment.