2016-06-23 11:25:51 -04:00
|
|
|
package cli
|
2016-04-19 12:59:48 -04:00
|
|
|
|
|
|
|
import (
|
2016-06-22 18:36:51 -04:00
|
|
|
"fmt"
|
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
|
|
|
"os"
|
2016-11-15 11:18:33 -05:00
|
|
|
"strings"
|
2016-06-22 18:36:51 -04:00
|
|
|
|
2018-12-11 09:50:04 -05:00
|
|
|
pluginmanager "github.com/docker/cli/cli-plugins/manager"
|
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
|
|
|
"github.com/docker/cli/cli/command"
|
2022-03-04 07:24:28 -05:00
|
|
|
"github.com/docker/cli/cli/config"
|
2018-12-18 05:16:52 -05:00
|
|
|
cliflags "github.com/docker/cli/cli/flags"
|
2020-04-16 05:23:37 -04:00
|
|
|
"github.com/moby/term"
|
2020-12-14 08:28:29 -05:00
|
|
|
"github.com/morikuni/aec"
|
2017-03-09 13:23:45 -05:00
|
|
|
"github.com/pkg/errors"
|
2016-04-19 12:59:48 -04:00
|
|
|
"github.com/spf13/cobra"
|
2018-12-18 05:16:52 -05:00
|
|
|
"github.com/spf13/pflag"
|
2016-04-19 12:59:48 -04:00
|
|
|
)
|
|
|
|
|
2018-12-10 10:30:19 -05:00
|
|
|
// setupCommonRootCommand contains the setup common to
|
|
|
|
// SetupRootCommand and SetupPluginRootCommand.
|
2018-12-11 09:50:04 -05:00
|
|
|
func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *pflag.FlagSet, *cobra.Command) {
|
2018-12-18 05:16:52 -05:00
|
|
|
opts := cliflags.NewClientOptions()
|
|
|
|
flags := rootCmd.Flags()
|
|
|
|
|
2022-03-04 07:24:28 -05:00
|
|
|
flags.StringVar(&opts.ConfigDir, "config", config.Dir(), "Location of client config files")
|
2018-12-18 05:16:52 -05:00
|
|
|
opts.Common.InstallFlags(flags)
|
|
|
|
|
2019-02-15 11:27:22 -05:00
|
|
|
cobra.AddTemplateFunc("add", func(a, b int) int { return a + b })
|
2016-09-12 11:37:00 -04:00
|
|
|
cobra.AddTemplateFunc("hasSubCommands", hasSubCommands)
|
|
|
|
cobra.AddTemplateFunc("hasManagementSubCommands", hasManagementSubCommands)
|
2018-12-19 06:29:01 -05:00
|
|
|
cobra.AddTemplateFunc("hasInvalidPlugins", hasInvalidPlugins)
|
2016-09-12 11:37:00 -04:00
|
|
|
cobra.AddTemplateFunc("operationSubCommands", operationSubCommands)
|
|
|
|
cobra.AddTemplateFunc("managementSubCommands", managementSubCommands)
|
2018-12-19 06:29:01 -05:00
|
|
|
cobra.AddTemplateFunc("invalidPlugins", invalidPlugins)
|
2017-02-01 11:20:51 -05:00
|
|
|
cobra.AddTemplateFunc("wrappedFlagUsages", wrappedFlagUsages)
|
2019-02-15 11:27:22 -05:00
|
|
|
cobra.AddTemplateFunc("vendorAndVersion", vendorAndVersion)
|
2018-12-19 06:29:01 -05:00
|
|
|
cobra.AddTemplateFunc("invalidPluginReason", invalidPluginReason)
|
2019-02-15 11:27:22 -05:00
|
|
|
cobra.AddTemplateFunc("isPlugin", isPlugin)
|
2020-10-02 09:30:52 -04:00
|
|
|
cobra.AddTemplateFunc("isExperimental", isExperimental)
|
2020-12-02 03:56:22 -05:00
|
|
|
cobra.AddTemplateFunc("hasAdditionalHelp", hasAdditionalHelp)
|
|
|
|
cobra.AddTemplateFunc("additionalHelp", additionalHelp)
|
2019-02-15 11:27:22 -05:00
|
|
|
cobra.AddTemplateFunc("decoratedName", decoratedName)
|
2016-09-12 11:37:00 -04:00
|
|
|
|
2016-05-16 17:20:29 -04:00
|
|
|
rootCmd.SetUsageTemplate(usageTemplate)
|
|
|
|
rootCmd.SetHelpTemplate(helpTemplate)
|
2016-06-22 18:36:51 -04:00
|
|
|
rootCmd.SetFlagErrorFunc(FlagErrorFunc)
|
2016-11-15 11:18:33 -05:00
|
|
|
rootCmd.SetHelpCommand(helpCommand)
|
2018-12-18 05:16:52 -05:00
|
|
|
|
2019-03-28 11:32:23 -04:00
|
|
|
rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
|
|
|
|
rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
|
|
|
|
rootCmd.PersistentFlags().Lookup("help").Hidden = true
|
|
|
|
|
2020-12-14 08:28:29 -05:00
|
|
|
rootCmd.Annotations = map[string]string{"additionalHelp": "To get more help with docker, check out our guides at https://docs.docker.com/go/guides/"}
|
2020-12-02 03:56:22 -05:00
|
|
|
|
2018-12-11 09:50:04 -05:00
|
|
|
return opts, flags, helpCommand
|
2018-12-18 05:02:47 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetupRootCommand sets default usage, help, and error handling for the
|
|
|
|
// root command.
|
2018-12-11 09:50:04 -05:00
|
|
|
func SetupRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *pflag.FlagSet, *cobra.Command) {
|
2018-05-18 16:57:28 -04:00
|
|
|
rootCmd.SetVersionTemplate("Docker version {{.Version}}\n")
|
2021-08-16 06:32:16 -04:00
|
|
|
return setupCommonRootCommand(rootCmd)
|
2016-04-19 12:59:48 -04:00
|
|
|
}
|
|
|
|
|
2018-12-10 10:30:19 -05:00
|
|
|
// SetupPluginRootCommand sets default usage, help and error handling for a plugin root command.
|
|
|
|
func SetupPluginRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *pflag.FlagSet) {
|
2018-12-11 09:50:04 -05:00
|
|
|
opts, flags, _ := setupCommonRootCommand(rootCmd)
|
2018-12-10 10:30:19 -05:00
|
|
|
return opts, flags
|
|
|
|
}
|
|
|
|
|
2016-08-28 09:30:14 -04:00
|
|
|
// FlagErrorFunc prints an error message which matches the format of the
|
2017-04-24 14:31:08 -04:00
|
|
|
// docker/cli/cli error messages
|
2016-06-22 18:36:51 -04:00
|
|
|
func FlagErrorFunc(cmd *cobra.Command, err error) error {
|
|
|
|
if err == nil {
|
2017-03-06 07:01:04 -05:00
|
|
|
return nil
|
2016-06-22 18:36:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
usage := ""
|
|
|
|
if cmd.HasSubCommands() {
|
|
|
|
usage = "\n\n" + cmd.UsageString()
|
|
|
|
}
|
2016-08-03 12:20:46 -04:00
|
|
|
return StatusError{
|
|
|
|
Status: fmt.Sprintf("%s\nSee '%s --help'.%s", err, cmd.CommandPath(), usage),
|
|
|
|
StatusCode: 125,
|
|
|
|
}
|
2016-06-22 18:36:51 -04:00
|
|
|
}
|
|
|
|
|
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
|
|
|
// TopLevelCommand encapsulates a top-level cobra command (either
|
|
|
|
// docker CLI or a plugin) and global flag handling logic necessary
|
|
|
|
// for plugins.
|
|
|
|
type TopLevelCommand struct {
|
|
|
|
cmd *cobra.Command
|
|
|
|
dockerCli *command.DockerCli
|
|
|
|
opts *cliflags.ClientOptions
|
|
|
|
flags *pflag.FlagSet
|
|
|
|
args []string
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewTopLevelCommand returns a new TopLevelCommand object
|
|
|
|
func NewTopLevelCommand(cmd *cobra.Command, dockerCli *command.DockerCli, opts *cliflags.ClientOptions, flags *pflag.FlagSet) *TopLevelCommand {
|
|
|
|
return &TopLevelCommand{cmd, dockerCli, opts, flags, os.Args[1:]}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetArgs sets the args (default os.Args[:1] used to invoke the command
|
|
|
|
func (tcmd *TopLevelCommand) SetArgs(args []string) {
|
|
|
|
tcmd.args = args
|
|
|
|
tcmd.cmd.SetArgs(args)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetFlag sets a flag in the local flag set of the top-level command
|
|
|
|
func (tcmd *TopLevelCommand) SetFlag(name, value string) {
|
|
|
|
tcmd.cmd.Flags().Set(name, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleGlobalFlags takes care of parsing global flags defined on the
|
|
|
|
// command, it returns the underlying cobra command and the args it
|
|
|
|
// will be called with (or an error).
|
|
|
|
//
|
|
|
|
// On success the caller is responsible for calling Initialize()
|
|
|
|
// before calling `Execute` on the returned command.
|
|
|
|
func (tcmd *TopLevelCommand) HandleGlobalFlags() (*cobra.Command, []string, error) {
|
|
|
|
cmd := tcmd.cmd
|
|
|
|
|
|
|
|
// We manually parse the global arguments and find the
|
|
|
|
// subcommand in order to properly deal with plugins. We rely
|
2019-03-12 07:13:50 -04:00
|
|
|
// on the root command never having any non-flag arguments. We
|
|
|
|
// create our own FlagSet so that we can configure it
|
|
|
|
// (e.g. `SetInterspersed` below) in an idempotent way.
|
|
|
|
flags := pflag.NewFlagSet(cmd.Name(), pflag.ContinueOnError)
|
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
|
|
|
|
|
|
|
// We need !interspersed to ensure we stop at the first
|
|
|
|
// potential command instead of accumulating it into
|
|
|
|
// flags.Args() and then continuing on and finding other
|
|
|
|
// arguments which we try and treat as globals (when they are
|
|
|
|
// actually arguments to the subcommand).
|
|
|
|
flags.SetInterspersed(false)
|
|
|
|
|
|
|
|
// We need the single parse to see both sets of flags.
|
2019-03-12 07:13:50 -04:00
|
|
|
flags.AddFlagSet(cmd.Flags())
|
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
|
|
|
flags.AddFlagSet(cmd.PersistentFlags())
|
|
|
|
// Now parse the global flags, up to (but not including) the
|
|
|
|
// first command. The result will be that all the remaining
|
|
|
|
// arguments are in `flags.Args()`.
|
|
|
|
if err := flags.Parse(tcmd.args); err != nil {
|
|
|
|
// Our FlagErrorFunc uses the cli, make sure it is initialized
|
|
|
|
if err := tcmd.Initialize(); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return nil, nil, cmd.FlagErrorFunc()(cmd, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return cmd, flags.Args(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize finalises global option parsing and initializes the docker client.
|
|
|
|
func (tcmd *TopLevelCommand) Initialize(ops ...command.InitializeOpt) error {
|
|
|
|
tcmd.opts.Common.SetDefaultOptions(tcmd.flags)
|
|
|
|
return tcmd.dockerCli.Initialize(tcmd.opts, ops...)
|
|
|
|
}
|
|
|
|
|
2018-12-17 11:59:11 -05:00
|
|
|
// VisitAll will traverse all commands from the root.
|
|
|
|
// This is different from the VisitAll of cobra.Command where only parents
|
|
|
|
// are checked.
|
|
|
|
func VisitAll(root *cobra.Command, fn func(*cobra.Command)) {
|
|
|
|
for _, cmd := range root.Commands() {
|
|
|
|
VisitAll(cmd, fn)
|
|
|
|
}
|
|
|
|
fn(root)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all
|
|
|
|
// commands within the tree rooted at 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
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-11-15 11:18:33 -05:00
|
|
|
var helpCommand = &cobra.Command{
|
|
|
|
Use: "help [command]",
|
|
|
|
Short: "Help about the command",
|
|
|
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
|
|
|
PersistentPostRun: func(cmd *cobra.Command, args []string) {},
|
|
|
|
RunE: func(c *cobra.Command, args []string) error {
|
|
|
|
cmd, args, e := c.Root().Find(args)
|
|
|
|
if cmd == nil || e != nil || len(args) > 0 {
|
2017-03-09 13:23:45 -05:00
|
|
|
return errors.Errorf("unknown help topic: %v", strings.Join(args, " "))
|
2016-11-15 11:18:33 -05:00
|
|
|
}
|
|
|
|
helpFunc := cmd.HelpFunc()
|
|
|
|
helpFunc(cmd, args)
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2020-10-02 09:30:52 -04:00
|
|
|
func isExperimental(cmd *cobra.Command) bool {
|
|
|
|
if _, ok := cmd.Annotations["experimentalCLI"]; ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
var experimental bool
|
|
|
|
cmd.VisitParents(func(cmd *cobra.Command) {
|
|
|
|
if _, ok := cmd.Annotations["experimentalCLI"]; ok {
|
|
|
|
experimental = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return experimental
|
|
|
|
}
|
|
|
|
|
2020-12-02 03:56:22 -05:00
|
|
|
func additionalHelp(cmd *cobra.Command) string {
|
|
|
|
if additionalHelp, ok := cmd.Annotations["additionalHelp"]; ok {
|
2020-12-14 08:28:29 -05:00
|
|
|
style := aec.EmptyBuilder.Bold().ANSI
|
|
|
|
return style.Apply(additionalHelp)
|
2020-12-02 03:56:22 -05:00
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func hasAdditionalHelp(cmd *cobra.Command) bool {
|
|
|
|
return additionalHelp(cmd) != ""
|
2020-12-01 09:04:33 -05:00
|
|
|
}
|
|
|
|
|
2018-12-19 06:29:01 -05:00
|
|
|
func isPlugin(cmd *cobra.Command) bool {
|
|
|
|
return cmd.Annotations[pluginmanager.CommandAnnotationPlugin] == "true"
|
|
|
|
}
|
|
|
|
|
2016-09-12 11:37:00 -04:00
|
|
|
func hasSubCommands(cmd *cobra.Command) bool {
|
|
|
|
return len(operationSubCommands(cmd)) > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func hasManagementSubCommands(cmd *cobra.Command) bool {
|
|
|
|
return len(managementSubCommands(cmd)) > 0
|
|
|
|
}
|
|
|
|
|
2018-12-19 06:29:01 -05:00
|
|
|
func hasInvalidPlugins(cmd *cobra.Command) bool {
|
|
|
|
return len(invalidPlugins(cmd)) > 0
|
|
|
|
}
|
|
|
|
|
2016-09-12 11:37:00 -04:00
|
|
|
func operationSubCommands(cmd *cobra.Command) []*cobra.Command {
|
|
|
|
cmds := []*cobra.Command{}
|
|
|
|
for _, sub := range cmd.Commands() {
|
2019-02-21 12:27:51 -05:00
|
|
|
if isPlugin(sub) {
|
2018-12-19 06:29:01 -05:00
|
|
|
continue
|
|
|
|
}
|
2016-09-12 11:37:00 -04:00
|
|
|
if sub.IsAvailableCommand() && !sub.HasSubCommands() {
|
|
|
|
cmds = append(cmds, sub)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return cmds
|
|
|
|
}
|
|
|
|
|
2017-02-01 11:20:51 -05:00
|
|
|
func wrappedFlagUsages(cmd *cobra.Command) string {
|
|
|
|
width := 80
|
|
|
|
if ws, err := term.GetWinsize(0); err == nil {
|
|
|
|
width = int(ws.Width)
|
|
|
|
}
|
|
|
|
return cmd.Flags().FlagUsagesWrapped(width - 1)
|
|
|
|
}
|
|
|
|
|
2019-02-15 11:27:22 -05:00
|
|
|
func decoratedName(cmd *cobra.Command) string {
|
|
|
|
decoration := " "
|
|
|
|
if isPlugin(cmd) {
|
|
|
|
decoration = "*"
|
|
|
|
}
|
|
|
|
return cmd.Name() + decoration
|
2018-12-11 09:50:04 -05:00
|
|
|
}
|
|
|
|
|
2019-02-15 11:27:22 -05:00
|
|
|
func vendorAndVersion(cmd *cobra.Command) string {
|
|
|
|
if vendor, ok := cmd.Annotations[pluginmanager.CommandAnnotationPluginVendor]; ok && isPlugin(cmd) {
|
|
|
|
version := ""
|
|
|
|
if v, ok := cmd.Annotations[pluginmanager.CommandAnnotationPluginVersion]; ok && v != "" {
|
|
|
|
version = ", " + v
|
2018-12-11 09:50:04 -05:00
|
|
|
}
|
2019-02-15 11:27:22 -05:00
|
|
|
return fmt.Sprintf("(%s%s)", vendor, version)
|
2018-12-11 09:50:04 -05:00
|
|
|
}
|
2019-02-15 11:27:22 -05:00
|
|
|
return ""
|
2018-12-11 09:50:04 -05:00
|
|
|
}
|
|
|
|
|
2016-09-12 11:37:00 -04:00
|
|
|
func managementSubCommands(cmd *cobra.Command) []*cobra.Command {
|
|
|
|
cmds := []*cobra.Command{}
|
|
|
|
for _, sub := range cmd.Commands() {
|
2019-02-21 12:27:51 -05:00
|
|
|
if isPlugin(sub) {
|
|
|
|
if invalidPluginReason(sub) == "" {
|
|
|
|
cmds = append(cmds, sub)
|
|
|
|
}
|
2018-12-19 06:29:01 -05:00
|
|
|
continue
|
|
|
|
}
|
2016-09-12 11:37:00 -04:00
|
|
|
if sub.IsAvailableCommand() && sub.HasSubCommands() {
|
|
|
|
cmds = append(cmds, sub)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return cmds
|
|
|
|
}
|
|
|
|
|
2018-12-19 06:29:01 -05:00
|
|
|
func invalidPlugins(cmd *cobra.Command) []*cobra.Command {
|
|
|
|
cmds := []*cobra.Command{}
|
|
|
|
for _, sub := range cmd.Commands() {
|
|
|
|
if !isPlugin(sub) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if invalidPluginReason(sub) != "" {
|
|
|
|
cmds = append(cmds, sub)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return cmds
|
|
|
|
}
|
|
|
|
|
|
|
|
func invalidPluginReason(cmd *cobra.Command) string {
|
|
|
|
return cmd.Annotations[pluginmanager.CommandAnnotationPluginInvalid]
|
|
|
|
}
|
|
|
|
|
2016-09-12 11:37:00 -04:00
|
|
|
var usageTemplate = `Usage:
|
2016-05-16 17:20:29 -04:00
|
|
|
|
2020-10-02 09:41:17 -04:00
|
|
|
{{- if not .HasSubCommands}} {{.UseLine}}{{end}}
|
|
|
|
{{- if .HasSubCommands}} {{ .CommandPath}}{{- if .HasAvailableFlags}} [OPTIONS]{{end}} COMMAND{{end}}
|
2016-09-12 11:37:00 -04:00
|
|
|
|
2018-05-31 11:09:51 -04:00
|
|
|
{{if ne .Long ""}}{{ .Long | trim }}{{ else }}{{ .Short | trim }}{{end}}
|
2020-10-02 09:30:52 -04:00
|
|
|
{{- if isExperimental .}}
|
|
|
|
|
|
|
|
EXPERIMENTAL:
|
|
|
|
{{.CommandPath}} is an experimental feature.
|
|
|
|
Experimental features provide early access to product functionality. These
|
|
|
|
features may change between releases without warning, or can be removed from a
|
|
|
|
future release. Learn more about experimental features in our documentation:
|
|
|
|
https://docs.docker.com/go/experimental/
|
2016-09-12 11:37:00 -04:00
|
|
|
|
2020-10-02 09:30:52 -04:00
|
|
|
{{- end}}
|
2016-09-12 11:37:00 -04:00
|
|
|
{{- if gt .Aliases 0}}
|
2016-04-19 12:59:48 -04:00
|
|
|
|
|
|
|
Aliases:
|
2016-09-12 11:37:00 -04:00
|
|
|
{{.NameAndAliases}}
|
|
|
|
|
|
|
|
{{- end}}
|
|
|
|
{{- if .HasExample}}
|
2016-04-19 12:59:48 -04:00
|
|
|
|
|
|
|
Examples:
|
2016-09-12 11:37:00 -04:00
|
|
|
{{ .Example }}
|
|
|
|
|
|
|
|
{{- end}}
|
2018-03-22 10:13:04 -04:00
|
|
|
{{- if .HasAvailableFlags}}
|
2016-04-19 12:59:48 -04:00
|
|
|
|
|
|
|
Options:
|
2017-02-01 11:20:51 -05:00
|
|
|
{{ wrappedFlagUsages . | trimRightSpace}}
|
2016-09-12 11:37:00 -04:00
|
|
|
|
|
|
|
{{- end}}
|
|
|
|
{{- if hasManagementSubCommands . }}
|
|
|
|
|
|
|
|
Management Commands:
|
|
|
|
|
|
|
|
{{- range managementSubCommands . }}
|
2019-02-15 11:27:22 -05:00
|
|
|
{{rpad (decoratedName .) (add .NamePadding 1)}}{{.Short}}{{ if isPlugin .}} {{vendorAndVersion .}}{{ end}}
|
2016-09-12 11:37:00 -04:00
|
|
|
{{- end}}
|
|
|
|
|
|
|
|
{{- end}}
|
|
|
|
{{- if hasSubCommands .}}
|
|
|
|
|
|
|
|
Commands:
|
|
|
|
|
|
|
|
{{- range operationSubCommands . }}
|
2019-02-21 12:27:51 -05:00
|
|
|
{{rpad .Name .NamePadding }} {{.Short}}
|
2016-09-12 11:37:00 -04:00
|
|
|
{{- end}}
|
|
|
|
{{- end}}
|
2016-04-19 12:59:48 -04:00
|
|
|
|
2018-12-19 06:29:01 -05:00
|
|
|
{{- if hasInvalidPlugins . }}
|
|
|
|
|
|
|
|
Invalid Plugins:
|
|
|
|
|
|
|
|
{{- range invalidPlugins . }}
|
|
|
|
{{rpad .Name .NamePadding }} {{invalidPluginReason .}}
|
|
|
|
{{- end}}
|
|
|
|
|
|
|
|
{{- end}}
|
|
|
|
|
2016-09-12 11:37:00 -04:00
|
|
|
{{- if .HasSubCommands }}
|
2016-04-19 12:59:48 -04:00
|
|
|
|
2016-09-12 11:37:00 -04:00
|
|
|
Run '{{.CommandPath}} COMMAND --help' for more information on a command.
|
|
|
|
{{- end}}
|
2020-12-02 03:56:22 -05:00
|
|
|
{{- if hasAdditionalHelp .}}
|
2020-12-14 08:28:29 -05:00
|
|
|
|
2020-12-02 03:56:22 -05:00
|
|
|
{{ additionalHelp . }}
|
2020-12-01 09:04:33 -05:00
|
|
|
{{- end}}
|
2016-04-19 12:59:48 -04:00
|
|
|
`
|
2016-05-16 17:20:29 -04:00
|
|
|
|
|
|
|
var helpTemplate = `
|
|
|
|
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
|