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
Родитель eb4a0271fb 8ea9ccf3a7
Коммит c81662eae4
26 изменённых файлов: 389 добавлений и 318 удалений

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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