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