github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/cmd/docker/docker.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "strings" 8 9 "github.com/Sirupsen/logrus" 10 "github.com/docker/docker/api/types/versions" 11 "github.com/docker/docker/cli" 12 "github.com/docker/docker/cli/command" 13 "github.com/docker/docker/cli/command/commands" 14 cliconfig "github.com/docker/docker/cli/config" 15 "github.com/docker/docker/cli/debug" 16 cliflags "github.com/docker/docker/cli/flags" 17 "github.com/docker/docker/dockerversion" 18 "github.com/docker/docker/pkg/term" 19 "github.com/spf13/cobra" 20 "github.com/spf13/pflag" 21 ) 22 23 func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { 24 opts := cliflags.NewClientOptions() 25 var flags *pflag.FlagSet 26 27 cmd := &cobra.Command{ 28 Use: "docker [OPTIONS] COMMAND [ARG...]", 29 Short: "A self-sufficient runtime for containers", 30 SilenceUsage: true, 31 SilenceErrors: true, 32 TraverseChildren: true, 33 Args: noArgs, 34 RunE: func(cmd *cobra.Command, args []string) error { 35 if opts.Version { 36 showVersion() 37 return nil 38 } 39 return dockerCli.ShowHelp(cmd, args) 40 }, 41 PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 42 // daemon command is special, we redirect directly to another binary 43 if cmd.Name() == "daemon" { 44 return nil 45 } 46 // flags must be the top-level command flags, not cmd.Flags() 47 opts.Common.SetDefaultOptions(flags) 48 dockerPreRun(opts) 49 if err := dockerCli.Initialize(opts); err != nil { 50 return err 51 } 52 return isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()) 53 }, 54 } 55 cli.SetupRootCommand(cmd) 56 57 flags = cmd.Flags() 58 flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit") 59 flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files") 60 opts.Common.InstallFlags(flags) 61 62 setFlagErrorFunc(dockerCli, cmd, flags, opts) 63 64 setHelpFunc(dockerCli, cmd, flags, opts) 65 66 cmd.SetOutput(dockerCli.Out()) 67 cmd.AddCommand(newDaemonCommand()) 68 commands.AddCommands(cmd, dockerCli) 69 70 setValidateArgs(dockerCli, cmd, flags, opts) 71 72 return cmd 73 } 74 75 func setFlagErrorFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) { 76 // When invoking `docker stack --nonsense`, we need to make sure FlagErrorFunc return appropriate 77 // output if the feature is not supported. 78 // As above cli.SetupRootCommand(cmd) have already setup the FlagErrorFunc, we will add a pre-check before the FlagErrorFunc 79 // is called. 80 flagErrorFunc := cmd.FlagErrorFunc() 81 cmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { 82 initializeDockerCli(dockerCli, flags, opts) 83 if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { 84 return err 85 } 86 return flagErrorFunc(cmd, err) 87 }) 88 } 89 90 func setHelpFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) { 91 cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) { 92 initializeDockerCli(dockerCli, flags, opts) 93 if err := isSupported(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { 94 ccmd.Println(err) 95 return 96 } 97 98 hideUnsupportedFeatures(ccmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()) 99 100 if err := ccmd.Help(); err != nil { 101 ccmd.Println(err) 102 } 103 }) 104 } 105 106 func setValidateArgs(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) { 107 // The Args is handled by ValidateArgs in cobra, which does not allows a pre-hook. 108 // As a result, here we replace the existing Args validation func to a wrapper, 109 // where the wrapper will check to see if the feature is supported or not. 110 // The Args validation error will only be returned if the feature is supported. 111 visitAll(cmd, func(ccmd *cobra.Command) { 112 // if there is no tags for a command or any of its parent, 113 // there is no need to wrap the Args validation. 114 if !hasTags(ccmd) { 115 return 116 } 117 118 if ccmd.Args == nil { 119 return 120 } 121 122 cmdArgs := ccmd.Args 123 ccmd.Args = func(cmd *cobra.Command, args []string) error { 124 initializeDockerCli(dockerCli, flags, opts) 125 if err := isSupported(cmd, dockerCli.Client().ClientVersion(), dockerCli.HasExperimental()); err != nil { 126 return err 127 } 128 return cmdArgs(cmd, args) 129 } 130 }) 131 } 132 133 func initializeDockerCli(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *cliflags.ClientOptions) { 134 if dockerCli.Client() == nil { // when using --help, PersistentPreRun is not called, so initialization is needed. 135 // flags must be the top-level command flags, not cmd.Flags() 136 opts.Common.SetDefaultOptions(flags) 137 dockerPreRun(opts) 138 dockerCli.Initialize(opts) 139 } 140 } 141 142 // visitAll will traverse all commands from the root. 143 // This is different from the VisitAll of cobra.Command where only parents 144 // are checked. 145 func visitAll(root *cobra.Command, fn func(*cobra.Command)) { 146 for _, cmd := range root.Commands() { 147 visitAll(cmd, fn) 148 } 149 fn(root) 150 } 151 152 func noArgs(cmd *cobra.Command, args []string) error { 153 if len(args) == 0 { 154 return nil 155 } 156 return fmt.Errorf( 157 "docker: '%s' is not a docker command.\nSee 'docker --help'", args[0]) 158 } 159 160 func main() { 161 // Set terminal emulation based on platform as required. 162 stdin, stdout, stderr := term.StdStreams() 163 logrus.SetOutput(stderr) 164 165 dockerCli := command.NewDockerCli(stdin, stdout, stderr) 166 cmd := newDockerCommand(dockerCli) 167 168 if err := cmd.Execute(); err != nil { 169 if sterr, ok := err.(cli.StatusError); ok { 170 if sterr.Status != "" { 171 fmt.Fprintln(stderr, sterr.Status) 172 } 173 // StatusError should only be used for errors, and all errors should 174 // have a non-zero exit status, so never exit with 0 175 if sterr.StatusCode == 0 { 176 os.Exit(1) 177 } 178 os.Exit(sterr.StatusCode) 179 } 180 fmt.Fprintln(stderr, err) 181 os.Exit(1) 182 } 183 } 184 185 func showVersion() { 186 fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit) 187 } 188 189 func dockerPreRun(opts *cliflags.ClientOptions) { 190 cliflags.SetLogLevel(opts.Common.LogLevel) 191 192 if opts.ConfigDir != "" { 193 cliconfig.SetDir(opts.ConfigDir) 194 } 195 196 if opts.Common.Debug { 197 debug.Enable() 198 } 199 } 200 201 func hideUnsupportedFeatures(cmd *cobra.Command, clientVersion string, hasExperimental bool) { 202 cmd.Flags().VisitAll(func(f *pflag.Flag) { 203 // hide experimental flags 204 if !hasExperimental { 205 if _, ok := f.Annotations["experimental"]; ok { 206 f.Hidden = true 207 } 208 } 209 210 // hide flags not supported by the server 211 if !isFlagSupported(f, clientVersion) { 212 f.Hidden = true 213 } 214 215 }) 216 217 for _, subcmd := range cmd.Commands() { 218 // hide experimental subcommands 219 if !hasExperimental { 220 if _, ok := subcmd.Tags["experimental"]; ok { 221 subcmd.Hidden = true 222 } 223 } 224 225 // hide subcommands not supported by the server 226 if subcmdVersion, ok := subcmd.Tags["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) { 227 subcmd.Hidden = true 228 } 229 } 230 } 231 232 func isSupported(cmd *cobra.Command, clientVersion string, hasExperimental bool) error { 233 // We check recursively so that, e.g., `docker stack ls` will return the same output as `docker stack` 234 if !hasExperimental { 235 for curr := cmd; curr != nil; curr = curr.Parent() { 236 if _, ok := curr.Tags["experimental"]; ok { 237 return errors.New("only supported on a Docker daemon with experimental features enabled") 238 } 239 } 240 } 241 242 if cmdVersion, ok := cmd.Tags["version"]; ok && versions.LessThan(clientVersion, cmdVersion) { 243 return fmt.Errorf("requires API version %s, but the Docker daemon API version is %s", cmdVersion, clientVersion) 244 } 245 246 errs := []string{} 247 248 cmd.Flags().VisitAll(func(f *pflag.Flag) { 249 if f.Changed { 250 if !isFlagSupported(f, clientVersion) { 251 errs = append(errs, fmt.Sprintf("\"--%s\" requires API version %s, but the Docker daemon API version is %s", f.Name, getFlagVersion(f), clientVersion)) 252 return 253 } 254 if _, ok := f.Annotations["experimental"]; ok && !hasExperimental { 255 errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon with experimental features enabled", f.Name)) 256 } 257 } 258 }) 259 if len(errs) > 0 { 260 return errors.New(strings.Join(errs, "\n")) 261 } 262 263 return nil 264 } 265 266 func getFlagVersion(f *pflag.Flag) string { 267 if flagVersion, ok := f.Annotations["version"]; ok && len(flagVersion) == 1 { 268 return flagVersion[0] 269 } 270 return "" 271 } 272 273 func isFlagSupported(f *pflag.Flag, clientVersion string) bool { 274 if v := getFlagVersion(f); v != "" { 275 return versions.GreaterThanOrEqualTo(clientVersion, v) 276 } 277 return true 278 } 279 280 // hasTags return true if any of the command's parents has tags 281 func hasTags(cmd *cobra.Command) bool { 282 for curr := cmd; curr != nil; curr = curr.Parent() { 283 if len(curr.Tags) > 0 { 284 return true 285 } 286 } 287 288 return false 289 }