From 12234c67d4961206e8ba54864b7290b481fc564f Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Tue, 5 May 2015 00:18:28 -0400 Subject: [PATCH] cli: new daemon command and new cli package This patch creates a new cli package that allows to combine both client and daemon commands (there is only one daemon command: docker daemon). The `-d` and `--daemon` top-level flags are deprecated and a special message is added to prompt the user to use `docker daemon`. Providing top-level daemon-specific flags for client commands result in an error message prompting the user to use `docker daemon`. This patch does not break any old but correct usages. This also makes `-d` and `--daemon` flags, as well as the `daemon` command illegal in client-only binaries. Signed-off-by: Tibor Vass Upstream-commit: 96ce3a194aab2807fdd638825b9ea7cb9ba55c36 Component: engine --- components/engine/api/client/attach.go | 6 +- components/engine/api/client/build.go | 7 +- components/engine/api/client/cli.go | 250 ++++++------------ components/engine/api/client/client.go | 12 - components/engine/api/client/commit.go | 3 +- components/engine/api/client/cp.go | 3 +- components/engine/api/client/create.go | 3 +- components/engine/api/client/diff.go | 3 +- components/engine/api/client/events.go | 3 +- components/engine/api/client/exec.go | 7 +- components/engine/api/client/export.go | 3 +- components/engine/api/client/help.go | 34 --- components/engine/api/client/history.go | 3 +- components/engine/api/client/images.go | 3 +- components/engine/api/client/import.go | 3 +- components/engine/api/client/info.go | 3 +- components/engine/api/client/inspect.go | 14 +- components/engine/api/client/kill.go | 3 +- components/engine/api/client/load.go | 3 +- components/engine/api/client/login.go | 3 +- components/engine/api/client/logout.go | 3 +- components/engine/api/client/logs.go | 3 +- components/engine/api/client/pause.go | 3 +- components/engine/api/client/port.go | 3 +- components/engine/api/client/ps.go | 3 +- components/engine/api/client/pull.go | 3 +- components/engine/api/client/push.go | 3 +- components/engine/api/client/rename.go | 3 +- components/engine/api/client/restart.go | 3 +- components/engine/api/client/rm.go | 3 +- components/engine/api/client/rmi.go | 3 +- components/engine/api/client/run.go | 5 +- components/engine/api/client/save.go | 3 +- components/engine/api/client/search.go | 3 +- components/engine/api/client/start.go | 5 +- components/engine/api/client/stats.go | 3 +- components/engine/api/client/stop.go | 3 +- components/engine/api/client/tag.go | 3 +- components/engine/api/client/top.go | 3 +- components/engine/api/client/unpause.go | 3 +- components/engine/api/client/version.go | 7 +- components/engine/api/client/wait.go | 3 +- components/engine/cli/cli.go | 200 ++++++++++++++ components/engine/cli/client.go | 12 + components/engine/cli/common.go | 20 ++ components/engine/daemon/config.go | 34 +-- .../engine/daemon/config_experimental.go | 6 +- components/engine/daemon/config_linux.go | 39 +-- components/engine/daemon/config_stub.go | 4 +- components/engine/daemon/config_windows.go | 4 +- components/engine/docker/client.go | 27 +- components/engine/docker/common.go | 101 +++++++ components/engine/docker/daemon.go | 164 ++++++++++-- components/engine/docker/daemon_none.go | 12 + components/engine/docker/docker.go | 143 +++------- components/engine/docker/flags.go | 170 ++++-------- components/engine/docker/log.go | 14 - .../integration-cli/docker_cli_proxy_test.go | 3 +- components/engine/opts/hosts_unix.go | 7 + components/engine/opts/hosts_windows.go | 7 + components/engine/opts/opts.go | 25 +- components/engine/opts/opts_test.go | 2 +- components/engine/opts/ulimit.go | 13 +- components/engine/pkg/mflag/flag.go | 52 +++- components/engine/pkg/tlsconfig/config.go | 15 +- components/engine/registry/config.go | 6 +- components/engine/runconfig/parse.go | 4 +- 67 files changed, 913 insertions(+), 614 deletions(-) delete mode 100644 components/engine/api/client/help.go create mode 100644 components/engine/cli/cli.go create mode 100644 components/engine/cli/client.go create mode 100644 components/engine/cli/common.go create mode 100644 components/engine/docker/common.go create mode 100644 components/engine/docker/daemon_none.go delete mode 100644 components/engine/docker/log.go create mode 100644 components/engine/opts/hosts_unix.go create mode 100644 components/engine/opts/hosts_windows.go diff --git a/components/engine/api/client/attach.go b/components/engine/api/client/attach.go index d851bfda34..584c53ea06 100644 --- a/components/engine/api/client/attach.go +++ b/components/engine/api/client/attach.go @@ -8,6 +8,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/signal" ) @@ -16,9 +17,10 @@ import ( // // Usage: docker attach [OPTIONS] CONTAINER func (cli *DockerCli) CmdAttach(args ...string) error { - cmd := cli.Subcmd("attach", []string{"CONTAINER"}, "Attach to a running container", true) + cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, "Attach to a running container", true) noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN") proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process") + cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) @@ -75,7 +77,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return err } if status != 0 { - return StatusError{StatusCode: status} + return Cli.StatusError{StatusCode: status} } return nil diff --git a/components/engine/api/client/build.go b/components/engine/api/client/build.go index d2799ce900..a6eee12b04 100644 --- a/components/engine/api/client/build.go +++ b/components/engine/api/client/build.go @@ -18,6 +18,7 @@ import ( "strings" "github.com/docker/docker/api" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/graph/tags" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/archive" @@ -46,7 +47,7 @@ const ( // // Usage: docker build [OPTIONS] PATH | URL | - func (cli *DockerCli) CmdBuild(args ...string) error { - cmd := cli.Subcmd("build", []string{"PATH | URL | -"}, "Build a new image from the source code at PATH", true) + cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, "Build a new image from the source code at PATH", true) tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image") suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers") noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image") @@ -64,7 +65,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container") ulimits := make(map[string]*ulimit.Ulimit) - flUlimits := opts.NewUlimitOpt(ulimits) + flUlimits := opts.NewUlimitOpt(&ulimits) cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options") cmd.Require(flag.Exact, 1) @@ -325,7 +326,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if jerr.Code == 0 { jerr.Code = 1 } - return StatusError{Status: jerr.Message, StatusCode: jerr.Code} + return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code} } return err } diff --git a/components/engine/api/client/cli.go b/components/engine/api/client/cli.go index a8dd276cee..fc4e236285 100644 --- a/components/engine/api/client/cli.go +++ b/components/engine/api/client/cli.go @@ -2,25 +2,28 @@ package client import ( "crypto/tls" - "encoding/json" "errors" "fmt" "io" "net/http" "net/url" - "reflect" + "os" "strings" - "text/template" + "github.com/docker/docker/cli" "github.com/docker/docker/cliconfig" - flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/opts" "github.com/docker/docker/pkg/sockets" "github.com/docker/docker/pkg/term" + "github.com/docker/docker/pkg/tlsconfig" ) // DockerCli represents the docker command line client. // Instances of the client can be returned from NewDockerCli. type DockerCli struct { + // initializing closure + init func() error + // proto holds the client protocol i.e. unix. proto string // addr holds the client address. @@ -55,116 +58,11 @@ type DockerCli struct { transport *http.Transport } -var funcMap = template.FuncMap{ - "json": func(v interface{}) string { - a, _ := json.Marshal(v) - return string(a) - }, -} - -func (cli *DockerCli) Out() io.Writer { - return cli.out -} - -func (cli *DockerCli) Err() io.Writer { - return cli.err -} - -func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) { - camelArgs := make([]string, len(args)) - for i, s := range args { - if len(s) == 0 { - return nil, false - } - camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:]) +func (cli *DockerCli) Initialize() error { + if cli.init == nil { + return nil } - methodName := "Cmd" + strings.Join(camelArgs, "") - method := reflect.ValueOf(cli).MethodByName(methodName) - if !method.IsValid() { - return nil, false - } - return method.Interface().(func(...string) error), true -} - -// Cmd executes the specified command. -func (cli *DockerCli) Cmd(args ...string) error { - if len(args) > 1 { - method, exists := cli.getMethod(args[:2]...) - if exists { - return method(args[2:]...) - } - } - if len(args) > 0 { - method, exists := cli.getMethod(args[0]) - if !exists { - return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'.", args[0]) - } - return method(args[1:]...) - } - return cli.CmdHelp() -} - -// Subcmd is a subcommand of the main "docker" command. -// A subcommand represents an action that can be performed -// from the Docker command line client. -// -// Multiple subcommand synopses may be provided with one 'Usage' line being -// printed for each in the following way: -// -// Usage: docker [OPTIONS] -// docker [OPTIONS] -// ... -// -// If no undeprecated flags are added to the returned FlagSet, "[OPTIONS]" will -// not be included on the usage synopsis lines. If no synopses are given, only -// one usage synopsis line will be printed with nothing following the -// "[OPTIONS]" section -// -// To see all available subcommands, run "docker --help". -func (cli *DockerCli) 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() { - options := "" - if flags.FlagCountUndeprecated() > 0 { - options = " [OPTIONS]" - } - - 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%s", lead, name, options, synopsis) - } - - fmt.Fprintf(flags.Out(), "\n\n%s\n", description) - } - - return flags + return cli.init() } // CheckTtyInput checks if we are trying to attach to a container tty @@ -187,64 +85,78 @@ func (cli *DockerCli) PsFormat() string { // 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, keyFile string, proto, addr string, tlsConfig *tls.Config) *DockerCli { - var ( - inFd uintptr - outFd uintptr - isTerminalIn = false - isTerminalOut = false - scheme = "http" - basePath = "" - ) - - if tlsConfig != nil { - scheme = "https" - } - if in != nil { - inFd, isTerminalIn = term.GetFdInfo(in) +func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli { + cli := &DockerCli{ + in: in, + out: out, + err: err, + keyFile: clientFlags.Common.TrustKey, } - if out != nil { - outFd, isTerminalOut = term.GetFdInfo(out) + cli.init = func() error { + clientFlags.PostParse() + + hosts := clientFlags.Common.Hosts + + switch len(hosts) { + case 0: + defaultHost := os.Getenv("DOCKER_HOST") + if defaultHost == "" { + defaultHost = opts.DefaultHost + } + defaultHost, err := opts.ValidateHost(defaultHost) + if err != nil { + return err + } + hosts = []string{defaultHost} + case 1: + // only accept one host to talk to + default: + return errors.New("Please specify only one -H") + } + + protoAddrParts := strings.SplitN(hosts[0], "://", 2) + cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1] + + if cli.proto == "tcp" { + // error is checked in pkg/parsers already + parsed, _ := url.Parse("tcp://" + cli.addr) + cli.addr = parsed.Host + cli.basePath = parsed.Path + } + + if clientFlags.Common.TLSOptions != nil { + cli.scheme = "https" + var e error + cli.tlsConfig, e = tlsconfig.Client(*clientFlags.Common.TLSOptions) + if e != nil { + return e + } + } else { + cli.scheme = "http" + } + + if cli.in != nil { + cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in) + } + if cli.out != nil { + cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out) + } + + // The transport is created here for reuse during the client session. + cli.transport = &http.Transport{ + TLSClientConfig: cli.tlsConfig, + } + sockets.ConfigureTCPTransport(cli.transport, cli.proto, cli.addr) + + configFile, e := cliconfig.Load(cliconfig.ConfigDir()) + if e != nil { + fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e) + } + cli.configFile = configFile + + return nil } - if err == nil { - err = out - } - - // The transport is created here for reuse during the client session. - tr := &http.Transport{ - TLSClientConfig: tlsConfig, - } - sockets.ConfigureTCPTransport(tr, proto, addr) - - configFile, e := cliconfig.Load(cliconfig.ConfigDir()) - if e != nil { - fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) - } - - if proto == "tcp" { - // error is checked in pkg/parsers already - parsed, _ := url.Parse("tcp://" + addr) - addr = parsed.Host - basePath = parsed.Path - } - - return &DockerCli{ - proto: proto, - addr: addr, - basePath: basePath, - configFile: configFile, - in: in, - out: out, - err: err, - keyFile: keyFile, - inFd: inFd, - outFd: outFd, - isTerminalIn: isTerminalIn, - isTerminalOut: isTerminalOut, - tlsConfig: tlsConfig, - scheme: scheme, - transport: tr, - } + return cli } diff --git a/components/engine/api/client/client.go b/components/engine/api/client/client.go index 3170881744..4cfce5f684 100644 --- a/components/engine/api/client/client.go +++ b/components/engine/api/client/client.go @@ -3,15 +3,3 @@ // Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand. // See https://docs.docker.com/installation/ for instructions on installing Docker. package client - -import "fmt" - -// An 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/components/engine/api/client/commit.go b/components/engine/api/client/commit.go index 2a14c084b8..fe4acd481b 100644 --- a/components/engine/api/client/commit.go +++ b/components/engine/api/client/commit.go @@ -6,6 +6,7 @@ import ( "net/url" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" @@ -17,7 +18,7 @@ import ( // // Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] func (cli *DockerCli) CmdCommit(args ...string) error { - cmd := cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, "Create a new image from a container's changes", true) + cmd := Cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, "Create a new image from a container's changes", true) flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit") flComment := cmd.String([]string{"m", "-message"}, "", "Commit message") flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith \")") diff --git a/components/engine/api/client/cp.go b/components/engine/api/client/cp.go index 0463a994b7..99278adfc1 100644 --- a/components/engine/api/client/cp.go +++ b/components/engine/api/client/cp.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/pkg/archive" flag "github.com/docker/docker/pkg/mflag" ) @@ -37,7 +38,7 @@ const ( // docker cp CONTAINER:PATH LOCALPATH|- // docker cp LOCALPATH|- CONTAINER:PATH func (cli *DockerCli) CmdCp(args ...string) error { - cmd := cli.Subcmd( + cmd := Cli.Subcmd( "cp", []string{"CONTAINER:PATH LOCALPATH|-", "LOCALPATH|- CONTAINER:PATH"}, strings.Join([]string{ diff --git a/components/engine/api/client/create.go b/components/engine/api/client/create.go index d9df039785..8d573c2218 100644 --- a/components/engine/api/client/create.go +++ b/components/engine/api/client/create.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/graph/tags" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/registry" @@ -137,7 +138,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc // // Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...] func (cli *DockerCli) CmdCreate(args ...string) error { - cmd := cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, "Create a new container", true) + cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, "Create a new container", true) // These are flags not stored in Config/HostConfig var ( diff --git a/components/engine/api/client/diff.go b/components/engine/api/client/diff.go index 3dfbb00b98..b955774ccd 100644 --- a/components/engine/api/client/diff.go +++ b/components/engine/api/client/diff.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/pkg/archive" flag "github.com/docker/docker/pkg/mflag" ) @@ -17,7 +18,7 @@ import ( // // Usage: docker diff CONTAINER func (cli *DockerCli) CmdDiff(args ...string) error { - cmd := cli.Subcmd("diff", []string{"CONTAINER"}, "Inspect changes on a container's filesystem", true) + cmd := Cli.Subcmd("diff", []string{"CONTAINER"}, "Inspect changes on a container's filesystem", true) cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) diff --git a/components/engine/api/client/events.go b/components/engine/api/client/events.go index d4829edfd4..c0168bdb15 100644 --- a/components/engine/api/client/events.go +++ b/components/engine/api/client/events.go @@ -4,6 +4,7 @@ import ( "net/url" "time" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers/filters" @@ -14,7 +15,7 @@ import ( // // Usage: docker events [OPTIONS] func (cli *DockerCli) CmdEvents(args ...string) error { - cmd := cli.Subcmd("events", nil, "Get real time events from the server", true) + cmd := Cli.Subcmd("events", nil, "Get real time events from the server", true) since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp") until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp") flFilter := opts.NewListOpts(nil) diff --git a/components/engine/api/client/exec.go b/components/engine/api/client/exec.go index ec4d124c01..d02c019bca 100644 --- a/components/engine/api/client/exec.go +++ b/components/engine/api/client/exec.go @@ -7,6 +7,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/runconfig" ) @@ -15,12 +16,12 @@ import ( // // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] func (cli *DockerCli) CmdExec(args ...string) error { - cmd := cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, "Run a command in a running container", true) + cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, "Run a command in a running container", true) execConfig, err := runconfig.ParseExec(cmd, args) // just in case the ParseExec does not exit if execConfig.Container == "" || err != nil { - return StatusError{StatusCode: 1} + return Cli.StatusError{StatusCode: 1} } serverResp, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil) @@ -126,7 +127,7 @@ func (cli *DockerCli) CmdExec(args ...string) error { } if status != 0 { - return StatusError{StatusCode: status} + return Cli.StatusError{StatusCode: status} } return nil diff --git a/components/engine/api/client/export.go b/components/engine/api/client/export.go index 381e555b63..78594997be 100644 --- a/components/engine/api/client/export.go +++ b/components/engine/api/client/export.go @@ -5,6 +5,7 @@ import ( "io" "os" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -14,7 +15,7 @@ import ( // // Usage: docker export [OPTIONS] CONTAINER func (cli *DockerCli) CmdExport(args ...string) error { - cmd := cli.Subcmd("export", []string{"CONTAINER"}, "Export the contents of a container's filesystem as a tar archive", true) + cmd := Cli.Subcmd("export", []string{"CONTAINER"}, "Export the contents of a container's filesystem as a tar archive", true) outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT") cmd.Require(flag.Exact, 1) diff --git a/components/engine/api/client/help.go b/components/engine/api/client/help.go deleted file mode 100644 index 8e1dc852b7..0000000000 --- a/components/engine/api/client/help.go +++ /dev/null @@ -1,34 +0,0 @@ -package client - -import ( - "fmt" - - flag "github.com/docker/docker/pkg/mflag" -) - -// 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 *DockerCli) CmdHelp(args ...string) error { - if len(args) > 1 { - method, exists := cli.getMethod(args[:2]...) - if exists { - method("--help") - return nil - } - } - if len(args) > 0 { - method, exists := cli.getMethod(args[0]) - if !exists { - return fmt.Errorf("docker: '%s' is not a docker command. See 'docker --help'.", args[0]) - } - method("--help") - return nil - } - - flag.Usage() - - return nil -} diff --git a/components/engine/api/client/history.go b/components/engine/api/client/history.go index ca66524286..925add6640 100644 --- a/components/engine/api/client/history.go +++ b/components/engine/api/client/history.go @@ -7,6 +7,7 @@ import ( "time" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringutils" @@ -17,7 +18,7 @@ import ( // // Usage: docker history [OPTIONS] IMAGE func (cli *DockerCli) CmdHistory(args ...string) error { - cmd := cli.Subcmd("history", []string{"IMAGE"}, "Show the history of an image", true) + cmd := Cli.Subcmd("history", []string{"IMAGE"}, "Show the history of an image", true) human := cmd.Bool([]string{"H", "-human"}, true, "Print sizes and dates in human readable format") quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") diff --git a/components/engine/api/client/images.go b/components/engine/api/client/images.go index 93dc35bbca..92adeed00e 100644 --- a/components/engine/api/client/images.go +++ b/components/engine/api/client/images.go @@ -8,6 +8,7 @@ import ( "time" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" @@ -21,7 +22,7 @@ import ( // // Usage: docker images [OPTIONS] [REPOSITORY] func (cli *DockerCli) CmdImages(args ...string) error { - cmd := cli.Subcmd("images", []string{"[REPOSITORY]"}, "List images", true) + cmd := Cli.Subcmd("images", []string{"[REPOSITORY]"}, "List images", true) quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") diff --git a/components/engine/api/client/import.go b/components/engine/api/client/import.go index 43edce3def..ec3d028f90 100644 --- a/components/engine/api/client/import.go +++ b/components/engine/api/client/import.go @@ -6,6 +6,7 @@ import ( "net/url" "os" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" @@ -19,7 +20,7 @@ import ( // // Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]] func (cli *DockerCli) CmdImport(args ...string) error { - cmd := cli.Subcmd("import", []string{"file|URL|- [REPOSITORY[:TAG]]"}, "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true) + cmd := Cli.Subcmd("import", []string{"file|URL|- [REPOSITORY[:TAG]]"}, "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true) flChanges := opts.NewListOpts(nil) cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image") cmd.Require(flag.Min, 1) diff --git a/components/engine/api/client/info.go b/components/engine/api/client/info.go index 985548ae70..c7b19ccb5c 100644 --- a/components/engine/api/client/info.go +++ b/components/engine/api/client/info.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/ioutils" flag "github.com/docker/docker/pkg/mflag" @@ -15,7 +16,7 @@ import ( // // Usage: docker info func (cli *DockerCli) CmdInfo(args ...string) error { - cmd := cli.Subcmd("info", nil, "Display system-wide information", true) + cmd := Cli.Subcmd("info", nil, "Display system-wide information", true) cmd.Require(flag.Exact, 0) cmd.ParseFlags(args, true) diff --git a/components/engine/api/client/inspect.go b/components/engine/api/client/inspect.go index b09646afa9..6e728bdf6b 100644 --- a/components/engine/api/client/inspect.go +++ b/components/engine/api/client/inspect.go @@ -9,14 +9,22 @@ import ( "text/template" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) +var funcMap = template.FuncMap{ + "json": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, +} + // CmdInspect displays low-level information on one or more containers or images. // // Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...] func (cli *DockerCli) CmdInspect(args ...string) error { - cmd := cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, "Return low-level information on a container or image", true) + cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, "Return low-level information on a container or image", true) tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template") inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)") cmd.Require(flag.Min, 1) @@ -29,7 +37,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { if *tmplStr != "" { if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil { - return StatusError{StatusCode: 64, + return Cli.StatusError{StatusCode: 64, Status: "Template parsing error: " + err.Error()} } } @@ -143,7 +151,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { } if status != 0 { - return StatusError{StatusCode: status} + return Cli.StatusError{StatusCode: status} } return nil } diff --git a/components/engine/api/client/kill.go b/components/engine/api/client/kill.go index 4f3d7ad0bd..63abed31f6 100644 --- a/components/engine/api/client/kill.go +++ b/components/engine/api/client/kill.go @@ -3,6 +3,7 @@ package client import ( "fmt" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -10,7 +11,7 @@ import ( // // Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdKill(args ...string) error { - cmd := cli.Subcmd("kill", []string{"CONTAINER [CONTAINER...]"}, "Kill a running container using SIGKILL or a specified signal", true) + cmd := Cli.Subcmd("kill", []string{"CONTAINER [CONTAINER...]"}, "Kill a running container using SIGKILL or a specified signal", true) signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container") cmd.Require(flag.Min, 1) diff --git a/components/engine/api/client/load.go b/components/engine/api/client/load.go index a512ffb06a..9501db4f82 100644 --- a/components/engine/api/client/load.go +++ b/components/engine/api/client/load.go @@ -4,6 +4,7 @@ import ( "io" "os" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -13,7 +14,7 @@ import ( // // Usage: docker load [OPTIONS] func (cli *DockerCli) CmdLoad(args ...string) error { - cmd := cli.Subcmd("load", nil, "Load an image from a tar archive or STDIN", true) + cmd := Cli.Subcmd("load", nil, "Load an image from a tar archive or STDIN", true) infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN") cmd.Require(flag.Exact, 0) diff --git a/components/engine/api/client/login.go b/components/engine/api/client/login.go index 145fe9c525..8c012737ff 100644 --- a/components/engine/api/client/login.go +++ b/components/engine/api/client/login.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/cliconfig" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" @@ -21,7 +22,7 @@ import ( // // Usage: docker login SERVER func (cli *DockerCli) CmdLogin(args ...string) error { - cmd := cli.Subcmd("login", []string{"[SERVER]"}, "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.INDEXSERVER+"\" is the default.", true) + cmd := Cli.Subcmd("login", []string{"[SERVER]"}, "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.INDEXSERVER+"\" is the default.", true) cmd.Require(flag.Max, 1) var username, password, email string diff --git a/components/engine/api/client/logout.go b/components/engine/api/client/logout.go index 32108d3429..7eb721ec7d 100644 --- a/components/engine/api/client/logout.go +++ b/components/engine/api/client/logout.go @@ -3,6 +3,7 @@ package client import ( "fmt" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/registry" ) @@ -13,7 +14,7 @@ import ( // // Usage: docker logout [SERVER] func (cli *DockerCli) CmdLogout(args ...string) error { - cmd := cli.Subcmd("logout", []string{"[SERVER]"}, "Log out from a Docker registry, if no server is\nspecified \""+registry.INDEXSERVER+"\" is the default.", true) + cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, "Log out from a Docker registry, if no server is\nspecified \""+registry.INDEXSERVER+"\" is the default.", true) cmd.Require(flag.Max, 1) cmd.ParseFlags(args, true) diff --git a/components/engine/api/client/logs.go b/components/engine/api/client/logs.go index 0f4be3dac6..f1d647f3af 100644 --- a/components/engine/api/client/logs.go +++ b/components/engine/api/client/logs.go @@ -7,6 +7,7 @@ import ( "time" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/timeutils" ) @@ -15,7 +16,7 @@ import ( // // docker logs [OPTIONS] CONTAINER func (cli *DockerCli) CmdLogs(args ...string) error { - cmd := cli.Subcmd("logs", []string{"CONTAINER"}, "Fetch the logs of a container", true) + cmd := Cli.Subcmd("logs", []string{"CONTAINER"}, "Fetch the logs of a container", true) follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output") since := cmd.String([]string{"-since"}, "", "Show logs since timestamp") times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps") diff --git a/components/engine/api/client/pause.go b/components/engine/api/client/pause.go index 46285755f6..94dd59d7e1 100644 --- a/components/engine/api/client/pause.go +++ b/components/engine/api/client/pause.go @@ -3,6 +3,7 @@ package client import ( "fmt" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -10,7 +11,7 @@ import ( // // Usage: docker pause CONTAINER [CONTAINER...] func (cli *DockerCli) CmdPause(args ...string) error { - cmd := cli.Subcmd("pause", []string{"CONTAINER [CONTAINER...]"}, "Pause all processes within a container", true) + cmd := Cli.Subcmd("pause", []string{"CONTAINER [CONTAINER...]"}, "Pause all processes within a container", true) cmd.Require(flag.Min, 1) cmd.ParseFlags(args, true) diff --git a/components/engine/api/client/port.go b/components/engine/api/client/port.go index 9f83aa47a1..d8bcbf6eff 100644 --- a/components/engine/api/client/port.go +++ b/components/engine/api/client/port.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/nat" ) @@ -14,7 +15,7 @@ import ( // // Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]] func (cli *DockerCli) CmdPort(args ...string) error { - cmd := cli.Subcmd("port", []string{"CONTAINER [PRIVATE_PORT[/PROTO]]"}, "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true) + cmd := Cli.Subcmd("port", []string{"CONTAINER [PRIVATE_PORT[/PROTO]]"}, "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true) cmd.Require(flag.Min, 1) cmd.ParseFlags(args, true) diff --git a/components/engine/api/client/ps.go b/components/engine/api/client/ps.go index 28b9615639..e7fb97c315 100644 --- a/components/engine/api/client/ps.go +++ b/components/engine/api/client/ps.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/api/client/ps" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers/filters" @@ -22,7 +23,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { psFilterArgs = filters.Args{} v = url.Values{} - cmd = cli.Subcmd("ps", nil, "List containers", true) + cmd = Cli.Subcmd("ps", nil, "List containers", true) quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs") size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes") all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)") diff --git a/components/engine/api/client/pull.go b/components/engine/api/client/pull.go index 8607eac192..7773b975ea 100644 --- a/components/engine/api/client/pull.go +++ b/components/engine/api/client/pull.go @@ -4,6 +4,7 @@ import ( "fmt" "net/url" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/graph/tags" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" @@ -15,7 +16,7 @@ import ( // // Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST] func (cli *DockerCli) CmdPull(args ...string) error { - cmd := cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, "Pull an image or a repository from a registry", true) + cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, "Pull an image or a repository from a registry", true) allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository") cmd.Require(flag.Exact, 1) diff --git a/components/engine/api/client/push.go b/components/engine/api/client/push.go index 2d99a51298..4a31f9ba61 100644 --- a/components/engine/api/client/push.go +++ b/components/engine/api/client/push.go @@ -4,6 +4,7 @@ import ( "fmt" "net/url" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/registry" @@ -13,7 +14,7 @@ import ( // // Usage: docker push NAME[:TAG] func (cli *DockerCli) CmdPush(args ...string) error { - cmd := cli.Subcmd("push", []string{"NAME[:TAG]"}, "Push an image or a repository to a registry", true) + cmd := Cli.Subcmd("push", []string{"NAME[:TAG]"}, "Push an image or a repository to a registry", true) cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) diff --git a/components/engine/api/client/rename.go b/components/engine/api/client/rename.go index caad597d00..ae09a46221 100644 --- a/components/engine/api/client/rename.go +++ b/components/engine/api/client/rename.go @@ -3,6 +3,7 @@ package client import ( "fmt" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -10,7 +11,7 @@ import ( // // Usage: docker rename OLD_NAME NEW_NAME func (cli *DockerCli) CmdRename(args ...string) error { - cmd := cli.Subcmd("rename", []string{"OLD_NAME NEW_NAME"}, "Rename a container", true) + cmd := Cli.Subcmd("rename", []string{"OLD_NAME NEW_NAME"}, "Rename a container", true) cmd.Require(flag.Exact, 2) cmd.ParseFlags(args, true) diff --git a/components/engine/api/client/restart.go b/components/engine/api/client/restart.go index 3dc6a650c3..88de4f758a 100644 --- a/components/engine/api/client/restart.go +++ b/components/engine/api/client/restart.go @@ -5,6 +5,7 @@ import ( "net/url" "strconv" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -12,7 +13,7 @@ import ( // // Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdRestart(args ...string) error { - cmd := cli.Subcmd("restart", []string{"CONTAINER [CONTAINER...]"}, "Restart a running container", true) + cmd := Cli.Subcmd("restart", []string{"CONTAINER [CONTAINER...]"}, "Restart a running container", true) nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container") cmd.Require(flag.Min, 1) diff --git a/components/engine/api/client/rm.go b/components/engine/api/client/rm.go index fe5d241210..5766727a94 100644 --- a/components/engine/api/client/rm.go +++ b/components/engine/api/client/rm.go @@ -5,6 +5,7 @@ import ( "net/url" "strings" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -12,7 +13,7 @@ import ( // // Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdRm(args ...string) error { - cmd := cli.Subcmd("rm", []string{"CONTAINER [CONTAINER...]"}, "Remove one or more containers", true) + cmd := Cli.Subcmd("rm", []string{"CONTAINER [CONTAINER...]"}, "Remove one or more containers", true) v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container") link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link") force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)") diff --git a/components/engine/api/client/rmi.go b/components/engine/api/client/rmi.go index 4bfbecc6e3..25d5646ebe 100644 --- a/components/engine/api/client/rmi.go +++ b/components/engine/api/client/rmi.go @@ -6,6 +6,7 @@ import ( "net/url" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -13,7 +14,7 @@ import ( // // Usage: docker rmi [OPTIONS] IMAGE [IMAGE...] func (cli *DockerCli) CmdRmi(args ...string) error { - cmd := cli.Subcmd("rmi", []string{"IMAGE [IMAGE...]"}, "Remove one or more images", true) + cmd := Cli.Subcmd("rmi", []string{"IMAGE [IMAGE...]"}, "Remove one or more images", true) force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image") noprune := cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents") cmd.Require(flag.Min, 1) diff --git a/components/engine/api/client/run.go b/components/engine/api/client/run.go index e80b9a8c47..7e28cb2219 100644 --- a/components/engine/api/client/run.go +++ b/components/engine/api/client/run.go @@ -8,6 +8,7 @@ import ( "runtime" "github.com/Sirupsen/logrus" + Cli "github.com/docker/docker/cli" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" @@ -39,7 +40,7 @@ func (cid *cidFile) Write(id string) error { // // Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] func (cli *DockerCli) CmdRun(args ...string) error { - cmd := cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, "Run a command in a new container", true) + cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, "Run a command in a new container", true) // These are flags not stored in Config/HostConfig var ( @@ -249,7 +250,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } } if status != 0 { - return StatusError{StatusCode: status} + return Cli.StatusError{StatusCode: status} } return nil } diff --git a/components/engine/api/client/save.go b/components/engine/api/client/save.go index 8101837cbf..5155319c80 100644 --- a/components/engine/api/client/save.go +++ b/components/engine/api/client/save.go @@ -6,6 +6,7 @@ import ( "net/url" "os" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -15,7 +16,7 @@ import ( // // Usage: docker save [OPTIONS] IMAGE [IMAGE...] func (cli *DockerCli) CmdSave(args ...string) error { - cmd := cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, "Save an image(s) to a tar archive (streamed to STDOUT by default)", true) + cmd := Cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, "Save an image(s) to a tar archive (streamed to STDOUT by default)", true) outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT") cmd.Require(flag.Min, 1) diff --git a/components/engine/api/client/search.go b/components/engine/api/client/search.go index 1fdff73f2f..2305d08343 100644 --- a/components/engine/api/client/search.go +++ b/components/engine/api/client/search.go @@ -8,6 +8,7 @@ import ( "strings" "text/tabwriter" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/stringutils" @@ -25,7 +26,7 @@ func (r ByStars) Less(i, j int) bool { return r[i].StarCount < r[j].StarCount } // // Usage: docker search [OPTIONS] TERM func (cli *DockerCli) CmdSearch(args ...string) error { - cmd := cli.Subcmd("search", []string{"TERM"}, "Search the Docker Hub for images", true) + cmd := Cli.Subcmd("search", []string{"TERM"}, "Search the Docker Hub for images", true) noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds") automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds") diff --git a/components/engine/api/client/start.go b/components/engine/api/client/start.go index e78b45d58c..e039df022d 100644 --- a/components/engine/api/client/start.go +++ b/components/engine/api/client/start.go @@ -9,6 +9,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" @@ -44,7 +45,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { // // Usage: docker start [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdStart(args ...string) error { - cmd := cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, "Start one or more stopped containers", true) + cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, "Start one or more stopped containers", true) attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals") openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN") cmd.Require(flag.Min, 1) @@ -162,7 +163,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { return err } if status != 0 { - return StatusError{StatusCode: status} + return Cli.StatusError{StatusCode: status} } } return nil diff --git a/components/engine/api/client/stats.go b/components/engine/api/client/stats.go index a1fa37f51d..1feb1e11b7 100644 --- a/components/engine/api/client/stats.go +++ b/components/engine/api/client/stats.go @@ -12,6 +12,7 @@ import ( "time" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/units" ) @@ -124,7 +125,7 @@ func (s *containerStats) Display(w io.Writer) error { // // Usage: docker stats CONTAINER [CONTAINER...] func (cli *DockerCli) CmdStats(args ...string) error { - cmd := cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, "Display a live stream of one or more containers' resource usage statistics", true) + cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, "Display a live stream of one or more containers' resource usage statistics", true) noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result") cmd.Require(flag.Min, 1) diff --git a/components/engine/api/client/stop.go b/components/engine/api/client/stop.go index ee9ce3bffe..b7348a7bbb 100644 --- a/components/engine/api/client/stop.go +++ b/components/engine/api/client/stop.go @@ -5,6 +5,7 @@ import ( "net/url" "strconv" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -14,7 +15,7 @@ import ( // // Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdStop(args ...string) error { - cmd := cli.Subcmd("stop", []string{"CONTAINER [CONTAINER...]"}, "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true) + cmd := Cli.Subcmd("stop", []string{"CONTAINER [CONTAINER...]"}, "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true) nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it") cmd.Require(flag.Min, 1) diff --git a/components/engine/api/client/tag.go b/components/engine/api/client/tag.go index 287d3e7cb6..454c7ec5b4 100644 --- a/components/engine/api/client/tag.go +++ b/components/engine/api/client/tag.go @@ -3,6 +3,7 @@ package client import ( "net/url" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/registry" @@ -12,7 +13,7 @@ import ( // // Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG] func (cli *DockerCli) CmdTag(args ...string) error { - cmd := cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, "Tag an image into a repository", true) + cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, "Tag an image into a repository", true) force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force") cmd.Require(flag.Exact, 2) diff --git a/components/engine/api/client/top.go b/components/engine/api/client/top.go index ac69a34c75..c9934fe08d 100644 --- a/components/engine/api/client/top.go +++ b/components/engine/api/client/top.go @@ -8,6 +8,7 @@ import ( "text/tabwriter" "github.com/docker/docker/api/types" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -15,7 +16,7 @@ import ( // // Usage: docker top CONTAINER func (cli *DockerCli) CmdTop(args ...string) error { - cmd := cli.Subcmd("top", []string{"CONTAINER [ps OPTIONS]"}, "Display the running processes of a container", true) + cmd := Cli.Subcmd("top", []string{"CONTAINER [ps OPTIONS]"}, "Display the running processes of a container", true) cmd.Require(flag.Min, 1) cmd.ParseFlags(args, true) diff --git a/components/engine/api/client/unpause.go b/components/engine/api/client/unpause.go index 0c9dc882db..cd1e676673 100644 --- a/components/engine/api/client/unpause.go +++ b/components/engine/api/client/unpause.go @@ -3,6 +3,7 @@ package client import ( "fmt" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -10,7 +11,7 @@ import ( // // Usage: docker unpause CONTAINER [CONTAINER...] func (cli *DockerCli) CmdUnpause(args ...string) error { - cmd := cli.Subcmd("unpause", []string{"CONTAINER [CONTAINER...]"}, "Unpause all processes within a container", true) + cmd := Cli.Subcmd("unpause", []string{"CONTAINER [CONTAINER...]"}, "Unpause all processes within a container", true) cmd.Require(flag.Min, 1) cmd.ParseFlags(args, true) diff --git a/components/engine/api/client/version.go b/components/engine/api/client/version.go index c741baa134..4c42a32de8 100644 --- a/components/engine/api/client/version.go +++ b/components/engine/api/client/version.go @@ -8,6 +8,7 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/utils" ) @@ -42,7 +43,7 @@ type VersionData struct { // // Usage: docker version func (cli *DockerCli) CmdVersion(args ...string) (err error) { - cmd := cli.Subcmd("version", nil, "Show the Docker version information.", true) + cmd := Cli.Subcmd("version", nil, "Show the Docker version information.", true) tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template") cmd.Require(flag.Exact, 0) @@ -53,7 +54,7 @@ func (cli *DockerCli) CmdVersion(args ...string) (err error) { var tmpl *template.Template if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil { - return StatusError{StatusCode: 64, + return Cli.StatusError{StatusCode: 64, Status: "Template parsing error: " + err.Error()} } @@ -85,7 +86,7 @@ func (cli *DockerCli) CmdVersion(args ...string) (err error) { defer serverResp.body.Close() if err = json.NewDecoder(serverResp.body).Decode(&vd.Server); err != nil { - return StatusError{StatusCode: 1, + return Cli.StatusError{StatusCode: 1, Status: "Error reading remote version: " + err.Error()} } diff --git a/components/engine/api/client/wait.go b/components/engine/api/client/wait.go index 068f0f1262..829a320cfe 100644 --- a/components/engine/api/client/wait.go +++ b/components/engine/api/client/wait.go @@ -3,6 +3,7 @@ package client import ( "fmt" + Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" ) @@ -12,7 +13,7 @@ import ( // // Usage: docker wait CONTAINER [CONTAINER...] func (cli *DockerCli) CmdWait(args ...string) error { - cmd := cli.Subcmd("wait", []string{"CONTAINER [CONTAINER...]"}, "Block until a container stops, then print its exit code.", true) + cmd := Cli.Subcmd("wait", []string{"CONTAINER [CONTAINER...]"}, "Block until a container stops, then print its exit code.", true) cmd.Require(flag.Min, 1) cmd.ParseFlags(args, true) diff --git a/components/engine/cli/cli.go b/components/engine/cli/cli.go new file mode 100644 index 0000000000..8e559fc3fd --- /dev/null +++ b/components/engine/cli/cli.go @@ -0,0 +1,200 @@ +package cli + +import ( + "errors" + "fmt" + "io" + "os" + "reflect" + "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{} + +// 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 +} + +// initErr is an error returned upon initialization of a handler implementing Initializer. +type initErr struct{ error } + +func (err initErr) Error() string { + return err.Error() +} + +func (cli *Cli) command(args ...string) (func(...string) error, error) { + for _, c := range cli.handlers { + if c == nil { + continue + } + camelArgs := make([]string, len(args)) + for i, s := range args { + if len(s) == 0 { + return nil, errors.New("empty command") + } + camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:]) + } + methodName := "Cmd" + strings.Join(camelArgs, "") + method := reflect.ValueOf(c).MethodByName(methodName) + if method.IsValid() { + if c, ok := c.(Initializer); ok { + if err := c.Initialize(); err != nil { + return nil, initErr{err} + } + } + return method.Interface().(func(...string) error), nil + } + } + return nil, errors.New("command not found") +} + +// Run executes the specified command. +func (cli *Cli) Run(args ...string) error { + if len(args) > 1 { + command, err := cli.command(args[:2]...) + switch err := err.(type) { + case nil: + return command(args[2:]...) + case initErr: + return err.error + } + } + if len(args) > 0 { + command, err := cli.command(args[0]) + switch err := err.(type) { + case nil: + return command(args[1:]...) + case initErr: + return err.error + } + cli.noSuchCommand(args[0]) + } + 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) +} + +// 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]...) + switch err := err.(type) { + case nil: + command("--help") + return nil + case initErr: + return err.error + } + } + if len(args) > 0 { + command, err := cli.command(args[0]) + switch err := err.(type) { + case nil: + command("--help") + return nil + case initErr: + return err.error + } + cli.noSuchCommand(args[0]) + } + + 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() { + options := "" + if flags.FlagCountUndeprecated() > 0 { + options = " [OPTIONS]" + } + + 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%s", lead, name, options, synopsis) + } + + fmt.Fprintf(flags.Out(), "\n\n%s\n", description) + } + + return flags +} + +// An 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/components/engine/cli/client.go b/components/engine/cli/client.go new file mode 100644 index 0000000000..6a82eb52a5 --- /dev/null +++ b/components/engine/cli/client.go @@ -0,0 +1,12 @@ +package cli + +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() + + ConfigDir string +} diff --git a/components/engine/cli/common.go b/components/engine/cli/common.go new file mode 100644 index 0000000000..85a02ac43b --- /dev/null +++ b/components/engine/cli/common.go @@ -0,0 +1,20 @@ +package cli + +import ( + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/tlsconfig" +) + +// CommonFlags represents flags that are common to both the client and the daemon. +type CommonFlags struct { + FlagSet *flag.FlagSet + PostParse func() + + Debug bool + Hosts []string + LogLevel string + TLS bool + TLSVerify bool + TLSOptions *tlsconfig.Options + TrustKey string +} diff --git a/components/engine/daemon/config.go b/components/engine/daemon/config.go index 23d9ebcf70..874ea4912a 100644 --- a/components/engine/daemon/config.go +++ b/components/engine/daemon/config.go @@ -41,22 +41,22 @@ type CommonConfig struct { // the current process. // Subsequent calls to `flag.Parse` will populate config with values parsed // from the command-line. -func (config *Config) InstallCommonFlags() { - opts.ListVar(&config.GraphOptions, []string{"-storage-opt"}, "Set storage driver options") - opts.ListVar(&config.ExecOptions, []string{"-exec-opt"}, "Set exec driver options") - flag.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, defaultPidFile, "Path to use for daemon PID file") - flag.StringVar(&config.Root, []string{"g", "-graph"}, defaultGraph, "Root of the Docker runtime") - flag.StringVar(&config.ExecRoot, []string{"-exec-root"}, "/var/run/docker", "Root of the Docker execdriver") - flag.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run") - flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Storage driver to use") - flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, defaultExec, "Exec driver to use") - flag.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, "Set the containers network MTU") - flag.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, "Enable CORS headers in the remote API, this is deprecated by --api-cors-header") - flag.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", "Set CORS headers in the remote API") +func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) string) { + cmd.Var(opts.NewListOptsRef(&config.GraphOptions, nil), []string{"-storage-opt"}, usageFn("Set storage driver options")) + cmd.Var(opts.NewListOptsRef(&config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Set exec driver 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.StringVar(&config.ExecRoot, []string{"-exec-root"}, "/var/run/docker", usageFn("Root of the Docker execdriver")) + 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.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, defaultExec, usageFn("Exec driver to use")) + cmd.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, usageFn("Set the containers network MTU")) + 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.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API")) // FIXME: why the inconsistency between "hosts" and "sockets"? - opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use") - opts.DNSSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use") - opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon") - flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Default driver for container logs") - opts.LogOptsVar(config.LogConfig.Config, []string{"-log-opt"}, "Set log driver options") + cmd.Var(opts.NewListOptsRef(&config.Dns, opts.ValidateIPAddress), []string{"#dns", "-dns"}, usageFn("DNS server to use")) + cmd.Var(opts.NewListOptsRef(&config.DnsSearch, opts.ValidateDNSSearch), []string{"-dns-search"}, usageFn("DNS search domains to use")) + cmd.Var(opts.NewListOptsRef(&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.NewMapOpts(config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options")) } diff --git a/components/engine/daemon/config_experimental.go b/components/engine/daemon/config_experimental.go index 55704d00dc..b3a1d3fbc7 100644 --- a/components/engine/daemon/config_experimental.go +++ b/components/engine/daemon/config_experimental.go @@ -4,7 +4,7 @@ package daemon import flag "github.com/docker/docker/pkg/mflag" -func (config *Config) attachExperimentalFlags() { - flag.StringVar(&config.DefaultNetwork, []string{"-default-network"}, "", "Set default network") - flag.StringVar(&config.NetworkKVStore, []string{"-kv-store"}, "", "Set KV Store configuration") +func (config *Config) attachExperimentalFlags(cmd *flag.FlagSet, usageFn func(string) string) { + cmd.StringVar(&config.DefaultNetwork, []string{"-default-network"}, "", usageFn("Set default network")) + cmd.StringVar(&config.NetworkKVStore, []string{"-kv-store"}, "", usageFn("Set KV Store configuration")) } diff --git a/components/engine/daemon/config_linux.go b/components/engine/daemon/config_linux.go index e1095e078c..783637ea6d 100644 --- a/components/engine/daemon/config_linux.go +++ b/components/engine/daemon/config_linux.go @@ -49,27 +49,28 @@ type bridgeConfig struct { // the current process. // Subsequent calls to `flag.Parse` will populate config with values parsed // from the command-line. -func (config *Config) InstallFlags() { +func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) string) { // First handle install flags which are consistent cross-platform - config.InstallCommonFlags() + config.InstallCommonFlags(cmd, usageFn) // Then platform-specific install flags - flag.BoolVar(&config.EnableSelinuxSupport, []string{"-selinux-enabled"}, false, "Enable selinux support") - flag.StringVar(&config.SocketGroup, []string{"G", "-group"}, "docker", "Group for the unix socket") + 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]*ulimit.Ulimit) - opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers") - flag.BoolVar(&config.Bridge.EnableIPTables, []string{"#iptables", "-iptables"}, true, "Enable addition of iptables rules") - flag.BoolVar(&config.Bridge.EnableIPForward, []string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward") - flag.BoolVar(&config.Bridge.EnableIPMasq, []string{"-ip-masq"}, true, "Enable IP masquerading") - flag.BoolVar(&config.Bridge.EnableIPv6, []string{"-ipv6"}, false, "Enable IPv6 networking") - flag.StringVar(&config.Bridge.IP, []string{"#bip", "-bip"}, "", "Specify network bridge IP") - flag.StringVar(&config.Bridge.Iface, []string{"b", "-bridge"}, "", "Attach containers to a network bridge") - flag.StringVar(&config.Bridge.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs") - flag.StringVar(&config.Bridge.FixedCIDRv6, []string{"-fixed-cidr-v6"}, "", "IPv6 subnet for fixed IPs") - opts.IPVar(&config.Bridge.DefaultGatewayIPv4, []string{"-default-gateway"}, "", "Container default gateway IPv4 address") - opts.IPVar(&config.Bridge.DefaultGatewayIPv6, []string{"-default-gateway-v6"}, "", "Container default gateway IPv6 address") - flag.BoolVar(&config.Bridge.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication") - opts.IPVar(&config.Bridge.DefaultIP, []string{"#ip", "-ip"}, "0.0.0.0", "Default IP when binding container ports") - flag.BoolVar(&config.Bridge.EnableUserlandProxy, []string{"-userland-proxy"}, true, "Use userland proxy for loopback traffic") - config.attachExperimentalFlags() + cmd.Var(opts.NewUlimitOpt(&config.Ulimits), []string{"-default-ulimit"}, usageFn("Set default ulimits for containers")) + cmd.BoolVar(&config.Bridge.EnableIPTables, []string{"#iptables", "-iptables"}, true, usageFn("Enable addition of iptables rules")) + cmd.BoolVar(&config.Bridge.EnableIPForward, []string{"#ip-forward", "-ip-forward"}, true, usageFn("Enable net.ipv4.ip_forward")) + cmd.BoolVar(&config.Bridge.EnableIPMasq, []string{"-ip-masq"}, true, usageFn("Enable IP masquerading")) + cmd.BoolVar(&config.Bridge.EnableIPv6, []string{"-ipv6"}, false, usageFn("Enable IPv6 networking")) + cmd.StringVar(&config.Bridge.IP, []string{"#bip", "-bip"}, "", usageFn("Specify network bridge IP")) + cmd.StringVar(&config.Bridge.Iface, []string{"b", "-bridge"}, "", usageFn("Attach containers to a network bridge")) + cmd.StringVar(&config.Bridge.FixedCIDR, []string{"-fixed-cidr"}, "", usageFn("IPv4 subnet for fixed IPs")) + cmd.StringVar(&config.Bridge.FixedCIDRv6, []string{"-fixed-cidr-v6"}, "", usageFn("IPv6 subnet for fixed IPs")) + cmd.Var(opts.NewIpOpt(&config.Bridge.DefaultGatewayIPv4, ""), []string{"-default-gateway"}, usageFn("Container default gateway IPv4 address")) + cmd.Var(opts.NewIpOpt(&config.Bridge.DefaultGatewayIPv6, ""), []string{"-default-gateway-v6"}, usageFn("Container default gateway IPv6 address")) + cmd.BoolVar(&config.Bridge.InterContainerCommunication, []string{"#icc", "-icc"}, true, usageFn("Enable inter-container communication")) + cmd.Var(opts.NewIpOpt(&config.Bridge.DefaultIP, "0.0.0.0"), []string{"#ip", "-ip"}, usageFn("Default IP when binding container ports")) + cmd.BoolVar(&config.Bridge.EnableUserlandProxy, []string{"-userland-proxy"}, true, usageFn("Use userland proxy for loopback traffic")) + + config.attachExperimentalFlags(cmd, usageFn) } diff --git a/components/engine/daemon/config_stub.go b/components/engine/daemon/config_stub.go index 32d551408c..796e6b6e4e 100644 --- a/components/engine/daemon/config_stub.go +++ b/components/engine/daemon/config_stub.go @@ -2,5 +2,7 @@ package daemon -func (config *Config) attachExperimentalFlags() { +import flag "github.com/docker/docker/pkg/mflag" + +func (config *Config) attachExperimentalFlags(cmd *flag.FlagSet, usageFn func(string) string) { } diff --git a/components/engine/daemon/config_windows.go b/components/engine/daemon/config_windows.go index 26bb8a7775..d693e285de 100644 --- a/components/engine/daemon/config_windows.go +++ b/components/engine/daemon/config_windows.go @@ -32,9 +32,9 @@ type Config struct { // the current process. // Subsequent calls to `flag.Parse` will populate config with values parsed // from the command-line. -func (config *Config) InstallFlags() { +func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) string) { // First handle install flags which are consistent cross-platform - config.InstallCommonFlags() + config.InstallCommonFlags(cmd, usageFn) // Then platform-specific install flags. flag.StringVar(&config.Bridge.VirtualSwitchName, []string{"b", "-bridge"}, "", "Attach containers to a virtual switch") diff --git a/components/engine/docker/client.go b/components/engine/docker/client.go index 0e05f7b8ce..8395805b10 100644 --- a/components/engine/docker/client.go +++ b/components/engine/docker/client.go @@ -1,11 +1,28 @@ -// +build !daemon - package main import ( - "log" // see gh#8745, client needs to use go log pkg + "path/filepath" + + "github.com/docker/docker/cli" + "github.com/docker/docker/cliconfig" + flag "github.com/docker/docker/pkg/mflag" ) -func mainDaemon() { - log.Fatal("This is a client-only binary - running the Docker daemon is not supported.") +var clientFlags = &cli.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags} + +func init() { + client := clientFlags.FlagSet + client.StringVar(&clientFlags.ConfigDir, []string{"-config"}, cliconfig.ConfigDir(), "Location of client config files") + + clientFlags.PostParse = func() { + clientFlags.Common.PostParse() + + if clientFlags.ConfigDir != "" { + cliconfig.SetConfigDir(clientFlags.ConfigDir) + } + + if clientFlags.Common.TrustKey == "" { + clientFlags.Common.TrustKey = filepath.Join(cliconfig.ConfigDir(), defaultTrustKeyFile) + } + } } diff --git a/components/engine/docker/common.go b/components/engine/docker/common.go new file mode 100644 index 0000000000..351257e779 --- /dev/null +++ b/components/engine/docker/common.go @@ -0,0 +1,101 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/cli" + "github.com/docker/docker/cliconfig" + "github.com/docker/docker/opts" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/tlsconfig" +) + +const ( + defaultTrustKeyFile = "key.json" + defaultCaFile = "ca.pem" + defaultKeyFile = "key.pem" + defaultCertFile = "cert.pem" +) + +var ( + daemonFlags *flag.FlagSet + commonFlags = &cli.CommonFlags{FlagSet: new(flag.FlagSet)} + + dockerCertPath = os.Getenv("DOCKER_CERT_PATH") + dockerTLSVerify = os.Getenv("DOCKER_TLS_VERIFY") != "" +) + +func init() { + if dockerCertPath == "" { + dockerCertPath = cliconfig.ConfigDir() + } + + commonFlags.PostParse = postParseCommon + + cmd := commonFlags.FlagSet + + 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") + + // 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.NewListOptsRef(&commonFlags.Hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to") +} + +func postParseCommon() { + cmd := commonFlags.FlagSet + + if commonFlags.LogLevel != "" { + lvl, err := logrus.ParseLevel(commonFlags.LogLevel) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to parse logging level: %s\n", commonFlags.LogLevel) + os.Exit(1) + } + logrus.SetLevel(lvl) + } else { + logrus.SetLevel(logrus.InfoLevel) + } + + if commonFlags.Debug { + os.Setenv("DEBUG", "1") + logrus.SetLevel(logrus.DebugLevel) + } + + // 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("-tlsverify") || commonFlags.TLSVerify { + commonFlags.TLS = true + } + + if !commonFlags.TLS { + commonFlags.TLSOptions = nil + } else { + tlsOptions := commonFlags.TLSOptions + tlsOptions.InsecureSkipVerify = !commonFlags.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 _, err := os.Stat(tlsOptions.CertFile); os.IsNotExist(err) { + tlsOptions.CertFile = "" + } + } + if !cmd.IsSet("-tlskey") { + if _, err := os.Stat(tlsOptions.KeyFile); os.IsNotExist(err) { + tlsOptions.KeyFile = "" + } + } + } +} diff --git a/components/engine/docker/daemon.go b/components/engine/docker/daemon.go index e7e0dfe3f9..e11b98d8fb 100644 --- a/components/engine/docker/daemon.go +++ b/components/engine/docker/daemon.go @@ -8,14 +8,18 @@ import ( "io" "os" "path/filepath" + "runtime" + "strings" "time" "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/cli" "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/logger" + "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/pidfile" "github.com/docker/docker/pkg/signal" @@ -26,17 +30,63 @@ import ( "github.com/docker/docker/utils" ) +const daemonUsage = " docker daemon [ --help | ... ]\n" + var ( - daemonCfg = &daemon.Config{} - registryCfg = ®istry.Options{} + flDaemon = flag.Bool([]string{"#d", "#-daemon"}, false, "Enable daemon mode (deprecated; use docker daemon)") + daemonCli cli.Handler = NewDaemonCli() ) -func init() { - if daemonCfg.LogConfig.Config == nil { - daemonCfg.LogConfig.Config = make(map[string]string) +// TODO: remove once `-d` is retired +func handleGlobalDaemonFlag() { + // This block makes sure that if the deprecated daemon flag `--daemon` is absent, + // then all daemon-specific flags are absent as well. + if !*flDaemon && daemonFlags != nil { + flag.CommandLine.Visit(func(fl *flag.Flag) { + for _, name := range fl.Names { + name := strings.TrimPrefix(name, "#") + if daemonFlags.Lookup(name) != nil { + // daemon flag was NOT specified, but daemon-specific flags were + // so let's error out + fmt.Fprintf(os.Stderr, "docker: the daemon flag '-%s' must follow the 'docker daemon' command.\n", name) + os.Exit(1) + } + } + }) + } + + if *flDaemon { + if *flHelp { + // We do not show the help output here, instead, we tell the user about the new daemon command, + // because the help output is so long they would not see the warning anyway. + fmt.Fprintln(os.Stderr, "Please use 'docker daemon --help' instead.") + os.Exit(0) + } + daemonCli.(*DaemonCli).CmdDaemon(flag.Args()...) + os.Exit(0) + } +} + +func presentInHelp(usage string) string { return usage } +func absentFromHelp(string) string { return "" } + +// NewDaemonCli returns a pre-configured daemon CLI +func NewDaemonCli() *DaemonCli { + daemonFlags = cli.Subcmd("daemon", nil, "Enable daemon mode", true) + + // TODO(tiborvass): remove InstallFlags? + daemonConfig := new(daemon.Config) + daemonConfig.InstallFlags(daemonFlags, presentInHelp) + daemonConfig.InstallFlags(flag.CommandLine, absentFromHelp) + registryOptions := new(registry.Options) + registryOptions.InstallFlags(daemonFlags, presentInHelp) + registryOptions.InstallFlags(flag.CommandLine, absentFromHelp) + daemonFlags.Require(flag.Exact, 0) + + return &DaemonCli{ + Config: daemonConfig, + registryOptions: registryOptions, } - daemonCfg.InstallFlags() - registryCfg.InstallFlags() } func migrateKey() (err error) { @@ -79,14 +129,56 @@ func migrateKey() (err error) { return nil } -func mainDaemon() { - if utils.ExperimentalBuild() { - logrus.Warn("Running experimental build") +// DaemonCli represents the daemon CLI. +type DaemonCli struct { + *daemon.Config + registryOptions *registry.Options +} + +func getGlobalFlag() (globalFlag *flag.Flag) { + defer func() { + if x := recover(); x != nil { + switch f := x.(type) { + case *flag.Flag: + globalFlag = f + default: + panic(x) + } + } + }() + visitor := func(f *flag.Flag) { panic(f) } + commonFlags.FlagSet.Visit(visitor) + clientFlags.FlagSet.Visit(visitor) + return +} + +// CmdDaemon is the daemon command, called the raw arguments after `docker daemon`. +func (cli *DaemonCli) CmdDaemon(args ...string) error { + if *flDaemon { + // allow legacy forms `docker -D -d` and `docker -d -D` + logrus.Warn("please use 'docker daemon' instead.") + } else if !commonFlags.FlagSet.IsEmpty() || !clientFlags.FlagSet.IsEmpty() { + // deny `docker -D daemon` + illegalFlag := getGlobalFlag() + fmt.Fprintf(os.Stderr, "invalid flag '-%s'.\nSee 'docker daemon --help'.\n", illegalFlag.Names[0]) + os.Exit(1) + } else { + // allow new form `docker daemon -D` + flag.Merge(daemonFlags, commonFlags.FlagSet) } - if flag.NArg() != 0 { - flag.Usage() - return + daemonFlags.ParseFlags(args, true) + commonFlags.PostParse() + + if len(commonFlags.Hosts) == 0 { + commonFlags.Hosts = []string{opts.DefaultHost} + } + if commonFlags.TrustKey == "" { + commonFlags.TrustKey = filepath.Join(getDaemonConfDir(), defaultTrustKeyFile) + } + + if utils.ExperimentalBuild() { + logrus.Warn("Running experimental build") } logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: timeutils.RFC3339NanoFixed}) @@ -95,15 +187,15 @@ func mainDaemon() { logrus.Fatalf("Failed to set umask: %v", err) } - if len(daemonCfg.LogConfig.Config) > 0 { - if err := logger.ValidateLogOpts(daemonCfg.LogConfig.Type, daemonCfg.LogConfig.Config); err != nil { + if len(cli.LogConfig.Config) > 0 { + if err := logger.ValidateLogOpts(cli.LogConfig.Type, cli.LogConfig.Config); err != nil { logrus.Fatalf("Failed to set log opts: %v", err) } } var pfile *pidfile.PidFile - if daemonCfg.Pidfile != "" { - pf, err := pidfile.New(daemonCfg.Pidfile) + if cli.Pidfile != "" { + pf, err := pidfile.New(cli.Pidfile) if err != nil { logrus.Fatalf("Error starting daemon: %v", err) } @@ -115,21 +207,26 @@ func mainDaemon() { }() } + if cli.LogConfig.Config == nil { + cli.LogConfig.Config = make(map[string]string) + } + serverConfig := &apiserver.ServerConfig{ Logging: true, - EnableCors: daemonCfg.EnableCors, - CorsHeaders: daemonCfg.CorsHeaders, + EnableCors: cli.EnableCors, + CorsHeaders: cli.CorsHeaders, Version: dockerversion.VERSION, } - serverConfig = setPlatformServerConfig(serverConfig, daemonCfg) + serverConfig = setPlatformServerConfig(serverConfig, cli.Config) - if *flTLS { - if *flTLSVerify { - tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert + if commonFlags.TLSOptions != nil { + if !commonFlags.TLSOptions.InsecureSkipVerify { + // server requires and verifies client's certificate + commonFlags.TLSOptions.ClientAuth = tls.RequireAndVerifyClientCert } - tlsConfig, err := tlsconfig.Server(tlsOptions) + tlsConfig, err := tlsconfig.Server(*commonFlags.TLSOptions) if err != nil { - logrus.Fatal(err) + logrus.Fatalf("foobar: %v", err) } serverConfig.TLSConfig = tlsConfig } @@ -141,7 +238,7 @@ func mainDaemon() { // daemon doesn't exit serveAPIWait := make(chan error) go func() { - if err := api.ServeApi(flHosts); err != nil { + if err := api.ServeApi(commonFlags.Hosts); err != nil { logrus.Errorf("ServeAPI error: %v", err) serveAPIWait <- err return @@ -152,10 +249,10 @@ func mainDaemon() { if err := migrateKey(); err != nil { logrus.Fatal(err) } - daemonCfg.TrustKeyPath = *flTrustKey + cli.TrustKeyPath = commonFlags.TrustKey - registryService := registry.NewService(registryCfg) - d, err := daemon.NewDaemon(daemonCfg, registryService) + registryService := registry.NewService(cli.registryOptions) + d, err := daemon.NewDaemon(cli.Config, registryService) if err != nil { if pfile != nil { if err := pfile.Remove(); err != nil { @@ -201,6 +298,7 @@ func mainDaemon() { } logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI) } + return nil } // shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case @@ -219,3 +317,11 @@ func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) { logrus.Error("Force shutdown daemon") } } + +func getDaemonConfDir() string { + // TODO: update for Windows daemon + if runtime.GOOS == "windows" { + return cliconfig.ConfigDir() + } + return "/etc/docker" +} diff --git a/components/engine/docker/daemon_none.go b/components/engine/docker/daemon_none.go new file mode 100644 index 0000000000..c829cc1ca2 --- /dev/null +++ b/components/engine/docker/daemon_none.go @@ -0,0 +1,12 @@ +// +build !daemon + +package main + +import "github.com/docker/docker/cli" + +const daemonUsage = "" + +var daemonCli cli.Handler + +// TODO: remove once `-d` is retired +func handleGlobalDaemonFlag() {} diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index b589f94440..179b56f633 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -1,31 +1,20 @@ package main import ( - "crypto/tls" "fmt" "os" - "runtime" - "strings" + "sort" "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/cliconfig" - "github.com/docker/docker/opts" + "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/term" - "github.com/docker/docker/pkg/tlsconfig" "github.com/docker/docker/utils" ) -const ( - defaultTrustKeyFile = "key.json" - defaultCaFile = "ca.pem" - defaultKeyFile = "key.pem" - defaultCertFile = "cert.pem" -) - func main() { if reexec.Init() { return @@ -34,116 +23,58 @@ func main() { // Set terminal emulation based on platform as required. stdin, stdout, stderr := term.StdStreams() - initLogging(stderr) + logrus.SetOutput(stderr) + + flag.Merge(flag.CommandLine, clientFlags.FlagSet, commonFlags.FlagSet) + + flag.Usage = func() { + fmt.Fprint(os.Stdout, "Usage: docker [OPTIONS] COMMAND [arg...]\n"+daemonUsage+" docker [ -h | --help | -v | --version ]\n\n") + fmt.Fprint(os.Stdout, "A self-sufficient runtime for containers.\n\nOptions:\n") + + flag.CommandLine.SetOutput(os.Stdout) + flag.PrintDefaults() + + help := "\nCommands:\n" + + // TODO(tiborvass): no need to sort if we ensure dockerCommands is sorted + sort.Sort(byName(dockerCommands)) + + for _, cmd := range 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(os.Stdout, "%s\n", help) + } flag.Parse() - // FIXME: validate daemon flags here if *flVersion { showVersion() return } - if *flConfigDir != "" { - cliconfig.SetConfigDir(*flConfigDir) - } + clientCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags) + // TODO: remove once `-d` is retired + handleGlobalDaemonFlag() - if *flLogLevel != "" { - lvl, err := logrus.ParseLevel(*flLogLevel) - if err != nil { - fmt.Fprintf(os.Stderr, "Unable to parse logging level: %s\n", *flLogLevel) - os.Exit(1) - } - setLogLevel(lvl) - } else { - setLogLevel(logrus.InfoLevel) - } - - if *flDebug { - os.Setenv("DEBUG", "1") - setLogLevel(logrus.DebugLevel) - } - - if len(flHosts) == 0 { - defaultHost := os.Getenv("DOCKER_HOST") - if defaultHost == "" || *flDaemon { - if runtime.GOOS != "windows" { - // If we do not have a host, default to unix socket - defaultHost = fmt.Sprintf("unix://%s", opts.DefaultUnixSocket) - } else { - // If we do not have a host, default to TCP socket on Windows - defaultHost = fmt.Sprintf("tcp://%s:%d", opts.DefaultHTTPHost, opts.DefaultHTTPPort) - } - } - defaultHost, err := opts.ValidateHost(defaultHost) - if err != nil { - if *flDaemon { - logrus.Fatal(err) - } else { - fmt.Fprint(os.Stderr, err) - } - os.Exit(1) - } - flHosts = append(flHosts, defaultHost) - } - - setDefaultConfFlag(flTrustKey, defaultTrustKeyFile) - - // Regardless of whether the user sets it to true or false, if they - // specify --tlsverify at all then we need to turn on tls - // *flTlsVerify can be true even if not set due to DOCKER_TLS_VERIFY env var, so we need to check that here as well - if flag.IsSet("-tlsverify") || *flTLSVerify { - *flTLS = true - } - - if *flDaemon { - if *flHelp { - flag.Usage() - return - } - mainDaemon() + if *flHelp { + // if global flag --help is present, regardless of what other options and commands there are, + // just print the usage. + flag.Usage() return } - // From here on, we assume we're a client, not a server. - - if len(flHosts) > 1 { - fmt.Fprintf(os.Stderr, "Please specify only one -H") - os.Exit(0) - } - protoAddrParts := strings.SplitN(flHosts[0], "://", 2) - - var tlsConfig *tls.Config - if *flTLS { - tlsOptions.InsecureSkipVerify = !*flTLSVerify - if !flag.IsSet("-tlscert") { - if _, err := os.Stat(tlsOptions.CertFile); os.IsNotExist(err) { - tlsOptions.CertFile = "" - } - } - if !flag.IsSet("-tlskey") { - if _, err := os.Stat(tlsOptions.KeyFile); os.IsNotExist(err) { - tlsOptions.KeyFile = "" - } - } - var err error - tlsConfig, err = tlsconfig.Client(tlsOptions) - if err != nil { - fmt.Fprintln(stderr, err) - os.Exit(1) - } - } - cli := client.NewDockerCli(stdin, stdout, stderr, *flTrustKey, protoAddrParts[0], protoAddrParts[1], tlsConfig) - - if err := cli.Cmd(flag.Args()...); err != nil { - if sterr, ok := err.(client.StatusError); ok { + c := cli.New(clientCli, daemonCli) + if err := c.Run(flag.Args()...); err != nil { + if sterr, ok := err.(cli.StatusError); ok { if sterr.Status != "" { - fmt.Fprintln(cli.Err(), sterr.Status) + fmt.Fprintln(os.Stderr, sterr.Status) os.Exit(1) } os.Exit(sterr.StatusCode) } - fmt.Fprintln(cli.Err(), err) + fmt.Fprintln(os.Stderr, err) os.Exit(1) } } diff --git a/components/engine/docker/flags.go b/components/engine/docker/flags.go index 9243b46f4d..34567bb332 100644 --- a/components/engine/docker/flags.go +++ b/components/engine/docker/flags.go @@ -1,16 +1,10 @@ package main -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "sort" +import flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/cliconfig" - "github.com/docker/docker/opts" - flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/pkg/tlsconfig" +var ( + flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") + flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") ) type command struct { @@ -24,118 +18,46 @@ 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 } -var ( - dockerCertPath = os.Getenv("DOCKER_CERT_PATH") - dockerTlSVerify = os.Getenv("DOCKER_TLS_VERIFY") != "" - - dockerCommands = []command{ - {"attach", "Attach to a running container"}, - {"build", "Build an image from a Dockerfile"}, - {"commit", "Create a new image from a container's changes"}, - {"cp", "Copy files/folders from a container to a HOSTDIR or to STDOUT"}, - {"create", "Create a new container"}, - {"diff", "Inspect changes on a container's filesystem"}, - {"events", "Get real time events from the server"}, - {"exec", "Run a command in a running container"}, - {"export", "Export a container's filesystem as a tar archive"}, - {"history", "Show the history of an image"}, - {"images", "List images"}, - {"import", "Import the contents from a tarball to create a filesystem image"}, - {"info", "Display system-wide information"}, - {"inspect", "Return low-level information on a container or image"}, - {"kill", "Kill a running container"}, - {"load", "Load an image from a tar archive or STDIN"}, - {"login", "Register or log in to a Docker registry"}, - {"logout", "Log out from a Docker registry"}, - {"logs", "Fetch the logs of a container"}, - {"port", "List port mappings or a specific mapping for the CONTAINER"}, - {"pause", "Pause all processes within a container"}, - {"ps", "List containers"}, - {"pull", "Pull an image or a repository from a registry"}, - {"push", "Push an image or a repository to a registry"}, - {"rename", "Rename a container"}, - {"restart", "Restart a running container"}, - {"rm", "Remove one or more containers"}, - {"rmi", "Remove one or more images"}, - {"run", "Run a command in a new container"}, - {"save", "Save an image(s) to a tar archive"}, - {"search", "Search the Docker Hub for images"}, - {"start", "Start one or more stopped containers"}, - {"stats", "Display a live stream of container(s) resource usage statistics"}, - {"stop", "Stop a running container"}, - {"tag", "Tag an image into a repository"}, - {"top", "Display the running processes of a container"}, - {"unpause", "Unpause all processes within a container"}, - {"version", "Show the Docker version information"}, - {"wait", "Block until a container stops, then print its exit code"}, - } -) - -func init() { - if dockerCertPath == "" { - dockerCertPath = cliconfig.ConfigDir() - } -} - -func getDaemonConfDir() string { - // TODO: update for Windows daemon - if runtime.GOOS == "windows" { - return cliconfig.ConfigDir() - } - return "/etc/docker" -} - -var ( - flConfigDir = flag.String([]string{"-config"}, cliconfig.ConfigDir(), "Location of client config files") - flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") - flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode") - flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode") - flLogLevel = flag.String([]string{"l", "-log-level"}, "info", "Set the logging level") - flTLS = flag.Bool([]string{"-tls"}, false, "Use TLS; implied by --tlsverify") - flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") - flTLSVerify = flag.Bool([]string{"-tlsverify"}, dockerTlSVerify, "Use TLS and verify the remote") - - // these are initialized in init() below since their default values depend on dockerCertPath which isn't fully initialized until init() runs - tlsOptions tlsconfig.Options - flTrustKey *string - flHosts []string -) - -func setDefaultConfFlag(flag *string, def string) { - if *flag == "" { - if *flDaemon { - *flag = filepath.Join(getDaemonConfDir(), def) - } else { - *flag = filepath.Join(cliconfig.ConfigDir(), def) - } - } -} - -func init() { - var placeholderTrustKey string - // TODO use flag flag.String([]string{"i", "-identity"}, "", "Path to libtrust key file") - flTrustKey = &placeholderTrustKey - - flag.StringVar(&tlsOptions.CAFile, []string{"-tlscacert"}, filepath.Join(dockerCertPath, defaultCaFile), "Trust certs signed only by this CA") - flag.StringVar(&tlsOptions.CertFile, []string{"-tlscert"}, filepath.Join(dockerCertPath, defaultCertFile), "Path to TLS certificate file") - flag.StringVar(&tlsOptions.KeyFile, []string{"-tlskey"}, filepath.Join(dockerCertPath, defaultKeyFile), "Path to TLS key file") - opts.HostListVar(&flHosts, []string{"H", "-host"}, "Daemon socket(s) to connect to") - - flag.Usage = func() { - fmt.Fprint(os.Stdout, "Usage: docker [OPTIONS] COMMAND [arg...]\n\nA self-sufficient runtime for containers.\n\nOptions:\n") - - flag.CommandLine.SetOutput(os.Stdout) - flag.PrintDefaults() - - help := "\nCommands:\n" - - sort.Sort(byName(dockerCommands)) - - for _, cmd := range 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(os.Stdout, "%s\n", help) - } +// TODO(tiborvass): do not show 'daemon' on client-only binaries +// and deduplicate description in dockerCommands and cli subcommands +var dockerCommands = []command{ + {"attach", "Attach to a running container"}, + {"build", "Build an image from a Dockerfile"}, + {"commit", "Create a new image from a container's changes"}, + {"cp", "Copy files/folders from a container to a HOSTDIR or to STDOUT"}, + {"create", "Create a new container"}, + {"diff", "Inspect changes on a container's filesystem"}, + {"events", "Get real time events from the server"}, + {"exec", "Run a command in a running container"}, + {"export", "Export a container's filesystem as a tar archive"}, + {"history", "Show the history of an image"}, + {"images", "List images"}, + {"import", "Import the contents from a tarball to create a filesystem image"}, + {"info", "Display system-wide information"}, + {"inspect", "Return low-level information on a container or image"}, + {"kill", "Kill a running container"}, + {"load", "Load an image from a tar archive or STDIN"}, + {"login", "Register or log in to a Docker registry"}, + {"logout", "Log out from a Docker registry"}, + {"logs", "Fetch the logs of a container"}, + {"port", "List port mappings or a specific mapping for the CONTAINER"}, + {"pause", "Pause all processes within a container"}, + {"ps", "List containers"}, + {"pull", "Pull an image or a repository from a registry"}, + {"push", "Push an image or a repository to a registry"}, + {"rename", "Rename a container"}, + {"restart", "Restart a running container"}, + {"rm", "Remove one or more containers"}, + {"rmi", "Remove one or more images"}, + {"run", "Run a command in a new container"}, + {"save", "Save an image(s) to a tar archive"}, + {"search", "Search the Docker Hub for images"}, + {"start", "Start one or more stopped containers"}, + {"stats", "Display a live stream of container(s) resource usage statistics"}, + {"stop", "Stop a running container"}, + {"tag", "Tag an image into a repository"}, + {"top", "Display the running processes of a container"}, + {"unpause", "Unpause all processes within a container"}, + {"version", "Show the Docker version information"}, + {"wait", "Block until a container stops, then print its exit code"}, } diff --git a/components/engine/docker/log.go b/components/engine/docker/log.go deleted file mode 100644 index 7b43b56f5f..0000000000 --- a/components/engine/docker/log.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "github.com/Sirupsen/logrus" - "io" -) - -func setLogLevel(lvl logrus.Level) { - logrus.SetLevel(lvl) -} - -func initLogging(stderr io.Writer) { - logrus.SetOutput(stderr) -} diff --git a/components/engine/integration-cli/docker_cli_proxy_test.go b/components/engine/integration-cli/docker_cli_proxy_test.go index 8b55c67d81..e05728d067 100644 --- a/components/engine/integration-cli/docker_cli_proxy_test.go +++ b/components/engine/integration-cli/docker_cli_proxy_test.go @@ -43,7 +43,8 @@ func (s *DockerDaemonSuite) TestCliProxyProxyTCPSock(c *check.C) { c.Fatal("could not find ip to connect to") } - if err := s.d.Start("-H", "tcp://"+ip+":2375"); err != nil { + s.d.GlobalFlags = []string{"-H", "tcp://" + ip + ":2375"} + if err := s.d.Start(); err != nil { c.Fatal(err) } diff --git a/components/engine/opts/hosts_unix.go b/components/engine/opts/hosts_unix.go new file mode 100644 index 0000000000..a29335e605 --- /dev/null +++ b/components/engine/opts/hosts_unix.go @@ -0,0 +1,7 @@ +// +build !windows + +package opts + +import "fmt" + +var DefaultHost = fmt.Sprintf("unix://%s", DefaultUnixSocket) diff --git a/components/engine/opts/hosts_windows.go b/components/engine/opts/hosts_windows.go new file mode 100644 index 0000000000..55eac2aaca --- /dev/null +++ b/components/engine/opts/hosts_windows.go @@ -0,0 +1,7 @@ +// +build windows + +package opts + +import "fmt" + +var DefaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) diff --git a/components/engine/opts/opts.go b/components/engine/opts/opts.go index b739012791..6d1b2f7b99 100644 --- a/components/engine/opts/opts.go +++ b/components/engine/opts/opts.go @@ -32,37 +32,37 @@ var ( // ListVar Defines a flag with the specified names and usage, and put the value // list into ListOpts that will hold the values. func ListVar(values *[]string, names []string, usage string) { - flag.Var(newListOptsRef(values, nil), names, usage) + flag.Var(NewListOptsRef(values, nil), names, usage) } // MapVar Defines a flag with the specified names and usage, and put the value // map into MapOpt that will hold the values (key,value). func MapVar(values map[string]string, names []string, usage string) { - flag.Var(newMapOpt(values, nil), names, usage) + flag.Var(NewMapOpts(values, nil), names, usage) } // LogOptsVar Defines a flag with the specified names and usage for --log-opts, // and put the value map into MapOpt that will hold the values (key,value). func LogOptsVar(values map[string]string, names []string, usage string) { - flag.Var(newMapOpt(values, nil), names, usage) + flag.Var(NewMapOpts(values, nil), names, usage) } // HostListVar Defines a flag with the specified names and usage and put the // value into a ListOpts that will hold the values, validating the Host format. func HostListVar(values *[]string, names []string, usage string) { - flag.Var(newListOptsRef(values, ValidateHost), names, usage) + flag.Var(NewListOptsRef(values, ValidateHost), names, usage) } // IPListVar Defines a flag with the specified names and usage and put the // value into a ListOpts that will hold the values, validating the IP format. func IPListVar(values *[]string, names []string, usage string) { - flag.Var(newListOptsRef(values, ValidateIPAddress), names, usage) + flag.Var(NewListOptsRef(values, ValidateIPAddress), names, usage) } // DNSSearchListVar Defines a flag with the specified names and usage and put the // value into a ListOpts that will hold the values, validating the DNS search format. func DNSSearchListVar(values *[]string, names []string, usage string) { - flag.Var(newListOptsRef(values, ValidateDNSSearch), names, usage) + flag.Var(NewListOptsRef(values, ValidateDNSSearch), names, usage) } // IPVar Defines a flag with the specified names and usage for IP and will use @@ -74,12 +74,12 @@ func IPVar(value *net.IP, names []string, defaultValue, usage string) { // LabelListVar Defines a flag with the specified names and usage and put the // value into a ListOpts that will hold the values, validating the label format. func LabelListVar(values *[]string, names []string, usage string) { - flag.Var(newListOptsRef(values, ValidateLabel), names, usage) + flag.Var(NewListOptsRef(values, ValidateLabel), names, usage) } // UlimitMapVar Defines a flag with the specified names and usage for --ulimit, // and put the value map into a UlimitOpt that will hold the values. -func UlimitMapVar(values map[string]*ulimit.Ulimit, names []string, usage string) { +func UlimitMapVar(values *map[string]*ulimit.Ulimit, names []string, usage string) { flag.Var(NewUlimitOpt(values), names, usage) } @@ -92,10 +92,10 @@ type ListOpts struct { // NewListOpts Create a new ListOpts with the specified validator. func NewListOpts(validator ValidatorFctType) ListOpts { var values []string - return *newListOptsRef(&values, validator) + return *NewListOptsRef(&values, validator) } -func newListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts { +func NewListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts { return &ListOpts{ values: values, validator: validator, @@ -191,7 +191,10 @@ func (opts *MapOpts) String() string { return fmt.Sprintf("%v", map[string]string((opts.values))) } -func newMapOpt(values map[string]string, validator ValidatorFctType) *MapOpts { +func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { + if values == nil { + values = make(map[string]string) + } return &MapOpts{ values: values, validator: validator, diff --git a/components/engine/opts/opts_test.go b/components/engine/opts/opts_test.go index 3e639c1fa0..f08df30be6 100644 --- a/components/engine/opts/opts_test.go +++ b/components/engine/opts/opts_test.go @@ -32,7 +32,7 @@ func TestValidateIPAddress(t *testing.T) { func TestMapOpts(t *testing.T) { tmpMap := make(map[string]string) - o := newMapOpt(tmpMap, logOptsValidator) + o := NewMapOpts(tmpMap, logOptsValidator) o.Set("max-size=1") if o.String() != "map[max-size:1]" { t.Errorf("%s != [map[max-size:1]", o.String()) diff --git a/components/engine/opts/ulimit.go b/components/engine/opts/ulimit.go index 361eadf220..f8d34365f5 100644 --- a/components/engine/opts/ulimit.go +++ b/components/engine/opts/ulimit.go @@ -7,10 +7,13 @@ import ( ) type UlimitOpt struct { - values map[string]*ulimit.Ulimit + values *map[string]*ulimit.Ulimit } -func NewUlimitOpt(ref map[string]*ulimit.Ulimit) *UlimitOpt { +func NewUlimitOpt(ref *map[string]*ulimit.Ulimit) *UlimitOpt { + if ref == nil { + ref = &map[string]*ulimit.Ulimit{} + } return &UlimitOpt{ref} } @@ -20,14 +23,14 @@ func (o *UlimitOpt) Set(val string) error { return err } - o.values[l.Name] = l + (*o.values)[l.Name] = l return nil } func (o *UlimitOpt) String() string { var out []string - for _, v := range o.values { + for _, v := range *o.values { out = append(out, v.String()) } @@ -36,7 +39,7 @@ func (o *UlimitOpt) String() string { func (o *UlimitOpt) GetList() []*ulimit.Ulimit { var ulimits []*ulimit.Ulimit - for _, v := range o.values { + for _, v := range *o.values { ulimits = append(ulimits, v) } diff --git a/components/engine/pkg/mflag/flag.go b/components/engine/pkg/mflag/flag.go index 9626a2f6b7..1c068df523 100644 --- a/components/engine/pkg/mflag/flag.go +++ b/components/engine/pkg/mflag/flag.go @@ -526,7 +526,7 @@ func (f *FlagSet) PrintDefaults() { names = append(names, name) } } - if len(names) > 0 { + if len(names) > 0 && len(flag.Usage) > 0 { val := flag.DefValue if home != "" && strings.HasPrefix(val, home) { @@ -1143,3 +1143,53 @@ func (f *FlagSet) Init(name string, errorHandling ErrorHandling) { f.name = name f.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 +} + +func Merge(dest *FlagSet, flagsets ...*FlagSet) error { + for _, fset := range flagsets { + 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} + dest.formal[k] = &newF + } + } + return nil +} + +func (f *FlagSet) IsEmpty() bool { + return len(f.actual) == 0 +} diff --git a/components/engine/pkg/tlsconfig/config.go b/components/engine/pkg/tlsconfig/config.go index aee2132c2b..88f768ae24 100644 --- a/components/engine/pkg/tlsconfig/config.go +++ b/components/engine/pkg/tlsconfig/config.go @@ -17,11 +17,18 @@ import ( // Options represents the information needed to create client and server TLS configurations. type Options struct { + CAFile string + + // If either CertFile or KeyFile is empty, Client() will not load them + // preventing the client from authenticating to the server. + // However, Server() requires them and will error out if they are empty. + CertFile string + KeyFile string + + // client-only option InsecureSkipVerify bool - ClientAuth tls.ClientAuthType - CAFile string - CertFile string - KeyFile string + // server-only option + ClientAuth tls.ClientAuthType } // Extra (server-side) accepted CBC cipher suites - will phase out in the future diff --git a/components/engine/registry/config.go b/components/engine/registry/config.go index 333f1c46c2..a1dc3aba7a 100644 --- a/components/engine/registry/config.go +++ b/components/engine/registry/config.go @@ -43,11 +43,11 @@ var ( // InstallFlags adds command-line options to the top-level flag parser for // the current process. -func (options *Options) InstallFlags() { +func (options *Options) InstallFlags(cmd *flag.FlagSet, usageFn func(string) string) { options.Mirrors = opts.NewListOpts(ValidateMirror) - flag.Var(&options.Mirrors, []string{"-registry-mirror"}, "Preferred Docker registry mirror") + cmd.Var(&options.Mirrors, []string{"-registry-mirror"}, usageFn("Preferred Docker registry mirror")) options.InsecureRegistries = opts.NewListOpts(ValidateIndexName) - flag.Var(&options.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure registry communication") + cmd.Var(&options.InsecureRegistries, []string{"-insecure-registry"}, usageFn("Enable insecure registry communication")) } type netIPNet net.IPNet diff --git a/components/engine/runconfig/parse.go b/components/engine/runconfig/parse.go index f32936daf3..c83d5bea17 100644 --- a/components/engine/runconfig/parse.go +++ b/components/engine/runconfig/parse.go @@ -9,7 +9,6 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/nat" "github.com/docker/docker/pkg/parsers" - "github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/pkg/units" ) @@ -48,8 +47,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flLabels = opts.NewListOpts(opts.ValidateEnv) flDevices = opts.NewListOpts(opts.ValidateDevice) - ulimits = make(map[string]*ulimit.Ulimit) - flUlimits = opts.NewUlimitOpt(ulimits) + flUlimits = opts.NewUlimitOpt(nil) flPublish = opts.NewListOpts(nil) flExpose = opts.NewListOpts(nil)