From 5b3afea6692c9d4c2d74b0d18dc4a737d666e0b3 Mon Sep 17 00:00:00 2001 From: Anton Prokhorov Date: Sun, 14 Jul 2024 16:09:27 +0100 Subject: [PATCH] Better CLI completion (#171) --- cmd/client/config.go | 2 +- cmd/client/main.go | 101 ++++++++++++++------ config.toml | 9 -- internal/actions/completion.go | 80 ++++++++++++++++ internal/actions/dns_records.go | 21 +++- internal/actions/events.go | 8 +- internal/actions/http_routes.go | 21 +++- internal/actions/mock/Actions.go | 2 +- internal/actions/payloads.go | 38 ++++---- internal/actions/user.go | 2 +- internal/actions/users.go | 4 +- internal/cmd/cmd.go | 53 ++++------ internal/cmd/generated.go | 36 +++---- internal/cmd/messengers.go | 3 +- internal/cmd/options.go | 15 +-- internal/codegen/code.go | 4 +- internal/modules/api/apiclient/generated.go | 2 +- internal/modules/api/generated.go | 8 +- internal/modules/telegram/telegram.go | 4 +- 19 files changed, 265 insertions(+), 148 deletions(-) delete mode 100644 config.toml create mode 100644 internal/actions/completion.go diff --git a/cmd/client/config.go b/cmd/client/config.go index 510f459..beb32d2 100644 --- a/cmd/client/config.go +++ b/cmd/client/config.go @@ -35,7 +35,7 @@ func (c Config) ValidateWithContext(ctx context.Context) error { } func (c *Config) Server() *Server { - srv, ok := cfg.Servers[cfg.Context.Server] + srv, ok := c.Servers[c.Context.Server] if !ok { return nil } diff --git a/cmd/client/main.go b/cmd/client/main.go index af1aef4..647136b 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -14,6 +14,7 @@ import ( "github.com/gookit/color" "github.com/spf13/cobra" "github.com/spf13/viper" + "golang.org/x/exp/maps" "github.com/nt0xa/sonar/internal/actions" "github.com/nt0xa/sonar/internal/cmd" @@ -21,33 +22,37 @@ import ( "github.com/nt0xa/sonar/internal/templates" ) -var ( - cfg Config - cfgFile string - jsonOutput bool -) - func init() { validation.ErrorTag = "err" - cobra.OnInitialize(initConfig) } func main() { + var ( + cfg Config + cfgFile string + jsonOutput bool + ) + c := cmd.New( nil, cmd.AllowFileAccess(true), - cmd.PreExec(func(root *cobra.Command) { - addConfigFlag(root) - addJSONFlag(root) - addContextCommand(root) - }), - cmd.InitActions(func() (actions.Actions, error) { - srv := cfg.Server() - if srv == nil { - return nil, errors.New("server must be set") + cmd.PreExec(func(acts *actions.Actions, root *cobra.Command) { + root.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { + if err := initConfig(cfgFile, &cfg); err != nil { + return err + } + + if err := initActions(acts, cfg); err != nil { + return err + } + + return nil } - client := apiclient.New(srv.URL, srv.Token, srv.Insecure, srv.Proxy) - return client, nil + + // Flags, commands... + root.PersistentFlags().StringVar(&cfgFile, "config", "", "config file") + jsonFlag(root, &jsonOutput) + contextCmd(root, &cfg) }), ) @@ -78,25 +83,23 @@ func main() { } } -func addConfigFlag(root *cobra.Command) { - root.PersistentFlags().StringVar(&cfgFile, "config", "", "config file") -} - -func addJSONFlag(root *cobra.Command) { +func jsonFlag(root *cobra.Command, jsonOutput *bool) { for _, cmd := range root.Commands() { if cmd.HasSubCommands() { - addJSONFlag(cmd) + jsonFlag(cmd, jsonOutput) } - if cmd.Name() == "help" || cmd.Name() == "completion" { + if cmd.Name() == "help" || + cmd.Name() == "completion" || + strings.HasPrefix(cmd.Name(), "_") { continue } - cmd.Flags().BoolVar(&jsonOutput, "json", false, "JSON output") + cmd.Flags().BoolVar(jsonOutput, "json", false, "JSON output") } } -func addContextCommand(root *cobra.Command) { +func contextCmd(root *cobra.Command, cfg *Config) { var server string cmd := &cobra.Command{ @@ -124,10 +127,18 @@ func addContextCommand(root *cobra.Command) { cmd.Flags().StringVarP(&server, "server", "s", "", "Server name from list of servers") viper.BindPFlag("context.server", cmd.Flags().Lookup("server")) + cmd.RegisterFlagCompletionFunc("server", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var cfg Config + if err := initConfig(findFlagValue("config", os.Args), &cfg); err != nil { + return nil, cobra.ShellCompDirectiveDefault + } + return maps.Keys(cfg.Servers), cobra.ShellCompDirectiveNoFileComp + }) + root.AddCommand(cmd) } -func initConfig() { +func initConfig(cfgFile string, cfg *Config) error { if cfgFile != "" { // Use config file from the flag. viper.SetConfigFile(cfgFile) @@ -143,7 +154,35 @@ func initConfig() { viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() - cobra.CheckErr(viper.ReadInConfig()) - cobra.CheckErr(viper.Unmarshal(&cfg)) - cobra.CheckErr(cfg.ValidateWithContext(context.Background())) + if err := viper.ReadInConfig(); err != nil { + return err + } + + if err := viper.Unmarshal(cfg); err != nil { + return err + } + + if err := cfg.ValidateWithContext(context.Background()); err != nil { + return err + } + + return nil +} + +func initActions(acts *actions.Actions, cfg Config) error { + srv := cfg.Server() + if srv == nil { + return errors.New("server must be set") + } + *acts = apiclient.New(srv.URL, srv.Token, srv.Insecure, srv.Proxy) + return nil +} + +func findFlagValue(f string, args []string) string { + for i := 1; i < len(args); i++ { + if args[i-1] == "--"+f { + return args[i] + } + } + return "" } diff --git a/config.toml b/config.toml deleted file mode 100644 index d4ff403..0000000 --- a/config.toml +++ /dev/null @@ -1,9 +0,0 @@ -[context] -server = 'test' - -[servers] -[servers.test] -token = '04a752d6c759f59978755f554bffa08c' -url = 'https://sonar.test:31337' -insecure = true -proxy = 'http://localhost:8080' diff --git a/internal/actions/completion.go b/internal/actions/completion.go new file mode 100644 index 0000000..00f029e --- /dev/null +++ b/internal/actions/completion.go @@ -0,0 +1,80 @@ +package actions + +import ( + "slices" + "strings" + + "github.com/spf13/cobra" +) + +type completionFunc func( + cmd *cobra.Command, + args []string, + toComplete string, +) ([]string, cobra.ShellCompDirective) + +func completePayloadName(acts *Actions) completionFunc { + return func( + cmd *cobra.Command, + _ []string, + toComplete string, + ) ([]string, cobra.ShellCompDirective) { + payloads, err := (*acts).PayloadsList(cmd.Context(), PayloadsListParams{ + Name: toComplete, + }) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + names := make([]string, len(payloads)) + + for i, p := range payloads { + names[i] = p.Name + } + + return names, cobra.ShellCompDirectiveNoFileComp + } +} + +func completeOne(list []string) completionFunc { + return func( + _ *cobra.Command, + _ []string, + _ string, + ) ([]string, cobra.ShellCompDirective) { + return list, cobra.ShellCompDirectiveNoFileComp + } +} + +func completeMany(completions []string) completionFunc { + return func( + _ *cobra.Command, + _ []string, + toComplete string, + ) ([]string, cobra.ShellCompDirective) { + // aaa,bbb,c -> [aaa, bbb, c] + parts := strings.Split(toComplete, ",") + + // [aaa, bb, c] -> prefix = "aaa,bbb," + prefix := strings.Join(parts[:len(parts)-1], ",") + if prefix != "" { + prefix += "," + } + + // lastPart = "c" + lastPart := parts[len(parts)-1] + + // Filter completions based on the current input + var result []string + for _, comp := range completions { + if slices.Contains(parts, comp) { + continue + } + if strings.HasPrefix(comp, lastPart) { + result = append(result, prefix+comp) + } + } + + return result, cobra.ShellCompDirectiveNoSpace + } +} diff --git a/internal/actions/dns_records.go b/internal/actions/dns_records.go index b16b671..7f635d6 100644 --- a/internal/actions/dns_records.go +++ b/internal/actions/dns_records.go @@ -70,9 +70,9 @@ func (r DNSRecordsCreateResult) ResultID() string { return DNSRecordsCreateResultID } -func DNSRecordsCreateCommand(p *DNSRecordsCreateParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func DNSRecordsCreateCommand(acts *Actions, p *DNSRecordsCreateParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ - Use: "new VALUES", + Use: "new VALUES...", Short: "Create new DNS records", Args: atLeastOneArg("VALUES"), } @@ -85,6 +85,11 @@ func DNSRecordsCreateCommand(p *DNSRecordsCreateParams, local bool) (*cobra.Comm cmd.Flags().StringVarP(&p.Strategy, "strategy", "s", models.DNSStrategyAll, fmt.Sprintf("Strategy for multiple records (one of %s)", quoteAndJoin(models.DNSStrategiesAll))) + _ = cmd.MarkFlagRequired("name") + _ = cmd.RegisterFlagCompletionFunc("payload", completePayloadName(acts)) + _ = cmd.RegisterFlagCompletionFunc("type", completeOne(models.DNSTypesAll)) + _ = cmd.RegisterFlagCompletionFunc("strategy", completeOne(models.DNSStrategiesAll)) + return cmd, func(cmd *cobra.Command, args []string) errors.Error { p.Values = args return nil @@ -115,7 +120,7 @@ func (r DNSRecordsDeleteResult) ResultID() string { return DNSRecordsDeleteResultID } -func DNSRecordsDeleteCommand(p *DNSRecordsDeleteParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func DNSRecordsDeleteCommand(acts *Actions, p *DNSRecordsDeleteParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "del INDEX", Short: "Delete DNS record", @@ -125,6 +130,8 @@ func DNSRecordsDeleteCommand(p *DNSRecordsDeleteParams, local bool) (*cobra.Comm cmd.Flags().StringVarP(&p.PayloadName, "payload", "p", "", "Payload name") + _ = cmd.RegisterFlagCompletionFunc("payload", completePayloadName(acts)) + return cmd, func(cmd *cobra.Command, args []string) errors.Error { i, err := strconv.ParseInt(args[0], 10, 64) if err != nil { @@ -156,7 +163,7 @@ func (r DNSRecordsClearResult) ResultID() string { return DNSRecordsClearResultID } -func DNSRecordsClearCommand(p *DNSRecordsClearParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func DNSRecordsClearCommand(acts *Actions, p *DNSRecordsClearParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "clr", Short: "Delete multiple DNS records", @@ -166,6 +173,8 @@ func DNSRecordsClearCommand(p *DNSRecordsClearParams, local bool) (*cobra.Comman cmd.Flags().StringVarP(&p.PayloadName, "payload", "p", "", "Payload name") cmd.Flags().StringVarP(&p.Name, "name", "n", "", "Subdomain") + _ = cmd.RegisterFlagCompletionFunc("payload", completePayloadName(acts)) + return cmd, nil } @@ -189,7 +198,7 @@ func (r DNSRecordsListResult) ResultID() string { return DNSRecordsListResultID } -func DNSRecordsListCommand(p *DNSRecordsListParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func DNSRecordsListCommand(acts *Actions, p *DNSRecordsListParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "list", Short: "List DNS records", @@ -197,5 +206,7 @@ func DNSRecordsListCommand(p *DNSRecordsListParams, local bool) (*cobra.Command, cmd.Flags().StringVarP(&p.PayloadName, "payload", "p", "", "Payload name") + _ = cmd.RegisterFlagCompletionFunc("payload", completePayloadName(acts)) + return cmd, nil } diff --git a/internal/actions/events.go b/internal/actions/events.go index 82773c2..8c9c8f0 100644 --- a/internal/actions/events.go +++ b/internal/actions/events.go @@ -61,7 +61,7 @@ func (r EventsListResult) ResultID() string { return EventsListResultID } -func EventsListCommand(p *EventsListParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func EventsListCommand(acts *Actions, p *EventsListParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "list", Short: "List payload events", @@ -73,6 +73,8 @@ func EventsListCommand(p *EventsListParams, local bool) (*cobra.Command, Prepare cmd.Flags().Int64VarP(&p.Before, "before", "b", 0, "Before ID") cmd.Flags().BoolVarP(&p.Reverse, "reverse", "r", false, "List events in reversed order") + _ = cmd.RegisterFlagCompletionFunc("payload", completePayloadName(acts)) + return cmd, nil } @@ -100,7 +102,7 @@ func (r EventsGetResult) ResultID() string { return EventsGetResultID } -func EventsGetCommand(p *EventsGetParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func EventsGetCommand(acts *Actions, p *EventsGetParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "get INDEX", Short: "Get payload event by INDEX", @@ -109,6 +111,8 @@ func EventsGetCommand(p *EventsGetParams, local bool) (*cobra.Command, PrepareCo cmd.Flags().StringVarP(&p.PayloadName, "payload", "p", "", "Payload name") + _ = cmd.RegisterFlagCompletionFunc("payload", completePayloadName(acts)) + return cmd, func(cmd *cobra.Command, args []string) errors.Error { i, err := strconv.ParseInt(args[0], 10, 64) if err != nil { diff --git a/internal/actions/http_routes.go b/internal/actions/http_routes.go index dba7a49..1b36446 100644 --- a/internal/actions/http_routes.go +++ b/internal/actions/http_routes.go @@ -79,7 +79,7 @@ func (r HTTPRoutesCreateResult) ResultID() string { return HTTPRoutesCreateResultID } -func HTTPRoutesCreateCommand(p *HTTPRoutesCreateParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func HTTPRoutesCreateCommand(acts *Actions, p *HTTPRoutesCreateParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "new BODY", Short: "Create new HTTP route", @@ -91,9 +91,11 @@ func HTTPRoutesCreateCommand(p *HTTPRoutesCreateParams, local bool) (*cobra.Comm file bool ) + methods := append([]string{models.HTTPMethodAny}, models.HTTPMethods...) + cmd.Flags().StringVarP(&p.PayloadName, "payload", "p", "", "Payload name") cmd.Flags().StringVarP(&p.Method, "method", "m", "GET", - fmt.Sprintf("Request method (one of %s)", quoteAndJoin(models.HTTPMethods))) + fmt.Sprintf("Request method (one of %s)", quoteAndJoin(methods))) cmd.Flags().StringVarP(&p.Path, "path", "P", "/", "Request path") cmd.Flags().StringArrayVarP(&headers, "header", "H", []string{}, "Response header") cmd.Flags().IntVarP(&p.Code, "code", "c", 200, "Response status code") @@ -105,6 +107,9 @@ func HTTPRoutesCreateCommand(p *HTTPRoutesCreateParams, local bool) (*cobra.Comm cmd.Flags().BoolVarP(&file, "file", "f", false, "Treat BODY as path to file") } + _ = cmd.RegisterFlagCompletionFunc("payload", completePayloadName(acts)) + _ = cmd.RegisterFlagCompletionFunc("method", completeOne(methods)) + return cmd, func(cmd *cobra.Command, args []string) errors.Error { hh := make(map[string][]string) for _, header := range headers { @@ -164,7 +169,7 @@ func (r HTTPRoutesDeleteResult) ResultID() string { return HTTPRoutesDeleteResultID } -func HTTPRoutesDeleteCommand(p *HTTPRoutesDeleteParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func HTTPRoutesDeleteCommand(acts *Actions, p *HTTPRoutesDeleteParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "del INDEX", Short: "Delete HTTP route", @@ -174,6 +179,8 @@ func HTTPRoutesDeleteCommand(p *HTTPRoutesDeleteParams, local bool) (*cobra.Comm cmd.Flags().StringVarP(&p.PayloadName, "payload", "p", "", "Payload name") + _ = cmd.RegisterFlagCompletionFunc("payload", completePayloadName(acts)) + return cmd, func(cmd *cobra.Command, args []string) errors.Error { i, err := strconv.ParseInt(args[0], 10, 64) if err != nil { @@ -205,7 +212,7 @@ func (r HTTPRoutesClearResult) ResultID() string { return HTTPRoutesClearResultID } -func HTTPRoutesClearCommand(p *HTTPRoutesClearParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func HTTPRoutesClearCommand(acts *Actions, p *HTTPRoutesClearParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "clr", Short: "Delete multiple HTTP routes", @@ -214,6 +221,8 @@ func HTTPRoutesClearCommand(p *HTTPRoutesClearParams, local bool) (*cobra.Comman cmd.Flags().StringVarP(&p.PayloadName, "payload", "p", "", "Payload name") cmd.Flags().StringVarP(&p.Path, "path", "P", "", "Path") + _ = cmd.RegisterFlagCompletionFunc("payload", completePayloadName(acts)) + return cmd, nil } @@ -237,7 +246,7 @@ func (r HTTPRoutesListResult) ResultID() string { return HTTPRoutesListResultID } -func HTTPRoutesListCommand(p *HTTPRoutesListParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func HTTPRoutesListCommand(acts *Actions, p *HTTPRoutesListParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "list", Short: "List HTTP routes", @@ -245,5 +254,7 @@ func HTTPRoutesListCommand(p *HTTPRoutesListParams, local bool) (*cobra.Command, cmd.Flags().StringVarP(&p.PayloadName, "payload", "p", "", "Payload name") + _ = cmd.RegisterFlagCompletionFunc("payload", completePayloadName(acts)) + return cmd, nil } diff --git a/internal/actions/mock/Actions.go b/internal/actions/mock/Actions.go index 34e911c..cbdcffe 100644 --- a/internal/actions/mock/Actions.go +++ b/internal/actions/mock/Actions.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.43.0. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package actions_mock diff --git a/internal/actions/payloads.go b/internal/actions/payloads.go index 3726bf6..a3b78a3 100644 --- a/internal/actions/payloads.go +++ b/internal/actions/payloads.go @@ -64,11 +64,11 @@ func (r PayloadsCreateResult) ResultID() string { return PayloadsCreateResultID } -func PayloadsCreateCommand(p *PayloadsCreateParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func PayloadsCreateCommand(acts *Actions, p *PayloadsCreateParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "new NAME", - Short: "Create new payload", - Long: "Create new payload identified by NAME", + Short: "Create a new payload", + Long: "Create a new payload identified by NAME", Args: oneArg("NAME"), } @@ -76,6 +76,8 @@ func PayloadsCreateCommand(p *PayloadsCreateParams, local bool) (*cobra.Command, models.ProtoCategoriesAll.Strings(), "Protocols to notify") cmd.Flags().BoolVarP(&p.StoreEvents, "events", "e", false, "Store events in database") + _ = cmd.RegisterFlagCompletionFunc("protocols", completeMany(models.ProtoCategoriesAll.Strings())) + return cmd, func(cmd *cobra.Command, args []string) errors.Error { p.Name = args[0] return nil @@ -111,12 +113,13 @@ func (r PayloadsUpdateResult) ResultID() string { return PayloadsUpdateResultID } -func PayloadsUpdateCommand(p *PayloadsUpdateParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func PayloadsUpdateCommand(acts *Actions, p *PayloadsUpdateParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ - Use: "mod NAME", - Short: "Modify existing payload", - Long: "Modify existing payload identified by NAME", - Args: oneArg("NAME"), + Use: "mod NAME", + Short: "Modify existing payload", + Long: "Modify existing payload identified by NAME", + Args: oneArg("NAME"), + ValidArgsFunction: completePayloadName(acts), } var storeEvents bool @@ -125,6 +128,8 @@ func PayloadsUpdateCommand(p *PayloadsUpdateParams, local bool) (*cobra.Command, cmd.Flags().StringSliceVarP(&p.NotifyProtocols, "protocols", "p", nil, "Protocols to notify") cmd.Flags().BoolVarP(&storeEvents, "events", "e", false, "Store events in database") + _ = cmd.RegisterFlagCompletionFunc("protocols", completeMany(models.ProtoCategoriesAll.Strings())) + return cmd, func(cmd *cobra.Command, args []string) errors.Error { p.Name = args[0] @@ -157,12 +162,13 @@ func (r PayloadsDeleteResult) ResultID() string { return PayloadsDeleteResultID } -func PayloadsDeleteCommand(p *PayloadsDeleteParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func PayloadsDeleteCommand(acts *Actions, p *PayloadsDeleteParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ - Use: "del NAME", - Short: "Delete payload", - Long: "Delete payload identified by NAME", - Args: oneArg("NAME"), + Use: "del NAME", + Short: "Delete payload", + Long: "Delete payload identified by NAME", + Args: oneArg("NAME"), + ValidArgsFunction: completePayloadName(acts), } return cmd, func(cmd *cobra.Command, args []string) errors.Error { @@ -189,7 +195,7 @@ func (r PayloadsClearResult) ResultID() string { return PayloadsClearResultID } -func PayloadsClearCommand(p *PayloadsClearParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func PayloadsClearCommand(acts *Actions, p *PayloadsClearParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "clr [SUBSTR]", Short: "Delete multiple payloads", @@ -222,9 +228,9 @@ func (r PayloadsListResult) ResultID() string { return PayloadsListResultID } -func PayloadsListCommand(p *PayloadsListParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func PayloadsListCommand(acts *Actions, p *PayloadsListParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ - Use: "list SUBSTR", + Use: "list [SUBSTR]", Short: "List payloads", Long: "List payloads whose NAME contain SUBSTR", } diff --git a/internal/actions/user.go b/internal/actions/user.go index b25b975..e597a74 100644 --- a/internal/actions/user.go +++ b/internal/actions/user.go @@ -24,7 +24,7 @@ func (r ProfileGetResult) ResultID() string { return ProfileGetResultID } -func ProfileGetCommand(local bool) (*cobra.Command, PrepareCommandFunc) { +func ProfileGetCommand(acts *Actions, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "profile", Short: "Get current user info", diff --git a/internal/actions/users.go b/internal/actions/users.go index c52f2eb..59eca3a 100644 --- a/internal/actions/users.go +++ b/internal/actions/users.go @@ -51,7 +51,7 @@ func (r UsersCreateResult) ResultID() string { return UsersCreateResultID } -func UsersCreateCommand(p *UsersCreateParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func UsersCreateCommand(acts *Actions, p *UsersCreateParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "new NAME", Short: "Create new user", @@ -95,7 +95,7 @@ func (r UsersDeleteResult) ResultID() string { return UsersDeleteResultID } -func UsersDeleteCommand(p *UsersDeleteParams, local bool) (*cobra.Command, PrepareCommandFunc) { +func UsersDeleteCommand(acts *Actions, p *UsersDeleteParams, local bool) (*cobra.Command, PrepareCommandFunc) { cmd := &cobra.Command{ Use: "del NAME", Short: "Delete user", diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 3f93ed1..308e17e 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -3,7 +3,6 @@ package cmd import ( "bytes" "context" - "errors" "strings" "github.com/Masterminds/sprig/v3" @@ -32,15 +31,10 @@ func New(a actions.Actions, opts ...Option) *Command { opt(&options) } - if a == nil && options.initActions == nil { - panic("you will need to provide either actions != nil or options.initActions") - } - return &Command{ actions: a, options: options, } - } func (c *Command) root(onResult func(actions.Result) error) *cobra.Command { @@ -49,12 +43,24 @@ func (c *Command) root(onResult func(actions.Result) error) *cobra.Command { Short: "CLI to control sonar server", } + root.AddGroup( + &cobra.Group{ + ID: "main", + Title: "Main commands", + }, + ) + // Main payloads commands - root.AddCommand(c.withAuthCheck(c.PayloadsCreate(onResult))) - root.AddCommand(c.withAuthCheck(c.PayloadsList(onResult))) - root.AddCommand(c.withAuthCheck(c.PayloadsUpdate(onResult))) - root.AddCommand(c.withAuthCheck(c.PayloadsDelete(onResult))) - root.AddCommand(c.withAuthCheck(c.PayloadsClear(onResult))) + for _, cmd := range []*cobra.Command{ + c.PayloadsCreate(onResult), + c.PayloadsList(onResult), + c.PayloadsUpdate(onResult), + c.PayloadsDelete(onResult), + c.PayloadsClear(onResult), + } { + cmd.GroupID = "main" + root.AddCommand(c.withAuthCheck(cmd)) + } // DNS dns := &cobra.Command{ @@ -72,7 +78,7 @@ func (c *Command) root(onResult func(actions.Result) error) *cobra.Command { // Events events := &cobra.Command{ Use: "events", - Short: "Payloads events", + Short: "View events", } events.AddCommand(c.withAuthCheck(c.EventsList(onResult))) @@ -114,7 +120,7 @@ func (c *Command) Exec(ctx context.Context, args []string, onResult func(actions cmd := c.root(onResult) if c.options.preExec != nil { - c.options.preExec(cmd) + c.options.preExec(&c.actions, cmd) } cmd.SetArgs(args) @@ -131,27 +137,6 @@ func (c *Command) Exec(ctx context.Context, args []string, onResult func(actions // Disable "Run 'sonar --help' for usage." messages. cmd.SilenceUsage = true - // Late init actions. - if c.actions == nil { - persistentPreRunE := cmd.PersistentPreRunE - cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { - if c.options.initActions == nil { - return errors.New("actions are not initialized") - } - acts, err := c.options.initActions() - if err != nil { - return err - } - c.actions = acts - - if persistentPreRunE != nil { - return persistentPreRunE(cmd, args) - } - - return nil - } - } - if err := cmd.ExecuteContext(ctx); err != nil { return "", "", err } diff --git a/internal/cmd/generated.go b/internal/cmd/generated.go index c217b19..37d0a9b 100644 --- a/internal/cmd/generated.go +++ b/internal/cmd/generated.go @@ -9,7 +9,7 @@ import ( func (c *Command) DNSRecordsClear(onResult func(actions.Result) error) *cobra.Command { var params actions.DNSRecordsClearParams - cmd, prepareFunc := actions.DNSRecordsClearCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.DNSRecordsClearCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -36,7 +36,7 @@ func (c *Command) DNSRecordsClear(onResult func(actions.Result) error) *cobra.Co func (c *Command) DNSRecordsCreate(onResult func(actions.Result) error) *cobra.Command { var params actions.DNSRecordsCreateParams - cmd, prepareFunc := actions.DNSRecordsCreateCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.DNSRecordsCreateCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -63,7 +63,7 @@ func (c *Command) DNSRecordsCreate(onResult func(actions.Result) error) *cobra.C func (c *Command) DNSRecordsDelete(onResult func(actions.Result) error) *cobra.Command { var params actions.DNSRecordsDeleteParams - cmd, prepareFunc := actions.DNSRecordsDeleteCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.DNSRecordsDeleteCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -90,7 +90,7 @@ func (c *Command) DNSRecordsDelete(onResult func(actions.Result) error) *cobra.C func (c *Command) DNSRecordsList(onResult func(actions.Result) error) *cobra.Command { var params actions.DNSRecordsListParams - cmd, prepareFunc := actions.DNSRecordsListCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.DNSRecordsListCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -117,7 +117,7 @@ func (c *Command) DNSRecordsList(onResult func(actions.Result) error) *cobra.Com func (c *Command) EventsGet(onResult func(actions.Result) error) *cobra.Command { var params actions.EventsGetParams - cmd, prepareFunc := actions.EventsGetCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.EventsGetCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -144,7 +144,7 @@ func (c *Command) EventsGet(onResult func(actions.Result) error) *cobra.Command func (c *Command) EventsList(onResult func(actions.Result) error) *cobra.Command { var params actions.EventsListParams - cmd, prepareFunc := actions.EventsListCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.EventsListCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -171,7 +171,7 @@ func (c *Command) EventsList(onResult func(actions.Result) error) *cobra.Command func (c *Command) HTTPRoutesClear(onResult func(actions.Result) error) *cobra.Command { var params actions.HTTPRoutesClearParams - cmd, prepareFunc := actions.HTTPRoutesClearCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.HTTPRoutesClearCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -198,7 +198,7 @@ func (c *Command) HTTPRoutesClear(onResult func(actions.Result) error) *cobra.Co func (c *Command) HTTPRoutesCreate(onResult func(actions.Result) error) *cobra.Command { var params actions.HTTPRoutesCreateParams - cmd, prepareFunc := actions.HTTPRoutesCreateCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.HTTPRoutesCreateCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -225,7 +225,7 @@ func (c *Command) HTTPRoutesCreate(onResult func(actions.Result) error) *cobra.C func (c *Command) HTTPRoutesDelete(onResult func(actions.Result) error) *cobra.Command { var params actions.HTTPRoutesDeleteParams - cmd, prepareFunc := actions.HTTPRoutesDeleteCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.HTTPRoutesDeleteCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -252,7 +252,7 @@ func (c *Command) HTTPRoutesDelete(onResult func(actions.Result) error) *cobra.C func (c *Command) HTTPRoutesList(onResult func(actions.Result) error) *cobra.Command { var params actions.HTTPRoutesListParams - cmd, prepareFunc := actions.HTTPRoutesListCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.HTTPRoutesListCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -279,7 +279,7 @@ func (c *Command) HTTPRoutesList(onResult func(actions.Result) error) *cobra.Com func (c *Command) PayloadsClear(onResult func(actions.Result) error) *cobra.Command { var params actions.PayloadsClearParams - cmd, prepareFunc := actions.PayloadsClearCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.PayloadsClearCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -306,7 +306,7 @@ func (c *Command) PayloadsClear(onResult func(actions.Result) error) *cobra.Comm func (c *Command) PayloadsCreate(onResult func(actions.Result) error) *cobra.Command { var params actions.PayloadsCreateParams - cmd, prepareFunc := actions.PayloadsCreateCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.PayloadsCreateCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -333,7 +333,7 @@ func (c *Command) PayloadsCreate(onResult func(actions.Result) error) *cobra.Com func (c *Command) PayloadsDelete(onResult func(actions.Result) error) *cobra.Command { var params actions.PayloadsDeleteParams - cmd, prepareFunc := actions.PayloadsDeleteCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.PayloadsDeleteCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -360,7 +360,7 @@ func (c *Command) PayloadsDelete(onResult func(actions.Result) error) *cobra.Com func (c *Command) PayloadsList(onResult func(actions.Result) error) *cobra.Command { var params actions.PayloadsListParams - cmd, prepareFunc := actions.PayloadsListCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.PayloadsListCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -387,7 +387,7 @@ func (c *Command) PayloadsList(onResult func(actions.Result) error) *cobra.Comma func (c *Command) PayloadsUpdate(onResult func(actions.Result) error) *cobra.Command { var params actions.PayloadsUpdateParams - cmd, prepareFunc := actions.PayloadsUpdateCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.PayloadsUpdateCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -412,7 +412,7 @@ func (c *Command) PayloadsUpdate(onResult func(actions.Result) error) *cobra.Com } func (c *Command) ProfileGet(onResult func(actions.Result) error) *cobra.Command { - cmd, prepareFunc := actions.ProfileGetCommand(c.options.allowFileAccess) + cmd, prepareFunc := actions.ProfileGetCommand(&c.actions, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -435,7 +435,7 @@ func (c *Command) ProfileGet(onResult func(actions.Result) error) *cobra.Command func (c *Command) UsersCreate(onResult func(actions.Result) error) *cobra.Command { var params actions.UsersCreateParams - cmd, prepareFunc := actions.UsersCreateCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.UsersCreateCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { @@ -462,7 +462,7 @@ func (c *Command) UsersCreate(onResult func(actions.Result) error) *cobra.Comman func (c *Command) UsersDelete(onResult func(actions.Result) error) *cobra.Command { var params actions.UsersDeleteParams - cmd, prepareFunc := actions.UsersDeleteCommand(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.UsersDeleteCommand(&c.actions, ¶ms, c.options.allowFileAccess) cmd.RunE = func(cmd *cobra.Command, args []string) error { if prepareFunc != nil { diff --git a/internal/cmd/messengers.go b/internal/cmd/messengers.go index ce1a512..ef6856d 100644 --- a/internal/cmd/messengers.go +++ b/internal/cmd/messengers.go @@ -1,10 +1,11 @@ package cmd import ( + "github.com/nt0xa/sonar/internal/actions" "github.com/spf13/cobra" ) -func DefaultMessengersPreExec(root *cobra.Command) { +func DefaultMessengersPreExec(acts *actions.Actions, root *cobra.Command) { root.SetHelpTemplate(messengersHelpTemplate) root.SetUsageTemplate(messengersUsageTemplate) root.CompletionOptions = cobra.CompletionOptions{ diff --git a/internal/cmd/options.go b/internal/cmd/options.go index b4a991c..8229338 100644 --- a/internal/cmd/options.go +++ b/internal/cmd/options.go @@ -8,13 +8,11 @@ import ( var defaultOptions = options{ allowFileAccess: false, preExec: nil, - initActions: nil, } type options struct { allowFileAccess bool - preExec func(*cobra.Command) - initActions func() (actions.Actions, error) + preExec func(actions *actions.Actions, cmd *cobra.Command) } type Option func(*options) @@ -30,17 +28,8 @@ func AllowFileAccess(b bool) Option { // PreExec is called after Root command is created. // Should be used to add specific subcommands or flags (e.g. "--json" for CLI). -func PreExec(f func(*cobra.Command)) Option { +func PreExec(f func(actions *actions.Actions, cmd *cobra.Command)) Option { return func(opts *options) { opts.preExec = f } } - -// InitActions is a workaround for CLI. -// In the CLI we can't pass `action.Actions`, before we parse config and flags, -// so we need a way to create actions late. -func InitActions(f func() (actions.Actions, error)) Option { - return func(opts *options) { - opts.initActions = f - } -} diff --git a/internal/codegen/code.go b/internal/codegen/code.go index 6551ea2..1a83918 100644 --- a/internal/codegen/code.go +++ b/internal/codegen/code.go @@ -13,9 +13,9 @@ func (c *Command) {{ .Name }}(onResult func(actions.Result) error) *cobra.Comman {{- if ne .Params.TypeName "" }} var params actions.{{ .Params.TypeName }} - cmd, prepareFunc := actions.{{ .Name }}Command(¶ms, c.options.allowFileAccess) + cmd, prepareFunc := actions.{{ .Name }}Command(&c.actions, ¶ms, c.options.allowFileAccess) {{ else }} - cmd, prepareFunc := actions.{{ .Name }}Command(c.options.allowFileAccess) + cmd, prepareFunc := actions.{{ .Name }}Command(&c.actions, c.options.allowFileAccess) {{ end }} cmd.RunE = func(cmd *cobra.Command, args []string) error { diff --git a/internal/modules/api/apiclient/generated.go b/internal/modules/api/apiclient/generated.go index 58a81f3..d3be6db 100644 --- a/internal/modules/api/apiclient/generated.go +++ b/internal/modules/api/apiclient/generated.go @@ -252,8 +252,8 @@ func (c *Client) PayloadsUpdate(ctx context.Context, params actions.PayloadsUpda var res *actions.PayloadsUpdateResult err := handle(c.client.R(). - SetPathParams(toPath(params)). SetBody(params). + SetPathParams(toPath(params)). SetError(&APIError{}). SetResult(&res). SetContext(ctx). diff --git a/internal/modules/api/generated.go b/internal/modules/api/generated.go index f118c6c..d512775 100644 --- a/internal/modules/api/generated.go +++ b/internal/modules/api/generated.go @@ -105,12 +105,12 @@ func (api *API) EventsList(w http.ResponseWriter, r *http.Request) { var params actions.EventsListParams - if err := fromPath(r, ¶ms); err != nil { + if err := fromQuery(r, ¶ms); err != nil { api.handleError(w, r, err) return } - if err := fromQuery(r, ¶ms); err != nil { + if err := fromPath(r, ¶ms); err != nil { api.handleError(w, r, err) return } @@ -277,12 +277,12 @@ func (api *API) PayloadsUpdate(w http.ResponseWriter, r *http.Request) { var params actions.PayloadsUpdateParams - if err := fromJSON(r, ¶ms); err != nil { + if err := fromPath(r, ¶ms); err != nil { api.handleError(w, r, err) return } - if err := fromPath(r, ¶ms); err != nil { + if err := fromJSON(r, ¶ms); err != nil { api.handleError(w, r, err) return } diff --git a/internal/modules/telegram/telegram.go b/internal/modules/telegram/telegram.go index ed7b937..8c855ed 100644 --- a/internal/modules/telegram/telegram.go +++ b/internal/modules/telegram/telegram.go @@ -80,8 +80,8 @@ func New(cfg *Config, db *database.DB, actions actions.Actions, domain string) ( return tg, nil } -func (tg *Telegram) preExec(root *cobra.Command) { - cmd.DefaultMessengersPreExec(root) +func (tg *Telegram) preExec(acts *actions.Actions, root *cobra.Command) { + cmd.DefaultMessengersPreExec(acts, root) c := &cobra.Command{ Use: "id",