diff --git a/components/engine/cli/command/checkpoint/cmd.go b/components/engine/cli/command/checkpoint/cmd.go index 84084ab716..7f9e537779 100644 --- a/components/engine/cli/command/checkpoint/cmd.go +++ b/components/engine/cli/command/checkpoint/cmd.go @@ -17,6 +17,7 @@ func NewCheckpointCommand(dockerCli *command.DockerCli) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) }, + Tags: map[string]string{"experimental": ""}, } cmd.AddCommand( newCreateCommand(dockerCli), diff --git a/components/engine/cli/command/cli.go b/components/engine/cli/command/cli.go index be82ecf6f3..33a26c4c64 100644 --- a/components/engine/cli/command/cli.go +++ b/components/engine/cli/command/cli.go @@ -32,27 +32,21 @@ type Streams interface { // DockerCli represents the docker command line client. // Instances of the client can be returned from NewDockerCli. type DockerCli struct { - configFile *configfile.ConfigFile - in *InStream - out *OutStream - err io.Writer - keyFile string - client client.APIClient - hasExperimental *bool + configFile *configfile.ConfigFile + in *InStream + out *OutStream + err io.Writer + keyFile string + client client.APIClient } // HasExperimental returns true if experimental features are accessible func (cli *DockerCli) HasExperimental() bool { - if cli.hasExperimental == nil { - if cli.client == nil { - cli.Initialize(cliflags.NewClientOptions()) - } - enabled := false - cli.hasExperimental = &enabled - enabled, _ = cli.client.Ping(context.Background()) + if cli.client == nil { + return false } - - return *cli.hasExperimental + enabled, _ := cli.client.Ping(context.Background()) + return enabled } // Client returns the APIClient diff --git a/components/engine/cli/command/commands/commands.go b/components/engine/cli/command/commands/commands.go index 425f90ba7d..fad709bca1 100644 --- a/components/engine/cli/command/commands/commands.go +++ b/components/engine/cli/command/commands/commands.go @@ -70,17 +70,12 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) { hide(image.NewSaveCommand(dockerCli)), hide(image.NewTagCommand(dockerCli)), hide(system.NewInspectCommand(dockerCli)), + stack.NewStackCommand(dockerCli), + stack.NewTopLevelDeployCommand(dockerCli), + checkpoint.NewCheckpointCommand(dockerCli), + plugin.NewPluginCommand(dockerCli), ) - if dockerCli.HasExperimental() { - cmd.AddCommand( - stack.NewStackCommand(dockerCli), - stack.NewTopLevelDeployCommand(dockerCli), - checkpoint.NewCheckpointCommand(dockerCli), - plugin.NewPluginCommand(dockerCli), - ) - } - } func hide(cmd *cobra.Command) *cobra.Command { diff --git a/components/engine/cli/command/container/start.go b/components/engine/cli/command/container/start.go index 8e0654da37..87e815fed5 100644 --- a/components/engine/cli/command/container/start.go +++ b/components/engine/cli/command/container/start.go @@ -45,11 +45,10 @@ func NewStartCommand(dockerCli *command.DockerCli) *cobra.Command { flags.BoolVarP(&opts.openStdin, "interactive", "i", false, "Attach container's STDIN") flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container") - if dockerCli.HasExperimental() { - flags.StringVar(&opts.checkpoint, "checkpoint", "", "Restore from this checkpoint") - flags.StringVar(&opts.checkpointDir, "checkpoint-dir", "", "Use a custom checkpoint storage directory") - } - + flags.StringVar(&opts.checkpoint, "checkpoint", "", "Restore from this checkpoint") + flags.SetAnnotation("checkpoint", "experimental", nil) + flags.StringVar(&opts.checkpointDir, "checkpoint-dir", "", "Use a custom checkpoint storage directory") + flags.SetAnnotation("checkpoint-dir", "experimental", nil) return cmd } diff --git a/components/engine/cli/command/image/build.go b/components/engine/cli/command/image/build.go index dc18601900..5cf36cfd53 100644 --- a/components/engine/cli/command/image/build.go +++ b/components/engine/cli/command/image/build.go @@ -111,9 +111,8 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command { command.AddTrustedFlags(flags, true) - if dockerCli.HasExperimental() { - flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer") - } + flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer") + flags.SetAnnotation("squash", "experimental", nil) return cmd } diff --git a/components/engine/cli/command/plugin/cmd.go b/components/engine/cli/command/plugin/cmd.go index 80fa61cb1c..c78f43a8d4 100644 --- a/components/engine/cli/command/plugin/cmd.go +++ b/components/engine/cli/command/plugin/cmd.go @@ -17,6 +17,7 @@ func NewPluginCommand(dockerCli *command.DockerCli) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) }, + Tags: map[string]string{"experimental": ""}, } cmd.AddCommand( diff --git a/components/engine/cli/command/stack/cmd.go b/components/engine/cli/command/stack/cmd.go index 49fcedf209..70afec9c6d 100644 --- a/components/engine/cli/command/stack/cmd.go +++ b/components/engine/cli/command/stack/cmd.go @@ -17,6 +17,7 @@ func NewStackCommand(dockerCli *command.DockerCli) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) }, + Tags: map[string]string{"experimental": ""}, } cmd.AddCommand( newConfigCommand(dockerCli), diff --git a/components/engine/cli/command/stack/deploy.go b/components/engine/cli/command/stack/deploy.go index fcf55fb7d2..b0f6b455a8 100644 --- a/components/engine/cli/command/stack/deploy.go +++ b/components/engine/cli/command/stack/deploy.go @@ -36,6 +36,7 @@ func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command { opts.namespace = strings.TrimSuffix(args[0], ".dab") return runDeploy(dockerCli, opts) }, + Tags: map[string]string{"experimental": ""}, } flags := cmd.Flags() diff --git a/components/engine/cmd/docker/docker.go b/components/engine/cmd/docker/docker.go index 53e14f7ec3..65568e1e61 100644 --- a/components/engine/cmd/docker/docker.go +++ b/components/engine/cmd/docker/docker.go @@ -33,7 +33,8 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { showVersion() return nil } - fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) + cmd.SetOutput(dockerCli.Err()) + cmd.HelpFunc()(cmd, args) return nil }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { @@ -45,6 +46,22 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { } cli.SetupRootCommand(cmd) + cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) { + var err error + if dockerCli.Client() == nil { + // flags must be the top-level command flags, not cmd.Flags() + opts.Common.SetDefaultOptions(flags) + dockerPreRun(opts) + err = dockerCli.Initialize(opts) + } + if err != nil || !dockerCli.HasExperimental() { + hideExperimentalFeatures(ccmd) + } + if err := ccmd.Help(); err != nil { + ccmd.Println(err) + } + }) + flags = cmd.Flags() flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit") flags.StringVar(&opts.ConfigDir, "config", cliconfig.ConfigDir(), "Location of client config files") @@ -105,3 +122,19 @@ func dockerPreRun(opts *cliflags.ClientOptions) { utils.EnableDebug() } } + +func hideExperimentalFeatures(cmd *cobra.Command) { + // hide flags + cmd.Flags().VisitAll(func(f *pflag.Flag) { + if _, ok := f.Annotations["experimental"]; ok { + f.Hidden = true + } + }) + + for _, subcmd := range cmd.Commands() { + // hide subcommands + if _, ok := subcmd.Tags["experimental"]; ok { + subcmd.Hidden = true + } + } +} diff --git a/components/engine/vendor.conf b/components/engine/vendor.conf index a511ce0b7b..fbad26d481 100644 --- a/components/engine/vendor.conf +++ b/components/engine/vendor.conf @@ -123,7 +123,7 @@ github.com/matttproud/golang_protobuf_extensions fc2b8d3a73c4867e51861bbdd5ae3c1 github.com/pkg/errors 01fa4104b9c248c8945d14d9f128454d5b28d595 # cli -github.com/spf13/cobra v1.4.1 https://github.com/dnephin/cobra.git +github.com/spf13/cobra v1.5 https://github.com/dnephin/cobra.git github.com/spf13/pflag dabebe21bf790f782ea4c7bbd2efc430de182afd github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff diff --git a/components/engine/vendor/github.com/spf13/cobra/command.go b/components/engine/vendor/github.com/spf13/cobra/command.go index 892b07d300..2887208620 100644 --- a/components/engine/vendor/github.com/spf13/cobra/command.go +++ b/components/engine/vendor/github.com/spf13/cobra/command.go @@ -59,6 +59,9 @@ type Command struct { Deprecated string // Is this command hidden and should NOT show up in the list of available commands? Hidden bool + // Tags are key/value pairs that can be used by applications to identify or + // group commands + Tags map[string]string // Full set of flags flags *flag.FlagSet // Set of flags childrens of this command will inherit @@ -111,10 +114,10 @@ type Command struct { flagErrorBuf *bytes.Buffer - args []string // actual args parsed from flags - output *io.Writer // nil means stderr; use Out() method instead - usageFunc func(*Command) error // Usage can be defined by application - usageTemplate string // Can be defined by Application + args []string // actual args parsed from flags + output *io.Writer // nil means stderr; use Out() method instead + usageFunc func(*Command) error // Usage can be defined by application + usageTemplate string // Can be defined by Application flagErrorFunc func(*Command, error) error helpTemplate string // Can be defined by Application helpFunc func(*Command, []string) // Help can be defined by application @@ -417,7 +420,7 @@ func argsMinusFirstX(args []string, x string) []string { func isFlagArg(arg string) bool { return ((len(arg) >= 3 && arg[1] == '-') || - (len(arg) >= 2 && arg[0] == '-' && arg[1] != '-')) + (len(arg) >= 2 && arg[0] == '-' && arg[1] != '-')) } // Find the target command given the args and command tree @@ -820,7 +823,7 @@ func (c commandSorterByName) Less(i, j int) bool { return c[i].Name() < c[j].Nam // Commands returns a sorted slice of child commands. func (c *Command) Commands() []*Command { // do not sort commands if it already sorted or sorting was disabled - if EnableCommandSorting && !c.commandsAreSorted{ + if EnableCommandSorting && !c.commandsAreSorted { sort.Sort(commandSorterByName(c.commands)) c.commandsAreSorted = true }