docker.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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. cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) {
  53. if dockerCli.Client() == nil { // when using --help, PersistentPreRun is not called, so initialization is needed.
  54. // flags must be the top-level command flags, not cmd.Flags()
  55. opts.Common.SetDefaultOptions(flags)
  56. dockerPreRun(opts)
  57. dockerCli.Initialize(opts)
  58. }
  59. if err := isSupported(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil {
  60. ccmd.Println(err)
  61. return
  62. }
  63. hideUnsupportedFeatures(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental())
  64. if err := ccmd.Help(); err != nil {
  65. ccmd.Println(err)
  66. }
  67. })
  68. flags = cmd.Flags()
  69. flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit")
  70. flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files")
  71. opts.Common.InstallFlags(flags)
  72. cmd.SetOutput(dockerCli.Out())
  73. cmd.AddCommand(newDaemonCommand())
  74. commands.AddCommands(cmd, dockerCli)
  75. return cmd
  76. }
  77. func noArgs(cmd *cobra.Command, args []string) error {
  78. if len(args) == 0 {
  79. return nil
  80. }
  81. return fmt.Errorf(
  82. "docker: '%s' is not a docker command.\nSee 'docker --help'", args[0])
  83. }
  84. func main() {
  85. // Set terminal emulation based on platform as required.
  86. stdin, stdout, stderr := term.StdStreams()
  87. logrus.SetOutput(stderr)
  88. dockerCli := command.NewDockerCli(stdin, stdout, stderr)
  89. cmd := newDockerCommand(dockerCli)
  90. if err := cmd.Execute(); err != nil {
  91. if sterr, ok := err.(cli.StatusError); ok {
  92. if sterr.Status != "" {
  93. fmt.Fprintln(stderr, sterr.Status)
  94. }
  95. // StatusError should only be used for errors, and all errors should
  96. // have a non-zero exit status, so never exit with 0
  97. if sterr.StatusCode == 0 {
  98. os.Exit(1)
  99. }
  100. os.Exit(sterr.StatusCode)
  101. }
  102. fmt.Fprintln(stderr, err)
  103. os.Exit(1)
  104. }
  105. }
  106. func showVersion() {
  107. fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit)
  108. }
  109. func dockerPreRun(opts *cliflags.ClientOptions) {
  110. cliflags.SetLogLevel(opts.Common.LogLevel)
  111. if opts.ConfigDir != "" {
  112. cliconfig.SetDir(opts.ConfigDir)
  113. }
  114. if opts.Common.Debug {
  115. debug.Enable()
  116. }
  117. }
  118. func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperimental bool) {
  119. cmd.Flags().VisitAll(func(f *pflag.Flag) {
  120. // hide experimental flags
  121. if !hasExperimental {
  122. if _, ok := f.Annotations["experimental"]; ok {
  123. f.Hidden = true
  124. }
  125. }
  126. // hide flags not supported by the server
  127. if !isFlagSupported(f, clientVersion) {
  128. f.Hidden = true
  129. }
  130. })
  131. for _, subcmd := range cmd.Commands() {
  132. // hide experimental subcommands
  133. if !hasExperimental {
  134. if _, ok := subcmd.Tags["experimental"]; ok {
  135. subcmd.Hidden = true
  136. }
  137. }
  138. // hide subcommands not supported by the server
  139. if subcmdVersion, ok := subcmd.Tags["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
  140. subcmd.Hidden = true
  141. }
  142. }
  143. }
  144. func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) error {
  145. if !hasExperimental {
  146. if _, ok := cmd.Tags["experimental"]; ok {
  147. return errors.New("only supported on a Docker daemon with experimental features enabled")
  148. }
  149. }
  150. if cmdVersion, ok := cmd.Tags["version"]; ok && versions.LessThan(clientVersion, cmdVersion) {
  151. return fmt.Errorf("requires API version %s, but the Docker daemon API version is %s", cmdVersion, clientVersion)
  152. }
  153. errs := []string{}
  154. cmd.Flags().VisitAll(func(f *pflag.Flag) {
  155. if f.Changed {
  156. if !isFlagSupported(f, clientVersion) {
  157. errs = append(errs, fmt.Sprintf("\"--%s\" requires API version %s, but the Docker daemon API version is %s", f.Name, getFlagVersion(f), clientVersion))
  158. return
  159. }
  160. if _, ok := f.Annotations["experimental"]; ok && !hasExperimental {
  161. errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon with experimental features enabled", f.Name))
  162. }
  163. }
  164. })
  165. if len(errs) > 0 {
  166. return errors.New(strings.Join(errs, "\n"))
  167. }
  168. return nil
  169. }
  170. func getFlagVersion(f *pflag.Flag) string {
  171. if flagVersion, ok := f.Annotations["version"]; ok && len(flagVersion) == 1 {
  172. return flagVersion[0]
  173. }
  174. return ""
  175. }
  176. func isFlagSupported(f *pflag.Flag, clientVersion string) bool {
  177. if v := getFlagVersion(f); v != "" {
  178. return versions.GreaterThanOrEqualTo(clientVersion, v)
  179. }
  180. return true
  181. }