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  }