Running root command logic + sub-command logic - go

I have the following command + subcommand:
aws.go
// s3Cmd represents the out command
var s3Cmd = &cobra.Command{
Use: "s3",
Short: "Uploads saved tarballs to an s3 bucket in aws",
Long: `Uploads files to S3 using the credentials passed as arguments
s3Bucket and the aws key and secret.`,
RunE: func(cmd *cobra.Command, args []string) error {
// some logic
},
}
func init() {
outCmd.AddCommand(s3Cmd)
}
out.go
// outCmd represents the out command
var outCmd = &cobra.Command{
Use: "out",
Short: "Consumes data out from RabbitMQ and stores to tarballs",
Long: `Select your output directory and batchsize of the tarballs.
When there are no more messages in the queue, press CTRL + c, to interrupt
the consumption and save the last message buffers.`,
RunE: func(cmd *cobra.Command, args []string) error {
//logic
},
}
func init() {
RootCmd.AddCommand(outCmd)
}
When I execute go run main.go out --args s3 --args
The above runs the logic inside s3Command but doesn't run what's inside outCmd, is there a way to run the outCommand logic first then s3Cmd first?

The go-cobra commands and subcommands are meant to be run individually. There are some hacky ways to run multiple, but generally that means requiring a special format to your args and handling batching up the runs yourself. See the discussion at https://github.com/spf13/cobra/issues/726 for an example of one way to do it and pointers to a few related issues.

Related

Require a flag as the first argument in a Cobra command

I'm trying to create a Cobra command that uses a flag to inform the action of the command, specifically a configuration command that can either add or remove a configured setting. For example
cli> prog_name config --set config_var var_vlue
cli> prog_name config --unset config_var var_value
Is there a way to do this in Cobra? I have been reading through the documentation and haven't found any way to validate that a flag is the first value in the command. I've seen information about positional arguments, but from what I've read it sounds like flags aren't considered arguments, so they wouldn't be covered by positional arguments.
I'd imagine I can do this in my PreRunE function and do the validation manually, but if there's a way to set this in Cobra I think that'd most likely be better, since I'd prefer Cobra to be doing that parsing and matching rather than me have to be comparing specific values in os.Args to "--set" and "--unset" or something similar.
It seems like the best option is to just use a subcommand for this instead of a flag.
You can solve this by consulting this link.
In short what you need is the Flags() function. You can find documentation here.
package main
import (
"fmt"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "testprog",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("rootCmd called")
},
}
var subCmd = &cobra.Command{
Use: "sub",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(args)
},
}
func main() {
rootCmd.AddCommand(subCmd)
flags := subCmd.Flags()
// not necessary in your case
flags.SetInterspersed(false)
// Bool defines a bool flag with specified name,
// default value, and usage string. The return value
// is the address of a bool variable that stores
// the value of the flag.
flags.Bool("test", false, "test flag")
rootCmd.Execute()
}
Let's see what happens in the terminal:
> ./cobraApp sub --test a
> [a]

ValidArgsFunction dynamic autocomplete not working with Golang Cobra cli program

I'm trying to get autocomplete working with Cobra but nothing happens after pressing the tab key - on something that I think should autocomplete. Am I missing something?
var HelloCmd = &cobra.Command{
Use: "hello <name>",
Short: "Say hello to someone",
Long: `Say hello to someone`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(args)
fmt.Println("hello " + args[0])
cmd.Help()
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return []string{"steve", "john"}, cobra.ShellCompDirectiveNoFileComp
},
}
And then I call go build . followed by ./program hello s[TAB] or ./program hello j[TAB] it suggests nothing. I want to it suggest the names "steve" and "john". If I type ./program hello [TAB] it suggests the files in the directory.
Pls help I've been ripping my hairs out all morning to fix this!
you need to generate autocomplete script and add it to your shell profile follow this section:
Creating your own completion command
and see [cmd] completion -h for more help.

Named Positional Arguments in Cobra

I have the following Cobra sub-command:
package stripeCommands
import (
"fmt"
"cmd/cliConstants"
"github.com/spf13/cobra"
"log"
)
var (
deleteCustomerCommand = &cobra.Command{
Use: "delete",
Short: "Delete Stripe customer(s) by ids.",
Args: cobra.MinimumNArgs(1),
ArgAliases: []string{"stripe_customer_id"},
PreRun: func(cmd *cobra.Command, args []string) {
},
Run: func(cmd *cobra.Command, args []string) {
log.Printf("IDs: %v", args)
},
}
)
func init() {
flags := deleteCustomerCommand.Flags()
// -k|--stripe-api-key|STRIPE_API_KEY
flags.StringP(cliConstants.CLIFlagStripeAPIKey, "k", "",
fmt.Sprintf("The Stripe API key. [env: %s]", cliConstants.EnvVarStripeAPIKey))
}
The idea is to call this via ./my-app stripe customers delete -k $STRIPE_API_KEY $CUSTOMER_ID_1 $CUSTOMER_ID_2.
While cobra.MinimumNArgs(1) does ensure I get at least one positional argument, I can't find a way to make this show up in the help documentation:
Error: requires at least 1 arg(s), only received 0
Usage:
my-app stripe customers delete [flags]
Flags:
-h, --help help for delete
-k, --stripe-api-key string The Stripe API key. [env: stripe_api_key]
2021/09/13 12:00:39 Failed to execute command: requires at least 1 arg(s), only received 0
Is there a way to tell Cobra to display positional args in the help like:
Usage:
my-app stripe customers delete [flags] customer_id [...customer_id]
Right now the help documentation is not very helpful in displaying to the user what they should pass as positional arguments.
Set the Use field for your command to :
deleteCustomerCommand = &cobra.Command{
Use: "delete [flags] customer_id [...customer_id]",
...
The complete details of how it is used can be found in the code for cmd.UseLine() :
https://github.com/spf13/cobra/blob/v1.2.1/command.go#L1245

GO: Run cli command with wrong args

I use cobra to create CLI command tool.
everything is looking OK except the error handling
what I want that if command was sent by mistake (wrong args or wrong input) return std.err instead of std.out
to simplify the secnario I've created this which demonstrate my use-case
package main
import (
"errors"
"fmt"
"os"
"github.com/spf13/cobra"
)
var (
RootCmd = &cobra.Command{
Use: "myApp",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("ROOT verbose = %d, args = %v\n", args)
},
}
provideCmd = &cobra.Command{
Use: "provide",
Run: nil,
}
appCmd = &cobra.Command{
Use: "apps",
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
if name != "myapp" {
err := errors.New("app name doesnt exist")
return err
}
return nil
},
SilenceUsage: true,
}
)
func init() {
// Add the application command to app command
provideCmd.AddCommand(appCmd)
// add provide command to root command
RootCmd.AddCommand(provideCmd)
}
func main() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(-1)
}
}
Now if I compile the binary and run exec.Command against the binary everything is working as expected. but if I want to test the error scenario like mycli provide apps apps1
I want to see that returned in std.err and not at std.out
When I execute mycli provide apps myapp everything should be OK
but if I run mycli provide apps myapp2 I want to get std.err and not std.out , which is not the case here ...what am I missing here ?
https://play.golang.org/p/B00z4eZ7Sj-
Your sample already prints the error both to stdout and stderr.
By default the cobra package prints any errors it encounters to stderr, unless you specifically change that.
So running
./main provide apps something 2> ./stderr.txt creates a text file with the following content (this is what cobra writes to stderr without your intervention):
Error: app name doesnt exist
And running ./main provide apps something > ./stdout.txt - creates a text file with the following content (you printed that yourself with fmt.Println(err), the second line from the bottom in your code):
app name doesnt exist
Which means default behaviour prints errors both to stdout and stderr.
As Devin has advised you, changing the last line to os.Stderr.WriteString(err) or
fmt.Fprintln(os.Stderr, err) (the one I would use) will make your project to print everything to stderr only, which means printing errors twice:
Error: app name doesnt exist
app name doesnt exist
It might be useful to know that cobra allows you some control of error printing behaviour. For example, you can tell a cobra command which stream to print to:
command.SetOutput(os.Stdout) // Defaults to os.Stderr
you could also prevent printing of errors:
command.SilenceErrors = true
or prevent printing of usage text:
command.SilenceUsage = true

How does cobra commander for go (golang) work?

I am trying to understand how to create costume commands for go using the cobra (and viper) libraries and be able to use them.
Specifically I was trying to understand and make the example they provide work. The example is the following:
import(
"github.com/spf13/cobra"
"fmt"
"strings"
)
func main() {
var echoTimes int
var cmdPrint = &cobra.Command{
Use: "print [string to print]",
Short: "Print anything to the screen",
Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.
`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdEcho = &cobra.Command{
Use: "echo [string to echo]",
Short: "Echo anything to the screen",
Long: `echo is for echoing anything back.
Echo works a lot like print, except it has a child command.
`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdTimes = &cobra.Command{
Use: "times [# times] [string to echo]",
Short: "Echo anything to the screen more times",
Long: `echo things multiple times back to the user by providing
a count and a string.`,
Run: func(cmd *cobra.Command, args []string) {
for i:=0; i < echoTimes; i++ {
fmt.Println("Echo: " + strings.Join(args, " "))
}
},
}
cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")
var rootCmd = &cobra.Command{Use: "app"}
rootCmd.AddCommand(cmdPrint, cmdEcho)
cmdEcho.AddCommand(cmdTimes)
rootCmd.Execute()
}
However, I have made the file for it, in package main and I can't seem to make it work properly. I do understand that the rootCmd is not suppose to be executable. However, it seems to me that the Use field behaves inconsistently (as far as I can tell). I was trying to understand how commands actually run in the command line and compile the file in different ways to experiment this.
For example I compiled this file I named as cobra.go with go build cobra.go and now I have an executable cobra.
If I do ./name it behaves the same way as ./name help and prints:
Usage:
app [command]
Available Commands:
print [string to print] Print anything to the screen
echo [string to echo] Echo anything to the screen
help [command] Help about any command
Available Flags:
--help=false: help for app
Use "app help [command]" for more information about that command.
However the description incorrectly says that to use it you must do "app help [command]".
If I do that ./app help print (with only the cobra executable of course) it prints:
zsh: no such file or directory: ./app
if I do app help print it also doesn't work (since it isn't found in PATH):
zsh: command not found: app
So my understanding is that, unless I build it with the same name as root, cobra will act funny.
So it means that the use field has to match the file name for the output of help to make sense and for us to be able to run its sub commands properly, right?
Also, how is one able to have (compile) multiple different "root" commands in the same project, if only one package main is allowed? If one has multiple "root" commands under main, it shouldn't work, right? I will try some of these ideas soon.
You could check how cobra is used is other projects (go-search).
For instance, the bitballoon-cli project defines multiple commands, each in their own file, each in the main package. Like create.go:
package main
import (
"github.com/BitBalloon/bitballoon-go"
"github.com/spf13/cobra"
"log"
)
var createCmd = &cobra.Command{
Use: "create",
Short: "create a new BitBalloon site",
Long: "creates a new BitBalloon site and returns an ID you can deploy to",
}
var siteName, customDomain, password, notificationEmail string
func init() {
createCmd.Run = create
createCmd.Flags().StringVarP(&siteName, "name", "n", "", "Name of the site (must be a valid subdomain: <name>.bitballoon.com)")
createCmd.Flags().StringVarP(&customDomain, "domain", "d", "", "Custom domain for the site (only works for premium sites)")
createCmd.Flags().StringVarP(&password, "password", "p", "", "Password for the site")
createCmd.Flags().StringVarP(&notificationEmail, "email", "e", "", "Notification email for form submissions (only works for premium sites)")
}
func create(cmd *cobra.Command, args []string) {
// your function for this command
}
You could check if that way of defining and adding a command to your project works in your case.
4 years later, see the video "justforfunc #32: CLI tools with Cobra", by Francesc Campoy.

Resources