diff --git a/api/client/cli.go b/api/client/cli.go index 1686e28751..f7a1ba0745 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "os" + "path/filepath" "runtime" "github.com/docker/docker/api" @@ -14,7 +15,7 @@ import ( "github.com/docker/docker/cliconfig/configfile" "github.com/docker/docker/cliconfig/credentials" "github.com/docker/docker/dockerversion" - "github.com/docker/docker/opts" + dopts "github.com/docker/docker/opts" "github.com/docker/docker/pkg/term" "github.com/docker/engine-api/client" "github.com/docker/go-connections/sockets" @@ -53,15 +54,6 @@ type DockerCli struct { outState *term.State } -// Initialize calls the init function that will setup the configuration for the client -// such as the TLS, tcp and other parameters used to run the client. -func (cli *DockerCli) Initialize() error { - if cli.init == nil { - return nil - } - return cli.init() -} - // Client returns the APIClient func (cli *DockerCli) Client() client.APIClient { return cli.client @@ -155,40 +147,41 @@ func (cli *DockerCli) restoreTerminal(in io.Closer) error { return nil } +// Initialize the dockerCli runs initialization that must happen after command +// line flags are parsed. +func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error { + cli.configFile = LoadDefaultConfigFile(cli.err) + + client, err := NewAPIClientFromFlags(opts.Common, cli.configFile) + if err != nil { + return err + } + + cli.client = client + + if cli.in != nil { + cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in) + } + if cli.out != nil { + cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out) + } + + if opts.Common.TrustKey == "" { + cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) + } else { + cli.keyFile = opts.Common.TrustKey + } + + return nil +} + // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. -// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config -// is set the client scheme will be set to https. -// The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). -func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cliflags.ClientFlags) *DockerCli { - cli := &DockerCli{ - in: in, - out: out, - err: err, - keyFile: clientFlags.Common.TrustKey, +func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli { + return &DockerCli{ + in: in, + out: out, + err: err, } - - cli.init = func() error { - clientFlags.PostParse() - cli.configFile = LoadDefaultConfigFile(err) - - client, err := NewAPIClientFromFlags(clientFlags, cli.configFile) - if err != nil { - return err - } - - cli.client = client - - if cli.in != nil { - cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in) - } - if cli.out != nil { - cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out) - } - - return nil - } - - return cli } // LoadDefaultConfigFile attempts to load the default config file and returns @@ -205,8 +198,8 @@ func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile { } // NewAPIClientFromFlags creates a new APIClient from command line flags -func NewAPIClientFromFlags(clientFlags *cliflags.ClientFlags, configFile *configfile.ConfigFile) (client.APIClient, error) { - host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions) +func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) { + host, err := getServerHost(opts.Hosts, opts.TLSOptions) if err != nil { return &client.Client{}, err } @@ -222,7 +215,7 @@ func NewAPIClientFromFlags(clientFlags *cliflags.ClientFlags, configFile *config verStr = tmpStr } - httpClient, err := newHTTPClient(host, clientFlags.Common.TLSOptions) + httpClient, err := newHTTPClient(host, opts.TLSOptions) if err != nil { return &client.Client{}, err } @@ -240,7 +233,7 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, return "", errors.New("Please specify only one -H") } - host, err = opts.ParseHost(tlsOptions != nil, host) + host, err = dopts.ParseHost(tlsOptions != nil, host) return } diff --git a/api/client/command/commands.go b/api/client/command/commands.go new file mode 100644 index 0000000000..4cd4a98467 --- /dev/null +++ b/api/client/command/commands.go @@ -0,0 +1,71 @@ +package command + +import ( + "github.com/docker/docker/api/client" + "github.com/docker/docker/api/client/container" + "github.com/docker/docker/api/client/image" + "github.com/docker/docker/api/client/network" + "github.com/docker/docker/api/client/node" + "github.com/docker/docker/api/client/plugin" + "github.com/docker/docker/api/client/registry" + "github.com/docker/docker/api/client/service" + "github.com/docker/docker/api/client/stack" + "github.com/docker/docker/api/client/swarm" + "github.com/docker/docker/api/client/system" + "github.com/docker/docker/api/client/volume" + "github.com/spf13/cobra" +) + +// AddCommands adds all the commands from api/client to the root command +func AddCommands(cmd *cobra.Command, dockerCli *client.DockerCli) { + cmd.AddCommand( + node.NewNodeCommand(dockerCli), + service.NewServiceCommand(dockerCli), + stack.NewStackCommand(dockerCli), + stack.NewTopLevelDeployCommand(dockerCli), + swarm.NewSwarmCommand(dockerCli), + container.NewAttachCommand(dockerCli), + container.NewCommitCommand(dockerCli), + container.NewCopyCommand(dockerCli), + container.NewCreateCommand(dockerCli), + container.NewDiffCommand(dockerCli), + container.NewExecCommand(dockerCli), + container.NewExportCommand(dockerCli), + container.NewKillCommand(dockerCli), + container.NewLogsCommand(dockerCli), + container.NewPauseCommand(dockerCli), + container.NewPortCommand(dockerCli), + container.NewPsCommand(dockerCli), + container.NewRenameCommand(dockerCli), + container.NewRestartCommand(dockerCli), + container.NewRmCommand(dockerCli), + container.NewRunCommand(dockerCli), + container.NewStartCommand(dockerCli), + container.NewStatsCommand(dockerCli), + container.NewStopCommand(dockerCli), + container.NewTopCommand(dockerCli), + container.NewUnpauseCommand(dockerCli), + container.NewUpdateCommand(dockerCli), + container.NewWaitCommand(dockerCli), + image.NewBuildCommand(dockerCli), + image.NewHistoryCommand(dockerCli), + image.NewImagesCommand(dockerCli), + image.NewLoadCommand(dockerCli), + image.NewRemoveCommand(dockerCli), + image.NewSaveCommand(dockerCli), + image.NewPullCommand(dockerCli), + image.NewPushCommand(dockerCli), + image.NewSearchCommand(dockerCli), + image.NewImportCommand(dockerCli), + image.NewTagCommand(dockerCli), + network.NewNetworkCommand(dockerCli), + system.NewEventsCommand(dockerCli), + system.NewInspectCommand(dockerCli), + registry.NewLoginCommand(dockerCli), + registry.NewLogoutCommand(dockerCli), + system.NewVersionCommand(dockerCli), + volume.NewVolumeCommand(dockerCli), + system.NewInfoCommand(dockerCli), + ) + plugin.NewPluginCommand(cmd, dockerCli) +} diff --git a/api/client/commands.go b/api/client/commands.go deleted file mode 100644 index 745180e19c..0000000000 --- a/api/client/commands.go +++ /dev/null @@ -1,6 +0,0 @@ -package client - -// Command returns a cli command handler if one exists -func (cli *DockerCli) Command(name string) func(...string) error { - return map[string]func(...string) error{}[name] -} diff --git a/api/client/container/attach.go b/api/client/container/attach.go index 0d466d98ef..a08065cc5c 100644 --- a/api/client/container/attach.go +++ b/api/client/container/attach.go @@ -36,7 +36,6 @@ func NewAttachCommand(dockerCli *client.DockerCli) *cobra.Command { return runAttach(dockerCli, &opts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) flags := cmd.Flags() flags.BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN") diff --git a/api/client/container/commit.go b/api/client/container/commit.go index eb23b168d7..c6e5c1b582 100644 --- a/api/client/container/commit.go +++ b/api/client/container/commit.go @@ -38,7 +38,6 @@ func NewCommitCommand(dockerCli *client.DockerCli) *cobra.Command { return runCommit(dockerCli, &opts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) flags := cmd.Flags() flags.SetInterspersed(false) diff --git a/api/client/container/create.go b/api/client/container/create.go index 680d5ad7df..ef9b8cd7f6 100644 --- a/api/client/container/create.go +++ b/api/client/container/create.go @@ -43,7 +43,6 @@ func NewCreateCommand(dockerCli *client.DockerCli) *cobra.Command { return runCreate(dockerCli, cmd.Flags(), &opts, copts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) flags := cmd.Flags() flags.SetInterspersed(false) diff --git a/api/client/container/diff.go b/api/client/container/diff.go index 5fdaeadd38..88b2e4e1cd 100644 --- a/api/client/container/diff.go +++ b/api/client/container/diff.go @@ -19,7 +19,7 @@ type diffOptions struct { func NewDiffCommand(dockerCli *client.DockerCli) *cobra.Command { var opts diffOptions - cmd := &cobra.Command{ + return &cobra.Command{ Use: "diff CONTAINER", Short: "Inspect changes on a container's filesystem", Args: cli.ExactArgs(1), @@ -28,9 +28,6 @@ func NewDiffCommand(dockerCli *client.DockerCli) *cobra.Command { return runDiff(dockerCli, &opts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) - - return cmd } func runDiff(dockerCli *client.DockerCli, opts *diffOptions) error { diff --git a/api/client/container/logs.go b/api/client/container/logs.go index 62119e3cbe..593ec60d29 100644 --- a/api/client/container/logs.go +++ b/api/client/container/logs.go @@ -41,7 +41,6 @@ func NewLogsCommand(dockerCli *client.DockerCli) *cobra.Command { return runLogs(dockerCli, &opts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) flags := cmd.Flags() flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output") diff --git a/api/client/container/pause.go b/api/client/container/pause.go index 41fcd98edd..46315ee2c1 100644 --- a/api/client/container/pause.go +++ b/api/client/container/pause.go @@ -19,7 +19,7 @@ type pauseOptions struct { func NewPauseCommand(dockerCli *client.DockerCli) *cobra.Command { var opts pauseOptions - cmd := &cobra.Command{ + return &cobra.Command{ Use: "pause CONTAINER [CONTAINER...]", Short: "Pause all processes within one or more containers", Args: cli.RequiresMinArgs(1), @@ -28,9 +28,6 @@ func NewPauseCommand(dockerCli *client.DockerCli) *cobra.Command { return runPause(dockerCli, &opts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) - - return cmd } func runPause(dockerCli *client.DockerCli, opts *pauseOptions) error { diff --git a/api/client/container/port.go b/api/client/container/port.go index 4164b30cbf..fe3317a697 100644 --- a/api/client/container/port.go +++ b/api/client/container/port.go @@ -34,8 +34,6 @@ func NewPortCommand(dockerCli *client.DockerCli) *cobra.Command { return runPort(dockerCli, &opts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) - return cmd } diff --git a/api/client/container/rename.go b/api/client/container/rename.go index af10102688..04685167b3 100644 --- a/api/client/container/rename.go +++ b/api/client/container/rename.go @@ -30,8 +30,6 @@ func NewRenameCommand(dockerCli *client.DockerCli) *cobra.Command { return runRename(dockerCli, &opts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) - return cmd } diff --git a/api/client/container/run.go b/api/client/container/run.go index 70755b433d..c72f8d2fd8 100644 --- a/api/client/container/run.go +++ b/api/client/container/run.go @@ -48,7 +48,6 @@ func NewRunCommand(dockerCli *client.DockerCli) *cobra.Command { return runRun(dockerCli, cmd.Flags(), &opts, copts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) flags := cmd.Flags() flags.SetInterspersed(false) diff --git a/api/client/container/start.go b/api/client/container/start.go index 1520b04621..e044985e60 100644 --- a/api/client/container/start.go +++ b/api/client/container/start.go @@ -37,7 +37,6 @@ func NewStartCommand(dockerCli *client.DockerCli) *cobra.Command { return runStart(dockerCli, &opts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) flags := cmd.Flags() flags.BoolVarP(&opts.attach, "attach", "a", false, "Attach STDOUT/STDERR and forward signals") diff --git a/api/client/container/stop.go b/api/client/container/stop.go index 3e2f27c298..5e8a23f09a 100644 --- a/api/client/container/stop.go +++ b/api/client/container/stop.go @@ -31,7 +31,6 @@ func NewStopCommand(dockerCli *client.DockerCli) *cobra.Command { return runStop(dockerCli, &opts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) flags := cmd.Flags() flags.IntVarP(&opts.time, "time", "t", 10, "Seconds to wait for stop before killing it") diff --git a/api/client/container/top.go b/api/client/container/top.go index e31a903adc..3ade5fbd6d 100644 --- a/api/client/container/top.go +++ b/api/client/container/top.go @@ -32,7 +32,6 @@ func NewTopCommand(dockerCli *client.DockerCli) *cobra.Command { return runTop(dockerCli, &opts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) flags := cmd.Flags() flags.SetInterspersed(false) diff --git a/api/client/container/unpause.go b/api/client/container/unpause.go index f8c9c0a673..8ce7c3f711 100644 --- a/api/client/container/unpause.go +++ b/api/client/container/unpause.go @@ -28,8 +28,6 @@ func NewUnpauseCommand(dockerCli *client.DockerCli) *cobra.Command { return runUnpause(dockerCli, &opts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) - return cmd } diff --git a/api/client/container/wait.go b/api/client/container/wait.go index 4b6be886e8..6389a2c907 100644 --- a/api/client/container/wait.go +++ b/api/client/container/wait.go @@ -28,8 +28,6 @@ func NewWaitCommand(dockerCli *client.DockerCli) *cobra.Command { return runWait(dockerCli, &opts) }, } - cmd.SetFlagErrorFunc(flagErrorFunc) - return cmd } diff --git a/cli/cli.go b/cli/cli.go deleted file mode 100644 index 8d21cda69d..0000000000 --- a/cli/cli.go +++ /dev/null @@ -1,191 +0,0 @@ -package cli - -import ( - "errors" - "fmt" - "io" - "os" - "strings" - - flag "github.com/docker/docker/pkg/mflag" -) - -// Cli represents a command line interface. -type Cli struct { - Stderr io.Writer - handlers []Handler - Usage func() -} - -// Handler holds the different commands Cli will call -// It should have methods with names starting with `Cmd` like: -// func (h myHandler) CmdFoo(args ...string) error -type Handler interface { - Command(name string) func(...string) error -} - -// Initializer can be optionally implemented by a Handler to -// initialize before each call to one of its commands. -type Initializer interface { - Initialize() error -} - -// New instantiates a ready-to-use Cli. -func New(handlers ...Handler) *Cli { - // make the generic Cli object the first cli handler - // in order to handle `docker help` appropriately - cli := new(Cli) - cli.handlers = append([]Handler{cli}, handlers...) - return cli -} - -var errCommandNotFound = errors.New("command not found") - -func (cli *Cli) command(args ...string) (func(...string) error, error) { - for _, c := range cli.handlers { - if c == nil { - continue - } - if cmd := c.Command(strings.Join(args, " ")); cmd != nil { - if ci, ok := c.(Initializer); ok { - if err := ci.Initialize(); err != nil { - return nil, err - } - } - return cmd, nil - } - } - return nil, errCommandNotFound -} - -// Run executes the specified command. -func (cli *Cli) Run(args ...string) error { - if len(args) > 1 { - command, err := cli.command(args[:2]...) - if err == nil { - return command(args[2:]...) - } - if err != errCommandNotFound { - return err - } - } - if len(args) > 0 { - command, err := cli.command(args[0]) - if err != nil { - if err == errCommandNotFound { - cli.noSuchCommand(args[0]) - return nil - } - return err - } - return command(args[1:]...) - } - return cli.CmdHelp() -} - -func (cli *Cli) noSuchCommand(command string) { - if cli.Stderr == nil { - cli.Stderr = os.Stderr - } - fmt.Fprintf(cli.Stderr, "docker: '%s' is not a docker command.\nSee 'docker --help'.\n", command) - os.Exit(1) -} - -// Command returns a command handler, or nil if the command does not exist -func (cli *Cli) Command(name string) func(...string) error { - return map[string]func(...string) error{ - "help": cli.CmdHelp, - }[name] -} - -// CmdHelp displays information on a Docker command. -// -// If more than one command is specified, information is only shown for the first command. -// -// Usage: docker help COMMAND or docker COMMAND --help -func (cli *Cli) CmdHelp(args ...string) error { - if len(args) > 1 { - command, err := cli.command(args[:2]...) - if err == nil { - command("--help") - return nil - } - if err != errCommandNotFound { - return err - } - } - if len(args) > 0 { - command, err := cli.command(args[0]) - if err != nil { - if err == errCommandNotFound { - cli.noSuchCommand(args[0]) - return nil - } - return err - } - command("--help") - return nil - } - - if cli.Usage == nil { - flag.Usage() - } else { - cli.Usage() - } - - return nil -} - -// Subcmd is a subcommand of the main "docker" command. -// A subcommand represents an action that can be performed -// from the Docker command line client. -// -// To see all available subcommands, run "docker --help". -func Subcmd(name string, synopses []string, description string, exitOnError bool) *flag.FlagSet { - var errorHandling flag.ErrorHandling - if exitOnError { - errorHandling = flag.ExitOnError - } else { - errorHandling = flag.ContinueOnError - } - flags := flag.NewFlagSet(name, errorHandling) - flags.Usage = func() { - flags.ShortUsage() - flags.PrintDefaults() - } - - flags.ShortUsage = func() { - if len(synopses) == 0 { - synopses = []string{""} - } - - // Allow for multiple command usage synopses. - for i, synopsis := range synopses { - lead := "\t" - if i == 0 { - // First line needs the word 'Usage'. - lead = "Usage:\t" - } - - if synopsis != "" { - synopsis = " " + synopsis - } - - fmt.Fprintf(flags.Out(), "\n%sdocker %s%s", lead, name, synopsis) - } - - fmt.Fprintf(flags.Out(), "\n\n%s\n", description) - } - - return flags -} - -// StatusError reports an unsuccessful exit by a command. -type StatusError struct { - Status string - StatusCode int -} - -func (e StatusError) Error() string { - return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode) -} diff --git a/cli/cobra.go b/cli/cobra.go new file mode 100644 index 0000000000..f924e67b27 --- /dev/null +++ b/cli/cobra.go @@ -0,0 +1,57 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// SetupRootCommand sets default usage, help, and error handling for the +// root command. +func SetupRootCommand(rootCmd *cobra.Command) { + rootCmd.SetUsageTemplate(usageTemplate) + rootCmd.SetHelpTemplate(helpTemplate) + rootCmd.SetFlagErrorFunc(FlagErrorFunc) + + rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage") + rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help") +} + +// FlagErrorFunc prints an error messages which matches the format of the +// docker/docker/cli error messages +func FlagErrorFunc(cmd *cobra.Command, err error) error { + if err == nil { + return err + } + + usage := "" + if cmd.HasSubCommands() { + usage = "\n\n" + cmd.UsageString() + } + return StatusError{ + Status: fmt.Sprintf("%s\nSee '%s --help'.%s", err, cmd.CommandPath(), usage), + StatusCode: 125, + } +} + +var usageTemplate = `Usage: {{if not .HasSubCommands}}{{.UseLine}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND{{end}} + +{{ .Short | trim }}{{if gt .Aliases 0}} + +Aliases: + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +Examples: +{{ .Example }}{{end}}{{if .HasFlags}} + +Options: +{{.Flags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableSubCommands}} + +Commands:{{range .Commands}}{{if .IsAvailableCommand}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }} + +Run '{{.CommandPath}} COMMAND --help' for more information on a command.{{end}} +` + +var helpTemplate = ` +{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` diff --git a/cli/cobraadaptor/adaptor.go b/cli/cobraadaptor/adaptor.go deleted file mode 100644 index 2df553bea1..0000000000 --- a/cli/cobraadaptor/adaptor.go +++ /dev/null @@ -1,163 +0,0 @@ -package cobraadaptor - -import ( - "github.com/docker/docker/api/client" - "github.com/docker/docker/api/client/container" - "github.com/docker/docker/api/client/image" - "github.com/docker/docker/api/client/network" - "github.com/docker/docker/api/client/node" - "github.com/docker/docker/api/client/plugin" - "github.com/docker/docker/api/client/registry" - "github.com/docker/docker/api/client/service" - "github.com/docker/docker/api/client/stack" - "github.com/docker/docker/api/client/swarm" - "github.com/docker/docker/api/client/system" - "github.com/docker/docker/api/client/volume" - "github.com/docker/docker/cli" - cliflags "github.com/docker/docker/cli/flags" - "github.com/docker/docker/pkg/term" - "github.com/spf13/cobra" -) - -// CobraAdaptor is an adaptor for supporting spf13/cobra commands in the -// docker/cli framework -type CobraAdaptor struct { - rootCmd *cobra.Command - dockerCli *client.DockerCli -} - -// NewCobraAdaptor returns a new handler -func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor { - stdin, stdout, stderr := term.StdStreams() - dockerCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags) - - var rootCmd = &cobra.Command{ - Use: "docker [OPTIONS]", - Short: "A self-sufficient runtime for containers", - SilenceUsage: true, - SilenceErrors: true, - } - rootCmd.SetUsageTemplate(usageTemplate) - rootCmd.SetHelpTemplate(helpTemplate) - rootCmd.SetFlagErrorFunc(cli.FlagErrorFunc) - rootCmd.SetOutput(stdout) - rootCmd.AddCommand( - node.NewNodeCommand(dockerCli), - service.NewServiceCommand(dockerCli), - stack.NewStackCommand(dockerCli), - stack.NewTopLevelDeployCommand(dockerCli), - swarm.NewSwarmCommand(dockerCli), - container.NewAttachCommand(dockerCli), - container.NewCommitCommand(dockerCli), - container.NewCopyCommand(dockerCli), - container.NewCreateCommand(dockerCli), - container.NewDiffCommand(dockerCli), - container.NewExecCommand(dockerCli), - container.NewExportCommand(dockerCli), - container.NewKillCommand(dockerCli), - container.NewLogsCommand(dockerCli), - container.NewPauseCommand(dockerCli), - container.NewPortCommand(dockerCli), - container.NewPsCommand(dockerCli), - container.NewRenameCommand(dockerCli), - container.NewRestartCommand(dockerCli), - container.NewRmCommand(dockerCli), - container.NewRunCommand(dockerCli), - container.NewStartCommand(dockerCli), - container.NewStatsCommand(dockerCli), - container.NewStopCommand(dockerCli), - container.NewTopCommand(dockerCli), - container.NewUnpauseCommand(dockerCli), - container.NewUpdateCommand(dockerCli), - container.NewWaitCommand(dockerCli), - image.NewBuildCommand(dockerCli), - image.NewHistoryCommand(dockerCli), - image.NewImagesCommand(dockerCli), - image.NewLoadCommand(dockerCli), - image.NewRemoveCommand(dockerCli), - image.NewSaveCommand(dockerCli), - image.NewPullCommand(dockerCli), - image.NewPushCommand(dockerCli), - image.NewSearchCommand(dockerCli), - image.NewImportCommand(dockerCli), - image.NewTagCommand(dockerCli), - network.NewNetworkCommand(dockerCli), - system.NewEventsCommand(dockerCli), - system.NewInspectCommand(dockerCli), - registry.NewLoginCommand(dockerCli), - registry.NewLogoutCommand(dockerCli), - system.NewVersionCommand(dockerCli), - volume.NewVolumeCommand(dockerCli), - system.NewInfoCommand(dockerCli), - ) - plugin.NewPluginCommand(rootCmd, dockerCli) - - rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage") - rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help") - - return CobraAdaptor{ - rootCmd: rootCmd, - dockerCli: dockerCli, - } -} - -// Usage returns the list of commands and their short usage string for -// all top level cobra commands. -func (c CobraAdaptor) Usage() []cli.Command { - cmds := []cli.Command{} - for _, cmd := range c.rootCmd.Commands() { - if cmd.Name() != "" { - cmds = append(cmds, cli.Command{Name: cmd.Name(), Description: cmd.Short}) - } - } - return cmds -} - -func (c CobraAdaptor) run(cmd string, args []string) error { - if err := c.dockerCli.Initialize(); err != nil { - return err - } - // Prepend the command name to support normal cobra command delegation - c.rootCmd.SetArgs(append([]string{cmd}, args...)) - return c.rootCmd.Execute() -} - -// Command returns a cli command handler if one exists -func (c CobraAdaptor) Command(name string) func(...string) error { - for _, cmd := range c.rootCmd.Commands() { - if cmd.Name() == name { - return func(args ...string) error { - return c.run(name, args) - } - } - } - return nil -} - -// GetRootCommand returns the root command. Required to generate the man pages -// and reference docs from a script outside this package. -func (c CobraAdaptor) GetRootCommand() *cobra.Command { - return c.rootCmd -} - -var usageTemplate = `Usage: {{if not .HasSubCommands}}{{.UseLine}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND{{end}} - -{{ .Short | trim }}{{if gt .Aliases 0}} - -Aliases: - {{.NameAndAliases}}{{end}}{{if .HasExample}} - -Examples: -{{ .Example }}{{end}}{{if .HasFlags}} - -Options: -{{.Flags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableSubCommands}} - -Commands:{{range .Commands}}{{if .IsAvailableCommand}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }} - -Run '{{.CommandPath}} COMMAND --help' for more information on a command.{{end}} -` - -var helpTemplate = ` -{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` diff --git a/cli/error.go b/cli/error.go index e421c7f7c7..62f62433b8 100644 --- a/cli/error.go +++ b/cli/error.go @@ -1,6 +1,9 @@ package cli -import "strings" +import ( + "fmt" + "strings" +) // Errors is a list of errors. // Useful in a loop if you don't want to return the error right away and you want to display after the loop, @@ -18,3 +21,13 @@ func (errList Errors) Error() string { } return strings.Join(out, ", ") } + +// StatusError reports an unsuccessful exit by a command. +type StatusError struct { + Status string + StatusCode int +} + +func (e StatusError) Error() string { + return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode) +} diff --git a/cli/flagerrors.go b/cli/flagerrors.go deleted file mode 100644 index aab8a98845..0000000000 --- a/cli/flagerrors.go +++ /dev/null @@ -1,21 +0,0 @@ -package cli - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -// FlagErrorFunc prints an error messages which matches the format of the -// docker/docker/cli error messages -func FlagErrorFunc(cmd *cobra.Command, err error) error { - if err == nil { - return err - } - - usage := "" - if cmd.HasSubCommands() { - usage = "\n\n" + cmd.UsageString() - } - return fmt.Errorf("%s\nSee '%s --help'.%s", err, cmd.CommandPath(), usage) -} diff --git a/cli/flags/client.go b/cli/flags/client.go index cc7309db4b..9b6940f6bd 100644 --- a/cli/flags/client.go +++ b/cli/flags/client.go @@ -1,12 +1,13 @@ package flags -import flag "github.com/docker/docker/pkg/mflag" - -// ClientFlags represents flags for the docker client. -type ClientFlags struct { - FlagSet *flag.FlagSet - Common *CommonFlags - PostParse func() - +// ClientOptions are the options used to configure the client cli +type ClientOptions struct { + Common *CommonOptions ConfigDir string + Version bool +} + +// NewClientOptions returns a new ClientOptions +func NewClientOptions() *ClientOptions { + return &ClientOptions{Common: NewCommonOptions()} } diff --git a/cli/flags/common.go b/cli/flags/common.go index 4726b04f2a..2318b9d975 100644 --- a/cli/flags/common.go +++ b/cli/flags/common.go @@ -8,8 +8,8 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/cliconfig" "github.com/docker/docker/opts" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/go-connections/tlsconfig" + "github.com/spf13/pflag" ) const ( @@ -21,8 +21,8 @@ const ( DefaultKeyFile = "key.pem" // DefaultCertFile is the default filename for the cert pem file DefaultCertFile = "cert.pem" - // TLSVerifyKey is the default flag name for the tls verification option - TLSVerifyKey = "tlsverify" + // FlagTLSVerify is the flag name for the tls verification option + FlagTLSVerify = "tlsverify" ) var ( @@ -30,11 +30,8 @@ var ( dockerTLSVerify = os.Getenv("DOCKER_TLS_VERIFY") != "" ) -// CommonFlags are flags common to both the client and the daemon. -type CommonFlags struct { - FlagSet *flag.FlagSet - PostParse func() - +// CommonOptions are options common to both the client and the daemon. +type CommonOptions struct { Debug bool Hosts []string LogLevel string @@ -44,62 +41,59 @@ type CommonFlags struct { TrustKey string } -// InitCommonFlags initializes flags common to both client and daemon -func InitCommonFlags() *CommonFlags { - var commonFlags = &CommonFlags{FlagSet: new(flag.FlagSet)} +// NewCommonOptions returns a new CommonOptions +func NewCommonOptions() *CommonOptions { + return &CommonOptions{} +} +// InstallFlags adds flags for the common options on the FlagSet +func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) { if dockerCertPath == "" { dockerCertPath = cliconfig.ConfigDir() } - commonFlags.PostParse = func() { postParseCommon(commonFlags) } + flags.BoolVarP(&commonOpts.Debug, "debug", "D", false, "Enable debug mode") + flags.StringVarP(&commonOpts.LogLevel, "log-level", "l", "info", "Set the logging level") + flags.BoolVar(&commonOpts.TLS, "tls", false, "Use TLS; implied by --tlsverify") + flags.BoolVar(&commonOpts.TLSVerify, FlagTLSVerify, dockerTLSVerify, "Use TLS and verify the remote") - cmd := commonFlags.FlagSet + // TODO use flag flags.String("identity"}, "i", "", "Path to libtrust key file") - cmd.BoolVar(&commonFlags.Debug, []string{"D", "-debug"}, false, "Enable debug mode") - cmd.StringVar(&commonFlags.LogLevel, []string{"l", "-log-level"}, "info", "Set the logging level") - cmd.BoolVar(&commonFlags.TLS, []string{"-tls"}, false, "Use TLS; implied by --tlsverify") - cmd.BoolVar(&commonFlags.TLSVerify, []string{"-tlsverify"}, dockerTLSVerify, "Use TLS and verify the remote") + commonOpts.TLSOptions = &tlsconfig.Options{} + tlsOptions := commonOpts.TLSOptions + flags.StringVar(&tlsOptions.CAFile, "tlscacert", filepath.Join(dockerCertPath, DefaultCaFile), "Trust certs signed only by this CA") + flags.StringVar(&tlsOptions.CertFile, "tlscert", filepath.Join(dockerCertPath, DefaultCertFile), "Path to TLS certificate file") + flags.StringVar(&tlsOptions.KeyFile, "tlskey", filepath.Join(dockerCertPath, DefaultKeyFile), "Path to TLS key file") - // TODO use flag flag.String([]string{"i", "-identity"}, "", "Path to libtrust key file") - - var tlsOptions tlsconfig.Options - commonFlags.TLSOptions = &tlsOptions - cmd.StringVar(&tlsOptions.CAFile, []string{"-tlscacert"}, filepath.Join(dockerCertPath, DefaultCaFile), "Trust certs signed only by this CA") - cmd.StringVar(&tlsOptions.CertFile, []string{"-tlscert"}, filepath.Join(dockerCertPath, DefaultCertFile), "Path to TLS certificate file") - cmd.StringVar(&tlsOptions.KeyFile, []string{"-tlskey"}, filepath.Join(dockerCertPath, DefaultKeyFile), "Path to TLS key file") - - cmd.Var(opts.NewNamedListOptsRef("hosts", &commonFlags.Hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to") - return commonFlags + hostOpt := opts.NewNamedListOptsRef("hosts", &commonOpts.Hosts, opts.ValidateHost) + flags.VarP(hostOpt, "host", "H", "Daemon socket(s) to connect to") } -func postParseCommon(commonFlags *CommonFlags) { - cmd := commonFlags.FlagSet - - SetDaemonLogLevel(commonFlags.LogLevel) - +// SetDefaultOptions sets default values for options after flag parsing is +// complete +func (commonOpts *CommonOptions) SetDefaultOptions(flags *pflag.FlagSet) { // Regardless of whether the user sets it to true or false, if they // specify --tlsverify at all then we need to turn on tls // TLSVerify can be true even if not set due to DOCKER_TLS_VERIFY env var, so we need // to check that here as well - if cmd.IsSet("-"+TLSVerifyKey) || commonFlags.TLSVerify { - commonFlags.TLS = true + if flags.Changed(FlagTLSVerify) || commonOpts.TLSVerify { + commonOpts.TLS = true } - if !commonFlags.TLS { - commonFlags.TLSOptions = nil + if !commonOpts.TLS { + commonOpts.TLSOptions = nil } else { - tlsOptions := commonFlags.TLSOptions - tlsOptions.InsecureSkipVerify = !commonFlags.TLSVerify + tlsOptions := commonOpts.TLSOptions + tlsOptions.InsecureSkipVerify = !commonOpts.TLSVerify // Reset CertFile and KeyFile to empty string if the user did not specify // the respective flags and the respective default files were not found. - if !cmd.IsSet("-tlscert") { + if !flags.Changed("tlscert") { if _, err := os.Stat(tlsOptions.CertFile); os.IsNotExist(err) { tlsOptions.CertFile = "" } } - if !cmd.IsSet("-tlskey") { + if !flags.Changed("tlskey") { if _, err := os.Stat(tlsOptions.KeyFile); os.IsNotExist(err) { tlsOptions.KeyFile = "" } diff --git a/cli/usage.go b/cli/usage.go deleted file mode 100644 index a8a0328643..0000000000 --- a/cli/usage.go +++ /dev/null @@ -1,19 +0,0 @@ -package cli - -// Command is the struct containing the command name and description -type Command struct { - Name string - Description string -} - -// DockerCommandUsage lists the top level docker commands and their short usage -var DockerCommandUsage = []Command{} - -// DockerCommands stores all the docker command -var DockerCommands = make(map[string]Command) - -func init() { - for _, cmd := range DockerCommandUsage { - DockerCommands[cmd.Name] = cmd - } -} diff --git a/cmd/docker/daemon.go b/cmd/docker/daemon.go deleted file mode 100644 index 8fe3484761..0000000000 --- a/cmd/docker/daemon.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -const daemonBinary = "dockerd" - -// DaemonProxy acts as a cli.Handler to proxy calls to the daemon binary -type DaemonProxy struct{} - -// NewDaemonProxy returns a new handler -func NewDaemonProxy() DaemonProxy { - return DaemonProxy{} -} - -// Command returns a cli command handler if one exists -func (p DaemonProxy) Command(name string) func(...string) error { - return map[string]func(...string) error{ - "daemon": p.CmdDaemon, - }[name] -} diff --git a/cmd/docker/daemon_none.go b/cmd/docker/daemon_none.go index d66bf1a546..65f9f37be2 100644 --- a/cmd/docker/daemon_none.go +++ b/cmd/docker/daemon_none.go @@ -6,10 +6,21 @@ import ( "fmt" "runtime" "strings" + + "github.com/spf13/cobra" ) -// CmdDaemon reports on an error on windows, because there is no exec -func (p DaemonProxy) CmdDaemon(args ...string) error { +func newDaemonCommand() *cobra.Command { + return &cobra.Command{ + Use: "daemon", + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runDaemon() + }, + } +} + +func runDaemon() error { return fmt.Errorf( "`docker daemon` is not supported on %s. Please run `dockerd` directly", strings.Title(runtime.GOOS)) diff --git a/cmd/docker/daemon_unix.go b/cmd/docker/daemon_unix.go index d515b82914..30a40a8611 100644 --- a/cmd/docker/daemon_unix.go +++ b/cmd/docker/daemon_unix.go @@ -3,23 +3,37 @@ package main import ( + "fmt" + "os" "os/exec" "path/filepath" "syscall" + + "github.com/spf13/cobra" ) -// CmdDaemon execs dockerd with the same flags -func (p DaemonProxy) CmdDaemon(args ...string) error { - // Special case for handling `docker help daemon`. When pkg/mflag is removed - // we can support this on the daemon side, but that is not possible with - // pkg/mflag because it uses os.Exit(1) instead of returning an error on - // unexpected args. - if len(args) == 0 || args[0] != "--help" { - // Use os.Args[1:] so that "global" args are passed to dockerd - args = stripDaemonArg(os.Args[1:]) - } +const daemonBinary = "dockerd" +func newDaemonCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "daemon", + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runDaemon() + }, + } + cmd.SetHelpFunc(helpFunc) + return cmd +} + +// CmdDaemon execs dockerd with the same flags +func runDaemon() error { + // Use os.Args[1:] so that "global" args are passed to dockerd + return execDaemon(stripDaemonArg(os.Args[1:])) +} + +func execDaemon(args []string) error { binaryPath, err := findDaemonBinary() if err != nil { return err @@ -31,6 +45,12 @@ func (p DaemonProxy) CmdDaemon(args ...string) error { os.Environ()) } +func helpFunc(cmd *cobra.Command, args []string) { + if err := execDaemon([]string{"--help"}); err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err.Error()) + } +} + // findDaemonBinary looks for the path to the dockerd binary starting with // the directory of the current executable (if one exists) and followed by $PATH func findDaemonBinary() (string, error) { diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index 0c727e32c9..38907970d3 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -3,73 +3,77 @@ package main import ( "fmt" "os" - "path/filepath" "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" + "github.com/docker/docker/api/client/command" "github.com/docker/docker/cli" - "github.com/docker/docker/cli/cobraadaptor" cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/cliconfig" "github.com/docker/docker/dockerversion" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" "github.com/docker/docker/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" ) -var ( - commonFlags = cliflags.InitCommonFlags() - clientFlags = initClientFlags(commonFlags) - flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") - flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") -) +func newDockerCommand(dockerCli *client.DockerCli) *cobra.Command { + opts := cliflags.NewClientOptions() + var flags *pflag.FlagSet + + cmd := &cobra.Command{ + Use: "docker [OPTIONS] COMMAND [arg...]", + Short: "A self-sufficient runtime for containers.", + SilenceUsage: true, + SilenceErrors: true, + TraverseChildren: true, + Args: noArgs, + RunE: func(cmd *cobra.Command, args []string) error { + if opts.Version { + showVersion() + return nil + } + fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) + return nil + }, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // flags must be the top-level command flags, not cmd.Flags() + opts.Common.SetDefaultOptions(flags) + dockerPreRun(opts) + return dockerCli.Initialize(opts) + }, + } + cli.SetupRootCommand(cmd) + + flags = cmd.Flags() + flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit") + flags.StringVar(&opts.ConfigDir, "config", cliconfig.ConfigDir(), "Location of client config files") + opts.Common.InstallFlags(flags) + + cmd.SetOutput(dockerCli.Out()) + cmd.AddCommand(newDaemonCommand()) + command.AddCommands(cmd, dockerCli) + + return cmd +} + +func noArgs(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return nil + } + return fmt.Errorf( + "docker: '%s' is not a docker command.\nSee 'docker --help'%s", args[0], ".") +} func main() { // Set terminal emulation based on platform as required. stdin, stdout, stderr := term.StdStreams() - logrus.SetOutput(stderr) - flag.Merge(flag.CommandLine, clientFlags.FlagSet, commonFlags.FlagSet) + dockerCli := client.NewDockerCli(stdin, stdout, stderr) + cmd := newDockerCommand(dockerCli) - cobraAdaptor := cobraadaptor.NewCobraAdaptor(clientFlags) - - flag.Usage = func() { - fmt.Fprint(stdout, "Usage: docker [OPTIONS] COMMAND [arg...]\n docker [ --help | -v | --version ]\n\n") - fmt.Fprint(stdout, "A self-sufficient runtime for containers.\n\nOptions:\n") - - flag.CommandLine.SetOutput(stdout) - flag.PrintDefaults() - - help := "\nCommands:\n" - - dockerCommands := append(cli.DockerCommandUsage, cobraAdaptor.Usage()...) - for _, cmd := range sortCommands(dockerCommands) { - help += fmt.Sprintf(" %-10.10s%s\n", cmd.Name, cmd.Description) - } - - help += "\nRun 'docker COMMAND --help' for more information on a command." - fmt.Fprintf(stdout, "%s\n", help) - } - - flag.Parse() - - if *flVersion { - showVersion() - return - } - - if *flHelp { - // if global flag --help is present, regardless of what other options and commands there are, - // just print the usage. - flag.Usage() - return - } - - clientCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags) - - c := cli.New(clientCli, NewDaemonProxy(), cobraAdaptor) - if err := c.Run(flag.Args()...); err != nil { + if err := cmd.Execute(); err != nil { if sterr, ok := err.(cli.StatusError); ok { if sterr.Status != "" { fmt.Fprintln(stderr, sterr.Status) @@ -94,25 +98,14 @@ func showVersion() { } } -func initClientFlags(commonFlags *cliflags.CommonFlags) *cliflags.ClientFlags { - clientFlags := &cliflags.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags} - client := clientFlags.FlagSet - client.StringVar(&clientFlags.ConfigDir, []string{"-config"}, cliconfig.ConfigDir(), "Location of client config files") +func dockerPreRun(opts *cliflags.ClientOptions) { + cliflags.SetDaemonLogLevel(opts.Common.LogLevel) - clientFlags.PostParse = func() { - clientFlags.Common.PostParse() - - if clientFlags.ConfigDir != "" { - cliconfig.SetConfigDir(clientFlags.ConfigDir) - } - - if clientFlags.Common.TrustKey == "" { - clientFlags.Common.TrustKey = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) - } - - if clientFlags.Common.Debug { - utils.EnableDebug() - } + if opts.ConfigDir != "" { + cliconfig.SetConfigDir(opts.ConfigDir) + } + + if opts.Common.Debug { + utils.EnableDebug() } - return clientFlags } diff --git a/cmd/docker/docker_test.go b/cmd/docker/docker_test.go index 5708c96cb5..72d2311521 100644 --- a/cmd/docker/docker_test.go +++ b/cmd/docker/docker_test.go @@ -6,13 +6,19 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/utils" + + "github.com/docker/docker/api/client" ) func TestClientDebugEnabled(t *testing.T) { defer utils.DisableDebug() - clientFlags.Common.FlagSet.Parse([]string{"-D"}) - clientFlags.PostParse() + cmd := newDockerCommand(&client.DockerCli{}) + cmd.Flags().Set("debug", "true") + + if err := cmd.PersistentPreRunE(cmd, []string{}); err != nil { + t.Fatalf("Unexpected error: %s", err.Error()) + } if os.Getenv("DEBUG") != "1" { t.Fatal("expected debug enabled, got false") diff --git a/cmd/docker/usage.go b/cmd/docker/usage.go deleted file mode 100644 index 792d178073..0000000000 --- a/cmd/docker/usage.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -import ( - "sort" - - "github.com/docker/docker/cli" -) - -type byName []cli.Command - -func (a byName) Len() int { return len(a) } -func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } - -// TODO(tiborvass): do not show 'daemon' on client-only binaries - -func sortCommands(commands []cli.Command) []cli.Command { - dockerCommands := make([]cli.Command, len(commands)) - copy(dockerCommands, commands) - sort.Sort(byName(dockerCommands)) - return dockerCommands -} diff --git a/cmd/docker/usage_test.go b/cmd/docker/usage_test.go deleted file mode 100644 index 0453265db8..0000000000 --- a/cmd/docker/usage_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "sort" - "testing" - - "github.com/docker/docker/cli" -) - -// Tests if the subcommands of docker are sorted -func TestDockerSubcommandsAreSorted(t *testing.T) { - if !sort.IsSorted(byName(cli.DockerCommandUsage)) { - t.Fatal("Docker subcommands are not in sorted order") - } -} diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go index 53e7e61dfb..fb6524277f 100644 --- a/cmd/dockerd/daemon.go +++ b/cmd/dockerd/daemon.go @@ -6,7 +6,6 @@ import ( "io" "os" "path/filepath" - "runtime" "strings" "time" @@ -31,11 +30,10 @@ import ( "github.com/docker/docker/daemon/logger" "github.com/docker/docker/dockerversion" "github.com/docker/docker/libcontainerd" - "github.com/docker/docker/opts" + dopts "github.com/docker/docker/opts" "github.com/docker/docker/pkg/authorization" "github.com/docker/docker/pkg/jsonlog" "github.com/docker/docker/pkg/listeners" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/pidfile" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/system" @@ -43,46 +41,27 @@ import ( "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" "github.com/docker/go-connections/tlsconfig" + "github.com/spf13/pflag" ) const ( - daemonConfigFileFlag = "-config-file" + flagDaemonConfigFile = "config-file" ) // DaemonCli represents the daemon CLI. type DaemonCli struct { *daemon.Config - commonFlags *cliflags.CommonFlags - configFile *string + configFile *string + flags *pflag.FlagSet api *apiserver.Server d *daemon.Daemon authzMiddleware *authorization.Middleware // authzMiddleware enables to dynamically reload the authorization plugins } -func presentInHelp(usage string) string { return usage } -func absentFromHelp(string) string { return "" } - -// NewDaemonCli returns a pre-configured daemon CLI +// NewDaemonCli returns a daemon CLI func NewDaemonCli() *DaemonCli { - // TODO(tiborvass): remove InstallFlags? - daemonConfig := new(daemon.Config) - daemonConfig.LogConfig.Config = make(map[string]string) - daemonConfig.ClusterOpts = make(map[string]string) - - daemonConfig.InstallFlags(flag.CommandLine, presentInHelp) - configFile := flag.CommandLine.String([]string{daemonConfigFileFlag}, defaultDaemonConfigFile, "Daemon configuration file") - flag.CommandLine.Require(flag.Exact, 0) - - if runtime.GOOS != "linux" { - daemonConfig.V2Only = true - } - - return &DaemonCli{ - Config: daemonConfig, - commonFlags: cliflags.InitCommonFlags(), - configFile: configFile, - } + return &DaemonCli{} } func migrateKey() (err error) { @@ -126,24 +105,25 @@ func migrateKey() (err error) { return nil } -func (cli *DaemonCli) start() (err error) { +func (cli *DaemonCli) start(opts daemonOptions) (err error) { stopc := make(chan bool) defer close(stopc) // warn from uuid package when running the daemon uuid.Loggerf = logrus.Warnf - flags := flag.CommandLine - cli.commonFlags.PostParse() + opts.common.SetDefaultOptions(opts.flags) - if cli.commonFlags.TrustKey == "" { - cli.commonFlags.TrustKey = filepath.Join(getDaemonConfDir(), cliflags.DefaultTrustKeyFile) + if opts.common.TrustKey == "" { + opts.common.TrustKey = filepath.Join( + getDaemonConfDir(), + cliflags.DefaultTrustKeyFile) } - cliConfig, err := loadDaemonCliConfig(cli.Config, flags, cli.commonFlags, *cli.configFile) - if err != nil { + if cli.Config, err = loadDaemonCliConfig(opts); err != nil { return err } - cli.Config = cliConfig + cli.configFile = &opts.configFile + cli.flags = opts.flags if cli.Config.Debug { utils.EnableDebug() @@ -215,7 +195,7 @@ func (cli *DaemonCli) start() (err error) { for i := 0; i < len(cli.Config.Hosts); i++ { var err error - if cli.Config.Hosts[i], err = opts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil { + if cli.Config.Hosts[i], err = dopts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil { return fmt.Errorf("error parsing -H %s : %v", cli.Config.Hosts[i], err) } @@ -250,7 +230,8 @@ func (cli *DaemonCli) start() (err error) { if err := migrateKey(); err != nil { return err } - cli.TrustKeyPath = cli.commonFlags.TrustKey + // FIXME: why is this down here instead of with the other TrustKey logic above? + cli.TrustKeyPath = opts.common.TrustKey registryService := registry.NewService(cli.Config.ServiceOptions) containerdRemote, err := libcontainerd.New(cli.getLibcontainerdRoot(), cli.getPlatformRemoteOptions()...) @@ -341,7 +322,7 @@ func (cli *DaemonCli) reloadConfig() { } } - if err := daemon.ReloadConfiguration(*cli.configFile, flag.CommandLine, reload); err != nil { + if err := daemon.ReloadConfiguration(*cli.configFile, cli.flags, reload); err != nil { logrus.Error(err) } } @@ -367,25 +348,27 @@ func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) { } } -func loadDaemonCliConfig(config *daemon.Config, flags *flag.FlagSet, commonConfig *cliflags.CommonFlags, configFile string) (*daemon.Config, error) { - config.Debug = commonConfig.Debug - config.Hosts = commonConfig.Hosts - config.LogLevel = commonConfig.LogLevel - config.TLS = commonConfig.TLS - config.TLSVerify = commonConfig.TLSVerify +func loadDaemonCliConfig(opts daemonOptions) (*daemon.Config, error) { + config := opts.daemonConfig + flags := opts.flags + config.Debug = opts.common.Debug + config.Hosts = opts.common.Hosts + config.LogLevel = opts.common.LogLevel + config.TLS = opts.common.TLS + config.TLSVerify = opts.common.TLSVerify config.CommonTLSOptions = daemon.CommonTLSOptions{} - if commonConfig.TLSOptions != nil { - config.CommonTLSOptions.CAFile = commonConfig.TLSOptions.CAFile - config.CommonTLSOptions.CertFile = commonConfig.TLSOptions.CertFile - config.CommonTLSOptions.KeyFile = commonConfig.TLSOptions.KeyFile + if opts.common.TLSOptions != nil { + config.CommonTLSOptions.CAFile = opts.common.TLSOptions.CAFile + config.CommonTLSOptions.CertFile = opts.common.TLSOptions.CertFile + config.CommonTLSOptions.KeyFile = opts.common.TLSOptions.KeyFile } - if configFile != "" { - c, err := daemon.MergeDaemonConfigurations(config, flags, configFile) + if opts.configFile != "" { + c, err := daemon.MergeDaemonConfigurations(config, flags, opts.configFile) if err != nil { - if flags.IsSet(daemonConfigFileFlag) || !os.IsNotExist(err) { - return nil, fmt.Errorf("unable to configure the Docker daemon with file %s: %v\n", configFile, err) + if flags.Changed(flagDaemonConfigFile) || !os.IsNotExist(err) { + return nil, fmt.Errorf("unable to configure the Docker daemon with file %s: %v\n", opts.configFile, err) } } // the merged configuration can be nil if the config file didn't exist. @@ -401,7 +384,7 @@ func loadDaemonCliConfig(config *daemon.Config, flags *flag.FlagSet, commonConfi // Regardless of whether the user sets it to true or false, if they // specify TLSVerify at all then we need to turn on TLS - if config.IsValueSet(cliflags.TLSVerifyKey) { + if config.IsValueSet(cliflags.FlagTLSVerify) { config.TLS = true } diff --git a/cmd/dockerd/daemon_test.go b/cmd/dockerd/daemon_test.go index f404bf7fc5..b364f87843 100644 --- a/cmd/dockerd/daemon_test.go +++ b/cmd/dockerd/daemon_test.go @@ -1,290 +1,145 @@ package main import ( - "io/ioutil" - "os" - "strings" "testing" "github.com/Sirupsen/logrus" cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/daemon" - "github.com/docker/docker/opts" - "github.com/docker/docker/pkg/mflag" - "github.com/docker/go-connections/tlsconfig" + "github.com/docker/docker/pkg/testutil/assert" + "github.com/docker/docker/pkg/testutil/tempfile" + "github.com/spf13/pflag" ) -func TestLoadDaemonCliConfigWithoutOverriding(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{ - Debug: true, +func defaultOptions(configFile string) daemonOptions { + opts := daemonOptions{ + daemonConfig: &daemon.Config{}, + flags: &pflag.FlagSet{}, + common: cliflags.NewCommonOptions(), } + opts.common.InstallFlags(opts.flags) + opts.daemonConfig.InstallFlags(opts.flags) + opts.flags.StringVar(&opts.configFile, flagDaemonConfigFile, defaultDaemonConfigFile, "") + opts.configFile = configFile + return opts +} - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - loadedConfig, err := loadDaemonCliConfig(c, flags, common, "/tmp/fooobarbaz") - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatalf("expected configuration %v, got nil", c) - } +func TestLoadDaemonCliConfigWithoutOverriding(t *testing.T) { + opts := defaultOptions("") + opts.common.Debug = true + + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) if !loadedConfig.Debug { t.Fatalf("expected debug to be copied from the common flags, got false") } } func TestLoadDaemonCliConfigWithTLS(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{ - TLS: true, - TLSOptions: &tlsconfig.Options{ - CAFile: "/tmp/ca.pem", - }, - } + opts := defaultOptions("") + opts.common.TLSOptions.CAFile = "/tmp/ca.pem" + opts.common.TLS = true - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - loadedConfig, err := loadDaemonCliConfig(c, flags, common, "/tmp/fooobarbaz") - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatalf("expected configuration %v, got nil", c) - } - if loadedConfig.CommonTLSOptions.CAFile != "/tmp/ca.pem" { - t.Fatalf("expected /tmp/ca.pem, got %s: %q", loadedConfig.CommonTLSOptions.CAFile, loadedConfig) - } + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) + assert.Equal(t, loadedConfig.CommonTLSOptions.CAFile, "/tmp/ca.pem") } func TestLoadDaemonCliConfigWithConflicts(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{} - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } - configFile := f.Name() - defer os.Remove(configFile) + tempFile := tempfile.NewTempFile(t, "config", `{"labels": ["l3=foo"]}`) + defer tempFile.Remove() + configFile := tempFile.Name() - f.Write([]byte(`{"labels": ["l3=foo"]}`)) - f.Close() + opts := defaultOptions(configFile) + flags := opts.flags - var labels []string + assert.NilError(t, flags.Set(flagDaemonConfigFile, configFile)) + assert.NilError(t, flags.Set("label", "l1=bar")) + assert.NilError(t, flags.Set("label", "l2=baz")) - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - flags.String([]string{daemonConfigFileFlag}, "", "") - flags.Var(opts.NewNamedListOptsRef("labels", &labels, opts.ValidateLabel), []string{"-label"}, "") - - flags.Set(daemonConfigFileFlag, configFile) - if err := flags.Set("-label", "l1=bar"); err != nil { - t.Fatal(err) - } - if err := flags.Set("-label", "l2=baz"); err != nil { - t.Fatal(err) - } - - _, err = loadDaemonCliConfig(c, flags, common, configFile) - if err == nil { - t.Fatalf("expected configuration error, got nil") - } - if !strings.Contains(err.Error(), "labels") { - t.Fatalf("expected labels conflict, got %v", err) - } + _, err := loadDaemonCliConfig(opts) + assert.Error(t, err, "as a flag and in the configuration file: labels") } func TestLoadDaemonCliConfigWithTLSVerify(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{ - TLSOptions: &tlsconfig.Options{ - CAFile: "/tmp/ca.pem", - }, - } + tempFile := tempfile.NewTempFile(t, "config", `{"tlsverify": true}`) + defer tempFile.Remove() - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } - configFile := f.Name() - defer os.Remove(configFile) + opts := defaultOptions(tempFile.Name()) + opts.common.TLSOptions.CAFile = "/tmp/ca.pem" - f.Write([]byte(`{"tlsverify": true}`)) - f.Close() - - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - flags.Bool([]string{"-tlsverify"}, false, "") - loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile) - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatalf("expected configuration %v, got nil", c) - } - - if !loadedConfig.TLS { - t.Fatalf("expected TLS enabled, got %q", loadedConfig) - } + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) + assert.Equal(t, loadedConfig.TLS, true) } func TestLoadDaemonCliConfigWithExplicitTLSVerifyFalse(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{ - TLSOptions: &tlsconfig.Options{ - CAFile: "/tmp/ca.pem", - }, - } + tempFile := tempfile.NewTempFile(t, "config", `{"tlsverify": false}`) + defer tempFile.Remove() - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } - configFile := f.Name() - defer os.Remove(configFile) + opts := defaultOptions(tempFile.Name()) + opts.common.TLSOptions.CAFile = "/tmp/ca.pem" - f.Write([]byte(`{"tlsverify": false}`)) - f.Close() - - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - flags.Bool([]string{"-tlsverify"}, false, "") - loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile) - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatalf("expected configuration %v, got nil", c) - } - - if !loadedConfig.TLS { - t.Fatalf("expected TLS enabled, got %q", loadedConfig) - } + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) + assert.Equal(t, loadedConfig.TLS, true) } func TestLoadDaemonCliConfigWithoutTLSVerify(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{ - TLSOptions: &tlsconfig.Options{ - CAFile: "/tmp/ca.pem", - }, - } + tempFile := tempfile.NewTempFile(t, "config", `{}`) + defer tempFile.Remove() - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } - configFile := f.Name() - defer os.Remove(configFile) + opts := defaultOptions(tempFile.Name()) + opts.common.TLSOptions.CAFile = "/tmp/ca.pem" - f.Write([]byte(`{}`)) - f.Close() - - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile) - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatalf("expected configuration %v, got nil", c) - } - - if loadedConfig.TLS { - t.Fatalf("expected TLS disabled, got %q", loadedConfig) - } + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) + assert.Equal(t, loadedConfig.TLS, false) } func TestLoadDaemonCliConfigWithLogLevel(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{} + tempFile := tempfile.NewTempFile(t, "config", `{"log-level": "warn"}`) + defer tempFile.Remove() - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } - configFile := f.Name() - defer os.Remove(configFile) - - f.Write([]byte(`{"log-level": "warn"}`)) - f.Close() - - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - flags.String([]string{"-log-level"}, "", "") - loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile) - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatalf("expected configuration %v, got nil", c) - } - if loadedConfig.LogLevel != "warn" { - t.Fatalf("expected warn log level, got %v", loadedConfig.LogLevel) - } - - if logrus.GetLevel() != logrus.WarnLevel { - t.Fatalf("expected warn log level, got %v", logrus.GetLevel()) - } + opts := defaultOptions(tempFile.Name()) + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) + assert.Equal(t, loadedConfig.LogLevel, "warn") + assert.Equal(t, logrus.GetLevel(), logrus.WarnLevel) } func TestLoadDaemonConfigWithEmbeddedOptions(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{} + content := `{"tlscacert": "/etc/certs/ca.pem", "log-driver": "syslog"}` + tempFile := tempfile.NewTempFile(t, "config", content) + defer tempFile.Remove() - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - flags.String([]string{"-tlscacert"}, "", "") - flags.String([]string{"-log-driver"}, "", "") - - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } - configFile := f.Name() - defer os.Remove(configFile) - - f.Write([]byte(`{"tlscacert": "/etc/certs/ca.pem", "log-driver": "syslog"}`)) - f.Close() - - loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile) - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatal("expected configuration, got nil") - } - if loadedConfig.CommonTLSOptions.CAFile != "/etc/certs/ca.pem" { - t.Fatalf("expected CA file path /etc/certs/ca.pem, got %v", loadedConfig.CommonTLSOptions.CAFile) - } - if loadedConfig.LogConfig.Type != "syslog" { - t.Fatalf("expected LogConfig type syslog, got %v", loadedConfig.LogConfig.Type) - } + opts := defaultOptions(tempFile.Name()) + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) + assert.Equal(t, loadedConfig.CommonTLSOptions.CAFile, "/etc/certs/ca.pem") + assert.Equal(t, loadedConfig.LogConfig.Type, "syslog") } func TestLoadDaemonConfigWithRegistryOptions(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{} - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - c.ServiceOptions.InstallCliFlags(flags, absentFromHelp) + content := `{ + "registry-mirrors": ["https://mirrors.docker.com"], + "insecure-registries": ["https://insecure.docker.com"] + }` + tempFile := tempfile.NewTempFile(t, "config", content) + defer tempFile.Remove() - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } - configFile := f.Name() - defer os.Remove(configFile) + opts := defaultOptions(tempFile.Name()) + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) - f.Write([]byte(`{"registry-mirrors": ["https://mirrors.docker.com"], "insecure-registries": ["https://insecure.docker.com"]}`)) - f.Close() - - loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile) - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatal("expected configuration, got nil") - } - - m := loadedConfig.Mirrors - if len(m) != 1 { - t.Fatalf("expected 1 mirror, got %d", len(m)) - } - - r := loadedConfig.InsecureRegistries - if len(r) != 1 { - t.Fatalf("expected 1 insecure registries, got %d", len(r)) - } + assert.Equal(t, len(loadedConfig.Mirrors), 1) + assert.Equal(t, len(loadedConfig.InsecureRegistries), 1) } diff --git a/cmd/dockerd/daemon_unix_test.go b/cmd/dockerd/daemon_unix_test.go index 2d8bebda08..28624a6e55 100644 --- a/cmd/dockerd/daemon_unix_test.go +++ b/cmd/dockerd/daemon_unix_test.go @@ -3,240 +3,110 @@ package main import ( - "io/ioutil" - "os" "testing" - cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/daemon" - "github.com/docker/docker/opts" - "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/testutil/assert" + "github.com/docker/docker/pkg/testutil/tempfile" ) func TestLoadDaemonCliConfigWithDaemonFlags(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{ - Debug: true, - LogLevel: "info", - } + content := `{"log-opts": {"max-size": "1k"}}` + tempFile := tempfile.NewTempFile(t, "config", content) + defer tempFile.Remove() - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } + opts := defaultOptions(tempFile.Name()) + opts.common.Debug = true + opts.common.LogLevel = "info" + assert.NilError(t, opts.flags.Set("selinux-enabled", "true")) - configFile := f.Name() - f.Write([]byte(`{"log-opts": {"max-size": "1k"}}`)) - f.Close() + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - flags.String([]string{daemonConfigFileFlag}, "", "") - flags.BoolVar(&c.EnableSelinuxSupport, []string{"-selinux-enabled"}, true, "") - flags.StringVar(&c.LogConfig.Type, []string{"-log-driver"}, "json-file", "") - flags.Var(opts.NewNamedMapOpts("log-opts", c.LogConfig.Config, nil), []string{"-log-opt"}, "") - flags.Set(daemonConfigFileFlag, configFile) - - loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile) - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatalf("expected configuration %v, got nil", c) - } - if !loadedConfig.Debug { - t.Fatalf("expected debug mode, got false") - } - if loadedConfig.LogLevel != "info" { - t.Fatalf("expected info log level, got %v", loadedConfig.LogLevel) - } - if !loadedConfig.EnableSelinuxSupport { - t.Fatalf("expected enabled selinux support, got disabled") - } - if loadedConfig.LogConfig.Type != "json-file" { - t.Fatalf("expected LogConfig type json-file, got %v", loadedConfig.LogConfig.Type) - } - if maxSize := loadedConfig.LogConfig.Config["max-size"]; maxSize != "1k" { - t.Fatalf("expected log max-size `1k`, got %s", maxSize) - } + assert.Equal(t, loadedConfig.Debug, true) + assert.Equal(t, loadedConfig.LogLevel, "info") + assert.Equal(t, loadedConfig.EnableSelinuxSupport, true) + assert.Equal(t, loadedConfig.LogConfig.Type, "json-file") + assert.Equal(t, loadedConfig.LogConfig.Config["max-size"], "1k") } func TestLoadDaemonConfigWithNetwork(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{} - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - flags.String([]string{"-bip"}, "", "") - flags.String([]string{"-ip"}, "", "") + content := `{"bip": "127.0.0.2", "ip": "127.0.0.1"}` + tempFile := tempfile.NewTempFile(t, "config", content) + defer tempFile.Remove() - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } + opts := defaultOptions(tempFile.Name()) + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) - configFile := f.Name() - f.Write([]byte(`{"bip": "127.0.0.2", "ip": "127.0.0.1"}`)) - f.Close() - - loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile) - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatalf("expected configuration %v, got nil", c) - } - if loadedConfig.IP != "127.0.0.2" { - t.Fatalf("expected IP 127.0.0.2, got %v", loadedConfig.IP) - } - if loadedConfig.DefaultIP.String() != "127.0.0.1" { - t.Fatalf("expected DefaultIP 127.0.0.1, got %s", loadedConfig.DefaultIP) - } + assert.Equal(t, loadedConfig.IP, "127.0.0.2") + assert.Equal(t, loadedConfig.DefaultIP.String(), "127.0.0.1") } func TestLoadDaemonConfigWithMapOptions(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{} - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - - flags.Var(opts.NewNamedMapOpts("cluster-store-opts", c.ClusterOpts, nil), []string{"-cluster-store-opt"}, "") - flags.Var(opts.NewNamedMapOpts("log-opts", c.LogConfig.Config, nil), []string{"-log-opt"}, "") - - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } - - configFile := f.Name() - f.Write([]byte(`{ + content := `{ "cluster-store-opts": {"kv.cacertfile": "/var/lib/docker/discovery_certs/ca.pem"}, "log-opts": {"tag": "test"} -}`)) - f.Close() +}` + tempFile := tempfile.NewTempFile(t, "config", content) + defer tempFile.Remove() - loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile) - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatal("expected configuration, got nil") - } - if loadedConfig.ClusterOpts == nil { - t.Fatal("expected cluster options, got nil") - } + opts := defaultOptions(tempFile.Name()) + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) + assert.NotNil(t, loadedConfig.ClusterOpts) expectedPath := "/var/lib/docker/discovery_certs/ca.pem" - if caPath := loadedConfig.ClusterOpts["kv.cacertfile"]; caPath != expectedPath { - t.Fatalf("expected %s, got %s", expectedPath, caPath) - } - - if loadedConfig.LogConfig.Config == nil { - t.Fatal("expected log config options, got nil") - } - if tag := loadedConfig.LogConfig.Config["tag"]; tag != "test" { - t.Fatalf("expected log tag `test`, got %s", tag) - } + assert.Equal(t, loadedConfig.ClusterOpts["kv.cacertfile"], expectedPath) + assert.NotNil(t, loadedConfig.LogConfig.Config) + assert.Equal(t, loadedConfig.LogConfig.Config["tag"], "test") } func TestLoadDaemonConfigWithTrueDefaultValues(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{} - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - flags.BoolVar(&c.EnableUserlandProxy, []string{"-userland-proxy"}, true, "") + content := `{ "userland-proxy": false }` + tempFile := tempfile.NewTempFile(t, "config", content) + defer tempFile.Remove() - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } + opts := defaultOptions(tempFile.Name()) + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) + assert.NotNil(t, loadedConfig.ClusterOpts) - if err := flags.ParseFlags([]string{}, false); err != nil { - t.Fatal(err) - } - - configFile := f.Name() - f.Write([]byte(`{ - "userland-proxy": false -}`)) - f.Close() - - loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile) - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatal("expected configuration, got nil") - } - - if loadedConfig.EnableUserlandProxy { - t.Fatal("expected userland proxy to be disabled, got enabled") - } + assert.Equal(t, loadedConfig.EnableUserlandProxy, false) // make sure reloading doesn't generate configuration // conflicts after normalizing boolean values. - err = daemon.ReloadConfiguration(configFile, flags, func(reloadedConfig *daemon.Config) { - if reloadedConfig.EnableUserlandProxy { - t.Fatal("expected userland proxy to be disabled, got enabled") - } - }) - if err != nil { - t.Fatal(err) + reload := func(reloadedConfig *daemon.Config) { + assert.Equal(t, reloadedConfig.EnableUserlandProxy, false) } + assert.NilError(t, daemon.ReloadConfiguration(opts.configFile, opts.flags, reload)) } func TestLoadDaemonConfigWithTrueDefaultValuesLeaveDefaults(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{} - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - flags.BoolVar(&c.EnableUserlandProxy, []string{"-userland-proxy"}, true, "") + tempFile := tempfile.NewTempFile(t, "config", `{}`) + defer tempFile.Remove() - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } + opts := defaultOptions(tempFile.Name()) + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) + assert.NotNil(t, loadedConfig.ClusterOpts) - if err := flags.ParseFlags([]string{}, false); err != nil { - t.Fatal(err) - } - - configFile := f.Name() - f.Write([]byte(`{}`)) - f.Close() - - loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile) - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatal("expected configuration, got nil") - } - - if !loadedConfig.EnableUserlandProxy { - t.Fatal("expected userland proxy to be enabled, got disabled") - } + assert.Equal(t, loadedConfig.EnableUserlandProxy, true) } func TestLoadDaemonConfigWithLegacyRegistryOptions(t *testing.T) { - c := &daemon.Config{} - common := &cliflags.CommonFlags{} - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - c.ServiceOptions.InstallCliFlags(flags, absentFromHelp) + content := `{"disable-legacy-registry": true}` + tempFile := tempfile.NewTempFile(t, "config", content) + defer tempFile.Remove() - f, err := ioutil.TempFile("", "docker-config-") - if err != nil { - t.Fatal(err) - } - configFile := f.Name() - defer os.Remove(configFile) - - f.Write([]byte(`{"disable-legacy-registry": true}`)) - f.Close() - - loadedConfig, err := loadDaemonCliConfig(c, flags, common, configFile) - if err != nil { - t.Fatal(err) - } - if loadedConfig == nil { - t.Fatal("expected configuration, got nil") - } - - if !loadedConfig.V2Only { - t.Fatal("expected disable-legacy-registry to be true, got false") - } + opts := defaultOptions(tempFile.Name()) + loadedConfig, err := loadDaemonCliConfig(opts) + assert.NilError(t, err) + assert.NotNil(t, loadedConfig) + assert.Equal(t, loadedConfig.V2Only, true) } diff --git a/cmd/dockerd/docker.go b/cmd/dockerd/docker.go index b1c9b803a1..3399597aef 100644 --- a/cmd/dockerd/docker.go +++ b/cmd/dockerd/docker.go @@ -5,72 +5,76 @@ import ( "os" "github.com/Sirupsen/logrus" + "github.com/docker/docker/cli" + cliflags "github.com/docker/docker/cli/flags" + "github.com/docker/docker/daemon" "github.com/docker/docker/dockerversion" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/term" "github.com/docker/docker/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" ) -var ( - daemonCli = NewDaemonCli() - flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") - flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") -) +type daemonOptions struct { + version bool + configFile string + daemonConfig *daemon.Config + common *cliflags.CommonOptions + flags *pflag.FlagSet +} -func main() { - if reexec.Init() { - return +func newDaemonCommand() *cobra.Command { + opts := daemonOptions{ + daemonConfig: daemon.NewConfig(), + common: cliflags.NewCommonOptions(), } - // Set terminal emulation based on platform as required. - _, stdout, stderr := term.StdStreams() - - logrus.SetOutput(stderr) - - flag.Merge(flag.CommandLine, daemonCli.commonFlags.FlagSet) - - flag.Usage = func() { - fmt.Fprint(stdout, "Usage: dockerd [OPTIONS]\n\n") - fmt.Fprint(stdout, "A self-sufficient runtime for containers.\n\nOptions:\n") - - flag.CommandLine.SetOutput(stdout) - flag.PrintDefaults() - } - flag.CommandLine.ShortUsage = func() { - fmt.Fprint(stderr, "\nUsage:\tdockerd [OPTIONS]\n") + cmd := &cobra.Command{ + Use: "dockerd [OPTIONS]", + Short: "A self-sufficient runtime for containers.", + SilenceUsage: true, + SilenceErrors: true, + Args: cli.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + opts.flags = cmd.Flags() + return runDaemon(opts) + }, } + cli.SetupRootCommand(cmd) - if err := flag.CommandLine.ParseFlags(os.Args[1:], false); err != nil { - os.Exit(1) - } + flags := cmd.Flags() + flags.BoolVarP(&opts.version, "version", "v", false, "Print version information and quit") + flags.StringVar(&opts.configFile, flagDaemonConfigFile, defaultDaemonConfigFile, "Daemon configuration file") + opts.common.InstallFlags(flags) + opts.daemonConfig.InstallFlags(flags) + installServiceFlags(flags) - if *flVersion { + return cmd +} + +func runDaemon(opts daemonOptions) error { + if opts.version { showVersion() - return + return nil } - if *flHelp { - // if global flag --help is present, regardless of what other options and commands there are, - // just print the usage. - flag.Usage() - return - } + daemonCli := NewDaemonCli() // On Windows, this may be launching as a service or with an option to // register the service. - stop, err := initService() + stop, err := initService(daemonCli) if err != nil { logrus.Fatal(err) } - if !stop { - err = daemonCli.start() - notifyShutdown(err) - if err != nil { - logrus.Fatal(err) - } + if stop { + return nil } + + err = daemonCli.start(opts) + notifyShutdown(err) + return err } func showVersion() { @@ -80,3 +84,20 @@ func showVersion() { fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit) } } + +func main() { + if reexec.Init() { + return + } + + // Set terminal emulation based on platform as required. + _, stdout, stderr := term.StdStreams() + logrus.SetOutput(stderr) + + cmd := newDaemonCommand() + cmd.SetOutput(stdout) + if err := cmd.Execute(); err != nil { + fmt.Fprintf(stderr, "%s\n", err) + os.Exit(1) + } +} diff --git a/cmd/dockerd/service_unsupported.go b/cmd/dockerd/service_unsupported.go index dd53802a5e..64ad7fcaa0 100644 --- a/cmd/dockerd/service_unsupported.go +++ b/cmd/dockerd/service_unsupported.go @@ -2,6 +2,13 @@ package main -func initService() (bool, error) { +import ( + "github.com/spf13/pflag" +) + +func initService(daemonCli *DaemonCli) (bool, error) { return false, nil } + +func installServiceFlags(flags *pflag.FlagSet) { +} diff --git a/cmd/dockerd/service_windows.go b/cmd/dockerd/service_windows.go index e78dad20c5..3761188e26 100644 --- a/cmd/dockerd/service_windows.go +++ b/cmd/dockerd/service_windows.go @@ -11,7 +11,7 @@ import ( "syscall" "github.com/Sirupsen/logrus" - flag "github.com/docker/docker/pkg/mflag" + "github.com/spf13/pflag" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/debug" @@ -20,10 +20,10 @@ import ( ) var ( - flServiceName = flag.String([]string{"-service-name"}, "docker", "Set the Windows service name") - flRegisterService = flag.Bool([]string{"-register-service"}, false, "Register the service and exit") - flUnregisterService = flag.Bool([]string{"-unregister-service"}, false, "Unregister the service and exit") - flRunService = flag.Bool([]string{"-run-service"}, false, "") + flServiceName *string + flRegisterService *bool + flUnregisterService *bool + flRunService *bool setStdHandle = syscall.NewLazyDLL("kernel32.dll").NewProc("SetStdHandle") oldStderr syscall.Handle @@ -44,9 +44,17 @@ const ( eventExtraOffset = 10 // Add this to any event to get a string that supports extended data ) +func installServiceFlags(flags *pflag.FlagSet) { + flServiceName = flags.String("service-name", "docker", "Set the Windows service name") + flRegisterService = flags.Bool("register-service", false, "Register the service and exit") + flUnregisterService = flags.Bool("unregister-service", false, "Unregister the service and exit") + flRunService = flags.Bool("run-service", false, "") +} + type handler struct { - tosvc chan bool - fromsvc chan error + tosvc chan bool + fromsvc chan error + daemonCli *DaemonCli } type etwHook struct { @@ -203,7 +211,7 @@ func unregisterService() error { return nil } -func initService() (bool, error) { +func initService(daemonCli *DaemonCli) (bool, error) { if *flUnregisterService { if *flRegisterService { return true, errors.New("--register-service and --unregister-service cannot be used together") @@ -225,8 +233,9 @@ func initService() (bool, error) { } h := &handler{ - tosvc: make(chan bool), - fromsvc: make(chan error), + tosvc: make(chan bool), + fromsvc: make(chan error), + daemonCli: daemonCli, } var log *eventlog.Log @@ -261,7 +270,7 @@ func initService() (bool, error) { func (h *handler) started() error { // This must be delayed until daemonCli initializes Config.Root - err := initPanicFile(filepath.Join(daemonCli.Config.Root, "panic.log")) + err := initPanicFile(filepath.Join(h.daemonCli.Config.Root, "panic.log")) if err != nil { return err } @@ -298,12 +307,12 @@ Loop: case c := <-r: switch c.Cmd { case svc.Cmd(windows.SERVICE_CONTROL_PARAMCHANGE): - daemonCli.reloadConfig() + h.daemonCli.reloadConfig() case svc.Interrogate: s <- c.CurrentStatus case svc.Stop, svc.Shutdown: s <- svc.Status{State: svc.StopPending, Accepts: 0} - daemonCli.stop() + h.daemonCli.stop() } } } diff --git a/daemon/config.go b/daemon/config.go index a00f338e44..588c698213 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -6,15 +6,16 @@ import ( "fmt" "io" "io/ioutil" + "runtime" "strings" "sync" "github.com/Sirupsen/logrus" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/discovery" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/registry" "github.com/imdario/mergo" + "github.com/spf13/pflag" ) const ( @@ -145,39 +146,37 @@ type CommonConfig struct { valuesSet map[string]interface{} } -// InstallCommonFlags adds command-line options to the top-level flag parser for -// the current process. -// Subsequent calls to `flag.Parse` will populate config with values parsed -// from the command-line. -func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) string) { +// InstallCommonFlags adds flags to the pflag.FlagSet to configure the daemon +func (config *Config) InstallCommonFlags(flags *pflag.FlagSet) { var maxConcurrentDownloads, maxConcurrentUploads int - config.ServiceOptions.InstallCliFlags(cmd, usageFn) + config.ServiceOptions.InstallCliFlags(flags) - cmd.Var(opts.NewNamedListOptsRef("storage-opts", &config.GraphOptions, nil), []string{"-storage-opt"}, usageFn("Storage driver options")) - cmd.Var(opts.NewNamedListOptsRef("authorization-plugins", &config.AuthorizationPlugins, nil), []string{"-authorization-plugin"}, usageFn("Authorization plugins to load")) - cmd.Var(opts.NewNamedListOptsRef("exec-opts", &config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Runtime execution options")) - cmd.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, defaultPidFile, usageFn("Path to use for daemon PID file")) - cmd.StringVar(&config.Root, []string{"g", "-graph"}, defaultGraph, usageFn("Root of the Docker runtime")) - cmd.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, usageFn("--restart on the daemon has been deprecated in favor of --restart policies on docker run")) - cmd.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", usageFn("Storage driver to use")) - cmd.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, usageFn("Set the containers network MTU")) - cmd.BoolVar(&config.RawLogs, []string{"-raw-logs"}, false, usageFn("Full timestamps without ANSI coloring")) + flags.Var(opts.NewNamedListOptsRef("storage-opts", &config.GraphOptions, nil), "storage-opt", "Storage driver options") + flags.Var(opts.NewNamedListOptsRef("authorization-plugins", &config.AuthorizationPlugins, nil), "authorization-plugin", "Authorization plugins to load") + flags.Var(opts.NewNamedListOptsRef("exec-opts", &config.ExecOptions, nil), "exec-opt", "Runtime execution options") + flags.StringVarP(&config.Pidfile, "pidfile", "p", defaultPidFile, "Path to use for daemon PID file") + flags.StringVarP(&config.Root, "graph", "g", defaultGraph, "Root of the Docker runtime") + flags.BoolVarP(&config.AutoRestart, "restart", "r", true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run") + flags.MarkDeprecated("restart", "Please use a restart policy on docker run") + flags.StringVarP(&config.GraphDriver, "storage-driver", "s", "", "Storage driver to use") + flags.IntVar(&config.Mtu, "mtu", 0, "Set the containers network MTU") + flags.BoolVar(&config.RawLogs, "raw-logs", false, "Full timestamps without ANSI coloring") // FIXME: why the inconsistency between "hosts" and "sockets"? - cmd.Var(opts.NewListOptsRef(&config.DNS, opts.ValidateIPAddress), []string{"#dns", "-dns"}, usageFn("DNS server to use")) - cmd.Var(opts.NewNamedListOptsRef("dns-opts", &config.DNSOptions, nil), []string{"-dns-opt"}, usageFn("DNS options to use")) - cmd.Var(opts.NewListOptsRef(&config.DNSSearch, opts.ValidateDNSSearch), []string{"-dns-search"}, usageFn("DNS search domains to use")) - cmd.Var(opts.NewNamedListOptsRef("labels", &config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon")) - cmd.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", usageFn("Default driver for container logs")) - cmd.Var(opts.NewNamedMapOpts("log-opts", config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Default log driver options for containers")) - cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address or interface name to advertise")) - cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("URL of the distributed storage backend")) - cmd.Var(opts.NewNamedMapOpts("cluster-store-opts", config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options")) - cmd.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API")) - cmd.IntVar(&maxConcurrentDownloads, []string{"-max-concurrent-downloads"}, defaultMaxConcurrentDownloads, usageFn("Set the max concurrent downloads for each pull")) - cmd.IntVar(&maxConcurrentUploads, []string{"-max-concurrent-uploads"}, defaultMaxConcurrentUploads, usageFn("Set the max concurrent uploads for each push")) + flags.Var(opts.NewListOptsRef(&config.DNS, opts.ValidateIPAddress), "dns", "DNS server to use") + flags.Var(opts.NewNamedListOptsRef("dns-opts", &config.DNSOptions, nil), "dns-opt", "DNS options to use") + flags.Var(opts.NewListOptsRef(&config.DNSSearch, opts.ValidateDNSSearch), "dns-search", "DNS search domains to use") + flags.Var(opts.NewNamedListOptsRef("labels", &config.Labels, opts.ValidateLabel), "label", "Set key=value labels to the daemon") + flags.StringVar(&config.LogConfig.Type, "log-driver", "json-file", "Default driver for container logs") + flags.Var(opts.NewNamedMapOpts("log-opts", config.LogConfig.Config, nil), "log-opt", "Default log driver options for containers") + flags.StringVar(&config.ClusterAdvertise, "cluster-advertise", "", "Address or interface name to advertise") + flags.StringVar(&config.ClusterStore, "cluster-store", "", "URL of the distributed storage backend") + flags.Var(opts.NewNamedMapOpts("cluster-store-opts", config.ClusterOpts, nil), "cluster-store-opt", "Set cluster store options") + flags.StringVar(&config.CorsHeaders, "api-cors-header", "", "Set CORS headers in the remote API") + flags.IntVar(&maxConcurrentDownloads, "max-concurrent-downloads", defaultMaxConcurrentDownloads, "Set the max concurrent downloads for each pull") + flags.IntVar(&maxConcurrentUploads, "max-concurrent-uploads", defaultMaxConcurrentUploads, "Set the max concurrent uploads for each push") - cmd.StringVar(&config.SwarmDefaultAdvertiseAddr, []string{"-swarm-default-advertise-addr"}, "", usageFn("Set default address or interface for swarm advertised address")) + flags.StringVar(&config.SwarmDefaultAdvertiseAddr, "swarm-default-advertise-addr", "", "Set default address or interface for swarm advertised address") config.MaxConcurrentDownloads = &maxConcurrentDownloads config.MaxConcurrentUploads = &maxConcurrentUploads @@ -193,6 +192,18 @@ func (config *Config) IsValueSet(name string) bool { return ok } +// NewConfig returns a new fully initialized Config struct +func NewConfig() *Config { + config := Config{} + config.LogConfig.Config = make(map[string]string) + config.ClusterOpts = make(map[string]string) + + if runtime.GOOS != "linux" { + config.V2Only = true + } + return &config +} + func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) { if clusterAdvertise == "" { return "", errDiscoveryDisabled @@ -209,7 +220,7 @@ func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (strin } // ReloadConfiguration reads the configuration in the host and reloads the daemon and server. -func ReloadConfiguration(configFile string, flags *flag.FlagSet, reload func(*Config)) error { +func ReloadConfiguration(configFile string, flags *pflag.FlagSet, reload func(*Config)) error { logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile) newConfig, err := getConflictFreeConfiguration(configFile, flags) if err != nil { @@ -234,7 +245,7 @@ type boolValue interface { // loads the file configuration in an isolated structure, // and merges the configuration provided from flags on top // if there are no conflicts. -func MergeDaemonConfigurations(flagsConfig *Config, flags *flag.FlagSet, configFile string) (*Config, error) { +func MergeDaemonConfigurations(flagsConfig *Config, flags *pflag.FlagSet, configFile string) (*Config, error) { fileConfig, err := getConflictFreeConfiguration(configFile, flags) if err != nil { return nil, err @@ -261,7 +272,7 @@ func MergeDaemonConfigurations(flagsConfig *Config, flags *flag.FlagSet, configF // getConflictFreeConfiguration loads the configuration from a JSON file. // It compares that configuration with the one provided by the flags, // and returns an error if there are conflicts. -func getConflictFreeConfiguration(configFile string, flags *flag.FlagSet) (*Config, error) { +func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Config, error) { b, err := ioutil.ReadFile(configFile) if err != nil { return nil, err @@ -289,7 +300,7 @@ func getConflictFreeConfiguration(configFile string, flags *flag.FlagSet) (*Conf // TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers. namedOptions := make(map[string]interface{}) for key, value := range configSet { - f := flags.Lookup("-" + key) + f := flags.Lookup(key) if f == nil { // ignore named flags that don't match namedOptions[key] = value continue @@ -301,7 +312,7 @@ func getConflictFreeConfiguration(configFile string, flags *flag.FlagSet) (*Conf } if len(namedOptions) > 0 { // set also default for mergeVal flags that are boolValue at the same time. - flags.VisitAll(func(f *flag.Flag) { + flags.VisitAll(func(f *pflag.Flag) { if opt, named := f.Value.(opts.NamedOption); named { v, set := namedOptions[opt.Name()] _, boolean := f.Value.(boolValue) @@ -339,12 +350,11 @@ func configValuesSet(config map[string]interface{}) map[string]interface{} { // findConfigurationConflicts iterates over the provided flags searching for // duplicated configurations and unknown keys. It returns an error with all the conflicts if // it finds any. -func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagSet) error { +func findConfigurationConflicts(config map[string]interface{}, flags *pflag.FlagSet) error { // 1. Search keys from the file that we don't recognize as flags. unknownKeys := make(map[string]interface{}) for key, value := range config { - flagName := "-" + key - if flag := flags.Lookup(flagName); flag == nil { + if flag := flags.Lookup(key); flag == nil { unknownKeys[key] = value } } @@ -352,7 +362,7 @@ func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagS // 2. Discard values that implement NamedOption. // Their configuration name differs from their flag name, like `labels` and `label`. if len(unknownKeys) > 0 { - unknownNamedConflicts := func(f *flag.Flag) { + unknownNamedConflicts := func(f *pflag.Flag) { if namedOption, ok := f.Value.(opts.NamedOption); ok { if _, valid := unknownKeys[namedOption.Name()]; valid { delete(unknownKeys, namedOption.Name()) @@ -376,17 +386,15 @@ func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagS } // 3. Search keys that are present as a flag and as a file option. - duplicatedConflicts := func(f *flag.Flag) { + duplicatedConflicts := func(f *pflag.Flag) { // search option name in the json configuration payload if the value is a named option if namedOption, ok := f.Value.(opts.NamedOption); ok { if optsValue, ok := config[namedOption.Name()]; ok { conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue)) } } else { - // search flag name in the json configuration payload without trailing dashes - for _, name := range f.Names { - name = strings.TrimLeft(name, "-") - + // search flag name in the json configuration payload + for _, name := range []string{f.Name, f.Shorthand} { if value, ok := config[name]; ok { conflicts = append(conflicts, printConflict(name, f.Value.String(), value)) break diff --git a/daemon/config_experimental.go b/daemon/config_experimental.go index ceb7c38225..55e87986b1 100644 --- a/daemon/config_experimental.go +++ b/daemon/config_experimental.go @@ -2,7 +2,9 @@ package daemon -import flag "github.com/docker/docker/pkg/mflag" +import ( + "github.com/spf13/pflag" +) -func (config *Config) attachExperimentalFlags(cmd *flag.FlagSet, usageFn func(string) string) { +func (config *Config) attachExperimentalFlags(cmd *pflag.FlagSet) { } diff --git a/daemon/config_solaris.go b/daemon/config_solaris.go index e59d0514df..e99d1bbf11 100644 --- a/daemon/config_solaris.go +++ b/daemon/config_solaris.go @@ -1,7 +1,7 @@ package daemon import ( - flag "github.com/docker/docker/pkg/mflag" + "github.com/spf13/pflag" ) var ( @@ -28,14 +28,12 @@ type bridgeConfig struct { // InstallFlags adds command-line options to the top-level flag parser for // the current process. -// Subsequent calls to `flag.Parse` will populate config with values parsed -// from the command-line. -func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) string) { +func (config *Config) InstallFlags(flags *pflag.FlagSet) { // First handle install flags which are consistent cross-platform - config.InstallCommonFlags(cmd, usageFn) + config.InstallCommonFlags(flags) // Then platform-specific install flags - config.attachExperimentalFlags(cmd, usageFn) + config.attachExperimentalFlags(flags) } // GetExecRoot returns the user configured Exec-root diff --git a/daemon/config_stub.go b/daemon/config_stub.go index 796e6b6e4e..fb64813e23 100644 --- a/daemon/config_stub.go +++ b/daemon/config_stub.go @@ -2,7 +2,9 @@ package daemon -import flag "github.com/docker/docker/pkg/mflag" +import ( + "github.com/spf13/pflag" +) -func (config *Config) attachExperimentalFlags(cmd *flag.FlagSet, usageFn func(string) string) { +func (config *Config) attachExperimentalFlags(cmd *pflag.FlagSet) { } diff --git a/daemon/config_test.go b/daemon/config_test.go index 0375c1ae21..3e9271aece 100644 --- a/daemon/config_test.go +++ b/daemon/config_test.go @@ -7,7 +7,8 @@ import ( "testing" "github.com/docker/docker/opts" - "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/testutil/assert" + "github.com/spf13/pflag" ) func TestDaemonConfigurationMerge(t *testing.T) { @@ -87,42 +88,26 @@ func TestParseClusterAdvertiseSettings(t *testing.T) { func TestFindConfigurationConflicts(t *testing.T) { config := map[string]interface{}{"authorization-plugins": "foobar"} - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) + flags := pflag.NewFlagSet("test", pflag.ContinueOnError) - flags.String([]string{"-authorization-plugins"}, "", "") - if err := flags.Set("-authorization-plugins", "asdf"); err != nil { - t.Fatal(err) - } + flags.String("authorization-plugins", "", "") + assert.NilError(t, flags.Set("authorization-plugins", "asdf")) - err := findConfigurationConflicts(config, flags) - if err == nil { - t.Fatal("expected error, got nil") - } - if !strings.Contains(err.Error(), "authorization-plugins: (from flag: asdf, from file: foobar)") { - t.Fatalf("expected authorization-plugins conflict, got %v", err) - } + assert.Error(t, + findConfigurationConflicts(config, flags), + "authorization-plugins: (from flag: asdf, from file: foobar)") } func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) { config := map[string]interface{}{"hosts": []string{"qwer"}} - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) + flags := pflag.NewFlagSet("test", pflag.ContinueOnError) var hosts []string - flags.Var(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to") - if err := flags.Set("-host", "tcp://127.0.0.1:4444"); err != nil { - t.Fatal(err) - } - if err := flags.Set("H", "unix:///var/run/docker.sock"); err != nil { - t.Fatal(err) - } + flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), "host", "H", "Daemon socket(s) to connect to") + assert.NilError(t, flags.Set("host", "tcp://127.0.0.1:4444")) + assert.NilError(t, flags.Set("host", "unix:///var/run/docker.sock")) - err := findConfigurationConflicts(config, flags) - if err == nil { - t.Fatal("expected error, got nil") - } - if !strings.Contains(err.Error(), "hosts") { - t.Fatalf("expected hosts conflict, got %v", err) - } + assert.Error(t, findConfigurationConflicts(config, flags), "hosts") } func TestDaemonConfigurationMergeConflicts(t *testing.T) { @@ -135,8 +120,8 @@ func TestDaemonConfigurationMergeConflicts(t *testing.T) { f.Write([]byte(`{"debug": true}`)) f.Close() - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - flags.Bool([]string{"debug"}, false, "") + flags := pflag.NewFlagSet("test", pflag.ContinueOnError) + flags.Bool("debug", false, "") flags.Set("debug", "false") _, err = MergeDaemonConfigurations(&Config{}, flags, configFile) @@ -158,8 +143,8 @@ func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) { f.Write([]byte(`{"tlscacert": "/etc/certificates/ca.pem"}`)) f.Close() - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - flags.String([]string{"tlscacert"}, "", "") + flags := pflag.NewFlagSet("test", pflag.ContinueOnError) + flags.String("tlscacert", "", "") flags.Set("tlscacert", "~/.docker/ca.pem") _, err = MergeDaemonConfigurations(&Config{}, flags, configFile) @@ -173,9 +158,9 @@ func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) { func TestFindConfigurationConflictsWithUnknownKeys(t *testing.T) { config := map[string]interface{}{"tls-verify": "true"} - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) + flags := pflag.NewFlagSet("test", pflag.ContinueOnError) - flags.Bool([]string{"-tlsverify"}, false, "") + flags.Bool("tlsverify", false, "") err := findConfigurationConflicts(config, flags) if err == nil { t.Fatal("expected error, got nil") @@ -188,18 +173,15 @@ func TestFindConfigurationConflictsWithUnknownKeys(t *testing.T) { func TestFindConfigurationConflictsWithMergedValues(t *testing.T) { var hosts []string config := map[string]interface{}{"hosts": "tcp://127.0.0.1:2345"} - base := mflag.NewFlagSet("base", mflag.ContinueOnError) - base.Var(opts.NewNamedListOptsRef("hosts", &hosts, nil), []string{"H", "-host"}, "") - - flags := mflag.NewFlagSet("test", mflag.ContinueOnError) - mflag.Merge(flags, base) + flags := pflag.NewFlagSet("base", pflag.ContinueOnError) + flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, nil), "host", "H", "") err := findConfigurationConflicts(config, flags) if err != nil { t.Fatal(err) } - flags.Set("-host", "unix:///var/run/docker.sock") + flags.Set("host", "unix:///var/run/docker.sock") err = findConfigurationConflicts(config, flags) if err == nil { t.Fatal("expected error, got nil") diff --git a/daemon/config_unix.go b/daemon/config_unix.go index 24548a428e..10b5a0da28 100644 --- a/daemon/config_unix.go +++ b/daemon/config_unix.go @@ -7,10 +7,10 @@ import ( "net" "github.com/docker/docker/opts" - flag "github.com/docker/docker/pkg/mflag" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/engine-api/types" - "github.com/docker/go-units" + units "github.com/docker/go-units" + "github.com/spf13/pflag" ) var ( @@ -56,44 +56,43 @@ type bridgeConfig struct { InterContainerCommunication bool `json:"icc,omitempty"` } -// InstallFlags adds command-line options to the top-level flag parser for -// the current process. -// Subsequent calls to `flag.Parse` will populate config with values parsed -// from the command-line. -func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) string) { +// InstallFlags adds flags to the pflag.FlagSet to configure the daemon +func (config *Config) InstallFlags(flags *pflag.FlagSet) { // First handle install flags which are consistent cross-platform - config.InstallCommonFlags(cmd, usageFn) + config.InstallCommonFlags(flags) + + config.Ulimits = make(map[string]*units.Ulimit) + config.Runtimes = make(map[string]types.Runtime) // Then platform-specific install flags - cmd.BoolVar(&config.EnableSelinuxSupport, []string{"-selinux-enabled"}, false, usageFn("Enable selinux support")) - cmd.StringVar(&config.SocketGroup, []string{"G", "-group"}, "docker", usageFn("Group for the unix socket")) - config.Ulimits = make(map[string]*units.Ulimit) - cmd.Var(runconfigopts.NewUlimitOpt(&config.Ulimits), []string{"-default-ulimit"}, usageFn("Default ulimits for containers")) - cmd.BoolVar(&config.bridgeConfig.EnableIPTables, []string{"#iptables", "-iptables"}, true, usageFn("Enable addition of iptables rules")) - cmd.BoolVar(&config.bridgeConfig.EnableIPForward, []string{"#ip-forward", "-ip-forward"}, true, usageFn("Enable net.ipv4.ip_forward")) - cmd.BoolVar(&config.bridgeConfig.EnableIPMasq, []string{"-ip-masq"}, true, usageFn("Enable IP masquerading")) - cmd.BoolVar(&config.bridgeConfig.EnableIPv6, []string{"-ipv6"}, false, usageFn("Enable IPv6 networking")) - cmd.StringVar(&config.ExecRoot, []string{"-exec-root"}, defaultExecRoot, usageFn("Root directory for execution state files")) - cmd.StringVar(&config.bridgeConfig.IP, []string{"#bip", "-bip"}, "", usageFn("Specify network bridge IP")) - cmd.StringVar(&config.bridgeConfig.Iface, []string{"b", "-bridge"}, "", usageFn("Attach containers to a network bridge")) - cmd.StringVar(&config.bridgeConfig.FixedCIDR, []string{"-fixed-cidr"}, "", usageFn("IPv4 subnet for fixed IPs")) - cmd.StringVar(&config.bridgeConfig.FixedCIDRv6, []string{"-fixed-cidr-v6"}, "", usageFn("IPv6 subnet for fixed IPs")) - cmd.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv4, ""), []string{"-default-gateway"}, usageFn("Container default gateway IPv4 address")) - cmd.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv6, ""), []string{"-default-gateway-v6"}, usageFn("Container default gateway IPv6 address")) - cmd.BoolVar(&config.bridgeConfig.InterContainerCommunication, []string{"#icc", "-icc"}, true, usageFn("Enable inter-container communication")) - cmd.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultIP, "0.0.0.0"), []string{"#ip", "-ip"}, usageFn("Default IP when binding container ports")) - cmd.BoolVar(&config.bridgeConfig.EnableUserlandProxy, []string{"-userland-proxy"}, true, usageFn("Use userland proxy for loopback traffic")) - cmd.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, usageFn("Enable CORS headers in the remote API, this is deprecated by --api-cors-header")) - cmd.StringVar(&config.CgroupParent, []string{"-cgroup-parent"}, "", usageFn("Set parent cgroup for all containers")) - cmd.StringVar(&config.RemappedRoot, []string{"-userns-remap"}, "", usageFn("User/Group setting for user namespaces")) - cmd.StringVar(&config.ContainerdAddr, []string{"-containerd"}, "", usageFn("Path to containerd socket")) - cmd.BoolVar(&config.LiveRestoreEnabled, []string{"-live-restore"}, false, usageFn("Enable live restore of docker when containers are still running")) - config.Runtimes = make(map[string]types.Runtime) - cmd.Var(runconfigopts.NewNamedRuntimeOpt("runtimes", &config.Runtimes, stockRuntimeName), []string{"-add-runtime"}, usageFn("Register an additional OCI compatible runtime")) - cmd.StringVar(&config.DefaultRuntime, []string{"-default-runtime"}, stockRuntimeName, usageFn("Default OCI runtime for containers")) - cmd.IntVar(&config.OOMScoreAdjust, []string{"-oom-score-adjust"}, -500, usageFn("Set the oom_score_adj for the daemon")) + flags.BoolVar(&config.EnableSelinuxSupport, "selinux-enabled", false, "Enable selinux support") + flags.StringVarP(&config.SocketGroup, "group", "G", "docker", "Group for the unix socket") + flags.Var(runconfigopts.NewUlimitOpt(&config.Ulimits), "default-ulimit", "Default ulimits for containers") + flags.BoolVar(&config.bridgeConfig.EnableIPTables, "iptables", true, "Enable addition of iptables rules") + flags.BoolVar(&config.bridgeConfig.EnableIPForward, "ip-forward", true, "Enable net.ipv4.ip_forward") + flags.BoolVar(&config.bridgeConfig.EnableIPMasq, "ip-masq", true, "Enable IP masquerading") + flags.BoolVar(&config.bridgeConfig.EnableIPv6, "ipv6", false, "Enable IPv6 networking") + flags.StringVar(&config.ExecRoot, "exec-root", defaultExecRoot, "Root directory for execution state files") + flags.StringVar(&config.bridgeConfig.IP, "bip", "", "Specify network bridge IP") + flags.StringVarP(&config.bridgeConfig.Iface, "bridge", "b", "", "Attach containers to a network bridge") + flags.StringVar(&config.bridgeConfig.FixedCIDR, "fixed-cidr", "", "IPv4 subnet for fixed IPs") + flags.StringVar(&config.bridgeConfig.FixedCIDRv6, "fixed-cidr-v6", "", "IPv6 subnet for fixed IPs") + flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv4, ""), "default-gateway", "Container default gateway IPv4 address") + flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv6, ""), "default-gateway-v6", "Container default gateway IPv6 address") + flags.BoolVar(&config.bridgeConfig.InterContainerCommunication, "icc", true, "Enable inter-container communication") + flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultIP, "0.0.0.0"), "ip", "Default IP when binding container ports") + flags.BoolVar(&config.bridgeConfig.EnableUserlandProxy, "userland-proxy", true, "Use userland proxy for loopback traffic") + flags.BoolVar(&config.EnableCors, "api-enable-cors", false, "Enable CORS headers in the remote API, this is deprecated by --api-cors-header") + flags.MarkDeprecated("api-enable-cors", "Please use --api-cors-header") + flags.StringVar(&config.CgroupParent, "cgroup-parent", "", "Set parent cgroup for all containers") + flags.StringVar(&config.RemappedRoot, "userns-remap", "", "User/Group setting for user namespaces") + flags.StringVar(&config.ContainerdAddr, "containerd", "", "Path to containerd socket") + flags.BoolVar(&config.LiveRestoreEnabled, "live-restore", false, "Enable live restore of docker when containers are still running") + flags.Var(runconfigopts.NewNamedRuntimeOpt("runtimes", &config.Runtimes, stockRuntimeName), "add-runtime", "Register an additional OCI compatible runtime") + flags.StringVar(&config.DefaultRuntime, "default-runtime", stockRuntimeName, "Default OCI runtime for containers") + flags.IntVar(&config.OOMScoreAdjust, "oom-score-adjust", -500, "Set the oom_score_adj for the daemon") - config.attachExperimentalFlags(cmd, usageFn) + config.attachExperimentalFlags(flags) } // GetRuntime returns the runtime path and arguments for a given diff --git a/daemon/config_windows.go b/daemon/config_windows.go index 061f7e737c..6740743a59 100644 --- a/daemon/config_windows.go +++ b/daemon/config_windows.go @@ -3,8 +3,8 @@ package daemon import ( "os" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/engine-api/types" + "github.com/spf13/pflag" ) var ( @@ -28,18 +28,15 @@ type Config struct { // for the Windows daemon.) } -// InstallFlags adds command-line options to the top-level flag parser for -// the current process. -// Subsequent calls to `flag.Parse` will populate config with values parsed -// from the command-line. -func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) string) { +// InstallFlags adds flags to the pflag.FlagSet to configure the daemon +func (config *Config) InstallFlags(flags *pflag.FlagSet) { // First handle install flags which are consistent cross-platform - config.InstallCommonFlags(cmd, usageFn) + config.InstallCommonFlags(flags) // Then platform-specific install flags. - cmd.StringVar(&config.bridgeConfig.FixedCIDR, []string{"-fixed-cidr"}, "", usageFn("IPv4 subnet for fixed IPs")) - cmd.StringVar(&config.bridgeConfig.Iface, []string{"b", "-bridge"}, "", "Attach containers to a virtual switch") - cmd.StringVar(&config.SocketGroup, []string{"G", "-group"}, "", usageFn("Users or groups that can access the named pipe")) + flags.StringVar(&config.bridgeConfig.FixedCIDR, "fixed-cidr", "", "IPv4 subnet for fixed IPs") + flags.StringVarP(&config.bridgeConfig.Iface, "bridge", "b", "", "Attach containers to a virtual switch") + flags.StringVarP(&config.SocketGroup, "group", "G", "", "Users or groups that can access the named pipe") } // GetRuntime returns the runtime path and arguments for a given diff --git a/hack/vendor.sh b/hack/vendor.sh index 5e1fa5897b..c5adbf119e 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -162,7 +162,7 @@ clone git github.com/matttproud/golang_protobuf_extensions fc2b8d3a73c4867e51861 clone git github.com/pkg/errors 01fa4104b9c248c8945d14d9f128454d5b28d595 # cli -clone git github.com/spf13/cobra 75205f23b3ea70dc7ae5e900d074e010c23c37e9 https://github.com/dnephin/cobra.git +clone git github.com/spf13/cobra v1.4.1 https://github.com/dnephin/cobra.git clone git github.com/spf13/pflag cb88ea77998c3f024757528e3305022ab50b43be clone git github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 clone git github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 1c85021245..b7e8f5bf5d 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -1555,12 +1555,12 @@ func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) { c.Fatalf("failed to chmod file to 700: %s", err) } - buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)) - buildCmd.Dir = ctx.Dir - if out, _, err := runCommandWithOutput(buildCmd); err != nil { - c.Fatalf("build should have worked: %s %s", err, out) - } - + result := icmd.RunCmd(icmd.Cmd{ + Dir: ctx.Dir, + Command: []string{"su", "unprivilegeduser", "-c", + fmt.Sprintf("%s build -t %s .", dockerBinary, name)}, + }) + result.Assert(c, icmd.Expected{}) } } diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index e3602c637c..174bf53ab8 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/go-check/check" ) @@ -399,10 +400,9 @@ func (s *DockerSuite) TestCpUnprivilegedUser(c *check.C) { c.Assert(os.Chmod(tmpdir, 0777), checker.IsNil) - path := cpTestName - - _, _, err = runCommandWithOutput(exec.Command("su", "unprivilegeduser", "-c", dockerBinary+" cp "+containerID+":"+path+" "+tmpdir)) - c.Assert(err, checker.IsNil, check.Commentf("couldn't copy with unprivileged user: %s:%s", containerID, path)) + result := icmd.RunCommand("su", "unprivilegeduser", "-c", + fmt.Sprintf("%s cp %s:%s %s", dockerBinary, containerID, cpTestName, tmpdir)) + result.Assert(c, icmd.Expected{}) } func (s *DockerSuite) TestCpSpecialFiles(c *check.C) { diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index ec73beac22..0f28cfbe9d 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -23,6 +23,7 @@ import ( "github.com/docker/docker/pkg/integration/checker" icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/mount" + "github.com/docker/docker/pkg/testutil/tempfile" "github.com/docker/go-units" "github.com/docker/libnetwork/iptables" "github.com/docker/libtrust" @@ -352,10 +353,8 @@ func (s *DockerDaemonSuite) TestDaemonIptablesCreate(c *check.C) { func (s *DockerSuite) TestDaemonIPv6Enabled(c *check.C) { testRequires(c, IPv6) - if err := setupV6(); err != nil { - c.Fatal("Could not set up host for IPv6 tests") - } - + setupV6(c) + defer teardownV6(c) d := NewDaemon(c) if err := d.StartWithBusybox("--ipv6"); err != nil { @@ -412,11 +411,6 @@ func (s *DockerSuite) TestDaemonIPv6Enabled(c *check.C) { if ip := net.ParseIP(out); ip != nil { c.Fatalf("Container should not have a global IPv6 address: %v", out) } - - if err := teardownV6(); err != nil { - c.Fatal("Could not perform teardown for IPv6 tests") - } - } // TestDaemonIPv6FixedCIDR checks that when the daemon is started with --ipv6=true and a fixed CIDR @@ -424,16 +418,16 @@ func (s *DockerSuite) TestDaemonIPv6Enabled(c *check.C) { func (s *DockerDaemonSuite) TestDaemonIPv6FixedCIDR(c *check.C) { // IPv6 setup is messing with local bridge address. testRequires(c, SameHostDaemon) - err := setupV6() - c.Assert(err, checker.IsNil, check.Commentf("Could not set up host for IPv6 tests")) + setupV6(c) + defer teardownV6(c) - err = s.d.StartWithBusybox("--ipv6", "--fixed-cidr-v6='2001:db8:2::/64'", "--default-gateway-v6='2001:db8:2::100'") + err := s.d.StartWithBusybox("--ipv6", "--fixed-cidr-v6=2001:db8:2::/64", "--default-gateway-v6=2001:db8:2::100") c.Assert(err, checker.IsNil, check.Commentf("Could not start daemon with busybox: %v", err)) out, err := s.d.Cmd("run", "-itd", "--name=ipv6test", "busybox:latest") c.Assert(err, checker.IsNil, check.Commentf("Could not run container: %s, %v", out, err)) - out, err = s.d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}'", "ipv6test") + out, err = s.d.Cmd("inspect", "--format", "{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}", "ipv6test") out = strings.Trim(out, " \r\n'") c.Assert(err, checker.IsNil, check.Commentf(out)) @@ -441,13 +435,10 @@ func (s *DockerDaemonSuite) TestDaemonIPv6FixedCIDR(c *check.C) { ip := net.ParseIP(out) c.Assert(ip, checker.NotNil, check.Commentf("Container should have a global IPv6 address")) - out, err = s.d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.IPv6Gateway}}'", "ipv6test") + out, err = s.d.Cmd("inspect", "--format", "{{.NetworkSettings.Networks.bridge.IPv6Gateway}}", "ipv6test") c.Assert(err, checker.IsNil, check.Commentf(out)) c.Assert(strings.Trim(out, " \r\n'"), checker.Equals, "2001:db8:2::100", check.Commentf("Container should have a global IPv6 gateway")) - - err = teardownV6() - c.Assert(err, checker.IsNil, check.Commentf("Could not perform teardown for IPv6 tests")) } // TestDaemonIPv6FixedCIDRAndMac checks that when the daemon is started with ipv6 fixed CIDR @@ -455,21 +446,18 @@ func (s *DockerDaemonSuite) TestDaemonIPv6FixedCIDR(c *check.C) { func (s *DockerDaemonSuite) TestDaemonIPv6FixedCIDRAndMac(c *check.C) { // IPv6 setup is messing with local bridge address. testRequires(c, SameHostDaemon) - err := setupV6() - c.Assert(err, checker.IsNil) + setupV6(c) + defer teardownV6(c) - err = s.d.StartWithBusybox("--ipv6", "--fixed-cidr-v6='2001:db8:1::/64'") + err := s.d.StartWithBusybox("--ipv6", "--fixed-cidr-v6=2001:db8:1::/64") c.Assert(err, checker.IsNil) out, err := s.d.Cmd("run", "-itd", "--name=ipv6test", "--mac-address", "AA:BB:CC:DD:EE:FF", "busybox") c.Assert(err, checker.IsNil) - out, err = s.d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}'", "ipv6test") + out, err = s.d.Cmd("inspect", "--format", "{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}", "ipv6test") c.Assert(err, checker.IsNil) c.Assert(strings.Trim(out, " \r\n'"), checker.Equals, "2001:db8:1::aabb:ccdd:eeff") - - err = teardownV6() - c.Assert(err, checker.IsNil) } func (s *DockerDaemonSuite) TestDaemonLogLevelWrong(c *check.C) { @@ -1724,13 +1712,15 @@ func (s *DockerDaemonSuite) TestDaemonNoTlsCliTlsVerifyWithEnv(c *check.C) { } -func setupV6() error { +func setupV6(c *check.C) { // Hack to get the right IPv6 address on docker0, which has already been created - return exec.Command("ip", "addr", "add", "fe80::1/64", "dev", "docker0").Run() + result := icmd.RunCommand("ip", "addr", "add", "fe80::1/64", "dev", "docker0") + result.Assert(c, icmd.Expected{}) } -func teardownV6() error { - return exec.Command("ip", "addr", "del", "fe80::1/64", "dev", "docker0").Run() +func teardownV6(c *check.C) { + result := icmd.RunCommand("ip", "addr", "del", "fe80::1/64", "dev", "docker0") + result.Assert(c, icmd.Expected{}) } func (s *DockerDaemonSuite) TestDaemonRestartWithContainerWithRestartPolicyAlways(c *check.C) { @@ -2362,31 +2352,26 @@ func (s *DockerSuite) TestDaemonDiscoveryBackendConfigReload(c *check.C) { testRequires(c, SameHostDaemon, DaemonIsLinux) // daemon config file - daemonConfig := `{ "debug" : false }` - configFilePath := "test.json" - - configFile, err := os.Create(configFilePath) - c.Assert(err, checker.IsNil) - fmt.Fprintf(configFile, "%s", daemonConfig) + tmpfile := tempfile.NewTempFile(c, "config-test", `{ "debug" : false }`) + defer tmpfile.Remove() d := NewDaemon(c) - err = d.Start(fmt.Sprintf("--config-file=%s", configFilePath)) + // --log-level needs to be set so that d.Start() doesn't add --debug causing + // a conflict with the config + err := d.Start("--config-file", tmpfile.Name(), "--log-level=info") c.Assert(err, checker.IsNil) defer d.Stop() // daemon config file - daemonConfig = `{ + daemonConfig := `{ "cluster-store": "consul://consuladdr:consulport/some/path", "cluster-advertise": "192.168.56.100:0", "debug" : false }` - configFile.Close() - os.Remove(configFilePath) - - configFile, err = os.Create(configFilePath) + os.Remove(tmpfile.Name()) + configFile, err := os.Create(tmpfile.Name()) c.Assert(err, checker.IsNil) - defer os.Remove(configFilePath) fmt.Fprintf(configFile, "%s", daemonConfig) configFile.Close() @@ -2396,6 +2381,7 @@ func (s *DockerSuite) TestDaemonDiscoveryBackendConfigReload(c *check.C) { out, err := d.Cmd("info") c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, fmt.Sprintf("Cluster Store: consul://consuladdr:consulport/some/path")) c.Assert(out, checker.Contains, fmt.Sprintf("Cluster Advertise: 192.168.56.100:0")) } diff --git a/integration-cli/docker_cli_help_test.go b/integration-cli/docker_cli_help_test.go index a4dca18ef0..2d6610c71f 100644 --- a/integration-cli/docker_cli_help_test.go +++ b/integration-cli/docker_cli_help_test.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/pkg/homedir" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/go-check/check" ) @@ -56,12 +57,7 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) { out, _, err := runCommandWithOutput(helpCmd) c.Assert(err, checker.IsNil, check.Commentf(out)) lines := strings.Split(out, "\n") - foundTooLongLine := false for _, line := range lines { - if !foundTooLongLine && len(line) > 80 { - c.Logf("Line is too long:\n%s", line) - foundTooLongLine = true - } // All lines should not end with a space c.Assert(line, checker.Not(checker.HasSuffix), " ", check.Commentf("Line should not end with a space")) @@ -229,14 +225,6 @@ func testCommand(cmd string, newEnvs []string, scanForHome bool, home string) er // Check each line for lots of stuff lines := strings.Split(out, "\n") for _, line := range lines { - if len(line) > 107 { - return fmt.Errorf("Help for %q is too long:\n%s\n", cmd, line) - } - - if scanForHome && strings.Contains(line, `"`+home) { - return fmt.Errorf("Help for %q should use ~ instead of %q on:\n%s\n", - cmd, home, line) - } i := strings.Index(line, "~") if i >= 0 && i != len(line)-1 && line[i+1] != '/' { return fmt.Errorf("Help for %q should not have used ~:\n%s", cmd, line) @@ -290,10 +278,6 @@ func testCommand(cmd string, newEnvs []string, scanForHome bool, home string) er } if _, ok := noShortUsage[cmd]; !ok { - // For each command run it w/o any args. It will either return - // valid output or print a short-usage - var dCmd *exec.Cmd - // skipNoArgs are ones that we don't want to try w/o // any args. Either because it'll hang the test or // lead to incorrect test result (like false negative). @@ -305,33 +289,31 @@ func testCommand(cmd string, newEnvs []string, scanForHome bool, home string) er "load": {}, } - ec := 0 + var result *icmd.Result if _, ok := skipNoArgs[cmd]; !ok { - args = strings.Split(cmd, " ") - dCmd = exec.Command(dockerBinary, args...) - out, stderr, ec, err = runCommandWithStdoutStderr(dCmd) + result = dockerCmdWithResult(strings.Split(cmd, " ")...) } // If its ok w/o any args then try again with an arg - if ec == 0 { - args = strings.Split(cmd+" badArg", " ") - dCmd = exec.Command(dockerBinary, args...) - out, stderr, ec, err = runCommandWithStdoutStderr(dCmd) + if result == nil || result.ExitCode == 0 { + result = dockerCmdWithResult(strings.Split(cmd+" badArg", " ")...) } - if len(out) != 0 || len(stderr) == 0 || ec == 0 || err == nil { - return fmt.Errorf("Bad output from %q\nstdout:%q\nstderr:%q\nec:%d\nerr:%q\n", args, out, stderr, ec, err) + if err := result.Compare(icmd.Expected{ + Out: icmd.None, + Err: "\nUsage:", + ExitCode: 1, + }); err != nil { + return err } - // Should have just short usage - if !strings.Contains(stderr, "\nUsage:") { - return fmt.Errorf("Missing short usage on %q\n:%#v", args, stderr) - } - // But shouldn't have full usage + + stderr := result.Stderr() + // Shouldn't have full usage if strings.Contains(stderr, "--help=false") { - return fmt.Errorf("Should not have full usage on %q\n", args) + return fmt.Errorf("Should not have full usage on %q:%v", result.Cmd.Args, stderr) } if strings.HasSuffix(stderr, "\n\n") { - return fmt.Errorf("Should not have a blank line on %q\n%v", args, stderr) + return fmt.Errorf("Should not have a blank line on %q\n%v", result.Cmd.Args, stderr) } } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index bdc6d620a9..8dd22f67b3 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -334,11 +334,8 @@ func (s *DockerSuite) TestUserDefinedNetworkAlias(c *check.C) { // Issue 9677. func (s *DockerSuite) TestRunWithDaemonFlags(c *check.C) { out, _, err := dockerCmdWithError("--exec-opt", "foo=bar", "run", "-i", "busybox", "true") - if err != nil { - if !strings.Contains(out, "flag provided but not defined: --exec-opt") { // no daemon (client-only) - c.Fatal(err, out) - } - } + c.Assert(err, checker.NotNil) + c.Assert(out, checker.Contains, "unknown flag: --exec-opt") } // Regression test for #4979 @@ -2663,15 +2660,11 @@ func (s *DockerSuite) TestRunTLSverify(c *check.C) { // Regardless of whether we specify true or false we need to // test to make sure tls is turned on if --tlsverify is specified at all - out, code, err := dockerCmdWithError("--tlsverify=false", "ps") - if err == nil || code == 0 || !strings.Contains(out, "trying to connect") { - c.Fatalf("Should have failed: \net:%v\nout:%v\nerr:%v", code, out, err) - } + result := dockerCmdWithResult("--tlsverify=false", "ps") + result.Assert(c, icmd.Expected{ExitCode: 1, Err: "trying to connect"}) - out, code, err = dockerCmdWithError("--tlsverify=true", "ps") - if err == nil || code == 0 || !strings.Contains(out, "cert") { - c.Fatalf("Should have failed: \net:%v\nout:%v\nerr:%v", code, out, err) - } + result = dockerCmdWithResult("--tlsverify=true", "ps") + result.Assert(c, icmd.Expected{ExitCode: 1, Err: "cert"}) } func (s *DockerSuite) TestRunPortFromDockerRangeInUse(c *check.C) { diff --git a/man/generate.go b/man/generate.go index 7bcc57009e..cee5a7fdd2 100644 --- a/man/generate.go +++ b/man/generate.go @@ -4,8 +4,10 @@ import ( "fmt" "os" - "github.com/docker/docker/cli/cobraadaptor" - cliflags "github.com/docker/docker/cli/flags" + "github.com/docker/docker/api/client" + "github.com/docker/docker/api/client/command" + "github.com/docker/docker/pkg/term" + "github.com/spf13/cobra" "github.com/spf13/cobra/doc" ) @@ -15,10 +17,12 @@ func generateManPages(path string) error { Section: "1", Source: "Docker Community", } - flags := &cliflags.ClientFlags{ - Common: cliflags.InitCommonFlags(), - } - cmd := cobraadaptor.NewCobraAdaptor(flags).GetRootCommand() + + stdin, stdout, stderr := term.StdStreams() + dockerCli := client.NewDockerCli(stdin, stdout, stderr) + cmd := &cobra.Command{Use: "docker"} + command.AddCommands(cmd, dockerCli) + cmd.DisableAutoGenTag = true return doc.GenManTreeFromOpts(cmd, doc.GenManTreeOptions{ Header: header, diff --git a/opts/ip.go b/opts/ip.go index c7b0dc9947..fb03b50111 100644 --- a/opts/ip.go +++ b/opts/ip.go @@ -40,3 +40,8 @@ func (o *IPOpt) String() string { } return o.IP.String() } + +// Type returns the type of the option +func (o *IPOpt) Type() string { + return "ip" +} diff --git a/pkg/mflag/LICENSE b/pkg/mflag/LICENSE deleted file mode 100644 index 9b4f4a294e..0000000000 --- a/pkg/mflag/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2014-2016 The Docker & Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkg/mflag/README.md b/pkg/mflag/README.md deleted file mode 100644 index 5e81bb2a36..0000000000 --- a/pkg/mflag/README.md +++ /dev/null @@ -1,40 +0,0 @@ -Package mflag (aka multiple-flag) implements command-line flag parsing. -It's a **hacky** fork of the [official golang package](http://golang.org/pkg/flag/) - -It adds: - -* both short and long flag version -`./example -s red` `./example --string blue` - -* multiple names for the same option -``` -$>./example -h -Usage of example: - -s, --string="": a simple string -``` - -___ -It is very flexible on purpose, so you can do things like: -``` -$>./example -h -Usage of example: - -s, -string, --string="": a simple string -``` - -Or: -``` -$>./example -h -Usage of example: - -oldflag, --newflag="": a simple string -``` - -You can also hide some flags from the usage, so if we want only `--newflag`: -``` -$>./example -h -Usage of example: - --newflag="": a simple string -$>./example -oldflag str -str -``` - -See [example.go](example/example.go) for more details. diff --git a/pkg/mflag/example/example.go b/pkg/mflag/example/example.go deleted file mode 100644 index 263f9dbcd2..0000000000 --- a/pkg/mflag/example/example.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "fmt" - - flag "github.com/docker/docker/pkg/mflag" -) - -var ( - i int - str string - b, b2, h bool -) - -func init() { - flag.Bool([]string{"#hp", "#-halp"}, false, "display the halp") - flag.BoolVar(&b, []string{"b", "#bal", "#bol", "-bal"}, false, "a simple bool") - flag.BoolVar(&b, []string{"g", "#gil"}, false, "a simple bool") - flag.BoolVar(&b2, []string{"#-bool"}, false, "a simple bool") - flag.IntVar(&i, []string{"-integer", "-number"}, -1, "a simple integer") - flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage - flag.BoolVar(&h, []string{"h", "#help", "-help"}, false, "display the help") - flag.StringVar(&str, []string{"mode"}, "mode1", "set the mode\nmode1: use the mode1\nmode2: use the mode2\nmode3: use the mode3") - flag.Parse() -} -func main() { - if h { - flag.PrintDefaults() - } else { - fmt.Printf("s/#hidden/-string: %s\n", str) - fmt.Printf("b: %t\n", b) - fmt.Printf("-bool: %t\n", b2) - fmt.Printf("-integer/-number: %d\n", i) - fmt.Printf("s/#hidden/-string(via lookup): %s\n", flag.Lookup("s").Value.String()) - fmt.Printf("ARGS: %v\n", flag.Args()) - } -} diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go deleted file mode 100644 index f13a8cf06c..0000000000 --- a/pkg/mflag/flag.go +++ /dev/null @@ -1,1280 +0,0 @@ -// Copyright 2014-2016 The Docker & Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package mflag implements command-line flag parsing. -// -// Usage: -// -// Define flags using flag.String(), Bool(), Int(), etc. -// -// This declares an integer flag, -f or --flagname, stored in the pointer ip, with type *int. -// import flag "github.com/docker/docker/pkg/mflag" -// var ip = flag.Int([]string{"f", "-flagname"}, 1234, "help message for flagname") -// If you like, you can bind the flag to a variable using the Var() functions. -// var flagvar int -// func init() { -// // -flaghidden will work, but will be hidden from the usage -// flag.IntVar(&flagvar, []string{"f", "#flaghidden", "-flagname"}, 1234, "help message for flagname") -// } -// Or you can create custom flags that satisfy the Value interface (with -// pointer receivers) and couple them to flag parsing by -// flag.Var(&flagVal, []string{"name"}, "help message for flagname") -// For such flags, the default value is just the initial value of the variable. -// -// You can also add "deprecated" flags, they are still usable, but are not shown -// in the usage and will display a warning when you try to use them. `#` before -// an option means this option is deprecated, if there is a following option -// without `#` ahead, then that's the replacement, if not, it will just be removed: -// var ip = flag.Int([]string{"#f", "#flagname", "-flagname"}, 1234, "help message for flagname") -// this will display: `Warning: '-f' is deprecated, it will be replaced by '--flagname' soon. See usage.` or -// this will display: `Warning: '-flagname' is deprecated, it will be replaced by '--flagname' soon. See usage.` -// var ip = flag.Int([]string{"f", "#flagname"}, 1234, "help message for flagname") -// will display: `Warning: '-flagname' is deprecated, it will be removed soon. See usage.` -// so you can only use `-f`. -// -// You can also group one letter flags, if you declare -// var v = flag.Bool([]string{"v", "-verbose"}, false, "help message for verbose") -// var s = flag.Bool([]string{"s", "-slow"}, false, "help message for slow") -// you will be able to use the -vs or -sv -// -// After all flags are defined, call -// flag.Parse() -// to parse the command line into the defined flags. -// -// Flags may then be used directly. If you're using the flags themselves, -// they are all pointers; if you bind to variables, they're values. -// fmt.Println("ip has value ", *ip) -// fmt.Println("flagvar has value ", flagvar) -// -// After parsing, the arguments after the flag are available as the -// slice flag.Args() or individually as flag.Arg(i). -// The arguments are indexed from 0 through flag.NArg()-1. -// -// Command line flag syntax: -// -flag -// -flag=x -// -flag="x" -// -flag='x' -// -flag x // non-boolean flags only -// One or two minus signs may be used; they are equivalent. -// The last form is not permitted for boolean flags because the -// meaning of the command -// cmd -x * -// will change if there is a file called 0, false, etc. You must -// use the -flag=false form to turn off a boolean flag. -// -// Flag parsing stops just before the first non-flag argument -// ("-" is a non-flag argument) or after the terminator "--". -// -// Integer flags accept 1234, 0664, 0x1234 and may be negative. -// Boolean flags may be 1, 0, t, f, true, false, TRUE, FALSE, True, False. -// Duration flags accept any input valid for time.ParseDuration. -// -// The default set of command-line flags is controlled by -// top-level functions. The FlagSet type allows one to define -// independent sets of flags, such as to implement subcommands -// in a command-line interface. The methods of FlagSet are -// analogous to the top-level functions for the command-line -// flag set. - -package mflag - -import ( - "errors" - "fmt" - "io" - "os" - "runtime" - "sort" - "strconv" - "strings" - "text/tabwriter" - "time" - - "github.com/docker/docker/pkg/homedir" -) - -// ErrHelp is the error returned if the flag -help is invoked but no such flag is defined. -var ErrHelp = errors.New("flag: help requested") - -// ErrRetry is the error returned if you need to try letter by letter -var ErrRetry = errors.New("flag: retry") - -// -- bool Value -type boolValue bool - -func newBoolValue(val bool, p *bool) *boolValue { - *p = val - return (*boolValue)(p) -} - -func (b *boolValue) Set(s string) error { - v, err := strconv.ParseBool(s) - *b = boolValue(v) - return err -} - -func (b *boolValue) Get() interface{} { return bool(*b) } - -func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) } - -func (b *boolValue) IsBoolFlag() bool { return true } - -// optional interface to indicate boolean flags that can be -// supplied without "=value" text -type boolFlag interface { - Value - IsBoolFlag() bool -} - -// -- int Value -type intValue int - -func newIntValue(val int, p *int) *intValue { - *p = val - return (*intValue)(p) -} - -func (i *intValue) Set(s string) error { - v, err := strconv.ParseInt(s, 0, 64) - *i = intValue(v) - return err -} - -func (i *intValue) Get() interface{} { return int(*i) } - -func (i *intValue) String() string { return fmt.Sprintf("%v", *i) } - -// -- int64 Value -type int64Value int64 - -func newInt64Value(val int64, p *int64) *int64Value { - *p = val - return (*int64Value)(p) -} - -func (i *int64Value) Set(s string) error { - v, err := strconv.ParseInt(s, 0, 64) - *i = int64Value(v) - return err -} - -func (i *int64Value) Get() interface{} { return int64(*i) } - -func (i *int64Value) String() string { return fmt.Sprintf("%v", *i) } - -// -- uint Value -type uintValue uint - -func newUintValue(val uint, p *uint) *uintValue { - *p = val - return (*uintValue)(p) -} - -func (i *uintValue) Set(s string) error { - v, err := strconv.ParseUint(s, 0, 64) - *i = uintValue(v) - return err -} - -func (i *uintValue) Get() interface{} { return uint(*i) } - -func (i *uintValue) String() string { return fmt.Sprintf("%v", *i) } - -// -- uint64 Value -type uint64Value uint64 - -func newUint64Value(val uint64, p *uint64) *uint64Value { - *p = val - return (*uint64Value)(p) -} - -func (i *uint64Value) Set(s string) error { - v, err := strconv.ParseUint(s, 0, 64) - *i = uint64Value(v) - return err -} - -func (i *uint64Value) Get() interface{} { return uint64(*i) } - -func (i *uint64Value) String() string { return fmt.Sprintf("%v", *i) } - -// -- uint16 Value -type uint16Value uint16 - -func newUint16Value(val uint16, p *uint16) *uint16Value { - *p = val - return (*uint16Value)(p) -} - -func (i *uint16Value) Set(s string) error { - v, err := strconv.ParseUint(s, 0, 16) - *i = uint16Value(v) - return err -} - -func (i *uint16Value) Get() interface{} { return uint16(*i) } - -func (i *uint16Value) String() string { return fmt.Sprintf("%v", *i) } - -// -- string Value -type stringValue string - -func newStringValue(val string, p *string) *stringValue { - *p = val - return (*stringValue)(p) -} - -func (s *stringValue) Set(val string) error { - *s = stringValue(val) - return nil -} - -func (s *stringValue) Get() interface{} { return string(*s) } - -func (s *stringValue) String() string { return fmt.Sprintf("%s", *s) } - -// -- float64 Value -type float64Value float64 - -func newFloat64Value(val float64, p *float64) *float64Value { - *p = val - return (*float64Value)(p) -} - -func (f *float64Value) Set(s string) error { - v, err := strconv.ParseFloat(s, 64) - *f = float64Value(v) - return err -} - -func (f *float64Value) Get() interface{} { return float64(*f) } - -func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) } - -// -- time.Duration Value -type durationValue time.Duration - -func newDurationValue(val time.Duration, p *time.Duration) *durationValue { - *p = val - return (*durationValue)(p) -} - -func (d *durationValue) Set(s string) error { - v, err := time.ParseDuration(s) - *d = durationValue(v) - return err -} - -func (d *durationValue) Get() interface{} { return time.Duration(*d) } - -func (d *durationValue) String() string { return (*time.Duration)(d).String() } - -// Value is the interface to the dynamic value stored in a flag. -// (The default value is represented as a string.) -// -// If a Value has an IsBoolFlag() bool method returning true, -// the command-line parser makes -name equivalent to -name=true -// rather than using the next command-line argument. -type Value interface { - String() string - Set(string) error -} - -// Getter is an interface that allows the contents of a Value to be retrieved. -// It wraps the Value interface, rather than being part of it, because it -// appeared after Go 1 and its compatibility rules. All Value types provided -// by this package satisfy the Getter interface. -type Getter interface { - Value - Get() interface{} -} - -// ErrorHandling defines how to handle flag parsing errors. -type ErrorHandling int - -// ErrorHandling strategies available when a flag parsing error occurs -const ( - ContinueOnError ErrorHandling = iota - ExitOnError - PanicOnError -) - -// A FlagSet represents a set of defined flags. The zero value of a FlagSet -// has no name and has ContinueOnError error handling. -type FlagSet struct { - // Usage is the function called when an error occurs while parsing flags. - // The field is a function (not a method) that may be changed to point to - // a custom error handler. - Usage func() - ShortUsage func() - - name string - parsed bool - actual map[string]*Flag - formal map[string]*Flag - args []string // arguments after flags - errorHandling ErrorHandling - output io.Writer // nil means stderr; use Out() accessor - nArgRequirements []nArgRequirement -} - -// A Flag represents the state of a flag. -type Flag struct { - Names []string // name as it appears on command line - Usage string // help message - Value Value // value as set - DefValue string // default value (as text); for usage message -} - -type flagSlice []string - -func (p flagSlice) Len() int { return len(p) } -func (p flagSlice) Less(i, j int) bool { - pi, pj := strings.TrimPrefix(p[i], "-"), strings.TrimPrefix(p[j], "-") - lpi, lpj := strings.ToLower(pi), strings.ToLower(pj) - if lpi != lpj { - return lpi < lpj - } - return pi < pj -} -func (p flagSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -// sortFlags returns the flags as a slice in lexicographical sorted order. -func sortFlags(flags map[string]*Flag) []*Flag { - var list flagSlice - - // The sorted list is based on the first name, when flag map might use the other names. - nameMap := make(map[string]string) - - for n, f := range flags { - fName := strings.TrimPrefix(f.Names[0], "#") - nameMap[fName] = n - if len(f.Names) == 1 { - list = append(list, fName) - continue - } - - found := false - for _, name := range list { - if name == fName { - found = true - break - } - } - if !found { - list = append(list, fName) - } - } - sort.Sort(list) - result := make([]*Flag, len(list)) - for i, name := range list { - result[i] = flags[nameMap[name]] - } - return result -} - -// Name returns the name of the FlagSet. -func (fs *FlagSet) Name() string { - return fs.name -} - -// Out returns the destination for usage and error messages. -func (fs *FlagSet) Out() io.Writer { - if fs.output == nil { - return os.Stderr - } - return fs.output -} - -// SetOutput sets the destination for usage and error messages. -// If output is nil, os.Stderr is used. -func (fs *FlagSet) SetOutput(output io.Writer) { - fs.output = output -} - -// VisitAll visits the flags in lexicographical order, calling fn for each. -// It visits all flags, even those not set. -func (fs *FlagSet) VisitAll(fn func(*Flag)) { - for _, flag := range sortFlags(fs.formal) { - fn(flag) - } -} - -// VisitAll visits the command-line flags in lexicographical order, calling -// fn for each. It visits all flags, even those not set. -func VisitAll(fn func(*Flag)) { - CommandLine.VisitAll(fn) -} - -// Visit visits the flags in lexicographical order, calling fn for each. -// It visits only those flags that have been set. -func (fs *FlagSet) Visit(fn func(*Flag)) { - for _, flag := range sortFlags(fs.actual) { - fn(flag) - } -} - -// Visit visits the command-line flags in lexicographical order, calling fn -// for each. It visits only those flags that have been set. -func Visit(fn func(*Flag)) { - CommandLine.Visit(fn) -} - -// Lookup returns the Flag structure of the named flag, returning nil if none exists. -func (fs *FlagSet) Lookup(name string) *Flag { - return fs.formal[name] -} - -// IsSet indicates whether the specified flag is set in the given FlagSet -func (fs *FlagSet) IsSet(name string) bool { - return fs.actual[name] != nil -} - -// Lookup returns the Flag structure of the named command-line flag, -// returning nil if none exists. -func Lookup(name string) *Flag { - return CommandLine.formal[name] -} - -// IsSet indicates whether the specified flag was specified at all on the cmd line. -func IsSet(name string) bool { - return CommandLine.IsSet(name) -} - -type nArgRequirementType int - -// Indicator used to pass to BadArgs function -const ( - Exact nArgRequirementType = iota - Max - Min -) - -type nArgRequirement struct { - Type nArgRequirementType - N int -} - -// Require adds a requirement about the number of arguments for the FlagSet. -// The first parameter can be Exact, Max, or Min to respectively specify the exact, -// the maximum, or the minimal number of arguments required. -// The actual check is done in FlagSet.CheckArgs(). -func (fs *FlagSet) Require(nArgRequirementType nArgRequirementType, nArg int) { - fs.nArgRequirements = append(fs.nArgRequirements, nArgRequirement{nArgRequirementType, nArg}) -} - -// CheckArgs uses the requirements set by FlagSet.Require() to validate -// the number of arguments. If the requirements are not met, -// an error message string is returned. -func (fs *FlagSet) CheckArgs() (message string) { - for _, req := range fs.nArgRequirements { - var arguments string - if req.N == 1 { - arguments = "1 argument" - } else { - arguments = fmt.Sprintf("%d arguments", req.N) - } - - str := func(kind string) string { - return fmt.Sprintf("%q requires %s%s", fs.name, kind, arguments) - } - - switch req.Type { - case Exact: - if fs.NArg() != req.N { - return str("") - } - case Max: - if fs.NArg() > req.N { - return str("a maximum of ") - } - case Min: - if fs.NArg() < req.N { - return str("a minimum of ") - } - } - } - return "" -} - -// Set sets the value of the named flag. -func (fs *FlagSet) Set(name, value string) error { - flag, ok := fs.formal[name] - if !ok { - return fmt.Errorf("no such flag -%v", name) - } - if err := flag.Value.Set(value); err != nil { - return err - } - if fs.actual == nil { - fs.actual = make(map[string]*Flag) - } - fs.actual[name] = flag - return nil -} - -// Set sets the value of the named command-line flag. -func Set(name, value string) error { - return CommandLine.Set(name, value) -} - -// isZeroValue guesses whether the string represents the zero -// value for a flag. It is not accurate but in practice works OK. -func isZeroValue(value string) bool { - switch value { - case "false": - return true - case "": - return true - case "0": - return true - } - return false -} - -// PrintDefaults prints, to standard error unless configured -// otherwise, the default values of all defined flags in the set. -func (fs *FlagSet) PrintDefaults() { - writer := tabwriter.NewWriter(fs.Out(), 20, 1, 3, ' ', 0) - home := homedir.Get() - - // Don't substitute when HOME is / - if runtime.GOOS != "windows" && home == "/" { - home = "" - } - - // Add a blank line between cmd description and list of options - if fs.FlagCount() > 0 { - fmt.Fprintln(writer, "") - } - - fs.VisitAll(func(flag *Flag) { - names := []string{} - for _, name := range flag.Names { - if name[0] != '#' { - names = append(names, name) - } - } - if len(names) > 0 && len(flag.Usage) > 0 { - val := flag.DefValue - - if home != "" && strings.HasPrefix(val, home) { - val = homedir.GetShortcutString() + val[len(home):] - } - - if isZeroValue(val) { - format := " -%s" - fmt.Fprintf(writer, format, strings.Join(names, ", -")) - } else { - format := " -%s=%s" - fmt.Fprintf(writer, format, strings.Join(names, ", -"), val) - } - for _, line := range strings.Split(flag.Usage, "\n") { - fmt.Fprintln(writer, "\t", line) - } - } - }) - writer.Flush() -} - -// PrintDefaults prints to standard error the default values of all defined command-line flags. -func PrintDefaults() { - CommandLine.PrintDefaults() -} - -// defaultUsage is the default function to print a usage message. -func defaultUsage(fs *FlagSet) { - if fs.name == "" { - fmt.Fprintf(fs.Out(), "Usage:\n") - } else { - fmt.Fprintf(fs.Out(), "Usage of %s:\n", fs.name) - } - fs.PrintDefaults() -} - -// NOTE: Usage is not just defaultUsage(CommandLine) -// because it serves (via godoc flag Usage) as the example -// for how to write your own usage function. - -// Usage prints to standard error a usage message documenting all defined command-line flags. -// The function is a variable that may be changed to point to a custom function. -var Usage = func() { - fmt.Fprintf(CommandLine.Out(), "Usage of %s:\n", os.Args[0]) - PrintDefaults() -} - -// ShortUsage prints to standard error a usage message documenting the standard command layout -// The function is a variable that may be changed to point to a custom function. -var ShortUsage = func() { - fmt.Fprintf(CommandLine.output, "Usage of %s:\n", os.Args[0]) -} - -// FlagCount returns the number of flags that have been defined. -func (fs *FlagSet) FlagCount() int { return len(sortFlags(fs.formal)) } - -// FlagCountUndeprecated returns the number of undeprecated flags that have been defined. -func (fs *FlagSet) FlagCountUndeprecated() int { - count := 0 - for _, flag := range sortFlags(fs.formal) { - for _, name := range flag.Names { - if name[0] != '#' { - count++ - break - } - } - } - return count -} - -// NFlag returns the number of flags that have been set. -func (fs *FlagSet) NFlag() int { return len(fs.actual) } - -// NFlag returns the number of command-line flags that have been set. -func NFlag() int { return len(CommandLine.actual) } - -// Arg returns the i'th argument. Arg(0) is the first remaining argument -// after flags have been processed. -func (fs *FlagSet) Arg(i int) string { - if i < 0 || i >= len(fs.args) { - return "" - } - return fs.args[i] -} - -// Arg returns the i'th command-line argument. Arg(0) is the first remaining argument -// after flags have been processed. -func Arg(i int) string { - return CommandLine.Arg(i) -} - -// NArg is the number of arguments remaining after flags have been processed. -func (fs *FlagSet) NArg() int { return len(fs.args) } - -// NArg is the number of arguments remaining after flags have been processed. -func NArg() int { return len(CommandLine.args) } - -// Args returns the non-flag arguments. -func (fs *FlagSet) Args() []string { return fs.args } - -// Args returns the non-flag command-line arguments. -func Args() []string { return CommandLine.args } - -// BoolVar defines a bool flag with specified name, default value, and usage string. -// The argument p points to a bool variable in which to store the value of the flag. -func (fs *FlagSet) BoolVar(p *bool, names []string, value bool, usage string) { - fs.Var(newBoolValue(value, p), names, usage) -} - -// BoolVar defines a bool flag with specified name, default value, and usage string. -// The argument p points to a bool variable in which to store the value of the flag. -func BoolVar(p *bool, names []string, value bool, usage string) { - CommandLine.Var(newBoolValue(value, p), names, usage) -} - -// Bool defines a bool flag with specified name, default value, and usage string. -// The return value is the address of a bool variable that stores the value of the flag. -func (fs *FlagSet) Bool(names []string, value bool, usage string) *bool { - p := new(bool) - fs.BoolVar(p, names, value, usage) - return p -} - -// Bool defines a bool flag with specified name, default value, and usage string. -// The return value is the address of a bool variable that stores the value of the flag. -func Bool(names []string, value bool, usage string) *bool { - return CommandLine.Bool(names, value, usage) -} - -// IntVar defines an int flag with specified name, default value, and usage string. -// The argument p points to an int variable in which to store the value of the flag. -func (fs *FlagSet) IntVar(p *int, names []string, value int, usage string) { - fs.Var(newIntValue(value, p), names, usage) -} - -// IntVar defines an int flag with specified name, default value, and usage string. -// The argument p points to an int variable in which to store the value of the flag. -func IntVar(p *int, names []string, value int, usage string) { - CommandLine.Var(newIntValue(value, p), names, usage) -} - -// Int defines an int flag with specified name, default value, and usage string. -// The return value is the address of an int variable that stores the value of the flag. -func (fs *FlagSet) Int(names []string, value int, usage string) *int { - p := new(int) - fs.IntVar(p, names, value, usage) - return p -} - -// Int defines an int flag with specified name, default value, and usage string. -// The return value is the address of an int variable that stores the value of the flag. -func Int(names []string, value int, usage string) *int { - return CommandLine.Int(names, value, usage) -} - -// Int64Var defines an int64 flag with specified name, default value, and usage string. -// The argument p points to an int64 variable in which to store the value of the flag. -func (fs *FlagSet) Int64Var(p *int64, names []string, value int64, usage string) { - fs.Var(newInt64Value(value, p), names, usage) -} - -// Int64Var defines an int64 flag with specified name, default value, and usage string. -// The argument p points to an int64 variable in which to store the value of the flag. -func Int64Var(p *int64, names []string, value int64, usage string) { - CommandLine.Var(newInt64Value(value, p), names, usage) -} - -// Int64 defines an int64 flag with specified name, default value, and usage string. -// The return value is the address of an int64 variable that stores the value of the flag. -func (fs *FlagSet) Int64(names []string, value int64, usage string) *int64 { - p := new(int64) - fs.Int64Var(p, names, value, usage) - return p -} - -// Int64 defines an int64 flag with specified name, default value, and usage string. -// The return value is the address of an int64 variable that stores the value of the flag. -func Int64(names []string, value int64, usage string) *int64 { - return CommandLine.Int64(names, value, usage) -} - -// UintVar defines a uint flag with specified name, default value, and usage string. -// The argument p points to a uint variable in which to store the value of the flag. -func (fs *FlagSet) UintVar(p *uint, names []string, value uint, usage string) { - fs.Var(newUintValue(value, p), names, usage) -} - -// UintVar defines a uint flag with specified name, default value, and usage string. -// The argument p points to a uint variable in which to store the value of the flag. -func UintVar(p *uint, names []string, value uint, usage string) { - CommandLine.Var(newUintValue(value, p), names, usage) -} - -// Uint defines a uint flag with specified name, default value, and usage string. -// The return value is the address of a uint variable that stores the value of the flag. -func (fs *FlagSet) Uint(names []string, value uint, usage string) *uint { - p := new(uint) - fs.UintVar(p, names, value, usage) - return p -} - -// Uint defines a uint flag with specified name, default value, and usage string. -// The return value is the address of a uint variable that stores the value of the flag. -func Uint(names []string, value uint, usage string) *uint { - return CommandLine.Uint(names, value, usage) -} - -// Uint64Var defines a uint64 flag with specified name, default value, and usage string. -// The argument p points to a uint64 variable in which to store the value of the flag. -func (fs *FlagSet) Uint64Var(p *uint64, names []string, value uint64, usage string) { - fs.Var(newUint64Value(value, p), names, usage) -} - -// Uint64Var defines a uint64 flag with specified name, default value, and usage string. -// The argument p points to a uint64 variable in which to store the value of the flag. -func Uint64Var(p *uint64, names []string, value uint64, usage string) { - CommandLine.Var(newUint64Value(value, p), names, usage) -} - -// Uint64 defines a uint64 flag with specified name, default value, and usage string. -// The return value is the address of a uint64 variable that stores the value of the flag. -func (fs *FlagSet) Uint64(names []string, value uint64, usage string) *uint64 { - p := new(uint64) - fs.Uint64Var(p, names, value, usage) - return p -} - -// Uint64 defines a uint64 flag with specified name, default value, and usage string. -// The return value is the address of a uint64 variable that stores the value of the flag. -func Uint64(names []string, value uint64, usage string) *uint64 { - return CommandLine.Uint64(names, value, usage) -} - -// Uint16Var defines a uint16 flag with specified name, default value, and usage string. -// The argument p points to a uint16 variable in which to store the value of the flag. -func (fs *FlagSet) Uint16Var(p *uint16, names []string, value uint16, usage string) { - fs.Var(newUint16Value(value, p), names, usage) -} - -// Uint16Var defines a uint16 flag with specified name, default value, and usage string. -// The argument p points to a uint16 variable in which to store the value of the flag. -func Uint16Var(p *uint16, names []string, value uint16, usage string) { - CommandLine.Var(newUint16Value(value, p), names, usage) -} - -// Uint16 defines a uint16 flag with specified name, default value, and usage string. -// The return value is the address of a uint16 variable that stores the value of the flag. -func (fs *FlagSet) Uint16(names []string, value uint16, usage string) *uint16 { - p := new(uint16) - fs.Uint16Var(p, names, value, usage) - return p -} - -// Uint16 defines a uint16 flag with specified name, default value, and usage string. -// The return value is the address of a uint16 variable that stores the value of the flag. -func Uint16(names []string, value uint16, usage string) *uint16 { - return CommandLine.Uint16(names, value, usage) -} - -// StringVar defines a string flag with specified name, default value, and usage string. -// The argument p points to a string variable in which to store the value of the flag. -func (fs *FlagSet) StringVar(p *string, names []string, value string, usage string) { - fs.Var(newStringValue(value, p), names, usage) -} - -// StringVar defines a string flag with specified name, default value, and usage string. -// The argument p points to a string variable in which to store the value of the flag. -func StringVar(p *string, names []string, value string, usage string) { - CommandLine.Var(newStringValue(value, p), names, usage) -} - -// String defines a string flag with specified name, default value, and usage string. -// The return value is the address of a string variable that stores the value of the flag. -func (fs *FlagSet) String(names []string, value string, usage string) *string { - p := new(string) - fs.StringVar(p, names, value, usage) - return p -} - -// String defines a string flag with specified name, default value, and usage string. -// The return value is the address of a string variable that stores the value of the flag. -func String(names []string, value string, usage string) *string { - return CommandLine.String(names, value, usage) -} - -// Float64Var defines a float64 flag with specified name, default value, and usage string. -// The argument p points to a float64 variable in which to store the value of the flag. -func (fs *FlagSet) Float64Var(p *float64, names []string, value float64, usage string) { - fs.Var(newFloat64Value(value, p), names, usage) -} - -// Float64Var defines a float64 flag with specified name, default value, and usage string. -// The argument p points to a float64 variable in which to store the value of the flag. -func Float64Var(p *float64, names []string, value float64, usage string) { - CommandLine.Var(newFloat64Value(value, p), names, usage) -} - -// Float64 defines a float64 flag with specified name, default value, and usage string. -// The return value is the address of a float64 variable that stores the value of the flag. -func (fs *FlagSet) Float64(names []string, value float64, usage string) *float64 { - p := new(float64) - fs.Float64Var(p, names, value, usage) - return p -} - -// Float64 defines a float64 flag with specified name, default value, and usage string. -// The return value is the address of a float64 variable that stores the value of the flag. -func Float64(names []string, value float64, usage string) *float64 { - return CommandLine.Float64(names, value, usage) -} - -// DurationVar defines a time.Duration flag with specified name, default value, and usage string. -// The argument p points to a time.Duration variable in which to store the value of the flag. -func (fs *FlagSet) DurationVar(p *time.Duration, names []string, value time.Duration, usage string) { - fs.Var(newDurationValue(value, p), names, usage) -} - -// DurationVar defines a time.Duration flag with specified name, default value, and usage string. -// The argument p points to a time.Duration variable in which to store the value of the flag. -func DurationVar(p *time.Duration, names []string, value time.Duration, usage string) { - CommandLine.Var(newDurationValue(value, p), names, usage) -} - -// Duration defines a time.Duration flag with specified name, default value, and usage string. -// The return value is the address of a time.Duration variable that stores the value of the flag. -func (fs *FlagSet) Duration(names []string, value time.Duration, usage string) *time.Duration { - p := new(time.Duration) - fs.DurationVar(p, names, value, usage) - return p -} - -// Duration defines a time.Duration flag with specified name, default value, and usage string. -// The return value is the address of a time.Duration variable that stores the value of the flag. -func Duration(names []string, value time.Duration, usage string) *time.Duration { - return CommandLine.Duration(names, value, usage) -} - -// Var defines a flag with the specified name and usage string. The type and -// value of the flag are represented by the first argument, of type Value, which -// typically holds a user-defined implementation of Value. For instance, the -// caller could create a flag that turns a comma-separated string into a slice -// of strings by giving the slice the methods of Value; in particular, Set would -// decompose the comma-separated string into the slice. -func (fs *FlagSet) Var(value Value, names []string, usage string) { - // Remember the default value as a string; it won't change. - flag := &Flag{names, usage, value, value.String()} - for _, name := range names { - name = strings.TrimPrefix(name, "#") - _, alreadythere := fs.formal[name] - if alreadythere { - var msg string - if fs.name == "" { - msg = fmt.Sprintf("flag redefined: %s", name) - } else { - msg = fmt.Sprintf("%s flag redefined: %s", fs.name, name) - } - fmt.Fprintln(fs.Out(), msg) - panic(msg) // Happens only if flags are declared with identical names - } - if fs.formal == nil { - fs.formal = make(map[string]*Flag) - } - fs.formal[name] = flag - } -} - -// Var defines a flag with the specified name and usage string. The type and -// value of the flag are represented by the first argument, of type Value, which -// typically holds a user-defined implementation of Value. For instance, the -// caller could create a flag that turns a comma-separated string into a slice -// of strings by giving the slice the methods of Value; in particular, Set would -// decompose the comma-separated string into the slice. -func Var(value Value, names []string, usage string) { - CommandLine.Var(value, names, usage) -} - -// failf prints to standard error a formatted error and usage message and -// returns the error. -func (fs *FlagSet) failf(format string, a ...interface{}) error { - err := fmt.Errorf(format, a...) - fmt.Fprintln(fs.Out(), err) - if os.Args[0] == fs.name { - fmt.Fprintf(fs.Out(), "See '%s --help'.\n", os.Args[0]) - } else { - fmt.Fprintf(fs.Out(), "See '%s %s --help'.\n", os.Args[0], fs.name) - } - return err -} - -// usage calls the Usage method for the flag set, or the usage function if -// the flag set is CommandLine. -func (fs *FlagSet) usage() { - if fs == CommandLine { - Usage() - } else if fs.Usage == nil { - defaultUsage(fs) - } else { - fs.Usage() - } -} - -func trimQuotes(str string) string { - if len(str) == 0 { - return str - } - type quote struct { - start, end byte - } - - // All valid quote types. - quotes := []quote{ - // Double quotes - { - start: '"', - end: '"', - }, - - // Single quotes - { - start: '\'', - end: '\'', - }, - } - - for _, quote := range quotes { - // Only strip if outermost match. - if str[0] == quote.start && str[len(str)-1] == quote.end { - str = str[1 : len(str)-1] - break - } - } - - return str -} - -// parseOne parses one flag. It reports whether a flag was seen. -func (fs *FlagSet) parseOne() (bool, string, error) { - if len(fs.args) == 0 { - return false, "", nil - } - s := fs.args[0] - if len(s) == 0 || s[0] != '-' || len(s) == 1 { - return false, "", nil - } - if s[1] == '-' && len(s) == 2 { // "--" terminates the flags - fs.args = fs.args[1:] - return false, "", nil - } - name := s[1:] - if len(name) == 0 || name[0] == '=' { - return false, "", fs.failf("bad flag syntax: %s", s) - } - - // it's a flag. does it have an argument? - fs.args = fs.args[1:] - hasValue := false - value := "" - if i := strings.Index(name, "="); i != -1 { - value = trimQuotes(name[i+1:]) - hasValue = true - name = name[:i] - } - - m := fs.formal - flag, alreadythere := m[name] // BUG - if !alreadythere { - if name == "-help" || name == "help" || name == "h" { // special case for nice help message. - fs.usage() - return false, "", ErrHelp - } - if len(name) > 0 && name[0] == '-' { - return false, "", fs.failf("flag provided but not defined: -%s", name) - } - return false, name, ErrRetry - } - if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg - if hasValue { - if err := fv.Set(value); err != nil { - return false, "", fs.failf("invalid boolean value %q for -%s: %v", value, name, err) - } - } else { - fv.Set("true") - } - } else { - // It must have a value, which might be the next argument. - if !hasValue && len(fs.args) > 0 { - // value is the next arg - hasValue = true - value, fs.args = fs.args[0], fs.args[1:] - } - if !hasValue { - return false, "", fs.failf("flag needs an argument: -%s", name) - } - if err := flag.Value.Set(value); err != nil { - return false, "", fs.failf("invalid value %q for flag -%s: %v", value, name, err) - } - } - if fs.actual == nil { - fs.actual = make(map[string]*Flag) - } - fs.actual[name] = flag - for i, n := range flag.Names { - if n == fmt.Sprintf("#%s", name) { - replacement := "" - for j := i; j < len(flag.Names); j++ { - if flag.Names[j][0] != '#' { - replacement = flag.Names[j] - break - } - } - if replacement != "" { - fmt.Fprintf(fs.Out(), "Warning: '-%s' is deprecated, it will be replaced by '-%s' soon. See usage.\n", name, replacement) - } else { - fmt.Fprintf(fs.Out(), "Warning: '-%s' is deprecated, it will be removed soon. See usage.\n", name) - } - } - } - return true, "", nil -} - -// Parse parses flag definitions from the argument list, which should not -// include the command name. Must be called after all flags in the FlagSet -// are defined and before flags are accessed by the program. -// The return value will be ErrHelp if -help was set but not defined. -func (fs *FlagSet) Parse(arguments []string) error { - fs.parsed = true - fs.args = arguments - for { - seen, name, err := fs.parseOne() - if seen { - continue - } - if err == nil { - break - } - if err == ErrRetry { - if len(name) > 1 { - err = nil - for _, letter := range strings.Split(name, "") { - fs.args = append([]string{"-" + letter}, fs.args...) - seen2, _, err2 := fs.parseOne() - if seen2 { - continue - } - if err2 != nil { - err = fs.failf("flag provided but not defined: -%s", name) - break - } - } - if err == nil { - continue - } - } else { - err = fs.failf("flag provided but not defined: -%s", name) - } - } - switch fs.errorHandling { - case ContinueOnError: - return err - case ExitOnError: - os.Exit(125) - case PanicOnError: - panic(err) - } - } - return nil -} - -// ParseFlags is a utility function that adds a help flag if withHelp is true, -// calls fs.Parse(args) and prints a relevant error message if there are -// incorrect number of arguments. It returns error only if error handling is -// set to ContinueOnError and parsing fails. If error handling is set to -// ExitOnError, it's safe to ignore the return value. -func (fs *FlagSet) ParseFlags(args []string, withHelp bool) error { - var help *bool - if withHelp { - help = fs.Bool([]string{"#help", "-help"}, false, "Print usage") - } - if err := fs.Parse(args); err != nil { - return err - } - if help != nil && *help { - fs.SetOutput(os.Stdout) - fs.Usage() - os.Exit(0) - } - if str := fs.CheckArgs(); str != "" { - fs.SetOutput(os.Stderr) - fs.ReportError(str, withHelp) - fs.ShortUsage() - os.Exit(1) - } - return nil -} - -// ReportError is a utility method that prints a user-friendly message -// containing the error that occurred during parsing and a suggestion to get help -func (fs *FlagSet) ReportError(str string, withHelp bool) { - if withHelp { - if os.Args[0] == fs.Name() { - str += ".\nSee '" + os.Args[0] + " --help'" - } else { - str += ".\nSee '" + os.Args[0] + " " + fs.Name() + " --help'" - } - } - fmt.Fprintf(fs.Out(), "%s: %s.\n", os.Args[0], str) -} - -// Parsed reports whether fs.Parse has been called. -func (fs *FlagSet) Parsed() bool { - return fs.parsed -} - -// Parse parses the command-line flags from os.Args[1:]. Must be called -// after all flags are defined and before flags are accessed by the program. -func Parse() { - // Ignore errors; CommandLine is set for ExitOnError. - CommandLine.Parse(os.Args[1:]) -} - -// Parsed returns true if the command-line flags have been parsed. -func Parsed() bool { - return CommandLine.Parsed() -} - -// CommandLine is the default set of command-line flags, parsed from os.Args. -// The top-level functions such as BoolVar, Arg, and on are wrappers for the -// methods of CommandLine. -var CommandLine = NewFlagSet(os.Args[0], ExitOnError) - -// NewFlagSet returns a new, empty flag set with the specified name and -// error handling property. -func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet { - f := &FlagSet{ - name: name, - errorHandling: errorHandling, - } - return f -} - -// Init sets the name and error handling property for a flag set. -// By default, the zero FlagSet uses an empty name and the -// ContinueOnError error handling policy. -func (fs *FlagSet) Init(name string, errorHandling ErrorHandling) { - fs.name = name - fs.errorHandling = errorHandling -} - -type mergeVal struct { - Value - key string - fset *FlagSet -} - -func (v mergeVal) Set(s string) error { - return v.fset.Set(v.key, s) -} - -func (v mergeVal) IsBoolFlag() bool { - if b, ok := v.Value.(boolFlag); ok { - return b.IsBoolFlag() - } - return false -} - -// Name returns the name of a mergeVal. -// If the original value had a name, return the original name, -// otherwise, return the key asinged to this mergeVal. -func (v mergeVal) Name() string { - type namedValue interface { - Name() string - } - if nVal, ok := v.Value.(namedValue); ok { - return nVal.Name() - } - return v.key -} - -// Merge is a helper function that merges n FlagSets into a single dest FlagSet -// In case of name collision between the flagsets it will apply -// the destination FlagSet's errorHandling behavior. -func Merge(dest *FlagSet, flagsets ...*FlagSet) error { - for _, fset := range flagsets { - if fset.formal == nil { - continue - } - for k, f := range fset.formal { - if _, ok := dest.formal[k]; ok { - var err error - if fset.name == "" { - err = fmt.Errorf("flag redefined: %s", k) - } else { - err = fmt.Errorf("%s flag redefined: %s", fset.name, k) - } - fmt.Fprintln(fset.Out(), err.Error()) - // Happens only if flags are declared with identical names - switch dest.errorHandling { - case ContinueOnError: - return err - case ExitOnError: - os.Exit(2) - case PanicOnError: - panic(err) - } - } - newF := *f - newF.Value = mergeVal{f.Value, k, fset} - if dest.formal == nil { - dest.formal = make(map[string]*Flag) - } - dest.formal[k] = &newF - } - } - return nil -} - -// IsEmpty reports if the FlagSet is actually empty. -func (fs *FlagSet) IsEmpty() bool { - return len(fs.actual) == 0 -} diff --git a/pkg/mflag/flag_test.go b/pkg/mflag/flag_test.go deleted file mode 100644 index 138355546e..0000000000 --- a/pkg/mflag/flag_test.go +++ /dev/null @@ -1,527 +0,0 @@ -// Copyright 2014-2016 The Docker & Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package mflag - -import ( - "bytes" - "fmt" - "os" - "sort" - "strings" - "testing" - "time" -) - -// ResetForTesting clears all flag state and sets the usage function as directed. -// After calling ResetForTesting, parse errors in flag handling will not -// exit the program. -func ResetForTesting(usage func()) { - CommandLine = NewFlagSet(os.Args[0], ContinueOnError) - Usage = usage -} -func boolString(s string) string { - if s == "0" { - return "false" - } - return "true" -} - -func TestEverything(t *testing.T) { - ResetForTesting(nil) - Bool([]string{"test_bool"}, false, "bool value") - Int([]string{"test_int"}, 0, "int value") - Int64([]string{"test_int64"}, 0, "int64 value") - Uint([]string{"test_uint"}, 0, "uint value") - Uint64([]string{"test_uint64"}, 0, "uint64 value") - String([]string{"test_string"}, "0", "string value") - Float64([]string{"test_float64"}, 0, "float64 value") - Duration([]string{"test_duration"}, 0, "time.Duration value") - - m := make(map[string]*Flag) - desired := "0" - visitor := func(f *Flag) { - for _, name := range f.Names { - if len(name) > 5 && name[0:5] == "test_" { - m[name] = f - ok := false - switch { - case f.Value.String() == desired: - ok = true - case name == "test_bool" && f.Value.String() == boolString(desired): - ok = true - case name == "test_duration" && f.Value.String() == desired+"s": - ok = true - } - if !ok { - t.Error("Visit: bad value", f.Value.String(), "for", name) - } - } - } - } - VisitAll(visitor) - if len(m) != 8 { - t.Error("VisitAll misses some flags") - for k, v := range m { - t.Log(k, *v) - } - } - m = make(map[string]*Flag) - Visit(visitor) - if len(m) != 0 { - t.Errorf("Visit sees unset flags") - for k, v := range m { - t.Log(k, *v) - } - } - // Now set all flags - Set("test_bool", "true") - Set("test_int", "1") - Set("test_int64", "1") - Set("test_uint", "1") - Set("test_uint64", "1") - Set("test_string", "1") - Set("test_float64", "1") - Set("test_duration", "1s") - desired = "1" - Visit(visitor) - if len(m) != 8 { - t.Error("Visit fails after set") - for k, v := range m { - t.Log(k, *v) - } - } - // Now test they're visited in sort order. - var flagNames []string - Visit(func(f *Flag) { - for _, name := range f.Names { - flagNames = append(flagNames, name) - } - }) - if !sort.StringsAreSorted(flagNames) { - t.Errorf("flag names not sorted: %v", flagNames) - } -} - -func TestGet(t *testing.T) { - ResetForTesting(nil) - Bool([]string{"test_bool"}, true, "bool value") - Int([]string{"test_int"}, 1, "int value") - Int64([]string{"test_int64"}, 2, "int64 value") - Uint([]string{"test_uint"}, 3, "uint value") - Uint64([]string{"test_uint64"}, 4, "uint64 value") - String([]string{"test_string"}, "5", "string value") - Float64([]string{"test_float64"}, 6, "float64 value") - Duration([]string{"test_duration"}, 7, "time.Duration value") - - visitor := func(f *Flag) { - for _, name := range f.Names { - if len(name) > 5 && name[0:5] == "test_" { - g, ok := f.Value.(Getter) - if !ok { - t.Errorf("Visit: value does not satisfy Getter: %T", f.Value) - return - } - switch name { - case "test_bool": - ok = g.Get() == true - case "test_int": - ok = g.Get() == int(1) - case "test_int64": - ok = g.Get() == int64(2) - case "test_uint": - ok = g.Get() == uint(3) - case "test_uint64": - ok = g.Get() == uint64(4) - case "test_string": - ok = g.Get() == "5" - case "test_float64": - ok = g.Get() == float64(6) - case "test_duration": - ok = g.Get() == time.Duration(7) - } - if !ok { - t.Errorf("Visit: bad value %T(%v) for %s", g.Get(), g.Get(), name) - } - } - } - } - VisitAll(visitor) -} - -func testParse(f *FlagSet, t *testing.T) { - if f.Parsed() { - t.Error("f.Parse() = true before Parse") - } - boolFlag := f.Bool([]string{"bool"}, false, "bool value") - bool2Flag := f.Bool([]string{"bool2"}, false, "bool2 value") - f.Bool([]string{"bool3"}, false, "bool3 value") - bool4Flag := f.Bool([]string{"bool4"}, false, "bool4 value") - intFlag := f.Int([]string{"-int"}, 0, "int value") - int64Flag := f.Int64([]string{"-int64"}, 0, "int64 value") - uintFlag := f.Uint([]string{"uint"}, 0, "uint value") - uint64Flag := f.Uint64([]string{"-uint64"}, 0, "uint64 value") - stringFlag := f.String([]string{"string"}, "0", "string value") - f.String([]string{"string2"}, "0", "string2 value") - singleQuoteFlag := f.String([]string{"squote"}, "", "single quoted value") - doubleQuoteFlag := f.String([]string{"dquote"}, "", "double quoted value") - mixedQuoteFlag := f.String([]string{"mquote"}, "", "mixed quoted value") - mixed2QuoteFlag := f.String([]string{"mquote2"}, "", "mixed2 quoted value") - nestedQuoteFlag := f.String([]string{"nquote"}, "", "nested quoted value") - nested2QuoteFlag := f.String([]string{"nquote2"}, "", "nested2 quoted value") - float64Flag := f.Float64([]string{"float64"}, 0, "float64 value") - durationFlag := f.Duration([]string{"duration"}, 5*time.Second, "time.Duration value") - extra := "one-extra-argument" - args := []string{ - "-bool", - "-bool2=true", - "-bool4=false", - "--int", "22", - "--int64", "0x23", - "-uint", "24", - "--uint64", "25", - "-string", "hello", - "-squote='single'", - `-dquote="double"`, - `-mquote='mixed"`, - `-mquote2="mixed2'`, - `-nquote="'single nested'"`, - `-nquote2='"double nested"'`, - "-float64", "2718e28", - "-duration", "2m", - extra, - } - if err := f.Parse(args); err != nil { - t.Fatal(err) - } - if !f.Parsed() { - t.Error("f.Parse() = false after Parse") - } - if *boolFlag != true { - t.Error("bool flag should be true, is ", *boolFlag) - } - if *bool2Flag != true { - t.Error("bool2 flag should be true, is ", *bool2Flag) - } - if !f.IsSet("bool2") { - t.Error("bool2 should be marked as set") - } - if f.IsSet("bool3") { - t.Error("bool3 should not be marked as set") - } - if !f.IsSet("bool4") { - t.Error("bool4 should be marked as set") - } - if *bool4Flag != false { - t.Error("bool4 flag should be false, is ", *bool4Flag) - } - if *intFlag != 22 { - t.Error("int flag should be 22, is ", *intFlag) - } - if *int64Flag != 0x23 { - t.Error("int64 flag should be 0x23, is ", *int64Flag) - } - if *uintFlag != 24 { - t.Error("uint flag should be 24, is ", *uintFlag) - } - if *uint64Flag != 25 { - t.Error("uint64 flag should be 25, is ", *uint64Flag) - } - if *stringFlag != "hello" { - t.Error("string flag should be `hello`, is ", *stringFlag) - } - if !f.IsSet("string") { - t.Error("string flag should be marked as set") - } - if f.IsSet("string2") { - t.Error("string2 flag should not be marked as set") - } - if *singleQuoteFlag != "single" { - t.Error("single quote string flag should be `single`, is ", *singleQuoteFlag) - } - if *doubleQuoteFlag != "double" { - t.Error("double quote string flag should be `double`, is ", *doubleQuoteFlag) - } - if *mixedQuoteFlag != `'mixed"` { - t.Error("mixed quote string flag should be `'mixed\"`, is ", *mixedQuoteFlag) - } - if *mixed2QuoteFlag != `"mixed2'` { - t.Error("mixed2 quote string flag should be `\"mixed2'`, is ", *mixed2QuoteFlag) - } - if *nestedQuoteFlag != "'single nested'" { - t.Error("nested quote string flag should be `'single nested'`, is ", *nestedQuoteFlag) - } - if *nested2QuoteFlag != `"double nested"` { - t.Error("double quote string flag should be `\"double nested\"`, is ", *nested2QuoteFlag) - } - if *float64Flag != 2718e28 { - t.Error("float64 flag should be 2718e28, is ", *float64Flag) - } - if *durationFlag != 2*time.Minute { - t.Error("duration flag should be 2m, is ", *durationFlag) - } - if len(f.Args()) != 1 { - t.Error("expected one argument, got", len(f.Args())) - } else if f.Args()[0] != extra { - t.Errorf("expected argument %q got %q", extra, f.Args()[0]) - } -} - -func testPanic(f *FlagSet, t *testing.T) { - f.Int([]string{"-int"}, 0, "int value") - if f.Parsed() { - t.Error("f.Parse() = true before Parse") - } - args := []string{ - "-int", "21", - } - f.Parse(args) -} - -func TestParsePanic(t *testing.T) { - ResetForTesting(func() {}) - testPanic(CommandLine, t) -} - -func TestParse(t *testing.T) { - ResetForTesting(func() { t.Error("bad parse") }) - testParse(CommandLine, t) -} - -func TestFlagSetParse(t *testing.T) { - testParse(NewFlagSet("test", ContinueOnError), t) -} - -// Declare a user-defined flag type. -type flagVar []string - -func (f *flagVar) String() string { - return fmt.Sprint([]string(*f)) -} - -func (f *flagVar) Set(value string) error { - *f = append(*f, value) - return nil -} - -func TestUserDefined(t *testing.T) { - var flags FlagSet - flags.Init("test", ContinueOnError) - var v flagVar - flags.Var(&v, []string{"v"}, "usage") - if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil { - t.Error(err) - } - if len(v) != 3 { - t.Fatal("expected 3 args; got ", len(v)) - } - expect := "[1 2 3]" - if v.String() != expect { - t.Errorf("expected value %q got %q", expect, v.String()) - } -} - -// Declare a user-defined boolean flag type. -type boolFlagVar struct { - count int -} - -func (b *boolFlagVar) String() string { - return fmt.Sprintf("%d", b.count) -} - -func (b *boolFlagVar) Set(value string) error { - if value == "true" { - b.count++ - } - return nil -} - -func (b *boolFlagVar) IsBoolFlag() bool { - return b.count < 4 -} - -func TestUserDefinedBool(t *testing.T) { - var flags FlagSet - flags.Init("test", ContinueOnError) - var b boolFlagVar - var err error - flags.Var(&b, []string{"b"}, "usage") - if err = flags.Parse([]string{"-b", "-b", "-b", "-b=true", "-b=false", "-b", "barg", "-b"}); err != nil { - if b.count < 4 { - t.Error(err) - } - } - - if b.count != 4 { - t.Errorf("want: %d; got: %d", 4, b.count) - } - - if err == nil { - t.Error("expected error; got none") - } -} - -func TestSetOutput(t *testing.T) { - var flags FlagSet - var buf bytes.Buffer - flags.SetOutput(&buf) - flags.Init("test", ContinueOnError) - flags.Parse([]string{"-unknown"}) - if out := buf.String(); !strings.Contains(out, "-unknown") { - t.Logf("expected output mentioning unknown; got %q", out) - } -} - -// This tests that one can reset the flags. This still works but not well, and is -// superseded by FlagSet. -func TestChangingArgs(t *testing.T) { - ResetForTesting(func() { t.Fatal("bad parse") }) - oldArgs := os.Args - defer func() { os.Args = oldArgs }() - os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"} - before := Bool([]string{"before"}, false, "") - if err := CommandLine.Parse(os.Args[1:]); err != nil { - t.Fatal(err) - } - cmd := Arg(0) - os.Args = Args() - after := Bool([]string{"after"}, false, "") - Parse() - args := Args() - - if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" { - t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args) - } -} - -// Test that -help invokes the usage message and returns ErrHelp. -func TestHelp(t *testing.T) { - var helpCalled = false - fs := NewFlagSet("help test", ContinueOnError) - fs.Usage = func() { helpCalled = true } - var flag bool - fs.BoolVar(&flag, []string{"flag"}, false, "regular flag") - // Regular flag invocation should work - err := fs.Parse([]string{"-flag=true"}) - if err != nil { - t.Fatal("expected no error; got ", err) - } - if !flag { - t.Error("flag was not set by -flag") - } - if helpCalled { - t.Error("help called for regular flag") - helpCalled = false // reset for next test - } - // Help flag should work as expected. - err = fs.Parse([]string{"-help"}) - if err == nil { - t.Fatal("error expected") - } - if err != ErrHelp { - t.Fatal("expected ErrHelp; got ", err) - } - if !helpCalled { - t.Fatal("help was not called") - } - // If we define a help flag, that should override. - var help bool - fs.BoolVar(&help, []string{"help"}, false, "help flag") - helpCalled = false - err = fs.Parse([]string{"-help"}) - if err != nil { - t.Fatal("expected no error for defined -help; got ", err) - } - if helpCalled { - t.Fatal("help was called; should not have been for defined help flag") - } -} - -// Test the flag count functions. -func TestFlagCounts(t *testing.T) { - fs := NewFlagSet("help test", ContinueOnError) - var flag bool - fs.BoolVar(&flag, []string{"flag1"}, false, "regular flag") - fs.BoolVar(&flag, []string{"#deprecated1"}, false, "regular flag") - fs.BoolVar(&flag, []string{"f", "flag2"}, false, "regular flag") - fs.BoolVar(&flag, []string{"#d", "#deprecated2"}, false, "regular flag") - fs.BoolVar(&flag, []string{"flag3"}, false, "regular flag") - fs.BoolVar(&flag, []string{"g", "#flag4", "-flag4"}, false, "regular flag") - - if fs.FlagCount() != 6 { - t.Fatal("FlagCount wrong. ", fs.FlagCount()) - } - if fs.FlagCountUndeprecated() != 4 { - t.Fatal("FlagCountUndeprecated wrong. ", fs.FlagCountUndeprecated()) - } - if fs.NFlag() != 0 { - t.Fatal("NFlag wrong. ", fs.NFlag()) - } - err := fs.Parse([]string{"-fd", "-g", "-flag4"}) - if err != nil { - t.Fatal("expected no error for defined -help; got ", err) - } - if fs.NFlag() != 4 { - t.Fatal("NFlag wrong. ", fs.NFlag()) - } -} - -// Show up bug in sortFlags -func TestSortFlags(t *testing.T) { - fs := NewFlagSet("help TestSortFlags", ContinueOnError) - - var err error - - var b bool - fs.BoolVar(&b, []string{"b", "-banana"}, false, "usage") - - err = fs.Parse([]string{"--banana=true"}) - if err != nil { - t.Fatal("expected no error; got ", err) - } - - count := 0 - - fs.VisitAll(func(flag *Flag) { - count++ - if flag == nil { - t.Fatal("VisitAll should not return a nil flag") - } - }) - flagcount := fs.FlagCount() - if flagcount != count { - t.Fatalf("FlagCount (%d) != number (%d) of elements visited", flagcount, count) - } - // Make sure its idempotent - if flagcount != fs.FlagCount() { - t.Fatalf("FlagCount (%d) != fs.FlagCount() (%d) of elements visited", flagcount, fs.FlagCount()) - } - - count = 0 - fs.Visit(func(flag *Flag) { - count++ - if flag == nil { - t.Fatal("Visit should not return a nil flag") - } - }) - nflag := fs.NFlag() - if nflag != count { - t.Fatalf("NFlag (%d) != number (%d) of elements visited", nflag, count) - } - if nflag != fs.NFlag() { - t.Fatalf("NFlag (%d) != fs.NFlag() (%d) of elements visited", nflag, fs.NFlag()) - } -} - -func TestMergeFlags(t *testing.T) { - base := NewFlagSet("base", ContinueOnError) - base.String([]string{"f"}, "", "") - - fs := NewFlagSet("test", ContinueOnError) - Merge(fs, base) - if len(fs.formal) != 1 { - t.Fatalf("FlagCount (%d) != number (1) of elements merged", len(fs.formal)) - } -} diff --git a/pkg/testutil/assert/assert.go b/pkg/testutil/assert/assert.go index 5b0dcce67a..f6ee8d75f0 100644 --- a/pkg/testutil/assert/assert.go +++ b/pkg/testutil/assert/assert.go @@ -18,7 +18,7 @@ type TestingT interface { // they are not equal. func Equal(t TestingT, actual, expected interface{}) { if expected != actual { - fatal(t, fmt.Sprintf("Expected '%v' (%T) got '%v' (%T)", expected, expected, actual, actual)) + fatal(t, "Expected '%v' (%T) got '%v' (%T)", expected, expected, actual, actual) } } @@ -26,12 +26,12 @@ func Equal(t TestingT, actual, expected interface{}) { // the same items. func EqualStringSlice(t TestingT, actual, expected []string) { if len(actual) != len(expected) { - t.Fatalf("Expected (length %d): %q\nActual (length %d): %q", + fatal(t, "Expected (length %d): %q\nActual (length %d): %q", len(expected), expected, len(actual), actual) } for i, item := range actual { if item != expected[i] { - t.Fatalf("Slices differ at element %d, expected %q got %q", + fatal(t, "Slices differ at element %d, expected %q got %q", i, expected[i], item) } } @@ -40,7 +40,7 @@ func EqualStringSlice(t TestingT, actual, expected []string) { // NilError asserts that the error is nil, otherwise it fails the test. func NilError(t TestingT, err error) { if err != nil { - fatal(t, fmt.Sprintf("Expected no error, got: %s", err.Error())) + fatal(t, "Expected no error, got: %s", err.Error()) } } @@ -52,7 +52,7 @@ func Error(t TestingT, err error, contains string) { } if !strings.Contains(err.Error(), contains) { - fatal(t, fmt.Sprintf("Expected error to contain '%s', got '%s'", contains, err.Error())) + fatal(t, "Expected error to contain '%s', got '%s'", contains, err.Error()) } } @@ -60,11 +60,26 @@ func Error(t TestingT, err error, contains string) { // test. func Contains(t TestingT, actual, contains string) { if !strings.Contains(actual, contains) { - fatal(t, fmt.Sprintf("Expected '%s' to contain '%s'", actual, contains)) + fatal(t, "Expected '%s' to contain '%s'", actual, contains) } } -func fatal(t TestingT, msg string) { - _, file, line, _ := runtime.Caller(2) - t.Fatalf("%s:%d: %s", filepath.Base(file), line, msg) +// NotNil fails the test if the object is nil +func NotNil(t TestingT, obj interface{}) { + if obj == nil { + fatal(t, "Expected non-nil value.") + } +} + +func fatal(t TestingT, format string, args ...interface{}) { + t.Fatalf(errorSource()+format, args...) +} + +// See testing.decorate() +func errorSource() string { + _, filename, line, ok := runtime.Caller(3) + if !ok { + return "" + } + return fmt.Sprintf("%s:%d: ", filepath.Base(filename), line) } diff --git a/pkg/testutil/tempfile/tempfile.go b/pkg/testutil/tempfile/tempfile.go new file mode 100644 index 0000000000..0e09d99dae --- /dev/null +++ b/pkg/testutil/tempfile/tempfile.go @@ -0,0 +1,36 @@ +package tempfile + +import ( + "io/ioutil" + "os" + + "github.com/docker/docker/pkg/testutil/assert" +) + +// TempFile is a temporary file that can be used with unit tests. TempFile +// reduces the boilerplate setup required in each test case by handling +// setup errors. +type TempFile struct { + File *os.File +} + +// NewTempFile returns a new temp file with contents +func NewTempFile(t assert.TestingT, prefix string, content string) *TempFile { + file, err := ioutil.TempFile("", prefix+"-") + assert.NilError(t, err) + + _, err = file.Write([]byte(content)) + assert.NilError(t, err) + file.Close() + return &TempFile{File: file} +} + +// Name returns the filename +func (f *TempFile) Name() string { + return f.File.Name() +} + +// Remove removes the file +func (f *TempFile) Remove() { + os.Remove(f.Name()) +} diff --git a/registry/config.go b/registry/config.go index 90723bfd51..0adcc9c122 100644 --- a/registry/config.go +++ b/registry/config.go @@ -8,9 +8,9 @@ import ( "strings" "github.com/docker/docker/opts" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/reference" registrytypes "github.com/docker/engine-api/types/registry" + "github.com/spf13/pflag" ) // ServiceOptions holds command line options. @@ -70,14 +70,14 @@ var lookupIP = net.LookupIP // InstallCliFlags adds command-line options to the top-level flag parser for // the current process. -func (options *ServiceOptions) InstallCliFlags(cmd *flag.FlagSet, usageFn func(string) string) { +func (options *ServiceOptions) InstallCliFlags(flags *pflag.FlagSet) { mirrors := opts.NewNamedListOptsRef("registry-mirrors", &options.Mirrors, ValidateMirror) - cmd.Var(mirrors, []string{"-registry-mirror"}, usageFn("Preferred Docker registry mirror")) - insecureRegistries := opts.NewNamedListOptsRef("insecure-registries", &options.InsecureRegistries, ValidateIndexName) - cmd.Var(insecureRegistries, []string{"-insecure-registry"}, usageFn("Enable insecure registry communication")) - options.installCliPlatformFlags(cmd, usageFn) + flags.Var(mirrors, "registry-mirror", "Preferred Docker registry mirror") + flags.Var(insecureRegistries, "insecure-registry", "Enable insecure registry communication") + + options.installCliPlatformFlags(flags) } // newServiceConfig returns a new instance of ServiceConfig diff --git a/registry/config_unix.go b/registry/config_unix.go index bae33970cc..d692e8ef50 100644 --- a/registry/config_unix.go +++ b/registry/config_unix.go @@ -3,7 +3,7 @@ package registry import ( - flag "github.com/docker/docker/pkg/mflag" + "github.com/spf13/pflag" ) var ( @@ -20,6 +20,6 @@ func cleanPath(s string) string { } // installCliPlatformFlags handles any platform specific flags for the service. -func (options *ServiceOptions) installCliPlatformFlags(cmd *flag.FlagSet, usageFn func(string) string) { - cmd.BoolVar(&options.V2Only, []string{"-disable-legacy-registry"}, false, usageFn("Disable contacting legacy registries")) +func (options *ServiceOptions) installCliPlatformFlags(flags *pflag.FlagSet) { + flags.BoolVar(&options.V2Only, "disable-legacy-registry", false, "Disable contacting legacy registries") } diff --git a/registry/config_windows.go b/registry/config_windows.go index d8b7f7eae7..d1b313dc1e 100644 --- a/registry/config_windows.go +++ b/registry/config_windows.go @@ -5,7 +5,7 @@ import ( "path/filepath" "strings" - flag "github.com/docker/docker/pkg/mflag" + "github.com/spf13/pflag" ) // CertsDir is the directory where certificates are stored @@ -20,6 +20,6 @@ func cleanPath(s string) string { } // installCliPlatformFlags handles any platform specific flags for the service. -func (options *ServiceOptions) installCliPlatformFlags(cmd *flag.FlagSet, usageFn func(string) string) { +func (options *ServiceOptions) installCliPlatformFlags(flags *pflag.FlagSet) { // No Windows specific flags. } diff --git a/runconfig/opts/runtime.go b/runconfig/opts/runtime.go index 1fc099834e..541fda017a 100644 --- a/runconfig/opts/runtime.go +++ b/runconfig/opts/runtime.go @@ -72,3 +72,8 @@ func (o *RuntimeOpt) GetMap() map[string]types.Runtime { return map[string]types.Runtime{} } + +// Type returns the type of the option +func (o *RuntimeOpt) Type() string { + return "runtime" +} diff --git a/vendor/src/github.com/spf13/cobra/.gitignore b/vendor/src/github.com/spf13/cobra/.gitignore index 36d1a84d39..1b8c7c2611 100644 --- a/vendor/src/github.com/spf13/cobra/.gitignore +++ b/vendor/src/github.com/spf13/cobra/.gitignore @@ -19,6 +19,18 @@ _cgo_export.* _testmain.go +# Vim files https://github.com/github/gitignore/blob/master/Global/Vim.gitignore +# swap +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + *.exe cobra.test diff --git a/vendor/src/github.com/spf13/cobra/bash_completions.go b/vendor/src/github.com/spf13/cobra/bash_completions.go index 3f33bb0ec8..236dee67f2 100644 --- a/vendor/src/github.com/spf13/cobra/bash_completions.go +++ b/vendor/src/github.com/spf13/cobra/bash_completions.go @@ -116,12 +116,12 @@ __handle_reply() fi local completions - if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then - completions=("${must_have_one_flag[@]}") - elif [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions=("${commands[@]}") + if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then completions=("${must_have_one_noun[@]}") - else - completions=("${commands[@]}") + fi + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions+=("${must_have_one_flag[@]}") fi COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) @@ -167,6 +167,11 @@ __handle_flag() must_have_one_flag=() fi + # if you set a flag which only applies to this command, don't show subcommands + if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + commands=() + fi + # keep flag value with flagname as flaghash if [ -n "${flagvalue}" ] ; then flaghash[${flagname}]=${flagvalue} @@ -263,6 +268,7 @@ func postscript(w io.Writer, name string) error { local c=0 local flags=() local two_word_flags=() + local local_nonpersistent_flags=() local flags_with_completion=() local flags_completion=() local commands=("%s") @@ -360,7 +366,7 @@ func writeFlagHandler(name string, annotations map[string][]string, w io.Writer) } func writeShortFlag(flag *pflag.Flag, w io.Writer) error { - b := (flag.Value.Type() == "bool") + b := (len(flag.NoOptDefVal) > 0) name := flag.Shorthand format := " " if !b { @@ -374,7 +380,7 @@ func writeShortFlag(flag *pflag.Flag, w io.Writer) error { } func writeFlag(flag *pflag.Flag, w io.Writer) error { - b := (flag.Value.Type() == "bool") + b := (len(flag.NoOptDefVal) > 0) name := flag.Name format := " flags+=(\"--%s" if !b { @@ -387,9 +393,24 @@ func writeFlag(flag *pflag.Flag, w io.Writer) error { return writeFlagHandler("--"+name, flag.Annotations, w) } +func writeLocalNonPersistentFlag(flag *pflag.Flag, w io.Writer) error { + b := (len(flag.NoOptDefVal) > 0) + name := flag.Name + format := " local_nonpersistent_flags+=(\"--%s" + if !b { + format += "=" + } + format += "\")\n" + if _, err := fmt.Fprintf(w, format, name); err != nil { + return err + } + return nil +} + func writeFlags(cmd *Command, w io.Writer) error { _, err := fmt.Fprintf(w, ` flags=() two_word_flags=() + local_nonpersistent_flags=() flags_with_completion=() flags_completion=() @@ -397,6 +418,7 @@ func writeFlags(cmd *Command, w io.Writer) error { if err != nil { return err } + localNonPersistentFlags := cmd.LocalNonPersistentFlags() var visitErr error cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { if err := writeFlag(flag, w); err != nil { @@ -409,6 +431,12 @@ func writeFlags(cmd *Command, w io.Writer) error { return } } + if localNonPersistentFlags.Lookup(flag.Name) != nil { + if err := writeLocalNonPersistentFlag(flag, w); err != nil { + visitErr = err + return + } + } }) if visitErr != nil { return visitErr diff --git a/vendor/src/github.com/spf13/cobra/bash_completions.md b/vendor/src/github.com/spf13/cobra/bash_completions.md index 84d5b012f4..6e3b71f13d 100644 --- a/vendor/src/github.com/spf13/cobra/bash_completions.md +++ b/vendor/src/github.com/spf13/cobra/bash_completions.md @@ -117,7 +117,7 @@ cmd := &cobra.Command{ ``` The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by -the completion aglorithm if entered manually, e.g. in: +the completion algorithm if entered manually, e.g. in: ```bash # kubectl get rc [tab][tab] @@ -175,7 +175,7 @@ So while there are many other files in the CWD it only shows me subdirs and thos # Specifiy custom flag completion -Similar to the filename completion and filtering usingn cobra.BashCompFilenameExt, you can specifiy +Similar to the filename completion and filtering using cobra.BashCompFilenameExt, you can specifiy a custom flag completion function with cobra.BashCompCustom: ```go diff --git a/vendor/src/github.com/spf13/cobra/cobra.go b/vendor/src/github.com/spf13/cobra/cobra.go index 0c4e2e5de1..93a2c0f3a7 100644 --- a/vendor/src/github.com/spf13/cobra/cobra.go +++ b/vendor/src/github.com/spf13/cobra/cobra.go @@ -41,6 +41,10 @@ var initializers []func() // Set this to true to enable it var EnablePrefixMatching = false +//EnableCommandSorting controls sorting of the slice of commands, which is turned on by default. +//To disable sorting, set it to false. +var EnableCommandSorting = true + //AddTemplateFunc adds a template function that's available to Usage and Help //template generation. func AddTemplateFunc(name string, tmplFunc interface{}) { diff --git a/vendor/src/github.com/spf13/cobra/command.go b/vendor/src/github.com/spf13/cobra/command.go index d9f85c14ef..892b07d300 100644 --- a/vendor/src/github.com/spf13/cobra/command.go +++ b/vendor/src/github.com/spf13/cobra/command.go @@ -21,6 +21,7 @@ import ( "io" "os" "path/filepath" + "sort" "strings" flag "github.com/spf13/pflag" @@ -105,6 +106,8 @@ type Command struct { commandsMaxUseLen int commandsMaxCommandPathLen int commandsMaxNameLen int + // is commands slice are sorted or not + commandsAreSorted bool flagErrorBuf *bytes.Buffer @@ -126,6 +129,9 @@ type Command struct { // Disable the flag parsing. If this is true all flags will be passed to the command as arguments. DisableFlagParsing bool + + // TraverseChildren parses flags on all parents before executing child command + TraverseChildren bool } // os.Args[1:] by default, if desired, can be overridden @@ -409,13 +415,14 @@ func argsMinusFirstX(args []string, x string) []string { return args } -// find the target command given the args and command tree +func isFlagArg(arg string) bool { + return ((len(arg) >= 3 && arg[1] == '-') || + (len(arg) >= 2 && arg[0] == '-' && arg[1] != '-')) +} + +// Find the target command given the args and command tree // Meant to be run on the highest node. Only searches down. func (c *Command) Find(args []string) (*Command, []string, error) { - if c == nil { - return nil, nil, fmt.Errorf("Called find() on a nil Command") - } - var innerfind func(*Command, []string) (*Command, []string) innerfind = func(c *Command, innerArgs []string) (*Command, []string) { @@ -424,28 +431,11 @@ func (c *Command) Find(args []string) (*Command, []string, error) { return c, innerArgs } nextSubCmd := argsWOflags[0] - matches := make([]*Command, 0) - for _, cmd := range c.commands { - if cmd.Name() == nextSubCmd || cmd.HasAlias(nextSubCmd) { // exact name or alias match - return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd)) - } - if EnablePrefixMatching { - if strings.HasPrefix(cmd.Name(), nextSubCmd) { // prefix match - matches = append(matches, cmd) - } - for _, x := range cmd.Aliases { - if strings.HasPrefix(x, nextSubCmd) { - matches = append(matches, cmd) - } - } - } - } - // only accept a single prefix match - multiple matches would be ambiguous - if len(matches) == 1 { - return innerfind(matches[0], argsMinusFirstX(innerArgs, argsWOflags[0])) + cmd := c.findNext(nextSubCmd) + if cmd != nil { + return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd)) } - return c, innerArgs } @@ -456,6 +446,66 @@ func (c *Command) Find(args []string) (*Command, []string, error) { return commandFound, a, nil } +func (c *Command) findNext(next string) *Command { + matches := make([]*Command, 0) + for _, cmd := range c.commands { + if cmd.Name() == next || cmd.HasAlias(next) { + return cmd + } + if EnablePrefixMatching && cmd.HasNameOrAliasPrefix(next) { + matches = append(matches, cmd) + } + } + + if len(matches) == 1 { + return matches[0] + } + return nil +} + +// Traverse the command tree to find the command, and parse args for +// each parent. +func (c *Command) Traverse(args []string) (*Command, []string, error) { + flags := []string{} + inFlag := false + + for i, arg := range args { + switch { + // A long flag with a space separated value + case strings.HasPrefix(arg, "--") && !strings.Contains(arg, "="): + // TODO: this isn't quite right, we should really check ahead for 'true' or 'false' + inFlag = !isBooleanFlag(arg[2:], c.Flags()) + flags = append(flags, arg) + continue + // A short flag with a space separated value + case strings.HasPrefix(arg, "-") && !strings.Contains(arg, "=") && len(arg) == 2 && !isBooleanShortFlag(arg[1:], c.Flags()): + inFlag = true + flags = append(flags, arg) + continue + // The value for a flag + case inFlag: + inFlag = false + flags = append(flags, arg) + continue + // A flag without a value, or with an `=` separated value + case isFlagArg(arg): + flags = append(flags, arg) + continue + } + + cmd := c.findNext(arg) + if cmd == nil { + return c, args, nil + } + + if err := c.ParseFlags(flags); err != nil { + return nil, args, err + } + return cmd.Traverse(args[i+1:]) + } + return c, args, nil +} + func (c *Command) findSuggestions(arg string) string { if c.DisableSuggestions { return "" @@ -668,7 +718,12 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { args = c.args } - cmd, flags, err := c.Find(args) + var flags []string + if c.TraverseChildren { + cmd, flags, err = c.Traverse(args) + } else { + cmd, flags, err = c.Find(args) + } if err != nil { // If found parse to a subcommand and then failed, talk about the subcommand if cmd != nil { @@ -680,6 +735,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { } return c, err } + err = cmd.execute(flags) if err != nil { // Always show help if requested, even if SilenceErrors is in @@ -754,8 +810,20 @@ func (c *Command) ResetCommands() { c.helpCommand = nil } -//Commands returns a slice of child commands. +// Sorts commands by their names +type commandSorterByName []*Command + +func (c commandSorterByName) Len() int { return len(c) } +func (c commandSorterByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +func (c commandSorterByName) Less(i, j int) bool { return c[i].Name() < c[j].Name() } + +// Commands returns a sorted slice of child commands. func (c *Command) Commands() []*Command { + // do not sort commands if it already sorted or sorting was disabled + if EnableCommandSorting && !c.commandsAreSorted{ + sort.Sort(commandSorterByName(c.commands)) + c.commandsAreSorted = true + } return c.commands } @@ -784,6 +852,7 @@ func (c *Command) AddCommand(cmds ...*Command) { x.SetGlobalNormalizationFunc(c.globNormFunc) } c.commands = append(c.commands, x) + c.commandsAreSorted = false } } @@ -953,6 +1022,20 @@ func (c *Command) HasAlias(s string) bool { return false } +// HasNameOrAliasPrefix returns true if the Name or any of aliases start +// with prefix +func (c *Command) HasNameOrAliasPrefix(prefix string) bool { + if strings.HasPrefix(c.Name(), prefix) { + return true + } + for _, alias := range c.Aliases { + if strings.HasPrefix(alias, prefix) { + return true + } + } + return false +} + func (c *Command) NameAndAliases() string { return strings.Join(append([]string{c.Name()}, c.Aliases...), ", ") } @@ -1065,6 +1148,19 @@ func (c *Command) Flags() *flag.FlagSet { return c.flags } +// LocalNonPersistentFlags are flags specific to this command which will NOT persist to subcommands +func (c *Command) LocalNonPersistentFlags() *flag.FlagSet { + persistentFlags := c.PersistentFlags() + + out := flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.LocalFlags().VisitAll(func(f *flag.Flag) { + if persistentFlags.Lookup(f.Name) == nil { + out.AddFlag(f) + } + }) + return out +} + // Get the local FlagSet specifically set in the current command func (c *Command) LocalFlags() *flag.FlagSet { c.mergePersistentFlags()