|
@@ -23,16 +23,20 @@ import (
|
|
|
"time"
|
|
|
|
|
|
"github.com/docker/docker/api"
|
|
|
- "github.com/docker/docker/archive"
|
|
|
"github.com/docker/docker/dockerversion"
|
|
|
"github.com/docker/docker/engine"
|
|
|
+ "github.com/docker/docker/graph"
|
|
|
"github.com/docker/docker/nat"
|
|
|
"github.com/docker/docker/opts"
|
|
|
+ "github.com/docker/docker/pkg/archive"
|
|
|
"github.com/docker/docker/pkg/log"
|
|
|
+ flag "github.com/docker/docker/pkg/mflag"
|
|
|
"github.com/docker/docker/pkg/parsers"
|
|
|
"github.com/docker/docker/pkg/parsers/filters"
|
|
|
+ "github.com/docker/docker/pkg/promise"
|
|
|
"github.com/docker/docker/pkg/signal"
|
|
|
"github.com/docker/docker/pkg/term"
|
|
|
+ "github.com/docker/docker/pkg/timeutils"
|
|
|
"github.com/docker/docker/pkg/units"
|
|
|
"github.com/docker/docker/registry"
|
|
|
"github.com/docker/docker/runconfig"
|
|
@@ -44,6 +48,13 @@ const (
|
|
|
)
|
|
|
|
|
|
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 {
|
|
@@ -53,52 +64,14 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
|
|
return nil
|
|
|
}
|
|
|
}
|
|
|
- help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[unix://%s]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", api.DEFAULTUNIXSOCKET)
|
|
|
- for _, command := range [][]string{
|
|
|
- {"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's filesystem to the host path"},
|
|
|
- {"diff", "Inspect changes on a container's filesystem"},
|
|
|
- {"events", "Get real time events from the server"},
|
|
|
- {"export", "Stream the contents of a container as a tar archive"},
|
|
|
- {"history", "Show the history of an image"},
|
|
|
- {"images", "List images"},
|
|
|
- {"import", "Create a new filesystem image from the contents of a tarball"},
|
|
|
- {"info", "Display system-wide information"},
|
|
|
- {"inspect", "Return low-level information on a container"},
|
|
|
- {"kill", "Kill a running container"},
|
|
|
- {"load", "Load an image from a tar archive"},
|
|
|
- {"login", "Register or log in to a Docker registry server"},
|
|
|
- {"logout", "Log out from a Docker registry server"},
|
|
|
- {"logs", "Fetch the logs of a container"},
|
|
|
- {"port", "Lookup the public-facing port that is NAT-ed to PRIVATE_PORT"},
|
|
|
- {"pause", "Pause all processes within a container"},
|
|
|
- {"ps", "List containers"},
|
|
|
- {"pull", "Pull an image or a repository from a Docker registry server"},
|
|
|
- {"push", "Push an image or a repository to a Docker registry server"},
|
|
|
- {"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 to a tar archive"},
|
|
|
- {"search", "Search for an image on the Docker Hub"},
|
|
|
- {"start", "Start a stopped container"},
|
|
|
- {"stop", "Stop a running container"},
|
|
|
- {"tag", "Tag an image into a repository"},
|
|
|
- {"top", "Lookup the running processes of a container"},
|
|
|
- {"unpause", "Unpause a paused container"},
|
|
|
- {"version", "Show the Docker version information"},
|
|
|
- {"wait", "Block until a container stops, then print its exit code"},
|
|
|
- } {
|
|
|
- help += fmt.Sprintf(" %-10.10s%s\n", command[0], command[1])
|
|
|
- }
|
|
|
- fmt.Fprintf(cli.err, "%s\n", help)
|
|
|
+
|
|
|
+ flag.Usage()
|
|
|
+
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdBuild(args ...string) error {
|
|
|
- cmd := cli.Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new image from the source code at PATH")
|
|
|
+ cmd := cli.Subcmd("build", "PATH | URL | -", "Build a new image from the source code at PATH")
|
|
|
tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success")
|
|
|
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")
|
|
@@ -203,10 +176,15 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|
|
|
|
|
//Check if the given image name can be resolved
|
|
|
if *tag != "" {
|
|
|
- repository, _ := parsers.ParseRepositoryTag(*tag)
|
|
|
+ repository, tag := parsers.ParseRepositoryTag(*tag)
|
|
|
if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
+ if len(tag) > 0 {
|
|
|
+ if err := graph.ValidateTagName(tag); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
v.Set("t", *tag)
|
|
@@ -255,7 +233,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|
|
|
|
|
// 'docker login': login / register a user to registry service.
|
|
|
func (cli *DockerCli) CmdLogin(args ...string) error {
|
|
|
- cmd := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or log in to a Docker registry server, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.")
|
|
|
+ cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.")
|
|
|
|
|
|
var username, password, email string
|
|
|
|
|
@@ -302,16 +280,18 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|
|
username = authconfig.Username
|
|
|
}
|
|
|
}
|
|
|
+ // Assume that a different username means they may not want to use
|
|
|
+ // the password or email from the config file, so prompt them
|
|
|
if username != authconfig.Username {
|
|
|
if password == "" {
|
|
|
- oldState, _ := term.SaveState(cli.terminalFd)
|
|
|
+ oldState, _ := term.SaveState(cli.inFd)
|
|
|
fmt.Fprintf(cli.out, "Password: ")
|
|
|
- term.DisableEcho(cli.terminalFd, oldState)
|
|
|
+ term.DisableEcho(cli.inFd, oldState)
|
|
|
|
|
|
password = readInput(cli.in, cli.out)
|
|
|
fmt.Fprint(cli.out, "\n")
|
|
|
|
|
|
- term.RestoreTerminal(cli.terminalFd, oldState)
|
|
|
+ term.RestoreTerminal(cli.inFd, oldState)
|
|
|
if password == "" {
|
|
|
return fmt.Errorf("Error : Password Required")
|
|
|
}
|
|
@@ -325,8 +305,16 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
- password = authconfig.Password
|
|
|
- email = authconfig.Email
|
|
|
+ // However, if they don't override the username use the
|
|
|
+ // password or email from the cmd line if specified. IOW, allow
|
|
|
+ // then to change/overide them. And if not specified, just
|
|
|
+ // use what's in the config file
|
|
|
+ if password == "" {
|
|
|
+ password = authconfig.Password
|
|
|
+ }
|
|
|
+ if email == "" {
|
|
|
+ email = authconfig.Email
|
|
|
+ }
|
|
|
}
|
|
|
authconfig.Username = username
|
|
|
authconfig.Password = password
|
|
@@ -529,7 +517,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdStop(args ...string) error {
|
|
|
- cmd := cli.Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container by sending SIGTERM and then SIGKILL after a grace period")
|
|
|
+ cmd := cli.Subcmd("stop", "CONTAINER [CONTAINER...]", "Stop a running container by sending SIGTERM and then SIGKILL after a grace period")
|
|
|
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.")
|
|
|
if err := cmd.Parse(args); err != nil {
|
|
|
return nil
|
|
@@ -556,7 +544,7 @@ func (cli *DockerCli) CmdStop(args ...string) error {
|
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdRestart(args ...string) error {
|
|
|
- cmd := cli.Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
|
|
|
+ cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container")
|
|
|
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default is 10 seconds.")
|
|
|
if err := cmd.Parse(args); err != nil {
|
|
|
return nil
|
|
@@ -661,8 +649,8 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
|
|
v.Set("stdout", "1")
|
|
|
v.Set("stderr", "1")
|
|
|
|
|
|
- cErr = utils.Go(func() error {
|
|
|
- return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil)
|
|
|
+ cErr = promise.Go(func() error {
|
|
|
+ return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil)
|
|
|
})
|
|
|
}
|
|
|
|
|
@@ -683,14 +671,13 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
|
|
if encounteredError != nil {
|
|
|
if *openStdin || *attach {
|
|
|
cli.in.Close()
|
|
|
- <-cErr
|
|
|
}
|
|
|
return encounteredError
|
|
|
}
|
|
|
|
|
|
if *openStdin || *attach {
|
|
|
- if tty && cli.isTerminal {
|
|
|
- if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
|
|
+ if tty && cli.isTerminalOut {
|
|
|
+ if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
|
|
log.Errorf("Error monitoring TTY size: %s", err)
|
|
|
}
|
|
|
}
|
|
@@ -861,26 +848,15 @@ func (cli *DockerCli) CmdTop(args ...string) error {
|
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdPort(args ...string) error {
|
|
|
- cmd := cli.Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port that is NAT-ed to PRIVATE_PORT")
|
|
|
+ cmd := cli.Subcmd("port", "CONTAINER [PRIVATE_PORT[/PROTO]]", "List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT")
|
|
|
if err := cmd.Parse(args); err != nil {
|
|
|
return nil
|
|
|
}
|
|
|
- if cmd.NArg() != 2 {
|
|
|
+ if cmd.NArg() < 1 {
|
|
|
cmd.Usage()
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- var (
|
|
|
- port = cmd.Arg(1)
|
|
|
- proto = "tcp"
|
|
|
- parts = strings.SplitN(port, "/", 2)
|
|
|
- )
|
|
|
-
|
|
|
- if len(parts) == 2 && len(parts[1]) != 0 {
|
|
|
- port = parts[0]
|
|
|
- proto = parts[1]
|
|
|
- }
|
|
|
-
|
|
|
steam, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
|
|
if err != nil {
|
|
|
return err
|
|
@@ -895,14 +871,34 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- if frontends, exists := ports[nat.Port(port+"/"+proto)]; exists && frontends != nil {
|
|
|
+ if cmd.NArg() == 2 {
|
|
|
+ var (
|
|
|
+ port = cmd.Arg(1)
|
|
|
+ proto = "tcp"
|
|
|
+ parts = strings.SplitN(port, "/", 2)
|
|
|
+ )
|
|
|
+
|
|
|
+ if len(parts) == 2 && len(parts[1]) != 0 {
|
|
|
+ port = parts[0]
|
|
|
+ proto = parts[1]
|
|
|
+ }
|
|
|
+ natPort := port + "/" + proto
|
|
|
+ if frontends, exists := ports[nat.Port(port+"/"+proto)]; exists && frontends != nil {
|
|
|
+ for _, frontend := range frontends {
|
|
|
+ fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return fmt.Errorf("Error: No public port '%s' published for %s", natPort, cmd.Arg(0))
|
|
|
+ }
|
|
|
+
|
|
|
+ for from, frontends := range ports {
|
|
|
for _, frontend := range frontends {
|
|
|
- fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort)
|
|
|
+ fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIp, frontend.HostPort)
|
|
|
}
|
|
|
- return nil
|
|
|
}
|
|
|
|
|
|
- return fmt.Errorf("Error: No public port '%s' published for %s", cmd.Arg(1), cmd.Arg(0))
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
// 'docker rmi IMAGE' removes all images with the name IMAGE
|
|
@@ -954,7 +950,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
|
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdHistory(args ...string) error {
|
|
|
- cmd := cli.Subcmd("history", "[OPTIONS] IMAGE", "Show the history of an image")
|
|
|
+ cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image")
|
|
|
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
|
|
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
|
|
|
@@ -1011,7 +1007,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdRm(args ...string) error {
|
|
|
- cmd := cli.Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
|
|
|
+ cmd := cli.Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove one or more containers")
|
|
|
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 and not the underlying container")
|
|
|
force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
|
|
@@ -1051,7 +1047,7 @@ func (cli *DockerCli) CmdRm(args ...string) error {
|
|
|
|
|
|
// 'docker kill NAME' kills a running container
|
|
|
func (cli *DockerCli) CmdKill(args ...string) error {
|
|
|
- cmd := cli.Subcmd("kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal")
|
|
|
+ cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal")
|
|
|
signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
|
|
|
|
|
|
if err := cmd.Parse(args); err != nil {
|
|
@@ -1101,7 +1097,8 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
|
|
|
|
|
if repository != "" {
|
|
|
//Check if the given image name can be resolved
|
|
|
- if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
|
+ repo, _ := parsers.ParseRepositoryTag(repository)
|
|
|
+ if _, _, err := registry.ResolveRepositoryName(repo); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
}
|
|
@@ -1182,7 +1179,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
|
|
|
|
|
func (cli *DockerCli) CmdPull(args ...string) error {
|
|
|
cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry")
|
|
|
- tag := cmd.String([]string{"#t", "#-tag"}, "", "Download tagged image in a repository")
|
|
|
+ allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
|
|
|
if err := cmd.Parse(args); err != nil {
|
|
|
return nil
|
|
|
}
|
|
@@ -1192,19 +1189,22 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
|
|
return nil
|
|
|
}
|
|
|
var (
|
|
|
- v = url.Values{}
|
|
|
- remote = cmd.Arg(0)
|
|
|
+ v = url.Values{}
|
|
|
+ remote = cmd.Arg(0)
|
|
|
+ newRemote = remote
|
|
|
)
|
|
|
-
|
|
|
- v.Set("fromImage", remote)
|
|
|
-
|
|
|
- if *tag == "" {
|
|
|
- v.Set("tag", *tag)
|
|
|
+ taglessRemote, tag := parsers.ParseRepositoryTag(remote)
|
|
|
+ if tag == "" && !*allTags {
|
|
|
+ newRemote = taglessRemote + ":latest"
|
|
|
}
|
|
|
+ if tag != "" && *allTags {
|
|
|
+ return fmt.Errorf("tag can't be used with --all-tags/-a")
|
|
|
+ }
|
|
|
+
|
|
|
+ v.Set("fromImage", newRemote)
|
|
|
|
|
|
- remote, _ = parsers.ParseRepositoryTag(remote)
|
|
|
// Resolve the Repository name from fqn to hostname + name
|
|
|
- hostname, _, err := registry.ResolveRepositoryName(remote)
|
|
|
+ hostname, _, err := registry.ResolveRepositoryName(taglessRemote)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
@@ -1244,7 +1244,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdImages(args ...string) error {
|
|
|
- cmd := cli.Subcmd("images", "[OPTIONS] [NAME]", "List images")
|
|
|
+ cmd := cli.Subcmd("images", "[NAME]", "List images")
|
|
|
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
|
|
all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (by default filter out the intermediate image layers)")
|
|
|
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
@@ -1475,57 +1475,68 @@ func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix stri
|
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdPs(args ...string) error {
|
|
|
- cmd := cli.Subcmd("ps", "[OPTIONS]", "List containers")
|
|
|
- quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
|
|
- size := cmd.Bool([]string{"s", "-size"}, false, "Display sizes")
|
|
|
- all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers. Only running containers are shown by default.")
|
|
|
- noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
|
- nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.")
|
|
|
- since := cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show only containers created since Id or Name, include non-running ones.")
|
|
|
- before := cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name, include non-running ones.")
|
|
|
- last := cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running ones.")
|
|
|
+ var (
|
|
|
+ err error
|
|
|
+
|
|
|
+ psFilterArgs = filters.Args{}
|
|
|
+ v = url.Values{}
|
|
|
+
|
|
|
+ cmd = cli.Subcmd("ps", "", "List containers")
|
|
|
+ quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
|
|
+ size = cmd.Bool([]string{"s", "-size"}, false, "Display sizes")
|
|
|
+ all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers. Only running containers are shown by default.")
|
|
|
+ noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
|
|
+ nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.")
|
|
|
+ since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show only containers created since Id or Name, include non-running ones.")
|
|
|
+ before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name, include non-running ones.")
|
|
|
+ last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running ones.")
|
|
|
+ flFilter = opts.NewListOpts(nil)
|
|
|
+ )
|
|
|
|
|
|
- flFilter := opts.NewListOpts(nil)
|
|
|
- cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values. Valid filters:\nexited=<int> - containers with exit code of <int>")
|
|
|
+ cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values. Valid filters:\nexited=<int> - containers with exit code of <int>\nstatus=(restarting|running|paused|exited)")
|
|
|
|
|
|
if err := cmd.Parse(args); err != nil {
|
|
|
return nil
|
|
|
}
|
|
|
- v := url.Values{}
|
|
|
+
|
|
|
if *last == -1 && *nLatest {
|
|
|
*last = 1
|
|
|
}
|
|
|
+
|
|
|
if *all {
|
|
|
v.Set("all", "1")
|
|
|
}
|
|
|
+
|
|
|
if *last != -1 {
|
|
|
v.Set("limit", strconv.Itoa(*last))
|
|
|
}
|
|
|
+
|
|
|
if *since != "" {
|
|
|
v.Set("since", *since)
|
|
|
}
|
|
|
+
|
|
|
if *before != "" {
|
|
|
v.Set("before", *before)
|
|
|
}
|
|
|
+
|
|
|
if *size {
|
|
|
v.Set("size", "1")
|
|
|
}
|
|
|
|
|
|
// Consolidate all filter flags, and sanity check them.
|
|
|
// They'll get processed in the daemon/server.
|
|
|
- psFilterArgs := filters.Args{}
|
|
|
for _, f := range flFilter.GetAll() {
|
|
|
- var err error
|
|
|
- psFilterArgs, err = filters.ParseFlag(f, psFilterArgs)
|
|
|
- if err != nil {
|
|
|
+ if psFilterArgs, err = filters.ParseFlag(f, psFilterArgs); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
if len(psFilterArgs) > 0 {
|
|
|
filterJson, err := filters.ToParam(psFilterArgs)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
+
|
|
|
v.Set("filters", filterJson)
|
|
|
}
|
|
|
|
|
@@ -1538,9 +1549,11 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|
|
if _, err := outs.ReadListFrom(body); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
+
|
|
|
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
|
|
if !*quiet {
|
|
|
fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
|
|
|
+
|
|
|
if *size {
|
|
|
fmt.Fprintln(w, "\tSIZE")
|
|
|
} else {
|
|
@@ -1548,54 +1561,74 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ stripNamePrefix := func(ss []string) []string {
|
|
|
+ for i, s := range ss {
|
|
|
+ ss[i] = s[1:]
|
|
|
+ }
|
|
|
+
|
|
|
+ return ss
|
|
|
+ }
|
|
|
+
|
|
|
for _, out := range outs.Data {
|
|
|
- var (
|
|
|
- outID = out.Get("Id")
|
|
|
- outNames = out.GetList("Names")
|
|
|
- )
|
|
|
+ outID := out.Get("Id")
|
|
|
|
|
|
if !*noTrunc {
|
|
|
outID = utils.TruncateID(outID)
|
|
|
}
|
|
|
|
|
|
- // Remove the leading / from the names
|
|
|
- for i := 0; i < len(outNames); i++ {
|
|
|
- outNames[i] = outNames[i][1:]
|
|
|
+ if *quiet {
|
|
|
+ fmt.Fprintln(w, outID)
|
|
|
+
|
|
|
+ continue
|
|
|
}
|
|
|
|
|
|
- if !*quiet {
|
|
|
- var (
|
|
|
- outCommand = out.Get("Command")
|
|
|
- ports = engine.NewTable("", 0)
|
|
|
- )
|
|
|
- outCommand = strconv.Quote(outCommand)
|
|
|
- if !*noTrunc {
|
|
|
- outCommand = utils.Trunc(outCommand, 20)
|
|
|
- }
|
|
|
- ports.ReadListFrom([]byte(out.Get("Ports")))
|
|
|
- fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ","))
|
|
|
- if *size {
|
|
|
- if out.GetInt("SizeRootFs") > 0 {
|
|
|
- fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(out.GetInt64("SizeRw")), units.HumanSize(out.GetInt64("SizeRootFs")))
|
|
|
- } else {
|
|
|
- fmt.Fprintf(w, "%s\n", units.HumanSize(out.GetInt64("SizeRw")))
|
|
|
+ var (
|
|
|
+ outNames = stripNamePrefix(out.GetList("Names"))
|
|
|
+ outCommand = strconv.Quote(out.Get("Command"))
|
|
|
+ ports = engine.NewTable("", 0)
|
|
|
+ )
|
|
|
+
|
|
|
+ if !*noTrunc {
|
|
|
+ outCommand = utils.Trunc(outCommand, 20)
|
|
|
+
|
|
|
+ // only display the default name for the container with notrunc is passed
|
|
|
+ for _, name := range outNames {
|
|
|
+ if len(strings.Split(name, "/")) == 1 {
|
|
|
+ outNames = []string{name}
|
|
|
+
|
|
|
+ break
|
|
|
}
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ports.ReadListFrom([]byte(out.Get("Ports")))
|
|
|
+
|
|
|
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand,
|
|
|
+ units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))),
|
|
|
+ out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ","))
|
|
|
+
|
|
|
+ if *size {
|
|
|
+ if out.GetInt("SizeRootFs") > 0 {
|
|
|
+ fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(out.GetInt64("SizeRw")), units.HumanSize(out.GetInt64("SizeRootFs")))
|
|
|
} else {
|
|
|
- fmt.Fprint(w, "\n")
|
|
|
+ fmt.Fprintf(w, "%s\n", units.HumanSize(out.GetInt64("SizeRw")))
|
|
|
}
|
|
|
- } else {
|
|
|
- fmt.Fprintln(w, outID)
|
|
|
+
|
|
|
+ continue
|
|
|
}
|
|
|
+
|
|
|
+ fmt.Fprint(w, "\n")
|
|
|
}
|
|
|
|
|
|
if !*quiet {
|
|
|
w.Flush()
|
|
|
}
|
|
|
+
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdCommit(args ...string) error {
|
|
|
- cmd := cli.Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
|
|
|
+ cmd := cli.Subcmd("commit", "CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
|
|
|
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 <hannibal@a-team.com>\")")
|
|
@@ -1656,7 +1689,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdEvents(args ...string) error {
|
|
|
- cmd := cli.Subcmd("events", "[OPTIONS]", "Get real time events from the server")
|
|
|
+ cmd := cli.Subcmd("events", "", "Get real time events from the server")
|
|
|
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
|
|
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
|
|
if err := cmd.Parse(args); err != nil {
|
|
@@ -1672,7 +1705,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
|
|
loc = time.FixedZone(time.Now().Zone())
|
|
|
)
|
|
|
var setTime = func(key, value string) {
|
|
|
- format := time.RFC3339Nano
|
|
|
+ format := timeutils.RFC3339NanoFixed
|
|
|
if len(value) < len(format) {
|
|
|
format = format[:len(value)]
|
|
|
}
|
|
@@ -1792,7 +1825,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
|
|
|
|
|
func (cli *DockerCli) CmdAttach(args ...string) error {
|
|
|
var (
|
|
|
- cmd = cli.Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
|
|
|
+ cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container")
|
|
|
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 (even in non-TTY mode). SIGCHLD, SIGKILL, and SIGSTOP are not proxied.")
|
|
|
)
|
|
@@ -1826,8 +1859,8 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
|
|
tty = config.GetBool("Tty")
|
|
|
)
|
|
|
|
|
|
- if tty && cli.isTerminal {
|
|
|
- if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
|
|
+ if tty && cli.isTerminalOut {
|
|
|
+ if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
|
|
log.Debugf("Error monitoring TTY size: %s", err)
|
|
|
}
|
|
|
}
|
|
@@ -1849,7 +1882,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
|
|
defer signal.StopCatch(sigc)
|
|
|
}
|
|
|
|
|
|
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil); err != nil {
|
|
|
+ if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
@@ -1920,7 +1953,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|
|
type ports []int
|
|
|
|
|
|
func (cli *DockerCli) CmdTag(args ...string) error {
|
|
|
- cmd := cli.Subcmd("tag", "[OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository")
|
|
|
+ cmd := cli.Subcmd("tag", "IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository")
|
|
|
force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
|
|
|
if err := cmd.Parse(args); err != nil {
|
|
|
return nil
|
|
@@ -1981,97 +2014,182 @@ func (cli *DockerCli) pullImage(image string) error {
|
|
|
registryAuthHeader := []string{
|
|
|
base64.URLEncoding.EncodeToString(buf),
|
|
|
}
|
|
|
- if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil {
|
|
|
+ if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (cli *DockerCli) CmdRun(args ...string) error {
|
|
|
- // FIXME: just use runconfig.Parse already
|
|
|
- config, hostConfig, cmd, err := runconfig.ParseSubcommand(cli.Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
+type cidFile struct {
|
|
|
+ path string
|
|
|
+ file *os.File
|
|
|
+ written bool
|
|
|
+}
|
|
|
+
|
|
|
+func newCIDFile(path string) (*cidFile, error) {
|
|
|
+ if _, err := os.Stat(path); err == nil {
|
|
|
+ return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
|
|
|
}
|
|
|
- if config.Image == "" {
|
|
|
- cmd.Usage()
|
|
|
- return nil
|
|
|
+ f, err := os.Create(path)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
|
|
|
}
|
|
|
|
|
|
- // Retrieve relevant client-side config
|
|
|
- var (
|
|
|
- flName = cmd.Lookup("name")
|
|
|
- flRm = cmd.Lookup("rm")
|
|
|
- flSigProxy = cmd.Lookup("sig-proxy")
|
|
|
- autoRemove, _ = strconv.ParseBool(flRm.Value.String())
|
|
|
- sigProxy, _ = strconv.ParseBool(flSigProxy.Value.String())
|
|
|
- )
|
|
|
+ return &cidFile{path: path, file: f}, nil
|
|
|
+}
|
|
|
|
|
|
- // Disable sigProxy in case on TTY
|
|
|
- if config.Tty {
|
|
|
- sigProxy = false
|
|
|
- }
|
|
|
+func (cid *cidFile) Close() error {
|
|
|
+ cid.file.Close()
|
|
|
|
|
|
- var containerIDFile io.WriteCloser
|
|
|
- if len(hostConfig.ContainerIDFile) > 0 {
|
|
|
- if _, err := os.Stat(hostConfig.ContainerIDFile); err == nil {
|
|
|
- return fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
|
|
|
+ if !cid.written {
|
|
|
+ if err := os.Remove(cid.path); err != nil {
|
|
|
+ return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
|
|
|
}
|
|
|
- if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil {
|
|
|
- return fmt.Errorf("Failed to create the container ID file: %s", err)
|
|
|
- }
|
|
|
- defer func() {
|
|
|
- containerIDFile.Close()
|
|
|
- var (
|
|
|
- cidFileInfo os.FileInfo
|
|
|
- err error
|
|
|
- )
|
|
|
- if cidFileInfo, err = os.Stat(hostConfig.ContainerIDFile); err != nil {
|
|
|
- return
|
|
|
- }
|
|
|
- if cidFileInfo.Size() == 0 {
|
|
|
- if err := os.Remove(hostConfig.ContainerIDFile); err != nil {
|
|
|
- fmt.Printf("failed to remove Container ID file '%s': %s \n", hostConfig.ContainerIDFile, err)
|
|
|
- }
|
|
|
- }
|
|
|
- }()
|
|
|
}
|
|
|
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (cid *cidFile) Write(id string) error {
|
|
|
+ if _, err := cid.file.Write([]byte(id)); err != nil {
|
|
|
+ return fmt.Errorf("Failed to write the container ID to the file: %s", err)
|
|
|
+ }
|
|
|
+ cid.written = true
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (engine.Env, error) {
|
|
|
containerValues := url.Values{}
|
|
|
- if name := flName.Value.String(); name != "" {
|
|
|
+ if name != "" {
|
|
|
containerValues.Set("name", name)
|
|
|
}
|
|
|
|
|
|
+ mergedConfig := runconfig.MergeConfigs(config, hostConfig)
|
|
|
+
|
|
|
+ var containerIDFile *cidFile
|
|
|
+ if cidfile != "" {
|
|
|
+ var err error
|
|
|
+ if containerIDFile, err = newCIDFile(cidfile); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ defer containerIDFile.Close()
|
|
|
+ }
|
|
|
+
|
|
|
//create the container
|
|
|
- stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)
|
|
|
+ stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false)
|
|
|
//if image not found try to pull it
|
|
|
if statusCode == 404 {
|
|
|
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", config.Image)
|
|
|
|
|
|
if err = cli.pullImage(config.Image); err != nil {
|
|
|
- return err
|
|
|
+ return nil, err
|
|
|
}
|
|
|
// Retry
|
|
|
- if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false); err != nil {
|
|
|
- return err
|
|
|
+ if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false); err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
} else if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ var result engine.Env
|
|
|
+ if err := result.Decode(stream); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, warning := range result.GetList("Warnings") {
|
|
|
+ fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
|
|
|
+ }
|
|
|
+
|
|
|
+ if containerIDFile != nil {
|
|
|
+ if err = containerIDFile.Write(result.Get("Id")); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result, nil
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+func (cli *DockerCli) CmdCreate(args ...string) error {
|
|
|
+ cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container")
|
|
|
+
|
|
|
+ // These are flags not stored in Config/HostConfig
|
|
|
+ var (
|
|
|
+ flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
|
|
|
+ )
|
|
|
+
|
|
|
+ config, hostConfig, cmd, err := runconfig.Parse(cmd, args, nil)
|
|
|
+ if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
+ if config.Image == "" {
|
|
|
+ cmd.Usage()
|
|
|
+ return nil
|
|
|
+ }
|
|
|
|
|
|
- var runResult engine.Env
|
|
|
- if err := runResult.Decode(stream); err != nil {
|
|
|
+ createResult, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
|
|
|
+ if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- for _, warning := range runResult.GetList("Warnings") {
|
|
|
- fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
|
|
|
+ fmt.Fprintf(cli.out, "%s\n", createResult.Get("Id"))
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (cli *DockerCli) CmdRun(args ...string) error {
|
|
|
+ // FIXME: just use runconfig.Parse already
|
|
|
+ cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
|
|
|
+
|
|
|
+ // These are flags not stored in Config/HostConfig
|
|
|
+ var (
|
|
|
+ flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)")
|
|
|
+ flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run the container in the background and print the new container ID")
|
|
|
+ flSigProxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process (even in non-TTY mode). SIGCHLD, SIGSTOP, and SIGKILL are not proxied.")
|
|
|
+ flName = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
|
|
|
+ flAttach *opts.ListOpts
|
|
|
+
|
|
|
+ ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
|
|
|
+ ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
|
|
|
+ ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
|
|
|
+ )
|
|
|
+
|
|
|
+ config, hostConfig, cmd, err := runconfig.Parse(cmd, args, nil)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if config.Image == "" {
|
|
|
+ cmd.Usage()
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
- if len(hostConfig.ContainerIDFile) > 0 {
|
|
|
- if _, err = containerIDFile.Write([]byte(runResult.Get("Id"))); err != nil {
|
|
|
- return fmt.Errorf("Failed to write the container ID to the file: %s", err)
|
|
|
+ if *flDetach {
|
|
|
+ if fl := cmd.Lookup("attach"); fl != nil {
|
|
|
+ flAttach = fl.Value.(*opts.ListOpts)
|
|
|
+ if flAttach.Len() != 0 {
|
|
|
+ return ErrConflictAttachDetach
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if *flAutoRemove {
|
|
|
+ return ErrConflictDetachAutoRemove
|
|
|
}
|
|
|
+
|
|
|
+ config.AttachStdin = false
|
|
|
+ config.AttachStdout = false
|
|
|
+ config.AttachStderr = false
|
|
|
+ config.StdinOnce = false
|
|
|
+ }
|
|
|
+
|
|
|
+ // Disable flSigProxy in case on TTY
|
|
|
+ sigProxy := *flSigProxy
|
|
|
+ if config.Tty {
|
|
|
+ sigProxy = false
|
|
|
+ }
|
|
|
+
|
|
|
+ runResult, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
}
|
|
|
|
|
|
if sigProxy {
|
|
@@ -2093,6 +2211,10 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|
|
}()
|
|
|
}
|
|
|
|
|
|
+ if *flAutoRemove && (hostConfig.RestartPolicy.Name == "always" || hostConfig.RestartPolicy.Name == "on-failure") {
|
|
|
+ return ErrConflictRestartPolicyAndAutoRemove
|
|
|
+ }
|
|
|
+
|
|
|
// We need to instanciate the chan because the select needs it. It can
|
|
|
// be closed but can't be uninitialized.
|
|
|
hijacked := make(chan io.Closer)
|
|
@@ -2130,8 +2252,8 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- errCh = utils.Go(func() error {
|
|
|
- return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked)
|
|
|
+ errCh = promise.Go(func() error {
|
|
|
+ return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil)
|
|
|
})
|
|
|
} else {
|
|
|
close(hijacked)
|
|
@@ -2157,8 +2279,8 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal {
|
|
|
- if err := cli.monitorTtySize(runResult.Get("Id")); err != nil {
|
|
|
+ if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
|
|
|
+ if err := cli.monitorTtySize(runResult.Get("Id"), false); err != nil {
|
|
|
log.Errorf("Error monitoring TTY size: %s", err)
|
|
|
}
|
|
|
}
|
|
@@ -2180,7 +2302,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|
|
var status int
|
|
|
|
|
|
// Attached mode
|
|
|
- if autoRemove {
|
|
|
+ if *flAutoRemove {
|
|
|
// Autoremove: wait for the container to finish, retrieve
|
|
|
// the exit code and remove the container
|
|
|
if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/wait", nil, false)); err != nil {
|
|
@@ -2254,14 +2376,14 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdSave(args ...string) error {
|
|
|
- cmd := cli.Subcmd("save", "IMAGE", "Save an image to a tar archive (streamed to STDOUT by default)")
|
|
|
- outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
|
|
|
+ cmd := cli.Subcmd("save", "IMAGE [IMAGE...]", "Save an image(s) to a tar archive (streamed to STDOUT by default)")
|
|
|
+ outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
|
|
|
|
|
if err := cmd.Parse(args); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- if cmd.NArg() != 1 {
|
|
|
+ if cmd.NArg() < 1 {
|
|
|
cmd.Usage()
|
|
|
return nil
|
|
|
}
|
|
@@ -2276,9 +2398,19 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
|
|
return err
|
|
|
}
|
|
|
}
|
|
|
- image := cmd.Arg(0)
|
|
|
- if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil {
|
|
|
- return err
|
|
|
+ if len(cmd.Args()) == 1 {
|
|
|
+ image := cmd.Arg(0)
|
|
|
+ if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ v := url.Values{}
|
|
|
+ for _, arg := range cmd.Args() {
|
|
|
+ v.Add("names", arg)
|
|
|
+ }
|
|
|
+ if err := cli.stream("GET", "/images/get?"+v.Encode(), nil, output, nil); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
@@ -2311,3 +2443,101 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
+
|
|
|
+func (cli *DockerCli) CmdExec(args ...string) error {
|
|
|
+ cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in an existing container")
|
|
|
+
|
|
|
+ execConfig, err := runconfig.ParseExec(cmd, args)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if execConfig.Container == "" {
|
|
|
+ cmd.Usage()
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ var execResult engine.Env
|
|
|
+ if err := execResult.Decode(stream); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ execID := execResult.Get("Id")
|
|
|
+
|
|
|
+ if execID == "" {
|
|
|
+ fmt.Fprintf(cli.out, "exec ID empty")
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ if execConfig.Detach {
|
|
|
+ if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // Interactive exec requested.
|
|
|
+ var (
|
|
|
+ out, stderr io.Writer
|
|
|
+ in io.ReadCloser
|
|
|
+ hijacked = make(chan io.Closer)
|
|
|
+ errCh chan error
|
|
|
+ )
|
|
|
+
|
|
|
+ // Block the return until the chan gets closed
|
|
|
+ defer func() {
|
|
|
+ log.Debugf("End of CmdExec(), Waiting for hijack to finish.")
|
|
|
+ if _, ok := <-hijacked; ok {
|
|
|
+ log.Errorf("Hijack did not finish (chan still open)")
|
|
|
+ }
|
|
|
+ }()
|
|
|
+
|
|
|
+ if execConfig.AttachStdin {
|
|
|
+ in = cli.in
|
|
|
+ }
|
|
|
+ if execConfig.AttachStdout {
|
|
|
+ out = cli.out
|
|
|
+ }
|
|
|
+ if execConfig.AttachStderr {
|
|
|
+ if execConfig.Tty {
|
|
|
+ stderr = cli.out
|
|
|
+ } else {
|
|
|
+ stderr = cli.err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ errCh = promise.Go(func() error {
|
|
|
+ return cli.hijack("POST", "/exec/"+execID+"/start", execConfig.Tty, in, out, stderr, hijacked, execConfig)
|
|
|
+ })
|
|
|
+
|
|
|
+ // Acknowledge the hijack before starting
|
|
|
+ select {
|
|
|
+ case closer := <-hijacked:
|
|
|
+ // Make sure that hijack gets closed when returning. (result
|
|
|
+ // in closing hijack chan and freeing server's goroutines.
|
|
|
+ if closer != nil {
|
|
|
+ defer closer.Close()
|
|
|
+ }
|
|
|
+ case err := <-errCh:
|
|
|
+ if err != nil {
|
|
|
+ log.Debugf("Error hijack: %s", err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if execConfig.Tty && cli.isTerminalIn {
|
|
|
+ if err := cli.monitorTtySize(execID, true); err != nil {
|
|
|
+ log.Errorf("Error monitoring TTY size: %s", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := <-errCh; err != nil {
|
|
|
+ log.Debugf("Error hijack: %s", err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|