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  }