Ver código fonte

Migrate volume commands to cobra.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
Daniel Nephin 9 anos atrás
pai
commit
69264beb40

+ 56 - 29
api/client/cli.go

@@ -60,6 +60,21 @@ func (cli *DockerCli) Initialize() error {
 	return cli.init()
 }
 
+// Client returns the APIClient
+func (cli *DockerCli) Client() client.APIClient {
+	return cli.client
+}
+
+// Out returns the writer used for stdout
+func (cli *DockerCli) Out() io.Writer {
+	return cli.out
+}
+
+// Err returns the writer used for stderr
+func (cli *DockerCli) Err() io.Writer {
+	return cli.err
+}
+
 // CheckTtyInput checks if we are trying to attach to a container tty
 // from a non-tty client input stream, and if so, returns an error.
 func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
@@ -127,40 +142,13 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cliflags.Cl
 
 	cli.init = func() error {
 		clientFlags.PostParse()
-		configFile, e := cliconfig.Load(cliconfig.ConfigDir())
-		if e != nil {
-			fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
-		}
-		if !configFile.ContainsAuth() {
-			credentials.DetectDefaultStore(configFile)
-		}
-		cli.configFile = configFile
-
-		host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions)
-		if err != nil {
-			return err
-		}
-
-		customHeaders := cli.configFile.HTTPHeaders
-		if customHeaders == nil {
-			customHeaders = map[string]string{}
-		}
-		customHeaders["User-Agent"] = clientUserAgent()
+		cli.configFile = LoadDefaultConfigFile(err)
 
-		verStr := api.DefaultVersion
-		if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
-			verStr = tmpStr
-		}
-
-		httpClient, err := newHTTPClient(host, clientFlags.Common.TLSOptions)
+		client, err := NewAPIClientFromFlags(clientFlags, cli.configFile)
 		if err != nil {
 			return err
 		}
 
-		client, err := client.NewClient(host, verStr, httpClient, customHeaders)
-		if err != nil {
-			return err
-		}
 		cli.client = client
 
 		if cli.in != nil {
@@ -176,6 +164,45 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cliflags.Cl
 	return cli
 }
 
+// LoadDefaultConfigFile attempts to load the default config file and returns
+// an initialized ConfigFile struct if none is found.
+func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
+	configFile, e := cliconfig.Load(cliconfig.ConfigDir())
+	if e != nil {
+		fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
+	}
+	if !configFile.ContainsAuth() {
+		credentials.DetectDefaultStore(configFile)
+	}
+	return configFile
+}
+
+// NewAPIClientFromFlags creates a new APIClient from command line flags
+func NewAPIClientFromFlags(clientFlags *cliflags.ClientFlags, configFile *configfile.ConfigFile) (client.APIClient, error) {
+	host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions)
+	if err != nil {
+		return &client.Client{}, err
+	}
+
+	customHeaders := configFile.HTTPHeaders
+	if customHeaders == nil {
+		customHeaders = map[string]string{}
+	}
+	customHeaders["User-Agent"] = clientUserAgent()
+
+	verStr := api.DefaultVersion
+	if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
+		verStr = tmpStr
+	}
+
+	httpClient, err := newHTTPClient(host, clientFlags.Common.TLSOptions)
+	if err != nil {
+		return &client.Client{}, err
+	}
+
+	return client.NewClient(host, verStr, httpClient, customHeaders)
+}
+
 func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) {
 	switch len(hosts) {
 	case 0:

+ 0 - 5
api/client/commands.go

@@ -49,11 +49,6 @@ func (cli *DockerCli) Command(name string) func(...string) error {
 		"unpause":            cli.CmdUnpause,
 		"update":             cli.CmdUpdate,
 		"version":            cli.CmdVersion,
-		"volume":             cli.CmdVolume,
-		"volume create":      cli.CmdVolumeCreate,
-		"volume inspect":     cli.CmdVolumeInspect,
-		"volume ls":          cli.CmdVolumeLs,
-		"volume rm":          cli.CmdVolumeRm,
 		"wait":               cli.CmdWait,
 	}[name]
 }

+ 0 - 181
api/client/volume.go

@@ -1,181 +0,0 @@
-package client
-
-import (
-	"fmt"
-	"sort"
-	"text/tabwriter"
-
-	"golang.org/x/net/context"
-
-	Cli "github.com/docker/docker/cli"
-	"github.com/docker/docker/opts"
-	flag "github.com/docker/docker/pkg/mflag"
-	runconfigopts "github.com/docker/docker/runconfig/opts"
-	"github.com/docker/engine-api/types"
-	"github.com/docker/engine-api/types/filters"
-)
-
-// CmdVolume is the parent subcommand for all volume commands
-//
-// Usage: docker volume <COMMAND> <OPTS>
-func (cli *DockerCli) CmdVolume(args ...string) error {
-	description := Cli.DockerCommands["volume"].Description + "\n\nCommands:\n"
-	commands := [][]string{
-		{"create", "Create a volume"},
-		{"inspect", "Return low-level information on a volume"},
-		{"ls", "List volumes"},
-		{"rm", "Remove a volume"},
-	}
-
-	for _, cmd := range commands {
-		description += fmt.Sprintf("  %-25.25s%s\n", cmd[0], cmd[1])
-	}
-
-	description += "\nRun 'docker volume COMMAND --help' for more information on a command"
-	cmd := Cli.Subcmd("volume", []string{"[COMMAND]"}, description, false)
-
-	cmd.Require(flag.Exact, 0)
-	err := cmd.ParseFlags(args, true)
-	cmd.Usage()
-	return err
-}
-
-// CmdVolumeLs outputs a list of Docker volumes.
-//
-// Usage: docker volume ls [OPTIONS]
-func (cli *DockerCli) CmdVolumeLs(args ...string) error {
-	cmd := Cli.Subcmd("volume ls", nil, "List volumes", true)
-
-	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display volume names")
-	flFilter := opts.NewListOpts(nil)
-	cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')")
-
-	cmd.Require(flag.Exact, 0)
-	cmd.ParseFlags(args, true)
-
-	volFilterArgs := filters.NewArgs()
-	for _, f := range flFilter.GetAll() {
-		var err error
-		volFilterArgs, err = filters.ParseFlag(f, volFilterArgs)
-		if err != nil {
-			return err
-		}
-	}
-
-	volumes, err := cli.client.VolumeList(context.Background(), volFilterArgs)
-	if err != nil {
-		return err
-	}
-
-	w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
-	if !*quiet {
-		for _, warn := range volumes.Warnings {
-			fmt.Fprintln(cli.err, warn)
-		}
-		fmt.Fprintf(w, "DRIVER \tVOLUME NAME")
-		fmt.Fprintf(w, "\n")
-	}
-
-	sort.Sort(byVolumeName(volumes.Volumes))
-	for _, vol := range volumes.Volumes {
-		if *quiet {
-			fmt.Fprintln(w, vol.Name)
-			continue
-		}
-		fmt.Fprintf(w, "%s\t%s\n", vol.Driver, vol.Name)
-	}
-	w.Flush()
-	return nil
-}
-
-type byVolumeName []*types.Volume
-
-func (r byVolumeName) Len() int      { return len(r) }
-func (r byVolumeName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
-func (r byVolumeName) Less(i, j int) bool {
-	return r[i].Name < r[j].Name
-}
-
-// CmdVolumeInspect displays low-level information on one or more volumes.
-//
-// Usage: docker volume inspect [OPTIONS] VOLUME [VOLUME...]
-func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
-	cmd := Cli.Subcmd("volume inspect", []string{"VOLUME [VOLUME...]"}, "Return low-level information on a volume", true)
-	tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
-
-	cmd.Require(flag.Min, 1)
-	cmd.ParseFlags(args, true)
-
-	if err := cmd.Parse(args); err != nil {
-		return nil
-	}
-
-	ctx := context.Background()
-
-	inspectSearcher := func(name string) (interface{}, []byte, error) {
-		i, err := cli.client.VolumeInspect(ctx, name)
-		return i, nil, err
-	}
-
-	return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
-}
-
-// CmdVolumeCreate creates a new volume.
-//
-// Usage: docker volume create [OPTIONS]
-func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
-	cmd := Cli.Subcmd("volume create", nil, "Create a volume", true)
-	flDriver := cmd.String([]string{"d", "-driver"}, "local", "Specify volume driver name")
-	flName := cmd.String([]string{"-name"}, "", "Specify volume name")
-
-	flDriverOpts := opts.NewMapOpts(nil, nil)
-	cmd.Var(flDriverOpts, []string{"o", "-opt"}, "Set driver specific options")
-
-	flLabels := opts.NewListOpts(nil)
-	cmd.Var(&flLabels, []string{"-label"}, "Set metadata for a volume")
-
-	cmd.Require(flag.Exact, 0)
-	cmd.ParseFlags(args, true)
-
-	volReq := types.VolumeCreateRequest{
-		Driver:     *flDriver,
-		DriverOpts: flDriverOpts.GetAll(),
-		Name:       *flName,
-		Labels:     runconfigopts.ConvertKVStringsToMap(flLabels.GetAll()),
-	}
-
-	vol, err := cli.client.VolumeCreate(context.Background(), volReq)
-	if err != nil {
-		return err
-	}
-
-	fmt.Fprintf(cli.out, "%s\n", vol.Name)
-	return nil
-}
-
-// CmdVolumeRm removes one or more volumes.
-//
-// Usage: docker volume rm VOLUME [VOLUME...]
-func (cli *DockerCli) CmdVolumeRm(args ...string) error {
-	cmd := Cli.Subcmd("volume rm", []string{"VOLUME [VOLUME...]"}, "Remove a volume", true)
-	cmd.Require(flag.Min, 1)
-	cmd.ParseFlags(args, true)
-
-	var status = 0
-
-	ctx := context.Background()
-
-	for _, name := range cmd.Args() {
-		if err := cli.client.VolumeRemove(ctx, name); err != nil {
-			fmt.Fprintf(cli.err, "%s\n", err)
-			status = 1
-			continue
-		}
-		fmt.Fprintf(cli.out, "%s\n", name)
-	}
-
-	if status != 0 {
-		return Cli.StatusError{StatusCode: status}
-	}
-	return nil
-}

+ 22 - 0
api/client/volume/cmd.go

@@ -0,0 +1,22 @@
+package volume
+
+import (
+	"github.com/spf13/cobra"
+
+	"github.com/docker/docker/api/client"
+)
+
+// NewVolumeCommand returns a cobra command for `volume` subcommands
+func NewVolumeCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "volume",
+		Short: "Manage Docker volumes",
+	}
+	cmd.AddCommand(
+		newCreateCommand(dockerCli),
+		newInspectCommand(dockerCli),
+		newListCommand(dockerCli),
+		newRemoveCommand(dockerCli),
+	)
+	return cmd
+}

+ 58 - 0
api/client/volume/create.go

@@ -0,0 +1,58 @@
+package volume
+
+import (
+	"fmt"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/opts"
+	runconfigopts "github.com/docker/docker/runconfig/opts"
+	"github.com/docker/engine-api/types"
+	"github.com/spf13/cobra"
+)
+
+type createOptions struct {
+	name       string
+	driver     string
+	driverOpts opts.MapOpts
+	labels     []string
+}
+
+func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var opts createOptions
+
+	cmd := &cobra.Command{
+		Use:   "create",
+		Short: "Create a volume",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runCreate(dockerCli, opts)
+		},
+	}
+	flags := cmd.Flags()
+	flags.StringVarP(&opts.driver, "driver", "d", "local", "Specify volume driver name")
+	flags.StringVar(&opts.name, "name", "", "Specify volume name")
+	flags.VarP(&opts.driverOpts, "opt", "o", "Set driver specific options")
+	flags.StringSliceVar(&opts.labels, "label", []string{}, "Set metadata for a volume")
+
+	return cmd
+}
+
+func runCreate(dockerCli *client.DockerCli, opts createOptions) error {
+	client := dockerCli.Client()
+
+	volReq := types.VolumeCreateRequest{
+		Driver:     opts.driver,
+		DriverOpts: opts.driverOpts.GetAll(),
+		Name:       opts.name,
+		Labels:     runconfigopts.ConvertKVStringsToMap(opts.labels),
+	}
+
+	vol, err := client.VolumeCreate(context.Background(), volReq)
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(dockerCli.Out(), "%s\n", vol.Name)
+	return nil
+}

+ 46 - 0
api/client/volume/inspect.go

@@ -0,0 +1,46 @@
+package volume
+
+import (
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/api/client/inspect"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+)
+
+type inspectOptions struct {
+	format string
+	names  []string
+}
+
+func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var opts inspectOptions
+
+	cmd := &cobra.Command{
+		Use:   "inspect [OPTIONS] VOLUME [VOLUME...]",
+		Short: "Return low-level information on a volume",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			if err := cli.MinRequiredArgs(args, 1, cmd); err != nil {
+				return err
+			}
+			opts.names = args
+			return runInspect(dockerCli, opts)
+		},
+	}
+
+	cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
+
+	return cmd
+}
+
+func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
+	client := dockerCli.Client()
+
+	getVolFunc := func(name string) (interface{}, []byte, error) {
+		i, err := client.VolumeInspect(context.Background(), name)
+		return i, nil, err
+	}
+
+	return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getVolFunc)
+}

+ 84 - 0
api/client/volume/list.go

@@ -0,0 +1,84 @@
+package volume
+
+import (
+	"fmt"
+	"sort"
+	"text/tabwriter"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/filters"
+	"github.com/spf13/cobra"
+)
+
+type byVolumeName []*types.Volume
+
+func (r byVolumeName) Len() int      { return len(r) }
+func (r byVolumeName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
+func (r byVolumeName) Less(i, j int) bool {
+	return r[i].Name < r[j].Name
+}
+
+type listOptions struct {
+	quiet  bool
+	filter []string
+}
+
+func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var opts listOptions
+
+	cmd := &cobra.Command{
+		Use:     "ls",
+		Aliases: []string{"list"},
+		Short:   "List volumes",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runList(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display volume names")
+	flags.StringSliceVarP(&opts.filter, "filter", "f", []string{}, "Provide filter values (i.e. 'dangling=true')")
+
+	return cmd
+}
+
+func runList(dockerCli *client.DockerCli, opts listOptions) error {
+	client := dockerCli.Client()
+
+	volFilterArgs := filters.NewArgs()
+	for _, f := range opts.filter {
+		var err error
+		volFilterArgs, err = filters.ParseFlag(f, volFilterArgs)
+		if err != nil {
+			return err
+		}
+	}
+
+	volumes, err := client.VolumeList(context.Background(), volFilterArgs)
+	if err != nil {
+		return err
+	}
+
+	w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
+	if !opts.quiet {
+		for _, warn := range volumes.Warnings {
+			fmt.Fprintln(dockerCli.Err(), warn)
+		}
+		fmt.Fprintf(w, "DRIVER \tVOLUME NAME")
+		fmt.Fprintf(w, "\n")
+	}
+
+	sort.Sort(byVolumeName(volumes.Volumes))
+	for _, vol := range volumes.Volumes {
+		if opts.quiet {
+			fmt.Fprintln(w, vol.Name)
+			continue
+		}
+		fmt.Fprintf(w, "%s\t%s\n", vol.Driver, vol.Name)
+	}
+	w.Flush()
+	return nil
+}

+ 44 - 0
api/client/volume/remove.go

@@ -0,0 +1,44 @@
+package volume
+
+import (
+	"fmt"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+)
+
+func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
+	return &cobra.Command{
+		Use:     "rm VOLUME [VOLUME]...",
+		Aliases: []string{"remove"},
+		Short:   "Remove a volume",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			if err := cli.MinRequiredArgs(args, 1, cmd); err != nil {
+				return err
+			}
+			return runRemove(dockerCli, args)
+		},
+	}
+}
+
+func runRemove(dockerCli *client.DockerCli, volumes []string) error {
+	client := dockerCli.Client()
+	var status = 0
+
+	for _, name := range volumes {
+		if err := client.VolumeRemove(context.Background(), name); err != nil {
+			fmt.Fprintf(dockerCli.Err(), "%s\n", err)
+			status = 1
+			continue
+		}
+		fmt.Fprintf(dockerCli.Err(), "%s\n", name)
+	}
+
+	if status != 0 {
+		return cli.StatusError{StatusCode: status}
+	}
+	return nil
+}

+ 83 - 0
cli/cobraadaptor/adaptor.go

@@ -0,0 +1,83 @@
+package cobraadaptor
+
+import (
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/api/client/volume"
+	"github.com/docker/docker/cli"
+	cliflags "github.com/docker/docker/cli/flags"
+	"github.com/docker/docker/pkg/term"
+	"github.com/spf13/cobra"
+)
+
+// CobraAdaptor is an adaptor for supporting spf13/cobra commands in the
+// docker/cli framework
+type CobraAdaptor struct {
+	rootCmd   *cobra.Command
+	dockerCli *client.DockerCli
+}
+
+// NewCobraAdaptor returns a new handler
+func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
+	var rootCmd = &cobra.Command{
+		Use: "docker",
+	}
+	rootCmd.SetUsageTemplate(usageTemplate)
+
+	stdin, stdout, stderr := term.StdStreams()
+	dockerCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags)
+
+	rootCmd.AddCommand(
+		volume.NewVolumeCommand(dockerCli),
+	)
+	return CobraAdaptor{
+		rootCmd:   rootCmd,
+		dockerCli: dockerCli,
+	}
+}
+
+// Usage returns the list of commands and their short usage string for
+// all top level cobra commands.
+func (c CobraAdaptor) Usage() []cli.Command {
+	cmds := []cli.Command{}
+	for _, cmd := range c.rootCmd.Commands() {
+		cmds = append(cmds, cli.Command{Name: cmd.Use, Description: cmd.Short})
+	}
+	return cmds
+}
+
+func (c CobraAdaptor) run(cmd string, args []string) error {
+	c.dockerCli.Initialize()
+	// Prepend the command name to support normal cobra command delegation
+	c.rootCmd.SetArgs(append([]string{cmd}, args...))
+	return c.rootCmd.Execute()
+}
+
+// Command returns a cli command handler if one exists
+func (c CobraAdaptor) Command(name string) func(...string) error {
+	for _, cmd := range c.rootCmd.Commands() {
+		if cmd.Name() == name {
+			return func(args ...string) error {
+				return c.run(name, args)
+			}
+		}
+	}
+	return nil
+}
+
+var usageTemplate = `Usage:  {{if .Runnable}}{{if .HasFlags}}{{appendIfNotPresent .UseLine "[OPTIONS]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND {{end}}{{if gt .Aliases 0}}
+
+Aliases:
+  {{.NameAndAliases}}
+{{end}}{{if .HasExample}}
+
+Examples:
+{{ .Example }}{{end}}{{ if .HasLocalFlags}}
+
+Options:
+{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableSubCommands}}
+
+Commands:{{range .Commands}}{{if .IsAvailableCommand}}
+  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }}
+
+Run '{{.CommandPath}} COMMAND --help' for more information on a command.{{end}}
+`

+ 23 - 0
cli/required.go

@@ -0,0 +1,23 @@
+package cli
+
+import (
+	"fmt"
+
+	"github.com/spf13/cobra"
+)
+
+// MinRequiredArgs checks if the minimum number of args exists, and returns an
+// error if they do not.
+func MinRequiredArgs(args []string, min int, cmd *cobra.Command) error {
+	if len(args) >= min {
+		return nil
+	}
+
+	return fmt.Errorf(
+		"\"%s\" requires at least %d argument(s).\n\nUsage:  %s\n\n%s",
+		cmd.CommandPath(),
+		min,
+		cmd.UseLine(),
+		cmd.Short,
+	)
+}

+ 6 - 3
cmd/docker/docker.go

@@ -8,6 +8,7 @@ import (
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/api/client"
 	"github.com/docker/docker/cli"
+	"github.com/docker/docker/cli/cobraadaptor"
 	cliflags "github.com/docker/docker/cli/flags"
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/dockerversion"
@@ -31,6 +32,8 @@ func main() {
 
 	flag.Merge(flag.CommandLine, clientFlags.FlagSet, commonFlags.FlagSet)
 
+	cobraAdaptor := cobraadaptor.NewCobraAdaptor(clientFlags)
+
 	flag.Usage = func() {
 		fmt.Fprint(stdout, "Usage: docker [OPTIONS] COMMAND [arg...]\n       docker [ --help | -v | --version ]\n\n")
 		fmt.Fprint(stdout, "A self-sufficient runtime for containers.\n\nOptions:\n")
@@ -40,8 +43,8 @@ func main() {
 
 		help := "\nCommands:\n"
 
-		dockerCommands := sortCommands(cli.DockerCommandUsage)
-		for _, cmd := range dockerCommands {
+		dockerCommands := append(cli.DockerCommandUsage, cobraAdaptor.Usage()...)
+		for _, cmd := range sortCommands(dockerCommands) {
 			help += fmt.Sprintf("    %-10.10s%s\n", cmd.Name, cmd.Description)
 		}
 
@@ -65,7 +68,7 @@ func main() {
 
 	clientCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags)
 
-	c := cli.New(clientCli, NewDaemonProxy())
+	c := cli.New(clientCli, NewDaemonProxy(), cobraAdaptor)
 	if err := c.Run(flag.Args()...); err != nil {
 		if sterr, ok := err.(cli.StatusError); ok {
 			if sterr.Status != "" {

+ 5 - 0
opts/opts.go

@@ -163,6 +163,11 @@ func (opts *MapOpts) String() string {
 	return fmt.Sprintf("%v", map[string]string((opts.values)))
 }
 
+// Type returns a string name for this Option type
+func (opts *MapOpts) Type() string {
+	return "map"
+}
+
 // NewMapOpts creates a new MapOpts with the specified map of values and a validator.
 func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts {
 	if values == nil {