How to pass flags as argument of urface/cli command? - go

I am using urfave/cli to build a CLI application in Go.
What I would like is options given after the first command to be treated as arguments and not flags (so that I can handle them myself or pass them to an other executable).
When using app.Action (see below), this is the behavior that I get, but if I am using cli.Commands then I get an error.
package main
import (
"fmt"
"github.com/urfave/cli"
"log"
"os"
)
func main() {
app := cli.NewApp()
app.Commands = []cli.Command{
{
Name: "test",
Action: func(c *cli.Context) error {
fmt.Println("test", c.Args())
return nil
},
},
}
app.Action = func(c *cli.Context) error {
fmt.Println(c.Args())
return nil
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
Output of: go run main.go cmd -f flag:
[cmd -f flag]
but go run main.go test cmd -f flag exits with an error where I would like it to oupput
test [cmd -f flag]

After looking at the documentation of cli.Command, there is actually a very simple way to have what I want: setting SkipFlagParsing to true will treat all flags as normal arguments.
app.Commands = []cli.Command{
{
Name: "test",
SkipFlagParsing: true
Action: func(c *cli.Context) error {
fmt.Println("test", c.Args())
return nil
},
},
}

Related

Validating flags using Cobra

The sketch below is a command line application written using Cobra and Go. I'd like to throw an error if the value of flag1 doesn't match the regex ^\s+\/\s+. How do I do that?
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
)
var flag1 string
var cfgFile string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "cobra-sketch",
Short: "Sketch for Cobra flags",
Long: "Sketch for Cobra flags",
Run: func(cmd *cobra.Command, args []string) { fmt.Printf("Flag1 is %s\n", flag1)},
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
cobra.CheckErr(rootCmd.Execute())
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra-sketch.yaml)")
rootCmd.PersistentFlags().StringVar(&flag1, "flag1", "", "Value of Flag 1")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
cobra.CheckErr(err)
// Search config in home directory with name ".cobra-sketch" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".cobra-sketch")
}
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
}
Let's say a user runs the command like this: cobra-sketch --flag1 "hello". "hello" will be stored in the var flag1 string variable you have assigned to the flag, to check if the input matches any regexp, you can do:
var rootCmd = &cobra.Command{
Use: "cobra-sketch",
...
RunE: func(cmd *cobra.Command, args []string) error {
// You can also use MustCompile if you are sure the regular expression
// is valid, it panics instead of returning an error
re, err := regexp.Compile(`^\s+\/\s+`)
if err != nil {
return err // Handle error
}
if !regexp.MatchString(flag1) {
return fmt.Errorf("invalid value: %q", flag1)
}
fmt.Printf("Flag1 is %s\n", flag1)
return nil
},
}

How can I invoke a default subcommand with cobra?

Using cobra, if my app is invoked without a specific action (but arguments), I'd like to run a default command:
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "mbmd",
Short: "ModBus Measurement Daemon",
Long: "Easily read and distribute data from ModBus meters and grid inverters",
Run: func(cmd *cobra.Command, args []string) {
run(cmd, args)
},
}
However, since the root command doesn't have all arguments the child command has this fails as it's apparently now aware of the child command's arguments:
❯ go run main.go -d sma:126#localhost:5061 --api 127.1:8081 -v
Error: unknown shorthand flag: 'd' in -d
as opposed to:
❯ go run main.go run -d sma:126#localhost:5061 --api 127.1:8081 -v
2019/07/29 20:58:10 mbmd unknown version (unknown commit)
How can I programmatically instantiate/invoke a child command?
Here is another solution:
cmd, _, err := rootCmd.Find(os.Args[1:])
// default cmd if no cmd is given
if err == nil && cmd.Use == rootCmd.Use && cmd.Flags().Parse(os.Args[1:]) != pflag.ErrHelp {
args := append([]string{defaultCmd.Use}, os.Args[1:]...)
rootCmd.SetArgs(args)
}
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
Replace defaultCmd with one you want to be default
This part cmd.Flags().Parse(os.Args[1:]) != pflag.ErrHelp keeps help command working for root command if no arguments was set
March 2021: You might consider a workaround as the one presented in spf13/cobra issue 823
func subCommands() (commandNames []string) {
for _, command := range cmd.Commands() {
commandNames = append(commandNames, append(command.Aliases, command.Name())...)
}
return
}
func setDefaultCommandIfNonePresent() {
if len(os.Args) > 1 {
potentialCommand := os.Args[1]
for _, command := range subCommands() {
if command == potentialCommand {
return
}
}
os.Args = append([]string{os.Args[0], "<default subcommand>"}, os.Args[1:]...)
}
}
func main() {
setDefaultCommandIfNonePresent()
if err := cmd.Execute(); err != nil {
zap.S().Error(err)
os.Exit(1)
}
}
The difference here is that it checks if len(os.Args) > 1 before changing the default subcommand.
This means that, if ran without any arguments, it will print the default help command (with all of the subcommands).
Otherwise, if supplied any arguments, it will use the subcommand.
So, it will display the main 'help' without arguments, and the subcommand's help if supplied '-h'/'--help'.
Or (Oct. 2021), from the author of PR 823:
Latest solve for this is the following:
main.go
func main() {
// Define the default sub command 'defCmd' here. If user doesn't submit
// using a default command, we'll use what is here.
defCmd:="mydefaultcmd"
cmd.Execute(defCmd)
}
root.go
func Execute(defCmd string) {
var cmdFound bool
cmd :=rootCmd.Commands()
for _,a:=range cmd{
for _,b:=range os.Args[1:] {
if a.Name()==b {
cmdFound=true
break
}
}
}
if !cmdFound {
args:=append([]string{defCmd}, os.Args[1:]...)
rootCmd.SetArgs(args)
}
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

Inside kubectl plugin, prompt for input?

I'm writing a kubectl plugin to authenticate users, and I would like to prompt the user for a password after the plugin is invoked. From what I understand, it's fairly trivial to get input from STDIN, but I'm struggling seeing messages written to STDOUT. Currently my code looks like this:
In cmd/kubectl-myauth.go:
// This is mostly boilerplate, but it's needed for the MRE
// https://stackoverflow.com/help/minimal-reproducible-example
package myauth
import (...)
func main() {
pflag.CommandLine = pflag.NewFlagSet("kubectl-myauth", pflag.ExitOnError)
root := cmd.NewCmdAuthOp(genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
if err := root.Execute(); err != nil {
os.Exit(1)
}
}
In pkg/cmd/auth.go:
package cmd
...
type AuthOpOptions struct {
configFlags *genericclioptions.ConfigFlags
resultingContext *api.Context
rawConfig api.Config
args []string
...
genericclioptions.IOStreams
}
func NewAuthOpOptions(streams genericclioptions.IOStreams) *AuthOpOptions {
return &AuthOpOptions{
configFlags: genericclioptions.NewConfigFlags(true),
IOStreams: streams,
}
}
func NewCmdAuthOp(streams genericclioptions.IOStreams) *cobra.Command {
o := NewAuthOpOptions(streams)
cmd := &cobra.Command{
RunE: func(c *cobra.Command, args []string) error {
return o.Run()
},
}
return cmd
}
func (o *AuthOpOptions) Run() error {
pass, err := getPassword(o)
if err != nil {
return err
}
// Do Auth Stuff
// Eventually print an ExecCredential to STDOUT
return nil
}
func getPassword(o *AuthOpOptions) (string, error) {
var reader *bufio.Reader
reader = nil
pass := ""
for pass == "" {
// THIS IS AN IMPORTANT LINE [1]
fmt.Fprintf(o.IOStreams.Out, "Password with which to authenticate:\n")
// THE REST OF THIS IS STILL IMPORTANT, BUT LESS SO [2]
if reader == nil {
// The first time through, initialize the reader
reader = bufio.NewReader(o.IOStreams.In)
}
pass, err := reader.ReadString('\n')
if err != nil {
return nil, err
}
pass = strings.Trim(pass, "\r\n")
if pass == "" {
// ALSO THIS LINE IS IMPORTANT [3]
fmt.Fprintf(o.IOStreams.Out, `Read password was empty string.
Please input a valid password.
`)
}
}
return pass, nil
}
This works the way that I expect when running from outside of the kubectl context - namely, it prints the string, prompts for input, and continues. However, from inside the kubectl context, I believe the print between the first two all-caps comments ([1] and [2]) is being swallowed by kubectl listening on STDOUT. I can get around this by printing to STDERR, but that feels... wrong. Is there a way that I can bypass kubectl's consumption of STDOUT to communicate with the user?
TL;DR: kubectl appears to be swallowing all of STDOUT for kubectl plugins, but I want to prompt the user for input - is there a simple way to do this?
Sorry I have no better answer than "Works for me" :-) Here are the steps:
git clone https://github.com/kubernetes/kubernetes.git
duplicate sample-cli-plugin as test-cli-plugin (this involves fixing import-restrictions.yaml, rules-godeps.yaml and rules.yaml under staging/publishing - maybe not necessary, but it's safer this way)
change kubectl-ns.go to kubectl-test.go:
package main
import (
"os"
"github.com/spf13/pflag"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/test-cli-plugin/pkg/cmd"
)
func main() {
flags := pflag.NewFlagSet("kubectl-test", pflag.ExitOnError)
pflag.CommandLine = flags
root := cmd.NewCmdTest(genericclioptions.IOStreams{In: os.Stdin,
Out: os.Stdout,
ErrOut: os.Stderr})
if err := root.Execute(); err != nil {
os.Exit(1)
}
}
change ns.go to test.go:
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
type TestOptions struct {
configFlags *genericclioptions.ConfigFlags
genericclioptions.IOStreams
}
func NewTestOptions(streams genericclioptions.IOStreams) *TestOptions {
return &TestOptions{
configFlags: genericclioptions.NewConfigFlags(true),
IOStreams: streams,
}
}
func NewCmdTest(streams genericclioptions.IOStreams) *cobra.Command {
o := NewTestOptions(streams)
cmd := &cobra.Command{
Use: "test",
Short: "Test plugin",
SilenceUsage: true,
RunE: func(c *cobra.Command, args []string) error {
o.Run()
return nil
},
}
return cmd
}
func (o *TestOptions) Run() error {
fmt.Fprintf(os.Stderr, "Testing Fprintf Stderr\n")
fmt.Fprintf(os.Stdout, "Testing Fprintf Stdout\n")
fmt.Printf("Testing Printf\n")
fmt.Fprintf(o.IOStreams.Out, "Testing Fprintf o.IOStreams.Out\n")
return nil
}
fix BUILD files accordingly
build the plugin
run make
copy kubectl-test to /usr/local/bin
run the compiled kubectl binary:
~/k8s/_output/bin$ ./kubectl test
Testing Fprintf Stderr
Testing Fprintf Stdout
Testing Printf
Testing Fprintf o.IOStreams.Out

Checking with Go if system package is installed exiting program

Im trying to check if package is installed in system (Centos/Yum). Im trying to use for that exec.Command method:
func YumCheckIfPackageInstalled(pkg string) string {
out,err := exec.Command("yum", "list", "installed", pkg).Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Output %s\n", out)
return "string"
}
Problem is that when "pkg" is installed program is continuing to work, but if it is not it is exiting with:
exit status 1
How to prevent program to exit on os command error?
What i want to achieve is to check if some packages are installed and if not i want to install them. Maybe there is some better way to solve that problem than executing exec.Command-s?
Your program is not exiting because of command error.
It is exiting because you put log.Fatal(err).
log.Fatal exits the program with SIGINT 1, if you just want to log the error, do log.Println(err). See the doc here: https://golang.org/pkg/log/#Logger.Fatal
Also, to do it the goway, you should bubble up the error and let the caller of the function handle the error.
Now, regarding what you want to do, I suggest to use the function LookPath of the exec package, it does exactly what you want by searching for an executable with the given name in your path. Here is the doc: https://golang.org/pkg/os/exec/#LookPath
You could do something like that:
package main
import (
"flag"
"fmt"
"log"
"os/exec"
)
var pkg = flag.String("pkg", "", "package name")
func main() {
flag.Parse()
if !PackageInstalled(*pkg) {
if err := InstallPackage(*pkg); err != nil {
log.Fatal(err)
}
fmt.Printf("Package %s installed\n", *pkg)
return
}
fmt.Printf("Package %s already installed\n", *pkg)
}
func PackageInstalled(pkg string) bool {
_, err := exec.LookPath(pkg)
// check error
if err != nil {
// the executable is not found, return false
if execErr, ok := err.(*exec.Error); ok && execErr.Err == exec.ErrNotFound {
return false
}
// another kind of error happened, let's log and exit
log.Fatal(err)
}
return true
}
func InstallPackage(pkg string) error {
// install your package
// ...
return nil
}
and run it this way go run main.go -pkg yum

How to make subcommand required in Go CLI using cobra

I am using Cobra to make some cli updated to my app. I want to make this command required, meaning the application should fail if it doesn't find the argument it is looking for.
package commands
import (
"github.com/spf13/cobra"
"errors"
"fmt"
)
var (
Env string
)
var RootCmd = &cobra.Command{
Use: "myapp",
Short: "tool",
Long: `tool`,
Run: func(cmd *cobra.Command, args []string) {
// Root command does nothing
},
}
func init() {
RootCmd.AddCommand(Environment)
}
var Environment = &cobra.Command{
Use: "env",
Short: "Specify Environment to run against",
Long: `Can be dev or prod`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("requires at least one arg")
}
if args[0] == "dev" || args[0] == "prod" {
return nil
}else {
return errors.New("input can only be dev or prod")
}
return fmt.Errorf("invalid env specified: %s", args[0])
},
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("env is required")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
Env = args[0]
},
}
and main package is
package main
import (
"fmt"
"log"
"os"
"util"
"commands"
)
func main() {
log.Println("Executing")
if err := commands.RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
log.Println("Executing")
}
Now if I run this as ./myApp without any env, it doesn't complain about it. However if I use env after myapp then it activates the function and runs all the validations.
You can just make the body of the function handle it, perhaps by printing help and exiting as non-successful:
Run: func(cmd *cobra.Command, args []string) {
// Root command does nothing
cmd.Help()
os.Exit(1)
},
Omitting the Run (and RunE) field from the cobra.Command will make it a requirement for a valid subcommand to be given:
var RootCmd = &cobra.Command{
Use: "myapp",
Short: "tool",
Long: `tool long help...`,
}
If no subcommand is given on the command line, Cobra will print out the command's Help() text , which will include the root command's Long help text and the autogenerated usage help for all subcommands.

Resources