Просмотр исходного кода

Merge branch 'master' into 1237-improve_docker_top-feature

Conflicts:
	docs/sources/api/docker_remote_api.rst
Victor Vieux 12 лет назад
Родитель
Сommit
c81662eae4

+ 20 - 1
buildfile.go

@@ -7,6 +7,7 @@ import (
 	"github.com/dotcloud/docker/utils"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
+	"net/url"
 	"os"
 	"os"
 	"path"
 	"path"
 	"reflect"
 	"reflect"
@@ -201,6 +202,24 @@ func (b *buildFile) addRemote(container *Container, orig, dest string) error {
 	}
 	}
 	defer file.Body.Close()
 	defer file.Body.Close()
 
 
+	// If the destination is a directory, figure out the filename.
+	if strings.HasSuffix(dest, "/") {
+		u, err := url.Parse(orig)
+		if err != nil {
+			return err
+		}
+		path := u.Path
+		if strings.HasSuffix(path, "/") {
+			path = path[:len(path)-1]
+		}
+		parts := strings.Split(path, "/")
+		filename := parts[len(parts)-1]
+		if filename == "" {
+			return fmt.Errorf("cannot determine filename from url: %s", u)
+		}
+		dest = dest + filename
+	}
+
 	return container.Inject(file.Body, dest)
 	return container.Inject(file.Body, dest)
 }
 }
 
 
@@ -208,7 +227,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
 	origPath := path.Join(b.context, orig)
 	origPath := path.Join(b.context, orig)
 	destPath := path.Join(container.RootfsPath(), dest)
 	destPath := path.Join(container.RootfsPath(), dest)
 	// Preserve the trailing '/'
 	// Preserve the trailing '/'
-	if dest[len(dest)-1] == '/' {
+	if strings.HasSuffix(dest, "/") {
 		destPath = destPath + "/"
 		destPath = destPath + "/"
 	}
 	}
 	fi, err := os.Stat(origPath)
 	fi, err := os.Stat(origPath)

+ 96 - 23
buildfile_test.go

@@ -3,13 +3,17 @@ package docker
 import (
 import (
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
+	"net"
+	"net/http"
+	"net/http/httptest"
+	"strings"
 	"testing"
 	"testing"
 )
 )
 
 
 // mkTestContext generates a build context from the contents of the provided dockerfile.
 // mkTestContext generates a build context from the contents of the provided dockerfile.
 // This context is suitable for use as an argument to BuildFile.Build()
 // This context is suitable for use as an argument to BuildFile.Build()
 func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
 func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
-	context, err := mkBuildContext(fmt.Sprintf(dockerfile, unitTestImageID), files)
+	context, err := mkBuildContext(dockerfile, files)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -22,6 +26,8 @@ type testContextTemplate struct {
 	dockerfile string
 	dockerfile string
 	// Additional files in the context, eg [][2]string{"./passwd", "gordon"}
 	// Additional files in the context, eg [][2]string{"./passwd", "gordon"}
 	files [][2]string
 	files [][2]string
+	// Additional remote files to host on a local HTTP server.
+	remoteFiles [][2]string
 }
 }
 
 
 // A table of all the contexts to build and test.
 // A table of all the contexts to build and test.
@@ -29,27 +35,31 @@ type testContextTemplate struct {
 var testContexts = []testContextTemplate{
 var testContexts = []testContextTemplate{
 	{
 	{
 		`
 		`
-from   %s
+from   {IMAGE}
 run    sh -c 'echo root:testpass > /tmp/passwd'
 run    sh -c 'echo root:testpass > /tmp/passwd'
 run    mkdir -p /var/run/sshd
 run    mkdir -p /var/run/sshd
 run    [ "$(cat /tmp/passwd)" = "root:testpass" ]
 run    [ "$(cat /tmp/passwd)" = "root:testpass" ]
 run    [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
 run    [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
 `,
 `,
 		nil,
 		nil,
+		nil,
 	},
 	},
 
 
 	{
 	{
 		`
 		`
-from %s
+from {IMAGE}
 add foo /usr/lib/bla/bar
 add foo /usr/lib/bla/bar
-run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ]
+run [ "$(cat /usr/lib/bla/bar)" = 'hello' ]
+add http://{SERVERADDR}/baz /usr/lib/baz/quux
+run [ "$(cat /usr/lib/baz/quux)" = 'world!' ]
 `,
 `,
-		[][2]string{{"foo", "hello world!"}},
+		[][2]string{{"foo", "hello"}},
+		[][2]string{{"/baz", "world!"}},
 	},
 	},
 
 
 	{
 	{
 		`
 		`
-from %s
+from {IMAGE}
 add f /
 add f /
 run [ "$(cat /f)" = "hello" ]
 run [ "$(cat /f)" = "hello" ]
 add f /abc
 add f /abc
@@ -71,38 +81,86 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
 			{"f", "hello"},
 			{"f", "hello"},
 			{"d/ga", "bu"},
 			{"d/ga", "bu"},
 		},
 		},
+		nil,
+	},
+
+	{
+		`
+from {IMAGE}
+add http://{SERVERADDR}/x /a/b/c
+run [ "$(cat /a/b/c)" = "hello" ]
+add http://{SERVERADDR}/x?foo=bar /
+run [ "$(cat /x)" = "hello" ]
+add http://{SERVERADDR}/x /d/
+run [ "$(cat /d/x)" = "hello" ]
+add http://{SERVERADDR} /e
+run [ "$(cat /e)" = "blah" ]
+`,
+		nil,
+		[][2]string{{"/x", "hello"}, {"/", "blah"}},
 	},
 	},
 
 
 	{
 	{
 		`
 		`
-from %s
+from   {IMAGE}
 env    FOO BAR
 env    FOO BAR
 run    [ "$FOO" = "BAR" ]
 run    [ "$FOO" = "BAR" ]
 `,
 `,
 		nil,
 		nil,
+		nil,
 	},
 	},
 
 
 	{
 	{
 		`
 		`
-from %s
+from {IMAGE}
 ENTRYPOINT /bin/echo
 ENTRYPOINT /bin/echo
 CMD Hello world
 CMD Hello world
 `,
 `,
 		nil,
 		nil,
+		nil,
 	},
 	},
 
 
 	{
 	{
 		`
 		`
-from %s
+from {IMAGE}
 VOLUME /test
 VOLUME /test
 CMD Hello world
 CMD Hello world
 `,
 `,
 		nil,
 		nil,
+		nil,
 	},
 	},
 }
 }
 
 
 // FIXME: test building with 2 successive overlapping ADD commands
 // FIXME: test building with 2 successive overlapping ADD commands
 
 
+func constructDockerfile(template string, ip net.IP, port string) string {
+	serverAddr := fmt.Sprintf("%s:%s", ip, port)
+	replacer := strings.NewReplacer("{IMAGE}", unitTestImageID, "{SERVERADDR}", serverAddr)
+	return replacer.Replace(template)
+}
+
+func mkTestingFileServer(files [][2]string) (*httptest.Server, error) {
+	mux := http.NewServeMux()
+	for _, file := range files {
+		name, contents := file[0], file[1]
+		mux.HandleFunc(name, func(w http.ResponseWriter, r *http.Request) {
+			w.Write([]byte(contents))
+		})
+	}
+
+	// This is how httptest.NewServer sets up a net.Listener, except that our listener must accept remote
+	// connections (from the container).
+	listener, err := net.Listen("tcp", ":0")
+	if err != nil {
+		return nil, err
+	}
+
+	s := httptest.NewUnstartedServer(mux)
+	s.Listener = listener
+	s.Start()
+	return s, nil
+}
+
 func TestBuild(t *testing.T) {
 func TestBuild(t *testing.T) {
 	for _, ctx := range testContexts {
 	for _, ctx := range testContexts {
 		buildImage(ctx, t)
 		buildImage(ctx, t)
@@ -121,9 +179,24 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
 		pullingPool: make(map[string]struct{}),
 		pullingPool: make(map[string]struct{}),
 		pushingPool: make(map[string]struct{}),
 		pushingPool: make(map[string]struct{}),
 	}
 	}
-	buildfile := NewBuildFile(srv, ioutil.Discard, false)
 
 
-	id, err := buildfile.Build(mkTestContext(context.dockerfile, context.files, t))
+	httpServer, err := mkTestingFileServer(context.remoteFiles)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer httpServer.Close()
+
+	idx := strings.LastIndex(httpServer.URL, ":")
+	if idx < 0 {
+		t.Fatalf("could not get port from test http server address %s", httpServer.URL)
+	}
+	port := httpServer.URL[idx+1:]
+
+	ip := runtime.networkManager.bridgeNetwork.IP
+	dockerfile := constructDockerfile(context.dockerfile, ip, port)
+
+	buildfile := NewBuildFile(srv, ioutil.Discard, false)
+	id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -137,10 +210,10 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
 
 
 func TestVolume(t *testing.T) {
 func TestVolume(t *testing.T) {
 	img := buildImage(testContextTemplate{`
 	img := buildImage(testContextTemplate{`
-        from %s
+        from {IMAGE}
         volume /test
         volume /test
         cmd Hello world
         cmd Hello world
-    `, nil}, t)
+    `, nil, nil}, t)
 
 
 	if len(img.Config.Volumes) == 0 {
 	if len(img.Config.Volumes) == 0 {
 		t.Fail()
 		t.Fail()
@@ -154,9 +227,9 @@ func TestVolume(t *testing.T) {
 
 
 func TestBuildMaintainer(t *testing.T) {
 func TestBuildMaintainer(t *testing.T) {
 	img := buildImage(testContextTemplate{`
 	img := buildImage(testContextTemplate{`
-        from %s
+        from {IMAGE}
         maintainer dockerio
         maintainer dockerio
-    `, nil}, t)
+    `, nil, nil}, t)
 
 
 	if img.Author != "dockerio" {
 	if img.Author != "dockerio" {
 		t.Fail()
 		t.Fail()
@@ -165,10 +238,10 @@ func TestBuildMaintainer(t *testing.T) {
 
 
 func TestBuildEnv(t *testing.T) {
 func TestBuildEnv(t *testing.T) {
 	img := buildImage(testContextTemplate{`
 	img := buildImage(testContextTemplate{`
-        from %s
+        from {IMAGE}
         env port 4243
         env port 4243
         `,
         `,
-		nil}, t)
+		nil, nil}, t)
 
 
 	if img.Config.Env[0] != "port=4243" {
 	if img.Config.Env[0] != "port=4243" {
 		t.Fail()
 		t.Fail()
@@ -177,10 +250,10 @@ func TestBuildEnv(t *testing.T) {
 
 
 func TestBuildCmd(t *testing.T) {
 func TestBuildCmd(t *testing.T) {
 	img := buildImage(testContextTemplate{`
 	img := buildImage(testContextTemplate{`
-        from %s
+        from {IMAGE}
         cmd ["/bin/echo", "Hello World"]
         cmd ["/bin/echo", "Hello World"]
         `,
         `,
-		nil}, t)
+		nil, nil}, t)
 
 
 	if img.Config.Cmd[0] != "/bin/echo" {
 	if img.Config.Cmd[0] != "/bin/echo" {
 		t.Log(img.Config.Cmd[0])
 		t.Log(img.Config.Cmd[0])
@@ -194,10 +267,10 @@ func TestBuildCmd(t *testing.T) {
 
 
 func TestBuildExpose(t *testing.T) {
 func TestBuildExpose(t *testing.T) {
 	img := buildImage(testContextTemplate{`
 	img := buildImage(testContextTemplate{`
-        from %s
+        from {IMAGE}
         expose 4243
         expose 4243
         `,
         `,
-		nil}, t)
+		nil, nil}, t)
 
 
 	if img.Config.PortSpecs[0] != "4243" {
 	if img.Config.PortSpecs[0] != "4243" {
 		t.Fail()
 		t.Fail()
@@ -206,10 +279,10 @@ func TestBuildExpose(t *testing.T) {
 
 
 func TestBuildEntrypoint(t *testing.T) {
 func TestBuildEntrypoint(t *testing.T) {
 	img := buildImage(testContextTemplate{`
 	img := buildImage(testContextTemplate{`
-        from %s
+        from {IMAGE}
         entrypoint ["/bin/echo"]
         entrypoint ["/bin/echo"]
         `,
         `,
-		nil}, t)
+		nil, nil}, t)
 
 
 	if img.Config.Entrypoint[0] != "/bin/echo" {
 	if img.Config.Entrypoint[0] != "/bin/echo" {
 	}
 	}

+ 19 - 5
commands.go

@@ -475,7 +475,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
 
 
 func (cli *DockerCli) CmdStop(args ...string) error {
 func (cli *DockerCli) CmdStop(args ...string) error {
 	cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container")
 	cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container")
-	nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Default=10")
+	nSeconds := cmd.Int("t", 10, "Number of seconds to wait for the container to stop before killing it.")
 	if err := cmd.Parse(args); err != nil {
 	if err := cmd.Parse(args); err != nil {
 		return nil
 		return nil
 	}
 	}
@@ -1110,10 +1110,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
 		return nil
 		return nil
 	}
 	}
 
 
-	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, cli.out); err != nil {
-		return err
-	}
-	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, cli.err); err != nil {
+	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out); err != nil {
 		return err
 		return err
 	}
 	}
 	return nil
 	return nil
@@ -1316,6 +1313,18 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		return nil
 		return nil
 	}
 	}
 
 
+	var containerIDFile *os.File
+	if len(hostConfig.ContainerIDFile) > 0 {
+		if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
+			return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
+		}
+		containerIDFile, err = os.Create(hostConfig.ContainerIDFile)
+		if err != nil {
+			return fmt.Errorf("failed to create the container ID file: %s", err)
+		}
+		defer containerIDFile.Close()
+	}
+
 	//create the container
 	//create the container
 	body, statusCode, err := cli.call("POST", "/containers/create", config)
 	body, statusCode, err := cli.call("POST", "/containers/create", config)
 	//if image not found try to pull it
 	//if image not found try to pull it
@@ -1346,6 +1355,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 	for _, warning := range runResult.Warnings {
 	for _, warning := range runResult.Warnings {
 		fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
 		fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
 	}
 	}
+	if len(hostConfig.ContainerIDFile) > 0 {
+		if _, err = containerIDFile.WriteString(runResult.ID); err != nil {
+			return fmt.Errorf("failed to write the container ID to the file: %s", err)
+		}
+	}
 
 
 	//start the container
 	//start the container
 	if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
 	if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {

+ 6 - 6
commands_test.go

@@ -59,7 +59,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
 	return nil
 	return nil
 }
 }
 
 
-
 // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
 // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
 func TestRunHostname(t *testing.T) {
 func TestRunHostname(t *testing.T) {
 	stdout, stdoutPipe := io.Pipe()
 	stdout, stdoutPipe := io.Pipe()
@@ -91,7 +90,6 @@ func TestRunHostname(t *testing.T) {
 
 
 }
 }
 
 
-
 // TestAttachStdin checks attaching to stdin without stdout and stderr.
 // TestAttachStdin checks attaching to stdin without stdout and stderr.
 // 'docker run -i -a stdin' should sends the client's stdin to the command,
 // 'docker run -i -a stdin' should sends the client's stdin to the command,
 // then detach from it and print the container id.
 // then detach from it and print the container id.
@@ -144,15 +142,17 @@ func TestRunAttachStdin(t *testing.T) {
 	})
 	})
 
 
 	// Check logs
 	// Check logs
-	if cmdLogs, err := container.ReadLog("stdout"); err != nil {
+	if cmdLogs, err := container.ReadLog("json"); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	} else {
 	} else {
 		if output, err := ioutil.ReadAll(cmdLogs); err != nil {
 		if output, err := ioutil.ReadAll(cmdLogs); err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		} else {
 		} else {
-			expectedLog := "hello\nhi there\n"
-			if string(output) != expectedLog {
-				t.Fatalf("Unexpected logs: should be '%s', not '%s'\n", expectedLog, output)
+			expectedLogs := []string{"{\"log\":\"hello\\n\",\"stream\":\"stdout\"", "{\"log\":\"hi there\\n\",\"stream\":\"stdout\""}
+			for _, expectedLog := range expectedLogs {
+				if !strings.Contains(string(output), expectedLog) {
+					t.Fatalf("Unexpected logs: should contains '%s', it is not '%s'\n", expectedLog, output)
+				}
 			}
 			}
 		}
 		}
 	}
 	}

+ 11 - 6
container.go

@@ -80,7 +80,8 @@ type Config struct {
 }
 }
 
 
 type HostConfig struct {
 type HostConfig struct {
-	Binds []string
+	Binds           []string
+	ContainerIDFile string
 }
 }
 
 
 type BindMap struct {
 type BindMap struct {
@@ -93,6 +94,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
 	cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
 	cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
 	if len(args) > 0 && args[0] != "--help" {
 	if len(args) > 0 && args[0] != "--help" {
 		cmd.SetOutput(ioutil.Discard)
 		cmd.SetOutput(ioutil.Discard)
+		cmd.Usage = nil
 	}
 	}
 
 
 	flHostname := cmd.String("h", "", "Container host name")
 	flHostname := cmd.String("h", "", "Container host name")
@@ -103,6 +105,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
 	flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
 	flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
 	flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
 	flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
 	flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
 	flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
+	flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
 
 
 	if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
 	if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
 		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
 		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
@@ -190,7 +193,8 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
 		Entrypoint:   entrypoint,
 		Entrypoint:   entrypoint,
 	}
 	}
 	hostConfig := &HostConfig{
 	hostConfig := &HostConfig{
-		Binds: binds,
+		Binds:           binds,
+		ContainerIDFile: *flContainerIDFile,
 	}
 	}
 
 
 	if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
 	if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
@@ -637,6 +641,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
 	params = append(params,
 	params = append(params,
 		"-e", "HOME=/",
 		"-e", "HOME=/",
 		"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
 		"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+		"-e", "container=lxc",
 	)
 	)
 
 
 	for _, elem := range container.Config.Env {
 	for _, elem := range container.Config.Env {
@@ -650,10 +655,10 @@ func (container *Container) Start(hostConfig *HostConfig) error {
 	container.cmd = exec.Command("lxc-start", params...)
 	container.cmd = exec.Command("lxc-start", params...)
 
 
 	// Setup logging of stdout and stderr to disk
 	// Setup logging of stdout and stderr to disk
-	if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil {
+	if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
 		return err
 		return err
 	}
 	}
-	if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr")); err != nil {
+	if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil {
 		return err
 		return err
 	}
 	}
 
 
@@ -712,13 +717,13 @@ func (container *Container) StdinPipe() (io.WriteCloser, error) {
 
 
 func (container *Container) StdoutPipe() (io.ReadCloser, error) {
 func (container *Container) StdoutPipe() (io.ReadCloser, error) {
 	reader, writer := io.Pipe()
 	reader, writer := io.Pipe()
-	container.stdout.AddWriter(writer)
+	container.stdout.AddWriter(writer, "")
 	return utils.NewBufReader(reader), nil
 	return utils.NewBufReader(reader), nil
 }
 }
 
 
 func (container *Container) StderrPipe() (io.ReadCloser, error) {
 func (container *Container) StderrPipe() (io.ReadCloser, error) {
 	reader, writer := io.Pipe()
 	reader, writer := io.Pipe()
-	container.stderr.AddWriter(writer)
+	container.stderr.AddWriter(writer, "")
 	return utils.NewBufReader(reader), nil
 	return utils.NewBufReader(reader), nil
 }
 }
 
 

+ 13 - 104
container_test.go

@@ -39,16 +39,11 @@ func TestIDFormat(t *testing.T) {
 func TestMultipleAttachRestart(t *testing.T) {
 func TestMultipleAttachRestart(t *testing.T) {
 	runtime := mkRuntime(t)
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	defer nuke(runtime)
-	container, err := NewBuilder(runtime).Create(
-		&Config{
-			Image: GetTestImage(runtime).ID,
-			Cmd: []string{"/bin/sh", "-c",
-				"i=1; while [ $i -le 5 ]; do i=`expr $i + 1`;  echo hello; done"},
-		},
+	container, hostConfig, _ := mkContainer(
+		runtime,
+		[]string{"_", "/bin/sh", "-c", "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`;  echo hello; done"},
+		t,
 	)
 	)
-	if err != nil {
-		t.Fatal(err)
-	}
 	defer runtime.Destroy(container)
 	defer runtime.Destroy(container)
 
 
 	// Simulate 3 client attaching to the container and stop/restart
 	// Simulate 3 client attaching to the container and stop/restart
@@ -65,7 +60,6 @@ func TestMultipleAttachRestart(t *testing.T) {
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	hostConfig := &HostConfig{}
 	if err := container.Start(hostConfig); err != nil {
 	if err := container.Start(hostConfig); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -140,19 +134,8 @@ func TestMultipleAttachRestart(t *testing.T) {
 func TestDiff(t *testing.T) {
 func TestDiff(t *testing.T) {
 	runtime := mkRuntime(t)
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	defer nuke(runtime)
-
-	builder := NewBuilder(runtime)
-
 	// Create a container and remove a file
 	// Create a container and remove a file
-	container1, err := builder.Create(
-		&Config{
-			Image: GetTestImage(runtime).ID,
-			Cmd:   []string{"/bin/rm", "/etc/passwd"},
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container1, _, _ := mkContainer(runtime, []string{"_", "/bin/rm", "/etc/passwd"}, t)
 	defer runtime.Destroy(container1)
 	defer runtime.Destroy(container1)
 
 
 	if err := container1.Run(); err != nil {
 	if err := container1.Run(); err != nil {
@@ -185,15 +168,7 @@ func TestDiff(t *testing.T) {
 	}
 	}
 
 
 	// Create a new container from the commited image
 	// Create a new container from the commited image
-	container2, err := builder.Create(
-		&Config{
-			Image: img.ID,
-			Cmd:   []string{"cat", "/etc/passwd"},
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container2, _, _ := mkContainer(runtime, []string{img.ID, "cat", "/etc/passwd"}, t)
 	defer runtime.Destroy(container2)
 	defer runtime.Destroy(container2)
 
 
 	if err := container2.Run(); err != nil {
 	if err := container2.Run(); err != nil {
@@ -212,15 +187,7 @@ func TestDiff(t *testing.T) {
 	}
 	}
 
 
 	// Create a new containere
 	// Create a new containere
-	container3, err := builder.Create(
-		&Config{
-			Image: GetTestImage(runtime).ID,
-			Cmd:   []string{"rm", "/bin/httpd"},
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container3, _, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t)
 	defer runtime.Destroy(container3)
 	defer runtime.Destroy(container3)
 
 
 	if err := container3.Run(); err != nil {
 	if err := container3.Run(); err != nil {
@@ -246,17 +213,7 @@ func TestDiff(t *testing.T) {
 func TestCommitAutoRun(t *testing.T) {
 func TestCommitAutoRun(t *testing.T) {
 	runtime := mkRuntime(t)
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	defer nuke(runtime)
-
-	builder := NewBuilder(runtime)
-	container1, err := builder.Create(
-		&Config{
-			Image: GetTestImage(runtime).ID,
-			Cmd:   []string{"/bin/sh", "-c", "echo hello > /world"},
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
 	defer runtime.Destroy(container1)
 	defer runtime.Destroy(container1)
 
 
 	if container1.State.Running {
 	if container1.State.Running {
@@ -279,14 +236,7 @@ func TestCommitAutoRun(t *testing.T) {
 	}
 	}
 
 
 	// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
 	// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
-	container2, err := builder.Create(
-		&Config{
-			Image: img.ID,
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container2, hostConfig, _ := mkContainer(runtime, []string{img.ID}, t)
 	defer runtime.Destroy(container2)
 	defer runtime.Destroy(container2)
 	stdout, err := container2.StdoutPipe()
 	stdout, err := container2.StdoutPipe()
 	if err != nil {
 	if err != nil {
@@ -296,7 +246,6 @@ func TestCommitAutoRun(t *testing.T) {
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	hostConfig := &HostConfig{}
 	if err := container2.Start(hostConfig); err != nil {
 	if err := container2.Start(hostConfig); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -324,17 +273,7 @@ func TestCommitRun(t *testing.T) {
 	runtime := mkRuntime(t)
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	defer nuke(runtime)
 
 
-	builder := NewBuilder(runtime)
-
-	container1, err := builder.Create(
-		&Config{
-			Image: GetTestImage(runtime).ID,
-			Cmd:   []string{"/bin/sh", "-c", "echo hello > /world"},
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container1, hostConfig, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
 	defer runtime.Destroy(container1)
 	defer runtime.Destroy(container1)
 
 
 	if container1.State.Running {
 	if container1.State.Running {
@@ -357,16 +296,7 @@ func TestCommitRun(t *testing.T) {
 	}
 	}
 
 
 	// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
 	// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
-
-	container2, err := builder.Create(
-		&Config{
-			Image: img.ID,
-			Cmd:   []string{"cat", "/world"},
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container2, hostConfig, _ := mkContainer(runtime, []string{img.ID, "cat", "/world"}, t)
 	defer runtime.Destroy(container2)
 	defer runtime.Destroy(container2)
 	stdout, err := container2.StdoutPipe()
 	stdout, err := container2.StdoutPipe()
 	if err != nil {
 	if err != nil {
@@ -376,7 +306,6 @@ func TestCommitRun(t *testing.T) {
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	hostConfig := &HostConfig{}
 	if err := container2.Start(hostConfig); err != nil {
 	if err := container2.Start(hostConfig); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -403,18 +332,7 @@ func TestCommitRun(t *testing.T) {
 func TestStart(t *testing.T) {
 func TestStart(t *testing.T) {
 	runtime := mkRuntime(t)
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	defer nuke(runtime)
-	container, err := NewBuilder(runtime).Create(
-		&Config{
-			Image:     GetTestImage(runtime).ID,
-			Memory:    33554432,
-			CpuShares: 1000,
-			Cmd:       []string{"/bin/cat"},
-			OpenStdin: true,
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container, hostConfig, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
 	defer runtime.Destroy(container)
 	defer runtime.Destroy(container)
 
 
 	cStdin, err := container.StdinPipe()
 	cStdin, err := container.StdinPipe()
@@ -422,7 +340,6 @@ func TestStart(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	hostConfig := &HostConfig{}
 	if err := container.Start(hostConfig); err != nil {
 	if err := container.Start(hostConfig); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -445,15 +362,7 @@ func TestStart(t *testing.T) {
 func TestRun(t *testing.T) {
 func TestRun(t *testing.T) {
 	runtime := mkRuntime(t)
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	defer nuke(runtime)
-	container, err := NewBuilder(runtime).Create(
-		&Config{
-			Image: GetTestImage(runtime).ID,
-			Cmd:   []string{"ls", "-al"},
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
 	defer runtime.Destroy(container)
 	defer runtime.Destroy(container)
 
 
 	if container.State.Running {
 	if container.State.Running {

+ 45 - 20
docs/sources/api/docker_remote_api.rst

@@ -2,6 +2,9 @@
 :description: API Documentation for Docker
 :description: API Documentation for Docker
 :keywords: API, Docker, rcli, REST, documentation
 :keywords: API, Docker, rcli, REST, documentation
 
 
+.. COMMENT use http://pythonhosted.org/sphinxcontrib-httpdomain/ to
+.. document the REST API.
+
 =================
 =================
 Docker Remote API
 Docker Remote API
 =================
 =================
@@ -13,15 +16,23 @@ Docker Remote API
 
 
 - The Remote API is replacing rcli
 - The Remote API is replacing rcli
 - Default port in the docker deamon is 4243 
 - Default port in the docker deamon is 4243 
-- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
-- Since API version 1.2, the auth configuration is now handled client side, so the client has to send the authConfig as POST in /images/(name)/push
+- The API tends to be REST, but for some complex commands, like attach
+  or pull, the HTTP connection is hijacked to transport stdout stdin
+  and stderr
+- Since API version 1.2, the auth configuration is now handled client
+  side, so the client has to send the authConfig as POST in
+  /images/(name)/push
 
 
 2. Versions
 2. Versions
 ===========
 ===========
 
 
-The current verson of the API is 1.4
-Calling /images/<name>/insert is the same as calling /v1.4/images/<name>/insert
-You can still call an old version of the api using /v1.0/images/<name>/insert
+The current verson of the API is 1.3
+
+Calling /images/<name>/insert is the same as calling
+/v1.3/images/<name>/insert 
+
+You can still call an old version of the api using
+/v1.0/images/<name>/insert
 
 
 :doc:`docker_remote_api_v1.3`
 :doc:`docker_remote_api_v1.3`
 *****************************
 *****************************
@@ -29,9 +40,9 @@ You can still call an old version of the api using /v1.0/images/<name>/insert
 What's new
 What's new
 ----------
 ----------
 
 
-Listing processes (/top):
+.. http:get:: /containers/(id)/top
 
 
-- You can now use ps args with docker top, like `docker top <container_id> aux`
+   **New!** You can now use ps args with docker top, like `docker top <container_id> aux`
 
 
 :doc:`docker_remote_api_v1.3`
 :doc:`docker_remote_api_v1.3`
 *****************************
 *****************************
@@ -41,19 +52,21 @@ docker v0.5.0 51f6c4a_
 What's new
 What's new
 ----------
 ----------
 
 
-Listing processes (/top):
-
-- List the processes inside a container
+.. http:get:: /containers/(id)/top
 
 
+   List the processes running inside a container.
 
 
 Builder (/build):
 Builder (/build):
 
 
 - Simplify the upload of the build context
 - Simplify the upload of the build context
-- Simply stream a tarball instead of multipart upload with 4 intermediary buffers
+- Simply stream a tarball instead of multipart upload with 4
+  intermediary buffers
 - Simpler, less memory usage, less disk usage and faster
 - Simpler, less memory usage, less disk usage and faster
 
 
-.. Note::
-The /build improvements are not reverse-compatible. Pre 1.3 clients will break on /build.
+.. Warning::
+
+  The /build improvements are not reverse-compatible. Pre 1.3 clients
+  will break on /build.
 
 
 List containers (/containers/json):
 List containers (/containers/json):
 
 
@@ -61,7 +74,8 @@ List containers (/containers/json):
 
 
 Start containers (/containers/<id>/start):
 Start containers (/containers/<id>/start):
 
 
-- You can now pass host-specific configuration (e.g. bind mounts) in the POST body for start calls 
+- You can now pass host-specific configuration (e.g. bind mounts) in
+  the POST body for start calls
 
 
 :doc:`docker_remote_api_v1.2`
 :doc:`docker_remote_api_v1.2`
 *****************************
 *****************************
@@ -72,14 +86,25 @@ What's new
 ----------
 ----------
 
 
 The auth configuration is now handled by the client.
 The auth configuration is now handled by the client.
-The client should send it's authConfig as POST on each call of /images/(name)/push
 
 
-.. http:get:: /auth is now deprecated
-.. http:post:: /auth only checks the configuration but doesn't store it on the server
+The client should send it's authConfig as POST on each call of
+/images/(name)/push
+
+.. http:get:: /auth 
+
+  **Deprecated.**
+
+.. http:post:: /auth 
+
+  Only checks the configuration but doesn't store it on the server
+
+  Deleting an image is now improved, will only untag the image if it
+  has chidren and remove all the untagged parents if has any.
 
 
-Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any.
+.. http:post:: /images/<name>/delete 
 
 
-.. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged
+  Now returns a JSON structure with the list of images
+  deleted/untagged.
 
 
 
 
 :doc:`docker_remote_api_v1.1`
 :doc:`docker_remote_api_v1.1`
@@ -94,7 +119,7 @@ What's new
 .. http:post:: /images/(name)/insert
 .. http:post:: /images/(name)/insert
 .. http:post:: /images/(name)/push
 .. http:post:: /images/(name)/push
 
 
-Uses json stream instead of HTML hijack, it looks like this:
+   Uses json stream instead of HTML hijack, it looks like this:
 
 
         .. sourcecode:: http
         .. sourcecode:: http
 
 

+ 7 - 2
docs/sources/api/docker_remote_api_v1.0.rst

@@ -1,3 +1,8 @@
+.. use orphan to suppress "WARNING: document isn't included in any toctree"
+.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
+
+:orphan:
+
 :title: Remote API v1.0
 :title: Remote API v1.0
 :description: API Documentation for Docker
 :description: API Documentation for Docker
 :keywords: API, Docker, rcli, REST, documentation
 :keywords: API, Docker, rcli, REST, documentation
@@ -300,8 +305,8 @@ Start a container
 	:statuscode 500: server error
 	:statuscode 500: server error
 
 
 
 
-Stop a contaier
-***************
+Stop a container
+****************
 
 
 .. http:post:: /containers/(id)/stop
 .. http:post:: /containers/(id)/stop
 
 

+ 4 - 0
docs/sources/api/docker_remote_api_v1.1.rst

@@ -1,3 +1,7 @@
+.. use orphan to suppress "WARNING: document isn't included in any toctree"
+.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
+
+:orphan:
 
 
 :title: Remote API v1.1
 :title: Remote API v1.1
 :description: API Documentation for Docker
 :description: API Documentation for Docker

+ 5 - 0
docs/sources/api/docker_remote_api_v1.2.rst

@@ -1,3 +1,8 @@
+.. use orphan to suppress "WARNING: document isn't included in any toctree"
+.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
+
+:orphan:
+
 :title: Remote API v1.2
 :title: Remote API v1.2
 :description: API Documentation for Docker
 :description: API Documentation for Docker
 :keywords: API, Docker, rcli, REST, documentation
 :keywords: API, Docker, rcli, REST, documentation

+ 5 - 0
docs/sources/api/docker_remote_api_v1.3.rst

@@ -1,3 +1,8 @@
+.. use orphan to suppress "WARNING: document isn't included in any toctree"
+.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
+
+:orphan:
+
 :title: Remote API v1.3
 :title: Remote API v1.3
 :description: API Documentation for Docker
 :description: API Documentation for Docker
 :keywords: API, Docker, rcli, REST, documentation
 :keywords: API, Docker, rcli, REST, documentation

+ 1 - 1
docs/sources/api/index_api.rst

@@ -452,7 +452,7 @@ User Register
          "username": "foobar"'}
          "username": "foobar"'}
 
 
     :jsonparameter email: valid email address, that needs to be confirmed
     :jsonparameter email: valid email address, that needs to be confirmed
-    :jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
+    :jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9\_].
     :jsonparameter password: min 5 characters
     :jsonparameter password: min 5 characters
 
 
     **Example Response**:
     **Example Response**:

+ 3 - 2
docs/sources/api/registry_index_spec.rst

@@ -367,7 +367,8 @@ POST /v1/users
     {"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'}
     {"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'}
 
 
 **Validation**:
 **Validation**:
-    - **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
+    - **username**: min 4 character, max 30 characters, must match the regular
+      expression [a-z0-9\_].
     - **password**: min 5 characters
     - **password**: min 5 characters
 
 
 **Valid**: return HTTP 200
 **Valid**: return HTTP 200
@@ -566,4 +567,4 @@ Next request::
 ---------------------
 ---------------------
 
 
 - 1.0 : May 6th 2013 : initial release 
 - 1.0 : May 6th 2013 : initial release 
-- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace.
+- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace.

+ 11 - 0
docs/sources/commandline/command/run.rst

@@ -14,6 +14,7 @@
 
 
       -a=map[]: Attach to stdin, stdout or stderr.
       -a=map[]: Attach to stdin, stdout or stderr.
       -c=0: CPU shares (relative weight)
       -c=0: CPU shares (relative weight)
+      -cidfile="": Write the container ID to the file
       -d=false: Detached mode: leave the container running in the background
       -d=false: Detached mode: leave the container running in the background
       -e=[]: Set environment variables
       -e=[]: Set environment variables
       -h="": Container host name
       -h="": Container host name
@@ -26,3 +27,13 @@
       -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume.
       -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume.
       -volumes-from="": Mount all volumes from the given container.
       -volumes-from="": Mount all volumes from the given container.
       -entrypoint="": Overwrite the default entrypoint set by the image.
       -entrypoint="": Overwrite the default entrypoint set by the image.
+
+
+Examples
+--------
+
+.. code-block:: bash
+
+    docker run -cidfile /tmp/docker_test.cid ubuntu echo "test"
+
+| This will create a container and print "test" to the console. The cidfile flag makes docker attempt to create a new file and write the container ID to it. If the file exists already, docker will return an error. Docker will close this file when docker run exits.

+ 3 - 1
docs/sources/commandline/command/stop.rst

@@ -8,6 +8,8 @@
 
 
 ::
 ::
 
 
-    Usage: docker stop [OPTIONS] NAME
+    Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
 
 
     Stop a running container
     Stop a running container
+
+      -t=10: Number of seconds to wait for the container to stop before killing it.

+ 2 - 1
docs/sources/commandline/index.rst

@@ -37,5 +37,6 @@ Contents:
   start   <command/start>
   start   <command/start>
   stop    <command/stop>
   stop    <command/stop>
   tag     <command/tag>
   tag     <command/tag>
+  top     <command/top>
   version <command/version>
   version <command/version>
-  wait    <command/wait>
+  wait    <command/wait>

+ 2 - 0
docs/sources/contributing/devenvironment.rst

@@ -46,11 +46,13 @@ in a standard build environment.
 You can run an interactive session in the newly built container:
 You can run an interactive session in the newly built container:
 
 
 ::
 ::
+
     docker run -i -t docker bash
     docker run -i -t docker bash
 
 
 
 
 To extract the binaries from the container:
 To extract the binaries from the container:
 
 
 ::
 ::
+
     docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build
     docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build
 
 

+ 0 - 2
docs/sources/index.rst

@@ -2,8 +2,6 @@
 :description: An overview of the Docker Documentation
 :description: An overview of the Docker Documentation
 :keywords: containers, lxc, concepts, explanation
 :keywords: containers, lxc, concepts, explanation
 
 
-.. _introduction:
-
 Welcome
 Welcome
 =======
 =======
 
 

+ 24 - 16
docs/sources/use/builder.rst

@@ -1,6 +1,6 @@
-:title: Dockerfile Builder
-:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image.
-:keywords: builder, docker, Docker Builder, automation, image creation
+:title: Dockerfiles for Images
+:description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image.
+:keywords: builder, docker, Dockerfile, automation, image creation
 
 
 ==================
 ==================
 Dockerfile Builder
 Dockerfile Builder
@@ -30,7 +30,7 @@ build succeeds:
 
 
     ``docker build -t shykes/myapp .``
     ``docker build -t shykes/myapp .``
 
 
-Docker will run your steps one-by-one, committing the result if necessary, 
+Docker will run your steps one-by-one, committing the result if necessary,
 before finally outputting the ID of your new image.
 before finally outputting the ID of your new image.
 
 
 2. Format
 2. Format
@@ -43,7 +43,7 @@ The Dockerfile format is quite simple:
     # Comment
     # Comment
     INSTRUCTION arguments
     INSTRUCTION arguments
 
 
-The Instruction is not case-sensitive, however convention is for them to be 
+The Instruction is not case-sensitive, however convention is for them to be
 UPPERCASE in order to distinguish them from arguments more easily.
 UPPERCASE in order to distinguish them from arguments more easily.
 
 
 Docker evaluates the instructions in a Dockerfile in order. **The first
 Docker evaluates the instructions in a Dockerfile in order. **The first
@@ -106,7 +106,7 @@ The ``CMD`` instruction sets the command to be executed when running
 the image.  This is functionally equivalent to running ``docker commit
 the image.  This is functionally equivalent to running ``docker commit
 -run '{"Cmd": <command>}'`` outside the builder.
 -run '{"Cmd": <command>}'`` outside the builder.
 
 
-.. note:: 
+.. note::
     Don't confuse `RUN` with `CMD`. `RUN` actually runs a
     Don't confuse `RUN` with `CMD`. `RUN` actually runs a
     command and commits the result; `CMD` does not execute anything at
     command and commits the result; `CMD` does not execute anything at
     build time, but specifies the intended command for the image.
     build time, but specifies the intended command for the image.
@@ -131,7 +131,7 @@ value ``<value>``. This value will be passed to all future ``RUN``
 instructions. This is functionally equivalent to prefixing the command
 instructions. This is functionally equivalent to prefixing the command
 with ``<key>=<value>``
 with ``<key>=<value>``
 
 
-.. note:: 
+.. note::
     The environment variables will persist when a container is run
     The environment variables will persist when a container is run
     from the resulting image.
     from the resulting image.
 
 
@@ -152,16 +152,24 @@ destination container.
 
 
 The copy obeys the following rules:
 The copy obeys the following rules:
 
 
+* If ``<src>`` is a URL and ``<dest>`` does not end with a trailing slash,
+  then a file is downloaded from the URL and copied to ``<dest>``.
+* If ``<src>`` is a URL and ``<dest>`` does end with a trailing slash,
+  then the filename is inferred from the URL and the file is downloaded to
+  ``<dest>/<filename>``. For instance, ``ADD http://example.com/foobar /``
+  would create the file ``/foobar``. The URL must have a nontrivial path
+  so that an appropriate filename can be discovered in this case
+  (``http://example.com`` will not work).
 * If ``<src>`` is a directory, the entire directory is copied,
 * If ``<src>`` is a directory, the entire directory is copied,
   including filesystem metadata.
   including filesystem metadata.
 * If ``<src>``` is a tar archive in a recognized compression format
 * If ``<src>``` is a tar archive in a recognized compression format
   (identity, gzip, bzip2 or xz), it is unpacked as a directory.
   (identity, gzip, bzip2 or xz), it is unpacked as a directory.
 
 
   When a directory is copied or unpacked, it has the same behavior as
   When a directory is copied or unpacked, it has the same behavior as
-  ``tar -x``: the result is the union of 
+  ``tar -x``: the result is the union of
 
 
   1. whatever existed at the destination path and
   1. whatever existed at the destination path and
-  2. the contents of the source tree, 
+  2. the contents of the source tree,
 
 
   with conflicts resolved in favor of 2) on a file-by-file basis.
   with conflicts resolved in favor of 2) on a file-by-file basis.
 
 
@@ -177,7 +185,7 @@ The copy obeys the following rules:
   with mode 0700, uid and gid 0.
   with mode 0700, uid and gid 0.
 
 
 3.8 ENTRYPOINT
 3.8 ENTRYPOINT
--------------
+--------------
 
 
     ``ENTRYPOINT /bin/echo``
     ``ENTRYPOINT /bin/echo``
 
 
@@ -203,14 +211,14 @@ container created from the image.
     # Nginx
     # Nginx
     #
     #
     # VERSION               0.0.1
     # VERSION               0.0.1
-    
+
     FROM      ubuntu
     FROM      ubuntu
     MAINTAINER Guillaume J. Charmes "guillaume@dotcloud.com"
     MAINTAINER Guillaume J. Charmes "guillaume@dotcloud.com"
-    
+
     # make sure the package repository is up to date
     # make sure the package repository is up to date
     RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
     RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
     RUN apt-get update
     RUN apt-get update
-    
+
     RUN apt-get install -y inotify-tools nginx apache2 openssh-server
     RUN apt-get install -y inotify-tools nginx apache2 openssh-server
 
 
 .. code-block:: bash
 .. code-block:: bash
@@ -218,12 +226,12 @@ container created from the image.
     # Firefox over VNC
     # Firefox over VNC
     #
     #
     # VERSION               0.3
     # VERSION               0.3
-    
+
     FROM ubuntu
     FROM ubuntu
     # make sure the package repository is up to date
     # make sure the package repository is up to date
     RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
     RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
     RUN apt-get update
     RUN apt-get update
-    
+
     # Install vnc, xvfb in order to create a 'fake' display and firefox
     # Install vnc, xvfb in order to create a 'fake' display and firefox
     RUN apt-get install -y x11vnc xvfb firefox
     RUN apt-get install -y x11vnc xvfb firefox
     RUN mkdir /.vnc
     RUN mkdir /.vnc
@@ -231,7 +239,7 @@ container created from the image.
     RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
     RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
     # Autostart firefox (might not be the best way, but it does the trick)
     # Autostart firefox (might not be the best way, but it does the trick)
     RUN bash -c 'echo "firefox" >> /.bashrc'
     RUN bash -c 'echo "firefox" >> /.bashrc'
-    
+
     EXPOSE 5900
     EXPOSE 5900
     CMD    ["x11vnc", "-forever", "-usepw", "-create"]
     CMD    ["x11vnc", "-forever", "-usepw", "-create"]
 
 

+ 1 - 1
docs/sources/use/workingwithrepository.rst

@@ -119,7 +119,7 @@ your container to an image within your username namespace.
 
 
 
 
 Pushing a container to its repository
 Pushing a container to its repository
-------------------------------------
+-------------------------------------
 
 
 In order to push an image to its repository you need to have committed
 In order to push an image to its repository you need to have committed
 your container to a named image (see above)
 your container to a named image (see above)

+ 2 - 2
runtime.go

@@ -167,12 +167,12 @@ func (runtime *Runtime) Register(container *Container) error {
 	return nil
 	return nil
 }
 }
 
 
-func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst string) error {
+func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst, stream string) error {
 	log, err := os.OpenFile(dst, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
 	log, err := os.OpenFile(dst, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	src.AddWriter(log)
+	src.AddWriter(log, stream)
 	return nil
 	return nil
 }
 }
 
 

+ 8 - 64
runtime_test.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"fmt"
 	"github.com/dotcloud/docker/utils"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io"
-	"io/ioutil"
 	"log"
 	"log"
 	"net"
 	"net"
 	"os"
 	"os"
@@ -247,36 +246,13 @@ func TestGet(t *testing.T) {
 	runtime := mkRuntime(t)
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	defer nuke(runtime)
 
 
-	builder := NewBuilder(runtime)
-
-	container1, err := builder.Create(&Config{
-		Image: GetTestImage(runtime).ID,
-		Cmd:   []string{"ls", "-al"},
-	},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container1, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
 	defer runtime.Destroy(container1)
 	defer runtime.Destroy(container1)
 
 
-	container2, err := builder.Create(&Config{
-		Image: GetTestImage(runtime).ID,
-		Cmd:   []string{"ls", "-al"},
-	},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container2, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
 	defer runtime.Destroy(container2)
 	defer runtime.Destroy(container2)
 
 
-	container3, err := builder.Create(&Config{
-		Image: GetTestImage(runtime).ID,
-		Cmd:   []string{"ls", "-al"},
-	},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container3, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
 	defer runtime.Destroy(container3)
 	defer runtime.Destroy(container3)
 
 
 	if runtime.Get(container1.ID) != container1 {
 	if runtime.Get(container1.ID) != container1 {
@@ -431,46 +407,14 @@ func TestAllocateUDPPortLocalhost(t *testing.T) {
 }
 }
 
 
 func TestRestore(t *testing.T) {
 func TestRestore(t *testing.T) {
-
-	root, err := ioutil.TempDir("", "docker-test")
-	if err != nil {
-		t.Fatal(err)
-	}
-	if err := os.Remove(root); err != nil {
-		t.Fatal(err)
-	}
-	if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
-		t.Fatal(err)
-	}
-
-	runtime1, err := NewRuntimeFromDirectory(root, false)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	builder := NewBuilder(runtime1)
-
+	runtime1 := mkRuntime(t)
+	defer nuke(runtime1)
 	// Create a container with one instance of docker
 	// Create a container with one instance of docker
-	container1, err := builder.Create(&Config{
-		Image: GetTestImage(runtime1).ID,
-		Cmd:   []string{"ls", "-al"},
-	},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container1, _, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t)
 	defer runtime1.Destroy(container1)
 	defer runtime1.Destroy(container1)
 
 
 	// Create a second container meant to be killed
 	// Create a second container meant to be killed
-	container2, err := builder.Create(&Config{
-		Image:     GetTestImage(runtime1).ID,
-		Cmd:       []string{"/bin/cat"},
-		OpenStdin: true,
-	},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
+	container2, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t)
 	defer runtime1.Destroy(container2)
 	defer runtime1.Destroy(container2)
 
 
 	// Start the container non blocking
 	// Start the container non blocking
@@ -505,7 +449,7 @@ func TestRestore(t *testing.T) {
 
 
 	// Here are are simulating a docker restart - that is, reloading all containers
 	// Here are are simulating a docker restart - that is, reloading all containers
 	// from scratch
 	// from scratch
-	runtime2, err := NewRuntimeFromDirectory(root, false)
+	runtime2, err := NewRuntimeFromDirectory(runtime1.root, false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}

+ 35 - 13
server.go

@@ -2,6 +2,7 @@ package docker
 
 
 import (
 import (
 	"bufio"
 	"bufio"
+	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"github.com/dotcloud/docker/auth"
 	"github.com/dotcloud/docker/auth"
@@ -1054,20 +1055,41 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
 	}
 	}
 	//logs
 	//logs
 	if logs {
 	if logs {
-		if stdout {
-			cLog, err := container.ReadLog("stdout")
-			if err != nil {
-				utils.Debugf("Error reading logs (stdout): %s", err)
-			} else if _, err := io.Copy(out, cLog); err != nil {
-				utils.Debugf("Error streaming logs (stdout): %s", err)
+		cLog, err := container.ReadLog("json")
+		if err != nil && os.IsNotExist(err) {
+			// Legacy logs
+			utils.Debugf("Old logs format")
+			if stdout {
+				cLog, err := container.ReadLog("stdout")
+				if err != nil {
+					utils.Debugf("Error reading logs (stdout): %s", err)
+				} else if _, err := io.Copy(out, cLog); err != nil {
+					utils.Debugf("Error streaming logs (stdout): %s", err)
+				}
 			}
 			}
-		}
-		if stderr {
-			cLog, err := container.ReadLog("stderr")
-			if err != nil {
-				utils.Debugf("Error reading logs (stderr): %s", err)
-			} else if _, err := io.Copy(out, cLog); err != nil {
-				utils.Debugf("Error streaming logs (stderr): %s", err)
+			if stderr {
+				cLog, err := container.ReadLog("stderr")
+				if err != nil {
+					utils.Debugf("Error reading logs (stderr): %s", err)
+				} else if _, err := io.Copy(out, cLog); err != nil {
+					utils.Debugf("Error streaming logs (stderr): %s", err)
+				}
+			}
+		} else if err != nil {
+			utils.Debugf("Error reading logs (json): %s", err)
+		} else {
+			dec := json.NewDecoder(cLog)
+			for {
+				var l utils.JSONLog
+				if err := dec.Decode(&l); err == io.EOF {
+					break
+				} else if err != nil {
+					utils.Debugf("Error streaming logs: %s", err)
+					break
+				}
+				if (l.Stream == "stdout" && stdout) || (l.Stream == "stderr" && stderr) {
+					fmt.Fprintf(out, "%s", l.Log)
+				}
 			}
 			}
 		}
 		}
 	}
 	}

+ 40 - 16
utils/utils.go

@@ -248,30 +248,54 @@ func (r *bufReader) Close() error {
 
 
 type WriteBroadcaster struct {
 type WriteBroadcaster struct {
 	sync.Mutex
 	sync.Mutex
-	writers map[io.WriteCloser]struct{}
+	buf     *bytes.Buffer
+	writers map[StreamWriter]bool
 }
 }
 
 
-func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser) {
-	w.Lock()
-	w.writers[writer] = struct{}{}
-	w.Unlock()
+type StreamWriter struct {
+	wc     io.WriteCloser
+	stream string
 }
 }
 
 
-// FIXME: Is that function used?
-// FIXME: This relies on the concrete writer type used having equality operator
-func (w *WriteBroadcaster) RemoveWriter(writer io.WriteCloser) {
+func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser, stream string) {
 	w.Lock()
 	w.Lock()
-	delete(w.writers, writer)
+	sw := StreamWriter{wc: writer, stream: stream}
+	w.writers[sw] = true
 	w.Unlock()
 	w.Unlock()
 }
 }
 
 
+type JSONLog struct {
+	Log     string    `json:"log,omitempty"`
+	Stream  string    `json:"stream,omitempty"`
+	Created time.Time `json:"time"`
+}
+
 func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
 func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
 	w.Lock()
 	w.Lock()
 	defer w.Unlock()
 	defer w.Unlock()
-	for writer := range w.writers {
-		if n, err := writer.Write(p); err != nil || n != len(p) {
+	w.buf.Write(p)
+	for sw := range w.writers {
+		lp := p
+		if sw.stream != "" {
+			lp = nil
+			for {
+				line, err := w.buf.ReadString('\n')
+				if err != nil {
+					w.buf.Write([]byte(line))
+					break
+				}
+				b, err := json.Marshal(&JSONLog{Log: line, Stream: sw.stream, Created: time.Now()})
+				if err != nil {
+					// On error, evict the writer
+					delete(w.writers, sw)
+					continue
+				}
+				lp = append(lp, b...)
+			}
+		}
+		if n, err := sw.wc.Write(lp); err != nil || n != len(lp) {
 			// On error, evict the writer
 			// On error, evict the writer
-			delete(w.writers, writer)
+			delete(w.writers, sw)
 		}
 		}
 	}
 	}
 	return len(p), nil
 	return len(p), nil
@@ -280,15 +304,15 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
 func (w *WriteBroadcaster) CloseWriters() error {
 func (w *WriteBroadcaster) CloseWriters() error {
 	w.Lock()
 	w.Lock()
 	defer w.Unlock()
 	defer w.Unlock()
-	for writer := range w.writers {
-		writer.Close()
+	for sw := range w.writers {
+		sw.wc.Close()
 	}
 	}
-	w.writers = make(map[io.WriteCloser]struct{})
+	w.writers = make(map[StreamWriter]bool)
 	return nil
 	return nil
 }
 }
 
 
 func NewWriteBroadcaster() *WriteBroadcaster {
 func NewWriteBroadcaster() *WriteBroadcaster {
-	return &WriteBroadcaster{writers: make(map[io.WriteCloser]struct{})}
+	return &WriteBroadcaster{writers: make(map[StreamWriter]bool), buf: bytes.NewBuffer(nil)}
 }
 }
 
 
 func GetTotalUsedFds() int {
 func GetTotalUsedFds() int {

+ 9 - 22
utils/utils_test.go

@@ -60,9 +60,9 @@ func TestWriteBroadcaster(t *testing.T) {
 
 
 	// Test 1: Both bufferA and bufferB should contain "foo"
 	// Test 1: Both bufferA and bufferB should contain "foo"
 	bufferA := &dummyWriter{}
 	bufferA := &dummyWriter{}
-	writer.AddWriter(bufferA)
+	writer.AddWriter(bufferA, "")
 	bufferB := &dummyWriter{}
 	bufferB := &dummyWriter{}
-	writer.AddWriter(bufferB)
+	writer.AddWriter(bufferB, "")
 	writer.Write([]byte("foo"))
 	writer.Write([]byte("foo"))
 
 
 	if bufferA.String() != "foo" {
 	if bufferA.String() != "foo" {
@@ -76,7 +76,7 @@ func TestWriteBroadcaster(t *testing.T) {
 	// Test2: bufferA and bufferB should contain "foobar",
 	// Test2: bufferA and bufferB should contain "foobar",
 	// while bufferC should only contain "bar"
 	// while bufferC should only contain "bar"
 	bufferC := &dummyWriter{}
 	bufferC := &dummyWriter{}
-	writer.AddWriter(bufferC)
+	writer.AddWriter(bufferC, "")
 	writer.Write([]byte("bar"))
 	writer.Write([]byte("bar"))
 
 
 	if bufferA.String() != "foobar" {
 	if bufferA.String() != "foobar" {
@@ -91,35 +91,22 @@ func TestWriteBroadcaster(t *testing.T) {
 		t.Errorf("Buffer contains %v", bufferC.String())
 		t.Errorf("Buffer contains %v", bufferC.String())
 	}
 	}
 
 
-	// Test3: Test removal
-	writer.RemoveWriter(bufferB)
-	writer.Write([]byte("42"))
-	if bufferA.String() != "foobar42" {
-		t.Errorf("Buffer contains %v", bufferA.String())
-	}
-	if bufferB.String() != "foobar" {
-		t.Errorf("Buffer contains %v", bufferB.String())
-	}
-	if bufferC.String() != "bar42" {
-		t.Errorf("Buffer contains %v", bufferC.String())
-	}
-
-	// Test4: Test eviction on failure
+	// Test3: Test eviction on failure
 	bufferA.failOnWrite = true
 	bufferA.failOnWrite = true
 	writer.Write([]byte("fail"))
 	writer.Write([]byte("fail"))
-	if bufferA.String() != "foobar42" {
+	if bufferA.String() != "foobar" {
 		t.Errorf("Buffer contains %v", bufferA.String())
 		t.Errorf("Buffer contains %v", bufferA.String())
 	}
 	}
-	if bufferC.String() != "bar42fail" {
+	if bufferC.String() != "barfail" {
 		t.Errorf("Buffer contains %v", bufferC.String())
 		t.Errorf("Buffer contains %v", bufferC.String())
 	}
 	}
 	// Even though we reset the flag, no more writes should go in there
 	// Even though we reset the flag, no more writes should go in there
 	bufferA.failOnWrite = false
 	bufferA.failOnWrite = false
 	writer.Write([]byte("test"))
 	writer.Write([]byte("test"))
-	if bufferA.String() != "foobar42" {
+	if bufferA.String() != "foobar" {
 		t.Errorf("Buffer contains %v", bufferA.String())
 		t.Errorf("Buffer contains %v", bufferA.String())
 	}
 	}
-	if bufferC.String() != "bar42failtest" {
+	if bufferC.String() != "barfailtest" {
 		t.Errorf("Buffer contains %v", bufferC.String())
 		t.Errorf("Buffer contains %v", bufferC.String())
 	}
 	}
 
 
@@ -141,7 +128,7 @@ func TestRaceWriteBroadcaster(t *testing.T) {
 	writer := NewWriteBroadcaster()
 	writer := NewWriteBroadcaster()
 	c := make(chan bool)
 	c := make(chan bool)
 	go func() {
 	go func() {
-		writer.AddWriter(devNullCloser(0))
+		writer.AddWriter(devNullCloser(0), "")
 		c <- true
 		c <- true
 	}()
 	}()
 	writer.Write([]byte("hello"))
 	writer.Write([]byte("hello"))

+ 10 - 3
utils_test.go

@@ -84,18 +84,25 @@ func readFile(src string, t *testing.T) (content string) {
 }
 }
 
 
 // Create a test container from the given runtime `r` and run arguments `args`.
 // Create a test container from the given runtime `r` and run arguments `args`.
-// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image.
+// If the image name is "_", (eg. []string{"-i", "-t", "_", "bash"}, it is
+// dynamically replaced by the current test image.
 // The caller is responsible for destroying the container.
 // The caller is responsible for destroying the container.
 // Call t.Fatal() at the first error.
 // Call t.Fatal() at the first error.
 func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig, error) {
 func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig, error) {
 	config, hostConfig, _, err := ParseRun(args, nil)
 	config, hostConfig, _, err := ParseRun(args, nil)
+	defer func() {
+		if err != nil && t != nil {
+			t.Fatal(err)
+		}
+	}()
 	if err != nil {
 	if err != nil {
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
-	config.Image = GetTestImage(r).ID
+	if config.Image == "_" {
+		config.Image = GetTestImage(r).ID
+	}
 	c, err := NewBuilder(r).Create(config)
 	c, err := NewBuilder(r).Create(config)
 	if err != nil {
 	if err != nil {
-		t.Fatal(err)
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
 	return c, hostConfig, nil
 	return c, hostConfig, nil