diff --git a/components/cli/cli/cobra.go b/components/cli/cli/cobra.go index e97abd2a1b..1b5809e9e5 100644 --- a/components/cli/cli/cobra.go +++ b/components/cli/cli/cobra.go @@ -17,12 +17,12 @@ func SetupRootCommand(rootCmd *cobra.Command) { cobra.AddTemplateFunc("operationSubCommands", operationSubCommands) cobra.AddTemplateFunc("managementSubCommands", managementSubCommands) cobra.AddTemplateFunc("wrappedFlagUsages", wrappedFlagUsages) - cobra.AddTemplateFunc("useLine", UseLine) rootCmd.SetUsageTemplate(usageTemplate) rootCmd.SetHelpTemplate(helpTemplate) rootCmd.SetFlagErrorFunc(FlagErrorFunc) rootCmd.SetHelpCommand(helpCommand) + rootCmd.SetVersionTemplate("Docker version {{.Version}}\n") rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage") rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help") @@ -99,19 +99,9 @@ func managementSubCommands(cmd *cobra.Command) []*cobra.Command { return cmds } -// UseLine returns the usage line for a command. This implementation is different -// from the default Command.UseLine in that it does not add a `[flags]` to the -// end of the line. -func UseLine(cmd *cobra.Command) string { - if cmd.HasParent() { - return cmd.Parent().CommandPath() + " " + cmd.Use - } - return cmd.Use -} - var usageTemplate = `Usage: -{{- if not .HasSubCommands}} {{ useLine . }}{{end}} +{{- if not .HasSubCommands}} {{.UseLine}}{{end}} {{- if .HasSubCommands}} {{ .CommandPath}} COMMAND{{end}} {{ .Short | trim }} diff --git a/components/cli/cli/command/image/build/context.go b/components/cli/cli/command/image/build/context.go index 1d3011aff6..463e3156c2 100644 --- a/components/cli/cli/command/image/build/context.go +++ b/components/cli/cli/command/image/build/context.go @@ -47,7 +47,7 @@ func ValidateContextDirectory(srcPath string, excludes []string) error { return errors.Errorf("can't stat '%s'", filePath) } if os.IsNotExist(err) { - return nil + return errors.Errorf("file ('%s') not found or excluded by .dockerignore", filePath) } return err } diff --git a/components/cli/cli/command/service/update.go b/components/cli/cli/command/service/update.go index 5fc8b729a7..0a808619be 100644 --- a/components/cli/cli/command/service/update.go +++ b/components/cli/cli/command/service/update.go @@ -963,45 +963,99 @@ func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error return nil } +type hostMapping struct { + IPAddr string + Host string +} + // updateHosts performs a diff between existing host entries, entries to be // removed, and entries to be added. Host entries preserve the order in which they // were added, as the specification mentions that in case multiple entries for a // host exist, the first entry should be used (by default). +// +// Note that, even though unsupported by the the CLI, the service specs format +// allow entries with both a _canonical_ hostname, and one or more aliases +// in an entry (IP-address canonical_hostname [alias ...]) +// +// Entries can be removed by either a specific `:` mapping, +// or by `` alone: +// +// - If both IP-address and host-name is provided, the hostname is removed only +// from entries that match the given IP-address. +// - If only a host-name is provided, the hostname is removed from any entry it +// is part of (either as canonical host-name, or as alias). +// - If, after removing the host-name from an entry, no host-names remain in +// the entry, the entry itself is removed. +// +// For example, the list of host-entries before processing could look like this: +// +// hosts = &[]string{ +// "127.0.0.2 host3 host1 host2 host4", +// "127.0.0.1 host1 host4", +// "127.0.0.3 host1", +// "127.0.0.1 host1", +// } +// +// Removing `host1` removes every occurrence: +// +// hosts = &[]string{ +// "127.0.0.2 host3 host2 host4", +// "127.0.0.1 host4", +// } +// +// Removing `host1:127.0.0.1` on the other hand, only remove the host if the +// IP-address matches: +// +// hosts = &[]string{ +// "127.0.0.2 host3 host1 host2 host4", +// "127.0.0.1 host4", +// "127.0.0.3 host1", +// } func updateHosts(flags *pflag.FlagSet, hosts *[]string) error { - // Combine existing Hosts (in swarmkit format) with the host to add (convert to swarmkit format) - if flags.Changed(flagHostAdd) { - values := convertExtraHostsToSwarmHosts(flags.Lookup(flagHostAdd).Value.(*opts.ListOpts).GetAll()) - *hosts = append(*hosts, values...) - } - // Remove duplicate - *hosts = removeDuplicates(*hosts) - - keysToRemove := make(map[string]struct{}) + var toRemove []hostMapping if flags.Changed(flagHostRemove) { - var empty struct{} extraHostsToRemove := flags.Lookup(flagHostRemove).Value.(*opts.ListOpts).GetAll() for _, entry := range extraHostsToRemove { - key := strings.SplitN(entry, ":", 2)[0] - keysToRemove[key] = empty + v := strings.SplitN(entry, ":", 2) + if len(v) > 1 { + toRemove = append(toRemove, hostMapping{IPAddr: v[1], Host: v[0]}) + } else { + toRemove = append(toRemove, hostMapping{Host: v[0]}) + } } } - newHosts := []string{} + var newHosts []string for _, entry := range *hosts { - // Since this is in swarmkit format, we need to find the key, which is canonical_hostname of: + // Since this is in SwarmKit format, we need to find the key, which is canonical_hostname of: // IP_address canonical_hostname [aliases...] parts := strings.Fields(entry) - if len(parts) > 1 { - key := parts[1] - if _, exists := keysToRemove[key]; !exists { - newHosts = append(newHosts, entry) + if len(parts) == 0 { + continue + } + ip := parts[0] + hostNames := parts[1:] + for _, rm := range toRemove { + if rm.IPAddr != "" && rm.IPAddr != ip { + continue } - } else { - newHosts = append(newHosts, entry) + for i, h := range hostNames { + if h == rm.Host { + hostNames = append(hostNames[:i], hostNames[i+1:]...) + } + } + } + if len(hostNames) > 0 { + newHosts = append(newHosts, fmt.Sprintf("%s %s", ip, strings.Join(hostNames, " "))) } } - *hosts = newHosts + // Append new hosts (in SwarmKit format) + if flags.Changed(flagHostAdd) { + values := convertExtraHostsToSwarmHosts(flags.Lookup(flagHostAdd).Value.(*opts.ListOpts).GetAll()) + newHosts = append(newHosts, values...) + } + *hosts = removeDuplicates(newHosts) return nil } diff --git a/components/cli/cli/command/service/update_test.go b/components/cli/cli/command/service/update_test.go index ff746a0420..8536dd3a73 100644 --- a/components/cli/cli/command/service/update_test.go +++ b/components/cli/cli/command/service/update_test.go @@ -366,7 +366,7 @@ func TestUpdateHosts(t *testing.T) { assert.ErrorContains(t, flags.Set("host-add", "$example.com$"), `bad format for add-host: "$example.com$"`) hosts := []string{"1.2.3.4 example.com", "4.3.2.1 example.org", "2001:db8:abc8::1 example.net"} - expected := []string{"1.2.3.4 example.com", "4.3.2.1 example.org", "2001:db8:abc8::1 ipv6.net"} + expected := []string{"1.2.3.4 example.com", "4.3.2.1 example.org", "2.2.2.2 example.net", "2001:db8:abc8::1 ipv6.net"} err := updateHosts(flags, &hosts) assert.NilError(t, err) @@ -385,6 +385,60 @@ func TestUpdateHostsPreservesOrder(t *testing.T) { assert.Check(t, is.DeepEqual([]string{"127.0.0.2 foobar", "127.0.0.1 foobar", "127.0.0.3 foobar"}, hosts)) } +func TestUpdateHostsReplaceEntry(t *testing.T) { + flags := newUpdateCommand(nil).Flags() + flags.Set("host-add", "foobar:127.0.0.4") + flags.Set("host-rm", "foobar:127.0.0.2") + + hosts := []string{"127.0.0.2 foobar", "127.0.0.1 foobar", "127.0.0.3 foobar"} + + err := updateHosts(flags, &hosts) + assert.NilError(t, err) + assert.Check(t, is.DeepEqual([]string{"127.0.0.1 foobar", "127.0.0.3 foobar", "127.0.0.4 foobar"}, hosts)) +} + +func TestUpdateHostsRemoveHost(t *testing.T) { + flags := newUpdateCommand(nil).Flags() + flags.Set("host-rm", "host1") + + hosts := []string{"127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1"} + + err := updateHosts(flags, &hosts) + assert.NilError(t, err) + + // Removing host `host1` should remove the entry from each line it appears in. + // If there are no other hosts in the entry, the entry itself should be removed. + assert.Check(t, is.DeepEqual([]string{"127.0.0.2 host3 host2 host4", "127.0.0.1 host4"}, hosts)) +} + +func TestUpdateHostsRemoveHostIP(t *testing.T) { + flags := newUpdateCommand(nil).Flags() + flags.Set("host-rm", "host1:127.0.0.1") + + hosts := []string{"127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1", "127.0.0.1 host1"} + + err := updateHosts(flags, &hosts) + assert.NilError(t, err) + + // Removing host `host1` should remove the entry from each line it appears in, + // but only if the IP-address matches. If there are no other hosts in the entry, + // the entry itself should be removed. + assert.Check(t, is.DeepEqual([]string{"127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host4", "127.0.0.3 host1"}, hosts)) +} + +func TestUpdateHostsRemoveAll(t *testing.T) { + flags := newUpdateCommand(nil).Flags() + flags.Set("host-add", "host-three:127.0.0.4") + flags.Set("host-add", "host-one:127.0.0.5") + flags.Set("host-rm", "host-one") + + hosts := []string{"127.0.0.1 host-one", "127.0.0.2 host-two", "127.0.0.3 host-one"} + + err := updateHosts(flags, &hosts) + assert.NilError(t, err) + assert.Check(t, is.DeepEqual([]string{"127.0.0.2 host-two", "127.0.0.4 host-three", "127.0.0.5 host-one"}, hosts)) +} + func TestUpdatePortsRmWithProtocol(t *testing.T) { flags := newUpdateCommand(nil).Flags() flags.Set("publish-add", "8081:81") diff --git a/components/cli/cli/command/stack/kubernetes/cli.go b/components/cli/cli/command/stack/kubernetes/cli.go index 018dd05e21..27bc819fa4 100644 --- a/components/cli/cli/command/stack/kubernetes/cli.go +++ b/components/cli/cli/command/stack/kubernetes/cli.go @@ -1,6 +1,10 @@ package kubernetes import ( + "fmt" + "net" + "net/url" + "github.com/docker/cli/cli/command" "github.com/docker/cli/kubernetes" flag "github.com/spf13/pflag" @@ -69,9 +73,43 @@ func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) { } cli.clientSet = clientSet + if dockerCli.ClientInfo().HasAll() { + if err := cli.checkHostsMatch(); err != nil { + return nil, err + } + } return cli, nil } func (c *KubeCli) composeClient() (*Factory, error) { return NewFactory(c.kubeNamespace, c.kubeConfig, c.clientSet) } + +func (c *KubeCli) checkHostsMatch() error { + daemonEndpoint, err := url.Parse(c.Client().DaemonHost()) + if err != nil { + return err + } + kubeEndpoint, err := url.Parse(c.kubeConfig.Host) + if err != nil { + return err + } + if daemonEndpoint.Hostname() == kubeEndpoint.Hostname() { + return nil + } + // The daemon can be local in Docker for Desktop, e.g. "npipe", "unix", ... + if daemonEndpoint.Scheme != "tcp" { + ips, err := net.LookupIP(kubeEndpoint.Hostname()) + if err != nil { + return err + } + for _, ip := range ips { + if ip.IsLoopback() { + return nil + } + } + } + fmt.Fprintf(c.Err(), "WARNING: Swarm and Kubernetes hosts do not match (docker host=%s, kubernetes host=%s).\n"+ + " Update $DOCKER_HOST (or pass -H), or use 'kubectl config use-context' to match.\n", daemonEndpoint.Hostname(), kubeEndpoint.Hostname()) + return nil +} diff --git a/components/cli/cli/command/system/inspect.go b/components/cli/cli/command/system/inspect.go index ef06f09974..437cd702fb 100644 --- a/components/cli/cli/command/system/inspect.go +++ b/components/cli/cli/command/system/inspect.go @@ -170,10 +170,6 @@ func inspectAll(ctx context.Context, dockerCli *command.DockerCli, getSize bool, return info.Swarm.ControlAvailable } - isErrNotSupported := func(err error) bool { - return strings.Contains(err.Error(), "not supported") - } - return func(ref string) (interface{}, []byte, error) { const ( swarmSupportUnknown = iota @@ -201,7 +197,7 @@ func inspectAll(ctx context.Context, dockerCli *command.DockerCli, getSize bool, } v, raw, err := inspectData.objectInspector(ref) if err != nil { - if typeConstraint == "" && (apiclient.IsErrNotFound(err) || isErrNotSupported(err)) { + if typeConstraint == "" && isErrSkippable(err) { continue } return v, raw, err @@ -214,3 +210,9 @@ func inspectAll(ctx context.Context, dockerCli *command.DockerCli, getSize bool, return nil, nil, errors.Errorf("Error: No such object: %s", ref) } } + +func isErrSkippable(err error) bool { + return apiclient.IsErrNotFound(err) || + strings.Contains(err.Error(), "not supported") || + strings.Contains(err.Error(), "invalid reference format") +} diff --git a/components/cli/cli/command/system/prune.go b/components/cli/cli/command/system/prune.go index 7ad24e005e..d036917015 100644 --- a/components/cli/cli/command/system/prune.go +++ b/components/cli/cli/command/system/prune.go @@ -128,6 +128,10 @@ func confirmationMessage(options pruneOptions) string { if options.pruneBuildCache { warnings = append(warnings, "all build cache") } + if len(options.filter.String()) > 0 { + warnings = append(warnings, "Elements to be pruned will be filtered with:") + warnings = append(warnings, "label="+options.filter.String()) + } var buffer bytes.Buffer t.Execute(&buffer, &warnings) diff --git a/components/cli/cli/flags/client.go b/components/cli/cli/flags/client.go index 9b6940f6bd..c57879e6a7 100644 --- a/components/cli/cli/flags/client.go +++ b/components/cli/cli/flags/client.go @@ -4,7 +4,6 @@ package flags type ClientOptions struct { Common *CommonOptions ConfigDir string - Version bool } // NewClientOptions returns a new ClientOptions diff --git a/components/cli/cmd/docker/docker.go b/components/cli/cmd/docker/docker.go index b96c1e2a98..68136657d9 100644 --- a/components/cli/cmd/docker/docker.go +++ b/components/cli/cmd/docker/docker.go @@ -32,13 +32,6 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { SilenceErrors: true, TraverseChildren: true, Args: noArgs, - RunE: func(cmd *cobra.Command, args []string) error { - if opts.Version { - showVersion() - return nil - } - return command.ShowHelp(dockerCli.Err())(cmd, args) - }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { // flags must be the top-level command flags, not cmd.Flags() opts.Common.SetDefaultOptions(flags) @@ -48,11 +41,13 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { } return isSupported(cmd, dockerCli) }, + Version: fmt.Sprintf("%s, build %s", cli.Version, cli.GitCommit), + DisableFlagsInUseLine: true, } cli.SetupRootCommand(cmd) flags = cmd.Flags() - flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit") + flags.BoolP("version", "v", false, "Print version information and quit") flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files") opts.Common.InstallFlags(flags) @@ -63,11 +58,19 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { cmd.SetOutput(dockerCli.Out()) commands.AddCommands(cmd, dockerCli) + disableFlagsInUseLine(cmd) setValidateArgs(dockerCli, cmd, flags, opts) return cmd } +func disableFlagsInUseLine(cmd *cobra.Command) { + visitAll(cmd, func(ccmd *cobra.Command) { + // do not add a `[flags]` to the end of the usage line. + ccmd.DisableFlagsInUseLine = true + }) +} + func setFlagErrorFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) { // When invoking `docker stack --nonsense`, we need to make sure FlagErrorFunc return appropriate // output if the feature is not supported. @@ -186,10 +189,6 @@ func contentTrustEnabled() bool { return false } -func showVersion() { - fmt.Printf("Docker version %s, build %s\n", cli.Version, cli.GitCommit) -} - func dockerPreRun(opts *cliflags.ClientOptions) { cliflags.SetLogLevel(opts.Common.LogLevel) diff --git a/components/cli/docs/reference/run.md b/components/cli/docs/reference/run.md index 3a6bf5b662..521e41de7f 100644 --- a/components/cli/docs/reference/run.md +++ b/components/cli/docs/reference/run.md @@ -1275,7 +1275,7 @@ container's logging driver. The following options are supported: The `docker logs` command is available only for the `json-file` and `journald` logging drivers. For detailed information on working with logging drivers, see -[Configure a logging driver](https://docs.docker.com/engine/admin/logging/overview/). +[Configure logging drivers](https://docs.docker.com/config/containers/logging/configure/). ## Overriding Dockerfile image defaults @@ -1398,7 +1398,7 @@ If the operator uses `--link` when starting a new client container in the default bridge network, then the client container can access the exposed port via a private networking interface. If `--link` is used when starting a container in a user-defined network as -described in [*Docker network overview*](https://docs.docker.com/engine/userguide/networking/), +described in [*Networking overview*](https://docs.docker.com/network/), it will provide a named alias for the container being linked to. ### ENV (environment variables) @@ -1571,8 +1571,7 @@ The example below mounts an empty tmpfs into the container with the `rw`, > a volume. The volumes commands are complex enough to have their own documentation -in section [*Manage data in -containers*](https://docs.docker.com/engine/tutorials/dockervolumes/). A developer can define +in section [*Use volumes*](https://docs.docker.com/storage/volumes/). A developer can define one or more `VOLUME`'s associated with an image, but only the operator can give access from one container to another (or from a container to a volume mounted on the host). diff --git a/components/cli/docs/yaml/yaml.go b/components/cli/docs/yaml/yaml.go index 8512be339b..5dc7164192 100644 --- a/components/cli/docs/yaml/yaml.go +++ b/components/cli/docs/yaml/yaml.go @@ -8,7 +8,6 @@ import ( "sort" "strings" - "github.com/docker/cli/cli" "github.com/spf13/cobra" "github.com/spf13/pflag" yaml "gopkg.in/yaml.v2" @@ -96,7 +95,7 @@ func GenYamlCustom(cmd *cobra.Command, w io.Writer) error { } if cmd.Runnable() { - cliDoc.Usage = cli.UseLine(cmd) + cliDoc.Usage = cmd.UseLine() } if len(cmd.Example) > 0 { diff --git a/components/cli/e2e/system/inspect_test.go b/components/cli/e2e/system/inspect_test.go new file mode 100644 index 0000000000..aefab51606 --- /dev/null +++ b/components/cli/e2e/system/inspect_test.go @@ -0,0 +1,18 @@ +package system + +import ( + "testing" + + "github.com/gotestyourself/gotestyourself/icmd" +) + +// TestInspectInvalidReference migrated from moby/integration-cli +func TestInspectInvalidReference(t *testing.T) { + // This test should work on both Windows and Linux + result := icmd.RunCmd(icmd.Command("docker", "inspect", "FooBar")) + result.Assert(t, icmd.Expected{ + Out: "[]", + Err: "Error: No such object: FooBar", + ExitCode: 1, + }) +} diff --git a/components/cli/e2e/system/main_test.go b/components/cli/e2e/system/main_test.go new file mode 100644 index 0000000000..b154d1a191 --- /dev/null +++ b/components/cli/e2e/system/main_test.go @@ -0,0 +1,17 @@ +package system + +import ( + "fmt" + "os" + "testing" + + "github.com/docker/cli/internal/test/environment" +) + +func TestMain(m *testing.M) { + if err := environment.Setup(); err != nil { + fmt.Println(err.Error()) + os.Exit(3) + } + os.Exit(m.Run()) +} diff --git a/components/cli/man/dockerd.8.md b/components/cli/man/dockerd.8.md index 6d2c930443..0224035970 100644 --- a/components/cli/man/dockerd.8.md +++ b/components/cli/man/dockerd.8.md @@ -811,8 +811,8 @@ command line or Docker's Engine API are allowed or denied by the plugin. If you have multiple plugins installed, each plugin, in order, must allow the request for it to complete. -For information about how to create an authorization plugin, see [authorization -plugin](https://docs.docker.com/engine/extend/authorization/) section in the +For information about how to create an authorization plugin, see [access authorization +plugin](https://docs.docker.com/engine/extend/plugins_authorization/) section in the Docker extend section of this documentation. # RUNTIME EXECUTION OPTIONS diff --git a/components/cli/man/src/image/pull.md b/components/cli/man/src/image/pull.md index 778c7e0bc9..73f641f727 100644 --- a/components/cli/man/src/image/pull.md +++ b/components/cli/man/src/image/pull.md @@ -56,7 +56,7 @@ same image, their layers are stored only once and do not consume extra disk space. For more information about images, layers, and the content-addressable store, -refer to [understand images, containers, and storage drivers](https://docs.docker.com/engine/userguide/storagedriver/imagesandcontainers/) +refer to [about storage drivers](https://docs.docker.com/storage/storagedriver/) in the online documentation. diff --git a/components/cli/vendor.conf b/components/cli/vendor.conf index f21dad5386..48209caad8 100755 --- a/components/cli/vendor.conf +++ b/components/cli/vendor.conf @@ -64,8 +64,8 @@ github.com/PuerkitoBio/urlesc 5bd2802263f21d8788851d5305584c82a5c75d7e github.com/russross/blackfriday 1d6b8e9301e720b08a8938b8c25c018285885438 github.com/shurcooL/sanitized_anchor_name 10ef21a441db47d8b13ebcc5fd2310f636973c77 github.com/sirupsen/logrus v1.0.3 -github.com/spf13/cobra 34ceca591bcf34a17a8b7bad5b3ce5f9c165bee5 -github.com/spf13/pflag 97afa5e7ca8a08a383cb259e06636b5e2cc7897f +github.com/spf13/cobra v0.0.3 +github.com/spf13/pflag v1.0.1 github.com/theupdateframework/notary v0.6.1 github.com/tonistiigi/fsutil dea3a0da73aee887fc02142d995be764106ac5e2 github.com/xeipuuv/gojsonpointer e0fe6f68307607d540ed8eac07a342c33fa1b54a diff --git a/components/cli/vendor/github.com/spf13/cobra/README.md b/components/cli/vendor/github.com/spf13/cobra/README.md index d7279e4f9f..851fcc087c 100644 --- a/components/cli/vendor/github.com/spf13/cobra/README.md +++ b/components/cli/vendor/github.com/spf13/cobra/README.md @@ -20,6 +20,7 @@ Many of the most widely used Go projects are built using Cobra including: * [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) * [rclone](http://rclone.org/) * [nehm](https://github.com/bogem/nehm) +* [Pouch](https://github.com/alibaba/pouch) [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) [![CircleCI status](https://circleci.com/gh/spf13/cobra.png?circle-token=:circle-token "CircleCI status")](https://circleci.com/gh/spf13/cobra) @@ -158,10 +159,7 @@ import ( ) func main() { - if err := cmd.RootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } + cmd.Execute() } ``` @@ -174,7 +172,7 @@ commands you want. It's the easiest way to incorporate Cobra into your applicati ## Using the Cobra Library -To manually implement Cobra you need to create a bare main.go file and a RootCmd file. +To manually implement Cobra you need to create a bare main.go file and a rootCmd file. You will optionally provide additional commands as you see fit. ### Create rootCmd @@ -184,7 +182,7 @@ Cobra doesn't require any special constructors. Simply create your commands. Ideally you place this in app/cmd/root.go: ```go -var RootCmd = &cobra.Command{ +var rootCmd = &cobra.Command{ Use: "hugo", Short: "Hugo is a very fast static site generator", Long: `A Fast and Flexible Static Site Generator built with @@ -194,6 +192,13 @@ var RootCmd = &cobra.Command{ // Do Stuff Here }, } + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} ``` You will additionally define flags and handle configuration in your init() function. @@ -212,14 +217,14 @@ import ( func init() { cobra.OnInitialize(initConfig) - RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") - RootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/") - RootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution") - RootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)") - RootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration") - viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author")) - viper.BindPFlag("projectbase", RootCmd.PersistentFlags().Lookup("projectbase")) - viper.BindPFlag("useViper", RootCmd.PersistentFlags().Lookup("viper")) + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") + rootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/") + rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution") + rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)") + rootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration") + viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) + viper.BindPFlag("projectbase", rootCmd.PersistentFlags().Lookup("projectbase")) + viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper")) viper.SetDefault("author", "NAME HERE ") viper.SetDefault("license", "apache") } @@ -267,10 +272,7 @@ import ( ) func main() { - if err := cmd.RootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } + cmd.Execute() } ``` @@ -286,12 +288,13 @@ populate it with the following: package cmd import ( - "github.com/spf13/cobra" "fmt" + + "github.com/spf13/cobra" ) func init() { - RootCmd.AddCommand(versionCmd) + rootCmd.AddCommand(versionCmd) } var versionCmd = &cobra.Command{ @@ -328,7 +331,7 @@ command it's assigned to as well as every command under that command. For global flags, assign a flag as a persistent flag on the root. ```go -RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") +rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") ``` ### Local Flags @@ -336,13 +339,13 @@ RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose out A flag can also be assigned locally which will only apply to that specific command. ```go -RootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") +rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") ``` ### Local Flag on Parent Commands -By default Cobra only parses local flags on the target command, any local flags on -parent commands are ignored. By enabling `Command.TraverseChildren` Cobra will +By default Cobra only parses local flags on the target command, any local flags on +parent commands are ignored. By enabling `Command.TraverseChildren` Cobra will parse local flags on each command before executing the target command. ```go @@ -359,8 +362,8 @@ You can also bind your flags with [viper](https://github.com/spf13/viper): var author string func init() { - RootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution") - viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author")) + rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution") + viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) } ``` @@ -370,6 +373,15 @@ when the `--author` flag is not provided by user. More in [viper documentation](https://github.com/spf13/viper#working-with-flags). +### Required flags + +Flags are optional by default. If instead you wish your command to report an error +when a flag has not been set, mark it as required: +```go +rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)") +rootCmd.MarkFlagRequired("region") +``` + ## Positional and Custom Arguments Validation of positional arguments can be specified using the `Args` field @@ -565,6 +577,13 @@ cmd.SetUsageFunc(f func(*Command) error) cmd.SetUsageTemplate(s string) ``` +## Version Flag + +Cobra adds a top-level '--version' flag if the Version field is set on the root command. +Running an application with the '--version' flag will print the version to stdout using +the version template. The template can be customized using the +`cmd.SetVersionTemplate(s string)` function. + ## PreRun and PostRun Hooks It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order: diff --git a/components/cli/vendor/github.com/spf13/cobra/bash_completions.go b/components/cli/vendor/github.com/spf13/cobra/bash_completions.go index c19fe7a068..8fa8f486fa 100644 --- a/components/cli/vendor/github.com/spf13/cobra/bash_completions.go +++ b/components/cli/vendor/github.com/spf13/cobra/bash_completions.go @@ -21,8 +21,8 @@ const ( func writePreamble(buf *bytes.Buffer, name string) { buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name)) - buf.WriteString(` -__debug() + buf.WriteString(fmt.Sprintf(` +__%[1]s_debug() { if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then echo "$*" >> "${BASH_COMP_DEBUG_FILE}" @@ -31,13 +31,13 @@ __debug() # Homebrew on Macs have version 1.3 of bash-completion which doesn't include # _init_completion. This is a very minimal version of that function. -__my_init_completion() +__%[1]s_init_completion() { COMPREPLY=() _get_comp_words_by_ref "$@" cur prev words cword } -__index_of_word() +__%[1]s_index_of_word() { local w word=$1 shift @@ -49,7 +49,7 @@ __index_of_word() index=-1 } -__contains_word() +__%[1]s_contains_word() { local w word=$1; shift for w in "$@"; do @@ -58,9 +58,9 @@ __contains_word() return 1 } -__handle_reply() +__%[1]s_handle_reply() { - __debug "${FUNCNAME[0]}" + __%[1]s_debug "${FUNCNAME[0]}" case $cur in -*) if [[ $(type -t compopt) = "builtin" ]]; then @@ -85,7 +85,7 @@ __handle_reply() local index flag flag="${cur%%=*}" - __index_of_word "${flag}" "${flags_with_completion[@]}" + __%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}" COMPREPLY=() if [[ ${index} -ge 0 ]]; then PREFIX="" @@ -103,7 +103,7 @@ __handle_reply() # check if we are handling a flag with special work handling local index - __index_of_word "${prev}" "${flags_with_completion[@]}" + __%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}" if [[ ${index} -ge 0 ]]; then ${flags_completion[${index}]} return @@ -136,24 +136,30 @@ __handle_reply() if declare -F __ltrim_colon_completions >/dev/null; then __ltrim_colon_completions "$cur" fi + + # If there is only 1 completion and it is a flag with an = it will be completed + # but we don't want a space after the = + if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then + compopt -o nospace + fi } # The arguments should be in the form "ext1|ext2|extn" -__handle_filename_extension_flag() +__%[1]s_handle_filename_extension_flag() { local ext="$1" _filedir "@(${ext})" } -__handle_subdirs_in_dir_flag() +__%[1]s_handle_subdirs_in_dir_flag() { local dir="$1" pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 } -__handle_flag() +__%[1]s_handle_flag() { - __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" # if a command required a flag, and we found it, unset must_have_one_flag() local flagname=${words[c]} @@ -164,27 +170,30 @@ __handle_flag() flagname=${flagname%%=*} # strip everything after the = flagname="${flagname}=" # but put the = back fi - __debug "${FUNCNAME[0]}: looking for ${flagname}" - if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then + __%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}" + if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then must_have_one_flag=() fi # if you set a flag which only applies to this command, don't show subcommands - if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then commands=() fi # keep flag value with flagname as flaghash - if [ -n "${flagvalue}" ] ; then - flaghash[${flagname}]=${flagvalue} - elif [ -n "${words[ $((c+1)) ]}" ] ; then - flaghash[${flagname}]=${words[ $((c+1)) ]} - else - flaghash[${flagname}]="true" # pad "true" for bool flag + # flaghash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then + if [ -n "${flagvalue}" ] ; then + flaghash[${flagname}]=${flagvalue} + elif [ -n "${words[ $((c+1)) ]}" ] ; then + flaghash[${flagname}]=${words[ $((c+1)) ]} + else + flaghash[${flagname}]="true" # pad "true" for bool flag + fi fi # skip the argument to a two word flag - if __contains_word "${words[c]}" "${two_word_flags[@]}"; then + if __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then c=$((c+1)) # if we are looking for a flags value, don't show commands if [[ $c -eq $cword ]]; then @@ -196,13 +205,13 @@ __handle_flag() } -__handle_noun() +__%[1]s_handle_noun() { - __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then must_have_one_noun=() - elif __contains_word "${words[c]}" "${noun_aliases[@]}"; then + elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then must_have_one_noun=() fi @@ -210,45 +219,53 @@ __handle_noun() c=$((c+1)) } -__handle_command() +__%[1]s_handle_command() { - __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" local next_command if [[ -n ${last_command} ]]; then next_command="_${last_command}_${words[c]//:/__}" else if [[ $c -eq 0 ]]; then - next_command="_$(basename "${words[c]//:/__}")" + next_command="_%[1]s_root_command" else next_command="_${words[c]//:/__}" fi fi c=$((c+1)) - __debug "${FUNCNAME[0]}: looking for ${next_command}" + __%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}" declare -F "$next_command" >/dev/null && $next_command } -__handle_word() +__%[1]s_handle_word() { if [[ $c -ge $cword ]]; then - __handle_reply + __%[1]s_handle_reply return fi - __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" if [[ "${words[c]}" == -* ]]; then - __handle_flag - elif __contains_word "${words[c]}" "${commands[@]}"; then - __handle_command - elif [[ $c -eq 0 ]] && __contains_word "$(basename "${words[c]}")" "${commands[@]}"; then - __handle_command + __%[1]s_handle_flag + elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then + __%[1]s_handle_command + elif [[ $c -eq 0 ]]; then + __%[1]s_handle_command + elif __%[1]s_contains_word "${words[c]}" "${command_aliases[@]}"; then + # aliashash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then + words[c]=${aliashash[${words[c]}]} + __%[1]s_handle_command + else + __%[1]s_handle_noun + fi else - __handle_noun + __%[1]s_handle_noun fi - __handle_word + __%[1]s_handle_word } -`) +`, name)) } func writePostscript(buf *bytes.Buffer, name string) { @@ -257,10 +274,11 @@ func writePostscript(buf *bytes.Buffer, name string) { buf.WriteString(fmt.Sprintf(`{ local cur prev words cword declare -A flaghash 2>/dev/null || : + declare -A aliashash 2>/dev/null || : if declare -F _init_completion >/dev/null 2>&1; then _init_completion -s || return else - __my_init_completion -n "=" || return + __%[1]s_init_completion -n "=" || return fi local c=0 @@ -269,13 +287,13 @@ func writePostscript(buf *bytes.Buffer, name string) { local local_nonpersistent_flags=() local flags_with_completion=() local flags_completion=() - local commands=("%s") + local commands=("%[1]s") local must_have_one_flag=() local must_have_one_noun=() local last_command local nouns=() - __handle_word + __%[1]s_handle_word } `, name)) @@ -296,11 +314,12 @@ func writeCommands(buf *bytes.Buffer, cmd *Command) { continue } buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name())) + writeCmdAliases(buf, c) } buf.WriteString("\n") } -func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string) { +func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) { for key, value := range annotations { switch key { case BashCompFilenameExt: @@ -308,7 +327,7 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s var ext string if len(value) > 0 { - ext = "__handle_filename_extension_flag " + strings.Join(value, "|") + ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|") } else { ext = "_filedir" } @@ -326,7 +345,7 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s var ext string if len(value) == 1 { - ext = "__handle_subdirs_in_dir_flag " + value[0] + ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0] } else { ext = "_filedir -d" } @@ -335,7 +354,7 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s } } -func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag) { +func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { name := flag.Shorthand format := " " if len(flag.NoOptDefVal) == 0 { @@ -343,10 +362,10 @@ func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag) { } format += "flags+=(\"-%s\")\n" buf.WriteString(fmt.Sprintf(format, name)) - writeFlagHandler(buf, "-"+name, flag.Annotations) + writeFlagHandler(buf, "-"+name, flag.Annotations, cmd) } -func writeFlag(buf *bytes.Buffer, flag *pflag.Flag) { +func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { name := flag.Name format := " flags+=(\"--%s" if len(flag.NoOptDefVal) == 0 { @@ -354,7 +373,7 @@ func writeFlag(buf *bytes.Buffer, flag *pflag.Flag) { } format += "\")\n" buf.WriteString(fmt.Sprintf(format, name)) - writeFlagHandler(buf, "--"+name, flag.Annotations) + writeFlagHandler(buf, "--"+name, flag.Annotations, cmd) } func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) { @@ -380,9 +399,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) { if nonCompletableFlag(flag) { return } - writeFlag(buf, flag) + writeFlag(buf, flag, cmd) if len(flag.Shorthand) > 0 { - writeShortFlag(buf, flag) + writeShortFlag(buf, flag, cmd) } if localNonPersistentFlags.Lookup(flag.Name) != nil { writeLocalNonPersistentFlag(buf, flag) @@ -392,9 +411,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) { if nonCompletableFlag(flag) { return } - writeFlag(buf, flag) + writeFlag(buf, flag, cmd) if len(flag.Shorthand) > 0 { - writeShortFlag(buf, flag) + writeShortFlag(buf, flag, cmd) } }) @@ -434,6 +453,21 @@ func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) { } } +func writeCmdAliases(buf *bytes.Buffer, cmd *Command) { + if len(cmd.Aliases) == 0 { + return + } + + sort.Sort(sort.StringSlice(cmd.Aliases)) + + buf.WriteString(fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n")) + for _, value := range cmd.Aliases { + buf.WriteString(fmt.Sprintf(" command_aliases+=(%q)\n", value)) + buf.WriteString(fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name())) + } + buf.WriteString(` fi`) + buf.WriteString("\n") +} func writeArgAliases(buf *bytes.Buffer, cmd *Command) { buf.WriteString(" noun_aliases=()\n") sort.Sort(sort.StringSlice(cmd.ArgAliases)) @@ -452,8 +486,18 @@ func gen(buf *bytes.Buffer, cmd *Command) { commandName := cmd.CommandPath() commandName = strings.Replace(commandName, " ", "_", -1) commandName = strings.Replace(commandName, ":", "__", -1) - buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName)) + + if cmd.Root() == cmd { + buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName)) + } else { + buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName)) + } + buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName)) + buf.WriteString("\n") + buf.WriteString(" command_aliases=()\n") + buf.WriteString("\n") + writeCommands(buf, cmd) writeFlags(buf, cmd) writeRequiredFlag(buf, cmd) @@ -491,17 +535,20 @@ func (c *Command) GenBashCompletionFile(filename string) error { return c.GenBashCompletion(outFile) } -// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists. +// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, +// and causes your command to report an error if invoked without the flag. func (c *Command) MarkFlagRequired(name string) error { return MarkFlagRequired(c.Flags(), name) } -// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag, if it exists. +// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists, +// and causes your command to report an error if invoked without the flag. func (c *Command) MarkPersistentFlagRequired(name string) error { return MarkFlagRequired(c.PersistentFlags(), name) } -// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists. +// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, +// and causes your command to report an error if invoked without the flag. func MarkFlagRequired(flags *pflag.FlagSet, name string) error { return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"}) } diff --git a/components/cli/vendor/github.com/spf13/cobra/cobra.go b/components/cli/vendor/github.com/spf13/cobra/cobra.go index e4b910c5d7..7010fd15b7 100644 --- a/components/cli/vendor/github.com/spf13/cobra/cobra.go +++ b/components/cli/vendor/github.com/spf13/cobra/cobra.go @@ -70,7 +70,8 @@ func AddTemplateFuncs(tmplFuncs template.FuncMap) { } } -// OnInitialize takes a series of func() arguments and appends them to a slice of func(). +// OnInitialize sets the passed functions to be run when each command's +// Execute method is called. func OnInitialize(y ...func()) { initializers = append(initializers, y...) } diff --git a/components/cli/vendor/github.com/spf13/cobra/command.go b/components/cli/vendor/github.com/spf13/cobra/command.go index 1e41557f29..34d1bf3671 100644 --- a/components/cli/vendor/github.com/spf13/cobra/command.go +++ b/components/cli/vendor/github.com/spf13/cobra/command.go @@ -27,6 +27,9 @@ import ( flag "github.com/spf13/pflag" ) +// FParseErrWhitelist configures Flag parse errors to be ignored +type FParseErrWhitelist flag.ParseErrorsWhitelist + // Command is just that, a command for your application. // E.g. 'go run ...' - 'run' is the command. Cobra requires // you to define the usage and description as part of your command @@ -75,6 +78,11 @@ type Command struct { // group commands. Annotations map[string]string + // Version defines the version for this command. If this value is non-empty and the command does not + // define a "version" flag, a "version" boolean flag will be added to the command and, if specified, + // will print content of the "Version" variable. + Version string + // The *Run functions are executed in the following order: // * PersistentPreRun() // * PreRun() @@ -132,6 +140,9 @@ type Command struct { // TraverseChildren parses flags on all parents before executing child command. TraverseChildren bool + //FParseErrWhitelist flag parse errors to be ignored + FParseErrWhitelist FParseErrWhitelist + // commands is the list of commands supported by this program. commands []*Command // parent is a parent command for this command. @@ -142,6 +153,11 @@ type Command struct { commandsMaxNameLen int // commandsAreSorted defines, if command slice are sorted or not. commandsAreSorted bool + // commandCalledAs is the name or alias value used to call this command. + commandCalledAs struct { + name string + called bool + } // args is actual args parsed from flags. args []string @@ -177,6 +193,8 @@ type Command struct { // helpCommand is command with usage 'help'. If it's not defined by user, // cobra uses default help command. helpCommand *Command + // versionTemplate is the version template defined by user. + versionTemplate string } // SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden @@ -222,6 +240,11 @@ func (c *Command) SetHelpTemplate(s string) { c.helpTemplate = s } +// SetVersionTemplate sets version template to be used. Application can use it to set custom template. +func (c *Command) SetVersionTemplate(s string) { + c.versionTemplate = s +} + // SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands. // The user should not have a cyclic dependency on commands. func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) { @@ -411,6 +434,19 @@ func (c *Command) HelpTemplate() string { {{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` } +// VersionTemplate return version template for the command. +func (c *Command) VersionTemplate() string { + if c.versionTemplate != "" { + return c.versionTemplate + } + + if c.HasParent() { + return c.parent.VersionTemplate() + } + return `{{with .Name}}{{printf "%s " .}}{{end}}{{printf "version %s" .Version}} +` +} + func hasNoOptDefVal(name string, fs *flag.FlagSet) bool { flag := fs.Lookup(name) if flag == nil { @@ -445,6 +481,9 @@ Loop: s := args[0] args = args[1:] switch { + case s == "--": + // "--" terminates the flags + break Loop case strings.HasPrefix(s, "--") && !strings.Contains(s, "=") && !hasNoOptDefVal(s[2:], flags): // If '--flag arg' then // delete arg from args. @@ -532,6 +571,7 @@ func (c *Command) findNext(next string) *Command { matches := make([]*Command, 0) for _, cmd := range c.commands { if cmd.Name() == next || cmd.HasAlias(next) { + cmd.commandCalledAs.name = next return cmd } if EnablePrefixMatching && cmd.hasNameOrAliasPrefix(next) { @@ -542,6 +582,7 @@ func (c *Command) findNext(next string) *Command { if len(matches) == 1 { return matches[0] } + return nil } @@ -640,9 +681,10 @@ func (c *Command) execute(a []string) (err error) { c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated) } - // initialize help flag as the last point possible to allow for user + // initialize help and version flag at the last point possible to allow for user // overriding c.InitDefaultHelpFlag() + c.InitDefaultVersionFlag() err = c.ParseFlags(a) if err != nil { @@ -659,7 +701,27 @@ func (c *Command) execute(a []string) (err error) { return err } - if helpVal || !c.Runnable() { + if helpVal { + return flag.ErrHelp + } + + // for back-compat, only add version flag behavior if version is defined + if c.Version != "" { + versionVal, err := c.Flags().GetBool("version") + if err != nil { + c.Println("\"version\" flag declared as non-bool. Please correct your code") + return err + } + if versionVal { + err := tmpl(c.OutOrStdout(), c.VersionTemplate(), c) + if err != nil { + c.Println(err) + } + return err + } + } + + if !c.Runnable() { return flag.ErrHelp } @@ -782,6 +844,11 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { return c, err } + cmd.commandCalledAs.called = true + if cmd.commandCalledAs.name == "" { + cmd.commandCalledAs.name = cmd.Name() + } + err = cmd.execute(flags) if err != nil { // Always show help if requested, even if SilenceErrors is in @@ -827,7 +894,7 @@ func (c *Command) validateRequiredFlags() error { }) if len(missingFlagNames) > 0 { - return fmt.Errorf(`Required flag(s) "%s" have/has not been set`, strings.Join(missingFlagNames, `", "`)) + return fmt.Errorf(`required flag(s) "%s" not set`, strings.Join(missingFlagNames, `", "`)) } return nil } @@ -848,6 +915,27 @@ func (c *Command) InitDefaultHelpFlag() { } } +// InitDefaultVersionFlag adds default version flag to c. +// It is called automatically by executing the c. +// If c already has a version flag, it will do nothing. +// If c.Version is empty, it will do nothing. +func (c *Command) InitDefaultVersionFlag() { + if c.Version == "" { + return + } + + c.mergePersistentFlags() + if c.Flags().Lookup("version") == nil { + usage := "version for " + if c.Name() == "" { + usage += "this command" + } else { + usage += c.Name() + } + c.Flags().Bool("version", false, usage) + } +} + // InitDefaultHelpCmd adds default help command to c. // It is called automatically by executing the c or by calling help and usage. // If c already has help command or c has no subcommands, it will do nothing. @@ -1068,14 +1156,25 @@ func (c *Command) HasAlias(s string) bool { return false } +// CalledAs returns the command name or alias that was used to invoke +// this command or an empty string if the command has not been called. +func (c *Command) CalledAs() string { + if c.commandCalledAs.called { + return c.commandCalledAs.name + } + return "" +} + // hasNameOrAliasPrefix returns true if the Name or any of aliases start // with prefix func (c *Command) hasNameOrAliasPrefix(prefix string) bool { if strings.HasPrefix(c.Name(), prefix) { + c.commandCalledAs.name = c.Name() return true } for _, alias := range c.Aliases { if strings.HasPrefix(alias, prefix) { + c.commandCalledAs.name = alias return true } } @@ -1178,7 +1277,7 @@ func (c *Command) HasParent() bool { return c.parent != nil } -// GlobalNormalizationFunc returns the global normalization function or nil if doesn't exists. +// GlobalNormalizationFunc returns the global normalization function or nil if it doesn't exist. func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) flag.NormalizedName { return c.globNormFunc } @@ -1370,6 +1469,10 @@ func (c *Command) ParseFlags(args []string) error { } beforeErrorBufLen := c.flagErrorBuf.Len() c.mergePersistentFlags() + + //do it here after merging all flags and just before parse + c.Flags().ParseErrorsWhitelist = flag.ParseErrorsWhitelist(c.FParseErrWhitelist) + err := c.Flags().Parse(args) // Print warnings if they occurred (e.g. deprecated flag messages). if c.flagErrorBuf.Len()-beforeErrorBufLen > 0 && err == nil { diff --git a/components/cli/vendor/github.com/spf13/cobra/doc/man_docs.go b/components/cli/vendor/github.com/spf13/cobra/doc/man_docs.go index ce92332dd1..baa48118a6 100644 --- a/components/cli/vendor/github.com/spf13/cobra/doc/man_docs.go +++ b/components/cli/vendor/github.com/spf13/cobra/doc/man_docs.go @@ -176,13 +176,13 @@ func manPrintFlags(buf *bytes.Buffer, flags *pflag.FlagSet) { func manPrintOptions(buf *bytes.Buffer, command *cobra.Command) { flags := command.NonInheritedFlags() - if flags.HasFlags() { + if flags.HasAvailableFlags() { buf.WriteString("# OPTIONS\n") manPrintFlags(buf, flags) buf.WriteString("\n") } flags = command.InheritedFlags() - if flags.HasFlags() { + if flags.HasAvailableFlags() { buf.WriteString("# OPTIONS INHERITED FROM PARENT COMMANDS\n") manPrintFlags(buf, flags) buf.WriteString("\n") diff --git a/components/cli/vendor/github.com/spf13/cobra/doc/md_docs.go b/components/cli/vendor/github.com/spf13/cobra/doc/md_docs.go index 68cf5bf648..d76f6d5ecb 100644 --- a/components/cli/vendor/github.com/spf13/cobra/doc/md_docs.go +++ b/components/cli/vendor/github.com/spf13/cobra/doc/md_docs.go @@ -29,7 +29,7 @@ import ( func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error { flags := cmd.NonInheritedFlags() flags.SetOutput(buf) - if flags.HasFlags() { + if flags.HasAvailableFlags() { buf.WriteString("### Options\n\n```\n") flags.PrintDefaults() buf.WriteString("```\n\n") @@ -37,7 +37,7 @@ func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error { parentFlags := cmd.InheritedFlags() parentFlags.SetOutput(buf) - if parentFlags.HasFlags() { + if parentFlags.HasAvailableFlags() { buf.WriteString("### Options inherited from parent commands\n\n```\n") parentFlags.PrintDefaults() buf.WriteString("```\n\n") @@ -67,7 +67,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) buf.WriteString("## " + name + "\n\n") buf.WriteString(short + "\n\n") buf.WriteString("### Synopsis\n\n") - buf.WriteString("\n" + long + "\n\n") + buf.WriteString(long + "\n\n") if cmd.Runnable() { buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine())) @@ -82,7 +82,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) return err } if hasSeeAlso(cmd) { - buf.WriteString("### SEE ALSO\n") + buf.WriteString("### SEE ALSO\n\n") if cmd.HasParent() { parent := cmd.Parent() pname := parent.CommandPath() diff --git a/components/cli/vendor/github.com/spf13/cobra/doc/rest_docs.go b/components/cli/vendor/github.com/spf13/cobra/doc/rest_docs.go index 4913e3ee2e..051d8dc832 100644 --- a/components/cli/vendor/github.com/spf13/cobra/doc/rest_docs.go +++ b/components/cli/vendor/github.com/spf13/cobra/doc/rest_docs.go @@ -29,7 +29,7 @@ import ( func printOptionsReST(buf *bytes.Buffer, cmd *cobra.Command, name string) error { flags := cmd.NonInheritedFlags() flags.SetOutput(buf) - if flags.HasFlags() { + if flags.HasAvailableFlags() { buf.WriteString("Options\n") buf.WriteString("~~~~~~~\n\n::\n\n") flags.PrintDefaults() @@ -38,7 +38,7 @@ func printOptionsReST(buf *bytes.Buffer, cmd *cobra.Command, name string) error parentFlags := cmd.InheritedFlags() parentFlags.SetOutput(buf) - if parentFlags.HasFlags() { + if parentFlags.HasAvailableFlags() { buf.WriteString("Options inherited from parent commands\n") buf.WriteString("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n::\n\n") parentFlags.PrintDefaults() diff --git a/components/cli/vendor/github.com/spf13/pflag/bytes.go b/components/cli/vendor/github.com/spf13/pflag/bytes.go new file mode 100644 index 0000000000..12c58db9fe --- /dev/null +++ b/components/cli/vendor/github.com/spf13/pflag/bytes.go @@ -0,0 +1,105 @@ +package pflag + +import ( + "encoding/hex" + "fmt" + "strings" +) + +// BytesHex adapts []byte for use as a flag. Value of flag is HEX encoded +type bytesHexValue []byte + +func (bytesHex bytesHexValue) String() string { + return fmt.Sprintf("%X", []byte(bytesHex)) +} + +func (bytesHex *bytesHexValue) Set(value string) error { + bin, err := hex.DecodeString(strings.TrimSpace(value)) + + if err != nil { + return err + } + + *bytesHex = bin + + return nil +} + +func (*bytesHexValue) Type() string { + return "bytesHex" +} + +func newBytesHexValue(val []byte, p *[]byte) *bytesHexValue { + *p = val + return (*bytesHexValue)(p) +} + +func bytesHexConv(sval string) (interface{}, error) { + + bin, err := hex.DecodeString(sval) + + if err == nil { + return bin, nil + } + + return nil, fmt.Errorf("invalid string being converted to Bytes: %s %s", sval, err) +} + +// GetBytesHex return the []byte value of a flag with the given name +func (f *FlagSet) GetBytesHex(name string) ([]byte, error) { + val, err := f.getFlagType(name, "bytesHex", bytesHexConv) + + if err != nil { + return []byte{}, err + } + + return val.([]byte), nil +} + +// BytesHexVar defines an []byte flag with specified name, default value, and usage string. +// The argument p points to an []byte variable in which to store the value of the flag. +func (f *FlagSet) BytesHexVar(p *[]byte, name string, value []byte, usage string) { + f.VarP(newBytesHexValue(value, p), name, "", usage) +} + +// BytesHexVarP is like BytesHexVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) BytesHexVarP(p *[]byte, name, shorthand string, value []byte, usage string) { + f.VarP(newBytesHexValue(value, p), name, shorthand, usage) +} + +// BytesHexVar defines an []byte flag with specified name, default value, and usage string. +// The argument p points to an []byte variable in which to store the value of the flag. +func BytesHexVar(p *[]byte, name string, value []byte, usage string) { + CommandLine.VarP(newBytesHexValue(value, p), name, "", usage) +} + +// BytesHexVarP is like BytesHexVar, but accepts a shorthand letter that can be used after a single dash. +func BytesHexVarP(p *[]byte, name, shorthand string, value []byte, usage string) { + CommandLine.VarP(newBytesHexValue(value, p), name, shorthand, usage) +} + +// BytesHex defines an []byte flag with specified name, default value, and usage string. +// The return value is the address of an []byte variable that stores the value of the flag. +func (f *FlagSet) BytesHex(name string, value []byte, usage string) *[]byte { + p := new([]byte) + f.BytesHexVarP(p, name, "", value, usage) + return p +} + +// BytesHexP is like BytesHex, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) BytesHexP(name, shorthand string, value []byte, usage string) *[]byte { + p := new([]byte) + f.BytesHexVarP(p, name, shorthand, value, usage) + return p +} + +// BytesHex defines an []byte flag with specified name, default value, and usage string. +// The return value is the address of an []byte variable that stores the value of the flag. +func BytesHex(name string, value []byte, usage string) *[]byte { + return CommandLine.BytesHexP(name, "", value, usage) +} + +// BytesHexP is like BytesHex, but accepts a shorthand letter that can be used after a single dash. +func BytesHexP(name, shorthand string, value []byte, usage string) *[]byte { + return CommandLine.BytesHexP(name, shorthand, value, usage) +} diff --git a/components/cli/vendor/github.com/spf13/pflag/duration_slice.go b/components/cli/vendor/github.com/spf13/pflag/duration_slice.go new file mode 100644 index 0000000000..52c6b6dc10 --- /dev/null +++ b/components/cli/vendor/github.com/spf13/pflag/duration_slice.go @@ -0,0 +1,128 @@ +package pflag + +import ( + "fmt" + "strings" + "time" +) + +// -- durationSlice Value +type durationSliceValue struct { + value *[]time.Duration + changed bool +} + +func newDurationSliceValue(val []time.Duration, p *[]time.Duration) *durationSliceValue { + dsv := new(durationSliceValue) + dsv.value = p + *dsv.value = val + return dsv +} + +func (s *durationSliceValue) Set(val string) error { + ss := strings.Split(val, ",") + out := make([]time.Duration, len(ss)) + for i, d := range ss { + var err error + out[i], err = time.ParseDuration(d) + if err != nil { + return err + } + + } + if !s.changed { + *s.value = out + } else { + *s.value = append(*s.value, out...) + } + s.changed = true + return nil +} + +func (s *durationSliceValue) Type() string { + return "durationSlice" +} + +func (s *durationSliceValue) String() string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = fmt.Sprintf("%s", d) + } + return "[" + strings.Join(out, ",") + "]" +} + +func durationSliceConv(val string) (interface{}, error) { + val = strings.Trim(val, "[]") + // Empty string would cause a slice with one (empty) entry + if len(val) == 0 { + return []time.Duration{}, nil + } + ss := strings.Split(val, ",") + out := make([]time.Duration, len(ss)) + for i, d := range ss { + var err error + out[i], err = time.ParseDuration(d) + if err != nil { + return nil, err + } + + } + return out, nil +} + +// GetDurationSlice returns the []time.Duration value of a flag with the given name +func (f *FlagSet) GetDurationSlice(name string) ([]time.Duration, error) { + val, err := f.getFlagType(name, "durationSlice", durationSliceConv) + if err != nil { + return []time.Duration{}, err + } + return val.([]time.Duration), nil +} + +// DurationSliceVar defines a durationSlice flag with specified name, default value, and usage string. +// The argument p points to a []time.Duration variable in which to store the value of the flag. +func (f *FlagSet) DurationSliceVar(p *[]time.Duration, name string, value []time.Duration, usage string) { + f.VarP(newDurationSliceValue(value, p), name, "", usage) +} + +// DurationSliceVarP is like DurationSliceVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) DurationSliceVarP(p *[]time.Duration, name, shorthand string, value []time.Duration, usage string) { + f.VarP(newDurationSliceValue(value, p), name, shorthand, usage) +} + +// DurationSliceVar defines a duration[] flag with specified name, default value, and usage string. +// The argument p points to a duration[] variable in which to store the value of the flag. +func DurationSliceVar(p *[]time.Duration, name string, value []time.Duration, usage string) { + CommandLine.VarP(newDurationSliceValue(value, p), name, "", usage) +} + +// DurationSliceVarP is like DurationSliceVar, but accepts a shorthand letter that can be used after a single dash. +func DurationSliceVarP(p *[]time.Duration, name, shorthand string, value []time.Duration, usage string) { + CommandLine.VarP(newDurationSliceValue(value, p), name, shorthand, usage) +} + +// DurationSlice defines a []time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a []time.Duration variable that stores the value of the flag. +func (f *FlagSet) DurationSlice(name string, value []time.Duration, usage string) *[]time.Duration { + p := []time.Duration{} + f.DurationSliceVarP(&p, name, "", value, usage) + return &p +} + +// DurationSliceP is like DurationSlice, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) DurationSliceP(name, shorthand string, value []time.Duration, usage string) *[]time.Duration { + p := []time.Duration{} + f.DurationSliceVarP(&p, name, shorthand, value, usage) + return &p +} + +// DurationSlice defines a []time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a []time.Duration variable that stores the value of the flag. +func DurationSlice(name string, value []time.Duration, usage string) *[]time.Duration { + return CommandLine.DurationSliceP(name, "", value, usage) +} + +// DurationSliceP is like DurationSlice, but accepts a shorthand letter that can be used after a single dash. +func DurationSliceP(name, shorthand string, value []time.Duration, usage string) *[]time.Duration { + return CommandLine.DurationSliceP(name, shorthand, value, usage) +} diff --git a/components/cli/vendor/github.com/spf13/pflag/flag.go b/components/cli/vendor/github.com/spf13/pflag/flag.go index 73c237b078..5eadc84e3c 100644 --- a/components/cli/vendor/github.com/spf13/pflag/flag.go +++ b/components/cli/vendor/github.com/spf13/pflag/flag.go @@ -101,6 +101,7 @@ package pflag import ( "bytes" "errors" + goflag "flag" "fmt" "io" "os" @@ -123,6 +124,12 @@ const ( PanicOnError ) +// ParseErrorsWhitelist defines the parsing errors that can be ignored +type ParseErrorsWhitelist struct { + // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags + UnknownFlags bool +} + // NormalizedName is a flag name that has been normalized according to rules // for the FlagSet (e.g. making '-' and '_' equivalent). type NormalizedName string @@ -138,6 +145,9 @@ type FlagSet struct { // help/usage messages. SortFlags bool + // ParseErrorsWhitelist is used to configure a whitelist of errors + ParseErrorsWhitelist ParseErrorsWhitelist + name string parsed bool actual map[NormalizedName]*Flag @@ -153,6 +163,8 @@ type FlagSet struct { output io.Writer // nil means stderr; use out() accessor interspersed bool // allow interspersed option/non-option args normalizeNameFunc func(f *FlagSet, name string) NormalizedName + + addedGoFlagSets []*goflag.FlagSet } // A Flag represents the state of a flag. @@ -267,16 +279,16 @@ func (f *FlagSet) VisitAll(fn func(*Flag)) { } } -// HasFlags returns a bool to indicate if the FlagSet has any flags definied. +// HasFlags returns a bool to indicate if the FlagSet has any flags defined. func (f *FlagSet) HasFlags() bool { return len(f.formal) > 0 } // HasAvailableFlags returns a bool to indicate if the FlagSet has any flags -// definied that are not hidden or deprecated. +// that are not hidden. func (f *FlagSet) HasAvailableFlags() bool { for _, flag := range f.formal { - if !flag.Hidden && len(flag.Deprecated) == 0 { + if !flag.Hidden { return true } } @@ -386,6 +398,7 @@ func (f *FlagSet) MarkDeprecated(name string, usageMessage string) error { return fmt.Errorf("deprecated message for flag %q must be set", name) } flag.Deprecated = usageMessage + flag.Hidden = true return nil } @@ -568,6 +581,10 @@ func UnquoteUsage(flag *Flag) (name string, usage string) { name = "strings" case "intSlice": name = "ints" + case "uintSlice": + name = "uints" + case "boolSlice": + name = "bools" } return @@ -582,11 +599,14 @@ func wrapN(i, slop int, s string) (string, string) { return s, "" } - w := strings.LastIndexAny(s[:i], " \t") + w := strings.LastIndexAny(s[:i], " \t\n") if w <= 0 { return s, "" } - + nlPos := strings.LastIndex(s[:i], "\n") + if nlPos > 0 && nlPos < w { + return s[:nlPos], s[nlPos+1:] + } return s[:w], s[w+1:] } @@ -595,7 +615,7 @@ func wrapN(i, slop int, s string) (string, string) { // caller). Pass `w` == 0 to do no wrapping func wrap(i, w int, s string) string { if w == 0 { - return s + return strings.Replace(s, "\n", "\n"+strings.Repeat(" ", i), -1) } // space between indent i and end of line width w into which @@ -613,7 +633,7 @@ func wrap(i, w int, s string) string { } // If still not enough space then don't even try to wrap. if wrap < 24 { - return s + return strings.Replace(s, "\n", r, -1) } // Try to avoid short orphan words on the final line, by @@ -625,14 +645,14 @@ func wrap(i, w int, s string) string { // Handle first line, which is indented by the caller (or the // special case above) l, s = wrapN(wrap, slop, s) - r = r + l + r = r + strings.Replace(l, "\n", "\n"+strings.Repeat(" ", i), -1) // Now wrap the rest for s != "" { var t string t, s = wrapN(wrap, slop, s) - r = r + "\n" + strings.Repeat(" ", i) + t + r = r + "\n" + strings.Repeat(" ", i) + strings.Replace(t, "\n", "\n"+strings.Repeat(" ", i), -1) } return r @@ -649,7 +669,7 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string { maxlen := 0 f.VisitAll(func(flag *Flag) { - if flag.Deprecated != "" || flag.Hidden { + if flag.Hidden { return } @@ -696,6 +716,9 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string { line += fmt.Sprintf(" (default %s)", flag.DefValue) } } + if len(flag.Deprecated) != 0 { + line += fmt.Sprintf(" (DEPRECATED: %s)", flag.Deprecated) + } lines = append(lines, line) }) @@ -892,6 +915,25 @@ func (f *FlagSet) usage() { } } +//--unknown (args will be empty) +//--unknown --next-flag ... (args will be --next-flag ...) +//--unknown arg ... (args will be arg ...) +func stripUnknownFlagValue(args []string) []string { + if len(args) == 0 { + //--unknown + return args + } + + first := args[0] + if first[0] == '-' { + //--unknown --next-flag ... + return args + } + + //--unknown arg ... (args will be arg ...) + return args[1:] +} + func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []string, err error) { a = args name := s[2:] @@ -903,13 +945,24 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin split := strings.SplitN(name, "=", 2) name = split[0] flag, exists := f.formal[f.normalizeFlagName(name)] + if !exists { - if name == "help" { // special case for nice help message. + switch { + case name == "help": f.usage() return a, ErrHelp + case f.ParseErrorsWhitelist.UnknownFlags: + // --unknown=unknownval arg ... + // we do not want to lose arg in this case + if len(split) >= 2 { + return a, nil + } + + return stripUnknownFlagValue(a), nil + default: + err = f.failf("unknown flag: --%s", name) + return } - err = f.failf("unknown flag: --%s", name) - return } var value string @@ -947,13 +1000,25 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse flag, exists := f.shorthands[c] if !exists { - if c == 'h' { // special case for nice help message. + switch { + case c == 'h': f.usage() err = ErrHelp return + case f.ParseErrorsWhitelist.UnknownFlags: + // '-f=arg arg ...' + // we do not want to lose arg in this case + if len(shorthands) > 2 && shorthands[1] == '=' { + outShorts = "" + return + } + + outArgs = stripUnknownFlagValue(outArgs) + return + default: + err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) + return } - err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) - return } var value string @@ -1040,6 +1105,11 @@ func (f *FlagSet) parseArgs(args []string, fn parseFunc) (err error) { // are defined and before flags are accessed by the program. // The return value will be ErrHelp if -help was set but not defined. func (f *FlagSet) Parse(arguments []string) error { + if f.addedGoFlagSets != nil { + for _, goFlagSet := range f.addedGoFlagSets { + goFlagSet.Parse(nil) + } + } f.parsed = true if len(arguments) < 0 { diff --git a/components/cli/vendor/github.com/spf13/pflag/golangflag.go b/components/cli/vendor/github.com/spf13/pflag/golangflag.go index c4f47ebe59..d3dd72b7fe 100644 --- a/components/cli/vendor/github.com/spf13/pflag/golangflag.go +++ b/components/cli/vendor/github.com/spf13/pflag/golangflag.go @@ -98,4 +98,8 @@ func (f *FlagSet) AddGoFlagSet(newSet *goflag.FlagSet) { newSet.VisitAll(func(goflag *goflag.Flag) { f.AddGoFlag(goflag) }) + if f.addedGoFlagSets == nil { + f.addedGoFlagSets = make([]*goflag.FlagSet, 0) + } + f.addedGoFlagSets = append(f.addedGoFlagSets, newSet) } diff --git a/components/cli/vendor/github.com/spf13/pflag/string_array.go b/components/cli/vendor/github.com/spf13/pflag/string_array.go index 276b7ed49e..fa7bc60187 100644 --- a/components/cli/vendor/github.com/spf13/pflag/string_array.go +++ b/components/cli/vendor/github.com/spf13/pflag/string_array.go @@ -52,7 +52,7 @@ func (f *FlagSet) GetStringArray(name string) ([]string, error) { // StringArrayVar defines a string flag with specified name, default value, and usage string. // The argument p points to a []string variable in which to store the values of the multiple flags. -// The value of each argument will not try to be separated by comma +// The value of each argument will not try to be separated by comma. Use a StringSlice for that. func (f *FlagSet) StringArrayVar(p *[]string, name string, value []string, usage string) { f.VarP(newStringArrayValue(value, p), name, "", usage) } @@ -64,7 +64,7 @@ func (f *FlagSet) StringArrayVarP(p *[]string, name, shorthand string, value []s // StringArrayVar defines a string flag with specified name, default value, and usage string. // The argument p points to a []string variable in which to store the value of the flag. -// The value of each argument will not try to be separated by comma +// The value of each argument will not try to be separated by comma. Use a StringSlice for that. func StringArrayVar(p *[]string, name string, value []string, usage string) { CommandLine.VarP(newStringArrayValue(value, p), name, "", usage) } @@ -76,7 +76,7 @@ func StringArrayVarP(p *[]string, name, shorthand string, value []string, usage // StringArray defines a string flag with specified name, default value, and usage string. // The return value is the address of a []string variable that stores the value of the flag. -// The value of each argument will not try to be separated by comma +// The value of each argument will not try to be separated by comma. Use a StringSlice for that. func (f *FlagSet) StringArray(name string, value []string, usage string) *[]string { p := []string{} f.StringArrayVarP(&p, name, "", value, usage) @@ -92,7 +92,7 @@ func (f *FlagSet) StringArrayP(name, shorthand string, value []string, usage str // StringArray defines a string flag with specified name, default value, and usage string. // The return value is the address of a []string variable that stores the value of the flag. -// The value of each argument will not try to be separated by comma +// The value of each argument will not try to be separated by comma. Use a StringSlice for that. func StringArray(name string, value []string, usage string) *[]string { return CommandLine.StringArrayP(name, "", value, usage) } diff --git a/components/cli/vendor/github.com/spf13/pflag/string_slice.go b/components/cli/vendor/github.com/spf13/pflag/string_slice.go index 05eee75438..0cd3ccc083 100644 --- a/components/cli/vendor/github.com/spf13/pflag/string_slice.go +++ b/components/cli/vendor/github.com/spf13/pflag/string_slice.go @@ -82,6 +82,11 @@ func (f *FlagSet) GetStringSlice(name string) ([]string, error) { // StringSliceVar defines a string flag with specified name, default value, and usage string. // The argument p points to a []string variable in which to store the value of the flag. +// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. +// For example: +// --ss="v1,v2" -ss="v3" +// will result in +// []string{"v1", "v2", "v3"} func (f *FlagSet) StringSliceVar(p *[]string, name string, value []string, usage string) { f.VarP(newStringSliceValue(value, p), name, "", usage) } @@ -93,6 +98,11 @@ func (f *FlagSet) StringSliceVarP(p *[]string, name, shorthand string, value []s // StringSliceVar defines a string flag with specified name, default value, and usage string. // The argument p points to a []string variable in which to store the value of the flag. +// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. +// For example: +// --ss="v1,v2" -ss="v3" +// will result in +// []string{"v1", "v2", "v3"} func StringSliceVar(p *[]string, name string, value []string, usage string) { CommandLine.VarP(newStringSliceValue(value, p), name, "", usage) } @@ -104,6 +114,11 @@ func StringSliceVarP(p *[]string, name, shorthand string, value []string, usage // StringSlice defines a string flag with specified name, default value, and usage string. // The return value is the address of a []string variable that stores the value of the flag. +// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. +// For example: +// --ss="v1,v2" -ss="v3" +// will result in +// []string{"v1", "v2", "v3"} func (f *FlagSet) StringSlice(name string, value []string, usage string) *[]string { p := []string{} f.StringSliceVarP(&p, name, "", value, usage) @@ -119,6 +134,11 @@ func (f *FlagSet) StringSliceP(name, shorthand string, value []string, usage str // StringSlice defines a string flag with specified name, default value, and usage string. // The return value is the address of a []string variable that stores the value of the flag. +// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. +// For example: +// --ss="v1,v2" -ss="v3" +// will result in +// []string{"v1", "v2", "v3"} func StringSlice(name string, value []string, usage string) *[]string { return CommandLine.StringSliceP(name, "", value, usage) }