From 977a19e21d2be2aeff170a7a52a2e88238c5843b Mon Sep 17 00:00:00 2001 From: John Howard Date: Sat, 23 Apr 2016 18:31:57 -0700 Subject: [PATCH 01/45] Make dockerd debuggable Signed-off-by: John Howard Upstream-commit: c6919a6e797dbafdf3da088a65f9519de3740de3 Component: cli --- components/cli/client.go | 38 +++++++++++++ components/cli/client_test.go | 23 ++++++++ components/cli/daemon.go | 11 ++++ components/cli/daemon_unix.go | 37 +++++++++++++ components/cli/daemon_windows.go | 11 ++++ components/cli/daemon_windows_test.go | 18 +++++++ components/cli/docker.go | 77 +++++++++++++++++++++++++++ components/cli/docker_windows.go | 5 ++ components/cli/flags.go | 30 +++++++++++ components/cli/flags_test.go | 13 +++++ 10 files changed, 263 insertions(+) create mode 100644 components/cli/client.go create mode 100644 components/cli/client_test.go create mode 100644 components/cli/daemon.go create mode 100644 components/cli/daemon_unix.go create mode 100644 components/cli/daemon_windows.go create mode 100644 components/cli/daemon_windows_test.go create mode 100644 components/cli/docker.go create mode 100644 components/cli/docker_windows.go create mode 100644 components/cli/flags.go create mode 100644 components/cli/flags_test.go diff --git a/components/cli/client.go b/components/cli/client.go new file mode 100644 index 0000000000..e8c7f889f8 --- /dev/null +++ b/components/cli/client.go @@ -0,0 +1,38 @@ +package main + +import ( + "path/filepath" + + "github.com/docker/docker/cli" + cliflags "github.com/docker/docker/cli/flags" + "github.com/docker/docker/cliconfig" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +var ( + commonFlags = cliflags.InitCommonFlags() + clientFlags = &cli.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags} +) + +func init() { + + client := clientFlags.FlagSet + client.StringVar(&clientFlags.ConfigDir, []string{"-config"}, cliconfig.ConfigDir(), "Location of client config files") + + clientFlags.PostParse = func() { + clientFlags.Common.PostParse() + + if clientFlags.ConfigDir != "" { + cliconfig.SetConfigDir(clientFlags.ConfigDir) + } + + if clientFlags.Common.TrustKey == "" { + clientFlags.Common.TrustKey = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) + } + + if clientFlags.Common.Debug { + utils.EnableDebug() + } + } +} diff --git a/components/cli/client_test.go b/components/cli/client_test.go new file mode 100644 index 0000000000..5708c96cb5 --- /dev/null +++ b/components/cli/client_test.go @@ -0,0 +1,23 @@ +package main + +import ( + "os" + "testing" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/utils" +) + +func TestClientDebugEnabled(t *testing.T) { + defer utils.DisableDebug() + + clientFlags.Common.FlagSet.Parse([]string{"-D"}) + clientFlags.PostParse() + + if os.Getenv("DEBUG") != "1" { + t.Fatal("expected debug enabled, got false") + } + if logrus.GetLevel() != logrus.DebugLevel { + t.Fatalf("expected logrus debug level, got %v", logrus.GetLevel()) + } +} diff --git a/components/cli/daemon.go b/components/cli/daemon.go new file mode 100644 index 0000000000..15dffbaefb --- /dev/null +++ b/components/cli/daemon.go @@ -0,0 +1,11 @@ +package main + +const daemonBinary = "dockerd" + +// DaemonProxy acts as a cli.Handler to proxy calls to the daemon binary +type DaemonProxy struct{} + +// NewDaemonProxy returns a new handler +func NewDaemonProxy() DaemonProxy { + return DaemonProxy{} +} diff --git a/components/cli/daemon_unix.go b/components/cli/daemon_unix.go new file mode 100644 index 0000000000..abe9ebfc51 --- /dev/null +++ b/components/cli/daemon_unix.go @@ -0,0 +1,37 @@ +// +build !windows + +package main + +import ( + "os" + "os/exec" + "syscall" +) + +// CmdDaemon execs dockerd with the same flags +// TODO: add a deprecation warning? +func (p DaemonProxy) CmdDaemon(args ...string) error { + // Use os.Args[1:] so that "global" args are passed to dockerd + args = stripDaemonArg(os.Args[1:]) + + // TODO: check dirname args[0] first + binaryAbsPath, err := exec.LookPath(daemonBinary) + if err != nil { + return err + } + + return syscall.Exec( + binaryAbsPath, + append([]string{daemonBinary}, args...), + os.Environ()) +} + +// stripDaemonArg removes the `daemon` argument from the list +func stripDaemonArg(args []string) []string { + for i, arg := range args { + if arg == "daemon" { + return append(args[:i], args[i+1:]...) + } + } + return args +} diff --git a/components/cli/daemon_windows.go b/components/cli/daemon_windows.go new file mode 100644 index 0000000000..41c0133b67 --- /dev/null +++ b/components/cli/daemon_windows.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" +) + +// CmdDaemon reports on an error on windows, because there is no exec +func (p DaemonProxy) CmdDaemon(args ...string) error { + return fmt.Errorf( + "`docker daemon` does not exist on windows. Please run `dockerd` directly") +} diff --git a/components/cli/daemon_windows_test.go b/components/cli/daemon_windows_test.go new file mode 100644 index 0000000000..3da4e5d7cc --- /dev/null +++ b/components/cli/daemon_windows_test.go @@ -0,0 +1,18 @@ +package main + +import ( + "strings" + "testing" +) + +func TestCmdDaemon(t *testing.T) { + proxy := NewDaemonProxy() + err := proxy.CmdDaemon("--help") + if err == nil { + t.Fatal("Expected CmdDaemon to fail in Windows.") + } + + if !strings.Contains(err.Error(), "Please run `dockerd`") { + t.Fatalf("Expected an error about running dockerd, got %s", err) + } +} diff --git a/components/cli/docker.go b/components/cli/docker.go new file mode 100644 index 0000000000..838602164d --- /dev/null +++ b/components/cli/docker.go @@ -0,0 +1,77 @@ +package main + +import ( + "fmt" + "os" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/client" + "github.com/docker/docker/cli" + "github.com/docker/docker/dockerversion" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/term" + "github.com/docker/docker/utils" +) + +func main() { + // Set terminal emulation based on platform as required. + stdin, stdout, stderr := term.StdStreams() + + logrus.SetOutput(stderr) + + flag.Merge(flag.CommandLine, clientFlags.FlagSet, commonFlags.FlagSet) + + 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") + + flag.CommandLine.SetOutput(stdout) + flag.PrintDefaults() + + help := "\nCommands:\n" + + for _, cmd := range dockerCommands { + help += fmt.Sprintf(" %-10.10s%s\n", cmd.Name, cmd.Description) + } + + help += "\nRun 'docker COMMAND --help' for more information on a command." + fmt.Fprintf(stdout, "%s\n", help) + } + + flag.Parse() + + if *flVersion { + showVersion() + return + } + + if *flHelp { + // if global flag --help is present, regardless of what other options and commands there are, + // just print the usage. + flag.Usage() + return + } + + clientCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags) + + c := cli.New(clientCli, NewDaemonProxy()) + if err := c.Run(flag.Args()...); err != nil { + if sterr, ok := err.(cli.StatusError); ok { + if sterr.Status != "" { + fmt.Fprintln(stderr, sterr.Status) + os.Exit(1) + } + os.Exit(sterr.StatusCode) + } + fmt.Fprintln(stderr, err) + os.Exit(1) + } +} + +func showVersion() { + if utils.ExperimentalBuild() { + fmt.Printf("Docker version %s, build %s, experimental\n", dockerversion.Version, dockerversion.GitCommit) + } else { + fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit) + } +} diff --git a/components/cli/docker_windows.go b/components/cli/docker_windows.go new file mode 100644 index 0000000000..889e35272d --- /dev/null +++ b/components/cli/docker_windows.go @@ -0,0 +1,5 @@ +package main + +import ( + _ "github.com/docker/docker/autogen/winresources/dockerd" +) diff --git a/components/cli/flags.go b/components/cli/flags.go new file mode 100644 index 0000000000..35a8108880 --- /dev/null +++ b/components/cli/flags.go @@ -0,0 +1,30 @@ +package main + +import ( + "sort" + + "github.com/docker/docker/cli" + flag "github.com/docker/docker/pkg/mflag" +) + +var ( + flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") + flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") +) + +type byName []cli.Command + +func (a byName) Len() int { return len(a) } +func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } + +var dockerCommands []cli.Command + +// TODO(tiborvass): do not show 'daemon' on client-only binaries + +func init() { + for _, cmd := range cli.DockerCommands { + dockerCommands = append(dockerCommands, cmd) + } + sort.Sort(byName(dockerCommands)) +} diff --git a/components/cli/flags_test.go b/components/cli/flags_test.go new file mode 100644 index 0000000000..28021ba4c9 --- /dev/null +++ b/components/cli/flags_test.go @@ -0,0 +1,13 @@ +package main + +import ( + "sort" + "testing" +) + +// Tests if the subcommands of docker are sorted +func TestDockerSubcommandsAreSorted(t *testing.T) { + if !sort.IsSorted(byName(dockerCommands)) { + t.Fatal("Docker subcommands are not in sorted order") + } +} From 73fd4d25b9a673a0812b84ece533f80c5f710e0b Mon Sep 17 00:00:00 2001 From: John Starks Date: Fri, 22 Apr 2016 17:16:14 -0700 Subject: [PATCH 02/45] Windows: Support running dockerd as a service This adds support for Windows dockerd to run as a Windows service, managed by the service control manager. The log is written to the Windows event log (and can be viewed in the event viewer or in PowerShell). If there is a Go panic, the stack is written to a file panic.log in the Docker root. Signed-off-by: John Starks Upstream-commit: 421e366d8d574bec16eee862d984d299d3fb9ac7 Component: cli --- components/cli/docker_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/docker_windows.go b/components/cli/docker_windows.go index 889e35272d..de32257586 100644 --- a/components/cli/docker_windows.go +++ b/components/cli/docker_windows.go @@ -1,5 +1,5 @@ package main import ( - _ "github.com/docker/docker/autogen/winresources/dockerd" + _ "github.com/docker/docker/autogen/winresources/docker" ) From 9e49ad8267a91b4e95564cd5680e74b3c8148b25 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 21 Apr 2016 17:51:28 -0400 Subject: [PATCH 03/45] Cleanup the structure of the cli package. Move all flags into cli/flags Move usage help into cli/usage.go Signed-off-by: Daniel Nephin Upstream-commit: 315e242b9c34b8e6bd258d5b5a76952dc16a6d84 Component: cli --- components/cli/client.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/cli/client.go b/components/cli/client.go index e8c7f889f8..4d98a33cc4 100644 --- a/components/cli/client.go +++ b/components/cli/client.go @@ -3,7 +3,6 @@ package main import ( "path/filepath" - "github.com/docker/docker/cli" cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/cliconfig" flag "github.com/docker/docker/pkg/mflag" @@ -12,7 +11,7 @@ import ( var ( commonFlags = cliflags.InitCommonFlags() - clientFlags = &cli.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags} + clientFlags = &cliflags.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags} ) func init() { From 9d807c44244f9f63808e7dcf28f50fdcefd1f262 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 25 Apr 2016 12:05:42 -0400 Subject: [PATCH 04/45] Consolidate the files in client/ Signed-off-by: Daniel Nephin Upstream-commit: ec6cc96fa177a91b0516c11ab3c1dfc5c42ac8db Component: cli --- components/cli/client.go | 37 ------------------- components/cli/docker.go | 34 +++++++++++++++++ .../cli/{client_test.go => docker_test.go} | 0 components/cli/{flags.go => usage.go} | 16 ++------ .../cli/{flags_test.go => usage_test.go} | 4 +- 5 files changed, 41 insertions(+), 50 deletions(-) delete mode 100644 components/cli/client.go rename components/cli/{client_test.go => docker_test.go} (100%) rename components/cli/{flags.go => usage.go} (51%) rename components/cli/{flags_test.go => usage_test.go} (70%) diff --git a/components/cli/client.go b/components/cli/client.go deleted file mode 100644 index 4d98a33cc4..0000000000 --- a/components/cli/client.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "path/filepath" - - cliflags "github.com/docker/docker/cli/flags" - "github.com/docker/docker/cliconfig" - flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" -) - -var ( - commonFlags = cliflags.InitCommonFlags() - clientFlags = &cliflags.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags} -) - -func init() { - - client := clientFlags.FlagSet - client.StringVar(&clientFlags.ConfigDir, []string{"-config"}, cliconfig.ConfigDir(), "Location of client config files") - - clientFlags.PostParse = func() { - clientFlags.Common.PostParse() - - if clientFlags.ConfigDir != "" { - cliconfig.SetConfigDir(clientFlags.ConfigDir) - } - - if clientFlags.Common.TrustKey == "" { - clientFlags.Common.TrustKey = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) - } - - if clientFlags.Common.Debug { - utils.EnableDebug() - } - } -} diff --git a/components/cli/docker.go b/components/cli/docker.go index 838602164d..8397124932 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -3,16 +3,26 @@ package main import ( "fmt" "os" + "path/filepath" "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" "github.com/docker/docker/cli" + cliflags "github.com/docker/docker/cli/flags" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/dockerversion" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" "github.com/docker/docker/utils" ) +var ( + commonFlags = cliflags.InitCommonFlags() + clientFlags = initClientFlags(commonFlags) + flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") + flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") +) + func main() { // Set terminal emulation based on platform as required. stdin, stdout, stderr := term.StdStreams() @@ -30,6 +40,7 @@ func main() { help := "\nCommands:\n" + dockerCommands := sortCommands(cli.DockerCommandUsage) for _, cmd := range dockerCommands { help += fmt.Sprintf(" %-10.10s%s\n", cmd.Name, cmd.Description) } @@ -75,3 +86,26 @@ func showVersion() { fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit) } } + +func initClientFlags(commonFlags *cliflags.CommonFlags) *cliflags.ClientFlags { + clientFlags := &cliflags.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags} + client := clientFlags.FlagSet + client.StringVar(&clientFlags.ConfigDir, []string{"-config"}, cliconfig.ConfigDir(), "Location of client config files") + + clientFlags.PostParse = func() { + clientFlags.Common.PostParse() + + if clientFlags.ConfigDir != "" { + cliconfig.SetConfigDir(clientFlags.ConfigDir) + } + + if clientFlags.Common.TrustKey == "" { + clientFlags.Common.TrustKey = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) + } + + if clientFlags.Common.Debug { + utils.EnableDebug() + } + } + return clientFlags +} diff --git a/components/cli/client_test.go b/components/cli/docker_test.go similarity index 100% rename from components/cli/client_test.go rename to components/cli/docker_test.go diff --git a/components/cli/flags.go b/components/cli/usage.go similarity index 51% rename from components/cli/flags.go rename to components/cli/usage.go index 35a8108880..792d178073 100644 --- a/components/cli/flags.go +++ b/components/cli/usage.go @@ -4,12 +4,6 @@ import ( "sort" "github.com/docker/docker/cli" - flag "github.com/docker/docker/pkg/mflag" -) - -var ( - flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") - flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") ) type byName []cli.Command @@ -18,13 +12,11 @@ func (a byName) Len() int { return len(a) } func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } -var dockerCommands []cli.Command - // TODO(tiborvass): do not show 'daemon' on client-only binaries -func init() { - for _, cmd := range cli.DockerCommands { - dockerCommands = append(dockerCommands, cmd) - } +func sortCommands(commands []cli.Command) []cli.Command { + dockerCommands := make([]cli.Command, len(commands)) + copy(dockerCommands, commands) sort.Sort(byName(dockerCommands)) + return dockerCommands } diff --git a/components/cli/flags_test.go b/components/cli/usage_test.go similarity index 70% rename from components/cli/flags_test.go rename to components/cli/usage_test.go index 28021ba4c9..0453265db8 100644 --- a/components/cli/flags_test.go +++ b/components/cli/usage_test.go @@ -3,11 +3,13 @@ package main import ( "sort" "testing" + + "github.com/docker/docker/cli" ) // Tests if the subcommands of docker are sorted func TestDockerSubcommandsAreSorted(t *testing.T) { - if !sort.IsSorted(byName(dockerCommands)) { + if !sort.IsSorted(byName(cli.DockerCommandUsage)) { t.Fatal("Docker subcommands are not in sorted order") } } From a190e4c730445cc69addb49c61c97f4f48f84f36 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 29 Apr 2016 11:16:34 -0400 Subject: [PATCH 05/45] Remove reflection on CLI init before: ``` $ time docker --help real 0m0.177s user 0m0.000s sys 0m0.040s ``` after: ``` $ time docker --help real 0m0.010s user 0m0.000s sys 0m0.000s ``` Signed-off-by: Brian Goff Upstream-commit: eb35552fb363ccc70c0473a29e40161ead848e37 Component: cli --- components/cli/daemon.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/cli/daemon.go b/components/cli/daemon.go index 15dffbaefb..8fe3484761 100644 --- a/components/cli/daemon.go +++ b/components/cli/daemon.go @@ -9,3 +9,10 @@ type DaemonProxy struct{} func NewDaemonProxy() DaemonProxy { return DaemonProxy{} } + +// Command returns a cli command handler if one exists +func (p DaemonProxy) Command(name string) func(...string) error { + return map[string]func(...string) error{ + "daemon": p.CmdDaemon, + }[name] +} From 55cad0247705a425bd4c3e8a950a6b09f5f583b0 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 27 Apr 2016 12:11:32 -0400 Subject: [PATCH 06/45] When exec'ing dockerd, look for it in the same directory as the docker binary first, before checking path. Signed-off-by: Daniel Nephin Upstream-commit: 625263e2c7ad70ff9ce55e1161e866db83c66b37 Component: cli --- components/cli/daemon_unix.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/components/cli/daemon_unix.go b/components/cli/daemon_unix.go index abe9ebfc51..896782b36c 100644 --- a/components/cli/daemon_unix.go +++ b/components/cli/daemon_unix.go @@ -5,27 +5,40 @@ package main import ( "os" "os/exec" + "path/filepath" "syscall" ) // CmdDaemon execs dockerd with the same flags -// TODO: add a deprecation warning? func (p DaemonProxy) CmdDaemon(args ...string) error { // Use os.Args[1:] so that "global" args are passed to dockerd args = stripDaemonArg(os.Args[1:]) - // TODO: check dirname args[0] first - binaryAbsPath, err := exec.LookPath(daemonBinary) + binaryPath, err := findDaemonBinary() if err != nil { return err } return syscall.Exec( - binaryAbsPath, + binaryPath, append([]string{daemonBinary}, args...), os.Environ()) } +// findDaemonBinary looks for the path to the dockerd binary starting with +// the directory of the current executable (if one exists) and followed by $PATH +func findDaemonBinary() (string, error) { + execDirname := filepath.Dir(os.Args[0]) + if execDirname != "" { + binaryPath := filepath.Join(execDirname, daemonBinary) + if _, err := os.Stat(binaryPath); err == nil { + return binaryPath, nil + } + } + + return exec.LookPath(daemonBinary) +} + // stripDaemonArg removes the `daemon` argument from the list func stripDaemonArg(args []string) []string { for i, arg := range args { From 3773be609d999ce7c820ef04a33b1ff03c397ba3 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 27 Apr 2016 13:08:20 -0400 Subject: [PATCH 07/45] Raise a more relevant error when dockerd is not available on the platform. Signed-off-by: Daniel Nephin Upstream-commit: 765ab2b6921b1a6f21504d0c4ef4eed046a76d89 Component: cli --- components/cli/{daemon_windows.go => daemon_none.go} | 7 ++++++- .../cli/{daemon_windows_test.go => daemon_none_test.go} | 4 +++- components/cli/daemon_unix.go | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) rename components/cli/{daemon_windows.go => daemon_none.go} (55%) rename components/cli/{daemon_windows_test.go => daemon_none_test.go} (80%) diff --git a/components/cli/daemon_windows.go b/components/cli/daemon_none.go similarity index 55% rename from components/cli/daemon_windows.go rename to components/cli/daemon_none.go index 41c0133b67..d66bf1a546 100644 --- a/components/cli/daemon_windows.go +++ b/components/cli/daemon_none.go @@ -1,11 +1,16 @@ +// +build !daemon + package main import ( "fmt" + "runtime" + "strings" ) // CmdDaemon reports on an error on windows, because there is no exec func (p DaemonProxy) CmdDaemon(args ...string) error { return fmt.Errorf( - "`docker daemon` does not exist on windows. Please run `dockerd` directly") + "`docker daemon` is not supported on %s. Please run `dockerd` directly", + strings.Title(runtime.GOOS)) } diff --git a/components/cli/daemon_windows_test.go b/components/cli/daemon_none_test.go similarity index 80% rename from components/cli/daemon_windows_test.go rename to components/cli/daemon_none_test.go index 3da4e5d7cc..d75453bcc5 100644 --- a/components/cli/daemon_windows_test.go +++ b/components/cli/daemon_none_test.go @@ -1,3 +1,5 @@ +// +build !daemon + package main import ( @@ -9,7 +11,7 @@ func TestCmdDaemon(t *testing.T) { proxy := NewDaemonProxy() err := proxy.CmdDaemon("--help") if err == nil { - t.Fatal("Expected CmdDaemon to fail in Windows.") + t.Fatal("Expected CmdDaemon to fail on Windows.") } if !strings.Contains(err.Error(), "Please run `dockerd`") { diff --git a/components/cli/daemon_unix.go b/components/cli/daemon_unix.go index 896782b36c..7a27518636 100644 --- a/components/cli/daemon_unix.go +++ b/components/cli/daemon_unix.go @@ -1,4 +1,4 @@ -// +build !windows +// +build daemon package main From 4b0173813a015922a583435c407a6143b749c7d6 Mon Sep 17 00:00:00 2001 From: John Starks Date: Fri, 20 May 2016 10:38:31 -0700 Subject: [PATCH 08/45] Windows: work around Go 1.6.2/Nano Server TP5 issue This works around golang/go#15286 by explicitly loading shell32.dll at load time, ensuring that syscall can load it dynamically during process startup. Signed-off-by: John Starks Signed-off-by: Antonio Murdaca Upstream-commit: 9b1a322d9ef85e0c0a4613d11d1ba93dac35569b Component: cli --- components/cli/docker_windows.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/components/cli/docker_windows.go b/components/cli/docker_windows.go index de32257586..9bc507e20c 100644 --- a/components/cli/docker_windows.go +++ b/components/cli/docker_windows.go @@ -1,5 +1,18 @@ package main import ( + "sync/atomic" + _ "github.com/docker/docker/autogen/winresources/docker" ) + +//go:cgo_import_dynamic main.dummy CommandLineToArgvW%2 "shell32.dll" + +var dummy uintptr + +func init() { + // Ensure that this import is not removed by the linker. This is used to + // ensure that shell32.dll is loaded by the system loader, preventing + // go#15286 from triggering on Nano Server TP5. + atomic.LoadUintptr(&dummy) +} From 64201068c60a08141f6404b2aea70f5408ee1587 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 19 Apr 2016 12:59:48 -0400 Subject: [PATCH 09/45] Migrate volume commands to cobra. Signed-off-by: Daniel Nephin Upstream-commit: ad83c422f25498afda0c9a45741dff76e5ae8a3d Component: cli --- components/cli/docker.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 8397124932..45de2e3fca 100644 --- a/components/cli/docker.go +++ b/components/cli/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 != "" { From 50bfe19924ce89101cd941c9adf0033bd5fbedc7 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 31 May 2016 22:19:13 -0700 Subject: [PATCH 10/45] Convert 'docker create' to use cobra and pflag Return the correct status code on flag parsins errors. Signed-off-by: Daniel Nephin Upstream-commit: aee35785205a0239ba071d18ea6e971961bb494c Component: cli --- components/cli/docker.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/cli/docker.go b/components/cli/docker.go index 45de2e3fca..0c727e32c9 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -73,6 +73,10 @@ func main() { if sterr, ok := err.(cli.StatusError); ok { if sterr.Status != "" { fmt.Fprintln(stderr, sterr.Status) + } + // StatusError should only be used for errors, and all errors should + // have a non-zero exit status, so never exit with 0 + if sterr.StatusCode == 0 { os.Exit(1) } os.Exit(sterr.StatusCode) From ab55be6e386beac9e8c5754be69e1f0edb2a31c1 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 9 Jun 2016 13:20:55 -0400 Subject: [PATCH 11/45] Support running 'docker help daemon' Signed-off-by: Daniel Nephin Upstream-commit: 90e5326097e1b508f440404d8b605eae7e57a337 Component: cli --- components/cli/daemon_unix.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/components/cli/daemon_unix.go b/components/cli/daemon_unix.go index 7a27518636..d515b82914 100644 --- a/components/cli/daemon_unix.go +++ b/components/cli/daemon_unix.go @@ -11,8 +11,14 @@ import ( // CmdDaemon execs dockerd with the same flags func (p DaemonProxy) CmdDaemon(args ...string) error { - // Use os.Args[1:] so that "global" args are passed to dockerd - args = stripDaemonArg(os.Args[1:]) + // Special case for handling `docker help daemon`. When pkg/mflag is removed + // we can support this on the daemon side, but that is not possible with + // pkg/mflag because it uses os.Exit(1) instead of returning an error on + // unexpected args. + if len(args) == 0 || args[0] != "--help" { + // Use os.Args[1:] so that "global" args are passed to dockerd + args = stripDaemonArg(os.Args[1:]) + } binaryPath, err := findDaemonBinary() if err != nil { From 88b6751cda7584a59f12091ec31057f63a705f6b Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 21 Jun 2016 16:42:47 -0400 Subject: [PATCH 12/45] Convert dockerd to use cobra and pflag Signed-off-by: Daniel Nephin Upstream-commit: 23dd85befd52f6d2cf25dbb7d8866bc6f72f50c8 Component: cli --- components/cli/docker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/cli/docker.go b/components/cli/docker.go index 0c727e32c9..7346d913ef 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -101,6 +101,7 @@ func initClientFlags(commonFlags *cliflags.CommonFlags) *cliflags.ClientFlags { clientFlags.PostParse = func() { clientFlags.Common.PostParse() + cliflags.SetDaemonLogLevel(commonOpts.LogLevel) if clientFlags.ConfigDir != "" { cliconfig.SetConfigDir(clientFlags.ConfigDir) From c526dbb69a7c900a17ca273e974ceffe8fa93d8b Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 22 Jun 2016 13:08:04 -0400 Subject: [PATCH 13/45] Convert docker root command to use pflag and cobra Fix the daemon proxy for cobra commands. Signed-off-by: Daniel Nephin Upstream-commit: 08784d7e0ec0e398c2eff368ae2d727ed1de4ef3 Component: cli --- components/cli/daemon.go | 18 ------ components/cli/daemon_none.go | 14 ++++- components/cli/daemon_unix.go | 40 +++++++++--- components/cli/docker.go | 114 +++++++++++++++------------------- components/cli/docker_test.go | 12 +++- 5 files changed, 101 insertions(+), 97 deletions(-) delete mode 100644 components/cli/daemon.go diff --git a/components/cli/daemon.go b/components/cli/daemon.go deleted file mode 100644 index 8fe3484761..0000000000 --- a/components/cli/daemon.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -const daemonBinary = "dockerd" - -// DaemonProxy acts as a cli.Handler to proxy calls to the daemon binary -type DaemonProxy struct{} - -// NewDaemonProxy returns a new handler -func NewDaemonProxy() DaemonProxy { - return DaemonProxy{} -} - -// Command returns a cli command handler if one exists -func (p DaemonProxy) Command(name string) func(...string) error { - return map[string]func(...string) error{ - "daemon": p.CmdDaemon, - }[name] -} diff --git a/components/cli/daemon_none.go b/components/cli/daemon_none.go index d66bf1a546..c57896ed71 100644 --- a/components/cli/daemon_none.go +++ b/components/cli/daemon_none.go @@ -4,12 +4,22 @@ package main import ( "fmt" + "github.com/spf13/cobra" "runtime" "strings" ) -// CmdDaemon reports on an error on windows, because there is no exec -func (p DaemonProxy) CmdDaemon(args ...string) error { +func newDaemonCommand() *cobra.Command { + return &cobra.Command{ + Use: "daemon", + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runDaemon() + }, + } +} + +func runDaemon() error { return fmt.Errorf( "`docker daemon` is not supported on %s. Please run `dockerd` directly", strings.Title(runtime.GOOS)) diff --git a/components/cli/daemon_unix.go b/components/cli/daemon_unix.go index d515b82914..30a40a8611 100644 --- a/components/cli/daemon_unix.go +++ b/components/cli/daemon_unix.go @@ -3,23 +3,37 @@ package main import ( + "fmt" + "os" "os/exec" "path/filepath" "syscall" + + "github.com/spf13/cobra" ) -// CmdDaemon execs dockerd with the same flags -func (p DaemonProxy) CmdDaemon(args ...string) error { - // Special case for handling `docker help daemon`. When pkg/mflag is removed - // we can support this on the daemon side, but that is not possible with - // pkg/mflag because it uses os.Exit(1) instead of returning an error on - // unexpected args. - if len(args) == 0 || args[0] != "--help" { - // Use os.Args[1:] so that "global" args are passed to dockerd - args = stripDaemonArg(os.Args[1:]) - } +const daemonBinary = "dockerd" +func newDaemonCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "daemon", + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runDaemon() + }, + } + cmd.SetHelpFunc(helpFunc) + return cmd +} + +// CmdDaemon execs dockerd with the same flags +func runDaemon() error { + // Use os.Args[1:] so that "global" args are passed to dockerd + return execDaemon(stripDaemonArg(os.Args[1:])) +} + +func execDaemon(args []string) error { binaryPath, err := findDaemonBinary() if err != nil { return err @@ -31,6 +45,12 @@ func (p DaemonProxy) CmdDaemon(args ...string) error { os.Environ()) } +func helpFunc(cmd *cobra.Command, args []string) { + if err := execDaemon([]string{"--help"}); err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err.Error()) + } +} + // findDaemonBinary looks for the path to the dockerd binary starting with // the directory of the current executable (if one exists) and followed by $PATH func findDaemonBinary() (string, error) { diff --git a/components/cli/docker.go b/components/cli/docker.go index 7346d913ef..bd04a3a1bd 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -12,64 +12,52 @@ import ( cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/cliconfig" "github.com/docker/docker/dockerversion" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" "github.com/docker/docker/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" ) -var ( - commonFlags = cliflags.InitCommonFlags() - clientFlags = initClientFlags(commonFlags) - flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") - flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") -) +func newDockerCommand(dockerCli *client.DockerCli, opts *cliflags.ClientOptions) *cobra.Command { + cmd := &cobra.Command{ + Use: "docker [OPTIONS] COMMAND [arg...]", + Short: "A self-sufficient runtime for containers.", + SilenceUsage: true, + SilenceErrors: true, + Args: cli.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + if opts.Version { + showVersion() + return nil + } + fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) + return nil + }, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + dockerPreRun(cmd.Flags(), opts) + return dockerCli.Initialize(opts) + }, + } + cobraadaptor.SetupRootCommand(cmd, dockerCli) + + 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") + opts.Common.InstallFlags(flags) + + return cmd +} func main() { // Set terminal emulation based on platform as required. stdin, stdout, stderr := term.StdStreams() - logrus.SetOutput(stderr) - flag.Merge(flag.CommandLine, clientFlags.FlagSet, commonFlags.FlagSet) + opts := cliflags.NewClientOptions() + dockerCli := client.NewDockerCli(stdin, stdout, stderr, opts) + cmd := newDockerCommand(dockerCli, opts) - 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") - - flag.CommandLine.SetOutput(stdout) - flag.PrintDefaults() - - help := "\nCommands:\n" - - dockerCommands := append(cli.DockerCommandUsage, cobraAdaptor.Usage()...) - for _, cmd := range sortCommands(dockerCommands) { - help += fmt.Sprintf(" %-10.10s%s\n", cmd.Name, cmd.Description) - } - - help += "\nRun 'docker COMMAND --help' for more information on a command." - fmt.Fprintf(stdout, "%s\n", help) - } - - flag.Parse() - - if *flVersion { - showVersion() - return - } - - if *flHelp { - // if global flag --help is present, regardless of what other options and commands there are, - // just print the usage. - flag.Usage() - return - } - - clientCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags) - - c := cli.New(clientCli, NewDaemonProxy(), cobraAdaptor) - if err := c.Run(flag.Args()...); err != nil { + if err := cmd.Execute(); err != nil { if sterr, ok := err.(cli.StatusError); ok { if sterr.Status != "" { fmt.Fprintln(stderr, sterr.Status) @@ -94,26 +82,22 @@ func showVersion() { } } -func initClientFlags(commonFlags *cliflags.CommonFlags) *cliflags.ClientFlags { - clientFlags := &cliflags.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags} - client := clientFlags.FlagSet - client.StringVar(&clientFlags.ConfigDir, []string{"-config"}, cliconfig.ConfigDir(), "Location of client config files") +func dockerPreRun(flags *pflag.FlagSet, opts *cliflags.ClientOptions) { + opts.Common.SetDefaultOptions(flags) + cliflags.SetDaemonLogLevel(opts.Common.LogLevel) - clientFlags.PostParse = func() { - clientFlags.Common.PostParse() - cliflags.SetDaemonLogLevel(commonOpts.LogLevel) + // TODO: remove this, set a default in New, and pass it in opts + if opts.ConfigDir != "" { + cliconfig.SetConfigDir(opts.ConfigDir) + } - if clientFlags.ConfigDir != "" { - cliconfig.SetConfigDir(clientFlags.ConfigDir) - } + if opts.Common.TrustKey == "" { + opts.Common.TrustKey = filepath.Join( + cliconfig.ConfigDir(), + cliflags.DefaultTrustKeyFile) + } - if clientFlags.Common.TrustKey == "" { - clientFlags.Common.TrustKey = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) - } - - if clientFlags.Common.Debug { - utils.EnableDebug() - } + if opts.Common.Debug { + utils.EnableDebug() } - return clientFlags } diff --git a/components/cli/docker_test.go b/components/cli/docker_test.go index 5708c96cb5..a1e84f1396 100644 --- a/components/cli/docker_test.go +++ b/components/cli/docker_test.go @@ -6,13 +6,21 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/utils" + + "github.com/docker/docker/api/client" + cliflags "github.com/docker/docker/cli/flags" ) func TestClientDebugEnabled(t *testing.T) { defer utils.DisableDebug() - clientFlags.Common.FlagSet.Parse([]string{"-D"}) - clientFlags.PostParse() + opts := cliflags.NewClientOptions() + cmd := newDockerCommand(&client.DockerCli{}, opts) + + opts.Common.Debug = true + if err := cmd.PersistentPreRunE(cmd, []string{}); err != nil { + t.Fatalf("Unexpected error: %s", err.Error()) + } if os.Getenv("DEBUG") != "1" { t.Fatal("expected debug enabled, got false") From 7e9f26ef705cd4c338b76571dedd3241252b0efe Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 22 Jun 2016 18:36:51 -0400 Subject: [PATCH 14/45] Update unit tests for new cobra root command. Cleanup cobra integration Update windows files for cobra and pflags Cleanup SetupRootcmd, and remove unnecessary SetFlagErrorFunc. Use cobra command traversal Signed-off-by: Daniel Nephin Upstream-commit: 58a14cd18cb99ccf02a615e8f87aad99f64b8a42 Component: cli --- components/cli/docker.go | 93 ++++++++++++++++++++++++++++------- components/cli/docker_test.go | 6 +-- 2 files changed, 77 insertions(+), 22 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index bd04a3a1bd..0ae3906abb 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -3,10 +3,20 @@ package main import ( "fmt" "os" - "path/filepath" "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/client/container" + "github.com/docker/docker/api/client/image" + "github.com/docker/docker/api/client/network" + "github.com/docker/docker/api/client/node" + "github.com/docker/docker/api/client/plugin" + "github.com/docker/docker/api/client/registry" + "github.com/docker/docker/api/client/service" + "github.com/docker/docker/api/client/stack" + "github.com/docker/docker/api/client/swarm" + "github.com/docker/docker/api/client/system" + "github.com/docker/docker/api/client/volume" "github.com/docker/docker/cli" "github.com/docker/docker/cli/cobraadaptor" cliflags "github.com/docker/docker/cli/flags" @@ -18,13 +28,15 @@ import ( "github.com/spf13/pflag" ) -func newDockerCommand(dockerCli *client.DockerCli, opts *cliflags.ClientOptions) *cobra.Command { +func newDockerCommand(dockerCli *client.DockerCli) *cobra.Command { + opts := cliflags.NewClientOptions() cmd := &cobra.Command{ - Use: "docker [OPTIONS] COMMAND [arg...]", - Short: "A self-sufficient runtime for containers.", - SilenceUsage: true, - SilenceErrors: true, - Args: cli.NoArgs, + Use: "docker [OPTIONS] COMMAND [arg...]", + Short: "A self-sufficient runtime for containers.", + SilenceUsage: true, + SilenceErrors: true, + TraverseChildren: true, + Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { if opts.Version { showVersion() @@ -38,13 +50,66 @@ func newDockerCommand(dockerCli *client.DockerCli, opts *cliflags.ClientOptions) return dockerCli.Initialize(opts) }, } - cobraadaptor.SetupRootCommand(cmd, dockerCli) + cobraadaptor.SetupRootCommand(cmd) 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") opts.Common.InstallFlags(flags) + cmd.SetOutput(dockerCli.Out()) + cmd.AddCommand( + newDaemonCommand(), + node.NewNodeCommand(dockerCli), + service.NewServiceCommand(dockerCli), + stack.NewStackCommand(dockerCli), + stack.NewTopLevelDeployCommand(dockerCli), + swarm.NewSwarmCommand(dockerCli), + container.NewAttachCommand(dockerCli), + container.NewCommitCommand(dockerCli), + container.NewCopyCommand(dockerCli), + container.NewCreateCommand(dockerCli), + container.NewDiffCommand(dockerCli), + container.NewExecCommand(dockerCli), + container.NewExportCommand(dockerCli), + container.NewKillCommand(dockerCli), + container.NewLogsCommand(dockerCli), + container.NewPauseCommand(dockerCli), + container.NewPortCommand(dockerCli), + container.NewPsCommand(dockerCli), + container.NewRenameCommand(dockerCli), + container.NewRestartCommand(dockerCli), + container.NewRmCommand(dockerCli), + container.NewRunCommand(dockerCli), + container.NewStartCommand(dockerCli), + container.NewStatsCommand(dockerCli), + container.NewStopCommand(dockerCli), + container.NewTopCommand(dockerCli), + container.NewUnpauseCommand(dockerCli), + container.NewUpdateCommand(dockerCli), + container.NewWaitCommand(dockerCli), + image.NewBuildCommand(dockerCli), + image.NewHistoryCommand(dockerCli), + image.NewImagesCommand(dockerCli), + image.NewLoadCommand(dockerCli), + image.NewRemoveCommand(dockerCli), + image.NewSaveCommand(dockerCli), + image.NewPullCommand(dockerCli), + image.NewPushCommand(dockerCli), + image.NewSearchCommand(dockerCli), + image.NewImportCommand(dockerCli), + image.NewTagCommand(dockerCli), + network.NewNetworkCommand(dockerCli), + system.NewEventsCommand(dockerCli), + system.NewInspectCommand(dockerCli), + registry.NewLoginCommand(dockerCli), + registry.NewLogoutCommand(dockerCli), + system.NewVersionCommand(dockerCli), + volume.NewVolumeCommand(dockerCli), + system.NewInfoCommand(dockerCli), + ) + plugin.NewPluginCommand(cmd, dockerCli) + return cmd } @@ -53,9 +118,8 @@ func main() { stdin, stdout, stderr := term.StdStreams() logrus.SetOutput(stderr) - opts := cliflags.NewClientOptions() - dockerCli := client.NewDockerCli(stdin, stdout, stderr, opts) - cmd := newDockerCommand(dockerCli, opts) + dockerCli := client.NewDockerCli(stdin, stdout, stderr) + cmd := newDockerCommand(dockerCli) if err := cmd.Execute(); err != nil { if sterr, ok := err.(cli.StatusError); ok { @@ -86,17 +150,10 @@ func dockerPreRun(flags *pflag.FlagSet, opts *cliflags.ClientOptions) { opts.Common.SetDefaultOptions(flags) cliflags.SetDaemonLogLevel(opts.Common.LogLevel) - // TODO: remove this, set a default in New, and pass it in opts if opts.ConfigDir != "" { cliconfig.SetConfigDir(opts.ConfigDir) } - if opts.Common.TrustKey == "" { - opts.Common.TrustKey = filepath.Join( - cliconfig.ConfigDir(), - cliflags.DefaultTrustKeyFile) - } - if opts.Common.Debug { utils.EnableDebug() } diff --git a/components/cli/docker_test.go b/components/cli/docker_test.go index a1e84f1396..72d2311521 100644 --- a/components/cli/docker_test.go +++ b/components/cli/docker_test.go @@ -8,16 +8,14 @@ import ( "github.com/docker/docker/utils" "github.com/docker/docker/api/client" - cliflags "github.com/docker/docker/cli/flags" ) func TestClientDebugEnabled(t *testing.T) { defer utils.DisableDebug() - opts := cliflags.NewClientOptions() - cmd := newDockerCommand(&client.DockerCli{}, opts) + cmd := newDockerCommand(&client.DockerCli{}) + cmd.Flags().Set("debug", "true") - opts.Common.Debug = true if err := cmd.PersistentPreRunE(cmd, []string{}); err != nil { t.Fatalf("Unexpected error: %s", err.Error()) } From e062c54a99b0308c292a0ea2c77eeb89587c7643 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 23 Jun 2016 11:25:51 -0400 Subject: [PATCH 15/45] Remove old cli framework. Also consolidate the leftover packages under cli. Remove pkg/mflag. Make manpage generation work with new cobra layout. Remove remaining mflag and fix tests after rebase with master. Signed-off-by: Daniel Nephin Upstream-commit: 3b178887a7bc9048cc0a86a1b912c7557ba85d81 Component: cli --- components/cli/docker.go | 68 +++--------------------------------- components/cli/usage.go | 22 ------------ components/cli/usage_test.go | 15 -------- 3 files changed, 4 insertions(+), 101 deletions(-) delete mode 100644 components/cli/usage.go delete mode 100644 components/cli/usage_test.go diff --git a/components/cli/docker.go b/components/cli/docker.go index 0ae3906abb..8d7861847f 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -6,19 +6,8 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" - "github.com/docker/docker/api/client/container" - "github.com/docker/docker/api/client/image" - "github.com/docker/docker/api/client/network" - "github.com/docker/docker/api/client/node" - "github.com/docker/docker/api/client/plugin" - "github.com/docker/docker/api/client/registry" - "github.com/docker/docker/api/client/service" - "github.com/docker/docker/api/client/stack" - "github.com/docker/docker/api/client/swarm" - "github.com/docker/docker/api/client/system" - "github.com/docker/docker/api/client/volume" + "github.com/docker/docker/api/client/command" "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" @@ -50,7 +39,7 @@ func newDockerCommand(dockerCli *client.DockerCli) *cobra.Command { return dockerCli.Initialize(opts) }, } - cobraadaptor.SetupRootCommand(cmd) + cli.SetupRootCommand(cmd) flags := cmd.Flags() flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit") @@ -58,57 +47,8 @@ func newDockerCommand(dockerCli *client.DockerCli) *cobra.Command { opts.Common.InstallFlags(flags) cmd.SetOutput(dockerCli.Out()) - cmd.AddCommand( - newDaemonCommand(), - node.NewNodeCommand(dockerCli), - service.NewServiceCommand(dockerCli), - stack.NewStackCommand(dockerCli), - stack.NewTopLevelDeployCommand(dockerCli), - swarm.NewSwarmCommand(dockerCli), - container.NewAttachCommand(dockerCli), - container.NewCommitCommand(dockerCli), - container.NewCopyCommand(dockerCli), - container.NewCreateCommand(dockerCli), - container.NewDiffCommand(dockerCli), - container.NewExecCommand(dockerCli), - container.NewExportCommand(dockerCli), - container.NewKillCommand(dockerCli), - container.NewLogsCommand(dockerCli), - container.NewPauseCommand(dockerCli), - container.NewPortCommand(dockerCli), - container.NewPsCommand(dockerCli), - container.NewRenameCommand(dockerCli), - container.NewRestartCommand(dockerCli), - container.NewRmCommand(dockerCli), - container.NewRunCommand(dockerCli), - container.NewStartCommand(dockerCli), - container.NewStatsCommand(dockerCli), - container.NewStopCommand(dockerCli), - container.NewTopCommand(dockerCli), - container.NewUnpauseCommand(dockerCli), - container.NewUpdateCommand(dockerCli), - container.NewWaitCommand(dockerCli), - image.NewBuildCommand(dockerCli), - image.NewHistoryCommand(dockerCli), - image.NewImagesCommand(dockerCli), - image.NewLoadCommand(dockerCli), - image.NewRemoveCommand(dockerCli), - image.NewSaveCommand(dockerCli), - image.NewPullCommand(dockerCli), - image.NewPushCommand(dockerCli), - image.NewSearchCommand(dockerCli), - image.NewImportCommand(dockerCli), - image.NewTagCommand(dockerCli), - network.NewNetworkCommand(dockerCli), - system.NewEventsCommand(dockerCli), - system.NewInspectCommand(dockerCli), - registry.NewLoginCommand(dockerCli), - registry.NewLogoutCommand(dockerCli), - system.NewVersionCommand(dockerCli), - volume.NewVolumeCommand(dockerCli), - system.NewInfoCommand(dockerCli), - ) - plugin.NewPluginCommand(cmd, dockerCli) + cmd.AddCommand(newDaemonCommand()) + command.AddCommands(cmd, dockerCli) return cmd } diff --git a/components/cli/usage.go b/components/cli/usage.go deleted file mode 100644 index 792d178073..0000000000 --- a/components/cli/usage.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -import ( - "sort" - - "github.com/docker/docker/cli" -) - -type byName []cli.Command - -func (a byName) Len() int { return len(a) } -func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } - -// TODO(tiborvass): do not show 'daemon' on client-only binaries - -func sortCommands(commands []cli.Command) []cli.Command { - dockerCommands := make([]cli.Command, len(commands)) - copy(dockerCommands, commands) - sort.Sort(byName(dockerCommands)) - return dockerCommands -} diff --git a/components/cli/usage_test.go b/components/cli/usage_test.go deleted file mode 100644 index 0453265db8..0000000000 --- a/components/cli/usage_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "sort" - "testing" - - "github.com/docker/docker/cli" -) - -// Tests if the subcommands of docker are sorted -func TestDockerSubcommandsAreSorted(t *testing.T) { - if !sort.IsSorted(byName(cli.DockerCommandUsage)) { - t.Fatal("Docker subcommands are not in sorted order") - } -} From e110c778f4f01567e86ad0e53225a763c666377e Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 3 Aug 2016 12:20:46 -0400 Subject: [PATCH 16/45] Fix tests and windows service. Support args to RunCommand Fix docker help text test. Fix for ipv6 tests. Fix TLSverify option. Fix TestDaemonDiscoveryBackendConfigReload Use tempfile for another test. Restore missing flag. Fix tests for removal of shlex. Signed-off-by: Daniel Nephin Upstream-commit: 9af25060cd47681b359fe736b31336013022d949 Component: cli --- components/cli/daemon_none.go | 3 ++- components/cli/docker.go | 21 ++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/components/cli/daemon_none.go b/components/cli/daemon_none.go index c57896ed71..65f9f37be2 100644 --- a/components/cli/daemon_none.go +++ b/components/cli/daemon_none.go @@ -4,9 +4,10 @@ package main import ( "fmt" - "github.com/spf13/cobra" "runtime" "strings" + + "github.com/spf13/cobra" ) func newDaemonCommand() *cobra.Command { diff --git a/components/cli/docker.go b/components/cli/docker.go index 8d7861847f..38907970d3 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -19,13 +19,15 @@ import ( func newDockerCommand(dockerCli *client.DockerCli) *cobra.Command { opts := cliflags.NewClientOptions() + var flags *pflag.FlagSet + cmd := &cobra.Command{ Use: "docker [OPTIONS] COMMAND [arg...]", Short: "A self-sufficient runtime for containers.", SilenceUsage: true, SilenceErrors: true, TraverseChildren: true, - Args: cli.NoArgs, + Args: noArgs, RunE: func(cmd *cobra.Command, args []string) error { if opts.Version { showVersion() @@ -35,13 +37,15 @@ func newDockerCommand(dockerCli *client.DockerCli) *cobra.Command { return nil }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - dockerPreRun(cmd.Flags(), opts) + // flags must be the top-level command flags, not cmd.Flags() + opts.Common.SetDefaultOptions(flags) + dockerPreRun(opts) return dockerCli.Initialize(opts) }, } cli.SetupRootCommand(cmd) - flags := cmd.Flags() + 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") opts.Common.InstallFlags(flags) @@ -53,6 +57,14 @@ func newDockerCommand(dockerCli *client.DockerCli) *cobra.Command { return cmd } +func noArgs(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return nil + } + return fmt.Errorf( + "docker: '%s' is not a docker command.\nSee 'docker --help'%s", args[0], ".") +} + func main() { // Set terminal emulation based on platform as required. stdin, stdout, stderr := term.StdStreams() @@ -86,8 +98,7 @@ func showVersion() { } } -func dockerPreRun(flags *pflag.FlagSet, opts *cliflags.ClientOptions) { - opts.Common.SetDefaultOptions(flags) +func dockerPreRun(opts *cliflags.ClientOptions) { cliflags.SetDaemonLogLevel(opts.Common.LogLevel) if opts.ConfigDir != "" { From c3ab654101b0920bc6239d04f0ae1ee9517e521a Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Fri, 26 Aug 2016 12:19:02 -0400 Subject: [PATCH 17/45] Fix daemon command proxy. Signed-off-by: Daniel Nephin Upstream-commit: ad96b991e960ba1e90f33d7bd52826aa44c2fc3e Component: cli --- components/cli/daemon_none_test.go | 17 +++++++---------- components/cli/daemon_unit_test.go | 30 ++++++++++++++++++++++++++++++ components/cli/daemon_unix.go | 6 ++++-- 3 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 components/cli/daemon_unit_test.go diff --git a/components/cli/daemon_none_test.go b/components/cli/daemon_none_test.go index d75453bcc5..32032fe1b3 100644 --- a/components/cli/daemon_none_test.go +++ b/components/cli/daemon_none_test.go @@ -3,18 +3,15 @@ package main import ( - "strings" "testing" + + "github.com/docker/docker/pkg/testutil/assert" ) -func TestCmdDaemon(t *testing.T) { - proxy := NewDaemonProxy() - err := proxy.CmdDaemon("--help") - if err == nil { - t.Fatal("Expected CmdDaemon to fail on Windows.") - } +func TestDaemonCommand(t *testing.T) { + cmd := newDaemonCommand() + cmd.SetArgs([]string{"--help"}) + err := cmd.Execute() - if !strings.Contains(err.Error(), "Please run `dockerd`") { - t.Fatalf("Expected an error about running dockerd, got %s", err) - } + assert.Error(t, err, "Please run `dockerd`") } diff --git a/components/cli/daemon_unit_test.go b/components/cli/daemon_unit_test.go new file mode 100644 index 0000000000..26348a8843 --- /dev/null +++ b/components/cli/daemon_unit_test.go @@ -0,0 +1,30 @@ +// +build daemon + +package main + +import ( + "testing" + + "github.com/docker/docker/pkg/testutil/assert" + "github.com/spf13/cobra" +) + +func stubRun(cmd *cobra.Command, args []string) error { + return nil +} + +func TestDaemonCommandHelp(t *testing.T) { + cmd := newDaemonCommand() + cmd.RunE = stubRun + cmd.SetArgs([]string{"--help"}) + err := cmd.Execute() + assert.NilError(t, err) +} + +func TestDaemonCommand(t *testing.T) { + cmd := newDaemonCommand() + cmd.RunE = stubRun + cmd.SetArgs([]string{"--containerd", "/foo"}) + err := cmd.Execute() + assert.NilError(t, err) +} diff --git a/components/cli/daemon_unix.go b/components/cli/daemon_unix.go index 30a40a8611..754bdeece3 100644 --- a/components/cli/daemon_unix.go +++ b/components/cli/daemon_unix.go @@ -17,8 +17,10 @@ const daemonBinary = "dockerd" func newDaemonCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "daemon", - Hidden: true, + Use: "daemon", + Hidden: true, + Args: cobra.ArbitraryArgs, + DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { return runDaemon() }, From a5bf28b72a0c1a8d2fc98a221568144509f9d6a6 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 8 Sep 2016 13:11:39 -0400 Subject: [PATCH 18/45] Move api/client -> cli/command Using gomvpkg -from github.com/docker/docker/api/client -to github.com/docker/docker/cli/command -vcs_mv_cmd 'git mv {{.Src}} {{.Dst}}' Signed-off-by: Daniel Nephin Upstream-commit: b2c77abc35514e3f37fcbdf592a994d55fc698a0 Component: cli --- components/cli/docker.go | 10 +++++----- components/cli/docker_test.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 38907970d3..969cd80876 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -5,9 +5,9 @@ import ( "os" "github.com/Sirupsen/logrus" - "github.com/docker/docker/api/client" - "github.com/docker/docker/api/client/command" "github.com/docker/docker/cli" + "github.com/docker/docker/cli/command" + "github.com/docker/docker/cli/command/commands" cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/cliconfig" "github.com/docker/docker/dockerversion" @@ -17,7 +17,7 @@ import ( "github.com/spf13/pflag" ) -func newDockerCommand(dockerCli *client.DockerCli) *cobra.Command { +func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { opts := cliflags.NewClientOptions() var flags *pflag.FlagSet @@ -52,7 +52,7 @@ func newDockerCommand(dockerCli *client.DockerCli) *cobra.Command { cmd.SetOutput(dockerCli.Out()) cmd.AddCommand(newDaemonCommand()) - command.AddCommands(cmd, dockerCli) + commands.AddCommands(cmd, dockerCli) return cmd } @@ -70,7 +70,7 @@ func main() { stdin, stdout, stderr := term.StdStreams() logrus.SetOutput(stderr) - dockerCli := client.NewDockerCli(stdin, stdout, stderr) + dockerCli := command.NewDockerCli(stdin, stdout, stderr) cmd := newDockerCommand(dockerCli) if err := cmd.Execute(); err != nil { diff --git a/components/cli/docker_test.go b/components/cli/docker_test.go index 72d2311521..47e24eb0da 100644 --- a/components/cli/docker_test.go +++ b/components/cli/docker_test.go @@ -7,13 +7,13 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/utils" - "github.com/docker/docker/api/client" + "github.com/docker/docker/cli/command" ) func TestClientDebugEnabled(t *testing.T) { defer utils.DisableDebug() - cmd := newDockerCommand(&client.DockerCli{}) + cmd := newDockerCommand(&command.DockerCli{}) cmd.Flags().Set("debug", "true") if err := cmd.PersistentPreRunE(cmd, []string{}); err != nil { From b06624604bf5663f3045b0bf9c7911ada5a5345e Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 22 Sep 2016 22:38:18 +0200 Subject: [PATCH 19/45] Deprecate "daemon" subcommand The daemon is in a separate (dockerd) binary since docker 1.12, so should no longer be used. This marks the command as deprecated, and adds it to the deprecated features list. Signed-off-by: Sebastiaan van Stijn Upstream-commit: 4a2f7d80920e4cdce78af9dc7ecbcd428f77b186 Component: cli --- components/cli/daemon_unix.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/cli/daemon_unix.go b/components/cli/daemon_unix.go index 754bdeece3..f68d220c2f 100644 --- a/components/cli/daemon_unix.go +++ b/components/cli/daemon_unix.go @@ -24,6 +24,7 @@ func newDaemonCommand() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runDaemon() }, + Deprecated: "and will be removed in Docker 1.16. Please run `dockerd` directly.", } cmd.SetHelpFunc(helpFunc) return cmd From b77b8228bfcbd8ce795f3212abe1559f4ec481d1 Mon Sep 17 00:00:00 2001 From: yuexiao-wang Date: Tue, 11 Oct 2016 19:35:12 +0800 Subject: [PATCH 20/45] Modify function name from SetDaemonLogLevel to SetLogLevel Signed-off-by: yuexiao-wang Upstream-commit: a0e694d7c04b927d664d384afb2ee27c373039ed Component: cli --- components/cli/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 969cd80876..d412a38b28 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -99,7 +99,7 @@ func showVersion() { } func dockerPreRun(opts *cliflags.ClientOptions) { - cliflags.SetDaemonLogLevel(opts.Common.LogLevel) + cliflags.SetLogLevel(opts.Common.LogLevel) if opts.ConfigDir != "" { cliconfig.SetConfigDir(opts.ConfigDir) From 2ff5e5a0f89cc63c55c7d84880cbdb441ab1d8d1 Mon Sep 17 00:00:00 2001 From: Kenfe-Mickael Laventure Date: Thu, 6 Oct 2016 07:09:54 -0700 Subject: [PATCH 21/45] Make experimental a runtime flag Signed-off-by: Kenfe-Mickael Laventure Upstream-commit: 773a7f6cb8be730f37a486705075b1ad214c3d73 Component: cli --- components/cli/docker.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index d412a38b28..e01e4ba5c7 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -91,11 +91,7 @@ func main() { } func showVersion() { - if utils.ExperimentalBuild() { - fmt.Printf("Docker version %s, build %s, experimental\n", dockerversion.Version, dockerversion.GitCommit) - } else { - fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit) - } + fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit) } func dockerPreRun(opts *cliflags.ClientOptions) { From a70e23cbcdaf40a9e1e249fadb78e6cbabdbe18a Mon Sep 17 00:00:00 2001 From: yupeng Date: Tue, 1 Nov 2016 11:07:31 +0800 Subject: [PATCH 22/45] Align with other cli descriptions Signed-off-by: yupeng Upstream-commit: f1b1e55f7a111ad7d67dc4c15ffba485a83332ba Component: cli --- components/cli/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index e01e4ba5c7..53e14f7ec3 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -23,7 +23,7 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { cmd := &cobra.Command{ Use: "docker [OPTIONS] COMMAND [arg...]", - Short: "A self-sufficient runtime for containers.", + Short: "A self-sufficient runtime for containers", SilenceUsage: true, SilenceErrors: true, TraverseChildren: true, From 0fef0ef25cfcfe7cca421e5bdfdda09208fa725e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 2 Nov 2016 17:43:32 -0700 Subject: [PATCH 23/45] always add but hide experimental cmds and flags Signed-off-by: Victor Vieux Upstream-commit: 73d63ec5a67b12a01549831e45ce8dc98492be48 Component: cli --- components/cli/docker.go | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 53e14f7ec3..3b6e7f8633 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "strings" "github.com/Sirupsen/logrus" "github.com/docker/docker/cli" @@ -33,7 +34,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 +47,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 +123,20 @@ 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 + name := strings.Split(subcmd.Use, " ")[0] + if name == "stack" || name == "deploy" || name == "checkpoint" || name == "plugin" { + subcmd.Hidden = true + } + } +} From 18b0c2adc0da39305004fa8e29985d6460c45f71 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 3 Nov 2016 17:12:15 -0700 Subject: [PATCH 24/45] update cobra and use Tags Signed-off-by: Victor Vieux Upstream-commit: 2ee5bbcbfa9c3b1da212f4523c95f2b54e9c1e0c Component: cli --- components/cli/docker.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 3b6e7f8633..65568e1e61 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os" - "strings" "github.com/Sirupsen/logrus" "github.com/docker/docker/cli" @@ -134,8 +133,7 @@ func hideExperimentalFeatures(cmd *cobra.Command) { for _, subcmd := range cmd.Commands() { // hide subcommands - name := strings.Split(subcmd.Use, " ")[0] - if name == "stack" || name == "deploy" || name == "checkpoint" || name == "plugin" { + if _, ok := subcmd.Tags["experimental"]; ok { subcmd.Hidden = true } } From 35da390f938a137a51743cb2565487f02a7e4476 Mon Sep 17 00:00:00 2001 From: yupeng Date: Sat, 5 Nov 2016 10:45:15 +0800 Subject: [PATCH 25/45] Align arg with other cli Signed-off-by: yupeng Upstream-commit: 46418414a20d618ab3b09aec5442e48d7d9b66ad Component: cli --- components/cli/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 65568e1e61..56c5a89895 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -22,7 +22,7 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { var flags *pflag.FlagSet cmd := &cobra.Command{ - Use: "docker [OPTIONS] COMMAND [arg...]", + Use: "docker [OPTIONS] COMMAND [ARG...]", Short: "A self-sufficient runtime for containers", SilenceUsage: true, SilenceErrors: true, From b322424e887c20c94ddab025c70e1d4276a36b8a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 2 Nov 2016 17:43:32 -0700 Subject: [PATCH 26/45] always add but hide experimental cmds and flags Signed-off-by: Victor Vieux update cobra and use Tags Signed-off-by: Victor Vieux allow client to talk to an older server Signed-off-by: Victor Vieux Upstream-commit: e1beebbeefaf85107e93ada7b04434bc56deaa3e Component: cli --- components/cli/docker.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 56c5a89895..18cd0e833c 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -5,6 +5,7 @@ import ( "os" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command/commands" @@ -47,16 +48,15 @@ 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) + dockerCli.Initialize(opts) } + + hideUnsupportedFeatures(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()) + if err := ccmd.Help(); err != nil { ccmd.Println(err) } @@ -123,18 +123,29 @@ func dockerPreRun(opts *cliflags.ClientOptions) { } } -func hideExperimentalFeatures(cmd *cobra.Command) { - // hide flags +func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperimental bool) { cmd.Flags().VisitAll(func(f *pflag.Flag) { + // hide experimental flags if _, ok := f.Annotations["experimental"]; ok { f.Hidden = true } + + // hide flags not supported by the server + if flagVersion, ok := f.Annotations["version"]; ok && len(flagVersion) == 1 && versions.LessThan(clientVersion, flagVersion[0]) { + f.Hidden = true + } + }) for _, subcmd := range cmd.Commands() { - // hide subcommands + // hide experimental subcommands if _, ok := subcmd.Tags["experimental"]; ok { subcmd.Hidden = true } + + // hide subcommands not supported by the server + if subcmdVersion, ok := subcmd.Tags["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) { + subcmd.Hidden = true + } } } From cc37942a6e69a4b1b67336888743a1b381754175 Mon Sep 17 00:00:00 2001 From: Wang Long Date: Fri, 11 Nov 2016 10:22:32 +0800 Subject: [PATCH 27/45] Use '.' directly Signed-off-by: Wang Long Upstream-commit: 076bfc0647de666cf04c40c506dcfe08ac90ebf3 Component: cli --- components/cli/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 18cd0e833c..1978fc33bc 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -79,7 +79,7 @@ func noArgs(cmd *cobra.Command, args []string) error { return nil } return fmt.Errorf( - "docker: '%s' is not a docker command.\nSee 'docker --help'%s", args[0], ".") + "docker: '%s' is not a docker command.\nSee 'docker --help'.", args[0]) } func main() { From c6c57b1d27c9f1cbfa9cf3842703fe21443ccf75 Mon Sep 17 00:00:00 2001 From: John Stephens Date: Fri, 11 Nov 2016 17:43:06 -0800 Subject: [PATCH 28/45] Show experimental flags and subcommands if enabled Signed-off-by: John Stephens Upstream-commit: 13d6a1bb679285ac6501125c8f0d9bcf722f13ef Component: cli --- components/cli/docker.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 1978fc33bc..02d2918f0c 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -126,8 +126,10 @@ func dockerPreRun(opts *cliflags.ClientOptions) { func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperimental bool) { cmd.Flags().VisitAll(func(f *pflag.Flag) { // hide experimental flags - if _, ok := f.Annotations["experimental"]; ok { - f.Hidden = true + if !hasExperimental { + if _, ok := f.Annotations["experimental"]; ok { + f.Hidden = true + } } // hide flags not supported by the server @@ -139,8 +141,10 @@ func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperi for _, subcmd := range cmd.Commands() { // hide experimental subcommands - if _, ok := subcmd.Tags["experimental"]; ok { - subcmd.Hidden = true + if !hasExperimental { + if _, ok := subcmd.Tags["experimental"]; ok { + subcmd.Hidden = true + } } // hide subcommands not supported by the server From cfe7cb61c6eb9c17e89944ad2d1d8a7be2eca8b2 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 16 Nov 2016 13:19:45 -0800 Subject: [PATCH 29/45] Skip cli initialization for daemon command Cli initialization pings back to remote API and creates a deadlock if socket is already being listened by systemd. Signed-off-by: Tonis Tiigi Upstream-commit: 28f8f9296320580da26628fcd2f7adf46c4a033c Component: cli --- components/cli/docker.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/cli/docker.go b/components/cli/docker.go index 02d2918f0c..e6b5048564 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -39,6 +39,10 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { return nil }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // daemon command is special, we redirect directly to another binary + if cmd.Name() == "daemon" { + return nil + } // flags must be the top-level command flags, not cmd.Flags() opts.Common.SetDefaultOptions(flags) dockerPreRun(opts) From 3164628a336a5d60e94d49e4173b9db25bc4bd8b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 17 Nov 2016 10:54:10 -0800 Subject: [PATCH 30/45] refactor help func in CLI Signed-off-by: Victor Vieux Upstream-commit: 05ddb16e5913ae92814dd21d43bd79de9496941a Component: cli --- components/cli/docker.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index e6b5048564..1e07cc8d7b 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -34,9 +34,7 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { showVersion() return nil } - cmd.SetOutput(dockerCli.Err()) - cmd.HelpFunc()(cmd, args) - return nil + return dockerCli.ShowHelp(cmd, args) }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { // daemon command is special, we redirect directly to another binary From c0c99dbe131ef694ae2329b2f76227fe15621e38 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 16 Nov 2016 16:38:28 -0800 Subject: [PATCH 31/45] error on cli when trying to use experimental feature with non experimental daemon Signed-off-by: Victor Vieux Upstream-commit: 1ab47a8be8a106dc68cbae3b2d019f9e77870a1f Component: cli --- components/cli/docker.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 1e07cc8d7b..d82c2526f4 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "os" @@ -44,19 +45,27 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { // flags must be the top-level command flags, not cmd.Flags() opts.Common.SetDefaultOptions(flags) dockerPreRun(opts) - return dockerCli.Initialize(opts) + if err := dockerCli.Initialize(opts); err != nil { + return err + } + return isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()) }, } cli.SetupRootCommand(cmd) cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) { - if dockerCli.Client() == nil { + if dockerCli.Client() == nil { // when using --help, PersistenPreRun is not called, so initialization is needed. // flags must be the top-level command flags, not cmd.Flags() opts.Common.SetDefaultOptions(flags) dockerPreRun(opts) dockerCli.Initialize(opts) } + if err := isSupported(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { + ccmd.Println(err) + return + } + hideUnsupportedFeatures(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()) if err := ccmd.Help(); err != nil { @@ -155,3 +164,17 @@ func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperi } } } + +func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) error { + if !hasExperimental { + if _, ok := cmd.Tags["experimental"]; ok { + return errors.New("only supported with experimental daemon") + } + } + + if cmdVersion, ok := cmd.Tags["version"]; ok && versions.LessThan(clientVersion, cmdVersion) { + return fmt.Errorf("only supported with daemon version >= %s", cmdVersion) + } + + return nil +} From 08da0db0bff6c4e4c795f756d724578d33da4aae Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 18 Nov 2016 15:57:11 -0800 Subject: [PATCH 32/45] fix a few golint errors Signed-off-by: Victor Vieux Upstream-commit: 40acabdfe9c937e6afce929080cf5f010cd69e87 Component: cli --- components/cli/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 1e07cc8d7b..4245f8c400 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -81,7 +81,7 @@ func noArgs(cmd *cobra.Command, args []string) error { return nil } return fmt.Errorf( - "docker: '%s' is not a docker command.\nSee 'docker --help'.", args[0]) + "docker: '%s' is not a docker command.\nSee 'docker --help'", args[0]) } func main() { From c34c8c73ffd5e28444f1542724e1c851eac7d03f Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 15 Nov 2016 11:18:33 -0500 Subject: [PATCH 33/45] exit with status 1 if help is called on an invalid command. Signed-off-by: Daniel Nephin Upstream-commit: 004fc6b9e4b5bd7cb000525aeb905f4fc8bd4aea Component: cli --- components/cli/docker_test.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/components/cli/docker_test.go b/components/cli/docker_test.go index 47e24eb0da..8738f6005d 100644 --- a/components/cli/docker_test.go +++ b/components/cli/docker_test.go @@ -1,13 +1,14 @@ package main import ( + "io/ioutil" "os" "testing" "github.com/Sirupsen/logrus" - "github.com/docker/docker/utils" - "github.com/docker/docker/cli/command" + "github.com/docker/docker/pkg/testutil/assert" + "github.com/docker/docker/utils" ) func TestClientDebugEnabled(t *testing.T) { @@ -16,14 +17,16 @@ func TestClientDebugEnabled(t *testing.T) { cmd := newDockerCommand(&command.DockerCli{}) cmd.Flags().Set("debug", "true") - if err := cmd.PersistentPreRunE(cmd, []string{}); err != nil { - t.Fatalf("Unexpected error: %s", err.Error()) - } + err := cmd.PersistentPreRunE(cmd, []string{}) + assert.NilError(t, err) + assert.Equal(t, os.Getenv("DEBUG"), "1") + assert.Equal(t, logrus.GetLevel(), logrus.DebugLevel) +} - if os.Getenv("DEBUG") != "1" { - t.Fatal("expected debug enabled, got false") - } - if logrus.GetLevel() != logrus.DebugLevel { - t.Fatalf("expected logrus debug level, got %v", logrus.GetLevel()) - } +func TestExitStatusForInvalidSubcommandWithHelpFlag(t *testing.T) { + discard := ioutil.Discard + cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard)) + cmd.SetArgs([]string{"help", "invalid"}) + err := cmd.Execute() + assert.Error(t, err, "unknown help topic: invalid") } From e670b0366835e4dd2f2bae83c593f0dd65728c56 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Mon, 12 Dec 2016 09:33:58 +0100 Subject: [PATCH 34/45] Move debug functions to cli/debug package Signed-off-by: Vincent Demeester Upstream-commit: 26c5b4b7b8eca61a447ce18e928ec84d220eca51 Component: cli --- components/cli/docker.go | 4 ++-- components/cli/docker_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index d4847a90ee..f4033738b7 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -10,11 +10,11 @@ import ( "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command/commands" + "github.com/docker/docker/cli/debug" cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/cliconfig" "github.com/docker/docker/dockerversion" "github.com/docker/docker/pkg/term" - "github.com/docker/docker/utils" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -130,7 +130,7 @@ func dockerPreRun(opts *cliflags.ClientOptions) { } if opts.Common.Debug { - utils.EnableDebug() + debug.Enable() } } diff --git a/components/cli/docker_test.go b/components/cli/docker_test.go index 8738f6005d..f8a5297ed4 100644 --- a/components/cli/docker_test.go +++ b/components/cli/docker_test.go @@ -7,12 +7,12 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/cli/command" + "github.com/docker/docker/cli/debug" "github.com/docker/docker/pkg/testutil/assert" - "github.com/docker/docker/utils" ) func TestClientDebugEnabled(t *testing.T) { - defer utils.DisableDebug() + defer debug.Disable() cmd := newDockerCommand(&command.DockerCli{}) cmd.Flags().Set("debug", "true") From aeae0b960a8cc6fd0e55942edca09646b497f4bd Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Sun, 25 Dec 2016 20:31:52 +0100 Subject: [PATCH 35/45] Move package cliconfig to cli/config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I felt it made more sence 👼 Signed-off-by: Vincent Demeester Upstream-commit: d29175b73c2628259f5442aed0a820e5686f100b Component: cli --- components/cli/docker.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index f4033738b7..685f565c8d 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -10,9 +10,9 @@ import ( "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command/commands" + cliconfig "github.com/docker/docker/cli/config" "github.com/docker/docker/cli/debug" cliflags "github.com/docker/docker/cli/flags" - "github.com/docker/docker/cliconfig" "github.com/docker/docker/dockerversion" "github.com/docker/docker/pkg/term" "github.com/spf13/cobra" @@ -75,7 +75,7 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { 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") + flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files") opts.Common.InstallFlags(flags) cmd.SetOutput(dockerCli.Out()) @@ -126,7 +126,7 @@ func dockerPreRun(opts *cliflags.ClientOptions) { cliflags.SetLogLevel(opts.Common.LogLevel) if opts.ConfigDir != "" { - cliconfig.SetConfigDir(opts.ConfigDir) + cliconfig.SetDir(opts.ConfigDir) } if opts.Common.Debug { From 1855724a2add3d3df375ccdeeda2640cd235916f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 16 Jan 2017 15:35:27 +0100 Subject: [PATCH 36/45] print error if unsupported flags are used Docker 1.13 and up allows a client to communicate with older daemons. As a result, flags may be present that are not supported by the older daemon. The client already _hides_ flags that are not supported yet, but this doesn't present users from using those flags. This change shows an error if a flag is used that is not supported by the daemon (either based on the API version, or because experimental features are not enabled). Note that for some options, a check is already in place in the API client. For those options, this is just a minor enhancement to more clearly indicate which _flag_ is not supported. Before this change; DOCKER_API_VERSION=1.24 docker run -d --stop-timeout=30 busybox top mjfyt3qpvnq0iwmun3sjwth9i echo -e "FROM busybox\nRUN echo foo > bar" | DOCKER_API_VERSION=1.24 docker build --squash - "squash" requires API version 1.25, but the Docker server is version 1.24 After this change; DOCKER_API_VERSION=1.24 docker run -d --stop-timeout=30 busybox top "--stop-timeout" requires API version 1.25, but the Docker daemon is version 1.24 echo -e "FROM busybox\nRUN echo foo > bar" | DOCKER_API_VERSION=1.24 docker build --squash - "--squash" requires API version 1.25, but the Docker daemon is version 1.24 echo -e "FROM busybox\nRUN echo foo > bar" | docker build --squash - "--squash" is only supported on a Docker daemon with experimental features enabled Signed-off-by: Sebastiaan van Stijn Upstream-commit: 0c71f3602737cf2df0d76467054835c93e284a47 Component: cli --- components/cli/docker.go | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 685f565c8d..f992b08469 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -17,6 +17,7 @@ import ( "github.com/docker/docker/pkg/term" "github.com/spf13/cobra" "github.com/spf13/pflag" + "strings" ) func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { @@ -144,7 +145,7 @@ func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperi } // hide flags not supported by the server - if flagVersion, ok := f.Annotations["version"]; ok && len(flagVersion) == 1 && versions.LessThan(clientVersion, flagVersion[0]) { + if !isFlagSupported(f, clientVersion) { f.Hidden = true } @@ -168,13 +169,44 @@ func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperi func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) error { if !hasExperimental { if _, ok := cmd.Tags["experimental"]; ok { - return errors.New("only supported with experimental daemon") + return errors.New("only supported on a Docker daemon with experimental features enabled") } } if cmdVersion, ok := cmd.Tags["version"]; ok && versions.LessThan(clientVersion, cmdVersion) { - return fmt.Errorf("only supported with daemon version >= %s", cmdVersion) + return fmt.Errorf("requires API version %s, but the Docker daemon API version is %s", cmdVersion, clientVersion) + } + + errs := []string{} + + cmd.Flags().VisitAll(func(f *pflag.Flag) { + if f.Changed { + if !isFlagSupported(f, clientVersion) { + errs = append(errs, fmt.Sprintf("\"--%s\" requires API version %s, but the Docker daemon API version is %s", f.Name, getFlagVersion(f), clientVersion)) + return + } + if _, ok := f.Annotations["experimental"]; ok && !hasExperimental { + errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon with experimental features enabled", f.Name)) + } + } + }) + if len(errs) > 0 { + return errors.New(strings.Join(errs, "\n")) } return nil } + +func getFlagVersion(f *pflag.Flag) string { + if flagVersion, ok := f.Annotations["version"]; ok && len(flagVersion) == 1 { + return flagVersion[0] + } + return "" +} + +func isFlagSupported(f *pflag.Flag, clientVersion string) bool { + if v := getFlagVersion(f); v != "" { + return versions.GreaterThanOrEqualTo(clientVersion, v) + } + return true +} From 7cd5a4ceb51cd14336426f4e6e05affd27c38c1d Mon Sep 17 00:00:00 2001 From: "Aaron.L.Xu" Date: Fri, 10 Feb 2017 15:35:05 +0800 Subject: [PATCH 37/45] review code about cmd/* and fix some easy typos :D Signed-off-by: Aaron.L.Xu Upstream-commit: 0d8fd8584279cbcfaa090da96404ef4a6f8c8993 Component: cli --- components/cli/docker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index f992b08469..654c7f41d6 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "strings" "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types/versions" @@ -17,7 +18,6 @@ import ( "github.com/docker/docker/pkg/term" "github.com/spf13/cobra" "github.com/spf13/pflag" - "strings" ) func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { @@ -55,7 +55,7 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { cli.SetupRootCommand(cmd) cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) { - if dockerCli.Client() == nil { // when using --help, PersistenPreRun is not called, so initialization is needed. + if dockerCli.Client() == nil { // when using --help, PersistentPreRun is not called, so initialization is needed. // flags must be the top-level command flags, not cmd.Flags() opts.Common.SetDefaultOptions(flags) dockerPreRun(opts) From bb163c30efce0565dbb0856e87b4b76b5dedb266 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sun, 20 Nov 2016 09:57:06 -0800 Subject: [PATCH 38/45] Improve error handling of experimental features in non-experimental mode This fix tries to address several issues raised in 28626 where run against a non-experimental daemon may not generate correct error message: 1. Incorrect flags were not checked against the supported features: ``` $ docker stack --nonsense unknown flag: --nonsense ``` 2. Subcommands were not checked against the supported features: ``` $ docker stack ls Error response from daemon: This node is not a swarm manager... ``` This fix address the above mentioned issues by: 1. Add a pre-check for FlagErrorFunc 2. Recursively check if a feature is supported for cmd and its parents. This fix fixes 28626. Signed-off-by: Yong Tang Upstream-commit: 8e688f17a35a01023b58b0da56b917a7d4345c38 Component: cli --- components/cli/docker.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 654c7f41d6..44e6529b82 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -53,6 +53,23 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { }, } cli.SetupRootCommand(cmd) + // When invoking `docker stack --nonsense`, we need to make sure FlagErrorFunc return appropriate + // output if the feature is not supported. + // As above cli.SetupRootCommand(cmd) have already setup the FlagErrorFunc, we will add a pre-check before the FlagErrorFunc + // is called. + flagErrorFunc := cmd.FlagErrorFunc() + cmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { + if dockerCli.Client() == nil { // when using --help, PersistenPreRun is not called, so initialization is needed. + // flags must be the top-level command flags, not cmd.Flags() + opts.Common.SetDefaultOptions(flags) + dockerPreRun(opts) + dockerCli.Initialize(opts) + } + if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { + return err + } + return flagErrorFunc(cmd, err) + }) cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) { if dockerCli.Client() == nil { // when using --help, PersistentPreRun is not called, so initialization is needed. @@ -167,9 +184,12 @@ func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperi } func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) error { + // We check recursively so that, e.g., `docker stack ls` will return the same output as `docker stack` if !hasExperimental { - if _, ok := cmd.Tags["experimental"]; ok { - return errors.New("only supported on a Docker daemon with experimental features enabled") + for curr := cmd; curr != nil; curr = curr.Parent() { + if _, ok := curr.Tags["experimental"]; ok { + return errors.New("only supported on a Docker daemon with experimental features enabled") + } } } From 0ea3cc9974402086d6f0b1a36945a40c141dad85 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Mon, 21 Nov 2016 14:34:55 -0800 Subject: [PATCH 39/45] Additional experimental features in non-experimental mode error handling This fix is the follow up of the last commit. In this fix: 1. If any of the parents of a command has tags, then this command's `Args` (Args validation func) will be wrapped up. The warpped up func will check to see if the feature is supported or not. If it is not supported, then a not supported message is generated instead. This fix is related to 28626. Signed-off-by: Yong Tang Upstream-commit: d5010088e37f577f0b476b927fc8550150d107a0 Component: cli --- components/cli/docker.go | 99 +++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 21 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 44e6529b82..efc1cac25e 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -53,32 +53,43 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { }, } cli.SetupRootCommand(cmd) + + flags = cmd.Flags() + flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit") + flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files") + opts.Common.InstallFlags(flags) + + setFlagErrorFunc(dockerCli, cmd, flags, opts) + + setHelpFunc(dockerCli, cmd, flags, opts) + + cmd.SetOutput(dockerCli.Out()) + cmd.AddCommand(newDaemonCommand()) + commands.AddCommands(cmd, dockerCli) + + setValidateArgs(dockerCli, cmd, flags, opts) + + return cmd +} + +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. // As above cli.SetupRootCommand(cmd) have already setup the FlagErrorFunc, we will add a pre-check before the FlagErrorFunc // is called. flagErrorFunc := cmd.FlagErrorFunc() cmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { - if dockerCli.Client() == nil { // when using --help, PersistenPreRun is not called, so initialization is needed. - // flags must be the top-level command flags, not cmd.Flags() - opts.Common.SetDefaultOptions(flags) - dockerPreRun(opts) - dockerCli.Initialize(opts) - } + initializeDockerCli(dockerCli, flags, opts) if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { return err } return flagErrorFunc(cmd, err) }) +} +func setHelpFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) { cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) { - if dockerCli.Client() == nil { // when using --help, PersistentPreRun is not called, so initialization is needed. - // flags must be the top-level command flags, not cmd.Flags() - opts.Common.SetDefaultOptions(flags) - dockerPreRun(opts) - dockerCli.Initialize(opts) - } - + initializeDockerCli(dockerCli, flags, opts) if err := isSupported(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { ccmd.Println(err) return @@ -90,17 +101,52 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { ccmd.Println(err) } }) +} - flags = cmd.Flags() - flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit") - flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files") - opts.Common.InstallFlags(flags) +func setValidateArgs(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) { + // The Args is handled by ValidateArgs in cobra, which does not allows a pre-hook. + // As a result, here we replace the existing Args validation func to a wrapper, + // where the wrapper will check to see if the feature is supported or not. + // The Args validation error will only be returned if the feature is supported. + visitAll(cmd, func(ccmd *cobra.Command) { + // if there is no tags for a command or any of its parent, + // there is no need to wrap the Args validation. + if !hasTags(ccmd) { + return + } - cmd.SetOutput(dockerCli.Out()) - cmd.AddCommand(newDaemonCommand()) - commands.AddCommands(cmd, dockerCli) + if ccmd.Args == nil { + return + } - return cmd + cmdArgs := ccmd.Args + ccmd.Args = func(cmd *cobra.Command, args []string) error { + initializeDockerCli(dockerCli, flags, opts) + if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { + return err + } + return cmdArgs(cmd, args) + } + }) +} + +func initializeDockerCli(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *cliflags.ClientOptions) { + if dockerCli.Client() == nil { // when using --help, PersistentPreRun is not called, so initialization is needed. + // flags must be the top-level command flags, not cmd.Flags() + opts.Common.SetDefaultOptions(flags) + dockerPreRun(opts) + dockerCli.Initialize(opts) + } +} + +// 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) } func noArgs(cmd *cobra.Command, args []string) error { @@ -230,3 +276,14 @@ func isFlagSupported(f *pflag.Flag, clientVersion string) bool { } return true } + +// hasTags return true if any of the command's parents has tags +func hasTags(cmd *cobra.Command) bool { + for curr := cmd; curr != nil; curr = curr.Parent() { + if len(curr.Tags) > 0 { + return true + } + } + + return false +} From d1cc836571347ac93e37d48e6a1c76dd8ae16625 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Sun, 5 Mar 2017 15:25:11 +1100 Subject: [PATCH 40/45] cmd: docker: fix TestDaemonCommand In more recent versions of Cobra, `--help` parsing is done before anything else resulting in TestDaemonCommand not actually passing. I'm actually unsure if this test ever passed since it appears that !daemon is not being run as part of the test suite. Signed-off-by: Aleksa Sarai Upstream-commit: aa74f278667c29752d71071503098cf71988c054 Component: cli --- components/cli/daemon_none.go | 6 ++++-- components/cli/daemon_none_test.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/cli/daemon_none.go b/components/cli/daemon_none.go index 65f9f37be2..6fbd000125 100644 --- a/components/cli/daemon_none.go +++ b/components/cli/daemon_none.go @@ -12,8 +12,10 @@ import ( func newDaemonCommand() *cobra.Command { return &cobra.Command{ - Use: "daemon", - Hidden: true, + Use: "daemon", + Hidden: true, + Args: cobra.ArbitraryArgs, + DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { return runDaemon() }, diff --git a/components/cli/daemon_none_test.go b/components/cli/daemon_none_test.go index 32032fe1b3..bd42add986 100644 --- a/components/cli/daemon_none_test.go +++ b/components/cli/daemon_none_test.go @@ -10,7 +10,7 @@ import ( func TestDaemonCommand(t *testing.T) { cmd := newDaemonCommand() - cmd.SetArgs([]string{"--help"}) + cmd.SetArgs([]string{"--version"}) err := cmd.Execute() assert.Error(t, err, "Please run `dockerd`") From 1efe06c1d37f8f42823450fcfa02d1255c8d739e Mon Sep 17 00:00:00 2001 From: Boaz Shuster Date: Tue, 7 Feb 2017 14:52:20 +0200 Subject: [PATCH 41/45] Hide command options that are related to Windows Signed-off-by: Boaz Shuster Upstream-commit: e398a784660725e47c68f7ae30e52de4ee6f330d Component: cli --- components/cli/docker.go | 42 +++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index efc1cac25e..570a52a72e 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -49,7 +49,7 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { if err := dockerCli.Initialize(opts); err != nil { return err } - return isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()) + return isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()) }, } cli.SetupRootCommand(cmd) @@ -80,7 +80,7 @@ func setFlagErrorFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *p flagErrorFunc := cmd.FlagErrorFunc() cmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { initializeDockerCli(dockerCli, flags, opts) - if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { + if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()); err != nil { return err } return flagErrorFunc(cmd, err) @@ -90,12 +90,12 @@ func setFlagErrorFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *p func setHelpFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) { cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) { initializeDockerCli(dockerCli, flags, opts) - if err := isSupported(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { + if err := isSupported(ccmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()); err != nil { ccmd.Println(err) return } - hideUnsupportedFeatures(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()) + hideUnsupportedFeatures(ccmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()) if err := ccmd.Help(); err != nil { ccmd.Println(err) @@ -122,7 +122,7 @@ func setValidateArgs(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pf cmdArgs := ccmd.Args ccmd.Args = func(cmd *cobra.Command, args []string) error { initializeDockerCli(dockerCli, flags, opts) - if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { + if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()); err != nil { return err } return cmdArgs(cmd, args) @@ -198,7 +198,7 @@ func dockerPreRun(opts *cliflags.ClientOptions) { } } -func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperimental bool) { +func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion, osType string, hasExperimental bool) { cmd.Flags().VisitAll(func(f *pflag.Flag) { // hide experimental flags if !hasExperimental { @@ -208,10 +208,9 @@ func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperi } // hide flags not supported by the server - if !isFlagSupported(f, clientVersion) { + if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) { f.Hidden = true } - }) for _, subcmd := range cmd.Commands() { @@ -229,7 +228,7 @@ func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperi } } -func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) error { +func isSupported(cmd *cobra.Command, clientVersion, osType string, hasExperimental bool) error { // We check recursively so that, e.g., `docker stack ls` will return the same output as `docker stack` if !hasExperimental { for curr := cmd; curr != nil; curr = curr.Parent() { @@ -247,8 +246,12 @@ func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) cmd.Flags().VisitAll(func(f *pflag.Flag) { if f.Changed { - if !isFlagSupported(f, clientVersion) { - errs = append(errs, fmt.Sprintf("\"--%s\" requires API version %s, but the Docker daemon API version is %s", f.Name, getFlagVersion(f), clientVersion)) + if !isVersionSupported(f, clientVersion) { + errs = append(errs, fmt.Sprintf("\"--%s\" requires API version %s, but the Docker daemon API version is %s", f.Name, getFlagAnnotation(f, "version"), clientVersion)) + return + } + if !isOSTypeSupported(f, osType) { + errs = append(errs, fmt.Sprintf("\"--%s\" requires the Docker daemon to run on %s, but the Docker daemon is running on %s", f.Name, getFlagAnnotation(f, "ostype"), osType)) return } if _, ok := f.Annotations["experimental"]; ok && !hasExperimental { @@ -263,20 +266,27 @@ func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) return nil } -func getFlagVersion(f *pflag.Flag) string { - if flagVersion, ok := f.Annotations["version"]; ok && len(flagVersion) == 1 { - return flagVersion[0] +func getFlagAnnotation(f *pflag.Flag, annotation string) string { + if value, ok := f.Annotations[annotation]; ok && len(value) == 1 { + return value[0] } return "" } -func isFlagSupported(f *pflag.Flag, clientVersion string) bool { - if v := getFlagVersion(f); v != "" { +func isVersionSupported(f *pflag.Flag, clientVersion string) bool { + if v := getFlagAnnotation(f, "version"); v != "" { return versions.GreaterThanOrEqualTo(clientVersion, v) } return true } +func isOSTypeSupported(f *pflag.Flag, osType string) bool { + if v := getFlagAnnotation(f, "ostype"); v != "" && osType != "" { + return osType == v + } + return true +} + // hasTags return true if any of the command's parents has tags func hasTags(cmd *cobra.Command) bool { for curr := cmd; curr != nil; curr = curr.Parent() { From a85098e5f06779f52bbceb70998da427b707fd16 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 3 Mar 2017 13:58:50 +0100 Subject: [PATCH 42/45] Improve error handling of commands run against unsupported daemon The current error-handling only checked for version annotations on the subcommand itself, but did not check the top-level command. This patch always traverses the command path (parents), and prints an error if the command is not supported. Before this change: $ docker service Usage: docker service COMMAND Manage services Options: --help Print usage Commands: create Create a new service inspect Display detailed information on one or more services ls List services ps List the tasks of one or more services rm Remove one or more services scale Scale one or multiple replicated services update Update a service Run 'docker service COMMAND --help' for more information on a command. $ docker service ls ID NAME MODE REPLICAS IMAGE After this change: $ DOCKER_API_VERSION=1.12 docker service docker service requires API version 1.24, but the Docker daemon API version is 1.12 $ DOCKER_API_VERSION=1.12 docker service ls docker service ls requires API version 1.24, but the Docker daemon API version is 1.12 $ DOCKER_API_VERSION=1.24 docker plugin --help docker plugin requires API version 1.25, but the Docker daemon API version is 1.24 $ DOCKER_API_VERSION=1.25 docker plugin upgrade --help docker plugin upgrade requires API version 1.26, but the Docker daemon API version is 1.25 Signed-off-by: Sebastiaan van Stijn Upstream-commit: 998950a9f4a4afc10191147b2d108215de9353fe Component: cli --- components/cli/docker.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 570a52a72e..8d589d4416 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -229,17 +229,14 @@ func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion, osType string, h } func isSupported(cmd *cobra.Command, clientVersion, osType string, hasExperimental bool) error { - // We check recursively so that, e.g., `docker stack ls` will return the same output as `docker stack` - if !hasExperimental { - for curr := cmd; curr != nil; curr = curr.Parent() { - if _, ok := curr.Tags["experimental"]; ok { - return errors.New("only supported on a Docker daemon with experimental features enabled") - } + // Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack` + for curr := cmd; curr != nil; curr = curr.Parent() { + if cmdVersion, ok := curr.Tags["version"]; ok && versions.LessThan(clientVersion, cmdVersion) { + return fmt.Errorf("%s requires API version %s, but the Docker daemon API version is %s", cmd.CommandPath(), cmdVersion, clientVersion) + } + if _, ok := curr.Tags["experimental"]; ok && !hasExperimental { + return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath()) } - } - - if cmdVersion, ok := cmd.Tags["version"]; ok && versions.LessThan(clientVersion, cmdVersion) { - return fmt.Errorf("requires API version %s, but the Docker daemon API version is %s", cmdVersion, clientVersion) } errs := []string{} From 1373ab09883bdb274f69deaf7f5981c8a7ac5d22 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 14 Mar 2017 17:53:29 -0400 Subject: [PATCH 43/45] Create a new ServerType struct for storing details about the server on the client. Signed-off-by: Daniel Nephin Upstream-commit: 749d8b2bdc87cff53013512245ccdfd9651749b6 Component: cli --- components/cli/docker.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/components/cli/docker.go b/components/cli/docker.go index 8d589d4416..96283debc4 100644 --- a/components/cli/docker.go +++ b/components/cli/docker.go @@ -14,6 +14,7 @@ import ( cliconfig "github.com/docker/docker/cli/config" "github.com/docker/docker/cli/debug" cliflags "github.com/docker/docker/cli/flags" + "github.com/docker/docker/client" "github.com/docker/docker/dockerversion" "github.com/docker/docker/pkg/term" "github.com/spf13/cobra" @@ -49,7 +50,7 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { if err := dockerCli.Initialize(opts); err != nil { return err } - return isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()) + return isSupported(cmd, dockerCli) }, } cli.SetupRootCommand(cmd) @@ -80,7 +81,7 @@ func setFlagErrorFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *p flagErrorFunc := cmd.FlagErrorFunc() cmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { initializeDockerCli(dockerCli, flags, opts) - if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()); err != nil { + if err := isSupported(cmd, dockerCli); err != nil { return err } return flagErrorFunc(cmd, err) @@ -90,12 +91,12 @@ func setFlagErrorFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *p func setHelpFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) { cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) { initializeDockerCli(dockerCli, flags, opts) - if err := isSupported(ccmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()); err != nil { + if err := isSupported(ccmd, dockerCli); err != nil { ccmd.Println(err) return } - hideUnsupportedFeatures(ccmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()) + hideUnsupportedFeatures(ccmd, dockerCli) if err := ccmd.Help(); err != nil { ccmd.Println(err) @@ -122,7 +123,7 @@ func setValidateArgs(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pf cmdArgs := ccmd.Args ccmd.Args = func(cmd *cobra.Command, args []string) error { initializeDockerCli(dockerCli, flags, opts) - if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.OSType(), dockerCli.HasExperimental()); err != nil { + if err := isSupported(cmd, dockerCli); err != nil { return err } return cmdArgs(cmd, args) @@ -198,7 +199,16 @@ func dockerPreRun(opts *cliflags.ClientOptions) { } } -func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion, osType string, hasExperimental bool) { +type versionDetails interface { + Client() client.APIClient + ServerInfo() command.ServerInfo +} + +func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) { + clientVersion := details.Client().ClientVersion() + osType := details.ServerInfo().OSType + hasExperimental := details.ServerInfo().HasExperimental + cmd.Flags().VisitAll(func(f *pflag.Flag) { // hide experimental flags if !hasExperimental { @@ -228,7 +238,11 @@ func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion, osType string, h } } -func isSupported(cmd *cobra.Command, clientVersion, osType string, hasExperimental bool) error { +func isSupported(cmd *cobra.Command, details versionDetails) error { + clientVersion := details.Client().ClientVersion() + osType := details.ServerInfo().OSType + hasExperimental := details.ServerInfo().HasExperimental + // Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack` for curr := cmd; curr != nil; curr = curr.Parent() { if cmdVersion, ok := curr.Tags["version"]; ok && versions.LessThan(clientVersion, cmdVersion) { From f91e9b960badf5065374724161d1259436977d13 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 22 Mar 2017 00:21:15 +0100 Subject: [PATCH 44/45] update "docker daemon" deprecation message for new version scheme Signed-off-by: Sebastiaan van Stijn Upstream-commit: b83bf0a4fd0a1cb42cecf5361eb0f8d393310f11 Component: cli --- components/cli/daemon_unix.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cli/daemon_unix.go b/components/cli/daemon_unix.go index f68d220c2f..6ec6b625a1 100644 --- a/components/cli/daemon_unix.go +++ b/components/cli/daemon_unix.go @@ -24,7 +24,7 @@ func newDaemonCommand() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runDaemon() }, - Deprecated: "and will be removed in Docker 1.16. Please run `dockerd` directly.", + Deprecated: "and will be removed in Docker 17.12. Please run `dockerd` directly.", } cmd.SetHelpFunc(helpFunc) return cmd From 9c185ed98aecbb05cdae7c611137d69213e00ad7 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Thu, 13 Apr 2017 15:45:37 -0700 Subject: [PATCH 45/45] Remove pkg/testutil/assert in favor of testify I noticed that we're using a homegrown package for assertions. The functions are extremely similar to testify, but with enough slight differences to be confusing (for example, Equal takes its arguments in a different order). We already vendor testify, and it's used in a few places by tests. I also found some problems with pkg/testutil/assert. For example, the NotNil function seems to be broken. It checks the argument against "nil", which only works for an interface. If you pass in a nil map or slice, the equality check will fail. In the interest of avoiding NIH, I'm proposing replacing pkg/testutil/assert with testify. The test code looks almost the same, but we avoid the confusion of having two similar but slightly different assertion packages, and having to maintain our own package instead of using a commonly-used one. In the process, I found a few places where the tests should halt if an assertion fails, so I've made those cases (that I noticed) use "require" instead of "assert", and I've vendored the "require" package from testify alongside the already-present "assert" package. Signed-off-by: Aaron Lehmann Upstream-commit: 6029def4cb2c003d08666924c7af21fae4719d40 Component: cli --- components/cli/daemon_none_test.go | 4 ++-- components/cli/daemon_unit_test.go | 6 +++--- components/cli/docker_test.go | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/components/cli/daemon_none_test.go b/components/cli/daemon_none_test.go index bd42add986..af0fcfd670 100644 --- a/components/cli/daemon_none_test.go +++ b/components/cli/daemon_none_test.go @@ -5,7 +5,7 @@ package main import ( "testing" - "github.com/docker/docker/pkg/testutil/assert" + "github.com/stretchr/testify/assert" ) func TestDaemonCommand(t *testing.T) { @@ -13,5 +13,5 @@ func TestDaemonCommand(t *testing.T) { cmd.SetArgs([]string{"--version"}) err := cmd.Execute() - assert.Error(t, err, "Please run `dockerd`") + assert.EqualError(t, err, "Please run `dockerd`") } diff --git a/components/cli/daemon_unit_test.go b/components/cli/daemon_unit_test.go index 26348a8843..ffd8a5e2f5 100644 --- a/components/cli/daemon_unit_test.go +++ b/components/cli/daemon_unit_test.go @@ -5,8 +5,8 @@ package main import ( "testing" - "github.com/docker/docker/pkg/testutil/assert" "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" ) func stubRun(cmd *cobra.Command, args []string) error { @@ -18,7 +18,7 @@ func TestDaemonCommandHelp(t *testing.T) { cmd.RunE = stubRun cmd.SetArgs([]string{"--help"}) err := cmd.Execute() - assert.NilError(t, err) + assert.NoError(t, err) } func TestDaemonCommand(t *testing.T) { @@ -26,5 +26,5 @@ func TestDaemonCommand(t *testing.T) { cmd.RunE = stubRun cmd.SetArgs([]string{"--containerd", "/foo"}) err := cmd.Execute() - assert.NilError(t, err) + assert.NoError(t, err) } diff --git a/components/cli/docker_test.go b/components/cli/docker_test.go index f8a5297ed4..88afb41e9e 100644 --- a/components/cli/docker_test.go +++ b/components/cli/docker_test.go @@ -8,7 +8,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/cli/command" "github.com/docker/docker/cli/debug" - "github.com/docker/docker/pkg/testutil/assert" + "github.com/stretchr/testify/assert" ) func TestClientDebugEnabled(t *testing.T) { @@ -18,9 +18,9 @@ func TestClientDebugEnabled(t *testing.T) { cmd.Flags().Set("debug", "true") err := cmd.PersistentPreRunE(cmd, []string{}) - assert.NilError(t, err) - assert.Equal(t, os.Getenv("DEBUG"), "1") - assert.Equal(t, logrus.GetLevel(), logrus.DebugLevel) + assert.NoError(t, err) + assert.Equal(t, "1", os.Getenv("DEBUG")) + assert.Equal(t, logrus.DebugLevel, logrus.GetLevel()) } func TestExitStatusForInvalidSubcommandWithHelpFlag(t *testing.T) { @@ -28,5 +28,5 @@ func TestExitStatusForInvalidSubcommandWithHelpFlag(t *testing.T) { cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard)) cmd.SetArgs([]string{"help", "invalid"}) err := cmd.Execute() - assert.Error(t, err, "unknown help topic: invalid") + assert.EqualError(t, err, "unknown help topic: invalid") }