docker.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "os"
  6. "strings"
  7. "github.com/Sirupsen/logrus"
  8. "github.com/docker/docker/api/types/versions"
  9. "github.com/docker/docker/cli"
  10. "github.com/docker/docker/cli/command"
  11. "github.com/docker/docker/cli/command/commands"
  12. cliconfig "github.com/docker/docker/cli/config"
  13. "github.com/docker/docker/cli/debug"
  14. cliflags "github.com/docker/docker/cli/flags"
  15. "github.com/docker/docker/dockerversion"
  16. "github.com/docker/docker/pkg/term"
  17. "github.com/spf13/cobra"
  18. "github.com/spf13/pflag"
  19. )
  20. func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
  21. opts := cliflags.NewClientOptions()
  22. var flags *pflag.FlagSet
  23. cmd := &cobra.Command{
  24. Use: "docker [OPTIONS] COMMAND [ARG...]",
  25. Short: "A self-sufficient runtime for containers",
  26. SilenceUsage: true,
  27. SilenceErrors: true,
  28. TraverseChildren: true,
  29. Args: noArgs,
  30. RunE: func(cmd *cobra.Command, args []string) error {
  31. if opts.Version {
  32. showVersion()
  33. return nil
  34. }
  35. return dockerCli.ShowHelp(cmd, args)
  36. },
  37. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  38. // daemon command is special, we redirect directly to another binary
  39. if cmd.Name() == "daemon" {
  40. return nil
  41. }
  42. // flags must be the top-level command flags, not cmd.Flags()
  43. opts.Common.SetDefaultOptions(flags)
  44. dockerPreRun(opts)
  45. if err := dockerCli.Initialize(opts); err != nil {
  46. return err
  47. }
  48. return isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental())
  49. },
  50. }
  51. cli.SetupRootCommand(cmd)
  52. // When invoking `docker stack --nonsense`, we need to make sure FlagErrorFunc return appropriate
  53. // output if the feature is not supported.
  54. // As above cli.SetupRootCommand(cmd) have already setup the FlagErrorFunc, we will add a pre-check before the FlagErrorFunc
  55. // is called.
  56. flagErrorFunc := cmd.FlagErrorFunc()
  57. cmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
  58. if dockerCli.Client() == nil { // when using --help, PersistenPreRun is not called, so initialization is needed.
  59. // flags must be the top-level command flags, not cmd.Flags()
  60. opts.Common.SetDefaultOptions(flags)
  61. dockerPreRun(opts)
  62. dockerCli.Initialize(opts)
  63. }
  64. if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil {
  65. return err
  66. }
  67. return flagErrorFunc(cmd, err)
  68. })
  69. cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) {
  70. if dockerCli.Client() == nil { // when using --help, PersistentPreRun is not called, so initialization is needed.
  71. // flags must be the top-level command flags, not cmd.Flags()
  72. opts.Common.SetDefaultOptions(flags)
  73. dockerPreRun(opts)
  74. dockerCli.Initialize(opts)
  75. }
  76. if err := isSupported(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil {
  77. ccmd.Println(err)
  78. return
  79. }
  80. hideUnsupportedFeatures(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental())
  81. if err := ccmd.Help(); err != nil {
  82. ccmd.Println(err)
  83. }
  84. })
  85. flags = cmd.Flags()
  86. flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit")
  87. flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files")
  88. opts.Common.InstallFlags(flags)
  89. cmd.SetOutput(dockerCli.Out())
  90. cmd.AddCommand(newDaemonCommand())
  91. commands.AddCommands(cmd, dockerCli)
  92. return cmd
  93. }
  94. func noArgs(cmd *cobra.Command, args []string) error {
  95. if len(args) == 0 {
  96. return nil
  97. }
  98. return fmt.Errorf(
  99. "docker: '%s' is not a docker command.\nSee 'docker --help'", args[0])
  100. }
  101. func main() {
  102. // Set terminal emulation based on platform as required.
  103. stdin, stdout, stderr := term.StdStreams()
  104. logrus.SetOutput(stderr)
  105. dockerCli := command.NewDockerCli(stdin, stdout, stderr)
  106. cmd := newDockerCommand(dockerCli)
  107. if err := cmd.Execute(); err != nil {
  108. if sterr, ok := err.(cli.StatusError); ok {
  109. if sterr.Status != "" {
  110. fmt.Fprintln(stderr, sterr.Status)
  111. }
  112. // StatusError should only be used for errors, and all errors should
  113. // have a non-zero exit status, so never exit with 0
  114. if sterr.StatusCode == 0 {
  115. os.Exit(1)
  116. }
  117. os.Exit(sterr.StatusCode)
  118. }
  119. fmt.Fprintln(stderr, err)
  120. os.Exit(1)
  121. }
  122. }
  123. func showVersion() {
  124. fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit)
  125. }
  126. func dockerPreRun(opts *cliflags.ClientOptions) {
  127. cliflags.SetLogLevel(opts.Common.LogLevel)
  128. if opts.ConfigDir != "" {
  129. cliconfig.SetDir(opts.ConfigDir)
  130. }
  131. if opts.Common.Debug {
  132. debug.Enable()
  133. }
  134. }
  135. func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperimental bool) {
  136. cmd.Flags().VisitAll(func(f *pflag.Flag) {
  137. // hide experimental flags
  138. if !hasExperimental {
  139. if _, ok := f.Annotations["experimental"]; ok {
  140. f.Hidden = true
  141. }
  142. }
  143. // hide flags not supported by the server
  144. if !isFlagSupported(f, clientVersion) {
  145. f.Hidden = true
  146. }
  147. })
  148. for _, subcmd := range cmd.Commands() {
  149. // hide experimental subcommands
  150. if !hasExperimental {
  151. if _, ok := subcmd.Tags["experimental"]; ok {
  152. subcmd.Hidden = true
  153. }
  154. }
  155. // hide subcommands not supported by the server
  156. if subcmdVersion, ok := subcmd.Tags["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
  157. subcmd.Hidden = true
  158. }
  159. }
  160. }
  161. func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) error {
  162. // We check recursively so that, e.g., `docker stack ls` will return the same output as `docker stack`
  163. if !hasExperimental {
  164. for curr := cmd; curr != nil; curr = curr.Parent() {
  165. if _, ok := curr.Tags["experimental"]; ok {
  166. return errors.New("only supported on a Docker daemon with experimental features enabled")
  167. }
  168. }
  169. }
  170. if cmdVersion, ok := cmd.Tags["version"]; ok && versions.LessThan(clientVersion, cmdVersion) {
  171. return fmt.Errorf("requires API version %s, but the Docker daemon API version is %s", cmdVersion, clientVersion)
  172. }
  173. errs := []string{}
  174. cmd.Flags().VisitAll(func(f *pflag.Flag) {
  175. if f.Changed {
  176. if !isFlagSupported(f, clientVersion) {
  177. errs = append(errs, fmt.Sprintf("\"--%s\" requires API version %s, but the Docker daemon API version is %s", f.Name, getFlagVersion(f), clientVersion))
  178. return
  179. }
  180. if _, ok := f.Annotations["experimental"]; ok && !hasExperimental {
  181. errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon with experimental features enabled", f.Name))
  182. }
  183. }
  184. })
  185. if len(errs) > 0 {
  186. return errors.New(strings.Join(errs, "\n"))
  187. }
  188. return nil
  189. }
  190. func getFlagVersion(f *pflag.Flag) string {
  191. if flagVersion, ok := f.Annotations["version"]; ok && len(flagVersion) == 1 {
  192. return flagVersion[0]
  193. }
  194. return ""
  195. }
  196. func isFlagSupported(f *pflag.Flag, clientVersion string) bool {
  197. if v := getFlagVersion(f); v != "" {
  198. return versions.GreaterThanOrEqualTo(clientVersion, v)
  199. }
  200. return true
  201. }