зеркало из https://github.com/microsoft/docker.git
Merge branch 'master' into 1237-improve_docker_top-feature
Conflicts: docs/sources/api/docker_remote_api.rst
This commit is contained in:
Коммит
c81662eae4
21
buildfile.go
21
buildfile.go
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
|
@ -201,6 +202,24 @@ func (b *buildFile) addRemote(container *Container, orig, dest string) error {
|
|||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -208,7 +227,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
|
|||
origPath := path.Join(b.context, orig)
|
||||
destPath := path.Join(container.RootfsPath(), dest)
|
||||
// Preserve the trailing '/'
|
||||
if dest[len(dest)-1] == '/' {
|
||||
if strings.HasSuffix(dest, "/") {
|
||||
destPath = destPath + "/"
|
||||
}
|
||||
fi, err := os.Stat(origPath)
|
||||
|
|
|
@ -3,13 +3,17 @@ package docker
|
|||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// mkTestContext generates a build context from the contents of the provided dockerfile.
|
||||
// This context is suitable for use as an argument to BuildFile.Build()
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -22,6 +26,8 @@ type testContextTemplate struct {
|
|||
dockerfile string
|
||||
// Additional files in the context, eg [][2]string{"./passwd", "gordon"}
|
||||
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.
|
||||
|
@ -29,27 +35,31 @@ type testContextTemplate struct {
|
|||
var testContexts = []testContextTemplate{
|
||||
{
|
||||
`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
run sh -c 'echo root:testpass > /tmp/passwd'
|
||||
run mkdir -p /var/run/sshd
|
||||
run [ "$(cat /tmp/passwd)" = "root:testpass" ]
|
||||
run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
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 /
|
||||
run [ "$(cat /f)" = "hello" ]
|
||||
add f /abc
|
||||
|
@ -71,38 +81,86 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
|
|||
{"f", "hello"},
|
||||
{"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
|
||||
run [ "$FOO" = "BAR" ]
|
||||
`,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from %s
|
||||
ENTRYPOINT /bin/echo
|
||||
CMD Hello world
|
||||
`,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
ENTRYPOINT /bin/echo
|
||||
CMD Hello world
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
VOLUME /test
|
||||
CMD Hello world
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
// 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) {
|
||||
for _, ctx := range testContexts {
|
||||
buildImage(ctx, t)
|
||||
|
@ -121,9 +179,24 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
|
|||
pullingPool: 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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -137,10 +210,10 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
|
|||
|
||||
func TestVolume(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
volume /test
|
||||
cmd Hello world
|
||||
`, nil}, t)
|
||||
`, nil, nil}, t)
|
||||
|
||||
if len(img.Config.Volumes) == 0 {
|
||||
t.Fail()
|
||||
|
@ -154,9 +227,9 @@ func TestVolume(t *testing.T) {
|
|||
|
||||
func TestBuildMaintainer(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
maintainer dockerio
|
||||
`, nil}, t)
|
||||
`, nil, nil}, t)
|
||||
|
||||
if img.Author != "dockerio" {
|
||||
t.Fail()
|
||||
|
@ -165,10 +238,10 @@ func TestBuildMaintainer(t *testing.T) {
|
|||
|
||||
func TestBuildEnv(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
env port 4243
|
||||
`,
|
||||
nil}, t)
|
||||
nil, nil}, t)
|
||||
|
||||
if img.Config.Env[0] != "port=4243" {
|
||||
t.Fail()
|
||||
|
@ -177,10 +250,10 @@ func TestBuildEnv(t *testing.T) {
|
|||
|
||||
func TestBuildCmd(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
cmd ["/bin/echo", "Hello World"]
|
||||
`,
|
||||
nil}, t)
|
||||
nil, nil}, t)
|
||||
|
||||
if img.Config.Cmd[0] != "/bin/echo" {
|
||||
t.Log(img.Config.Cmd[0])
|
||||
|
@ -194,10 +267,10 @@ func TestBuildCmd(t *testing.T) {
|
|||
|
||||
func TestBuildExpose(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
expose 4243
|
||||
`,
|
||||
nil}, t)
|
||||
nil, nil}, t)
|
||||
|
||||
if img.Config.PortSpecs[0] != "4243" {
|
||||
t.Fail()
|
||||
|
@ -206,10 +279,10 @@ func TestBuildExpose(t *testing.T) {
|
|||
|
||||
func TestBuildEntrypoint(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
entrypoint ["/bin/echo"]
|
||||
`,
|
||||
nil}, t)
|
||||
nil, nil}, t)
|
||||
|
||||
if img.Config.Entrypoint[0] != "/bin/echo" {
|
||||
}
|
||||
|
|
24
commands.go
24
commands.go
|
@ -475,7 +475,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||
|
||||
func (cli *DockerCli) CmdStop(args ...string) error {
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
|
@ -1110,10 +1110,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
|||
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 nil
|
||||
|
@ -1316,6 +1313,18 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||
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
|
||||
body, statusCode, err := cli.call("POST", "/containers/create", config)
|
||||
//if image not found try to pull it
|
||||
|
@ -1346,6 +1355,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||
for _, warning := range runResult.Warnings {
|
||||
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
|
||||
if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
|
||||
|
|
|
@ -59,7 +59,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
|
|||
return nil
|
||||
}
|
||||
|
||||
|
||||
// TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
|
||||
func TestRunHostname(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
@ -91,7 +90,6 @@ func TestRunHostname(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
|
||||
// TestAttachStdin checks attaching to stdin without stdout and stderr.
|
||||
// 'docker run -i -a stdin' should sends the client's stdin to the command,
|
||||
// then detach from it and print the container id.
|
||||
|
@ -144,15 +142,17 @@ func TestRunAttachStdin(t *testing.T) {
|
|||
})
|
||||
|
||||
// Check logs
|
||||
if cmdLogs, err := container.ReadLog("stdout"); err != nil {
|
||||
if cmdLogs, err := container.ReadLog("json"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if output, err := ioutil.ReadAll(cmdLogs); err != nil {
|
||||
t.Fatal(err)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
17
container.go
17
container.go
|
@ -80,7 +80,8 @@ type Config struct {
|
|||
}
|
||||
|
||||
type HostConfig struct {
|
||||
Binds []string
|
||||
Binds []string
|
||||
ContainerIDFile string
|
||||
}
|
||||
|
||||
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")
|
||||
if len(args) > 0 && args[0] != "--help" {
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.Usage = nil
|
||||
}
|
||||
|
||||
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")
|
||||
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
|
||||
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 {
|
||||
//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,
|
||||
}
|
||||
hostConfig := &HostConfig{
|
||||
Binds: binds,
|
||||
Binds: binds,
|
||||
ContainerIDFile: *flContainerIDFile,
|
||||
}
|
||||
|
||||
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
|
||||
|
@ -637,6 +641,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|||
params = append(params,
|
||||
"-e", "HOME=/",
|
||||
"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"-e", "container=lxc",
|
||||
)
|
||||
|
||||
for _, elem := range container.Config.Env {
|
||||
|
@ -650,10 +655,10 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|||
container.cmd = exec.Command("lxc-start", params...)
|
||||
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -712,13 +717,13 @@ func (container *Container) StdinPipe() (io.WriteCloser, error) {
|
|||
|
||||
func (container *Container) StdoutPipe() (io.ReadCloser, error) {
|
||||
reader, writer := io.Pipe()
|
||||
container.stdout.AddWriter(writer)
|
||||
container.stdout.AddWriter(writer, "")
|
||||
return utils.NewBufReader(reader), nil
|
||||
}
|
||||
|
||||
func (container *Container) StderrPipe() (io.ReadCloser, error) {
|
||||
reader, writer := io.Pipe()
|
||||
container.stderr.AddWriter(writer)
|
||||
container.stderr.AddWriter(writer, "")
|
||||
return utils.NewBufReader(reader), nil
|
||||
}
|
||||
|
||||
|
|
|
@ -39,16 +39,11 @@ func TestIDFormat(t *testing.T) {
|
|||
func TestMultipleAttachRestart(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
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)
|
||||
|
||||
// Simulate 3 client attaching to the container and stop/restart
|
||||
|
@ -65,7 +60,6 @@ func TestMultipleAttachRestart(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -140,19 +134,8 @@ func TestMultipleAttachRestart(t *testing.T) {
|
|||
func TestDiff(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
|
||||
// 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)
|
||||
|
||||
if err := container1.Run(); err != nil {
|
||||
|
@ -185,15 +168,7 @@ func TestDiff(t *testing.T) {
|
|||
}
|
||||
|
||||
// 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)
|
||||
|
||||
if err := container2.Run(); err != nil {
|
||||
|
@ -212,15 +187,7 @@ func TestDiff(t *testing.T) {
|
|||
}
|
||||
|
||||
// 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)
|
||||
|
||||
if err := container3.Run(); err != nil {
|
||||
|
@ -246,17 +213,7 @@ func TestDiff(t *testing.T) {
|
|||
func TestCommitAutoRun(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
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)
|
||||
|
||||
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
|
||||
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)
|
||||
stdout, err := container2.StdoutPipe()
|
||||
if err != nil {
|
||||
|
@ -296,7 +246,6 @@ func TestCommitAutoRun(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container2.Start(hostConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -324,17 +273,7 @@ func TestCommitRun(t *testing.T) {
|
|||
runtime := mkRuntime(t)
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
stdout, err := container2.StdoutPipe()
|
||||
if err != nil {
|
||||
|
@ -376,7 +306,6 @@ func TestCommitRun(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container2.Start(hostConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -403,18 +332,7 @@ func TestCommitRun(t *testing.T) {
|
|||
func TestStart(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
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)
|
||||
|
||||
cStdin, err := container.StdinPipe()
|
||||
|
@ -422,7 +340,6 @@ func TestStart(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -445,15 +362,7 @@ func TestStart(t *testing.T) {
|
|||
func TestRun(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
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)
|
||||
|
||||
if container.State.Running {
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
|
||||
.. COMMENT use http://pythonhosted.org/sphinxcontrib-httpdomain/ to
|
||||
.. document the REST API.
|
||||
|
||||
=================
|
||||
Docker Remote API
|
||||
=================
|
||||
|
@ -13,15 +16,23 @@ Docker Remote API
|
|||
|
||||
- The Remote API is replacing rcli
|
||||
- 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
|
||||
===========
|
||||
|
||||
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`
|
||||
*****************************
|
||||
|
@ -29,9 +40,9 @@ You can still call an old version of the api using /v1.0/images/<name>/insert
|
|||
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`
|
||||
*****************************
|
||||
|
@ -41,19 +52,21 @@ docker v0.5.0 51f6c4a_
|
|||
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):
|
||||
|
||||
- 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
|
||||
|
||||
.. 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):
|
||||
|
||||
|
@ -61,7 +74,8 @@ List containers (/containers/json):
|
|||
|
||||
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`
|
||||
*****************************
|
||||
|
@ -72,14 +86,25 @@ What's new
|
|||
----------
|
||||
|
||||
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
|
||||
|
||||
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`
|
||||
|
@ -94,7 +119,7 @@ What's new
|
|||
.. http:post:: /images/(name)/insert
|
||||
.. 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
|
||||
|
||||
|
|
|
@ -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
|
||||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
|
@ -300,8 +305,8 @@ Start a container
|
|||
:statuscode 500: server error
|
||||
|
||||
|
||||
Stop a contaier
|
||||
***************
|
||||
Stop a container
|
||||
****************
|
||||
|
||||
.. http:post:: /containers/(id)/stop
|
||||
|
||||
|
|
|
@ -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
|
||||
:description: API Documentation for Docker
|
||||
|
|
|
@ -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
|
||||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
|
|
|
@ -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
|
||||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
|
|
|
@ -452,7 +452,7 @@ User Register
|
|||
"username": "foobar"'}
|
||||
|
||||
: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
|
||||
|
||||
**Example Response**:
|
||||
|
|
|
@ -367,7 +367,8 @@ POST /v1/users
|
|||
{"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'}
|
||||
|
||||
**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
|
||||
|
||||
**Valid**: return HTTP 200
|
||||
|
@ -566,4 +567,4 @@ Next request::
|
|||
---------------------
|
||||
|
||||
- 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.
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
-a=map[]: Attach to stdin, stdout or stderr.
|
||||
-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
|
||||
-e=[]: Set environment variables
|
||||
-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.
|
||||
-volumes-from="": Mount all volumes from the given container.
|
||||
-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.
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
::
|
||||
|
||||
Usage: docker stop [OPTIONS] NAME
|
||||
Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
||||
|
||||
Stop a running container
|
||||
|
||||
-t=10: Number of seconds to wait for the container to stop before killing it.
|
||||
|
|
|
@ -37,5 +37,6 @@ Contents:
|
|||
start <command/start>
|
||||
stop <command/stop>
|
||||
tag <command/tag>
|
||||
top <command/top>
|
||||
version <command/version>
|
||||
wait <command/wait>
|
||||
wait <command/wait>
|
||||
|
|
|
@ -46,11 +46,13 @@ in a standard build environment.
|
|||
You can run an interactive session in the newly built container:
|
||||
|
||||
::
|
||||
|
||||
docker run -i -t docker bash
|
||||
|
||||
|
||||
To extract the binaries from the container:
|
||||
|
||||
::
|
||||
|
||||
docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
:description: An overview of the Docker Documentation
|
||||
:keywords: containers, lxc, concepts, explanation
|
||||
|
||||
.. _introduction:
|
||||
|
||||
Welcome
|
||||
=======
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -30,7 +30,7 @@ build succeeds:
|
|||
|
||||
``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.
|
||||
|
||||
2. Format
|
||||
|
@ -43,7 +43,7 @@ The Dockerfile format is quite simple:
|
|||
# Comment
|
||||
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.
|
||||
|
||||
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
|
||||
-run '{"Cmd": <command>}'`` outside the builder.
|
||||
|
||||
.. note::
|
||||
.. note::
|
||||
Don't confuse `RUN` with `CMD`. `RUN` actually runs a
|
||||
command and commits the result; `CMD` does not execute anything at
|
||||
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
|
||||
with ``<key>=<value>``
|
||||
|
||||
.. note::
|
||||
.. note::
|
||||
The environment variables will persist when a container is run
|
||||
from the resulting image.
|
||||
|
||||
|
@ -152,16 +152,24 @@ destination container.
|
|||
|
||||
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,
|
||||
including filesystem metadata.
|
||||
* If ``<src>``` is a tar archive in a recognized compression format
|
||||
(identity, gzip, bzip2 or xz), it is unpacked as a directory.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
|
@ -177,7 +185,7 @@ The copy obeys the following rules:
|
|||
with mode 0700, uid and gid 0.
|
||||
|
||||
3.8 ENTRYPOINT
|
||||
-------------
|
||||
--------------
|
||||
|
||||
``ENTRYPOINT /bin/echo``
|
||||
|
||||
|
@ -203,14 +211,14 @@ container created from the image.
|
|||
# Nginx
|
||||
#
|
||||
# VERSION 0.0.1
|
||||
|
||||
|
||||
FROM ubuntu
|
||||
MAINTAINER Guillaume J. Charmes "guillaume@dotcloud.com"
|
||||
|
||||
|
||||
# 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 apt-get update
|
||||
|
||||
|
||||
RUN apt-get install -y inotify-tools nginx apache2 openssh-server
|
||||
|
||||
.. code-block:: bash
|
||||
|
@ -218,12 +226,12 @@ container created from the image.
|
|||
# Firefox over VNC
|
||||
#
|
||||
# VERSION 0.3
|
||||
|
||||
|
||||
FROM ubuntu
|
||||
# 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 apt-get update
|
||||
|
||||
|
||||
# Install vnc, xvfb in order to create a 'fake' display and firefox
|
||||
RUN apt-get install -y x11vnc xvfb firefox
|
||||
RUN mkdir /.vnc
|
||||
|
@ -231,7 +239,7 @@ container created from the image.
|
|||
RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
|
||||
# Autostart firefox (might not be the best way, but it does the trick)
|
||||
RUN bash -c 'echo "firefox" >> /.bashrc'
|
||||
|
||||
|
||||
EXPOSE 5900
|
||||
CMD ["x11vnc", "-forever", "-usepw", "-create"]
|
||||
|
||||
|
|
|
@ -119,7 +119,7 @@ your container to an image within your username namespace.
|
|||
|
||||
|
||||
Pushing a container to its repository
|
||||
------------------------------------
|
||||
-------------------------------------
|
||||
|
||||
In order to push an image to its repository you need to have committed
|
||||
your container to a named image (see above)
|
||||
|
|
|
@ -167,12 +167,12 @@ func (runtime *Runtime) Register(container *Container) error {
|
|||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
src.AddWriter(log)
|
||||
src.AddWriter(log, stream)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
@ -247,36 +246,13 @@ func TestGet(t *testing.T) {
|
|||
runtime := mkRuntime(t)
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
if runtime.Get(container1.ID) != container1 {
|
||||
|
@ -431,46 +407,14 @@ func TestAllocateUDPPortLocalhost(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
|
||||
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)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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
|
||||
// from scratch
|
||||
runtime2, err := NewRuntimeFromDirectory(root, false)
|
||||
runtime2, err := NewRuntimeFromDirectory(runtime1.root, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
48
server.go
48
server.go
|
@ -2,6 +2,7 @@ package docker
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
|
@ -1054,20 +1055,41 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
|
|||
}
|
||||
//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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -248,30 +248,54 @@ func (r *bufReader) Close() error {
|
|||
|
||||
type WriteBroadcaster struct {
|
||||
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.writers[writer] = struct{}{}
|
||||
sw := StreamWriter{wc: writer, stream: stream}
|
||||
w.writers[sw] = true
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
// FIXME: Is that function used?
|
||||
// FIXME: This relies on the concrete writer type used having equality operator
|
||||
func (w *WriteBroadcaster) RemoveWriter(writer io.WriteCloser) {
|
||||
w.Lock()
|
||||
delete(w.writers, writer)
|
||||
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) {
|
||||
w.Lock()
|
||||
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
|
||||
delete(w.writers, writer)
|
||||
delete(w.writers, sw)
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
|
@ -280,15 +304,15 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
|
|||
func (w *WriteBroadcaster) CloseWriters() error {
|
||||
w.Lock()
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -60,9 +60,9 @@ func TestWriteBroadcaster(t *testing.T) {
|
|||
|
||||
// Test 1: Both bufferA and bufferB should contain "foo"
|
||||
bufferA := &dummyWriter{}
|
||||
writer.AddWriter(bufferA)
|
||||
writer.AddWriter(bufferA, "")
|
||||
bufferB := &dummyWriter{}
|
||||
writer.AddWriter(bufferB)
|
||||
writer.AddWriter(bufferB, "")
|
||||
writer.Write([]byte("foo"))
|
||||
|
||||
if bufferA.String() != "foo" {
|
||||
|
@ -76,7 +76,7 @@ func TestWriteBroadcaster(t *testing.T) {
|
|||
// Test2: bufferA and bufferB should contain "foobar",
|
||||
// while bufferC should only contain "bar"
|
||||
bufferC := &dummyWriter{}
|
||||
writer.AddWriter(bufferC)
|
||||
writer.AddWriter(bufferC, "")
|
||||
writer.Write([]byte("bar"))
|
||||
|
||||
if bufferA.String() != "foobar" {
|
||||
|
@ -91,35 +91,22 @@ func TestWriteBroadcaster(t *testing.T) {
|
|||
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
|
||||
writer.Write([]byte("fail"))
|
||||
if bufferA.String() != "foobar42" {
|
||||
if bufferA.String() != "foobar" {
|
||||
t.Errorf("Buffer contains %v", bufferA.String())
|
||||
}
|
||||
if bufferC.String() != "bar42fail" {
|
||||
if bufferC.String() != "barfail" {
|
||||
t.Errorf("Buffer contains %v", bufferC.String())
|
||||
}
|
||||
// Even though we reset the flag, no more writes should go in there
|
||||
bufferA.failOnWrite = false
|
||||
writer.Write([]byte("test"))
|
||||
if bufferA.String() != "foobar42" {
|
||||
if bufferA.String() != "foobar" {
|
||||
t.Errorf("Buffer contains %v", bufferA.String())
|
||||
}
|
||||
if bufferC.String() != "bar42failtest" {
|
||||
if bufferC.String() != "barfailtest" {
|
||||
t.Errorf("Buffer contains %v", bufferC.String())
|
||||
}
|
||||
|
||||
|
@ -141,7 +128,7 @@ func TestRaceWriteBroadcaster(t *testing.T) {
|
|||
writer := NewWriteBroadcaster()
|
||||
c := make(chan bool)
|
||||
go func() {
|
||||
writer.AddWriter(devNullCloser(0))
|
||||
writer.AddWriter(devNullCloser(0), "")
|
||||
c <- true
|
||||
}()
|
||||
writer.Write([]byte("hello"))
|
||||
|
|
|
@ -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`.
|
||||
// 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.
|
||||
// Call t.Fatal() at the first error.
|
||||
func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig, error) {
|
||||
config, hostConfig, _, err := ParseRun(args, nil)
|
||||
defer func() {
|
||||
if err != nil && t != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
config.Image = GetTestImage(r).ID
|
||||
if config.Image == "_" {
|
||||
config.Image = GetTestImage(r).ID
|
||||
}
|
||||
c, err := NewBuilder(r).Create(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
return c, hostConfig, nil
|
||||
|
|
Загрузка…
Ссылка в новой задаче