1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge branch 'master' into 1237-improve_docker_top-feature

Conflicts:
	docs/sources/api/docker_remote_api.rst
This commit is contained in:
Victor Vieux 2013-07-22 16:22:11 +00:00
commit c81662eae4
26 changed files with 389 additions and 318 deletions

View file

@ -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)

View file

@ -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 %s 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 {IMAGE}
env FOO BAR env FOO BAR
run [ "$FOO" = "BAR" ] run [ "$FOO" = "BAR" ]
`, `,
nil, nil,
},
{
`
from %s
ENTRYPOINT /bin/echo
CMD Hello world
`,
nil, nil,
}, },
{ {
` `
from %s from {IMAGE}
ENTRYPOINT /bin/echo
CMD Hello world
`,
nil,
nil,
},
{
`
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" {
} }

View file

@ -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 { if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=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 {
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 {

View file

@ -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" expectedLogs := []string{"{\"log\":\"hello\\n\",\"stream\":\"stdout\"", "{\"log\":\"hi there\\n\",\"stream\":\"stdout\""}
if string(output) != expectedLog { for _, expectedLog := range expectedLogs {
t.Fatalf("Unexpected logs: should be '%s', not '%s'\n", expectedLog, output) if !strings.Contains(string(output), expectedLog) {
t.Fatalf("Unexpected logs: should contains '%s', it is not '%s'\n", expectedLog, output)
}
} }
} }
} }

View file

@ -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
} }

View file

@ -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( container, hostConfig, _ := mkContainer(
&Config{ runtime,
Image: GetTestImage(runtime).ID, []string{"_", "/bin/sh", "-c", "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"},
Cmd: []string{"/bin/sh", "-c", t,
"i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"},
},
) )
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( container1, _, _ := mkContainer(runtime, []string{"_", "/bin/rm", "/etc/passwd"}, t)
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/rm", "/etc/passwd"},
},
)
if err != nil {
t.Fatal(err)
}
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( container2, _, _ := mkContainer(runtime, []string{img.ID, "cat", "/etc/passwd"}, t)
&Config{
Image: img.ID,
Cmd: []string{"cat", "/etc/passwd"},
},
)
if err != nil {
t.Fatal(err)
}
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( container3, _, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t)
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"rm", "/bin/httpd"},
},
)
if err != nil {
t.Fatal(err)
}
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)
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
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)
}
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( container2, hostConfig, _ := mkContainer(runtime, []string{img.ID}, t)
&Config{
Image: img.ID,
},
)
if err != nil {
t.Fatal(err)
}
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, hostConfig, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
container1, err := builder.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
},
)
if err != nil {
t.Fatal(err)
}
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, hostConfig, _ := mkContainer(runtime, []string{img.ID, "cat", "/world"}, t)
container2, err := builder.Create(
&Config{
Image: img.ID,
Cmd: []string{"cat", "/world"},
},
)
if err != nil {
t.Fatal(err)
}
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( container, hostConfig, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
&Config{
Image: GetTestImage(runtime).ID,
Memory: 33554432,
CpuShares: 1000,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
)
if err != nil {
t.Fatal(err)
}
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( container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container) defer runtime.Destroy(container)
if container.State.Running { if container.State.Running {

View file

@ -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 - The API tends to be REST, but for some complex commands, like attach
- 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 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 The current verson of the API is 1.3
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 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): .. http:get:: /containers/(id)/top
- List the processes inside a container
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:: .. Warning::
The /build improvements are not reverse-compatible. Pre 1.3 clients will break on /build.
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 The client should send it's authConfig as POST on each call of
.. http:post:: /auth only checks the configuration but doesn't store it on the server /images/(name)/push
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:get:: /auth
.. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged **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.
.. http:post:: /images/<name>/delete
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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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**:

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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>

View file

@ -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

View file

@ -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
======= =======

View file

@ -1,6 +1,6 @@
:title: Dockerfile Builder :title: Dockerfiles for Images
:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image. :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, Docker Builder, automation, image creation :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"]

View file

@ -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)

View file

@ -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
} }

View file

@ -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, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
container1, err := builder.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container1) defer runtime.Destroy(container1)
container2, err := builder.Create(&Config{ container2, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container2) defer runtime.Destroy(container2)
container3, err := builder.Create(&Config{ container3, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
if err != nil {
t.Fatal(err)
}
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) {
runtime1 := mkRuntime(t)
root, err := ioutil.TempDir("", "docker-test") defer nuke(runtime1)
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)
// Create a container with one instance of docker // Create a container with one instance of docker
container1, err := builder.Create(&Config{ container1, _, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t)
Image: GetTestImage(runtime1).ID,
Cmd: []string{"ls", "-al"},
},
)
if err != nil {
t.Fatal(err)
}
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{ container2, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t)
Image: GetTestImage(runtime1).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
)
if err != nil {
t.Fatal(err)
}
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)
} }

View file

@ -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("json")
cLog, err := container.ReadLog("stdout") if err != nil && os.IsNotExist(err) {
if err != nil { // Legacy logs
utils.Debugf("Error reading logs (stdout): %s", err) utils.Debugf("Old logs format")
} else if _, err := io.Copy(out, cLog); err != nil { if stdout {
utils.Debugf("Error streaming logs (stdout): %s", err) 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 {
if stderr { cLog, err := container.ReadLog("stderr")
cLog, err := container.ReadLog("stderr") if err != nil {
if err != nil { utils.Debugf("Error reading logs (stderr): %s", err)
utils.Debugf("Error reading logs (stderr): %s", err) } else if _, err := io.Copy(out, cLog); err != nil {
} else if _, err := io.Copy(out, cLog); err != nil { utils.Debugf("Error streaming logs (stderr): %s", err)
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)
}
} }
} }
} }

View file

@ -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) { type StreamWriter struct {
wc io.WriteCloser
stream string
}
func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser, stream string) {
w.Lock() w.Lock()
w.writers[writer] = struct{}{} sw := StreamWriter{wc: writer, stream: stream}
w.writers[sw] = true
w.Unlock() w.Unlock()
} }
// FIXME: Is that function used? type JSONLog struct {
// FIXME: This relies on the concrete writer type used having equality operator Log string `json:"log,omitempty"`
func (w *WriteBroadcaster) RemoveWriter(writer io.WriteCloser) { Stream string `json:"stream,omitempty"`
w.Lock() Created time.Time `json:"time"`
delete(w.writers, writer)
w.Unlock()
} }
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 { w.buf.Write(p)
if n, err := writer.Write(p); err != nil || n != len(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 { for sw := range w.writers {
writer.Close() 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 {

View file

@ -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 // Test3: Test eviction on failure
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
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"))

View file

@ -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