Named Positional Arguments in Cobra - go

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

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.

How to run another command from cobra

I am building a cli app in GoLang . I am using cobra for doing that and I have the following code for that:
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// pullCmd represents the pull command
var pullCmd = &cobra.Command{
Use: "pull",
Short: "Take pull from repo",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("pull called")
},
}
func init() {
rootCmd.AddCommand(pullCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// pullCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// pullCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
I want the pull command to run git pull command internally whenever I run my pull command. How can I do that ? I am new to GoLang & Cobra library.
Thanks
The os/exec package can help you out here. Create a command for git pull, and then Run it.

Running root command logic + sub-command logic

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.

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