+ Runtime: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
+ Runtime: 'docker build -t FOO' applies the tag FOO to the newly built container.
diff --git a/MAINTAINERS b/MAINTAINERS
index 6203fee..dc0d1a1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1,4 +1,5 @@
Solomon Hykes <solomon@dotcloud.com>
Guillaume Charmes <guillaume@dotcloud.com>
+Victor Vieux <victor@dotcloud.com>
api.go: Victor Vieux <victor@dotcloud.com>
Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com>
diff --git a/commands.go b/commands.go
index ca49579..38deb1a 100644
--- a/commands.go
+++ b/commands.go
@@ -993,7 +993,7 @@
v.Set("stdout", "1")
v.Set("stderr", "1")
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false); err != nil {
+ if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false, nil, os.Stdout); err != nil {
return err
}
return nil
@@ -1020,15 +1020,35 @@
return err
}
+ splitStderr := container.Config.Tty
+
+ connections := 1
+ if splitStderr {
+ connections += 1
+ }
+ chErrors := make(chan error, connections)
+ cli.monitorTtySize(cmd.Arg(0))
+ if splitStderr {
+ go func() {
+ chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?stream=1&stderr=1", false, nil, os.Stderr)
+ }()
+ }
v := url.Values{}
v.Set("stream", "1")
- v.Set("stdout", "1")
- v.Set("stderr", "1")
v.Set("stdin", "1")
-
- cli.monitorTtySize(cmd.Arg(0))
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil {
- return err
+ v.Set("stdout", "1")
+ if !splitStderr {
+ v.Set("stderr", "1")
+ }
+ go func() {
+ chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout)
+ }()
+ for connections > 0 {
+ err := <-chErrors
+ if err != nil {
+ return err
+ }
+ connections -= 1
}
return nil
}
@@ -1192,19 +1212,14 @@
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
}
- v := url.Values{}
- v.Set("logs", "1")
- v.Set("stream", "1")
+ splitStderr := !config.Tty
- if config.AttachStdin {
- v.Set("stdin", "1")
+ connections := 0
+ if config.AttachStdin || config.AttachStdout || (!splitStderr && config.AttachStderr) {
+ connections += 1
}
- if config.AttachStdout {
- v.Set("stdout", "1")
- }
- if config.AttachStderr {
- v.Set("stderr", "1")
-
+ if splitStderr && config.AttachStderr {
+ connections += 1
}
//start the container
@@ -1213,10 +1228,38 @@
return err
}
- if config.AttachStdin || config.AttachStdout || config.AttachStderr {
+ if connections > 0 {
+ chErrors := make(chan error, connections)
cli.monitorTtySize(out.Id)
- if err := cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil {
- return err
+
+ if splitStderr && config.AttachStderr {
+ go func() {
+ chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr)
+ }()
+ }
+
+ v := url.Values{}
+ v.Set("logs", "1")
+ v.Set("stream", "1")
+
+ if config.AttachStdin {
+ v.Set("stdin", "1")
+ }
+ if config.AttachStdout {
+ v.Set("stdout", "1")
+ }
+ if !splitStderr && config.AttachStderr {
+ v.Set("stderr", "1")
+ }
+ go func() {
+ chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout)
+ }()
+ for connections > 0 {
+ err := <-chErrors
+ if err != nil {
+ return err
+ }
+ connections -= 1
}
}
if !config.AttachStdout && !config.AttachStderr {
@@ -1352,7 +1395,7 @@
return nil
}
-func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
+func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", API_VERSION, path), nil)
if err != nil {
return err
@@ -1370,20 +1413,19 @@
defer rwc.Close()
receiveStdout := utils.Go(func() error {
- _, err := io.Copy(os.Stdout, br)
+ _, err := io.Copy(out, br)
return err
})
- if setRawTerminal && term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
+ if in != nil && setRawTerminal && term.IsTerminal(int(in.Fd())) && os.Getenv("NORAW") == "" {
if oldState, err := term.SetRawTerminal(); err != nil {
return err
} else {
defer term.RestoreTerminal(oldState)
}
}
-
sendStdin := utils.Go(func() error {
- _, err := io.Copy(rwc, os.Stdin)
+ _, err := io.Copy(rwc, in)
if err := rwc.(*net.TCPConn).CloseWrite(); err != nil {
fmt.Fprintf(os.Stderr, "Couldn't send EOF: %s\n", err)
}
diff --git a/runtime_test.go b/runtime_test.go
index 384cbd1..adb5e55 100644
--- a/runtime_test.go
+++ b/runtime_test.go
@@ -5,9 +5,12 @@
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
+ "log"
"net"
"os"
"os/user"
+ "strconv"
+ "strings"
"sync"
"testing"
"time"
@@ -277,24 +280,50 @@
}
+func findAvailalblePort(runtime *Runtime, port int) (*Container, error) {
+ strPort := strconv.Itoa(port)
+ container, err := NewBuilder(runtime).Create(&Config{
+ Image: GetTestImage(runtime).Id,
+ Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p " + strPort},
+ PortSpecs: []string{strPort},
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+ if err := container.Start(); err != nil {
+ if strings.Contains(err.Error(), "address already in use") {
+ return nil, nil
+ }
+ return nil, err
+ }
+ return container, nil
+}
+
// Run a container with a TCP port allocated, and test that it can receive connections on localhost
func TestAllocatePortLocalhost(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
- container, err := NewBuilder(runtime).Create(&Config{
- Image: GetTestImage(runtime).Id,
- Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p 5555"},
- PortSpecs: []string{"5555"},
- },
- )
- if err != nil {
- t.Fatal(err)
+ port := 5554
+
+ var container *Container
+ for {
+ port += 1
+ log.Println("Trying port", port)
+ t.Log("Trying port", port)
+ container, err = findAvailalblePort(runtime, port)
+ if container != nil {
+ break
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ log.Println("Port", port, "already in use")
+ t.Log("Port", port, "already in use")
}
- if err := container.Start(); err != nil {
- t.Fatal(err)
- }
+
defer container.Kill()
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
@@ -308,7 +337,7 @@
conn, err := net.Dial("tcp",
fmt.Sprintf(
- "localhost:%s", container.NetworkSettings.PortMapping["5555"],
+ "localhost:%s", container.NetworkSettings.PortMapping[strconv.Itoa(port)],
),
)
if err != nil {