|
@@ -23,6 +23,7 @@ import (
|
|
"os/signal"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"path/filepath"
|
|
"reflect"
|
|
"reflect"
|
|
|
|
+ "regexp"
|
|
"runtime"
|
|
"runtime"
|
|
"sort"
|
|
"sort"
|
|
"strconv"
|
|
"strconv"
|
|
@@ -41,9 +42,13 @@ var (
|
|
ErrConnectionRefused = errors.New("Can't connect to docker daemon. Is 'docker -d' running on this host?")
|
|
ErrConnectionRefused = errors.New("Can't connect to docker daemon. Is 'docker -d' running on this host?")
|
|
)
|
|
)
|
|
|
|
|
|
-func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
|
|
|
|
|
|
+func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {
|
|
methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
|
|
methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
|
|
- return reflect.TypeOf(cli).MethodByName(methodName)
|
|
|
|
|
|
+ method := reflect.ValueOf(cli).MethodByName(methodName)
|
|
|
|
+ if !method.IsValid() {
|
|
|
|
+ return nil, false
|
|
|
|
+ }
|
|
|
|
+ return method.Interface().(func(...string) error), true
|
|
}
|
|
}
|
|
|
|
|
|
func ParseCommands(proto, addr string, args ...string) error {
|
|
func ParseCommands(proto, addr string, args ...string) error {
|
|
@@ -55,14 +60,7 @@ func ParseCommands(proto, addr string, args ...string) error {
|
|
fmt.Println("Error: Command not found:", args[0])
|
|
fmt.Println("Error: Command not found:", args[0])
|
|
return cli.CmdHelp(args[1:]...)
|
|
return cli.CmdHelp(args[1:]...)
|
|
}
|
|
}
|
|
- ret := method.Func.CallSlice([]reflect.Value{
|
|
|
|
- reflect.ValueOf(cli),
|
|
|
|
- reflect.ValueOf(args[1:]),
|
|
|
|
- })[0].Interface()
|
|
|
|
- if ret == nil {
|
|
|
|
- return nil
|
|
|
|
- }
|
|
|
|
- return ret.(error)
|
|
|
|
|
|
+ return method(args[1:]...)
|
|
}
|
|
}
|
|
return cli.CmdHelp(args...)
|
|
return cli.CmdHelp(args...)
|
|
}
|
|
}
|
|
@@ -73,10 +71,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
|
if !exists {
|
|
if !exists {
|
|
fmt.Fprintf(cli.err, "Error: Command not found: %s\n", args[0])
|
|
fmt.Fprintf(cli.err, "Error: Command not found: %s\n", args[0])
|
|
} else {
|
|
} else {
|
|
- method.Func.CallSlice([]reflect.Value{
|
|
|
|
- reflect.ValueOf(cli),
|
|
|
|
- reflect.ValueOf([]string{"--help"}),
|
|
|
|
- })[0].Interface()
|
|
|
|
|
|
+ method("--help")
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -99,7 +94,6 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
|
{"login", "Register or Login to the docker registry server"},
|
|
{"login", "Register or Login to the docker registry server"},
|
|
{"logs", "Fetch the logs of a container"},
|
|
{"logs", "Fetch the logs of a container"},
|
|
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
|
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
|
- {"top", "Lookup the running processes of a container"},
|
|
|
|
{"ps", "List containers"},
|
|
{"ps", "List containers"},
|
|
{"pull", "Pull an image or a repository from the docker registry server"},
|
|
{"pull", "Pull an image or a repository from the docker registry server"},
|
|
{"push", "Push an image or a repository to the docker registry server"},
|
|
{"push", "Push an image or a repository to the docker registry server"},
|
|
@@ -111,6 +105,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
|
{"start", "Start a stopped container"},
|
|
{"start", "Start a stopped container"},
|
|
{"stop", "Stop a running container"},
|
|
{"stop", "Stop a running container"},
|
|
{"tag", "Tag an image into a repository"},
|
|
{"tag", "Tag an image into a repository"},
|
|
|
|
+ {"top", "Lookup the running processes of a container"},
|
|
{"version", "Show the docker version information"},
|
|
{"version", "Show the docker version information"},
|
|
{"wait", "Block until a container stops, then print its exit code"},
|
|
{"wait", "Block until a container stops, then print its exit code"},
|
|
} {
|
|
} {
|
|
@@ -545,8 +540,23 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
|
|
|
+ sigc := make(chan os.Signal, 1)
|
|
|
|
+ utils.CatchAll(sigc)
|
|
|
|
+ go func() {
|
|
|
|
+ for s := range sigc {
|
|
|
|
+ if _, _, err := cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cid, s), nil); err != nil {
|
|
|
|
+ utils.Debugf("Error sending signal: %s", err)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+ return sigc
|
|
|
|
+}
|
|
|
|
+
|
|
func (cli *DockerCli) CmdStart(args ...string) error {
|
|
func (cli *DockerCli) CmdStart(args ...string) error {
|
|
cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
|
|
cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
|
|
|
|
+ attach := cmd.Bool("a", false, "Attach container's stdout/stderr and forward all signals to the process")
|
|
|
|
+ openStdin := cmd.Bool("i", false, "Attach container's stdin")
|
|
if err := cmd.Parse(args); err != nil {
|
|
if err := cmd.Parse(args); err != nil {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
@@ -555,17 +565,75 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ var cErr chan error
|
|
|
|
+ if *attach || *openStdin {
|
|
|
|
+ if cmd.NArg() > 1 {
|
|
|
|
+ return fmt.Errorf("Impossible to start and attach multiple containers at once.")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ container := &Container{}
|
|
|
|
+ err = json.Unmarshal(body, container)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if !container.Config.Tty {
|
|
|
|
+ sigc := cli.forwardAllSignals(cmd.Arg(0))
|
|
|
|
+ defer utils.StopCatch(sigc)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if container.Config.Tty && cli.isTerminal {
|
|
|
|
+ if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var in io.ReadCloser
|
|
|
|
+
|
|
|
|
+ v := url.Values{}
|
|
|
|
+ v.Set("stream", "1")
|
|
|
|
+ if *openStdin && container.Config.OpenStdin {
|
|
|
|
+ v.Set("stdin", "1")
|
|
|
|
+ in = cli.in
|
|
|
|
+ }
|
|
|
|
+ v.Set("stdout", "1")
|
|
|
|
+ v.Set("stderr", "1")
|
|
|
|
+
|
|
|
|
+ cErr = utils.Go(func() error {
|
|
|
|
+ return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
var encounteredError error
|
|
var encounteredError error
|
|
- for _, name := range args {
|
|
|
|
|
|
+ for _, name := range cmd.Args() {
|
|
_, _, err := cli.call("POST", "/containers/"+name+"/start", nil)
|
|
_, _, err := cli.call("POST", "/containers/"+name+"/start", nil)
|
|
if err != nil {
|
|
if err != nil {
|
|
- fmt.Fprintf(cli.err, "%s\n", err)
|
|
|
|
- encounteredError = fmt.Errorf("Error: failed to start one or more containers")
|
|
|
|
|
|
+ if !*attach || !*openStdin {
|
|
|
|
+ fmt.Fprintf(cli.err, "%s\n", err)
|
|
|
|
+ encounteredError = fmt.Errorf("Error: failed to start one or more containers")
|
|
|
|
+ }
|
|
} else {
|
|
} else {
|
|
- fmt.Fprintf(cli.out, "%s\n", name)
|
|
|
|
|
|
+ if !*attach || !*openStdin {
|
|
|
|
+ fmt.Fprintf(cli.out, "%s\n", name)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if encounteredError != nil {
|
|
|
|
+ if *openStdin || *attach {
|
|
|
|
+ cli.in.Close()
|
|
|
|
+ <-cErr
|
|
}
|
|
}
|
|
|
|
+ return encounteredError
|
|
}
|
|
}
|
|
- return encounteredError
|
|
|
|
|
|
+ if *openStdin || *attach {
|
|
|
|
+ return <-cErr
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
}
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdInspect(args ...string) error {
|
|
func (cli *DockerCli) CmdInspect(args ...string) error {
|
|
@@ -577,30 +645,39 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
|
cmd.Usage()
|
|
cmd.Usage()
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
- fmt.Fprintf(cli.out, "[")
|
|
|
|
- for i, name := range args {
|
|
|
|
- if i > 0 {
|
|
|
|
- fmt.Fprintf(cli.out, ",")
|
|
|
|
- }
|
|
|
|
|
|
+
|
|
|
|
+ indented := new(bytes.Buffer)
|
|
|
|
+ status := 0
|
|
|
|
+
|
|
|
|
+ for _, name := range args {
|
|
obj, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
|
|
obj, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
|
|
if err != nil {
|
|
if err != nil {
|
|
obj, _, err = cli.call("GET", "/images/"+name+"/json", nil)
|
|
obj, _, err = cli.call("GET", "/images/"+name+"/json", nil)
|
|
if err != nil {
|
|
if err != nil {
|
|
fmt.Fprintf(cli.err, "No such image or container: %s\n", name)
|
|
fmt.Fprintf(cli.err, "No such image or container: %s\n", name)
|
|
|
|
+ status = 1
|
|
continue
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- indented := new(bytes.Buffer)
|
|
|
|
if err = json.Indent(indented, obj, "", " "); err != nil {
|
|
if err = json.Indent(indented, obj, "", " "); err != nil {
|
|
fmt.Fprintf(cli.err, "%s\n", err)
|
|
fmt.Fprintf(cli.err, "%s\n", err)
|
|
|
|
+ status = 1
|
|
continue
|
|
continue
|
|
}
|
|
}
|
|
- if _, err := io.Copy(cli.out, indented); err != nil {
|
|
|
|
- fmt.Fprintf(cli.err, "%s\n", err)
|
|
|
|
- }
|
|
|
|
|
|
+ indented.WriteString(",")
|
|
|
|
+ }
|
|
|
|
+ // Remove trailling ','
|
|
|
|
+ indented.Truncate(indented.Len() - 1)
|
|
|
|
+
|
|
|
|
+ fmt.Fprintf(cli.out, "[")
|
|
|
|
+ if _, err := io.Copy(cli.out, indented); err != nil {
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
fmt.Fprintf(cli.out, "]")
|
|
fmt.Fprintf(cli.out, "]")
|
|
|
|
+ if status != 0 {
|
|
|
|
+ return &utils.StatusError{Status: status}
|
|
|
|
+ }
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
@@ -647,11 +724,11 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
|
}
|
|
}
|
|
|
|
|
|
port := cmd.Arg(1)
|
|
port := cmd.Arg(1)
|
|
- proto := "Tcp"
|
|
|
|
|
|
+ proto := "tcp"
|
|
parts := strings.SplitN(port, "/", 2)
|
|
parts := strings.SplitN(port, "/", 2)
|
|
if len(parts) == 2 && len(parts[1]) != 0 {
|
|
if len(parts) == 2 && len(parts[1]) != 0 {
|
|
port = parts[0]
|
|
port = parts[0]
|
|
- proto = strings.ToUpper(parts[1][:1]) + strings.ToLower(parts[1][1:])
|
|
|
|
|
|
+ proto = parts[1]
|
|
}
|
|
}
|
|
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
|
|
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -663,8 +740,14 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
- if frontend, exists := out.NetworkSettings.PortMapping[proto][port]; exists {
|
|
|
|
- fmt.Fprintf(cli.out, "%s\n", frontend)
|
|
|
|
|
|
+ if frontends, exists := out.NetworkSettings.Ports[Port(port+"/"+proto)]; exists {
|
|
|
|
+ if frontends == nil {
|
|
|
|
+ fmt.Fprintf(cli.out, "%s\n", port)
|
|
|
|
+ } else {
|
|
|
|
+ for _, frontend := range frontends {
|
|
|
|
+ fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
} else {
|
|
} else {
|
|
return fmt.Errorf("Error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0))
|
|
return fmt.Errorf("Error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0))
|
|
}
|
|
}
|
|
@@ -740,6 +823,8 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
|
func (cli *DockerCli) CmdRm(args ...string) error {
|
|
func (cli *DockerCli) CmdRm(args ...string) error {
|
|
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
|
|
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
|
|
v := cmd.Bool("v", false, "Remove the volumes associated to the container")
|
|
v := cmd.Bool("v", false, "Remove the volumes associated to the container")
|
|
|
|
+ link := cmd.Bool("link", false, "Remove the specified link and not the underlying container")
|
|
|
|
+
|
|
if err := cmd.Parse(args); err != nil {
|
|
if err := cmd.Parse(args); err != nil {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
@@ -751,6 +836,9 @@ func (cli *DockerCli) CmdRm(args ...string) error {
|
|
if *v {
|
|
if *v {
|
|
val.Set("v", "1")
|
|
val.Set("v", "1")
|
|
}
|
|
}
|
|
|
|
+ if *link {
|
|
|
|
+ val.Set("link", "1")
|
|
|
|
+ }
|
|
for _, name := range cmd.Args() {
|
|
for _, name := range cmd.Args() {
|
|
_, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)
|
|
_, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -985,25 +1073,19 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|
out.Tag = "<none>"
|
|
out.Tag = "<none>"
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if !*noTrunc {
|
|
|
|
+ out.ID = utils.TruncateID(out.ID)
|
|
|
|
+ }
|
|
|
|
+
|
|
if !*quiet {
|
|
if !*quiet {
|
|
- fmt.Fprintf(w, "%s\t%s\t", out.Repository, out.Tag)
|
|
|
|
- if *noTrunc {
|
|
|
|
- fmt.Fprintf(w, "%s\t", out.ID)
|
|
|
|
- } else {
|
|
|
|
- fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID))
|
|
|
|
- }
|
|
|
|
- fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
|
|
|
|
|
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", out.Repository, out.Tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
|
if out.VirtualSize > 0 {
|
|
if out.VirtualSize > 0 {
|
|
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
|
|
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
|
|
} else {
|
|
} else {
|
|
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
|
|
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
- if *noTrunc {
|
|
|
|
- fmt.Fprintln(w, out.ID)
|
|
|
|
- } else {
|
|
|
|
- fmt.Fprintln(w, utils.TruncateID(out.ID))
|
|
|
|
- }
|
|
|
|
|
|
+ fmt.Fprintln(w, out.ID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1017,10 +1099,10 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|
func displayablePorts(ports []APIPort) string {
|
|
func displayablePorts(ports []APIPort) string {
|
|
result := []string{}
|
|
result := []string{}
|
|
for _, port := range ports {
|
|
for _, port := range ports {
|
|
- if port.Type == "tcp" {
|
|
|
|
- result = append(result, fmt.Sprintf("%d->%d", port.PublicPort, port.PrivatePort))
|
|
|
|
|
|
+ if port.IP == "" {
|
|
|
|
+ result = append(result, fmt.Sprintf("%d/%s", port.PublicPort, port.Type))
|
|
} else {
|
|
} else {
|
|
- result = append(result, fmt.Sprintf("%d->%d/%s", port.PublicPort, port.PrivatePort, port.Type))
|
|
|
|
|
|
+ result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sort.Strings(result)
|
|
sort.Strings(result)
|
|
@@ -1073,7 +1155,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|
}
|
|
}
|
|
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
|
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
|
if !*quiet {
|
|
if !*quiet {
|
|
- fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS")
|
|
|
|
|
|
+ fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
|
|
if *size {
|
|
if *size {
|
|
fmt.Fprintln(w, "\tSIZE")
|
|
fmt.Fprintln(w, "\tSIZE")
|
|
} else {
|
|
} else {
|
|
@@ -1082,12 +1164,20 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|
}
|
|
}
|
|
|
|
|
|
for _, out := range outs {
|
|
for _, out := range outs {
|
|
|
|
+ if !*noTrunc {
|
|
|
|
+ out.ID = utils.TruncateID(out.ID)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Remove the leading / from the names
|
|
|
|
+ for i := 0; i < len(out.Names); i++ {
|
|
|
|
+ out.Names[i] = out.Names[i][1:]
|
|
|
|
+ }
|
|
|
|
+
|
|
if !*quiet {
|
|
if !*quiet {
|
|
- if *noTrunc {
|
|
|
|
- fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports))
|
|
|
|
- } else {
|
|
|
|
- fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports))
|
|
|
|
|
|
+ if !*noTrunc {
|
|
|
|
+ out.Command = utils.Trunc(out.Command, 20)
|
|
}
|
|
}
|
|
|
|
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ","))
|
|
if *size {
|
|
if *size {
|
|
if out.SizeRootFs > 0 {
|
|
if out.SizeRootFs > 0 {
|
|
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
|
|
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
|
|
@@ -1098,11 +1188,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|
fmt.Fprint(w, "\n")
|
|
fmt.Fprint(w, "\n")
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
- if *noTrunc {
|
|
|
|
- fmt.Fprintln(w, out.ID)
|
|
|
|
- } else {
|
|
|
|
- fmt.Fprintln(w, utils.TruncateID(out.ID))
|
|
|
|
- }
|
|
|
|
|
|
+ fmt.Fprintln(w, out.ID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1229,15 +1315,18 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
|
cmd.Usage()
|
|
cmd.Usage()
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
+ name := cmd.Arg(0)
|
|
|
|
|
|
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err); err != nil {
|
|
|
|
|
|
+ if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err, nil); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) CmdAttach(args ...string) error {
|
|
func (cli *DockerCli) CmdAttach(args ...string) error {
|
|
- cmd := Subcmd("attach", "CONTAINER", "Attach to a running container")
|
|
|
|
|
|
+ cmd := Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
|
|
|
|
+ noStdin := cmd.Bool("nostdin", false, "Do not attach stdin")
|
|
|
|
+ proxy := cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
|
|
if err := cmd.Parse(args); err != nil {
|
|
if err := cmd.Parse(args); err != nil {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
@@ -1245,8 +1334,8 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
|
cmd.Usage()
|
|
cmd.Usage()
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
-
|
|
|
|
- body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
|
|
|
|
|
|
+ name := cmd.Arg(0)
|
|
|
|
+ body, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
@@ -1261,19 +1350,29 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
|
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
|
|
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
|
|
}
|
|
}
|
|
|
|
|
|
- if container.Config.Tty {
|
|
|
|
|
|
+ if container.Config.Tty && cli.isTerminal {
|
|
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
|
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
|
- utils.Debugf("Error monitoring tty size: %s", err)
|
|
|
|
|
|
+ utils.Debugf("Error monitoring TTY size: %s", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ var in io.ReadCloser
|
|
|
|
+
|
|
v := url.Values{}
|
|
v := url.Values{}
|
|
v.Set("stream", "1")
|
|
v.Set("stream", "1")
|
|
- v.Set("stdin", "1")
|
|
|
|
|
|
+ if !*noStdin && container.Config.OpenStdin {
|
|
|
|
+ v.Set("stdin", "1")
|
|
|
|
+ in = cli.in
|
|
|
|
+ }
|
|
v.Set("stdout", "1")
|
|
v.Set("stdout", "1")
|
|
v.Set("stderr", "1")
|
|
v.Set("stderr", "1")
|
|
|
|
|
|
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, cli.in, cli.out, cli.err); err != nil {
|
|
|
|
|
|
+ if *proxy && !container.Config.Tty {
|
|
|
|
+ sigc := cli.forwardAllSignals(cmd.Arg(0))
|
|
|
|
+ defer utils.StopCatch(sigc)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
return nil
|
|
@@ -1326,18 +1425,6 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|
// Ports type - Used to parse multiple -p flags
|
|
// Ports type - Used to parse multiple -p flags
|
|
type ports []int
|
|
type ports []int
|
|
|
|
|
|
-// ListOpts type
|
|
|
|
-type ListOpts []string
|
|
|
|
-
|
|
|
|
-func (opts *ListOpts) String() string {
|
|
|
|
- return fmt.Sprint(*opts)
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func (opts *ListOpts) Set(value string) error {
|
|
|
|
- *opts = append(*opts, value)
|
|
|
|
- return nil
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
// AttachOpts stores arguments to 'docker run -a', eg. which streams to attach to
|
|
// AttachOpts stores arguments to 'docker run -a', eg. which streams to attach to
|
|
type AttachOpts map[string]bool
|
|
type AttachOpts map[string]bool
|
|
|
|
|
|
@@ -1436,6 +1523,13 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|
flRm := cmd.Lookup("rm")
|
|
flRm := cmd.Lookup("rm")
|
|
autoRemove, _ := strconv.ParseBool(flRm.Value.String())
|
|
autoRemove, _ := strconv.ParseBool(flRm.Value.String())
|
|
|
|
|
|
|
|
+ flSigProxy := cmd.Lookup("sig-proxy")
|
|
|
|
+ sigProxy, _ := strconv.ParseBool(flSigProxy.Value.String())
|
|
|
|
+ flName := cmd.Lookup("name")
|
|
|
|
+ if config.Tty {
|
|
|
|
+ sigProxy = false
|
|
|
|
+ }
|
|
|
|
+
|
|
var containerIDFile *os.File
|
|
var containerIDFile *os.File
|
|
if len(hostConfig.ContainerIDFile) > 0 {
|
|
if len(hostConfig.ContainerIDFile) > 0 {
|
|
if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
|
|
if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
|
|
@@ -1447,9 +1541,14 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|
}
|
|
}
|
|
defer containerIDFile.Close()
|
|
defer containerIDFile.Close()
|
|
}
|
|
}
|
|
|
|
+ containerValues := url.Values{}
|
|
|
|
+ name := flName.Value.String()
|
|
|
|
+ if name != "" {
|
|
|
|
+ containerValues.Set("name", name)
|
|
|
|
+ }
|
|
|
|
|
|
//create the container
|
|
//create the container
|
|
- body, statusCode, err := cli.call("POST", "/containers/create", config)
|
|
|
|
|
|
+ body, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config)
|
|
//if image not found try to pull it
|
|
//if image not found try to pull it
|
|
if statusCode == 404 {
|
|
if statusCode == 404 {
|
|
_, tag := utils.ParseRepositoryTag(config.Image)
|
|
_, tag := utils.ParseRepositoryTag(config.Image)
|
|
@@ -1490,7 +1589,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
- body, _, err = cli.call("POST", "/containers/create", config)
|
|
|
|
|
|
+ body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
@@ -1514,12 +1613,15 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- //start the container
|
|
|
|
- if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
|
|
|
|
- return err
|
|
|
|
|
|
+ if sigProxy {
|
|
|
|
+ sigc := cli.forwardAllSignals(runResult.ID)
|
|
|
|
+ defer utils.StopCatch(sigc)
|
|
}
|
|
}
|
|
|
|
|
|
- var wait chan struct{}
|
|
|
|
|
|
+ var (
|
|
|
|
+ wait chan struct{}
|
|
|
|
+ errCh chan error
|
|
|
|
+ )
|
|
|
|
|
|
if !config.AttachStdout && !config.AttachStderr {
|
|
if !config.AttachStdout && !config.AttachStderr {
|
|
// Make this asynchrone in order to let the client write to stdin before having to read the ID
|
|
// Make this asynchrone in order to let the client write to stdin before having to read the ID
|
|
@@ -1530,20 +1632,18 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|
}()
|
|
}()
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ hijacked := make(chan bool)
|
|
|
|
+
|
|
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
|
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
|
- if config.Tty {
|
|
|
|
- if err := cli.monitorTtySize(runResult.ID); err != nil {
|
|
|
|
- utils.Errorf("Error monitoring TTY size: %s\n", err)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
|
|
v := url.Values{}
|
|
v := url.Values{}
|
|
- v.Set("logs", "1")
|
|
|
|
v.Set("stream", "1")
|
|
v.Set("stream", "1")
|
|
var out, stderr io.Writer
|
|
var out, stderr io.Writer
|
|
|
|
+ var in io.ReadCloser
|
|
|
|
|
|
if config.AttachStdin {
|
|
if config.AttachStdin {
|
|
v.Set("stdin", "1")
|
|
v.Set("stdin", "1")
|
|
|
|
+ in = cli.in
|
|
}
|
|
}
|
|
if config.AttachStdout {
|
|
if config.AttachStdout {
|
|
v.Set("stdout", "1")
|
|
v.Set("stdout", "1")
|
|
@@ -1558,18 +1658,36 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- signals := make(chan os.Signal, 1)
|
|
|
|
- signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
- go func() {
|
|
|
|
- for sig := range signals {
|
|
|
|
- fmt.Printf("\nReceived signal: %s; cleaning up\n", sig)
|
|
|
|
- if err := cli.CmdStop("-t", "4", runResult.ID); err != nil {
|
|
|
|
- fmt.Printf("failed to stop container: %v", err)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }()
|
|
|
|
|
|
+ errCh = utils.Go(func() error {
|
|
|
|
+ return cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked)
|
|
|
|
+ })
|
|
|
|
+ } else {
|
|
|
|
+ close(hijacked)
|
|
|
|
+ }
|
|
|
|
|
|
- if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, out, stderr); err != nil {
|
|
|
|
|
|
+ // Acknowledge the hijack before starting
|
|
|
|
+ select {
|
|
|
|
+ case <-hijacked:
|
|
|
|
+ case err := <-errCh:
|
|
|
|
+ if err != nil {
|
|
|
|
+ utils.Debugf("Error hijack: %s", err)
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //start the container
|
|
|
|
+ if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal {
|
|
|
|
+ if err := cli.monitorTtySize(runResult.ID); err != nil {
|
|
|
|
+ utils.Errorf("Error monitoring TTY size: %s\n", err)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if errCh != nil {
|
|
|
|
+ if err := <-errCh; err != nil {
|
|
utils.Debugf("Error hijack: %s", err)
|
|
utils.Debugf("Error hijack: %s", err)
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
@@ -1579,13 +1697,19 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|
// Detached mode
|
|
// Detached mode
|
|
<-wait
|
|
<-wait
|
|
} else {
|
|
} else {
|
|
- status, err := getExitCode(cli, runResult.ID)
|
|
|
|
|
|
+ running, status, err := getExitCode(cli, runResult.ID)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
if autoRemove {
|
|
if autoRemove {
|
|
- _, _, err = cli.call("DELETE", "/containers/"+runResult.ID, nil)
|
|
|
|
- if err != nil {
|
|
|
|
|
|
+ if running {
|
|
|
|
+ return fmt.Errorf("Impossible to auto-remove a detached container")
|
|
|
|
+ }
|
|
|
|
+ // Wait for the process to
|
|
|
|
+ if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -1642,6 +1766,10 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
|
|
params = bytes.NewBuffer(buf)
|
|
params = bytes.NewBuffer(buf)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // fixme: refactor client to support redirect
|
|
|
|
+ re := regexp.MustCompile("/+")
|
|
|
|
+ path = re.ReplaceAllString(path, "/")
|
|
|
|
+
|
|
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
|
|
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, -1, err
|
|
return nil, -1, err
|
|
@@ -1670,6 +1798,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
|
|
return nil, -1, err
|
|
return nil, -1, err
|
|
}
|
|
}
|
|
defer resp.Body.Close()
|
|
defer resp.Body.Close()
|
|
|
|
+
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, -1, err
|
|
return nil, -1, err
|
|
@@ -1687,6 +1816,11 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
|
|
if (method == "POST" || method == "PUT") && in == nil {
|
|
if (method == "POST" || method == "PUT") && in == nil {
|
|
in = bytes.NewReader([]byte{})
|
|
in = bytes.NewReader([]byte{})
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // fixme: refactor client to support redirect
|
|
|
|
+ re := regexp.MustCompile("/+")
|
|
|
|
+ path = re.ReplaceAllString(path, "/")
|
|
|
|
+
|
|
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
|
|
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
@@ -1742,7 +1876,10 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer) error {
|
|
|
|
|
|
+func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan bool) error {
|
|
|
|
+ // fixme: refactor client to support redirect
|
|
|
|
+ re := regexp.MustCompile("/+")
|
|
|
|
+ path = re.ReplaceAllString(path, "/")
|
|
|
|
|
|
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
|
|
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -1768,6 +1905,10 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|
rwc, br := clientconn.Hijack()
|
|
rwc, br := clientconn.Hijack()
|
|
defer rwc.Close()
|
|
defer rwc.Close()
|
|
|
|
|
|
|
|
+ if started != nil {
|
|
|
|
+ started <- true
|
|
|
|
+ }
|
|
|
|
+
|
|
var receiveStdout chan error
|
|
var receiveStdout chan error
|
|
|
|
|
|
if stdout != nil {
|
|
if stdout != nil {
|
|
@@ -1854,9 +1995,6 @@ func (cli *DockerCli) resizeTty(id string) {
|
|
}
|
|
}
|
|
|
|
|
|
func (cli *DockerCli) monitorTtySize(id string) error {
|
|
func (cli *DockerCli) monitorTtySize(id string) error {
|
|
- if !cli.isTerminal {
|
|
|
|
- return fmt.Errorf("Impossible to monitor size on non-tty")
|
|
|
|
- }
|
|
|
|
cli.resizeTty(id)
|
|
cli.resizeTty(id)
|
|
|
|
|
|
sigchan := make(chan os.Signal, 1)
|
|
sigchan := make(chan os.Signal, 1)
|
|
@@ -1904,20 +2042,22 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
|
return out.StatusCode, nil
|
|
return out.StatusCode, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func getExitCode(cli *DockerCli, containerId string) (int, error) {
|
|
|
|
|
|
+// getExitCode perform an inspect on the container. It returns
|
|
|
|
+// the running state and the exit code.
|
|
|
|
+func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
|
body, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil)
|
|
body, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil)
|
|
if err != nil {
|
|
if err != nil {
|
|
// If we can't connect, then the daemon probably died.
|
|
// If we can't connect, then the daemon probably died.
|
|
if err != ErrConnectionRefused {
|
|
if err != ErrConnectionRefused {
|
|
- return -1, err
|
|
|
|
|
|
+ return false, -1, err
|
|
}
|
|
}
|
|
- return -1, nil
|
|
|
|
|
|
+ return false, -1, nil
|
|
}
|
|
}
|
|
c := &Container{}
|
|
c := &Container{}
|
|
if err := json.Unmarshal(body, c); err != nil {
|
|
if err := json.Unmarshal(body, c); err != nil {
|
|
- return -1, err
|
|
|
|
|
|
+ return false, -1, err
|
|
}
|
|
}
|
|
- return c.State.ExitCode, nil
|
|
|
|
|
|
+ return c.State.Running, c.State.ExitCode, nil
|
|
}
|
|
}
|
|
|
|
|
|
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
|
|
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
|