From 4388bef996063b3b69e738082b6820d3a979921e Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Fri, 5 Jul 2013 16:49:55 -0700 Subject: [PATCH 01/36] testing, issue #1104: Make the test use static flags --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 9b06df3d64..46d003d878 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ BUILD_DIR := $(CURDIR)/.gopath GOPATH ?= $(BUILD_DIR) export GOPATH -GO_OPTIONS ?= +GO_OPTIONS ?= -a -ldflags='-w -d' ifeq ($(VERBOSE), 1) GO_OPTIONS += -v endif @@ -79,10 +79,10 @@ test: tar --exclude=${BUILD_SRC} -cz . | tar -xz -C ${BUILD_PATH} GOPATH=${CURDIR}/${BUILD_SRC} go get -d # Do the test - sudo -E GOPATH=${CURDIR}/${BUILD_SRC} go test ${GO_OPTIONS} + sudo -E GOPATH=${CURDIR}/${BUILD_SRC} CGO_ENABLED=0 go test ${GO_OPTIONS} testall: all - @(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS)) + @(cd $(DOCKER_DIR); CGO_ENABLED=0 sudo -E go test ./... $(GO_OPTIONS)) fmt: @gofmt -s -l -w . From 54f9cdb0c30ef7d192c4ae59c669a7a5fd7d1003 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Thu, 18 Jul 2013 19:04:51 -0700 Subject: [PATCH 02/36] Make docs build without warnings or errors. Minor additional cleanup. --- docs/sources/api/docker_remote_api.rst | 61 +++++++++++++++------ docs/sources/api/docker_remote_api_v1.0.rst | 9 ++- docs/sources/api/docker_remote_api_v1.1.rst | 4 ++ docs/sources/api/docker_remote_api_v1.2.rst | 5 ++ docs/sources/api/docker_remote_api_v1.3.rst | 5 ++ docs/sources/api/index_api.rst | 2 +- docs/sources/api/registry_index_spec.rst | 5 +- docs/sources/index.rst | 2 - docs/sources/use/builder.rst | 8 +-- docs/sources/use/workingwithrepository.rst | 2 +- 10 files changed, 73 insertions(+), 30 deletions(-) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 183347c23b..f8a1381c53 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -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.3 -Calling /images//insert is the same as calling /v1.3/images//insert -You can still call an old version of the api using /v1.0/images//insert +The current verson of the API is 1.3 + +Calling /images//insert is the same as calling +/v1.3/images//insert + +You can still call an old version of the api using +/v1.0/images//insert :doc:`docker_remote_api_v1.3` ***************************** @@ -29,19 +40,21 @@ You can still call an old version of the api using /v1.0/images//insert What's new ---------- -Listing processes (/top): - -- List the processes inside a container +.. http:get:: /containers/(id)/top + **New!** 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): @@ -49,7 +62,8 @@ List containers (/containers/json): Start containers (/containers//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` ***************************** @@ -60,14 +74,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//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//delete + + Now returns a JSON structure with the list of images + deleted/untagged. :doc:`docker_remote_api_v1.1` @@ -82,7 +107,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 diff --git a/docs/sources/api/docker_remote_api_v1.0.rst b/docs/sources/api/docker_remote_api_v1.0.rst index a789337093..5aa98cbe59 100644 --- a/docs/sources/api/docker_remote_api_v1.0.rst +++ b/docs/sources/api/docker_remote_api_v1.0.rst @@ -1,3 +1,8 @@ +.. use orphan to suppress "WARNING: document isn't included in any toctree" +.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata + +:orphan: + :title: Remote API v1.0 :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 diff --git a/docs/sources/api/docker_remote_api_v1.1.rst b/docs/sources/api/docker_remote_api_v1.1.rst index 3e0ef34eba..e0159ddb65 100644 --- a/docs/sources/api/docker_remote_api_v1.1.rst +++ b/docs/sources/api/docker_remote_api_v1.1.rst @@ -1,3 +1,7 @@ +.. use orphan to suppress "WARNING: document isn't included in any toctree" +.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata + +:orphan: :title: Remote API v1.1 :description: API Documentation for Docker diff --git a/docs/sources/api/docker_remote_api_v1.2.rst b/docs/sources/api/docker_remote_api_v1.2.rst index a6c2c31920..96ee6bb9bb 100644 --- a/docs/sources/api/docker_remote_api_v1.2.rst +++ b/docs/sources/api/docker_remote_api_v1.2.rst @@ -1,3 +1,8 @@ +.. use orphan to suppress "WARNING: document isn't included in any toctree" +.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata + +:orphan: + :title: Remote API v1.2 :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index 9f33365e81..273ec2e98d 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -1,3 +1,8 @@ +.. use orphan to suppress "WARNING: document isn't included in any toctree" +.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata + +:orphan: + :title: Remote API v1.3 :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation diff --git a/docs/sources/api/index_api.rst b/docs/sources/api/index_api.rst index 42dc49a5d7..1d4f475caf 100644 --- a/docs/sources/api/index_api.rst +++ b/docs/sources/api/index_api.rst @@ -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**: diff --git a/docs/sources/api/registry_index_spec.rst b/docs/sources/api/registry_index_spec.rst index c1854194b2..3ae39e37d9 100644 --- a/docs/sources/api/registry_index_spec.rst +++ b/docs/sources/api/registry_index_spec.rst @@ -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. \ No newline at end of file +- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace. diff --git a/docs/sources/index.rst b/docs/sources/index.rst index 05e69dd8e5..ba8f60c3fa 100644 --- a/docs/sources/index.rst +++ b/docs/sources/index.rst @@ -2,8 +2,6 @@ :description: An overview of the Docker Documentation :keywords: containers, lxc, concepts, explanation -.. _introduction: - Welcome ======= diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 9ea8033b98..7f370609c8 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -1,6 +1,6 @@ -:title: Dockerfile Builder -:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image. -:keywords: builder, docker, Docker Builder, automation, image creation +:title: Dockerfiles for Images +:description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image. +:keywords: builder, docker, Dockerfile, automation, image creation ================== Dockerfile Builder @@ -177,7 +177,7 @@ The copy obeys the following rules: with mode 0700, uid and gid 0. 3.8 ENTRYPOINT -------------- +-------------- ``ENTRYPOINT /bin/echo`` diff --git a/docs/sources/use/workingwithrepository.rst b/docs/sources/use/workingwithrepository.rst index 3cdbfe49d6..4a2e39aea1 100644 --- a/docs/sources/use/workingwithrepository.rst +++ b/docs/sources/use/workingwithrepository.rst @@ -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) From a0eec14c7da5b213302c2675801aaf788e84efed Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 02:47:35 +0000 Subject: [PATCH 03/36] fix overwrites EXPOSE --- utils.go | 2 +- utils_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/utils.go b/utils.go index 33cfbe506f..16efccd387 100644 --- a/utils.go +++ b/utils.go @@ -78,7 +78,7 @@ func MergeConfig(userConf, imageConf *Config) { imageNat, _ := parseNat(imagePortSpec) for _, userPortSpec := range userConf.PortSpecs { userNat, _ := parseNat(userPortSpec) - if imageNat.Proto == userNat.Proto && imageNat.Frontend == userNat.Frontend { + if imageNat.Proto == userNat.Proto && imageNat.Backend == userNat.Backend { found = true } } diff --git a/utils_test.go b/utils_test.go index 1bd18f4af0..5ea3682bac 100644 --- a/utils_test.go +++ b/utils_test.go @@ -148,7 +148,7 @@ func TestMergeConfig(t *testing.T) { volumesUser["/test3"] = struct{}{} configUser := &Config{ Dns: []string{"3.3.3.3"}, - PortSpecs: []string{"2222:3333", "3333:3333"}, + PortSpecs: []string{"3333:2222", "3333:3333"}, Env: []string{"VAR2=3", "VAR3=3"}, Volumes: volumesUser, } @@ -165,11 +165,11 @@ func TestMergeConfig(t *testing.T) { } if len(configUser.PortSpecs) != 3 { - t.Fatalf("Expected 3 portSpecs, 1111:1111, 2222:3333 and 3333:3333, found %d", len(configUser.PortSpecs)) + t.Fatalf("Expected 3 portSpecs, 1111:1111, 3333:2222 and 3333:3333, found %d", len(configUser.PortSpecs)) } for _, portSpecs := range configUser.PortSpecs { - if portSpecs != "1111:1111" && portSpecs != "2222:3333" && portSpecs != "3333:3333" { - t.Fatalf("Expected 1111:1111 or 2222:3333 or 3333:3333, found %s", portSpecs) + if portSpecs != "1111:1111" && portSpecs != "3333:2222" && portSpecs != "3333:3333" { + t.Fatalf("Expected 1111:1111 or 3333:2222 or 3333:3333, found %s", portSpecs) } } if len(configUser.Env) != 3 { From 2b5386f039b5c99cf0f64fb3091cc14e4446dc64 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 03:01:39 +0000 Subject: [PATCH 04/36] add regression test from @crosbymichael --- utils_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/utils_test.go b/utils_test.go index 5ea3682bac..78f3afa66c 100644 --- a/utils_test.go +++ b/utils_test.go @@ -190,3 +190,45 @@ func TestMergeConfig(t *testing.T) { } } } + +func TestMergeConfigPublicPortNotHonored(t *testing.T) { + volumesImage := make(map[string]struct{}) + volumesImage["/test1"] = struct{}{} + volumesImage["/test2"] = struct{}{} + configImage := &Config{ + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"1111", "2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + Volumes: volumesImage, + } + + volumesUser := make(map[string]struct{}) + volumesUser["/test3"] = struct{}{} + configUser := &Config{ + Dns: []string{"3.3.3.3"}, + PortSpecs: []string{"1111:3333"}, + Env: []string{"VAR2=3", "VAR3=3"}, + Volumes: volumesUser, + } + + MergeConfig(configUser, configImage) + + contains := func(a []string, expect string) bool { + for _, p := range a { + if p == expect { + return true + } + } + return false + } + + if !contains(configUser.PortSpecs, "2222") { + t.Logf("Expected '2222' Ports: %v", configUser.PortSpecs) + t.Fail() + } + + if !contains(configUser.PortSpecs, "1111:3333") { + t.Logf("Expected '1111:3333' Ports: %v", configUser.PortSpecs) + t.Fail() + } +} From e8ad82f9ba126414e1813fadfb17167a34afa8d4 Mon Sep 17 00:00:00 2001 From: Ryan Fowler Date: Fri, 19 Jul 2013 10:11:21 -0500 Subject: [PATCH 05/36] Make the ENTRYPOINT example work The incantation listed in the ENTRYPOINT example didn't actually pass the arguments to your script. Changing the definition to an array fixes this. --- docs/sources/use/builder.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 9ea8033b98..3f7a8e8ef1 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -179,7 +179,7 @@ The copy obeys the following rules: 3.8 ENTRYPOINT ------------- - ``ENTRYPOINT /bin/echo`` + ``ENTRYPOINT ["/bin/echo"]`` The ``ENTRYPOINT`` instruction adds an entry command that will not be overwritten when arguments are passed to docker run, unlike the From ea1258852493a83754553163db1db52a72ffb8fc Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 15:56:00 +0000 Subject: [PATCH 06/36] remove usage from tests --- container.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container.go b/container.go index d0a0dd7714..f18aa0fe74 100644 --- a/container.go +++ b/container.go @@ -94,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") From 67f1e3f5ed4d3061e07e910e28ac866b7bb13e18 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 17:22:16 +0000 Subject: [PATCH 07/36] add container=lxc in default env --- container.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container.go b/container.go index d0a0dd7714..f472b199ea 100644 --- a/container.go +++ b/container.go @@ -640,6 +640,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 { From 32663bf431b64d1169509bacba36e3c90e131b44 Mon Sep 17 00:00:00 2001 From: dsissitka Date: Sat, 20 Jul 2013 21:27:55 -0400 Subject: [PATCH 08/36] Fixed a couple of minor syntax errors. --- docs/sources/contributing/devenvironment.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/contributing/devenvironment.rst b/docs/sources/contributing/devenvironment.rst index 6f5d6c1dc1..869cc43749 100644 --- a/docs/sources/contributing/devenvironment.rst +++ b/docs/sources/contributing/devenvironment.rst @@ -46,11 +46,13 @@ in a standard build environment. You can run an interactive session in the newly built container: :: + 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 From 788935175e8500b451a844f6971ac62dd099bdfc Mon Sep 17 00:00:00 2001 From: dsissitka Date: Sun, 21 Jul 2013 18:30:51 -0400 Subject: [PATCH 09/36] Added top to the list of commands in the sidebar. --- docs/sources/commandline/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/commandline/index.rst b/docs/sources/commandline/index.rst index f1a3e2da45..a7296b27da 100644 --- a/docs/sources/commandline/index.rst +++ b/docs/sources/commandline/index.rst @@ -37,5 +37,6 @@ Contents: start stop tag + top version - wait \ No newline at end of file + wait From 1d02a7ffb63915055f5fd9bda420bd08a8679da1 Mon Sep 17 00:00:00 2001 From: David Sissitka Date: Sun, 21 Jul 2013 19:00:18 -0400 Subject: [PATCH 10/36] Updated the stop command's docs. --- commands.go | 2 +- docs/sources/commandline/command/stop.rst | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index f0e1695b3f..86385f4187 100644 --- a/commands.go +++ b/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 } diff --git a/docs/sources/commandline/command/stop.rst b/docs/sources/commandline/command/stop.rst index 3d571563ec..6a64908eae 100644 --- a/docs/sources/commandline/command/stop.rst +++ b/docs/sources/commandline/command/stop.rst @@ -8,6 +8,8 @@ :: - Usage: docker stop [OPTIONS] NAME + Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] Stop a running container + + -t=10: Number of seconds to wait for the container to stop before killing it. From 3342bdb33184b83cac66921807c5403168d13f6b Mon Sep 17 00:00:00 2001 From: Stefan Praszalowicz Date: Sun, 21 Jul 2013 17:11:47 -0700 Subject: [PATCH 11/36] Support networkless containers with new docker run option '-n' --- container.go | 86 ++++++++++++++---------- container_test.go | 38 +++++++++++ docs/sources/commandline/command/run.rst | 1 + lxc_template.go | 5 ++ 4 files changed, 93 insertions(+), 37 deletions(-) diff --git a/container.go b/container.go index f18aa0fe74..6ae9ab5a44 100644 --- a/container.go +++ b/container.go @@ -58,25 +58,26 @@ type Container struct { } type Config struct { - Hostname string - User string - Memory int64 // Memory limit (in bytes) - MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap - CpuShares int64 // CPU shares (relative weight vs. other containers) - AttachStdin bool - AttachStdout bool - AttachStderr bool - PortSpecs []string - Tty bool // Attach standard streams to a tty, including stdin if it is not closed. - OpenStdin bool // Open stdin - StdinOnce bool // If true, close stdin after the 1 attached client disconnects. - Env []string - Cmd []string - Dns []string - Image string // Name of the image as it was passed by the operator (eg. could be symbolic) - Volumes map[string]struct{} - VolumesFrom string - Entrypoint []string + Hostname string + User string + Memory int64 // Memory limit (in bytes) + MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap + CpuShares int64 // CPU shares (relative weight vs. other containers) + AttachStdin bool + AttachStdout bool + AttachStderr bool + PortSpecs []string + Tty bool // Attach standard streams to a tty, including stdin if it is not closed. + OpenStdin bool // Open stdin + StdinOnce bool // If true, close stdin after the 1 attached client disconnects. + Env []string + Cmd []string + Dns []string + Image string // Name of the image as it was passed by the operator (eg. could be symbolic) + Volumes map[string]struct{} + VolumesFrom string + Entrypoint []string + NetworkEnabled bool } type HostConfig struct { @@ -106,6 +107,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, 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") + flNetwork := cmd.Bool("n", true, "Enable networking for this container") if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") @@ -174,23 +176,24 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, } config := &Config{ - Hostname: *flHostname, - PortSpecs: flPorts, - User: *flUser, - Tty: *flTty, - OpenStdin: *flStdin, - Memory: *flMemory, - CpuShares: *flCpuShares, - AttachStdin: flAttach.Get("stdin"), - AttachStdout: flAttach.Get("stdout"), - AttachStderr: flAttach.Get("stderr"), - Env: flEnv, - Cmd: runCmd, - Dns: flDns, - Image: image, - Volumes: flVolumes, - VolumesFrom: *flVolumesFrom, - Entrypoint: entrypoint, + Hostname: *flHostname, + PortSpecs: flPorts, + User: *flUser, + Tty: *flTty, + NetworkEnabled: *flNetwork, + OpenStdin: *flStdin, + Memory: *flMemory, + CpuShares: *flCpuShares, + AttachStdin: flAttach.Get("stdin"), + AttachStdout: flAttach.Get("stdout"), + AttachStderr: flAttach.Get("stderr"), + Env: flEnv, + Cmd: runCmd, + Dns: flDns, + Image: image, + Volumes: flVolumes, + VolumesFrom: *flVolumesFrom, + Entrypoint: entrypoint, } hostConfig := &HostConfig{ Binds: binds, @@ -626,7 +629,9 @@ func (container *Container) Start(hostConfig *HostConfig) error { } // Networking - params = append(params, "-g", container.network.Gateway.String()) + if container.Config.NetworkEnabled { + params = append(params, "-g", container.network.Gateway.String()) + } // User if container.Config.User != "" { @@ -727,6 +732,10 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) { } func (container *Container) allocateNetwork() error { + if !container.Config.NetworkEnabled { + return nil + } + iface, err := container.runtime.networkManager.Allocate() if err != nil { return err @@ -753,6 +762,9 @@ func (container *Container) allocateNetwork() error { } func (container *Container) releaseNetwork() { + if !container.Config.NetworkEnabled { + return + } container.network.Release() container.network = nil container.NetworkSettings = &NetworkSettings{} diff --git a/container_test.go b/container_test.go index 028c03a318..8d4bbf6c74 100644 --- a/container_test.go +++ b/container_test.go @@ -1251,3 +1251,41 @@ func TestRestartWithVolumes(t *testing.T) { t.Fatalf("Expected volume path: %s Actual path: %s", expected, actual) } } + +func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + + config, hc, _, err := ParseRun([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show"}, nil) + if err != nil { + t.Fatal(err) + } + c, err := NewBuilder(runtime).Create(config) + if err != nil { + t.Fatal(err) + } + stdout, err := c.StdoutPipe() + if err != nil { + t.Fatal(err) + } + + defer runtime.Destroy(c) + if err := c.Start(hc); err != nil { + t.Fatal(err) + } + c.WaitTimeout(500 * time.Millisecond) + c.Wait() + output, err := ioutil.ReadAll(stdout) + if err != nil { + t.Fatal(err) + } + + interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(string(output), -1) + if len(interfaces) != 1 { + t.Fatalf("Wrong interface count in test container: expected [1: lo], got [%s]", interfaces) + } + if interfaces[0] != "1: lo" { + t.Fatalf("Wrong interface in test container: expected [1: lo], got [%s]", interfaces) + } + +} diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index 19efd85821..db67ef0705 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -20,6 +20,7 @@ -h="": Container host name -i=false: Keep stdin open even if not attached -m=0: Memory limit (in bytes) + -n=true: Enable networking for this container -p=[]: Map a network port to the container -t=false: Allocate a pseudo-tty -u="": Username or UID diff --git a/lxc_template.go b/lxc_template.go index 93b795e901..27670c8076 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -13,6 +13,7 @@ lxc.utsname = {{.Id}} {{end}} #lxc.aa_profile = unconfined +{{if .Config.NetworkEnabled}} # network configuration lxc.network.type = veth lxc.network.flags = up @@ -20,6 +21,10 @@ lxc.network.link = {{.NetworkSettings.Bridge}} lxc.network.name = eth0 lxc.network.mtu = 1500 lxc.network.ipv4 = {{.NetworkSettings.IPAddress}}/{{.NetworkSettings.IPPrefixLen}} +{{else}} +# Network configuration disabled +lxc.network.type = empty +{{end}} # root filesystem {{$ROOTFS := .RootfsPath}} From 49673fc45cc5cfc15219bf1eb6eaff7621696919 Mon Sep 17 00:00:00 2001 From: Stefan Praszalowicz Date: Sun, 21 Jul 2013 17:49:09 -0700 Subject: [PATCH 12/36] Support completely disabling network configuration with docker -d -b none --- container.go | 8 ++++++-- network.go | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/container.go b/container.go index 6ae9ab5a44..189e882c44 100644 --- a/container.go +++ b/container.go @@ -514,8 +514,12 @@ func (container *Container) Start(hostConfig *HostConfig) error { if err := container.EnsureMounted(); err != nil { return err } - if err := container.allocateNetwork(); err != nil { - return err + if container.runtime.networkManager.disabled { + container.Config.NetworkEnabled = false + } else { + if err := container.allocateNetwork(); err != nil { + return err + } } // Make sure the config is compatible with the current kernel diff --git a/network.go b/network.go index 0f98c899f1..c71ecfb3ae 100644 --- a/network.go +++ b/network.go @@ -17,6 +17,7 @@ var NetworkBridgeIface string const ( DefaultNetworkBridge = "docker0" + DisableNetworkBridge = "none" portRangeStart = 49153 portRangeEnd = 65535 ) @@ -453,10 +454,16 @@ type NetworkInterface struct { manager *NetworkManager extPorts []*Nat + disabled bool } // Allocate an external TCP port and map it to the interface func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) { + + if iface.disabled { + return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME + } + nat, err := parseNat(spec) if err != nil { return nil, err @@ -552,6 +559,11 @@ func parseNat(spec string) (*Nat, error) { // Release: Network cleanup - release all resources func (iface *NetworkInterface) Release() { + + if iface.disabled { + return + } + for _, nat := range iface.extPorts { utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend) if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil { @@ -579,10 +591,17 @@ type NetworkManager struct { tcpPortAllocator *PortAllocator udpPortAllocator *PortAllocator portMapper *PortMapper + + disabled bool } // Allocate a network interface func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { + + if manager.disabled { + return &NetworkInterface{disabled: true}, nil + } + ip, err := manager.ipAllocator.Acquire() if err != nil { return nil, err @@ -596,6 +615,14 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { } func newNetworkManager(bridgeIface string) (*NetworkManager, error) { + + if bridgeIface == DisableNetworkBridge { + manager := &NetworkManager{ + disabled: true, + } + return manager, nil + } + addr, err := getIfaceAddr(bridgeIface) if err != nil { // If the iface is not found, try to create it From 964e826a9beb69a0246ed22c7935fd86df6712e1 Mon Sep 17 00:00:00 2001 From: Stefan Praszalowicz Date: Sun, 21 Jul 2013 18:01:52 -0700 Subject: [PATCH 13/36] Document -b none --- docker/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker.go b/docker/docker.go index fb7c465369..2db50bf328 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -28,7 +28,7 @@ func main() { flDaemon := flag.Bool("d", false, "Daemon mode") flDebug := flag.Bool("D", false, "Debug mode") flAutoRestart := flag.Bool("r", false, "Restart previously running containers") - bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge") + bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking") pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID") flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") From f236e62d9d288ac695129157d1753512f5cd2b0a Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Sun, 7 Jul 2013 23:24:52 -0700 Subject: [PATCH 14/36] Test pulling remote files using ADD in a buildfile. --- buildfile_test.go | 117 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 30 deletions(-) diff --git a/buildfile_test.go b/buildfile_test.go index 14edbc088f..602af5061b 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -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,70 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ] {"f", "hello"}, {"d/ga", "bu"}, }, + nil, }, { ` -from %s +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 +163,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 +194,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 +211,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 +222,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 +234,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 +251,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 +263,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" { } From 2b0ebf5d32c65276ce50fce168f32483ffb9c311 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Mon, 8 Jul 2013 00:43:22 -0700 Subject: [PATCH 15/36] Buildfile: for ADD command, determine filename from URL. This is used if the destination is a directory. This makes the URL download behavior more closely match file copying. Fixes #1142. --- buildfile.go | 21 ++++++++++++++++++++- buildfile_test.go | 16 ++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index 7ade058c69..75ebdd7a7c 100644 --- a/buildfile.go +++ b/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) diff --git a/buildfile_test.go b/buildfile_test.go index 602af5061b..b7eca52336 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -84,6 +84,22 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ] nil, }, + { + ` +from {IMAGE} +add http://{SERVERADDR}/x /a/b/c +run [ "$(cat /a/b/c)" = "hello" ] +add http://{SERVERADDR}/x?foo=bar / +run [ "$(cat /x)" = "hello" ] +add http://{SERVERADDR}/x /d/ +run [ "$(cat /d/x)" = "hello" ] +add http://{SERVERADDR} /e +run [ "$(cat /e)" = "blah" ] +`, + nil, + [][2]string{{"/x", "hello"}, {"/", "blah"}}, + }, + { ` from {IMAGE} From 416fdaa3d5d7b84f7e36fdfcff7153e3a38262c9 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Mon, 8 Jul 2013 01:14:01 -0700 Subject: [PATCH 16/36] Remove some trailing whitespace. --- docs/sources/use/builder.rst | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 7f370609c8..ea98541dbf 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -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": }'`` 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 ````. This value will be passed to all future ``RUN`` instructions. This is functionally equivalent to prefixing the command with ``=`` -.. note:: +.. note:: The environment variables will persist when a container is run from the resulting image. @@ -158,10 +158,10 @@ The copy obeys the following rules: (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. @@ -203,14 +203,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 +218,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 +231,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"] From c383d598809daec6bccd60bd211c10f6a4ef60b0 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Mon, 8 Jul 2013 01:29:13 -0700 Subject: [PATCH 17/36] Update ADD documentation to specify new behavior. --- docs/sources/use/builder.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index ea98541dbf..302527a740 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -152,6 +152,14 @@ destination container. The copy obeys the following rules: +* If ```` is a URL and ```` does not end with a trailing slash, + then a file is downloaded from the URL and copied to ````. +* If ```` is a URL and ```` does end with a trailing slash, + then the filename is inferred from the URL and the file is downloaded to + ``/``. 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 ```` is a directory, the entire directory is copied, including filesystem metadata. * If ````` is a tar archive in a recognized compression format From 74a2b13687787a33b0963f020f704d3cdde4b06d Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 22 Jul 2013 14:52:05 +0000 Subject: [PATCH 18/36] fix error message when invalid directory --- commands.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/commands.go b/commands.go index 86385f4187..b0e32162e6 100644 --- a/commands.go +++ b/commands.go @@ -185,6 +185,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) { isRemote = true } else { + if _, err := os.Stat(cmd.Arg(0)); err != nil { + return err + } context, err = Tar(cmd.Arg(0), Uncompressed) } var body io.Reader From 5c1af383eb14121174b43fa706f524d4eedd5cad Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 22 Jul 2013 16:26:05 +0000 Subject: [PATCH 19/36] fix test env --- container_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container_test.go b/container_test.go index 028c03a318..a90018decc 100644 --- a/container_test.go +++ b/container_test.go @@ -959,6 +959,7 @@ func TestEnv(t *testing.T) { goodEnv := []string{ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOME=/", + "container=lxc", } sort.Strings(goodEnv) if len(goodEnv) != len(actualEnv) { From 4714f102d72f03159acd0f7be71cde3d169c06b8 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Jul 2013 12:06:24 -0700 Subject: [PATCH 20/36] Allocate a /16 IP range by default, with fallback to /24. Try a total of 12 ranges instead of 3. --- network.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/network.go b/network.go index 0f98c899f1..d2d6668b26 100644 --- a/network.go +++ b/network.go @@ -111,10 +111,29 @@ func checkRouteOverlaps(dockerNetwork *net.IPNet) error { return nil } +// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`, +// and attempts to configure it with an address which doesn't conflict with any other interface on the host. +// If it can't find an address which doesn't conflict, it will return an error. func CreateBridgeIface(ifaceName string) error { - // FIXME: try more IP ranges - // FIXME: try bigger ranges! /24 is too small. - addrs := []string{"172.16.42.1/24", "10.0.42.1/24", "192.168.42.1/24"} + addrs := []string{ + // Here we don't follow the convention of using the 1st IP of the range for the gateway. + // This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges. + // In theory this shouldn't matter - in practice there's bound to be a few scripts relying + // on the internal addressing or other stupid things like that. + // The shouldn't, but hey, let's not break them unless we really have to. + "172.16.42.1/16", + "10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive + "10.1.42.1/16", + "10.42.42.1/16", + "172.16.42.1/24", + "172.16.43.1/24", + "172.16.44.1/24", + "10.0.42.1/24", + "10.0.43.1/24", + "192.168.42.1/24", + "192.168.43.1/24", + "192.168.44.1/24", + } var ifaceAddr string for _, addr := range addrs { From ce43f4af1cf7c2692c4c6203bc5872053d089863 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Jul 2013 18:32:55 -0700 Subject: [PATCH 21/36] Hack: first draft of the maintainer boot camp. Work in progress! --- hack/bootcamp/README.md | 92 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 hack/bootcamp/README.md diff --git a/hack/bootcamp/README.md b/hack/bootcamp/README.md new file mode 100644 index 0000000000..f4cc91f19e --- /dev/null +++ b/hack/bootcamp/README.md @@ -0,0 +1,92 @@ +# Docker maintainer bootcamp + +## Introduction: we need more maintainers + +Docker is growing incredibly fast. At the time of writing, it has received over 200 contributions from 90 people, +and its API is used by dozens of 3d-party tools. Over 1,000 issues have been opened. As the first production deployments +start going live, the growth will only accelerate. + +Also at the time of writing, Docker has 3 full-time maintainers, and 7 part-time subsystem maintainers. If docker +is going to live up to the expectations, we need more than that. + +This document describes a *bootcamp* to guide and train volunteers interested in helping the project, either with individual +contributions, maintainer work, or both. + +This bootcamp is an experiment. If you decide to go through it, consider yourself an alpha-tester. You should expect quirks, +and report them to us as you encounter them to help us smooth out the process. + + +## How it works + +The maintainer bootcamp is a 12-step program - one step for each of the maintainer's responsibilities. The aspiring maintainer must +validate all 12 steps by 1) studying it, 2) practicing it, and 3) getting endorsed for it. + +Steps are all equally important and can be validated in any order. Validating all 12 steps is a pre-requisite for becoming a core +maintainer, but even 1 step will make you a better contributor! + +### List of steps + +#### 1) Be a power user + +Use docker daily, build cool things with it, know its quirks inside and out. + + +#### 2) Help users + +Answer questions on irc, twitter, email, in person. + + +#### 3) Manage the bug tracker + +Help triage tickets - ask the right questions, find duplicates, reference relevant resources, know when to close a ticket when necessary, take the time to go over older tickets. + + +#### 4) Improve the documentation + +Follow the documentation from scratch regularly and make sure it is still up-to-date. Find and fix inconsistencies. Remove stale information. Find a frequently asked question that is not documented. Simplify the content and the form. + + +#### 5) Evangelize the principles of docker + +Understand what the underlying goals and principle of docker are. Explain design decisions based on what docker is, and what it is not. When someone is not using docker, find how docker can be valuable to them. If they are using docker, find how they can use it better. + + +#### 6) Fix bugs + +Self-explanatory. Contribute improvements to docker which solve defects. Bugfixes should be well-tested, and prioritized by impact to the user. + + +#### 7) Improve the testing infrastructure + +Automated testing is complicated and should be perpetually improved. Invest time to improve the current tooling. Refactor existing tests, create new ones, make testing more accessible to developers, add new testing capabilities (integration tests, mocking, stress test...), improve integration between tests and documentation... + + +#### 8) Contribute features + +Improve docker to do more things, or get better at doing the same things. Features should be well-tested, not break existing APIs, respect the project goals. They should make the user's life measurably better. Features should be discussed ahead of time to avoid wasting time and duplicating effort. + + +#### 9) Refactor internals + +Improve docker to repay technical debt. Simplify code layout, improve performance, add missing comments, reduce the number of files and functions, rename functions and variables to be more readable, go over FIXMEs, etc. + +#### 10) Review and merge contributions + +Review pull requests in a timely manner, review code in detail and offer feedback. Keep a high bar without being pedantic. Share the load of testing and merging pull requests. + +#### 11) Release + +Manage a release of docker from beginning to end. Tests, final review, tags, builds, upload to mirrors, distro packaging, etc. + +#### 12) Train other maintainers + +Contribute to training other maintainers. Give advic + + +### How to study a step + +### How to practice a step + +### How to get endorsed for a step + + From 5714f0a74ef6f6b0be521d7e1e21c1cab1a9ad73 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Jul 2013 18:36:36 -0700 Subject: [PATCH 22/36] Hack: completed step 12 of the bootcamp --- hack/bootcamp/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hack/bootcamp/README.md b/hack/bootcamp/README.md index f4cc91f19e..88440e8c64 100644 --- a/hack/bootcamp/README.md +++ b/hack/bootcamp/README.md @@ -80,8 +80,7 @@ Manage a release of docker from beginning to end. Tests, final review, tags, bui #### 12) Train other maintainers -Contribute to training other maintainers. Give advic - +Contribute to training other maintainers. Give advice, delegate work, help organize the bootcamp. This also means contribute to the maintainer's manual, look for ways to improve the project organization etc. ### How to study a step From 6745bdd0b3a7944985614dae194538645eacb4e7 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Jul 2013 18:39:58 -0700 Subject: [PATCH 23/36] Typo in 3rd-party --- hack/bootcamp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/bootcamp/README.md b/hack/bootcamp/README.md index 88440e8c64..2c3d356daf 100644 --- a/hack/bootcamp/README.md +++ b/hack/bootcamp/README.md @@ -3,7 +3,7 @@ ## Introduction: we need more maintainers Docker is growing incredibly fast. At the time of writing, it has received over 200 contributions from 90 people, -and its API is used by dozens of 3d-party tools. Over 1,000 issues have been opened. As the first production deployments +and its API is used by dozens of 3rd-party tools. Over 1,000 issues have been opened. As the first production deployments start going live, the growth will only accelerate. Also at the time of writing, Docker has 3 full-time maintainers, and 7 part-time subsystem maintainers. If docker From bc172e5e5f1f231f878727a180a8da46e653c0a7 Mon Sep 17 00:00:00 2001 From: Stefan Praszalowicz Date: Mon, 22 Jul 2013 19:00:35 -0700 Subject: [PATCH 24/36] Invert network disable flag and logic (unbreaks TestAllocate*PortLocalhost) --- container.go | 84 ++++++++++++++++++++++++------------------------- lxc_template.go | 8 ++--- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/container.go b/container.go index 189e882c44..f172604356 100644 --- a/container.go +++ b/container.go @@ -58,26 +58,26 @@ type Container struct { } type Config struct { - Hostname string - User string - Memory int64 // Memory limit (in bytes) - MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap - CpuShares int64 // CPU shares (relative weight vs. other containers) - AttachStdin bool - AttachStdout bool - AttachStderr bool - PortSpecs []string - Tty bool // Attach standard streams to a tty, including stdin if it is not closed. - OpenStdin bool // Open stdin - StdinOnce bool // If true, close stdin after the 1 attached client disconnects. - Env []string - Cmd []string - Dns []string - Image string // Name of the image as it was passed by the operator (eg. could be symbolic) - Volumes map[string]struct{} - VolumesFrom string - Entrypoint []string - NetworkEnabled bool + Hostname string + User string + Memory int64 // Memory limit (in bytes) + MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap + CpuShares int64 // CPU shares (relative weight vs. other containers) + AttachStdin bool + AttachStdout bool + AttachStderr bool + PortSpecs []string + Tty bool // Attach standard streams to a tty, including stdin if it is not closed. + OpenStdin bool // Open stdin + StdinOnce bool // If true, close stdin after the 1 attached client disconnects. + Env []string + Cmd []string + Dns []string + Image string // Name of the image as it was passed by the operator (eg. could be symbolic) + Volumes map[string]struct{} + VolumesFrom string + Entrypoint []string + NetworkDisabled bool } type HostConfig struct { @@ -176,24 +176,24 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, } config := &Config{ - Hostname: *flHostname, - PortSpecs: flPorts, - User: *flUser, - Tty: *flTty, - NetworkEnabled: *flNetwork, - OpenStdin: *flStdin, - Memory: *flMemory, - CpuShares: *flCpuShares, - AttachStdin: flAttach.Get("stdin"), - AttachStdout: flAttach.Get("stdout"), - AttachStderr: flAttach.Get("stderr"), - Env: flEnv, - Cmd: runCmd, - Dns: flDns, - Image: image, - Volumes: flVolumes, - VolumesFrom: *flVolumesFrom, - Entrypoint: entrypoint, + Hostname: *flHostname, + PortSpecs: flPorts, + User: *flUser, + Tty: *flTty, + NetworkDisabled: !*flNetwork, + OpenStdin: *flStdin, + Memory: *flMemory, + CpuShares: *flCpuShares, + AttachStdin: flAttach.Get("stdin"), + AttachStdout: flAttach.Get("stdout"), + AttachStderr: flAttach.Get("stderr"), + Env: flEnv, + Cmd: runCmd, + Dns: flDns, + Image: image, + Volumes: flVolumes, + VolumesFrom: *flVolumesFrom, + Entrypoint: entrypoint, } hostConfig := &HostConfig{ Binds: binds, @@ -515,7 +515,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { return err } if container.runtime.networkManager.disabled { - container.Config.NetworkEnabled = false + container.Config.NetworkDisabled = true } else { if err := container.allocateNetwork(); err != nil { return err @@ -633,7 +633,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { } // Networking - if container.Config.NetworkEnabled { + if !container.Config.NetworkDisabled { params = append(params, "-g", container.network.Gateway.String()) } @@ -736,7 +736,7 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) { } func (container *Container) allocateNetwork() error { - if !container.Config.NetworkEnabled { + if container.Config.NetworkDisabled { return nil } @@ -766,7 +766,7 @@ func (container *Container) allocateNetwork() error { } func (container *Container) releaseNetwork() { - if !container.Config.NetworkEnabled { + if container.Config.NetworkDisabled { return } container.network.Release() diff --git a/lxc_template.go b/lxc_template.go index 27670c8076..d1f67e3376 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -13,7 +13,10 @@ lxc.utsname = {{.Id}} {{end}} #lxc.aa_profile = unconfined -{{if .Config.NetworkEnabled}} +{{if .Config.NetworkDisabled}} +# network is disabled (-n=false) +lxc.network.type = empty +{{else}} # network configuration lxc.network.type = veth lxc.network.flags = up @@ -21,9 +24,6 @@ lxc.network.link = {{.NetworkSettings.Bridge}} lxc.network.name = eth0 lxc.network.mtu = 1500 lxc.network.ipv4 = {{.NetworkSettings.IPAddress}}/{{.NetworkSettings.IPPrefixLen}} -{{else}} -# Network configuration disabled -lxc.network.type = empty {{end}} # root filesystem From 58a1c5720a42fde72c27532ce1122609225bc3bb Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Mon, 22 Jul 2013 20:26:40 -0700 Subject: [PATCH 25/36] Added new docker logo to the documentation header, and added other links. --- docs/sources/use/basics.rst | 4 ++-- docs/theme/docker/layout.html | 13 ++++++------- docs/theme/docker/static/css/main.css | 10 +++++----- docs/theme/docker/static/css/main.less | 10 +++------- docs/theme/docker/static/img/docker-top-logo.png | Bin 0 -> 3426 bytes .../docker/static/img/external-link-icon.png | Bin 0 -> 2917 bytes 6 files changed, 16 insertions(+), 21 deletions(-) create mode 100644 docs/theme/docker/static/img/docker-top-logo.png create mode 100644 docs/theme/docker/static/img/external-link-icon.png diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index 7c9e2e9055..7ce86416dd 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -51,7 +51,7 @@ For example: .. code-block:: bash # Run docker in daemon mode - sudo /docker -H 0.0.0.0:5555 & + sudo /docker -H 0.0.0.0:5555 -d & # Download a base image docker -H :5555 pull base @@ -61,7 +61,7 @@ on both tcp and a unix socket .. code-block:: bash # Run docker in daemon mode - sudo /docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock + sudo /docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock -d & # Download a base image docker pull base # OR diff --git a/docs/theme/docker/layout.html b/docs/theme/docker/layout.html index f4f17f2dba..198cd5d7d8 100755 --- a/docs/theme/docker/layout.html +++ b/docs/theme/docker/layout.html @@ -68,19 +68,18 @@
- +
@@ -96,7 +95,7 @@ -

DOCUMENTATION

+

DOCUMENTATION

diff --git a/docs/theme/docker/static/css/main.css b/docs/theme/docker/static/css/main.css index c79d590e07..0b11890f71 100755 --- a/docs/theme/docker/static/css/main.css +++ b/docs/theme/docker/static/css/main.css @@ -34,12 +34,12 @@ h4 { .navbar .nav li a { padding: 22px 15px 22px; } -.navbar .brand { - padding: 13px 10px 13px 28px ; -} .navbar-dotcloud .container { border-bottom: 2px #000000 solid; } +.inline-icon { + margin-bottom: 6px; +} /* * Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS * http://www.jonsuh.com @@ -82,7 +82,7 @@ h4 { .btn-custom { background-color: #292929 !important; background-repeat: repeat-x; - filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828"); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828"); background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828)); background-image: -moz-linear-gradient(top, #515151, #282828); background-image: -ms-linear-gradient(top, #515151, #282828); @@ -301,7 +301,7 @@ section.header { height: 28px; line-height: 28px; background-color: #43484c; - filter: progid:dximagetransform.microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35'); + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35'); background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #747474), color-stop(100%, #43484c)); background-image: -webkit-linear-gradient(top, #747474 0%, #43484c 100%); background-image: -moz-linear-gradient(top, #747474 0%, #43484c 100%); diff --git a/docs/theme/docker/static/css/main.less b/docs/theme/docker/static/css/main.less index c8c38dc8ed..8c470518d8 100644 --- a/docs/theme/docker/static/css/main.less +++ b/docs/theme/docker/static/css/main.less @@ -53,13 +53,6 @@ h1, h2, h3, h4 { padding: 22px 15px 22px; } } - - .brand { - padding: 13px 10px 13px 28px ; - // padding-left: 30px; - - } - background-color: white; } @@ -67,6 +60,9 @@ h1, h2, h3, h4 { border-bottom: 2px @black solid; } +.inline-icon { + margin-bottom: 6px; +} /* * Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS diff --git a/docs/theme/docker/static/img/docker-top-logo.png b/docs/theme/docker/static/img/docker-top-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4955f499bdc39daa24c3e6b779283e113eeaf1ce GIT binary patch literal 3426 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 P00000NkvXXu0mjfh3RP# literal 0 HcmV?d00001 From 3bae188b8dc51911a44ea1c7b5681f9f07f9d3af Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 23 Jul 2013 15:04:31 +0000 Subject: [PATCH 26/36] change dockercfg to json and support multiple auth remote --- api.go | 58 +++------------------------- auth/auth.go | 104 ++++++++++++++++++++++++++------------------------- commands.go | 52 ++++++++++++++------------ 3 files changed, 87 insertions(+), 127 deletions(-) diff --git a/api.go b/api.go index b6ab7badfa..975134f22d 100644 --- a/api.go +++ b/api.go @@ -81,54 +81,15 @@ func getBoolParam(value string) (bool, error) { return ret, nil } -func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if version > 1.1 { - w.WriteHeader(http.StatusNotFound) - return nil - } - authConfig, err := auth.LoadConfig(srv.runtime.root) - if err != nil { - if err != auth.ErrConfigFileMissing { - return err - } - authConfig = &auth.AuthConfig{} - } - b, err := json.Marshal(&auth.AuthConfig{Username: authConfig.Username, Email: authConfig.Email}) - if err != nil { - return err - } - writeJSON(w, b) - return nil -} - func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { authConfig := &auth.AuthConfig{} err := json.NewDecoder(r.Body).Decode(authConfig) if err != nil { return err } - status := "" - if version > 1.1 { - status, err = auth.Login(authConfig, false) - if err != nil { - return err - } - } else { - localAuthConfig, err := auth.LoadConfig(srv.runtime.root) - if err != nil { - if err != auth.ErrConfigFileMissing { - return err - } - } - if authConfig.Username == localAuthConfig.Username { - authConfig.Password = localAuthConfig.Password - } - - newAuthConfig := auth.NewAuthConfig(authConfig.Username, authConfig.Password, authConfig.Email, srv.runtime.root) - status, err = auth.Login(newAuthConfig, true) - if err != nil { - return err - } + status, err := auth.Login(authConfig) + if err != nil { + return err } if status != "" { b, err := json.Marshal(&APIAuth{Status: status}) @@ -429,16 +390,8 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { authConfig := &auth.AuthConfig{} - if version > 1.1 { - if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { - return err - } - } else { - localAuthConfig, err := auth.LoadConfig(srv.runtime.root) - if err != nil && err != auth.ErrConfigFileMissing { - return err - } - authConfig = localAuthConfig + if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { + return err } if err := parseForm(r); err != nil { return err @@ -854,7 +807,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{ "GET": { - "/auth": getAuth, "/version": getVersion, "/info": getInfo, "/images/json": getImagesJSON, diff --git a/auth/auth.go b/auth/auth.go index 97df928b6b..8f4d09fb8f 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -25,19 +25,15 @@ var ( ) type AuthConfig struct { - Username string `json:"username"` - Password string `json:"password"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Auth string `json:"auth"` Email string `json:"email"` - rootPath string } -func NewAuthConfig(username, password, email, rootPath string) *AuthConfig { - return &AuthConfig{ - Username: username, - Password: password, - Email: email, - rootPath: rootPath, - } +type ConfigFile struct { + Configs map[string]AuthConfig `json:"configs,omitempty"` + rootPath string } func IndexServerAddress() string { @@ -54,61 +50,83 @@ func encodeAuth(authConfig *AuthConfig) string { } // decode the auth string -func decodeAuth(authStr string) (*AuthConfig, error) { +func decodeAuth(authStr string) (string, string, error) { decLen := base64.StdEncoding.DecodedLen(len(authStr)) decoded := make([]byte, decLen) authByte := []byte(authStr) n, err := base64.StdEncoding.Decode(decoded, authByte) if err != nil { - return nil, err + return "", "", err } if n > decLen { - return nil, fmt.Errorf("Something went wrong decoding auth config") + return "", "", fmt.Errorf("Something went wrong decoding auth config") } arr := strings.Split(string(decoded), ":") if len(arr) != 2 { - return nil, fmt.Errorf("Invalid auth configuration file") + return "", "", fmt.Errorf("Invalid auth configuration file") } password := strings.Trim(arr[1], "\x00") - return &AuthConfig{Username: arr[0], Password: password}, nil + return arr[0], password, nil } // load up the auth config information and return values // FIXME: use the internal golang config parser -func LoadConfig(rootPath string) (*AuthConfig, error) { +func LoadConfig(rootPath string) (*ConfigFile, error) { + configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath} confFile := path.Join(rootPath, CONFIGFILE) if _, err := os.Stat(confFile); err != nil { - return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing + return &configFile, ErrConfigFileMissing } b, err := ioutil.ReadFile(confFile) if err != nil { return nil, err } - arr := strings.Split(string(b), "\n") - if len(arr) < 2 { - return nil, fmt.Errorf("The Auth config file is empty") + + if err := json.Unmarshal(b, &configFile.Configs); err != nil { + arr := strings.Split(string(b), "\n") + if len(arr) < 2 { + return nil, fmt.Errorf("The Auth config file is empty") + } + authConfig := AuthConfig{} + origAuth := strings.Split(arr[0], " = ") + authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) + if err != nil { + return nil, err + } + origEmail := strings.Split(arr[1], " = ") + authConfig.Email = origEmail[1] + configFile.Configs[IndexServerAddress()] = authConfig + } else { + for k, authConfig := range configFile.Configs { + authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) + if err != nil { + return nil, err + } + configFile.Configs[k] = authConfig + } } - origAuth := strings.Split(arr[0], " = ") - origEmail := strings.Split(arr[1], " = ") - authConfig, err := decodeAuth(origAuth[1]) - if err != nil { - return nil, err - } - authConfig.Email = origEmail[1] - authConfig.rootPath = rootPath - return authConfig, nil + return &configFile, nil } // save the auth config -func SaveConfig(authConfig *AuthConfig) error { - confFile := path.Join(authConfig.rootPath, CONFIGFILE) - if len(authConfig.Email) == 0 { +func SaveConfig(configFile *ConfigFile) error { + confFile := path.Join(configFile.rootPath, CONFIGFILE) + if len(configFile.Configs) == 0 { os.Remove(confFile) return nil } - lines := "auth = " + encodeAuth(authConfig) + "\n" + "email = " + authConfig.Email + "\n" - b := []byte(lines) - err := ioutil.WriteFile(confFile, b, 0600) + for k, authConfig := range configFile.Configs { + authConfig.Auth = encodeAuth(&authConfig) + authConfig.Username = "" + authConfig.Password = "" + configFile.Configs[k] = authConfig + } + + b, err := json.Marshal(configFile.Configs) + if err != nil { + return err + } + err = ioutil.WriteFile(confFile, b, 0600) if err != nil { return err } @@ -116,8 +134,7 @@ func SaveConfig(authConfig *AuthConfig) error { } // try to register/login to the registry server -func Login(authConfig *AuthConfig, store bool) (string, error) { - storeConfig := false +func Login(authConfig *AuthConfig) (string, error) { client := &http.Client{} reqStatusCode := 0 var status string @@ -143,7 +160,6 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { if reqStatusCode == 201 { status = "Account created. Please use the confirmation link we sent" + " to your e-mail to activate it." - storeConfig = true } else if reqStatusCode == 403 { return "", fmt.Errorf("Login: Your account hasn't been activated. " + "Please check your e-mail for a confirmation link.") @@ -162,14 +178,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { } if resp.StatusCode == 200 { status = "Login Succeeded" - storeConfig = true } else if resp.StatusCode == 401 { - if store { - authConfig.Email = "" - if err := SaveConfig(authConfig); err != nil { - return "", err - } - } return "", fmt.Errorf("Wrong login/password, please try again") } else { return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, @@ -181,10 +190,5 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { } else { return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody) } - if storeConfig && store { - if err := SaveConfig(authConfig); err != nil { - return "", err - } - } return status, nil } diff --git a/commands.go b/commands.go index b0e32162e6..17597320b5 100644 --- a/commands.go +++ b/commands.go @@ -313,16 +313,21 @@ func (cli *DockerCli) CmdLogin(args ...string) error { email string ) + authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] + if !ok { + authconfig = auth.AuthConfig{} + } + if *flUsername == "" { - fmt.Fprintf(cli.out, "Username (%s): ", cli.authConfig.Username) + fmt.Fprintf(cli.out, "Username (%s): ", authconfig.Username) username = readAndEchoString(cli.in, cli.out) if username == "" { - username = cli.authConfig.Username + username = authconfig.Username } } else { username = *flUsername } - if username != cli.authConfig.Username { + if username != authconfig.Username { if *flPassword == "" { fmt.Fprintf(cli.out, "Password: ") password = readString(cli.in, cli.out) @@ -334,31 +339,30 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } if *flEmail == "" { - fmt.Fprintf(cli.out, "Email (%s): ", cli.authConfig.Email) + fmt.Fprintf(cli.out, "Email (%s): ", authconfig.Email) email = readAndEchoString(cli.in, cli.out) if email == "" { - email = cli.authConfig.Email + email = authconfig.Email } } else { email = *flEmail } } else { - password = cli.authConfig.Password - email = cli.authConfig.Email + password = authconfig.Password + email = authconfig.Email } if oldState != nil { term.RestoreTerminal(cli.terminalFd, oldState) } - cli.authConfig.Username = username - cli.authConfig.Password = password - cli.authConfig.Email = email + authconfig.Username = username + authconfig.Password = password + authconfig.Email = email + cli.configFile.Configs[auth.IndexServerAddress()] = authconfig - body, statusCode, err := cli.call("POST", "/auth", cli.authConfig) + body, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[auth.IndexServerAddress()]) if statusCode == 401 { - cli.authConfig.Username = "" - cli.authConfig.Password = "" - cli.authConfig.Email = "" - auth.SaveConfig(cli.authConfig) + delete(cli.configFile.Configs, auth.IndexServerAddress()) + auth.SaveConfig(cli.configFile) return err } if err != nil { @@ -368,10 +372,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error { var out2 APIAuth err = json.Unmarshal(body, &out2) if err != nil { - auth.LoadConfig(os.Getenv("HOME")) + cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME")) return err } - auth.SaveConfig(cli.authConfig) + auth.SaveConfig(cli.configFile) if out2.Status != "" { fmt.Fprintf(cli.out, "%s\n", out2.Status) } @@ -802,10 +806,10 @@ func (cli *DockerCli) CmdPush(args ...string) error { // Custom repositories can have different rules, and we must also // allow pushing by image ID. if len(strings.SplitN(name, "/", 2)) == 1 { - return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", cli.authConfig.Username, name) + return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", cli.configFile.Configs[auth.IndexServerAddress()].Username, name) } - buf, err := json.Marshal(cli.authConfig) + buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()]) if err != nil { return err } @@ -1410,11 +1414,11 @@ func (cli *DockerCli) CmdRun(args ...string) error { func (cli *DockerCli) checkIfLogged(action string) error { // If condition AND the login failed - if cli.authConfig.Username == "" { + if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" { if err := cli.CmdLogin(""); err != nil { return err } - if cli.authConfig.Username == "" { + if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" { return fmt.Errorf("Please login prior to %s. ('docker login')", action) } } @@ -1670,11 +1674,11 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc err = out } - authConfig, _ := auth.LoadConfig(os.Getenv("HOME")) + configFile, _ := auth.LoadConfig(os.Getenv("HOME")) return &DockerCli{ proto: proto, addr: addr, - authConfig: authConfig, + configFile: configFile, in: in, out: out, err: err, @@ -1686,7 +1690,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc type DockerCli struct { proto string addr string - authConfig *auth.AuthConfig + configFile *auth.ConfigFile in io.ReadCloser out io.Writer err io.Writer From b5da816487d68853a8ac46630cb3118646f71d2d Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 10 Jul 2013 12:55:05 +0000 Subject: [PATCH 27/36] basic version of the /events endpoint --- api.go | 28 +++++++++++++++++++++++++++- api_params.go | 15 ++++++++------- commands.go | 31 ++++++++++++++++++++++--------- runtime_test.go | 12 ++++++------ server.go | 34 ++++++++++++++++++++++++++-------- utils/utils.go | 15 +++++++++++++++ 6 files changed, 104 insertions(+), 31 deletions(-) diff --git a/api.go b/api.go index b6ab7badfa..9d0348b608 100644 --- a/api.go +++ b/api.go @@ -217,6 +217,31 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques return nil } +func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + events := make(chan utils.JSONMessage) + srv.Lock() + srv.events[r.RemoteAddr] = events + srv.Unlock() + w.Header().Set("Content-Type", "application/json") + wf := utils.NewWriteFlusher(w) + for { + event := <-events + b, err := json.Marshal(event) + if err != nil { + continue + } + _, err = wf.Write(b) + if err != nil { + utils.Debugf("%s", err) + srv.Lock() + delete(srv.events, r.RemoteAddr) + srv.Unlock() + return err + } + } + return nil +} + func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") @@ -855,8 +880,9 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{ "GET": { "/auth": getAuth, - "/version": getVersion, + "/events": getEvents, "/info": getInfo, + "/version": getVersion, "/images/json": getImagesJSON, "/images/viz": getImagesViz, "/images/search": getImagesSearch, diff --git a/api_params.go b/api_params.go index b371ca314f..26d70711a1 100644 --- a/api_params.go +++ b/api_params.go @@ -17,13 +17,14 @@ type APIImages struct { } type APIInfo struct { - Debug bool - Containers int - Images int - NFd int `json:",omitempty"` - NGoroutines int `json:",omitempty"` - MemoryLimit bool `json:",omitempty"` - SwapLimit bool `json:",omitempty"` + Debug bool + Containers int + Images int + NFd int `json:",omitempty"` + NGoroutines int `json:",omitempty"` + MemoryLimit bool `json:",omitempty"` + SwapLimit bool `json:",omitempty"` + NEventsListener int `json:",omitempty"` } type APITop struct { diff --git a/commands.go b/commands.go index b0e32162e6..12647feead 100644 --- a/commands.go +++ b/commands.go @@ -78,6 +78,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"build", "Build a container from a Dockerfile"}, {"commit", "Create a new image from a container's changes"}, {"diff", "Inspect changes on a container's filesystem"}, + {"events", "Get real time events from the server"}, {"export", "Stream the contents of a container as a tar archive"}, {"history", "Show the history of an image"}, {"images", "List images"}, @@ -466,6 +467,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") fmt.Fprintf(cli.out, "Fds: %d\n", out.NFd) fmt.Fprintf(cli.out, "Goroutines: %d\n", out.NGoroutines) + fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener) } if !out.MemoryLimit { fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") @@ -1055,6 +1057,23 @@ func (cli *DockerCli) CmdCommit(args ...string) error { return nil } +func (cli *DockerCli) CmdEvents(args ...string) error { + cmd := Subcmd("events", "", "Get real time events from the server") + if err := cmd.Parse(args); err != nil { + return nil + } + + if cmd.NArg() != 0 { + cmd.Usage() + return nil + } + + if err := cli.stream("GET", "/events", nil, cli.out); err != nil { + return err + } + return nil +} + func (cli *DockerCli) CmdExport(args ...string) error { cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive") if err := cmd.Parse(args); err != nil { @@ -1509,19 +1528,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e if resp.Header.Get("Content-Type") == "application/json" { dec := json.NewDecoder(resp.Body) for { - var m utils.JSONMessage - if err := dec.Decode(&m); err == io.EOF { + var jm utils.JSONMessage + if err := dec.Decode(&jm); err == io.EOF { break } else if err != nil { return err } - if m.Progress != "" { - fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress) - } else if m.Error != "" { - return fmt.Errorf(m.Error) - } else { - fmt.Fprintf(out, "%s\n", m.Status) - } + jm.Display(out) } } else { if _, err := io.Copy(out, resp.Body); err != nil { diff --git a/runtime_test.go b/runtime_test.go index 66d92c8100..807097404d 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -17,12 +17,12 @@ import ( ) const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" ) var globalRuntime *Runtime diff --git a/server.go b/server.go index b92ed8fd73..2499d64397 100644 --- a/server.go +++ b/server.go @@ -32,8 +32,9 @@ func (srv *Server) DockerVersion() APIVersion { func (srv *Server) ContainerKill(name string) error { if container := srv.runtime.Get(name); container != nil { if err := container.Kill(); err != nil { - return fmt.Errorf("Error restarting container %s: %s", name, err) + return fmt.Errorf("Error killing container %s: %s", name, err) } + srv.SendEvent("kill", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -52,6 +53,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { if _, err := io.Copy(out, data); err != nil { return err } + srv.SendEvent("export", name) return nil } return fmt.Errorf("No such container: %s", name) @@ -209,13 +211,14 @@ func (srv *Server) DockerInfo() *APIInfo { imgcount = len(images) } return &APIInfo{ - Containers: len(srv.runtime.List()), - Images: imgcount, - MemoryLimit: srv.runtime.capabilities.MemoryLimit, - SwapLimit: srv.runtime.capabilities.SwapLimit, - Debug: os.Getenv("DEBUG") != "", - NFd: utils.GetTotalUsedFds(), - NGoroutines: runtime.NumGoroutine(), + Containers: len(srv.runtime.List()), + Images: imgcount, + MemoryLimit: srv.runtime.capabilities.MemoryLimit, + SwapLimit: srv.runtime.capabilities.SwapLimit, + Debug: os.Getenv("DEBUG") != "", + NFd: utils.GetTotalUsedFds(), + NGoroutines: runtime.NumGoroutine(), + NEventsListener: len(srv.events), } } @@ -810,6 +813,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) { } return "", err } + srv.SendEvent("create", container.ShortID()) return container.ShortID(), nil } @@ -818,6 +822,7 @@ func (srv *Server) ContainerRestart(name string, t int) error { if err := container.Restart(t); err != nil { return fmt.Errorf("Error restarting container %s: %s", name, err) } + srv.SendEvent("restart", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -837,6 +842,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { if err := srv.runtime.Destroy(container); err != nil { return fmt.Errorf("Error destroying container %s: %s", name, err) } + srv.SendEvent("destroy", name) if removeVolume { // Retrieve all volumes from all remaining containers @@ -903,6 +909,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error { return err } *imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)}) + srv.SendEvent("delete", utils.TruncateID(id)) return nil } return nil @@ -946,6 +953,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro } if tagDeleted { imgs = append(imgs, APIRmi{Untagged: img.ShortID()}) + srv.SendEvent("untagged", img.ShortID()) } if len(srv.runtime.repositories.ByID()[img.ID]) == 0 { if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil { @@ -1018,6 +1026,7 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { if err := container.Start(hostConfig); err != nil { return fmt.Errorf("Error starting container %s: %s", name, err) } + srv.SendEvent("start", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -1029,6 +1038,7 @@ func (srv *Server) ContainerStop(name string, t int) error { if err := container.Stop(t); err != nil { return fmt.Errorf("Error stopping container %s: %s", name, err) } + srv.SendEvent("stop", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -1162,15 +1172,23 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) ( enableCors: enableCors, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), + events: make(map[string]chan utils.JSONMessage), } runtime.srv = srv return srv, nil } +func (srv *Server) SendEvent(action, id string) { + for _, c := range srv.events { + c <- utils.JSONMessage{Status: action, ID: id} + } +} + type Server struct { sync.Mutex runtime *Runtime enableCors bool pullingPool map[string]struct{} pushingPool map[string]struct{} + events map[string]chan utils.JSONMessage } diff --git a/utils/utils.go b/utils/utils.go index 77b3f879cd..1523835f99 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -611,8 +611,23 @@ type JSONMessage struct { Status string `json:"status,omitempty"` Progress string `json:"progress,omitempty"` Error string `json:"error,omitempty"` + ID string `json:"id,omitempty"` } +func (jm *JSONMessage) Display(out io.Writer) (error) { + if jm.Progress != "" { + fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress) + } else if jm.Error != "" { + return fmt.Errorf(jm.Error) + } else if jm.ID != "" { + fmt.Fprintf(out, "%s: %s\n", jm.ID, jm.Status) + } else { + fmt.Fprintf(out, "%s\n", jm.Status) + } + return nil +} + + type StreamFormatter struct { json bool used bool From b8d52ec2669332988a972bff3b5f5d2e9d526b33 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 12 Jul 2013 15:09:20 +0000 Subject: [PATCH 28/36] add timestamp and change untagged -> untag --- server.go | 5 +++-- utils/utils.go | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 2499d64397..8ba90c69e3 100644 --- a/server.go +++ b/server.go @@ -19,6 +19,7 @@ import ( "runtime" "strings" "sync" + "time" ) func (srv *Server) DockerVersion() APIVersion { @@ -953,7 +954,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro } if tagDeleted { imgs = append(imgs, APIRmi{Untagged: img.ShortID()}) - srv.SendEvent("untagged", img.ShortID()) + srv.SendEvent("untag", img.ShortID()) } if len(srv.runtime.repositories.ByID()[img.ID]) == 0 { if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil { @@ -1180,7 +1181,7 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) ( func (srv *Server) SendEvent(action, id string) { for _, c := range srv.events { - c <- utils.JSONMessage{Status: action, ID: id} + c <- utils.JSONMessage{Status: action, ID: id, Time: time.Now().Unix()} } } diff --git a/utils/utils.go b/utils/utils.go index 1523835f99..acb015becd 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -612,9 +612,13 @@ type JSONMessage struct { Progress string `json:"progress,omitempty"` Error string `json:"error,omitempty"` ID string `json:"id,omitempty"` + Time int64 `json:"time,omitempty"` } func (jm *JSONMessage) Display(out io.Writer) (error) { + if jm.Time != 0 { + fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0)) + } if jm.Progress != "" { fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress) } else if jm.Error != "" { From 2e4d4c9f60d0ad92ab0cc84c56c060678222c4db Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 12 Jul 2013 16:29:23 +0000 Subject: [PATCH 29/36] add since for polling, rename some vars --- api.go | 51 +++++++++++++++++++++++++++++++++++++++++---------- commands.go | 10 ++++++++-- server.go | 33 +++++++++++++++++++-------------- 3 files changed, 68 insertions(+), 26 deletions(-) diff --git a/api.go b/api.go index 9d0348b608..0a7f5744b1 100644 --- a/api.go +++ b/api.go @@ -218,24 +218,55 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques } func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - events := make(chan utils.JSONMessage) - srv.Lock() - srv.events[r.RemoteAddr] = events - srv.Unlock() - w.Header().Set("Content-Type", "application/json") - wf := utils.NewWriteFlusher(w) - for { - event := <-events + sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) (bool, error) { b, err := json.Marshal(event) if err != nil { - continue + return true, nil } _, err = wf.Write(b) if err != nil { utils.Debugf("%s", err) srv.Lock() - delete(srv.events, r.RemoteAddr) + delete(srv.listeners, r.RemoteAddr) srv.Unlock() + return false, err + } + return false, nil + } + + if err := parseForm(r); err != nil { + return err + } + listener := make(chan utils.JSONMessage) + srv.Lock() + srv.listeners[r.RemoteAddr] = listener + srv.Unlock() + since, err := strconv.ParseInt(r.Form.Get("since"), 10, 0) + if err != nil { + since = 0 + } + w.Header().Set("Content-Type", "application/json") + wf := utils.NewWriteFlusher(w) + if since != 0 { + for _, event := range srv.events { + if event.Time >= since { + skip, err := sendEvent(wf, &event) + if skip { + continue + } + if err != nil { + return err + } + } + } + } + for { + event := <-listener + skip, err := sendEvent(wf, &event) + if skip { + continue + } + if err != nil { return err } } diff --git a/commands.go b/commands.go index 12647feead..2ab107bb77 100644 --- a/commands.go +++ b/commands.go @@ -1058,7 +1058,8 @@ func (cli *DockerCli) CmdCommit(args ...string) error { } func (cli *DockerCli) CmdEvents(args ...string) error { - cmd := Subcmd("events", "", "Get real time events from the server") + cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server") + since := cmd.String("since", "", "Show events previously created (used for polling).") if err := cmd.Parse(args); err != nil { return nil } @@ -1068,7 +1069,12 @@ func (cli *DockerCli) CmdEvents(args ...string) error { return nil } - if err := cli.stream("GET", "/events", nil, cli.out); err != nil { + v := url.Values{} + if *since != "" { + v.Set("since", *since) + } + + if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out); err != nil { return err } return nil diff --git a/server.go b/server.go index 8ba90c69e3..7efb850882 100644 --- a/server.go +++ b/server.go @@ -35,7 +35,7 @@ func (srv *Server) ContainerKill(name string) error { if err := container.Kill(); err != nil { return fmt.Errorf("Error killing container %s: %s", name, err) } - srv.SendEvent("kill", name) + srv.LogEvent("kill", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -54,7 +54,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { if _, err := io.Copy(out, data); err != nil { return err } - srv.SendEvent("export", name) + srv.LogEvent("export", name) return nil } return fmt.Errorf("No such container: %s", name) @@ -814,7 +814,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) { } return "", err } - srv.SendEvent("create", container.ShortID()) + srv.LogEvent("create", container.ShortID()) return container.ShortID(), nil } @@ -823,7 +823,7 @@ func (srv *Server) ContainerRestart(name string, t int) error { if err := container.Restart(t); err != nil { return fmt.Errorf("Error restarting container %s: %s", name, err) } - srv.SendEvent("restart", name) + srv.LogEvent("restart", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -843,7 +843,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { if err := srv.runtime.Destroy(container); err != nil { return fmt.Errorf("Error destroying container %s: %s", name, err) } - srv.SendEvent("destroy", name) + srv.LogEvent("destroy", name) if removeVolume { // Retrieve all volumes from all remaining containers @@ -910,7 +910,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error { return err } *imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)}) - srv.SendEvent("delete", utils.TruncateID(id)) + srv.LogEvent("delete", utils.TruncateID(id)) return nil } return nil @@ -954,7 +954,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro } if tagDeleted { imgs = append(imgs, APIRmi{Untagged: img.ShortID()}) - srv.SendEvent("untag", img.ShortID()) + srv.LogEvent("untag", img.ShortID()) } if len(srv.runtime.repositories.ByID()[img.ID]) == 0 { if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil { @@ -1027,7 +1027,7 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { if err := container.Start(hostConfig); err != nil { return fmt.Errorf("Error starting container %s: %s", name, err) } - srv.SendEvent("start", name) + srv.LogEvent("start", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -1039,7 +1039,7 @@ func (srv *Server) ContainerStop(name string, t int) error { if err := container.Stop(t); err != nil { return fmt.Errorf("Error stopping container %s: %s", name, err) } - srv.SendEvent("stop", name) + srv.LogEvent("stop", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -1173,15 +1173,19 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) ( enableCors: enableCors, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), - events: make(map[string]chan utils.JSONMessage), + events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events + listeners: make(map[string]chan utils.JSONMessage), } runtime.srv = srv return srv, nil } -func (srv *Server) SendEvent(action, id string) { - for _, c := range srv.events { - c <- utils.JSONMessage{Status: action, ID: id, Time: time.Now().Unix()} +func (srv *Server) LogEvent(action, id string) { + now := time.Now().Unix() + jm := utils.JSONMessage{Status: action, ID: id, Time: now} + srv.events = append(srv.events, jm) + for _, c := range srv.listeners { + c <- jm } } @@ -1191,5 +1195,6 @@ type Server struct { enableCors bool pullingPool map[string]struct{} pushingPool map[string]struct{} - events map[string]chan utils.JSONMessage + events []utils.JSONMessage + listeners map[string]chan utils.JSONMessage } From ec559c02b8dd70692821b3dc7f497d6fccaa88ad Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 17 Jul 2013 15:44:06 +0200 Subject: [PATCH 30/36] add docs --- docs/sources/api/docker_remote_api.rst | 4 +++ docs/sources/api/docker_remote_api_v1.3.rst | 30 +++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index f8a1381c53..792d43f501 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -44,6 +44,10 @@ What's new **New!** List the processes running inside a container. +.. http:get:: /events: + + **New!** Monitor docker's events via streaming or via polling + Builder (/build): - Simplify the upload of the build context diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index 273ec2e98d..69f480e453 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -1059,6 +1059,36 @@ Create a new image from a container's changes :statuscode 500: server error +Monitor Docker's events +*********************** + +.. http:get:: /events + + Get events from docker, either in real time via streaming, or via polling (using `since`) + + **Example request**: + + .. sourcecode:: http + + POST /events?since=1374067924 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"create","id":"dfdf82bd3881","time":1374067924} + {"status":"start","id":"dfdf82bd3881","time":1374067924} + {"status":"stop","id":"dfdf82bd3881","time":1374067966} + {"status":"destroy","id":"dfdf82bd3881","time":1374067970} + + :query since: timestamp used for polling + :statuscode 200: no error + :statuscode 500: server error + + 3. Going further ================ From 8b3519c5f7540b99d19ed2c3163aabf9897dd5a4 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 17 Jul 2013 13:56:09 +0000 Subject: [PATCH 31/36] getEvents a bit simpler --- api.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/api.go b/api.go index 0a7f5744b1..dd3e8eed0d 100644 --- a/api.go +++ b/api.go @@ -218,20 +218,21 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques } func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) (bool, error) { + sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) (error) { b, err := json.Marshal(event) if err != nil { - return true, nil + return fmt.Errorf("JSON error") } _, err = wf.Write(b) if err != nil { + // On error, evict the listener utils.Debugf("%s", err) srv.Lock() delete(srv.listeners, r.RemoteAddr) srv.Unlock() - return false, err + return err } - return false, nil + return nil } if err := parseForm(r); err != nil { @@ -248,10 +249,11 @@ func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Requ w.Header().Set("Content-Type", "application/json") wf := utils.NewWriteFlusher(w) if since != 0 { + // If since, send previous events that happened after the timestamp for _, event := range srv.events { if event.Time >= since { - skip, err := sendEvent(wf, &event) - if skip { + err := sendEvent(wf, &event) + if err != nil && err.Error() == "JSON error" { continue } if err != nil { @@ -262,8 +264,8 @@ func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Requ } for { event := <-listener - skip, err := sendEvent(wf, &event) - if skip { + err := sendEvent(wf, &event) + if err != nil && err.Error() == "JSON error" { continue } if err != nil { From 040c3b50d0a56baf98bd1ec14ad7d59c55a4ab31 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Jul 2013 14:35:14 +0000 Subject: [PATCH 32/36] use non-blocking channel to prevent dead-lock and add test for server --- server.go | 5 ++++- server_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index 7efb850882..8eff17a947 100644 --- a/server.go +++ b/server.go @@ -1185,7 +1185,10 @@ func (srv *Server) LogEvent(action, id string) { jm := utils.JSONMessage{Status: action, ID: id, Time: now} srv.events = append(srv.events, jm) for _, c := range srv.listeners { - c <- jm + select { // non blocking channel + case c <- jm: + default: + } } } diff --git a/server_test.go b/server_test.go index 05a286aaa8..8612b3fcea 100644 --- a/server_test.go +++ b/server_test.go @@ -1,7 +1,9 @@ package docker import ( + "github.com/dotcloud/docker/utils" "testing" + "time" ) func TestContainerTagImageDelete(t *testing.T) { @@ -163,3 +165,41 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) { } } + +func TestLogEvent(t *testing.T) { + runtime := mkRuntime(t) + srv := &Server{ + runtime: runtime, + events: make([]utils.JSONMessage, 0, 64), + listeners: make(map[string]chan utils.JSONMessage), + } + + srv.LogEvent("fakeaction", "fakeid") + + listener := make(chan utils.JSONMessage) + srv.Lock() + srv.listeners["test"] = listener + srv.Unlock() + + srv.LogEvent("fakeaction2", "fakeid") + + if len(srv.events) != 2 { + t.Fatalf("Expected 2 events, found %d", len(srv.events)) + } + go func() { + time.Sleep(200 * time.Millisecond) + srv.LogEvent("fakeaction3", "fakeid") + time.Sleep(200 * time.Millisecond) + srv.LogEvent("fakeaction4", "fakeid") + }() + + setTimeout(t, "Listening for events timed out", 2*time.Second, func() { + for i := 2; i < 4; i++ { + event := <-listener + if event != srv.events[i] { + t.Fatalf("Event received it different than expected") + } + } + }) + +} From ed7a4236b32f3f711c183dff8b70fbef17bae2d7 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Jul 2013 14:54:58 +0000 Subject: [PATCH 33/36] Add tests for the api --- api_test.go | 38 ++++++++++++++++++++++++++++++++++++++ commands_test.go | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/api_test.go b/api_test.go index 17ada96eab..13731dbf9e 100644 --- a/api_test.go +++ b/api_test.go @@ -89,6 +89,44 @@ func TestGetInfo(t *testing.T) { } } +func TestGetEvents(t *testing.T) { + runtime := mkRuntime(t) + srv := &Server{ + runtime: runtime, + events: make([]utils.JSONMessage, 0, 64), + listeners: make(map[string]chan utils.JSONMessage), + } + + srv.LogEvent("fakeaction", "fakeid") + srv.LogEvent("fakeaction2", "fakeid") + + req, err := http.NewRequest("GET", "/events?since=1", nil) + if err != nil { + t.Fatal(err) + } + + r := httptest.NewRecorder() + setTimeout(t, "", 500*time.Millisecond, func() { + if err := getEvents(srv, APIVERSION, r, req, nil); err != nil { + t.Fatal(err) + } + }) + + dec := json.NewDecoder(r.Body) + for i := 0; i < 2; i++ { + var jm utils.JSONMessage + if err := dec.Decode(&jm); err == io.EOF { + break + } else if err != nil { + t.Fatal(err) + } + if jm != srv.events[i] { + t.Fatalf("Event received it different than expected") + } + } + +} + func TestGetImagesJSON(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) diff --git a/commands_test.go b/commands_test.go index 233c6337d4..030fb29f95 100644 --- a/commands_test.go +++ b/commands_test.go @@ -38,7 +38,7 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { f() c <- false }() - if <-c { + if <-c && msg != "" { t.Fatal(msg) } } From a41384ad7312a21fd8fe429637c8d6b5c883fa2a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 14:31:42 +0000 Subject: [PATCH 34/36] add die event --- container.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/container.go b/container.go index f4a3c762ab..1e7bfed299 100644 --- a/container.go +++ b/container.go @@ -789,7 +789,9 @@ func (container *Container) monitor() { } } utils.Debugf("Process finished") - + if container.runtime != nil && container.runtime.srv != nil { + container.runtime.srv.LogEvent("die", container.ShortID()) + } exitCode := -1 if container.cmd != nil { exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() From 7aba68cd548d69e10e710029ca143b32fd291585 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 23 Jul 2013 19:55:38 +0000 Subject: [PATCH 35/36] update AUTHORS --- AUTHORS | 1 + runtime_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index 7506cd885c..86d03f6e12 100644 --- a/AUTHORS +++ b/AUTHORS @@ -76,6 +76,7 @@ Shawn Siefkas Silas Sewell Solomon Hykes Sridhar Ratnakumar +Stefan Praszalowicz Thatcher Peskens Thomas Bikeev Thomas Hansen diff --git a/runtime_test.go b/runtime_test.go index 66d92c8100..807097404d 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -17,12 +17,12 @@ import ( ) const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" ) var globalRuntime *Runtime From f4b41e1a6c2c5d531451bf2feeb3877e03eb8c1c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Jul 2013 12:28:22 +0000 Subject: [PATCH 36/36] fix tests --- api.go | 2 +- auth/auth.go | 1 + auth/auth_test.go | 14 ++++++++------ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/api.go b/api.go index 94c1a3b8d2..834c41a68c 100644 --- a/api.go +++ b/api.go @@ -179,7 +179,7 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques } func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) (error) { + sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) error { b, err := json.Marshal(event) if err != nil { return fmt.Errorf("JSON error") diff --git a/auth/auth.go b/auth/auth.go index 8f4d09fb8f..bffed49807 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -102,6 +102,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) { if err != nil { return nil, err } + authConfig.Auth = "" configFile.Configs[k] = authConfig } } diff --git a/auth/auth_test.go b/auth/auth_test.go index d036de7364..458a505ea2 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -11,7 +11,9 @@ import ( func TestEncodeAuth(t *testing.T) { newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} authStr := encodeAuth(newAuthConfig) - decAuthConfig, err := decodeAuth(authStr) + decAuthConfig := &AuthConfig{} + var err error + decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr) if err != nil { t.Fatal(err) } @@ -29,8 +31,8 @@ func TestEncodeAuth(t *testing.T) { func TestLogin(t *testing.T) { os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") defer os.Setenv("DOCKER_INDEX_URL", "") - authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp") - status, err := Login(authConfig, false) + authConfig := &AuthConfig{Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@dotcloud.com"} + status, err := Login(authConfig) if err != nil { t.Fatal(err) } @@ -49,8 +51,8 @@ func TestCreateAccount(t *testing.T) { } token := hex.EncodeToString(tokenBuffer)[:12] username := "ut" + token - authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp") - status, err := Login(authConfig, false) + authConfig := &AuthConfig{Username: username, Password: "test42", Email: "docker-ut+"+token+"@example.com"} + status, err := Login(authConfig) if err != nil { t.Fatal(err) } @@ -60,7 +62,7 @@ func TestCreateAccount(t *testing.T) { t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status) } - status, err = Login(authConfig, false) + status, err = Login(authConfig) if err == nil { t.Fatalf("Expected error but found nil instead") }