From d17d5e16aec04980c7cc1c2441ffa3626f81705e Mon Sep 17 00:00:00 2001 From: Zilin Du Date: Tue, 8 Oct 2013 14:29:22 -0700 Subject: [PATCH 001/184] replace 127.0.0.1 by the assigned IP address in the container's /etc/hosts file. Upstream-commit: 7d95d300abd9d9cc811713e53fd41adec66f2e00 Component: engine --- components/engine/container.go | 28 ++++++++++++++++++++++++++++ components/engine/runtime.go | 27 +-------------------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 316e0d259f..c6317c087e 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -579,10 +579,12 @@ func (container *Container) Start(hostConfig *HostConfig) error { } if container.runtime.networkManager.disabled { container.Config.NetworkDisabled = true + container.buildHostnameAndHostsFiles("127.0.0.1") } else { if err := container.allocateNetwork(); err != nil { return err } + container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress) } // Make sure the config is compatible with the current kernel @@ -868,6 +870,32 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) { return utils.NewBufReader(reader), nil } +func (container *Container) buildHostnameAndHostsFiles(IP string) { + container.HostnamePath = path.Join(container.root, "hostname") + ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644) + + hostsContent := []byte(` +127.0.0.1 localhost +::1 localhost ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters +`) + + container.HostsPath = path.Join(container.root, "hosts") + + if container.Config.Domainname != "" { + hostsContent = append([]byte(fmt.Sprintf("::1\t\t%s.%s %s\n", container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...) + hostsContent = append([]byte(fmt.Sprintf("%s\t%s.%s %s\n", IP, container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...) + } else { + hostsContent = append([]byte(fmt.Sprintf("::1\t\t%s\n", container.Config.Hostname)), hostsContent...) + hostsContent = append([]byte(fmt.Sprintf("%s\t%s\n", IP, container.Config.Hostname)), hostsContent...) + } + + ioutil.WriteFile(container.HostsPath, hostsContent, 0644) +} + func (container *Container) allocateNetwork() error { if container.Config.NetworkDisabled { return nil diff --git a/components/engine/runtime.go b/components/engine/runtime.go index d77ecca722..e4bca0457c 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -368,32 +368,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { return nil, err } - // Step 3: if hostname, build hostname and hosts files - container.HostnamePath = path.Join(container.root, "hostname") - ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644) - - hostsContent := []byte(` -127.0.0.1 localhost -::1 localhost ip6-localhost ip6-loopback -fe00::0 ip6-localnet -ff00::0 ip6-mcastprefix -ff02::1 ip6-allnodes -ff02::2 ip6-allrouters -`) - - container.HostsPath = path.Join(container.root, "hosts") - - if container.Config.Domainname != "" { - hostsContent = append([]byte(fmt.Sprintf("::1\t\t%s.%s %s\n", container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...) - hostsContent = append([]byte(fmt.Sprintf("127.0.0.1\t%s.%s %s\n", container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...) - } else { - hostsContent = append([]byte(fmt.Sprintf("::1\t\t%s\n", container.Config.Hostname)), hostsContent...) - hostsContent = append([]byte(fmt.Sprintf("127.0.0.1\t%s\n", container.Config.Hostname)), hostsContent...) - } - - ioutil.WriteFile(container.HostsPath, hostsContent, 0644) - - // Step 4: register the container + // Step 3: register the container if err := runtime.Register(container); err != nil { return nil, err } From 8ac7033a81e9c1fc0913d10a421d805b128d6e34 Mon Sep 17 00:00:00 2001 From: Zilin Du Date: Thu, 10 Oct 2013 09:44:48 -0700 Subject: [PATCH 002/184] change 127.0.0.1 -> 127.0.1.1 & remove ::1 -> hostname mapping Upstream-commit: c2912c82aa7398a10d39fe114eb6328abd636d82 Component: engine --- components/engine/container.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index c6317c087e..555f4a524e 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -579,7 +579,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { } if container.runtime.networkManager.disabled { container.Config.NetworkDisabled = true - container.buildHostnameAndHostsFiles("127.0.0.1") + container.buildHostnameAndHostsFiles("127.0.1.1") } else { if err := container.allocateNetwork(); err != nil { return err @@ -886,10 +886,8 @@ ff02::2 ip6-allrouters container.HostsPath = path.Join(container.root, "hosts") if container.Config.Domainname != "" { - hostsContent = append([]byte(fmt.Sprintf("::1\t\t%s.%s %s\n", container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...) hostsContent = append([]byte(fmt.Sprintf("%s\t%s.%s %s\n", IP, container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...) } else { - hostsContent = append([]byte(fmt.Sprintf("::1\t\t%s\n", container.Config.Hostname)), hostsContent...) hostsContent = append([]byte(fmt.Sprintf("%s\t%s\n", IP, container.Config.Hostname)), hostsContent...) } From 78b1cb1fcb80ac90f625d8e374054ca60bdf1019 Mon Sep 17 00:00:00 2001 From: Mathieu Le Marec - Pasquet Date: Fri, 25 Oct 2013 18:41:03 +0200 Subject: [PATCH 003/184] Do not drop sys_boot This fixes #2391 Upstream-commit: b64ce8e33cb0a63ede881da78580e37ea4f0d6a7 Component: engine --- components/engine/AUTHORS | 1 + components/engine/lxc_template.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/components/engine/AUTHORS b/components/engine/AUTHORS index 7b1bba49e7..13872ecebf 100644 --- a/components/engine/AUTHORS +++ b/components/engine/AUTHORS @@ -117,6 +117,7 @@ Marko Mikulicic Markus Fix Martin Redmond Matt Apperson +Mathieu Le Marec - Pasquet Matt Bachmann Matthew Mueller Maxim Treskin diff --git a/components/engine/lxc_template.go b/components/engine/lxc_template.go index 37232a89d3..315e84744a 100644 --- a/components/engine/lxc_template.go +++ b/components/engine/lxc_template.go @@ -113,7 +113,7 @@ lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,{{ if ind # (Note: 'lxc.cap.keep' is coming soon and should replace this under the # security principle 'deny all unless explicitly permitted', see # http://sourceforge.net/mailarchive/message.php?msg_id=31054627 ) -lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config +lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod setpcap sys_admin sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config {{end}} # limits From a1a49c89198b79e666a50901863423f8d6246fb3 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Thu, 3 Oct 2013 18:23:29 +0000 Subject: [PATCH 004/184] Container memory limit can be specified in kilobytes, megabytes or gigabytes -m 10 # 10 bytes -m 10b # 10 bytes -m 10k # 10240 bytes (10 * 1024) -m 10m # 10485760 bytes (10 * 1024 * 1024) -m 10g # 10737418240 bytes (10 * 1024 * 1024 * 1024) Units are case-insensitive, and 'kb', 'mb' and 'gb' are equivalent to 'k', 'm' and 'g'. Upstream-commit: 9ee9d2f9959390d1cda56accbbd975df18d157ad Component: engine --- components/engine/container.go | 22 ++++++++++--- .../engine/docs/sources/commandline/cli.rst | 2 +- components/engine/utils/utils.go | 32 ++++++++++++++++++ components/engine/utils/utils_test.go | 33 +++++++++++++++++++ 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index e67c8f5b0b..30c010a66c 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -162,7 +162,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.") flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached") flTty := cmd.Bool("t", false, "Allocate a pseudo-tty") - flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)") + flMemoryString := cmd.String("m", "", "Memory limit (format: , where unit = b, k, m or g)") flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file") flNetwork := cmd.Bool("n", true, "Enable networking for this container") flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container") @@ -170,9 +170,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)") cmd.String("name", "", "Assign a name to the container") - if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit { + if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") - *flMemory = 0 + *flMemoryString = "" } flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)") @@ -239,6 +239,18 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, } } + var flMemory int64 + + if *flMemoryString != "" { + parsedMemory, err := utils.RAMInBytes(*flMemoryString) + + if err != nil { + return nil, nil, cmd, err + } + + flMemory = parsedMemory + } + var binds []string // add any bind targets to the list of container volumes @@ -306,7 +318,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, Tty: *flTty, NetworkDisabled: !*flNetwork, OpenStdin: *flStdin, - Memory: *flMemory, + Memory: flMemory, CpuShares: *flCpuShares, AttachStdin: flAttach.Get("stdin"), AttachStdout: flAttach.Get("stdout"), @@ -330,7 +342,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, Links: flLinks, } - if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit { + if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") config.MemorySwap = -1 } diff --git a/components/engine/docs/sources/commandline/cli.rst b/components/engine/docs/sources/commandline/cli.rst index c6579da355..a3c2b9eeda 100644 --- a/components/engine/docs/sources/commandline/cli.rst +++ b/components/engine/docs/sources/commandline/cli.rst @@ -568,7 +568,7 @@ network communication. -h="": Container host name -i=false: Keep stdin open even if not attached -privileged=false: Give extended privileges to this container - -m=0: Memory limit (in bytes) + -m="": Memory limit (format: , where unit = b, k, m or g) -n=true: Enable networking for this container -p=[]: Map a network port to the container -rm=false: Automatically remove the container when it exits (incompatible with -d) diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index d53094397f..c37b3cc6a1 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -20,6 +20,7 @@ import ( "strings" "sync" "time" + "regexp" ) var ( @@ -176,6 +177,37 @@ func HumanSize(size int64) string { return fmt.Sprintf("%.4g %s", sizef, units[i]) } +// Parses a human-readable string representing an amount of RAM +// in bytes, kibibytes, mebibytes or gibibytes, and returns the +// number of bytes, or -1 if the string is unparseable. +// Units are case-insensitive, and the 'b' suffix is optional. +func RAMInBytes(size string) (bytes int64, err error) { + re, error := regexp.Compile("^(\\d+)([kKmMgG])?[bB]?$") + if error != nil { return -1, error } + + matches := re.FindStringSubmatch(size) + + if len(matches) != 3 { + return -1, fmt.Errorf("Invalid size: '%s'", size) + } + + memLimit, error := strconv.ParseInt(matches[1], 10, 0) + if error != nil { return -1, error } + + unit := strings.ToLower(matches[2]) + + if unit == "k" { + memLimit *= 1024 + } else if unit == "m" { + memLimit *= 1024*1024 + } else if unit == "g" { + memLimit *= 1024*1024*1024 + } + + return memLimit, nil +} + + func Trunc(s string, maxlen int) string { if len(s) <= maxlen { return s diff --git a/components/engine/utils/utils_test.go b/components/engine/utils/utils_test.go index 49f19bf759..8775bd0891 100644 --- a/components/engine/utils/utils_test.go +++ b/components/engine/utils/utils_test.go @@ -265,6 +265,39 @@ func TestHumanSize(t *testing.T) { } } +func TestRAMInBytes(t *testing.T) { + assertRAMInBytes(t, "32", false, 32) + assertRAMInBytes(t, "32b", false, 32) + assertRAMInBytes(t, "32B", false, 32) + assertRAMInBytes(t, "32k", false, 32*1024) + assertRAMInBytes(t, "32K", false, 32*1024) + assertRAMInBytes(t, "32kb", false, 32*1024) + assertRAMInBytes(t, "32Kb", false, 32*1024) + assertRAMInBytes(t, "32Mb", false, 32*1024*1024) + assertRAMInBytes(t, "32Gb", false, 32*1024*1024*1024) + + assertRAMInBytes(t, "", true, -1) + assertRAMInBytes(t, "hello", true, -1) + assertRAMInBytes(t, "-32", true, -1) + assertRAMInBytes(t, " 32 ", true, -1) + assertRAMInBytes(t, "32 mb", true, -1) + assertRAMInBytes(t, "32m b", true, -1) + assertRAMInBytes(t, "32bm", true, -1) +} + +func assertRAMInBytes(t *testing.T, size string, expectError bool, expectedBytes int64) { + actualBytes, err := RAMInBytes(size) + if (err != nil) && !expectError { + t.Errorf("Unexpected error parsing '%s': %s", size, err) + } + if (err == nil) && expectError { + t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes) + } + if actualBytes != expectedBytes { + t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes) + } +} + func TestParseHost(t *testing.T) { if addr, err := ParseHost("127.0.0.1", 4243, "0.0.0.0"); err != nil || addr != "tcp://0.0.0.0:4243" { t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr) From adc686474c8d19affd47cf475e3c56e00ab75d10 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Wed, 23 Oct 2013 19:04:03 -0700 Subject: [PATCH 005/184] Add known issues to sections via new "issues" extension. Upstream-commit: 4194617bfe1e29bae2c2515aefbed5f1f2a1ebc1 Component: engine --- components/engine/docs/sources/conf.py | 6 +++++- components/engine/docs/sources/use/builder.rst | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/components/engine/docs/sources/conf.py b/components/engine/docs/sources/conf.py index 7aa27c7b84..0ccd4a4ed5 100644 --- a/components/engine/docs/sources/conf.py +++ b/components/engine/docs/sources/conf.py @@ -40,7 +40,11 @@ html_additional_pages = { # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinxcontrib.httpdomain'] +extensions = ['sphinxcontrib.httpdomain', 'sphinx.ext.extlinks'] + +# Configure extlinks +extlinks = { 'issue': ('https://github.com/dotcloud/docker/issues/%s', + 'Issue ') } # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/components/engine/docs/sources/use/builder.rst b/components/engine/docs/sources/use/builder.rst index d1747c3fb3..a1fd85df91 100644 --- a/components/engine/docs/sources/use/builder.rst +++ b/components/engine/docs/sources/use/builder.rst @@ -116,6 +116,16 @@ core concepts of Docker where commits are cheap and containers can be created from any point in an image's history, much like source control. +Known Issues +............ + +* :issue:`783` is about file permissions problems that can occur when + using the AUFS file system. You might notice it during an attempt to + ``rm`` a file, for example. The issue describes a workaround. + + + + 3.4 CMD ------- From 938714910e543ee50e6fe6df8387c3a4f00b85b4 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Fri, 1 Nov 2013 16:06:46 -0700 Subject: [PATCH 006/184] Add known issues. Fix build warnings. Upstream-commit: 5a9adfe9fb5bc988185ef41ca637fbeee73e947c Component: engine --- components/engine/docs/sources/commandline/cli.rst | 12 ++++++++++++ .../docs/sources/contributing/contributing.rst | 9 ++++++--- components/engine/docs/sources/use/builder.rst | 6 +++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/components/engine/docs/sources/commandline/cli.rst b/components/engine/docs/sources/commandline/cli.rst index 8f778c32de..ab622bff54 100644 --- a/components/engine/docs/sources/commandline/cli.rst +++ b/components/engine/docs/sources/commandline/cli.rst @@ -408,6 +408,12 @@ Insert file from github The main process inside the container will be sent SIGKILL. +Known Issues (kill) +~~~~~~~~~~~~~~~~~~~ + +* :issue:`197` indicates that ``docker kill`` may leave directories + behind and make it difficult to remove the container. + .. _cli_login: ``login`` @@ -516,6 +522,12 @@ The main process inside the container will be sent SIGKILL. Remove one or more containers -link="": Remove the link instead of the actual container +Known Issues (rm) +~~~~~~~~~~~~~~~~~~~ + +* :issue:`197` indicates that ``docker kill`` may leave directories + behind and make it difficult to remove the container. + Examples: ~~~~~~~~~ diff --git a/components/engine/docs/sources/contributing/contributing.rst b/components/engine/docs/sources/contributing/contributing.rst index 3cdb0b6f14..3b3b3f8f88 100644 --- a/components/engine/docs/sources/contributing/contributing.rst +++ b/components/engine/docs/sources/contributing/contributing.rst @@ -10,13 +10,16 @@ Want to hack on Docker? Awesome! The repository includes `all the instructions you need to get started `_. -The developer environment `Dockerfile `_ +The `developer environment Dockerfile +`_ specifies the tools and versions used to test and build Docker. If you're making changes to the documentation, see the `README.md `_. -The documentation environment `Dockerfile `_ +The `documentation environment Dockerfile +`_ specifies the tools and versions used to build the Documentation. -Further interesting details can be found in the `Packaging hints `_. +Further interesting details can be found in the `Packaging hints +`_. diff --git a/components/engine/docs/sources/use/builder.rst b/components/engine/docs/sources/use/builder.rst index a1fd85df91..e48f3f2a48 100644 --- a/components/engine/docs/sources/use/builder.rst +++ b/components/engine/docs/sources/use/builder.rst @@ -116,13 +116,13 @@ core concepts of Docker where commits are cheap and containers can be created from any point in an image's history, much like source control. -Known Issues -............ +Known Issues (RUN) +.................. * :issue:`783` is about file permissions problems that can occur when using the AUFS file system. You might notice it during an attempt to ``rm`` a file, for example. The issue describes a workaround. - +* :issue:`2424` Locale will not be set automatically. From 14d0aebe52c031d53be017a3c4ae8d1fc9d35bee Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 4 Nov 2013 19:18:37 -0600 Subject: [PATCH 007/184] Updated Redis service example to use links Upstream-commit: 1182797c6d6bf4bb87e5edcddd75edb3f995dac0 Component: engine --- .../examples/running_redis_service.rst | 111 ++++++++++-------- 1 file changed, 65 insertions(+), 46 deletions(-) diff --git a/components/engine/docs/sources/examples/running_redis_service.rst b/components/engine/docs/sources/examples/running_redis_service.rst index ac869501b0..edcf73e657 100644 --- a/components/engine/docs/sources/examples/running_redis_service.rst +++ b/components/engine/docs/sources/examples/running_redis_service.rst @@ -9,74 +9,93 @@ Redis Service .. include:: example_header.inc -Very simple, no frills, Redis service. +Very simple, no frills, Redis service attached to a web application using a link. -Open a docker container ------------------------ +Create a docker container for Redis +----------------------------------- + +Firstly, we create a ``Dockerfile`` for our new Redis image. .. code-block:: bash - sudo docker run -i -t ubuntu /bin/bash + FROM ubuntu:12.10 + RUN apt-get update + RUN apt-get -y install redis-server + EXPOSE 6379 + ENTRYPOINT ["/usr/bin/redis-server"] -Building your image -------------------- - -Update your Docker container, install the Redis server. Once -installed, exit out of the Docker container. +Next we build an image from our ``Dockerfile``. Replace ```` +with your own user name. .. code-block:: bash - apt-get update - apt-get install redis-server - exit - -Snapshot the installation -------------------------- - -.. code-block:: bash - - sudo docker ps -a # grab the container id (this will be the first one in the list) - sudo docker commit /redis + sudo docker build -t /redis Run the service --------------- +Use the image we've just created and name your container ``redis``. + Running the service with ``-d`` runs the container in detached mode, leaving the -container running in the background. Use your snapshot. +container running in the background. + +Importantly, we're not exposing any ports on our container. Instead we're going to +use a container link to provide access to our Redis database. .. code-block:: bash - sudo docker run -d -p 6379 /redis /usr/bin/redis-server + sudo docker run -name redis -d /redis -Test 1 -++++++ +Create your web application container +------------------------------------- -Connect to the container with the ``redis-cli`` binary. +Next we can create a container for our application. We're going to use the ``-link`` +flag to create a link to the ``redis`` container we've just created with an alias of +``db``. This will create a secure tunnel to the ``redis`` container and expose the +Redis instance running inside that container to only this container. .. code-block:: bash - sudo docker ps # grab the new container id - sudo docker inspect # grab the ipaddress of the container - redis-cli -h -p 6379 - redis 10.0.3.32:6379> set docker awesome + sudo docker run -link redis:db -i -t ubuntu:12.10 /bin/bash + +Once inside our freshly created container we need to install Redis to get the +``redis-cli`` binary to test our connection. + +.. code-block:: bash + + apt-get update + apt-get -y install redis-server + service redis-server stop + +Now we can test the connection. Firstly, let's look at the available environmental +variables in our web application container. We can use these to get the IP and port +of our ``redis`` container. + +.. code-block:: bash + + env + . . . + DB_NAME=/violet_wolf/db + DB_PORT_6379_TCP_PORT=6379 + DB_PORT=tcp://172.17.0.33:6379 + DB_PORT_6379_TCP=tcp://172.17.0.33:6379 + DB_PORT_6379_TCP_ADDR=172.17.0.33 + DB_PORT_6379_TCP_PROTO=tcp + +We can see that we've got a small list of environmental varaibles prefixed with ``DB``. +The ``DB`` comes from the link alias specified when we launched the container. Let's use +the ``DB_PORT_6379_TCP_ADDR`` variable to connect to our Redis container. + +.. code-block:: bash + + redis-cli -h $DB_PORT_6379_TCP_ADDR + redis 172.17.0.33:6379> + redis 172.17.0.33:6379> set docker awesome OK - redis 10.0.3.32:6379> get docker + redis 172.17.0.33:6379> get docker "awesome" - redis 10.0.3.32:6379> exit + redis 172.17.0.33:6379> exit -Test 2 -++++++ +We could easily use this or other environment variables in our web application to make a +connection to our ``redis`` container. -Connect to the host os with the ``redis-cli`` binary. - -.. code-block:: bash - - sudo docker ps # grab the new container id - sudo docker port 6379 # grab the external port - ip addr show # grab the host ip address - redis-cli -h -p - redis 192.168.0.1:49153> set docker awesome - OK - redis 192.168.0.1:49153> get docker - "awesome" - redis 192.168.0.1:49153> exit From 255b88c5328f4fc107037059384b2636d8701586 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 5 Nov 2013 07:33:07 -0800 Subject: [PATCH 008/184] Check the output of iptables command. Upstream-commit: ff8a4ba0aa20dc3b382e2dcddcd89bb0bb168496 Component: engine --- components/engine/iptables/iptables.go | 43 +++++++++++++++------ components/engine/iptables/iptables_test.go | 4 +- components/engine/links.go | 8 +++- components/engine/network.go | 8 +++- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/components/engine/iptables/iptables.go b/components/engine/iptables/iptables.go index 463f443fda..8139b9881e 100644 --- a/components/engine/iptables/iptables.go +++ b/components/engine/iptables/iptables.go @@ -27,8 +27,10 @@ type Chain struct { } func NewChain(name, bridge string) (*Chain, error) { - if err := Raw("-t", "nat", "-N", name); err != nil { + if output, err := Raw("-t", "nat", "-N", name); err != nil { return nil, err + } else if len(output) != 0 { + return nil, fmt.Errorf("Error creating new iptables chain: %s", output) } chain := &Chain{ Name: name, @@ -52,13 +54,18 @@ func RemoveExistingChain(name string) error { } func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr string, dest_port int) error { - return Raw("-t", "nat", fmt.Sprint(action), c.Name, + if output, err := Raw("-t", "nat", fmt.Sprint(action), c.Name, "-p", proto, "-d", ip.String(), "--dport", strconv.Itoa(port), "!", "-i", c.Bridge, "-j", "DNAT", - "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))) + "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))); err != nil { + return err + } else if len(output) != 0 { + return fmt.Errorf("Error iptables forward: %s", output) + } + return nil } func (c *Chain) Prerouting(action Action, args ...string) error { @@ -66,7 +73,12 @@ func (c *Chain) Prerouting(action Action, args ...string) error { if len(args) > 0 { a = append(a, args...) } - return Raw(append(a, "-j", c.Name)...) + if output, err := Raw(append(a, "-j", c.Name)...); err != nil { + return err + } else if len(output) != 0 { + return fmt.Errorf("Error iptables prerouting: %s", output) + } + return nil } func (c *Chain) Output(action Action, args ...string) error { @@ -74,7 +86,12 @@ func (c *Chain) Output(action Action, args ...string) error { if len(args) > 0 { a = append(a, args...) } - return Raw(append(a, "-j", c.Name)...) + if output, err := Raw(append(a, "-j", c.Name)...); err != nil { + return err + } else if len(output) != 0 { + return fmt.Errorf("Error iptables output: %s", output) + } + return nil } func (c *Chain) Remove() error { @@ -94,17 +111,21 @@ func (c *Chain) Remove() error { // Check if an existing rule exists func Exists(args ...string) bool { - return Raw(append([]string{"-C"}, args...)...) == nil + if _, err := Raw(append([]string{"-C"}, args...)...); err != nil { + return false + } + return true } -func Raw(args ...string) error { +func Raw(args ...string) ([]byte, error) { path, err := exec.LookPath("iptables") if err != nil { - return ErrIptablesNotFound + return nil, ErrIptablesNotFound } - if err := exec.Command(path, args...).Run(); err != nil { - return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " ")) + output, err := exec.Command(path, args...).CombinedOutput() + if err != nil { + return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err) } - return nil + return output, err } diff --git a/components/engine/iptables/iptables_test.go b/components/engine/iptables/iptables_test.go index aad8acdb81..886a63c03f 100644 --- a/components/engine/iptables/iptables_test.go +++ b/components/engine/iptables/iptables_test.go @@ -6,13 +6,13 @@ import ( ) func TestIptables(t *testing.T) { - if err := Raw("-L"); err != nil { + if _, err := Raw("-L"); err != nil { t.Fatal(err) } path := os.Getenv("PATH") os.Setenv("PATH", "") defer os.Setenv("PATH", path) - if err := Raw("-L"); err == nil { + if _, err := Raw("-L"); err == nil { t.Fatal("Not finding iptables in the PATH should cause an error") } } diff --git a/components/engine/links.go b/components/engine/links.go index 60c738abb4..3c689d0bf3 100644 --- a/components/engine/links.go +++ b/components/engine/links.go @@ -120,7 +120,7 @@ func (l *Link) Disable() { func (l *Link) toggle(action string, ignoreErrors bool) error { for _, p := range l.Ports { - if err := iptables.Raw(action, "FORWARD", + if output, err := iptables.Raw(action, "FORWARD", "-i", l.BridgeInterface, "-o", l.BridgeInterface, "-p", p.Proto(), "-s", l.ParentIP, @@ -128,9 +128,11 @@ func (l *Link) toggle(action string, ignoreErrors bool) error { "-d", l.ChildIP, "-j", "ACCEPT"); !ignoreErrors && err != nil { return err + } else if len(output) != 0 { + return fmt.Errorf("Error toggle iptables forward: %s", output) } - if err := iptables.Raw(action, "FORWARD", + if output, err := iptables.Raw(action, "FORWARD", "-i", l.BridgeInterface, "-o", l.BridgeInterface, "-p", p.Proto(), "-s", l.ChildIP, @@ -138,6 +140,8 @@ func (l *Link) toggle(action string, ignoreErrors bool) error { "-d", l.ParentIP, "-j", "ACCEPT"); !ignoreErrors && err != nil { return err + } else if len(output) != 0 { + return fmt.Errorf("Error toggle iptables forward: %s", output) } } return nil diff --git a/components/engine/network.go b/components/engine/network.go index c237ccc86d..acb94ce1f5 100644 --- a/components/engine/network.go +++ b/components/engine/network.go @@ -141,9 +141,11 @@ func CreateBridgeIface(config *DaemonConfig) error { } if config.EnableIptables { - if err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr, + if output, err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr, "!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil { return fmt.Errorf("Unable to enable network bridge NAT: %s", err) + } else if len(output) != 0 { + return fmt.Errorf("Error iptables postrouting: %s", output) } } return nil @@ -656,8 +658,10 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { if !config.InterContainerCommunication { if !iptables.Exists(args...) { utils.Debugf("Disable inter-container communication") - if err := iptables.Raw(append([]string{"-A"}, args...)...); err != nil { + if output, err := iptables.Raw(append([]string{"-A"}, args...)...); err != nil { return nil, fmt.Errorf("Unable to prevent intercontainer communication: %s", err) + } else if len(output) != 0 { + return nil, fmt.Errorf("Error enabling iptables: %s", output) } } } else { From 8a182f4aaff8696f338d58c58b1d8e78eeb26f09 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 26 Oct 2013 17:19:35 -0700 Subject: [PATCH 009/184] Separate a) initialization of the http api and b) actually serving the api into 2 distinct jobs Upstream-commit: 433c8e9c7da7cd3cd952c3dce3763db70fc450e5 Component: engine --- components/engine/config.go | 2 -- components/engine/docker/docker.go | 8 ++++++-- components/engine/server.go | 31 +++++++++++++++--------------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/components/engine/config.go b/components/engine/config.go index 40c47e692c..d5f852c26f 100644 --- a/components/engine/config.go +++ b/components/engine/config.go @@ -9,7 +9,6 @@ import ( type DaemonConfig struct { Pidfile string Root string - ProtoAddresses []string AutoRestart bool EnableCors bool Dns []string @@ -36,7 +35,6 @@ func ConfigFromJob(job *engine.Job) *DaemonConfig { } else { config.BridgeIface = DefaultNetworkBridge } - config.ProtoAddresses = job.GetenvList("ProtoAddresses") config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp")) config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication") return &config diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index c500633a71..2fc864adf4 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -71,7 +71,8 @@ func main() { if err != nil { log.Fatal(err) } - job := eng.Job("serveapi") + // Load plugin: httpapi + job := eng.Job("initapi") job.Setenv("Pidfile", *pidfile) job.Setenv("Root", *flRoot) job.SetenvBool("AutoRestart", *flAutoRestart) @@ -79,12 +80,15 @@ func main() { job.Setenv("Dns", *flDns) job.SetenvBool("EnableIptables", *flEnableIptables) job.Setenv("BridgeIface", *bridgeName) - job.SetenvList("ProtoAddresses", flHosts) job.Setenv("DefaultIp", *flDefaultIp) job.SetenvBool("InterContainerCommunication", *flInterContainerComm) if err := job.Run(); err != nil { log.Fatal(err) } + // Serve api + if err := eng.Job("serveapi", flHosts...).Run(); err != nil { + log.Fatal(err) + } } else { if len(flHosts) > 1 { log.Fatal("Please specify only one -H") diff --git a/components/engine/server.go b/components/engine/server.go index 314df0256b..d8b70b88ae 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -33,30 +33,20 @@ func (srv *Server) Close() error { } func init() { - engine.Register("serveapi", JobServeApi) + engine.Register("initapi", jobInitApi) } -func JobServeApi(job *engine.Job) string { +// jobInitApi runs the remote api server `srv` as a daemon, +// Only one api server can run at the same time - this is enforced by a pidfile. +// The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup. +func jobInitApi(job *engine.Job) string { srv, err := NewServer(ConfigFromJob(job)) if err != nil { return err.Error() } - defer srv.Close() - if err := srv.Daemon(); err != nil { - return err.Error() - } - return "0" -} - -// Daemon runs the remote api server `srv` as a daemon, -// Only one api server can run at the same time - this is enforced by a pidfile. -// The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup. -func (srv *Server) Daemon() error { if err := utils.CreatePidFile(srv.runtime.config.Pidfile); err != nil { log.Fatal(err) } - defer utils.RemovePidFile(srv.runtime.config.Pidfile) - c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM)) go func() { @@ -66,8 +56,17 @@ func (srv *Server) Daemon() error { srv.Close() os.Exit(0) }() + err = engine.Register("serveapi", func(job *engine.Job) string { + return srv.ListenAndServe(job.Args...).Error() + }) + if err != nil { + return err.Error() + } + return "0" +} - protoAddrs := srv.runtime.config.ProtoAddresses + +func (srv *Server) ListenAndServe(protoAddrs ...string) error { chErrors := make(chan error, len(protoAddrs)) for _, protoAddr := range protoAddrs { protoAddrParts := strings.SplitN(protoAddr, "://", 2) From 6c362cebcc557b11e8455f8c7c9d02b807172673 Mon Sep 17 00:00:00 2001 From: Daniel Garcia Date: Sun, 13 Oct 2013 15:58:54 -0500 Subject: [PATCH 010/184] Add ability to mount volumes in readonly mode using -volumes-from Upstream-commit: f9cb6ae46a3478c19e85a2a159c4ac31223ec499 Component: engine --- components/engine/container.go | 24 +++++-- components/engine/container_test.go | 62 +++++++++++++++++++ .../engine/docs/sources/commandline/cli.rst | 13 +++- 3 files changed, 93 insertions(+), 6 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index a22484c2d6..db444e758c 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -199,7 +199,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)") var flVolumesFrom utils.ListOpts - cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container") + cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)") flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image") @@ -749,9 +749,23 @@ func (container *Container) Start() (err error) { // Apply volumes from another container if requested if container.Config.VolumesFrom != "" { - volumes := strings.Split(container.Config.VolumesFrom, ",") - for _, v := range volumes { - c := container.runtime.Get(v) + containerSpecs := strings.Split(container.Config.VolumesFrom, ",") + for _, containerSpec := range containerSpecs { + mountRW := true + specParts := strings.SplitN(containerSpec, ":", 2) + switch len(specParts) { + case 0: + return fmt.Errorf("Malformed volumes-from specification: %s", container.Config.VolumesFrom) + case 2: + switch specParts[1] { + case "ro": + mountRW = false + case "rw": // mountRW is already true + default: + return fmt.Errorf("Malformed volumes-from speficication: %s", containerSpec) + } + } + c := container.runtime.Get(specParts[0]) if c == nil { return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.ID) } @@ -764,7 +778,7 @@ func (container *Container) Start() (err error) { } container.Volumes[volPath] = id if isRW, exists := c.VolumesRW[volPath]; exists { - container.VolumesRW[volPath] = isRW + container.VolumesRW[volPath] = isRW && mountRW } } diff --git a/components/engine/container_test.go b/components/engine/container_test.go index cbabffc364..5540bb9c27 100644 --- a/components/engine/container_test.go +++ b/components/engine/container_test.go @@ -1338,6 +1338,68 @@ func TestBindMounts(t *testing.T) { } } +// Test that -volumes-from supports both read-only mounts +func TestFromVolumesInReadonlyMode(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + container, _, err := runtime.Create( + &Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"/bin/echo", "-n", "foobar"}, + Volumes: map[string]struct{}{"/test": {}}, + }, + "", + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + _, err = container.Output() + if err != nil { + t.Fatal(err) + } + if !container.VolumesRW["/test"] { + t.Fail() + } + + container2, _, err := runtime.Create( + &Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"/bin/echo", "-n", "foobar"}, + VolumesFrom: container.ID + ":ro", + }, + "", + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container2) + + _, err = container2.Output() + if err != nil { + t.Fatal(err) + } + + if container.Volumes["/test"] != container2.Volumes["/test"] { + t.Logf("container volumes do not match: %s | %s ", + container.Volumes["/test"], + container2.Volumes["/test"]) + t.Fail() + } + + _, exists := container2.VolumesRW["/test"] + if !exists { + t.Logf("container2 is missing '/test' volume: %s", container2.VolumesRW) + t.Fail() + } + + if container2.VolumesRW["/test"] != false { + t.Log("'/test' volume mounted in read-write mode, expected read-only") + t.Fail() + } +} + + // Test that VolumesRW values are copied to the new container. Regression test for #1201 func TestVolumesFromReadonlyMount(t *testing.T) { runtime := mkRuntime(t) diff --git a/components/engine/docs/sources/commandline/cli.rst b/components/engine/docs/sources/commandline/cli.rst index da0c262c4a..786fca6a6f 100644 --- a/components/engine/docs/sources/commandline/cli.rst +++ b/components/engine/docs/sources/commandline/cli.rst @@ -576,7 +576,7 @@ network communication. -u="": Username or UID -dns=[]: Set custom dns servers for the container -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume. - -volumes-from="": Mount all volumes from the given container + -volumes-from="": Mount all volumes from the given container(s) -entrypoint="": Overwrite the default entrypoint set by the image -w="": Working directory inside the container -lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" @@ -668,6 +668,17 @@ can access the network and environment of the redis container via environment variables. The ``-name`` flag will assign the name ``console`` to the newly created container. +.. code-block:: bash + + docker run -volumes-from 777f7dc92da7,ba8c0c54f0f2:ro -i -t ubuntu pwd + +The ``-volumes-from`` flag mounts all the defined volumes from the +refrence containers. Containers can be specified by a comma seperated +list or by repetitions of the ``-volumes-from`` argument. The container +id may be optionally suffixed with ``:ro`` or ``:rw`` to mount the volumes in +read-only or read-write mode, respectively. By default, the volumes are mounted +in the same mode (rw or ro) as the reference container. + .. _cli_search: ``search`` From 26507356b34b65b1d70480e58042653e0c3961ef Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 26 Oct 2013 19:24:01 -0700 Subject: [PATCH 011/184] Engine: 'start' starts the specified container Upstream-commit: 958b4a8757e83c3fada757b10dd1be4ab7bff5ee Component: engine --- components/engine/api.go | 24 +++----- components/engine/api_test.go | 6 +- components/engine/engine/engine.go | 10 ++-- components/engine/engine/hack.go | 23 +++++++ components/engine/engine/job.go | 60 ++++++++++++++++++- .../engine/engine/{init_test.go => utils.go} | 4 +- components/engine/server.go | 54 +++++++++++------ components/engine/server_test.go | 52 +++++++++++----- components/engine/utils_test.go | 13 ++++ 9 files changed, 186 insertions(+), 60 deletions(-) create mode 100644 components/engine/engine/hack.go rename components/engine/engine/{init_test.go => utils.go} (90%) diff --git a/components/engine/api.go b/components/engine/api.go index 0c06b2e6d1..1a3d92de32 100644 --- a/components/engine/api.go +++ b/components/engine/api.go @@ -639,26 +639,20 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R } func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - var hostConfig *HostConfig - // allow a nil body for backwards compatibility - if r.Body != nil { - if matchesContentType(r.Header.Get("Content-Type"), "application/json") { - hostConfig = &HostConfig{} - if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil { - return err - } - } - } - if vars == nil { return fmt.Errorf("Missing parameter") } name := vars["name"] - // Register any links from the host config before starting the container - if err := srv.RegisterLinks(name, hostConfig); err != nil { - return err + job := srv.Eng.Job("start", name) + // allow a nil body for backwards compatibility + if r.Body != nil { + if matchesContentType(r.Header.Get("Content-Type"), "application/json") { + if err := job.DecodeEnv(r.Body); err != nil { + return err + } + } } - if err := srv.ContainerStart(name, hostConfig); err != nil { + if err := job.Run(); err != nil { return err } w.WriteHeader(http.StatusNoContent) diff --git a/components/engine/api_test.go b/components/engine/api_test.go index fcca8ce83f..50a86e3f23 100644 --- a/components/engine/api_test.go +++ b/components/engine/api_test.go @@ -781,11 +781,11 @@ func TestPostContainersRestart(t *testing.T) { } func TestPostContainersStart(t *testing.T) { - runtime := mkRuntime(t) + eng := NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + runtime := srv.runtime defer nuke(runtime) - srv := &Server{runtime: runtime} - container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, diff --git a/components/engine/engine/engine.go b/components/engine/engine/engine.go index 8d67242ca2..a0d5a3c4ad 100644 --- a/components/engine/engine/engine.go +++ b/components/engine/engine/engine.go @@ -13,10 +13,11 @@ type Handler func(*Job) string var globalHandlers map[string]Handler +func init() { + globalHandlers = make(map[string]Handler) +} + func Register(name string, handler Handler) error { - if globalHandlers == nil { - globalHandlers = make(map[string]Handler) - } globalHandlers[name] = handler return nil } @@ -27,6 +28,7 @@ func Register(name string, handler Handler) error { type Engine struct { root string handlers map[string]Handler + hack Hack // data for temporary hackery (see hack.go) } // New initializes a new engine managing the directory specified at `root`. @@ -66,7 +68,7 @@ func New(root string) (*Engine, error) { // This function mimics `Command` from the standard os/exec package. func (eng *Engine) Job(name string, args ...string) *Job { job := &Job{ - eng: eng, + Eng: eng, Name: name, Args: args, Stdin: os.Stdin, diff --git a/components/engine/engine/hack.go b/components/engine/engine/hack.go new file mode 100644 index 0000000000..7f6e79c0e9 --- /dev/null +++ b/components/engine/engine/hack.go @@ -0,0 +1,23 @@ +package engine + + +type Hack map[string]interface{} + + +func (eng *Engine) Hack_GetGlobalVar(key string) interface{} { + if eng.hack == nil { + return nil + } + val, exists := eng.hack[key] + if !exists { + return nil + } + return val +} + +func (eng *Engine) Hack_SetGlobalVar(key string, val interface{}) { + if eng.hack == nil { + eng.hack = make(Hack) + } + eng.hack[key] = val +} diff --git a/components/engine/engine/job.go b/components/engine/engine/job.go index 0bde2a0beb..5c02fe15d5 100644 --- a/components/engine/engine/job.go +++ b/components/engine/engine/job.go @@ -1,6 +1,7 @@ package engine import ( + "bytes" "io" "strings" "fmt" @@ -22,7 +23,7 @@ import ( // This allows for richer error reporting. // type Job struct { - eng *Engine + Eng *Engine Name string Args []string env []string @@ -111,3 +112,60 @@ func (job *Job) SetenvList(key string, value []string) error { func (job *Job) Setenv(key, value string) { job.env = append(job.env, key + "=" + value) } + +// DecodeEnv decodes `src` as a json dictionary, and adds +// each decoded key-value pair to the environment. +// +// If `text` cannot be decoded as a json dictionary, an error +// is returned. +func (job *Job) DecodeEnv(src io.Reader) error { + m := make(map[string]interface{}) + if err := json.NewDecoder(src).Decode(&m); err != nil { + return err + } + for k, v := range m { + if sval, ok := v.(string); ok { + job.Setenv(k, sval) + } else if val, err := json.Marshal(v); err == nil { + job.Setenv(k, string(val)) + } else { + job.Setenv(k, fmt.Sprintf("%v", v)) + } + } + return nil +} + +func (job *Job) EncodeEnv(dst io.Writer) error { + return json.NewEncoder(dst).Encode(job.Environ()) +} + +func (job *Job) ExportEnv(dst interface{}) error { + var buf bytes.Buffer + if err := job.EncodeEnv(&buf); err != nil { + return err + } + if err := json.NewDecoder(&buf).Decode(dst); err != nil { + return err + } + return nil +} + +func (job *Job) ImportEnv(src interface{}) error { + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(src); err != nil { + return err + } + if err := job.DecodeEnv(&buf); err != nil { + return err + } + return nil +} + +func (job *Job) Environ() map[string]string { + m := make(map[string]string) + for _, kv := range job.env { + parts := strings.SplitN(kv, "=", 2) + m[parts[0]] = parts[1] + } + return m +} diff --git a/components/engine/engine/init_test.go b/components/engine/engine/utils.go similarity index 90% rename from components/engine/engine/init_test.go rename to components/engine/engine/utils.go index 5c03ded87d..58e5f9aae2 100644 --- a/components/engine/engine/init_test.go +++ b/components/engine/engine/utils.go @@ -15,7 +15,7 @@ func init() { Register("dummy", func(job *Job) string { return ""; }) } -func mkEngine(t *testing.T) *Engine { +func NewTestEngine(t *testing.T) *Engine { // Use the caller function name as a prefix. // This helps trace temp directories back to their test. pc, _, _, _ := runtime.Caller(1) @@ -38,5 +38,5 @@ func mkEngine(t *testing.T) *Engine { } func mkJob(t *testing.T, name string, args ...string) *Job { - return mkEngine(t).Job(name, args...) + return NewTestEngine(t).Job(name, args...) } diff --git a/components/engine/server.go b/components/engine/server.go index d8b70b88ae..efc34d14e1 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -40,7 +40,7 @@ func init() { // Only one api server can run at the same time - this is enforced by a pidfile. // The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup. func jobInitApi(job *engine.Job) string { - srv, err := NewServer(ConfigFromJob(job)) + srv, err := NewServer(job.Eng, ConfigFromJob(job)) if err != nil { return err.Error() } @@ -56,17 +56,19 @@ func jobInitApi(job *engine.Job) string { srv.Close() os.Exit(0) }() - err = engine.Register("serveapi", func(job *engine.Job) string { - return srv.ListenAndServe(job.Args...).Error() - }) - if err != nil { + job.Eng.Hack_SetGlobalVar("httpapi.server", srv) + if err := engine.Register("start", srv.ContainerStart); err != nil { + return err.Error() + } + if err := engine.Register("serveapi", srv.ListenAndServe); err != nil { return err.Error() } return "0" } -func (srv *Server) ListenAndServe(protoAddrs ...string) error { +func (srv *Server) ListenAndServe(job *engine.Job) string { + protoAddrs := job.Args chErrors := make(chan error, len(protoAddrs)) for _, protoAddr := range protoAddrs { protoAddrParts := strings.SplitN(protoAddr, "://", 2) @@ -80,7 +82,7 @@ func (srv *Server) ListenAndServe(protoAddrs ...string) error { log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } default: - return fmt.Errorf("Invalid protocol format.") + return "Invalid protocol format." } go func() { chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, true) @@ -89,10 +91,10 @@ func (srv *Server) ListenAndServe(protoAddrs ...string) error { for i := 0; i < len(protoAddrs); i += 1 { err := <-chErrors if err != nil { - return err + return err.Error() } } - return nil + return "0" } func (srv *Server) DockerVersion() APIVersion { @@ -1282,8 +1284,7 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error { return fmt.Errorf("No such container: %s", name) } - // Register links - if hostConfig != nil && hostConfig.Links != nil { + if hostConfig.Links != nil { for _, l := range hostConfig.Links { parts, err := parseLink(l) if err != nil { @@ -1296,7 +1297,6 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error { if child == nil { return fmt.Errorf("Could not get container for %s", parts["name"]) } - if err := runtime.RegisterLink(container, child, parts["alias"]); err != nil { return err } @@ -1312,22 +1312,36 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error { return nil } -func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { +func (srv *Server) ContainerStart(job *engine.Job) string { + if len(job.Args) < 1 { + return fmt.Sprintf("Usage: %s container_id", job.Name) + } + name := job.Args[0] runtime := srv.runtime container := runtime.Get(name) if container == nil { - return fmt.Errorf("No such container: %s", name) + return fmt.Sprintf("No such container: %s", name) } - if hostConfig != nil { - container.hostConfig = hostConfig + // If no environment was set, then no hostconfig was passed. + if len(job.Environ()) > 0 { + var hostConfig HostConfig + if err := job.ExportEnv(&hostConfig); err != nil { + return err.Error() + } + // Register any links from the host config before starting the container + // FIXME: we could just pass the container here, no need to lookup by name again. + if err := srv.RegisterLinks(name, &hostConfig); err != nil { + return err.Error() + } + container.hostConfig = &hostConfig container.ToDisk() } if err := container.Start(); err != nil { - return fmt.Errorf("Cannot start container %s: %s", name, err) + return fmt.Sprintf("Cannot start container %s: %s", name, err) } srv.LogEvent("start", container.ShortID(), runtime.repositories.ImageName(container.Image)) - return nil + return "0" } func (srv *Server) ContainerStop(name string, t int) error { @@ -1478,12 +1492,13 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(config *DaemonConfig) (*Server, error) { +func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) { runtime, err := NewRuntime(config) if err != nil { return nil, err } srv := &Server{ + Eng: eng, runtime: runtime, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), @@ -1527,4 +1542,5 @@ type Server struct { events []utils.JSONMessage listeners map[string]chan utils.JSONMessage reqFactory *utils.HTTPRequestFactory + Eng *engine.Engine } diff --git a/components/engine/server_test.go b/components/engine/server_test.go index 7f9cbaadfd..20894f8fe1 100644 --- a/components/engine/server_test.go +++ b/components/engine/server_test.go @@ -1,6 +1,7 @@ package docker import ( + "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/utils" "strings" "testing" @@ -109,10 +110,11 @@ func TestCreateRm(t *testing.T) { } func TestCreateRmVolumes(t *testing.T) { - runtime := mkRuntime(t) - defer nuke(runtime) + eng := engine.NewTestEngine(t) - srv := &Server{runtime: runtime} + srv := mkServerFromEngine(eng, t) + runtime := srv.runtime + defer nuke(runtime) config, hostConfig, _, err := ParseRun([]string{"-v", "/srv", GetTestImage(runtime).ID, "echo test"}, nil) if err != nil { @@ -128,8 +130,11 @@ func TestCreateRmVolumes(t *testing.T) { t.Errorf("Expected 1 container, %v found", len(runtime.List())) } - err = srv.ContainerStart(id, hostConfig) - if err != nil { + job := eng.Job("start", id) + if err := job.ImportEnv(hostConfig); err != nil { + t.Fatal(err) + } + if err := job.Run(); err != nil { t.Fatal(err) } @@ -169,11 +174,11 @@ func TestCommit(t *testing.T) { } func TestCreateStartRestartStopStartKillRm(t *testing.T) { - runtime := mkRuntime(t) + eng := engine.NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + runtime := srv.runtime defer nuke(runtime) - srv := &Server{runtime: runtime} - config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil) if err != nil { t.Fatal(err) @@ -188,7 +193,11 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { t.Errorf("Expected 1 container, %v found", len(runtime.List())) } - if err := srv.ContainerStart(id, hostConfig); err != nil { + job := eng.Job("start", id) + if err := job.ImportEnv(hostConfig); err != nil { + t.Fatal(err) + } + if err := job.Run(); err != nil { t.Fatal(err) } @@ -200,7 +209,11 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { t.Fatal(err) } - if err := srv.ContainerStart(id, hostConfig); err != nil { + job = eng.Job("start", id) + if err := job.ImportEnv(hostConfig); err != nil { + t.Fatal(err) + } + if err := job.Run(); err != nil { t.Fatal(err) } @@ -384,9 +397,10 @@ func TestLogEvent(t *testing.T) { } func TestRmi(t *testing.T) { - runtime := mkRuntime(t) + eng := engine.NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + runtime := srv.runtime defer nuke(runtime) - srv := &Server{runtime: runtime} initialImages, err := srv.Images(false, "") if err != nil { @@ -404,8 +418,11 @@ func TestRmi(t *testing.T) { } //To remove - err = srv.ContainerStart(containerID, hostConfig) - if err != nil { + job := eng.Job("start", containerID) + if err := job.ImportEnv(hostConfig); err != nil { + t.Fatal(err) + } + if err := job.Run(); err != nil { t.Fatal(err) } @@ -425,8 +442,11 @@ func TestRmi(t *testing.T) { } //To remove - err = srv.ContainerStart(containerID, hostConfig) - if err != nil { + job = eng.Job("start", containerID) + if err := job.ImportEnv(hostConfig); err != nil { + t.Fatal(err) + } + if err := job.Run(); err != nil { t.Fatal(err) } diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 14cbd7c6be..282954285c 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -40,6 +41,18 @@ func mkRuntime(f Fataler) *Runtime { return runtime } +func mkServerFromEngine(eng *engine.Engine, t Fataler) *Server { + iSrv := eng.Hack_GetGlobalVar("httpapi.server") + if iSrv == nil { + t.Fatal("Legacy server field not set in engine") + } + srv, ok := iSrv.(*Server) + if !ok { + t.Fatal("Legacy server field in engine does not cast to *Server") + } + return srv +} + // A common interface to access the Fatal method of // both testing.B and testing.T. type Fataler interface { From 6d109989282c8612909a605cff595fd88efcc833 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Oct 2013 06:51:43 +0000 Subject: [PATCH 012/184] httpapi: don't create a pidfile if it isn't set in the configuration Upstream-commit: 7b17d555992c65c6b10da21c8aa48062a1aba0d9 Component: engine --- components/engine/server.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/engine/server.go b/components/engine/server.go index efc34d14e1..de79dd966d 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -44,8 +44,11 @@ func jobInitApi(job *engine.Job) string { if err != nil { return err.Error() } - if err := utils.CreatePidFile(srv.runtime.config.Pidfile); err != nil { - log.Fatal(err) + if srv.runtime.config.Pidfile != "" { + job.Logf("Creating pidfile") + if err := utils.CreatePidFile(srv.runtime.config.Pidfile); err != nil { + log.Fatal(err) + } } c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM)) From 02af0b76845576b17e6018c518795aa22f5fdda1 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Oct 2013 06:54:51 +0000 Subject: [PATCH 013/184] Engine: fix a bug which caused handlers to be shared between multiple engine instances Upstream-commit: 847411a1ee6e5ee5d051fc4729425215dc0c8561 Component: engine --- components/engine/engine/engine.go | 22 +++++++++++++++++++++- components/engine/server.go | 4 ++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/components/engine/engine/engine.go b/components/engine/engine/engine.go index a0d5a3c4ad..956847adee 100644 --- a/components/engine/engine/engine.go +++ b/components/engine/engine/engine.go @@ -18,6 +18,10 @@ func init() { } func Register(name string, handler Handler) error { + _, exists := globalHandlers[name] + if exists { + return fmt.Errorf("Can't overwrite global handler for command %s", name) + } globalHandlers[name] = handler return nil } @@ -31,6 +35,17 @@ type Engine struct { hack Hack // data for temporary hackery (see hack.go) } +func (eng *Engine) Register(name string, handler Handler) error { + eng.Logf("Register(%s) (handlers=%v)", name, eng.handlers) + _, exists := eng.handlers[name] + if exists { + return fmt.Errorf("Can't overwrite handler for command %s", name) + } + eng.handlers[name] = handler + return nil +} + + // New initializes a new engine managing the directory specified at `root`. // `root` is used to store containers and any other state private to the engine. // Changing the contents of the root without executing a job will cause unspecified @@ -59,7 +74,12 @@ func New(root string) (*Engine, error) { } eng := &Engine{ root: root, - handlers: globalHandlers, + handlers: make(map[string]Handler), + id: utils.RandomString(), + } + // Copy existing global handlers + for k, v := range globalHandlers { + eng.handlers[k] = v } return eng, nil } diff --git a/components/engine/server.go b/components/engine/server.go index de79dd966d..72ad39cdce 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -60,10 +60,10 @@ func jobInitApi(job *engine.Job) string { os.Exit(0) }() job.Eng.Hack_SetGlobalVar("httpapi.server", srv) - if err := engine.Register("start", srv.ContainerStart); err != nil { + if err := job.Eng.Register("start", srv.ContainerStart); err != nil { return err.Error() } - if err := engine.Register("serveapi", srv.ListenAndServe); err != nil { + if err := job.Eng.Register("serveapi", srv.ListenAndServe); err != nil { return err.Error() } return "0" From be8463c1d5899c8f3ea3fcd26db5155334fac8a3 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Oct 2013 06:57:29 +0000 Subject: [PATCH 014/184] Engine: don't export private testing utilities Upstream-commit: ca6f0aa107117d2125a63eb5e78d74095bf08a4c Component: engine --- components/engine/engine/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/engine/utils.go b/components/engine/engine/utils.go index 58e5f9aae2..14ee4742ff 100644 --- a/components/engine/engine/utils.go +++ b/components/engine/engine/utils.go @@ -15,7 +15,7 @@ func init() { Register("dummy", func(job *Job) string { return ""; }) } -func NewTestEngine(t *testing.T) *Engine { +func newTestEngine(t *testing.T) *Engine { // Use the caller function name as a prefix. // This helps trace temp directories back to their test. pc, _, _, _ := runtime.Caller(1) @@ -38,5 +38,5 @@ func NewTestEngine(t *testing.T) *Engine { } func mkJob(t *testing.T, name string, args ...string) *Job { - return NewTestEngine(t).Job(name, args...) + return newTestEngine(t).Job(name, args...) } From 18c16d13e346c415036cf1cfc1ffdccc9dd10bf1 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Oct 2013 07:01:15 +0000 Subject: [PATCH 015/184] Engine: improved logging and identification of jobs Upstream-commit: 4e7cb37dcc18975010df630d8c9580a3a65e0e69 Component: engine --- components/engine/engine/engine.go | 15 +++++++++++++++ components/engine/engine/job.go | 28 +++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/components/engine/engine/engine.go b/components/engine/engine/engine.go index 956847adee..565c6ed4fa 100644 --- a/components/engine/engine/engine.go +++ b/components/engine/engine/engine.go @@ -5,6 +5,7 @@ import ( "os" "log" "runtime" + "strings" "github.com/dotcloud/docker/utils" ) @@ -33,6 +34,11 @@ type Engine struct { root string handlers map[string]Handler hack Hack // data for temporary hackery (see hack.go) + id string +} + +func (eng *Engine) Root() string { + return eng.root } func (eng *Engine) Register(name string, handler Handler) error { @@ -84,6 +90,10 @@ func New(root string) (*Engine, error) { return eng, nil } +func (eng *Engine) String() string { + return fmt.Sprintf("%s|%s", eng.Root(), eng.id[:8]) +} + // Job creates a new job which can later be executed. // This function mimics `Command` from the standard os/exec package. func (eng *Engine) Job(name string, args ...string) *Job { @@ -102,3 +112,8 @@ func (eng *Engine) Job(name string, args ...string) *Job { return job } + +func (eng *Engine) Logf(format string, args ...interface{}) (n int, err error) { + prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n")) + return fmt.Printf(prefixedFormat, args...) +} diff --git a/components/engine/engine/job.go b/components/engine/engine/job.go index 5c02fe15d5..7a261409e0 100644 --- a/components/engine/engine/job.go +++ b/components/engine/engine/job.go @@ -6,7 +6,6 @@ import ( "strings" "fmt" "encoding/json" - "github.com/dotcloud/docker/utils" ) // A job is the fundamental unit of work in the docker engine. @@ -38,9 +37,10 @@ type Job struct { // If the job returns a failure status, an error is returned // which includes the status. func (job *Job) Run() error { - randId := utils.RandomString()[:4] - fmt.Printf("Job #%s: %s\n", randId, job) - defer fmt.Printf("Job #%s: %s = '%s'", randId, job, job.status) + job.Logf("{") + defer func() { + job.Logf("}") + }() if job.handler == nil { job.status = "command not found" } else { @@ -54,7 +54,20 @@ func (job *Job) Run() error { // String returns a human-readable description of `job` func (job *Job) String() string { - return strings.Join(append([]string{job.Name}, job.Args...), " ") + s := fmt.Sprintf("%s.%s(%s)", job.Eng, job.Name, strings.Join(job.Args, ", ")) + // FIXME: if a job returns the empty string, it will be printed + // as not having returned. + // (this only affects String which is a convenience function). + if job.status != "" { + var okerr string + if job.status == "0" { + okerr = "OK" + } else { + okerr = "ERR" + } + s = fmt.Sprintf("%s = %s (%s)", s, okerr, job.status) + } + return s } func (job *Job) Getenv(key string) (value string) { @@ -169,3 +182,8 @@ func (job *Job) Environ() map[string]string { } return m } + +func (job *Job) Logf(format string, args ...interface{}) (n int, err error) { + prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n")) + return fmt.Fprintf(job.Stdout, prefixedFormat, args...) +} From 5d5633048a460aae53a7bdf8359ef620066b94b6 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Oct 2013 07:06:43 +0000 Subject: [PATCH 016/184] Engine: optional environment variable 'Logging' in 'serveapi' Upstream-commit: 02ddaad5d985186eed94dea4105a57fa21ba24db Component: engine --- components/engine/docker/docker.go | 4 +++- components/engine/server.go | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index 2fc864adf4..e58bd40013 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -86,7 +86,9 @@ func main() { log.Fatal(err) } // Serve api - if err := eng.Job("serveapi", flHosts...).Run(); err != nil { + job := eng.Job("serveapi", flHosts...) + job.Setenv("Logging", true) + if err := job.Run(); err != nil { log.Fatal(err) } } else { diff --git a/components/engine/server.go b/components/engine/server.go index 72ad39cdce..c041491ead 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -88,7 +88,8 @@ func (srv *Server) ListenAndServe(job *engine.Job) string { return "Invalid protocol format." } go func() { - chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, true) + // FIXME: merge Server.ListenAndServe with ListenAndServe + chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, job.GetenvBool("Logging")) }() } for i := 0; i < len(protoAddrs); i += 1 { From 8da2c74ae50b4d2309d29cc0183e99b8a7ffa6db Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 5 Nov 2013 14:28:57 -0600 Subject: [PATCH 017/184] Removed redundant linking example Upstream-commit: e88368b34cab8d4b7d5a1179cdd25fc9cd2b3d79 Component: engine --- .../engine/docs/sources/examples/index.rst | 1 - .../sources/examples/linking_into_redis.rst | 137 ------------------ 2 files changed, 138 deletions(-) delete mode 100644 components/engine/docs/sources/examples/linking_into_redis.rst diff --git a/components/engine/docs/sources/examples/index.rst b/components/engine/docs/sources/examples/index.rst index 6167e35652..ea3294d2d3 100644 --- a/components/engine/docs/sources/examples/index.rst +++ b/components/engine/docs/sources/examples/index.rst @@ -24,4 +24,3 @@ to more substantial services like those which you might find in production. postgresql_service mongodb running_riak_service - linking_into_redis diff --git a/components/engine/docs/sources/examples/linking_into_redis.rst b/components/engine/docs/sources/examples/linking_into_redis.rst deleted file mode 100644 index 1b91b8451d..0000000000 --- a/components/engine/docs/sources/examples/linking_into_redis.rst +++ /dev/null @@ -1,137 +0,0 @@ -:title: Linking to an Redis container -:description: Running redis linked into your web app -:keywords: docker, example, networking, redis, link - -.. _linking_redis: - -Linking Redis -============= - -.. include:: example_header.inc - -Building a Redis container to link as a child of our web application. - -Building the Redis container ----------------------------- - -Lets build a Redis image with the following Dockerfile. - -First checkout the Redis source code. - -.. code-block:: bash - - git clone https://github.com/antirez/redis.git - cd redis - git checkout 2.6 - - -Now let's create a Dockerfile in the root of the Redis repository. - -.. code-block:: bash - - # Build redis from source - # Make sure you have the redis source code checked out in - # the same directory as this Dockerfile - FROM ubuntu - - RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list - RUN apt-get update - RUN apt-get upgrade -y - - RUN apt-get install -y gcc make g++ build-essential libc6-dev tcl - - ADD . /redis - - RUN (cd /redis && make) - - RUN mkdir -p /redis-data - VOLUME ["/redis-data"] - EXPOSE 6379 - - ENTRYPOINT ["/redis/src/redis-server"] - CMD ["--dir", "/redis-data"] - - # docker build our new redis image from source - docker build -t redis-2.6 . - - -We need to ``EXPOSE`` the default port of 6379 so that our link knows what ports -to connect to our Redis container on. If you do not expose any ports for the -image then docker will not be able to establish the link between containers. - - -Run the Redis container ------------------------ - -.. code-block:: bash - - sudo docker run -d -e PASSWORD=docker -name redis redis-2.6 --requirepass docker - -This will run our Redis container with the password docker -to secure our service. By specifying the ``-name`` flag on run -we will assign the name ``redis`` to this container. If we do not specify a name for -our container via the ``-name`` flag docker will automatically generate a name for us. -We can issue all the commands that you would expect; start, stop, attach, using the name for our container. -The name also allows us to link other containers into this one. - -Linking Redis as a child ------------------------- - -Next we can start a new web application that has a dependency on Redis and apply a link -to connect both containers. If you noticed when running our Redis server we did not use -the ``-p`` flag to publish the Redis port to the host system. Redis exposed port 6379 via the Dockerfile -and this is all we need to establish a link. - -Now let's start our web application with a link into Redis. - -.. code-block:: bash - - sudo docker run -t -i -link redis:db -name webapp ubuntu bash - - root@4c01db0b339c:/# env - - HOSTNAME=4c01db0b339c - DB_NAME=/webapp/db - TERM=xterm - DB_PORT=tcp://172.17.0.8:6379 - DB_PORT_6379_TCP=tcp://172.17.0.8:6379 - DB_PORT_6379_TCP_PROTO=tcp - DB_PORT_6379_TCP_ADDR=172.17.0.8 - DB_PORT_6379_TCP_PORT=6379 - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin - PWD=/ - DB_ENV_PASSWORD=docker - SHLVL=1 - HOME=/ - container=lxc - _=/usr/bin/env - root@4c01db0b339c:/# - - -When we inspect the environment of the linked container we can see a few extra environment -variables have been added. When you specified ``-link redis:db`` you are telling docker -to link the container named ``redis`` into this new container with the alias ``db``. -Environment variables are prefixed with the alias so that the parent container can access -network and environment information from the containers that are linked into it. - -.. code-block:: bash - - # The name of the child container - DB_NAME=/webapp/db - - # The default protocol, ip, and port of the service running in the container - DB_PORT=tcp://172.17.0.8:6379 - - # A specific protocol, ip, and port of various services - DB_PORT_6379_TCP=tcp://172.17.0.8:6379 - DB_PORT_6379_TCP_PROTO=tcp - DB_PORT_6379_TCP_ADDR=172.17.0.8 - DB_PORT_6379_TCP_PORT=6379 - - # Get environment variables of the container - DB_ENV_PASSWORD=docker - - -Accessing the network information along with the environment of the child container allows -us to easily connect to the Redis service on the specific IP and port and use the password -specified in the environment. From c13616829073ffd8846ef2eb3c1c20c484161837 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Oct 2013 07:13:45 +0000 Subject: [PATCH 018/184] Hack: simplify the creation of test directories Upstream-commit: 5a85456d481a5f88fc0efc02c41b3bff987c0ed1 Component: engine --- components/engine/runtime_test.go | 45 ++++++++------- components/engine/server_test.go | 7 +-- components/engine/utils/utils.go | 6 ++ components/engine/utils_test.go | 96 +++++++++++++++++-------------- 4 files changed, 85 insertions(+), 69 deletions(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 4e46b7b6d0..4aac2f3442 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -3,6 +3,7 @@ package docker import ( "bytes" "fmt" + "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" "io" @@ -17,6 +18,7 @@ import ( "syscall" "testing" "time" + "net/url" ) const ( @@ -118,22 +120,19 @@ func init() { } func setupBaseImage() { - config := &DaemonConfig{ - Root: unitTestStoreBase, - AutoRestart: false, - BridgeIface: unitTestNetworkBridge, - } - runtime, err := NewRuntimeFromDirectory(config) + eng, err := engine.New(unitTestStoreBase) if err != nil { + log.Fatalf("Can't initialize engine at %s: %s", unitTestStoreBase, err) + } + job := eng.Job("initapi") + job.Setenv("Root", unitTestStoreBase) + job.SetenvBool("Autorestart", false) + job.Setenv("BridgeIface", unitTestNetworkBridge) + if err := job.Run(); err != nil { log.Fatalf("Unable to create a runtime for tests:", err) } - - // Create the "Server" - srv := &Server{ - runtime: runtime, - pullingPool: make(map[string]struct{}), - pushingPool: make(map[string]struct{}), - } + srv := mkServerFromEngine(eng, log.New(os.Stderr, "", 0)) + runtime := srv.runtime // If the unit test is not found, try to download it. if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID { @@ -149,18 +148,22 @@ func spawnGlobalDaemon() { utils.Debugf("Global runtime already exists. Skipping.") return } - globalRuntime = mkRuntime(log.New(os.Stderr, "", 0)) - srv := &Server{ - runtime: globalRuntime, - pullingPool: make(map[string]struct{}), - pushingPool: make(map[string]struct{}), - } + t := log.New(os.Stderr, "", 0) + eng := NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + globalRuntime = srv.runtime // Spawn a Daemon go func() { utils.Debugf("Spawning global daemon for integration tests") - if err := ListenAndServe(testDaemonProto, testDaemonAddr, srv, os.Getenv("DEBUG") != ""); err != nil { - log.Fatalf("Unable to spawn the test daemon:", err) + listenURL := &url.URL{ + Scheme: testDaemonProto, + Host: testDaemonAddr, + } + job := eng.Job("serveapi", listenURL.String()) + job.SetenvBool("Logging", os.Getenv("DEBUG") != "") + if err := job.Run(); err != nil { + log.Fatalf("Unable to spawn the test daemon: %s", err) } }() // Give some time to ListenAndServer to actually start diff --git a/components/engine/server_test.go b/components/engine/server_test.go index 20894f8fe1..a4bfb0155f 100644 --- a/components/engine/server_test.go +++ b/components/engine/server_test.go @@ -1,7 +1,6 @@ package docker import ( - "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/utils" "strings" "testing" @@ -110,7 +109,7 @@ func TestCreateRm(t *testing.T) { } func TestCreateRmVolumes(t *testing.T) { - eng := engine.NewTestEngine(t) + eng := NewTestEngine(t) srv := mkServerFromEngine(eng, t) runtime := srv.runtime @@ -174,7 +173,7 @@ func TestCommit(t *testing.T) { } func TestCreateStartRestartStopStartKillRm(t *testing.T) { - eng := engine.NewTestEngine(t) + eng := NewTestEngine(t) srv := mkServerFromEngine(eng, t) runtime := srv.runtime defer nuke(runtime) @@ -397,7 +396,7 @@ func TestLogEvent(t *testing.T) { } func TestRmi(t *testing.T) { - eng := engine.NewTestEngine(t) + eng := NewTestEngine(t) srv := mkServerFromEngine(eng, t) runtime := srv.runtime defer nuke(runtime) diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index cadd095031..5fd5e4e5b9 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -27,6 +27,12 @@ var ( INITSHA1 string // sha1sum of separate static dockerinit, if Docker itself was compiled dynamically via ./hack/make.sh dynbinary ) +// A common interface to access the Fatal method of +// both testing.B and testing.T. +type Fataler interface { + Fatal(args ...interface{}) +} + // ListOpts type type ListOpts []string diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 282954285c..95e12b6393 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -21,27 +21,24 @@ var globalTestID string // Create a temporary runtime suitable for unit testing. // Call t.Fatal() at the first error. -func mkRuntime(f Fataler) *Runtime { - // Use the caller function name as a prefix. - // This helps trace temp directories back to their test. - pc, _, _, _ := runtime.Caller(1) - callerLongName := runtime.FuncForPC(pc).Name() - parts := strings.Split(callerLongName, ".") - callerShortName := parts[len(parts)-1] - if globalTestID == "" { - globalTestID = GenerateID()[:4] - } - prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, callerShortName) - utils.Debugf("prefix = '%s'", prefix) - - runtime, err := newTestRuntime(prefix) +func mkRuntime(f utils.Fataler) *Runtime { + root, err := newTestDirectory(unitTestStoreBase) if err != nil { f.Fatal(err) } - return runtime + config := &DaemonConfig{ + Root: root, + AutoRestart: false, + } + r, err := NewRuntimeFromDirectory(config) + if err != nil { + f.Fatal(err) + } + r.UpdateCapabilities(true) + return r } -func mkServerFromEngine(eng *engine.Engine, t Fataler) *Server { +func mkServerFromEngine(eng *engine.Engine, t utils.Fataler) *Server { iSrv := eng.Hack_GetGlobalVar("httpapi.server") if iSrv == nil { t.Fatal("Legacy server field not set in engine") @@ -53,42 +50,53 @@ func mkServerFromEngine(eng *engine.Engine, t Fataler) *Server { return srv } -// A common interface to access the Fatal method of -// both testing.B and testing.T. -type Fataler interface { - Fatal(args ...interface{}) + +func NewTestEngine(t utils.Fataler) *engine.Engine { + root, err := newTestDirectory(unitTestStoreBase) + if err != nil { + t.Fatal(err) + } + eng, err := engine.New(root) + if err != nil { + t.Fatal(err) + } + // Load default plugins + // (This is manually copied and modified from main() until we have a more generic plugin system) + job := eng.Job("initapi") + job.Setenv("Root", root) + job.SetenvBool("AutoRestart", false) + if err := job.Run(); err != nil { + t.Fatal(err) + } + return eng } -func newTestRuntime(prefix string) (runtime *Runtime, err error) { +func newTestDirectory(templateDir string) (dir string, err error) { + if globalTestID == "" { + globalTestID = GenerateID()[:4] + } + prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, getCallerName(2)) if prefix == "" { prefix = "docker-test-" } - utils.Debugf("prefix = %s", prefix) - utils.Debugf("newTestRuntime start") - root, err := ioutil.TempDir("", prefix) - defer func() { - utils.Debugf("newTestRuntime: %s", root) - }() - if err != nil { - return nil, err + dir, err = ioutil.TempDir("", prefix) + if err = os.Remove(dir); err != nil { + return } - if err := os.Remove(root); err != nil { - return nil, err - } - if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil { - return nil, err + if err = utils.CopyDirectory(templateDir, dir); err != nil { + return } + return +} - config := &DaemonConfig{ - Root: root, - AutoRestart: false, - } - runtime, err = NewRuntimeFromDirectory(config) - if err != nil { - return nil, err - } - runtime.UpdateCapabilities(true) - return runtime, nil +func getCallerName(depth int) string { + // Use the caller function name as a prefix. + // This helps trace temp directories back to their test. + pc, _, _, _ := runtime.Caller(depth + 1) + callerLongName := runtime.FuncForPC(pc).Name() + parts := strings.Split(callerLongName, ".") + callerShortName := parts[len(parts)-1] + return callerShortName } // Write `content` to the file at path `dst`, creating it if necessary, From 53c37bc88ef3a60c1be138e42f4605ead115694f Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Oct 2013 07:15:22 +0000 Subject: [PATCH 019/184] Engine: fix a bug when encoding a job environment to json Upstream-commit: 434f06d03dc2825cb4f348a88ddc6d1aa17ea19c Component: engine --- components/engine/api.go | 3 +++ components/engine/engine/job.go | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/components/engine/api.go b/components/engine/api.go index 1a3d92de32..18251eba2f 100644 --- a/components/engine/api.go +++ b/components/engine/api.go @@ -644,6 +644,9 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r } name := vars["name"] job := srv.Eng.Job("start", name) + if err := job.ImportEnv(HostConfig{}); err != nil { + return fmt.Errorf("Couldn't initialize host configuration") + } // allow a nil body for backwards compatibility if r.Body != nil { if matchesContentType(r.Header.Get("Content-Type"), "application/json") { diff --git a/components/engine/engine/job.go b/components/engine/engine/job.go index 7a261409e0..b6296eb91d 100644 --- a/components/engine/engine/job.go +++ b/components/engine/engine/job.go @@ -149,10 +149,22 @@ func (job *Job) DecodeEnv(src io.Reader) error { } func (job *Job) EncodeEnv(dst io.Writer) error { - return json.NewEncoder(dst).Encode(job.Environ()) + m := make(map[string]interface{}) + for k, v := range job.Environ() { + var val interface{} + if err := json.Unmarshal([]byte(v), &val); err == nil { + m[k] = val + } else { + m[k] = v + } + } + if err := json.NewEncoder(dst).Encode(&m); err != nil { + return err + } + return nil } -func (job *Job) ExportEnv(dst interface{}) error { +func (job *Job) ExportEnv(dst interface{}) (err error) { var buf bytes.Buffer if err := job.EncodeEnv(&buf); err != nil { return err From c3fb3fd5123b23dd7f16fee3012726bb84f4e4e2 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Oct 2013 07:16:32 +0000 Subject: [PATCH 020/184] Better error reporting in engine logs and unit tests Upstream-commit: d3f074494a9594bc268bf4c639a7aea0934ec7c0 Component: engine --- components/engine/runtime_test.go | 2 +- components/engine/server.go | 7 ++++++- components/engine/utils_test.go | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 4aac2f3442..946a8ebf61 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -183,7 +183,7 @@ func GetTestImage(runtime *Runtime) *Image { return image } } - log.Fatalf("Test image %v not found", unitTestImageID) + log.Fatalf("Test image %v not found in %s: %s", unitTestImageID, runtime.graph.Root, imgs) return nil } diff --git a/components/engine/server.go b/components/engine/server.go index c041491ead..0a1270bede 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -40,6 +40,7 @@ func init() { // Only one api server can run at the same time - this is enforced by a pidfile. // The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup. func jobInitApi(job *engine.Job) string { + job.Logf("Creating server") srv, err := NewServer(job.Eng, ConfigFromJob(job)) if err != nil { return err.Error() @@ -50,6 +51,7 @@ func jobInitApi(job *engine.Job) string { log.Fatal(err) } } + job.Logf("Setting up signal traps") c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM)) go func() { @@ -1288,7 +1290,7 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error { return fmt.Errorf("No such container: %s", name) } - if hostConfig.Links != nil { + if hostConfig != nil && hostConfig.Links != nil { for _, l := range hostConfig.Links { parts, err := parseLink(l) if err != nil { @@ -1317,11 +1319,14 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error { } func (srv *Server) ContainerStart(job *engine.Job) string { + job.Logf("srv engine = %s", srv.Eng.Root()) + job.Logf("job engine = %s", job.Eng.Root()) if len(job.Args) < 1 { return fmt.Sprintf("Usage: %s container_id", job.Name) } name := job.Args[0] runtime := srv.runtime + job.Logf("loading containers from %s", runtime.repository) container := runtime.Get(name) if container == nil { return fmt.Sprintf("No such container: %s", name) diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 95e12b6393..529af127e5 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -41,11 +41,11 @@ func mkRuntime(f utils.Fataler) *Runtime { func mkServerFromEngine(eng *engine.Engine, t utils.Fataler) *Server { iSrv := eng.Hack_GetGlobalVar("httpapi.server") if iSrv == nil { - t.Fatal("Legacy server field not set in engine") + panic("Legacy server field not set in engine") } srv, ok := iSrv.(*Server) if !ok { - t.Fatal("Legacy server field in engine does not cast to *Server") + panic("Legacy server field in engine does not cast to *Server") } return srv } From bf0674c59a158fd9910142950dfbecd8c03d57d1 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 5 Nov 2013 19:57:40 +0000 Subject: [PATCH 021/184] Fix main() Upstream-commit: 5c42b2b5122c1db08d229c258da26869b4d4d9cc Component: engine --- components/engine/docker/docker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index e58bd40013..213ec73fd9 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -86,8 +86,8 @@ func main() { log.Fatal(err) } // Serve api - job := eng.Job("serveapi", flHosts...) - job.Setenv("Logging", true) + job = eng.Job("serveapi", flHosts...) + job.SetenvBool("Logging", true) if err := job.Run(); err != nil { log.Fatal(err) } From e6df3df72cabaa67df3030445aca7f87562981b2 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Oct 2013 19:20:00 -0700 Subject: [PATCH 022/184] Engine: 'create' creates a container and prints its ID on stdout Upstream-commit: e5f8ab6160401fb541121da5b5cbc3af4fce28b7 Component: engine --- components/engine/api.go | 31 +++--- components/engine/api_test.go | 28 ++--- components/engine/engine/engine.go | 2 +- components/engine/engine/job.go | 165 ++++++++++++++++++++++++++--- components/engine/runtime_test.go | 58 ++++------ components/engine/server.go | 35 ++++-- components/engine/server_test.go | 64 ++++------- components/engine/utils_test.go | 16 +++ 8 files changed, 260 insertions(+), 139 deletions(-) diff --git a/components/engine/api.go b/components/engine/api.go index 18251eba2f..1fc74d764a 100644 --- a/components/engine/api.go +++ b/components/engine/api.go @@ -526,43 +526,36 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r if err := parseForm(r); err != nil { return nil } - config := &Config{} out := &APIRun{} - name := r.Form.Get("name") - - if err := json.NewDecoder(r.Body).Decode(config); err != nil { + job := srv.Eng.Job("create", r.Form.Get("name")) + if err := job.DecodeEnv(r.Body); err != nil { return err } - resolvConf, err := utils.GetResolvConf() if err != nil { return err } - - if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { + if !job.GetenvBool("NetworkDisabled") && len(job.Getenv("Dns")) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns)) - config.Dns = defaultDns + job.SetenvList("Dns", defaultDns) } - - id, warnings, err := srv.ContainerCreate(config, name) - if err != nil { + // Read container ID from the first line of stdout + job.StdoutParseString(&out.ID) + // Read warnings from stderr + job.StderrParseLines(&out.Warnings, 0) + if err := job.Run(); err != nil { return err } - out.ID = id - for _, warning := range warnings { - out.Warnings = append(out.Warnings, warning) - } - - if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { + if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.MemoryLimit { log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.") } - if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit { + if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.SwapLimit { log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.") out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.") } - if !config.NetworkDisabled && srv.runtime.capabilities.IPv4ForwardingDisabled { + if !job.GetenvBool("NetworkDisabled") && srv.runtime.capabilities.IPv4ForwardingDisabled { log.Println("Warning: IPv4 forwarding is disabled.") out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.") } diff --git a/components/engine/api_test.go b/components/engine/api_test.go index 50a86e3f23..7b0dfaa077 100644 --- a/components/engine/api_test.go +++ b/components/engine/api_test.go @@ -634,11 +634,11 @@ func TestPostCommit(t *testing.T) { } func TestPostContainersCreate(t *testing.T) { - runtime := mkRuntime(t) + eng := NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + runtime := srv.runtime defer nuke(runtime) - srv := &Server{runtime: runtime} - configJSON, err := json.Marshal(&Config{ Image: GetTestImage(runtime).ID, Memory: 33554432, @@ -786,22 +786,18 @@ func TestPostContainersStart(t *testing.T) { runtime := srv.runtime defer nuke(runtime) - container, _, err := runtime.Create( + id := createTestContainer( + eng, &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, OpenStdin: true, }, - "", - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container) + t) hostConfigJSON, err := json.Marshal(&HostConfig{}) - req, err := http.NewRequest("POST", "/containers/"+container.ID+"/start", bytes.NewReader(hostConfigJSON)) + req, err := http.NewRequest("POST", "/containers/"+id+"/start", bytes.NewReader(hostConfigJSON)) if err != nil { t.Fatal(err) } @@ -809,22 +805,26 @@ func TestPostContainersStart(t *testing.T) { req.Header.Set("Content-Type", "application/json") r := httptest.NewRecorder() - if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { + if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": id}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) } + container := runtime.Get(id) + if container == nil { + t.Fatalf("Container %s was not created", id) + } // Give some time to the process to start + // FIXME: use Wait once it's available as a job container.WaitTimeout(500 * time.Millisecond) - if !container.State.Running { t.Errorf("Container should be running") } r = httptest.NewRecorder() - if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil { + if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": id}); err == nil { t.Fatalf("A running container should be able to be started") } diff --git a/components/engine/engine/engine.go b/components/engine/engine/engine.go index 565c6ed4fa..428ef706c9 100644 --- a/components/engine/engine/engine.go +++ b/components/engine/engine/engine.go @@ -115,5 +115,5 @@ func (eng *Engine) Job(name string, args ...string) *Job { func (eng *Engine) Logf(format string, args ...interface{}) (n int, err error) { prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n")) - return fmt.Printf(prefixedFormat, args...) + return fmt.Fprintf(os.Stderr, prefixedFormat, args...) } diff --git a/components/engine/engine/job.go b/components/engine/engine/job.go index b6296eb91d..ece8d8f8d6 100644 --- a/components/engine/engine/job.go +++ b/components/engine/engine/job.go @@ -1,11 +1,16 @@ package engine import ( + "bufio" "bytes" "io" + "io/ioutil" + "strconv" "strings" "fmt" + "sync" "encoding/json" + "os" ) // A job is the fundamental unit of work in the docker engine. @@ -26,20 +31,38 @@ type Job struct { Name string Args []string env []string - Stdin io.ReadCloser - Stdout io.WriteCloser - Stderr io.WriteCloser + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer handler func(*Job) string status string + onExit []func() } // Run executes the job and blocks until the job completes. // If the job returns a failure status, an error is returned // which includes the status. func (job *Job) Run() error { - job.Logf("{") defer func() { - job.Logf("}") + var wg sync.WaitGroup + for _, f := range job.onExit { + wg.Add(1) + go func(f func()) { + f() + wg.Done() + }(f) + } + wg.Wait() + }() + if job.Stdout != nil && job.Stdout != os.Stdout { + job.Stdout = io.MultiWriter(job.Stdout, os.Stdout) + } + if job.Stderr != nil && job.Stderr != os.Stderr { + job.Stderr = io.MultiWriter(job.Stderr, os.Stderr) + } + job.Eng.Logf("+job %s", job.CallString()) + defer func() { + job.Eng.Logf("-job %s%s", job.CallString(), job.StatusString()) }() if job.handler == nil { job.status = "command not found" @@ -52,9 +75,66 @@ func (job *Job) Run() error { return nil } -// String returns a human-readable description of `job` -func (job *Job) String() string { - s := fmt.Sprintf("%s.%s(%s)", job.Eng, job.Name, strings.Join(job.Args, ", ")) +func (job *Job) StdoutParseLines(dst *[]string, limit int) { + job.parseLines(job.StdoutPipe(), dst, limit) +} + +func (job *Job) StderrParseLines(dst *[]string, limit int) { + job.parseLines(job.StderrPipe(), dst, limit) +} + +func (job *Job) parseLines(src io.Reader, dst *[]string, limit int) { + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + scanner := bufio.NewScanner(src) + for scanner.Scan() { + // If the limit is reached, flush the rest of the source and return + if limit > 0 && len(*dst) >= limit { + io.Copy(ioutil.Discard, src) + return + } + line := scanner.Text() + // Append the line (with delimitor removed) + *dst = append(*dst, line) + } + }() + job.onExit = append(job.onExit, wg.Wait) +} + +func (job *Job) StdoutParseString(dst *string) { + lines := make([]string, 0, 1) + job.StdoutParseLines(&lines, 1) + job.onExit = append(job.onExit, func() { if len(lines) >= 1 { *dst = lines[0] }}) +} + +func (job *Job) StderrParseString(dst *string) { + lines := make([]string, 0, 1) + job.StderrParseLines(&lines, 1) + job.onExit = append(job.onExit, func() { *dst = lines[0]; }) +} + +func (job *Job) StdoutPipe() io.ReadCloser { + r, w := io.Pipe() + job.Stdout = w + job.onExit = append(job.onExit, func(){ w.Close() }) + return r +} + +func (job *Job) StderrPipe() io.ReadCloser { + r, w := io.Pipe() + job.Stderr = w + job.onExit = append(job.onExit, func(){ w.Close() }) + return r +} + + +func (job *Job) CallString() string { + return fmt.Sprintf("%s(%s)", job.Name, strings.Join(job.Args, ", ")) +} + +func (job *Job) StatusString() string { // FIXME: if a job returns the empty string, it will be printed // as not having returned. // (this only affects String which is a convenience function). @@ -65,9 +145,14 @@ func (job *Job) String() string { } else { okerr = "ERR" } - s = fmt.Sprintf("%s = %s (%s)", s, okerr, job.status) + return fmt.Sprintf(" = %s (%s)", okerr, job.status) } - return s + return "" +} + +// String returns a human-readable description of `job` +func (job *Job) String() string { + return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString()) } func (job *Job) Getenv(key string) (value string) { @@ -104,6 +189,19 @@ func (job *Job) SetenvBool(key string, value bool) { } } +func (job *Job) GetenvInt(key string) int64 { + s := strings.Trim(job.Getenv(key), " \t") + val, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return -1 + } + return val +} + +func (job *Job) SetenvInt(key string, value int64) { + job.Setenv(key, fmt.Sprintf("%d", value)) +} + func (job *Job) GetenvList(key string) []string { sval := job.Getenv(key) l := make([]string, 0, 1) @@ -137,13 +235,21 @@ func (job *Job) DecodeEnv(src io.Reader) error { return err } for k, v := range m { - if sval, ok := v.(string); ok { + // FIXME: we fix-convert float values to int, because + // encoding/json decodes integers to float64, but cannot encode them back. + // (See http://golang.org/src/pkg/encoding/json/decode.go#L46) + if fval, ok := v.(float64); ok { + job.Logf("Converted to float: %v->%v", v, fval) + job.SetenvInt(k, int64(fval)) + } else if sval, ok := v.(string); ok { + job.Logf("Converted to string: %v->%v", v, sval) job.Setenv(k, sval) } else if val, err := json.Marshal(v); err == nil { job.Setenv(k, string(val)) } else { job.Setenv(k, fmt.Sprintf("%v", v)) } + job.Logf("Decoded %s=%#v to %s=%#v", k, v, k, job.Getenv(k)) } return nil } @@ -153,10 +259,17 @@ func (job *Job) EncodeEnv(dst io.Writer) error { for k, v := range job.Environ() { var val interface{} if err := json.Unmarshal([]byte(v), &val); err == nil { + // FIXME: we fix-convert float values to int, because + // encoding/json decodes integers to float64, but cannot encode them back. + // (See http://golang.org/src/pkg/encoding/json/decode.go#L46) + if fval, isFloat := val.(float64); isFloat { + val = int(fval) + } m[k] = val } else { m[k] = v } + job.Logf("Encoded %s=%#v to %s=%#v", k, v, k, m[k]) } if err := json.NewEncoder(dst).Encode(&m); err != nil { return err @@ -165,21 +278,38 @@ func (job *Job) EncodeEnv(dst io.Writer) error { } func (job *Job) ExportEnv(dst interface{}) (err error) { + fmt.Fprintf(os.Stderr, "ExportEnv()\n") + defer func() { + if err != nil { + err = fmt.Errorf("ExportEnv %s", err) + } + }() var buf bytes.Buffer + job.Logf("ExportEnv: step 1: encode/marshal the env to an intermediary json representation") + fmt.Fprintf(os.Stderr, "Printed ExportEnv step 1\n") if err := job.EncodeEnv(&buf); err != nil { return err } + job.Logf("ExportEnv: step 1 complete: json=|%s|", buf) + job.Logf("ExportEnv: step 2: decode/unmarshal the intermediary json into the destination object") if err := json.NewDecoder(&buf).Decode(dst); err != nil { return err } + job.Logf("ExportEnv: step 2 complete") return nil } -func (job *Job) ImportEnv(src interface{}) error { +func (job *Job) ImportEnv(src interface{}) (err error) { + defer func() { + if err != nil { + err = fmt.Errorf("ImportEnv: %s", err) + } + }() var buf bytes.Buffer if err := json.NewEncoder(&buf).Encode(src); err != nil { return err } + job.Logf("ImportEnv: json=|%s|", buf) if err := job.DecodeEnv(&buf); err != nil { return err } @@ -197,5 +327,14 @@ func (job *Job) Environ() map[string]string { func (job *Job) Logf(format string, args ...interface{}) (n int, err error) { prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n")) - return fmt.Fprintf(job.Stdout, prefixedFormat, args...) + return fmt.Fprintf(job.Stderr, prefixedFormat, args...) +} + +func (job *Job) Printf(format string, args ...interface{}) (n int, err error) { + return fmt.Fprintf(job.Stdout, format, args...) +} + +func (job *Job) Errorf(format string, args ...interface{}) (n int, err error) { + return fmt.Fprintf(job.Stderr, format, args...) + } diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 946a8ebf61..ce69465961 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -645,20 +645,17 @@ func TestReloadContainerLinks(t *testing.T) { } func TestDefaultContainerName(t *testing.T) { - runtime := mkRuntime(t) + eng := NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + runtime := srv.runtime defer nuke(runtime) - srv := &Server{runtime: runtime} config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) if err != nil { t.Fatal(err) } - shortId, _, err := srv.ContainerCreate(config, "some_name") - if err != nil { - t.Fatal(err) - } - container := runtime.Get(shortId) + container := runtime.Get(createNamedTestContainer(eng, config, t, "some_name")) containerID := container.ID if container.Name != "/some_name" { @@ -682,20 +679,17 @@ func TestDefaultContainerName(t *testing.T) { } func TestRandomContainerName(t *testing.T) { - runtime := mkRuntime(t) + eng := NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + runtime := srv.runtime defer nuke(runtime) - srv := &Server{runtime: runtime} config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) if err != nil { t.Fatal(err) } - shortId, _, err := srv.ContainerCreate(config, "") - if err != nil { - t.Fatal(err) - } - container := runtime.Get(shortId) + container := runtime.Get(createTestContainer(eng, config, t)) containerID := container.ID if container.Name == "" { @@ -719,20 +713,17 @@ func TestRandomContainerName(t *testing.T) { } func TestLinkChildContainer(t *testing.T) { - runtime := mkRuntime(t) + eng := NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + runtime := srv.runtime defer nuke(runtime) - srv := &Server{runtime: runtime} config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) if err != nil { t.Fatal(err) } - shortId, _, err := srv.ContainerCreate(config, "/webapp") - if err != nil { - t.Fatal(err) - } - container := runtime.Get(shortId) + container := runtime.Get(createNamedTestContainer(eng, config, t, "/webapp")) webapp, err := runtime.GetByName("/webapp") if err != nil { @@ -748,12 +739,7 @@ func TestLinkChildContainer(t *testing.T) { t.Fatal(err) } - shortId, _, err = srv.ContainerCreate(config, "") - if err != nil { - t.Fatal(err) - } - - childContainer := runtime.Get(shortId) + childContainer := runtime.Get(createTestContainer(eng, config, t)) if err := runtime.RegisterLink(webapp, childContainer, "db"); err != nil { t.Fatal(err) @@ -770,20 +756,17 @@ func TestLinkChildContainer(t *testing.T) { } func TestGetAllChildren(t *testing.T) { - runtime := mkRuntime(t) + eng := NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + runtime := srv.runtime defer nuke(runtime) - srv := &Server{runtime: runtime} config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) if err != nil { t.Fatal(err) } - shortId, _, err := srv.ContainerCreate(config, "/webapp") - if err != nil { - t.Fatal(err) - } - container := runtime.Get(shortId) + container := runtime.Get(createNamedTestContainer(eng, config, t, "/webapp")) webapp, err := runtime.GetByName("/webapp") if err != nil { @@ -799,12 +782,7 @@ func TestGetAllChildren(t *testing.T) { t.Fatal(err) } - shortId, _, err = srv.ContainerCreate(config, "") - if err != nil { - t.Fatal(err) - } - - childContainer := runtime.Get(shortId) + childContainer := runtime.Get(createTestContainer(eng, config, t)) if err := runtime.RegisterLink(webapp, childContainer, "db"); err != nil { t.Fatal(err) diff --git a/components/engine/server.go b/components/engine/server.go index 0a1270bede..6196df8981 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -62,6 +62,9 @@ func jobInitApi(job *engine.Job) string { os.Exit(0) }() job.Eng.Hack_SetGlobalVar("httpapi.server", srv) + if err := job.Eng.Register("create", srv.ContainerCreate); err != nil { + return err.Error() + } if err := job.Eng.Register("start", srv.ContainerStart); err != nil { return err.Error() } @@ -1009,33 +1012,43 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write return nil } -func (srv *Server) ContainerCreate(config *Config, name string) (string, []string, error) { - if config.Memory != 0 && config.Memory < 524288 { - return "", nil, fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)") +func (srv *Server) ContainerCreate(job *engine.Job) string { + var name string + if len(job.Args) == 1 { + name = job.Args[0] + } else if len(job.Args) > 1 { + return fmt.Sprintf("Usage: %s ", job.Name) + } + var config Config + if err := job.ExportEnv(&config); err != nil { + return err.Error() + } + if config.Memory != 0 && config.Memory < 524288 { + return "Memory limit must be given in bytes (minimum 524288 bytes)" } - if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { config.Memory = 0 } - if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit { config.MemorySwap = -1 } - container, buildWarnings, err := srv.runtime.Create(config, name) + container, buildWarnings, err := srv.runtime.Create(&config, name) if err != nil { if srv.runtime.graph.IsNotExist(err) { - _, tag := utils.ParseRepositoryTag(config.Image) if tag == "" { tag = DEFAULTTAG } - - return "", nil, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag) + return fmt.Sprintf("No such image: %s (tag: %s)", config.Image, tag) } - return "", nil, err + return err.Error() } srv.LogEvent("create", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) - return container.ShortID(), buildWarnings, nil + job.Printf("%s\n", container.ShortID()) + for _, warning := range buildWarnings { + job.Errorf("%s\n", warning) + } + return "0" } func (srv *Server) ContainerRestart(name string, t int) error { diff --git a/components/engine/server_test.go b/components/engine/server_test.go index a4bfb0155f..111043eca2 100644 --- a/components/engine/server_test.go +++ b/components/engine/server_test.go @@ -79,20 +79,17 @@ func TestContainerTagImageDelete(t *testing.T) { } func TestCreateRm(t *testing.T) { - runtime := mkRuntime(t) + eng := NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + runtime := srv.runtime defer nuke(runtime) - srv := &Server{runtime: runtime} - config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) if err != nil { t.Fatal(err) } - id, _, err := srv.ContainerCreate(config, "") - if err != nil { - t.Fatal(err) - } + id := createTestContainer(eng, config, t) if len(runtime.List()) != 1 { t.Errorf("Expected 1 container, %v found", len(runtime.List())) @@ -120,10 +117,7 @@ func TestCreateRmVolumes(t *testing.T) { t.Fatal(err) } - id, _, err := srv.ContainerCreate(config, "") - if err != nil { - t.Fatal(err) - } + id := createTestContainer(eng, config, t) if len(runtime.List()) != 1 { t.Errorf("Expected 1 container, %v found", len(runtime.List())) @@ -152,20 +146,17 @@ func TestCreateRmVolumes(t *testing.T) { } func TestCommit(t *testing.T) { - runtime := mkRuntime(t) + eng := NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + runtime := srv.runtime defer nuke(runtime) - srv := &Server{runtime: runtime} - config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil) if err != nil { t.Fatal(err) } - id, _, err := srv.ContainerCreate(config, "") - if err != nil { - t.Fatal(err) - } + id := createTestContainer(eng, config, t) if _, err := srv.ContainerCommit(id, "testrepo", "testtag", "", "", config); err != nil { t.Fatal(err) @@ -183,10 +174,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { t.Fatal(err) } - id, _, err := srv.ContainerCreate(config, "") - if err != nil { - t.Fatal(err) - } + id := createTestContainer(eng, config, t) if len(runtime.List()) != 1 { t.Errorf("Expected 1 container, %v found", len(runtime.List())) @@ -232,22 +220,22 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { } func TestRunWithTooLowMemoryLimit(t *testing.T) { - runtime := mkRuntime(t) + eng := NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + runtime := srv.runtime defer nuke(runtime) // Try to create a container with a memory limit of 1 byte less than the minimum allowed limit. - if _, _, err := (*Server).ContainerCreate(&Server{runtime: runtime}, - &Config{ - Image: GetTestImage(runtime).ID, - Memory: 524287, - CpuShares: 1000, - Cmd: []string{"/bin/cat"}, - }, - "", - ); err == nil { + job := eng.Job("create") + job.Setenv("Image", GetTestImage(runtime).ID) + job.Setenv("Memory", "524287") + job.Setenv("CpuShares", "1000") + job.SetenvList("Cmd", []string{"/bin/cat"}) + var id string + job.StdoutParseString(&id) + if err := job.Run(); err == nil { t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") } - } func TestContainerTop(t *testing.T) { @@ -411,10 +399,7 @@ func TestRmi(t *testing.T) { t.Fatal(err) } - containerID, _, err := srv.ContainerCreate(config, "") - if err != nil { - t.Fatal(err) - } + containerID := createTestContainer(eng, config, t) //To remove job := eng.Job("start", containerID) @@ -435,10 +420,7 @@ func TestRmi(t *testing.T) { t.Fatal(err) } - containerID, _, err = srv.ContainerCreate(config, "") - if err != nil { - t.Fatal(err) - } + containerID = createTestContainer(eng, config, t) //To remove job = eng.Job("start", containerID) diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 529af127e5..2e8c0ceb1b 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -38,6 +38,22 @@ func mkRuntime(f utils.Fataler) *Runtime { return r } +func createNamedTestContainer(eng *engine.Engine, config *Config, f utils.Fataler, name string) (shortId string) { + job := eng.Job("create", name) + if err := job.ImportEnv(config); err != nil { + f.Fatal(err) + } + job.StdoutParseString(&shortId) + if err := job.Run(); err != nil { + f.Fatal(err) + } + return +} + +func createTestContainer(eng *engine.Engine, config *Config, f utils.Fataler) (shortId string) { + return createNamedTestContainer(eng, config, f, "") +} + func mkServerFromEngine(eng *engine.Engine, t utils.Fataler) *Server { iSrv := eng.Hack_GetGlobalVar("httpapi.server") if iSrv == nil { From e83abd1b633af0240d7add404d88534ba90a5448 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 5 Nov 2013 22:22:37 +0000 Subject: [PATCH 023/184] Remove debug messages Upstream-commit: 8d6df3a7e2080d4fad9743beb159f12caa0ff6f7 Component: engine --- components/engine/engine/job.go | 13 ++----------- components/engine/server.go | 3 --- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/components/engine/engine/job.go b/components/engine/engine/job.go index ece8d8f8d6..419ac73466 100644 --- a/components/engine/engine/job.go +++ b/components/engine/engine/job.go @@ -239,17 +239,14 @@ func (job *Job) DecodeEnv(src io.Reader) error { // encoding/json decodes integers to float64, but cannot encode them back. // (See http://golang.org/src/pkg/encoding/json/decode.go#L46) if fval, ok := v.(float64); ok { - job.Logf("Converted to float: %v->%v", v, fval) job.SetenvInt(k, int64(fval)) } else if sval, ok := v.(string); ok { - job.Logf("Converted to string: %v->%v", v, sval) job.Setenv(k, sval) } else if val, err := json.Marshal(v); err == nil { job.Setenv(k, string(val)) } else { job.Setenv(k, fmt.Sprintf("%v", v)) } - job.Logf("Decoded %s=%#v to %s=%#v", k, v, k, job.Getenv(k)) } return nil } @@ -269,7 +266,6 @@ func (job *Job) EncodeEnv(dst io.Writer) error { } else { m[k] = v } - job.Logf("Encoded %s=%#v to %s=%#v", k, v, k, m[k]) } if err := json.NewEncoder(dst).Encode(&m); err != nil { return err @@ -278,24 +274,20 @@ func (job *Job) EncodeEnv(dst io.Writer) error { } func (job *Job) ExportEnv(dst interface{}) (err error) { - fmt.Fprintf(os.Stderr, "ExportEnv()\n") defer func() { if err != nil { err = fmt.Errorf("ExportEnv %s", err) } }() var buf bytes.Buffer - job.Logf("ExportEnv: step 1: encode/marshal the env to an intermediary json representation") - fmt.Fprintf(os.Stderr, "Printed ExportEnv step 1\n") + // step 1: encode/marshal the env to an intermediary json representation if err := job.EncodeEnv(&buf); err != nil { return err } - job.Logf("ExportEnv: step 1 complete: json=|%s|", buf) - job.Logf("ExportEnv: step 2: decode/unmarshal the intermediary json into the destination object") + // step 2: decode/unmarshal the intermediary json into the destination object if err := json.NewDecoder(&buf).Decode(dst); err != nil { return err } - job.Logf("ExportEnv: step 2 complete") return nil } @@ -309,7 +301,6 @@ func (job *Job) ImportEnv(src interface{}) (err error) { if err := json.NewEncoder(&buf).Encode(src); err != nil { return err } - job.Logf("ImportEnv: json=|%s|", buf) if err := job.DecodeEnv(&buf); err != nil { return err } diff --git a/components/engine/server.go b/components/engine/server.go index 6196df8981..abcb001bb0 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1332,14 +1332,11 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error { } func (srv *Server) ContainerStart(job *engine.Job) string { - job.Logf("srv engine = %s", srv.Eng.Root()) - job.Logf("job engine = %s", job.Eng.Root()) if len(job.Args) < 1 { return fmt.Sprintf("Usage: %s container_id", job.Name) } name := job.Args[0] runtime := srv.runtime - job.Logf("loading containers from %s", runtime.repository) container := runtime.Get(name) if container == nil { return fmt.Sprintf("No such container: %s", name) From 2a939ef85fc1938b0963c5a21514c5ae7af9ada3 Mon Sep 17 00:00:00 2001 From: Daniel Norberg Date: Tue, 5 Nov 2013 18:26:07 -0500 Subject: [PATCH 024/184] lock around read operations in graph Writes and reads will fail with ErrBusy if there's concurrent reads or writes, respectively. It is not sufficient to only lock around writes. Upstream-commit: 1dc34e2b965253a62d52f84a0f548334d4d6aa9d Component: engine --- components/engine/gograph/gograph.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/components/engine/gograph/gograph.go b/components/engine/gograph/gograph.go index 32b3a491bb..2b25464344 100644 --- a/components/engine/gograph/gograph.go +++ b/components/engine/gograph/gograph.go @@ -138,7 +138,14 @@ func (db *Database) Set(fullPath, id string) (*Entity, error) { // Return true if a name already exists in the database func (db *Database) Exists(name string) bool { - return db.Get(name) != nil + db.mux.Lock() + defer db.mux.Unlock() + + e, err := db.get(name) + if err != nil { + return false + } + return e != nil } func (db *Database) setEdge(parentPath, name string, e *Entity) error { @@ -165,6 +172,9 @@ func (db *Database) RootEntity() *Entity { // Return the entity for a given path func (db *Database) Get(name string) *Entity { + db.mux.Lock() + defer db.mux.Unlock() + e, err := db.get(name) if err != nil { return nil @@ -200,6 +210,9 @@ func (db *Database) get(name string) (*Entity, error) { // List all entities by from the name // The key will be the full path of the entity func (db *Database) List(name string, depth int) Entities { + db.mux.Lock() + defer db.mux.Unlock() + out := Entities{} e, err := db.get(name) if err != nil { @@ -212,6 +225,9 @@ func (db *Database) List(name string, depth int) Entities { } func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { + db.mux.Lock() + defer db.mux.Unlock() + e, err := db.get(name) if err != nil { return err @@ -226,6 +242,9 @@ func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { // Return the refrence count for a specified id func (db *Database) Refs(id string) int { + db.mux.Lock() + defer db.mux.Unlock() + var count int if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil { return 0 @@ -235,6 +254,9 @@ func (db *Database) Refs(id string) int { // Return all the id's path references func (db *Database) RefPaths(id string) Edges { + db.mux.Lock() + defer db.mux.Unlock() + refs := Edges{} rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id) From b581ea208c28d7b2b8766c7d010661d78dd69e76 Mon Sep 17 00:00:00 2001 From: Daniel Norberg Date: Tue, 5 Nov 2013 22:07:14 -0500 Subject: [PATCH 025/184] gograph: Use RWMutex to allow concurrent readers Upstream-commit: 04aca7c9e3edddc57a1fbc11e57e6c62e6847126 Component: engine --- components/engine/gograph/gograph.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/components/engine/gograph/gograph.go b/components/engine/gograph/gograph.go index 2b25464344..626bf53ed8 100644 --- a/components/engine/gograph/gograph.go +++ b/components/engine/gograph/gograph.go @@ -48,7 +48,7 @@ type WalkFunc func(fullPath string, entity *Entity) error // Graph database for storing entities and their relationships type Database struct { conn *sql.DB - mux sync.Mutex + mux sync.RWMutex } // Create a new graph database initialized with a root entity @@ -138,8 +138,8 @@ func (db *Database) Set(fullPath, id string) (*Entity, error) { // Return true if a name already exists in the database func (db *Database) Exists(name string) bool { - db.mux.Lock() - defer db.mux.Unlock() + db.mux.RLock() + defer db.mux.RUnlock() e, err := db.get(name) if err != nil { @@ -172,8 +172,8 @@ func (db *Database) RootEntity() *Entity { // Return the entity for a given path func (db *Database) Get(name string) *Entity { - db.mux.Lock() - defer db.mux.Unlock() + db.mux.RLock() + defer db.mux.RUnlock() e, err := db.get(name) if err != nil { @@ -210,8 +210,8 @@ func (db *Database) get(name string) (*Entity, error) { // List all entities by from the name // The key will be the full path of the entity func (db *Database) List(name string, depth int) Entities { - db.mux.Lock() - defer db.mux.Unlock() + db.mux.RLock() + defer db.mux.RUnlock() out := Entities{} e, err := db.get(name) @@ -225,8 +225,8 @@ func (db *Database) List(name string, depth int) Entities { } func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { - db.mux.Lock() - defer db.mux.Unlock() + db.mux.RLock() + defer db.mux.RUnlock() e, err := db.get(name) if err != nil { @@ -242,8 +242,8 @@ func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { // Return the refrence count for a specified id func (db *Database) Refs(id string) int { - db.mux.Lock() - defer db.mux.Unlock() + db.mux.RLock() + defer db.mux.RUnlock() var count int if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil { @@ -254,8 +254,8 @@ func (db *Database) Refs(id string) int { // Return all the id's path references func (db *Database) RefPaths(id string) Edges { - db.mux.Lock() - defer db.mux.Unlock() + db.mux.RLock() + defer db.mux.RUnlock() refs := Edges{} From 59c6d9849856dca6d8e356668d3f672a3c12632a Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Sun, 6 Oct 2013 05:44:04 +0000 Subject: [PATCH 026/184] rework images JSON Upstream-commit: 15867ff4308c5c1330a400be56410d404336100f Component: engine --- components/engine/api_params.go | 6 ++--- components/engine/commands.go | 36 ++++++++++++++++-------------- components/engine/server.go | 39 +++++++++++++++++++++++---------- components/engine/sorter.go | 2 +- 4 files changed, 51 insertions(+), 32 deletions(-) diff --git a/components/engine/api_params.go b/components/engine/api_params.go index f666ac2393..d796b96542 100644 --- a/components/engine/api_params.go +++ b/components/engine/api_params.go @@ -9,12 +9,12 @@ type APIHistory struct { } type APIImages struct { - Repository string `json:",omitempty"` - Tag string `json:",omitempty"` - ID string `json:"Id"` + ID string `json:"Id"` + RepoTags []string `json:",omitempty"` Created int64 Size int64 VirtualSize int64 + ParentId string `json:",omitempty"` } type APIInfo struct { diff --git a/components/engine/commands.go b/components/engine/commands.go index d52114553f..7fc3dceb43 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -1097,27 +1097,29 @@ func (cli *DockerCli) CmdImages(args ...string) error { fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED\tSIZE") } + var repo string + var tag string for _, out := range outs { - if out.Repository == "" { - out.Repository = "" - } - if out.Tag == "" { - out.Tag = "" - } + for _, repotag := range out.RepoTags { - if !*noTrunc { - out.ID = utils.TruncateID(out.ID) - } + components := strings.SplitN(repotag, ":", 2) + repo = components[0] + tag = components[1] - if !*quiet { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", out.Repository, out.Tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0)))) - if out.VirtualSize > 0 { - fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize)) - } else { - fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size)) + if !*noTrunc { + out.ID = utils.TruncateID(out.ID) + } + + if !*quiet { + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", repo, tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0)))) + if out.VirtualSize > 0 { + fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize)) + } else { + fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size)) + } + } else { + fmt.Fprintln(w, out.ID) } - } else { - fmt.Fprintln(w, out.ID) } } diff --git a/components/engine/server.go b/components/engine/server.go index cbe7c6435c..2492c88683 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -284,7 +284,7 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) { if err != nil { return nil, err } - outs := []APIImages{} //produce [] when empty instead of 'null' + lookup := make(map[string]APIImages) for name, repository := range srv.runtime.repositories.Repositories { if filter != "" { if match, _ := path.Match(filter, name); !match { @@ -292,23 +292,40 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) { } } for tag, id := range repository { - var out APIImages image, err := srv.runtime.graph.Get(id) if err != nil { log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err) continue } - delete(allImages, id) - out.Repository = name - out.Tag = tag - out.ID = image.ID - out.Created = image.Created.Unix() - out.Size = image.Size - out.VirtualSize = image.getParentsSize(0) + image.Size - outs = append(outs, out) + + if out, exists := lookup[id]; exists { + out.RepoTags = append(out.RepoTags, fmt.Sprintf("%s:%s", name, tag)) + + lookup[id] = out + } else { + var out APIImages + + delete(allImages, id) + + out.ParentId = image.Parent + out.RepoTags = []string{fmt.Sprintf("%s:%s", name, tag)} + out.ID = image.ID + out.Created = image.Created.Unix() + out.Size = image.Size + out.VirtualSize = image.getParentsSize(0) + image.Size + + lookup[id] = out + } + } } - // Display images which aren't part of a + + outs := make([]APIImages, 0, len(lookup)) + for _, value := range lookup { + outs = append(outs, value) + } + + // Display images which aren't part of a repository/tag if filter == "" { for _, image := range allImages { var out APIImages diff --git a/components/engine/sorter.go b/components/engine/sorter.go index d4331eaf1f..c9a86b45c0 100644 --- a/components/engine/sorter.go +++ b/components/engine/sorter.go @@ -25,7 +25,7 @@ func (s *imageSorter) Less(i, j int) bool { // Sort []ApiImages by most recent creation date and tag name. func sortImagesByCreationAndTag(images []APIImages) { creationAndTag := func(i1, i2 *APIImages) bool { - return i1.Created > i2.Created || (i1.Created == i2.Created && i2.Tag > i1.Tag) + return i1.Created > i2.Created } sorter := &imageSorter{ From b5c0cbf91743269580effe18b5326aeca53b7f3e Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Sun, 6 Oct 2013 05:59:49 +0000 Subject: [PATCH 027/184] bump api version and ensure backward compat Upstream-commit: d7928b9a673e9b2aff36fa6961481312455c91f4 Component: engine --- components/engine/api.go | 13 +++++++++++-- components/engine/api_params.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/components/engine/api.go b/components/engine/api.go index 0c06b2e6d1..00673de339 100644 --- a/components/engine/api.go +++ b/components/engine/api.go @@ -23,7 +23,7 @@ import ( ) const ( - APIVERSION = 1.6 + APIVERSION = 1.7 DEFAULTHTTPHOST = "127.0.0.1" DEFAULTHTTPPORT = 4243 DEFAULTUNIXSOCKET = "/var/run/docker.sock" @@ -191,7 +191,16 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. return err } - return writeJSON(w, http.StatusOK, outs) + if version < 1.7 { + outs2 := []APIImagesOld{} + for _, ctnr := range outs { + outs2 = append(outs2, ctnr.ToLegacy()...) + } + + return writeJSON(w, http.StatusOK, outs2) + } else { + return writeJSON(w, http.StatusOK, outs) + } } func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/components/engine/api_params.go b/components/engine/api_params.go index d796b96542..e4508542ef 100644 --- a/components/engine/api_params.go +++ b/components/engine/api_params.go @@ -1,5 +1,7 @@ package docker +import "strings" + type APIHistory struct { ID string `json:"Id"` Tags []string `json:",omitempty"` @@ -17,6 +19,35 @@ type APIImages struct { ParentId string `json:",omitempty"` } +type APIImagesOld struct { + Repository string `json:",omitempty"` + Tag string `json:",omitempty"` + ID string `json:"Id"` + Created int64 + Size int64 + VirtualSize int64 +} + +func (self *APIImages) ToLegacy() []APIImagesOld { + + outs := []APIImagesOld{} + for _, repotag := range self.RepoTags { + + components := strings.SplitN(repotag, ":", 2) + + outs = append(outs, APIImagesOld{ + ID: self.ID, + Repository: components[0], + Tag: components[1], + Created: self.Created, + Size: self.Size, + VirtualSize: self.VirtualSize, + }) + } + + return outs +} + type APIInfo struct { Debug bool Containers int From 08d9fe490e2649027b08c8c40eb3702490e36bf2 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Sun, 6 Oct 2013 16:36:38 +0000 Subject: [PATCH 028/184] fix "images -a" Upstream-commit: dcf0279a50e7a2eca97b205725df474a516b8f79 Component: engine --- components/engine/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/server.go b/components/engine/server.go index 2492c88683..ab8193f4c2 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -330,6 +330,7 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) { for _, image := range allImages { var out APIImages out.ID = image.ID + out.RepoTags = []string{":"} out.Created = image.Created.Unix() out.Size = image.Size out.VirtualSize = image.getParentsSize(0) + image.Size From cbbaa36a2b49fc7f26abe29e019aacaef48903bb Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Tue, 8 Oct 2013 13:41:44 +0000 Subject: [PATCH 029/184] move "images -viz" to client Upstream-commit: 2a5998baf172f94a8d6866e46207a0d7d75e70e5 Component: engine --- components/engine/api.go | 8 ------- components/engine/commands.go | 24 +++++++++++++++++++-- components/engine/server.go | 39 +---------------------------------- 3 files changed, 23 insertions(+), 48 deletions(-) diff --git a/components/engine/api.go b/components/engine/api.go index 00673de339..095095954e 100644 --- a/components/engine/api.go +++ b/components/engine/api.go @@ -203,13 +203,6 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. } } -func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := srv.ImagesViz(w); err != nil { - return err - } - return nil -} - func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { return writeJSON(w, http.StatusOK, srv.DockerInfo()) } @@ -1046,7 +1039,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { "/info": getInfo, "/version": getVersion, "/images/json": getImagesJSON, - "/images/viz": getImagesViz, "/images/search": getImagesSearch, "/images/{name:.*}/history": getImagesHistory, "/images/{name:.*}/json": getImagesByName, diff --git a/components/engine/commands.go b/components/engine/commands.go index 7fc3dceb43..b3cd7958a4 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -1067,11 +1067,31 @@ func (cli *DockerCli) CmdImages(args ...string) error { } if *flViz { - body, _, err := cli.call("GET", "/images/viz", false) + body, _, err := cli.call("GET", "/images/json?all=1", nil) if err != nil { return err } - fmt.Fprintf(cli.out, "%s", body) + + var outs []APIImages + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + + fmt.Fprintf(cli.out, "digraph docker {\n") + + for _, image := range outs { + if image.ParentId == "" { + fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", utils.TruncateID(image.ID)) + } else { + fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", utils.TruncateID(image.ParentId), utils.TruncateID(image.ID)) + } + if image.RepoTags[0] != ":" { + fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", utils.TruncateID(image.ID), utils.TruncateID(image.ID), strings.Join(image.RepoTags, "\\n")) + } + } + + fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") } else { v := url.Values{} if cmd.NArg() == 1 { diff --git a/components/engine/server.go b/components/engine/server.go index ab8193f4c2..644a433eeb 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -233,44 +233,6 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. return img.ShortID(), nil } -func (srv *Server) ImagesViz(out io.Writer) error { - images, _ := srv.runtime.graph.Map() - if images == nil { - return nil - } - out.Write([]byte("digraph docker {\n")) - - var ( - parentImage *Image - err error - ) - for _, image := range images { - parentImage, err = image.GetParent() - if err != nil { - return err - } - if parentImage != nil { - out.Write([]byte(" \"" + parentImage.ShortID() + "\" -> \"" + image.ShortID() + "\"\n")) - } else { - out.Write([]byte(" base -> \"" + image.ShortID() + "\" [style=invis]\n")) - } - } - - reporefs := make(map[string][]string) - - for name, repository := range srv.runtime.repositories.Repositories { - for tag, id := range repository { - reporefs[utils.TruncateID(id)] = append(reporefs[utils.TruncateID(id)], fmt.Sprintf("%s:%s", name, tag)) - } - } - - for id, repos := range reporefs { - out.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n")) - } - out.Write([]byte(" base [style=invisible]\n}\n")) - return nil -} - func (srv *Server) Images(all bool, filter string) ([]APIImages, error) { var ( allImages map[string]*Image @@ -330,6 +292,7 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) { for _, image := range allImages { var out APIImages out.ID = image.ID + out.ParentId = image.Parent out.RepoTags = []string{":"} out.Created = image.Created.Unix() out.Size = image.Size From 736f474973b1e20b009c82ea26083adfcc3d934d Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Tue, 8 Oct 2013 13:52:36 +0000 Subject: [PATCH 030/184] bring back /images/viz, but 404 for latest Upstream-commit: 8f64759881b3d48e42927ed9bdf1c9162d3c8bb3 Component: engine --- components/engine/api.go | 13 +++++++++++++ components/engine/server.go | 38 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/components/engine/api.go b/components/engine/api.go index 095095954e..61252ab9af 100644 --- a/components/engine/api.go +++ b/components/engine/api.go @@ -203,6 +203,18 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. } } +func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if version > 1.6 { + w.WriteHeader(http.StatusNotFound) + return fmt.Errorf("This is now implemented in the client.") + } + + if err := srv.ImagesViz(w); err != nil { + return err + } + return nil +} + func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { return writeJSON(w, http.StatusOK, srv.DockerInfo()) } @@ -1039,6 +1051,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { "/info": getInfo, "/version": getVersion, "/images/json": getImagesJSON, + "/images/viz": getImagesViz, "/images/search": getImagesSearch, "/images/{name:.*}/history": getImagesHistory, "/images/{name:.*}/json": getImagesByName, diff --git a/components/engine/server.go b/components/engine/server.go index 644a433eeb..3441029027 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -233,6 +233,44 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. return img.ShortID(), nil } +func (srv *Server) ImagesViz(out io.Writer) error { + images, _ := srv.runtime.graph.Map() + if images == nil { + return nil + } + out.Write([]byte("digraph docker {\n")) + + var ( + parentImage *Image + err error + ) + for _, image := range images { + parentImage, err = image.GetParent() + if err != nil { + return fmt.Errorf("Error while getting parent image: %v", err) + } + if parentImage != nil { + out.Write([]byte(" \"" + parentImage.ShortID() + "\" -> \"" + image.ShortID() + "\"\n")) + } else { + out.Write([]byte(" base -> \"" + image.ShortID() + "\" [style=invis]\n")) + } + } + + reporefs := make(map[string][]string) + + for name, repository := range srv.runtime.repositories.Repositories { + for tag, id := range repository { + reporefs[utils.TruncateID(id)] = append(reporefs[utils.TruncateID(id)], fmt.Sprintf("%s:%s", name, tag)) + } + } + + for id, repos := range reporefs { + out.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n")) + } + out.Write([]byte(" base [style=invisible]\n}\n")) + return nil +} + func (srv *Server) Images(all bool, filter string) ([]APIImages, error) { var ( allImages map[string]*Image From 2dbd3407deb26687f4a8719a7862fb7af33e95a2 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Wed, 9 Oct 2013 14:38:39 +0000 Subject: [PATCH 031/184] implement "images -tree" Upstream-commit: ec9a9a08b8b76079207ced5b55a7caade27fb3d3 Component: engine --- components/engine/commands.go | 88 +++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/components/engine/commands.go b/components/engine/commands.go index b3cd7958a4..c793b27c08 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -1057,6 +1057,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { all := cmd.Bool("a", false, "show all images") noTrunc := cmd.Bool("notrunc", false, "Don't truncate output") flViz := cmd.Bool("viz", false, "output graph in graphviz format") + flTree := cmd.Bool("tree", false, "output graph in tree format") if err := cmd.Parse(args); err != nil { return nil @@ -1092,6 +1093,52 @@ func (cli *DockerCli) CmdImages(args ...string) error { } fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") + } else if *flTree { + body, _, err := cli.call("GET", "/images/json?all=1", nil) + if err != nil { + return err + } + + var outs []APIImages + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + + var startImageArg = cmd.Arg(0) + var startImage APIImages + + var roots []APIImages + var byParent = make(map[string][]APIImages) + for _, image := range outs { + if image.ParentId == "" { + roots = append(roots, image) + } else { + if children, exists := byParent[image.ParentId]; exists { + byParent[image.ParentId] = append(children, image) + } else { + byParent[image.ParentId] = []APIImages{image} + } + } + + if startImageArg != "" { + if startImageArg == image.ID || startImageArg == utils.TruncateID(image.ID) { + startImage = image + } + + for _, repotag := range image.RepoTags { + if repotag == startImageArg { + startImage = image + } + } + } + } + + if startImageArg != "" { + WalkTree(cli, noTrunc, []APIImages{startImage}, byParent, "") + } else { + WalkTree(cli, noTrunc, roots, byParent, "") + } } else { v := url.Values{} if cmd.NArg() == 1 { @@ -1150,6 +1197,47 @@ func (cli *DockerCli) CmdImages(args ...string) error { return nil } +func WalkTree(cli *DockerCli, noTrunc *bool, images []APIImages, byParent map[string][]APIImages, prefix string) { + if len(images) > 1 { + length := len(images) + for index, image := range images { + if index+1 == length { + PrintTreeNode(cli, noTrunc, image, prefix+"└─") + if subimages, exists := byParent[image.ID]; exists { + WalkTree(cli, noTrunc, subimages, byParent, prefix+" ") + } + } else { + PrintTreeNode(cli, noTrunc, image, prefix+"|─") + if subimages, exists := byParent[image.ID]; exists { + WalkTree(cli, noTrunc, subimages, byParent, prefix+"| ") + } + } + } + } else { + for _, image := range images { + PrintTreeNode(cli, noTrunc, image, prefix+"└─") + if subimages, exists := byParent[image.ID]; exists { + WalkTree(cli, noTrunc, subimages, byParent, prefix+" ") + } + } + } +} + +func PrintTreeNode(cli *DockerCli, noTrunc *bool, image APIImages, prefix string) { + var imageID string + if *noTrunc { + imageID = image.ID + } else { + imageID = utils.TruncateID(image.ID) + } + + if image.RepoTags[0] != ":" { + fmt.Fprintf(cli.out, "%s%s Tags: %s\n", prefix, imageID, strings.Join(image.RepoTags, ",")) + } else { + fmt.Fprintf(cli.out, "%s%s\n", prefix, imageID) + } +} + func displayablePorts(ports []APIPort) string { result := []string{} for _, port := range ports { From f53ad091dd3386b2892109267b1e5c131e853487 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Sun, 13 Oct 2013 13:42:01 +0000 Subject: [PATCH 032/184] make existing tests pass Upstream-commit: 059bb7a262a7620d8c7c5fdd064af352ae242f65 Component: engine --- components/engine/api_test.go | 31 +++---------------------------- components/engine/server_test.go | 18 +++++++++--------- components/engine/sorter_test.go | 32 +++++++++++++++++++++++++------- 3 files changed, 37 insertions(+), 44 deletions(-) diff --git a/components/engine/api_test.go b/components/engine/api_test.go index fcca8ce83f..0027362009 100644 --- a/components/engine/api_test.go +++ b/components/engine/api_test.go @@ -184,7 +184,7 @@ func TestGetImagesJSON(t *testing.T) { found := false for _, img := range images { - if img.Repository == unitTestImageName { + if strings.Contains(img.RepoTags[0], unitTestImageName) { found = true break } @@ -275,31 +275,6 @@ func TestGetImagesJSON(t *testing.T) { } } -func TestGetImagesViz(t *testing.T) { - runtime := mkRuntime(t) - defer nuke(runtime) - - srv := &Server{runtime: runtime} - - r := httptest.NewRecorder() - if err := getImagesViz(srv, APIVERSION, r, nil, nil); err != nil { - t.Fatal(err) - } - - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } - - reader := bufio.NewReader(r.Body) - line, err := reader.ReadString('\n') - if err != nil { - t.Fatal(err) - } - if line != "digraph docker {\n" { - t.Errorf("Expected digraph docker {\n, %s found", line) - } -} - func TestGetImagesHistory(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) @@ -1226,7 +1201,7 @@ func TestDeleteImages(t *testing.T) { t.Fatal(err) } - if len(images) != len(initialImages)+1 { + if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 { t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images)) } @@ -1265,7 +1240,7 @@ func TestDeleteImages(t *testing.T) { t.Fatal(err) } - if len(images) != len(initialImages) { + if len(images[0].RepoTags) != len(initialImages[0].RepoTags) { t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) } diff --git a/components/engine/server_test.go b/components/engine/server_test.go index 7f9cbaadfd..4072344f35 100644 --- a/components/engine/server_test.go +++ b/components/engine/server_test.go @@ -34,8 +34,8 @@ func TestContainerTagImageDelete(t *testing.T) { t.Fatal(err) } - if len(images) != len(initialImages)+3 { - t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images)) + if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+3 { + t.Errorf("Expected %d images, %d found", len(initialImages)+3, len(images)) } if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil { @@ -47,7 +47,7 @@ func TestContainerTagImageDelete(t *testing.T) { t.Fatal(err) } - if len(images) != len(initialImages)+2 { + if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+2 { t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images)) } @@ -60,7 +60,7 @@ func TestContainerTagImageDelete(t *testing.T) { t.Fatal(err) } - if len(images) != len(initialImages)+1 { + if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 { t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images)) } @@ -462,7 +462,7 @@ func TestRmi(t *testing.T) { if strings.Contains(unitTestImageID, image.ID) { continue } - if image.Repository == "" { + if image.RepoTags[0] == ":" { t.Fatalf("Expected tagged image, got untagged one.") } } @@ -490,7 +490,7 @@ func TestImagesFilter(t *testing.T) { t.Fatal(err) } - if len(images) != 2 { + if len(images[0].RepoTags) != 2 { t.Fatal("incorrect number of matches returned") } @@ -499,7 +499,7 @@ func TestImagesFilter(t *testing.T) { t.Fatal(err) } - if len(images) != 1 { + if len(images[0].RepoTags) != 1 { t.Fatal("incorrect number of matches returned") } @@ -508,7 +508,7 @@ func TestImagesFilter(t *testing.T) { t.Fatal(err) } - if len(images) != 1 { + if len(images[0].RepoTags) != 1 { t.Fatal("incorrect number of matches returned") } @@ -517,7 +517,7 @@ func TestImagesFilter(t *testing.T) { t.Fatal(err) } - if len(images) != 1 { + if len(images[0].RepoTags) != 1 { t.Fatal("incorrect number of matches returned") } } diff --git a/components/engine/sorter_test.go b/components/engine/sorter_test.go index d61b1a7112..54f647132f 100644 --- a/components/engine/sorter_test.go +++ b/components/engine/sorter_test.go @@ -3,6 +3,7 @@ package docker import ( "fmt" "testing" + "time" ) func TestServerListOrderedImagesByCreationDate(t *testing.T) { @@ -34,29 +35,46 @@ func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - archive, err := fakeTar() + err := generateImage("bar", runtime) if err != nil { t.Fatal(err) } - image, err := runtime.graph.Create(archive, nil, "Testing", "", nil) + + time.Sleep(time.Second) + + err = generateImage("zed", runtime) if err != nil { t.Fatal(err) } srv := &Server{runtime: runtime} - srv.ContainerTag(image.ID, "repo", "foo", false) - srv.ContainerTag(image.ID, "repo", "bar", false) - images, err := srv.Images(true, "") if err != nil { t.Fatal(err) } - if images[0].Created != images[1].Created || images[0].Tag >= images[1].Tag { - t.Error("Expected []APIImges to be ordered by most recent creation date and tag name.") + if images[0].RepoTags[0] != "repo:zed" && images[0].RepoTags[0] != "repo:bar" { + t.Errorf("Expected []APIImges to be ordered by most recent creation date. %s", images) } } +func generateImage(name string, runtime *Runtime) error { + + archive, err := fakeTar() + if err != nil { + return err + } + image, err := runtime.graph.Create(archive, nil, "Testing", "", nil) + if err != nil { + return err + } + + srv := &Server{runtime: runtime} + srv.ContainerTag(image.ID, "repo", name, false) + + return nil +} + func TestSortUniquePorts(t *testing.T) { ports := []Port{ Port("6379/tcp"), From 7820a4a479c52553a3db625dedc9e4e79a7bc6bb Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Sun, 13 Oct 2013 22:36:05 +0000 Subject: [PATCH 033/184] adding test for "images -viz" Upstream-commit: d9fe0647cb464ccd6f79ac33fb1d1ac56441f6b0 Component: engine --- components/engine/commands_test.go | 48 ++++++++++++++++++++++++++++++ components/engine/runtime_test.go | 1 + 2 files changed, 49 insertions(+) diff --git a/components/engine/commands_test.go b/components/engine/commands_test.go index 1ec005bdd1..28a19d2af8 100644 --- a/components/engine/commands_test.go +++ b/components/engine/commands_test.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" + "regexp" "strings" "testing" "time" @@ -699,3 +700,50 @@ func TestRunErrorBindNonExistingSource(t *testing.T) { <-c }) } + +func TestImagesViz(t *testing.T) { + stdout, stdoutPipe := io.Pipe() + + cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + + c := make(chan struct{}) + go func() { + defer close(c) + if err := cli.CmdImages("-viz"); err != nil { + t.Fatal(err) + } + stdoutPipe.Close() + }() + + setTimeout(t, "Reading command output time out", 2*time.Second, func() { + cmdOutputBytes, err := ioutil.ReadAll(bufio.NewReader(stdout)) + if err != nil { + t.Fatal(err) + } + cmdOutput := string(cmdOutputBytes) + + regexpStrings := []string{ + "digraph docker {", + fmt.Sprintf("base -> \"%s\" \\[style=invis]", unitTestImageIDShort), + fmt.Sprintf("label=\"%s\\\\n%s:latest\"", unitTestImageIDShort, unitTestImageName), + "base \\[style=invisible]", + } + + compiledRegexps := []*regexp.Regexp{} + for _, regexpString := range regexpStrings { + regexp, err := regexp.Compile(regexpString) + if err != nil { + fmt.Println("Error in regex string: ", err) + return + } + compiledRegexps = append(compiledRegexps, regexp) + } + + for _, regexp := range compiledRegexps { + if !regexp.MatchString(cmdOutput) { + t.Fatalf("images -viz content '%s' did not match regexp '%s'", cmdOutput, regexp) + } + } + }) +} diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 4e46b7b6d0..e61f63d495 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -22,6 +22,7 @@ import ( const ( unitTestImageName = "docker-test-image" unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestImageIDShort = "83599e29c455" unitTestNetworkBridge = "testdockbr0" unitTestStoreBase = "/var/lib/docker/unit-tests" testDaemonAddr = "127.0.0.1:4270" From 377a3eb7ca6d4593635f3ef1fcde0c1038a6b4ea Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Sun, 27 Oct 2013 23:13:00 +0000 Subject: [PATCH 034/184] adding test for "images -tree" Upstream-commit: f1aeac361aadc90a35e1b839933afbf9ad27a1a1 Component: engine --- components/engine/commands_test.go | 78 ++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/components/engine/commands_test.go b/components/engine/commands_test.go index 28a19d2af8..bed9557bb1 100644 --- a/components/engine/commands_test.go +++ b/components/engine/commands_test.go @@ -707,6 +707,9 @@ func TestImagesViz(t *testing.T) { cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) defer cleanup(globalRuntime) + srv := &Server{runtime: globalRuntime} + image := buildTestImages(t, srv) + c := make(chan struct{}) go func() { defer close(c) @@ -727,6 +730,7 @@ func TestImagesViz(t *testing.T) { "digraph docker {", fmt.Sprintf("base -> \"%s\" \\[style=invis]", unitTestImageIDShort), fmt.Sprintf("label=\"%s\\\\n%s:latest\"", unitTestImageIDShort, unitTestImageName), + fmt.Sprintf("label=\"%s\\\\n%s:%s\"", utils.TruncateID(image.ID), "test", "latest"), "base \\[style=invisible]", } @@ -747,3 +751,77 @@ func TestImagesViz(t *testing.T) { } }) } + +func TestImagesTree(t *testing.T) { + stdout, stdoutPipe := io.Pipe() + + cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + + srv := &Server{runtime: globalRuntime} + image := buildTestImages(t, srv) + + c := make(chan struct{}) + go func() { + defer close(c) + if err := cli.CmdImages("-tree"); err != nil { + t.Fatal(err) + } + stdoutPipe.Close() + }() + + setTimeout(t, "Reading command output time out", 2*time.Second, func() { + cmdOutputBytes, err := ioutil.ReadAll(bufio.NewReader(stdout)) + if err != nil { + t.Fatal(err) + } + cmdOutput := string(cmdOutputBytes) + + regexpStrings := []string{ + fmt.Sprintf("└─%s Tags: %s:latest", unitTestImageIDShort, unitTestImageName), + "(?m)^ └─[0-9a-f]+", + "(?m)^ └─[0-9a-f]+", + "(?m)^ └─[0-9a-f]+", + fmt.Sprintf(" └─%s Tags: test:latest", utils.TruncateID(image.ID)), + } + + compiledRegexps := []*regexp.Regexp{} + for _, regexpString := range regexpStrings { + regexp, err := regexp.Compile(regexpString) + if err != nil { + fmt.Println("Error in regex string: ", err) + return + } + compiledRegexps = append(compiledRegexps, regexp) + } + + for _, regexp := range compiledRegexps { + if !regexp.MatchString(cmdOutput) { + t.Fatalf("images -tree content '%s' did not match regexp '%s'", cmdOutput, regexp) + } + } + }) +} + +func buildTestImages(t *testing.T, srv *Server) *Image { + + var testBuilder = testContextTemplate{ + ` +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, + } + image := buildImage(testBuilder, t, srv, true) + + err := srv.ContainerTag(image.ID, "test", "latest", false) + if err != nil { + t.Fatal(err) + } + + return image +} From e9a1f4f7fff99b9858482221f5f64caabc5d0837 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Sun, 27 Oct 2013 23:42:09 +0000 Subject: [PATCH 035/184] add image size to -tree Upstream-commit: b7d1d35c278c57ad4732dc23e529cf771158ee96 Component: engine --- components/engine/commands.go | 5 +++-- components/engine/commands_test.go | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index c793b27c08..8cb51d4195 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -1231,10 +1231,11 @@ func PrintTreeNode(cli *DockerCli, noTrunc *bool, image APIImages, prefix string imageID = utils.TruncateID(image.ID) } + fmt.Fprintf(cli.out, "%s%s Size: %s (virtual %s)", prefix, imageID, utils.HumanSize(image.Size), utils.HumanSize(image.VirtualSize)) if image.RepoTags[0] != ":" { - fmt.Fprintf(cli.out, "%s%s Tags: %s\n", prefix, imageID, strings.Join(image.RepoTags, ",")) + fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ",")) } else { - fmt.Fprintf(cli.out, "%s%s\n", prefix, imageID) + fmt.Fprint(cli.out, "\n") } } diff --git a/components/engine/commands_test.go b/components/engine/commands_test.go index bed9557bb1..479e295dd8 100644 --- a/components/engine/commands_test.go +++ b/components/engine/commands_test.go @@ -778,11 +778,11 @@ func TestImagesTree(t *testing.T) { cmdOutput := string(cmdOutputBytes) regexpStrings := []string{ - fmt.Sprintf("└─%s Tags: %s:latest", unitTestImageIDShort, unitTestImageName), + fmt.Sprintf("└─%s Size: (\\d+.\\d+ MB) \\(virtual \\d+.\\d+ MB\\) Tags: %s:latest", unitTestImageIDShort, unitTestImageName), "(?m)^ └─[0-9a-f]+", "(?m)^ └─[0-9a-f]+", "(?m)^ └─[0-9a-f]+", - fmt.Sprintf(" └─%s Tags: test:latest", utils.TruncateID(image.ID)), + fmt.Sprintf(" └─%s Size: \\d+.\\d+ kB \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)), } compiledRegexps := []*regexp.Regexp{} From 2e403649d0b704d4c6bb40b7ed71567521ce9126 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Mon, 28 Oct 2013 04:02:24 +0000 Subject: [PATCH 036/184] update "images" documentation Upstream-commit: 6d5b73c2a6d668b11d6b5c643a45170265bf73c0 Component: engine --- .../docs/sources/api/docker_remote_api.rst | 108 +- .../sources/api/docker_remote_api_v1.7.rst | 1187 +++++++++++++++++ .../engine/docs/sources/commandline/cli.rst | 30 + 3 files changed, 1323 insertions(+), 2 deletions(-) create mode 100644 components/engine/docs/sources/api/docker_remote_api_v1.7.rst diff --git a/components/engine/docs/sources/api/docker_remote_api.rst b/components/engine/docs/sources/api/docker_remote_api.rst index 86cacd17d9..d5cb1a44a9 100644 --- a/components/engine/docs/sources/api/docker_remote_api.rst +++ b/components/engine/docs/sources/api/docker_remote_api.rst @@ -26,14 +26,118 @@ Docker Remote API 2. Versions =========== -The current version of the API is 1.6 +The current version of the API is 1.7 Calling /images//insert is the same as calling -/v1.6/images//insert +/v1.7/images//insert You can still call an old version of the api using /v1.0/images//insert +v1.7 +**** + +Full Documentation +------------------ + +:doc:`docker_remote_api_v1.7` + +What's new +---------- + +.. http:get:: /images/json + + The format of the json returned from this uri changed. Instead of an entry + for each repo/tag on an image, each image is only represented once, with a + nested attribute indicating the repo/tags that apply to that image. + + Instead of: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "VirtualSize": 131506275, + "Size": 131506275, + "Created": 1365714795, + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Tag": "12.04", + "Repository": "ubuntu" + }, + { + "VirtualSize": 131506275, + "Size": 131506275, + "Created": 1365714795, + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Tag": "latest", + "Repository": "ubuntu" + }, + { + "VirtualSize": 131506275, + "Size": 131506275, + "Created": 1365714795, + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Tag": "precise", + "Repository": "ubuntu" + }, + { + "VirtualSize": 180116135, + "Size": 24653, + "Created": 1364102658, + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Tag": "12.10", + "Repository": "ubuntu" + }, + { + "VirtualSize": 180116135, + "Size": 24653, + "Created": 1364102658, + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Tag": "quantal", + "Repository": "ubuntu" + } + ] + + The returned json looks like this: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "RepoTag": [ + "ubuntu:12.04", + "ubuntu:precise", + "ubuntu:latest" + ], + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Created": 1365714795, + "Size": 131506275, + "VirtualSize": 131506275 + }, + { + "RepoTag": [ + "ubuntu:12.10", + "ubuntu:quantal" + ], + "ParentId": "27cf784147099545", + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Created": 1364102658, + "Size": 24653, + "VirtualSize": 180116135 + } + ] + +.. http:get:: /images/viz + + This URI no longer exists. The ``images -viz`` output is now generated in + the client, using the ``/images/json`` data. + v1.6 **** diff --git a/components/engine/docs/sources/api/docker_remote_api_v1.7.rst b/components/engine/docs/sources/api/docker_remote_api_v1.7.rst new file mode 100644 index 0000000000..d1b094c872 --- /dev/null +++ b/components/engine/docs/sources/api/docker_remote_api_v1.7.rst @@ -0,0 +1,1187 @@ +:title: Remote API v1.7 +:description: API Documentation for Docker +:keywords: API, Docker, rcli, REST, documentation + +:orphan: + +====================== +Docker Remote API v1.7 +====================== + +.. contents:: Table of Contents + +1. Brief introduction +===================== + +- The Remote API is replacing rcli +- Default port in the docker daemon 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 + +2. Endpoints +============ + +2.1 Containers +-------------- + +List containers +*************** + +.. http:get:: /containers/json + + List containers + + **Example request**: + + .. sourcecode:: http + + GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id": "8dfafdbc3a40", + "Image": "base:latest", + "Command": "echo 1", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "9cd87474be90", + "Image": "base:latest", + "Command": "echo 222222", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "3176a2479c92", + "Image": "base:latest", + "Command": "echo 3333333333333333", + "Created": 1367854154, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "4cb07b47f9fb", + "Image": "base:latest", + "Command": "echo 444444444444444444444444444444444", + "Created": 1367854152, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + } + ] + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default + :query limit: Show ``limit`` last created containers, include non-running ones. + :query since: Show only containers created since Id, include non-running ones. + :query before: Show only containers created before Id, include non-running ones. + :query size: 1/True/true or 0/False/false, Show the containers sizes + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error + + +Create a container +****************** + +.. http:post:: /containers/create + + Create a container + + **Example request**: + + .. sourcecode:: http + + POST /containers/create HTTP/1.1 + Content-Type: application/json + + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Privileged": false, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "date" + ], + "Dns":null, + "Image":"base", + "Volumes":{}, + "VolumesFrom":"", + "WorkingDir":"" + + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/json + + { + "Id":"e90e34656806" + "Warnings":[] + } + + :jsonparam config: the container's configuration + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 406: impossible to attach (container not running) + :statuscode 500: server error + + +Inspect a container +******************* + +.. http:get:: /containers/(id)/json + + Return low-level information on the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", + "Created": "2013-05-07T14:51:42.041847+02:00", + "Path": "date", + "Args": [], + "Config": { + "Hostname": "4fa6e0f0c678", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "PortSpecs": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Dns": null, + "Image": "base", + "Volumes": {}, + "VolumesFrom": "", + "WorkingDir":"" + + }, + "State": { + "Running": false, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-05-07T14:51:42.087658+02:01360", + "Ghost": false + }, + "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "NetworkSettings": { + "IpAddress": "", + "IpPrefixLen": 0, + "Gateway": "", + "Bridge": "", + "PortMapping": null + }, + "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", + "ResolvConfPath": "/etc/resolv.conf", + "Volumes": {} + } + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +List processes running inside a container +***************************************** + +.. http:get:: /containers/(id)/top + + List processes running inside the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/top HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Titles":[ + "USER", + "PID", + "%CPU", + "%MEM", + "VSZ", + "RSS", + "TTY", + "STAT", + "START", + "TIME", + "COMMAND" + ], + "Processes":[ + ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], + ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] + ] + } + + :query ps_args: ps arguments to use (eg. aux) + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Inspect changes on a container's filesystem +******************************************* + +.. http:get:: /containers/(id)/changes + + Inspect changes on container ``id`` 's filesystem + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/changes HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Path":"/dev", + "Kind":0 + }, + { + "Path":"/dev/kmsg", + "Kind":1 + }, + { + "Path":"/test", + "Kind":1 + } + ] + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Export a container +****************** + +.. http:get:: /containers/(id)/export + + Export the contents of container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/export HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Start a container +***************** + +.. http:post:: /containers/(id)/start + + Start the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/(id)/start HTTP/1.1 + Content-Type: application/json + + { + "Binds":["/tmp:/tmp"], + "LxcConf":{"lxc.utsname":"docker"} + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 No Content + Content-Type: text/plain + + :jsonparam hostConfig: the container's host configuration (optional) + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Stop a container +**************** + +.. http:post:: /containers/(id)/stop + + Stop the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/stop?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Restart a container +******************* + +.. http:post:: /containers/(id)/restart + + Restart the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/restart?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Kill a container +**************** + +.. http:post:: /containers/(id)/kill + + Kill the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/kill HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Attach to a container +********************* + +.. http:post:: /containers/(id)/attach + + Attach to the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream + + {{ STREAM }} + + :query logs: 1/True/true or 0/False/false, return logs. Default false + :query stream: 1/True/true or 0/False/false, return stream. Default false + :query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false + :query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false + :query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + **Stream details**: + + When using the TTY setting is enabled in + :http:post:`/containers/create`, the stream is the raw data + from the process PTY and client's stdin. When the TTY is + disabled, then the stream is multiplexed to separate stdout + and stderr. + + The format is a **Header** and a **Payload** (frame). + + **HEADER** + + The header will contain the information on which stream write + the stream (stdout or stderr). It also contain the size of + the associated frame encoded on the last 4 bytes (uint32). + + It is encoded on the first 8 bytes like this:: + + header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} + + ``STREAM_TYPE`` can be: + + - 0: stdin (will be writen on stdout) + - 1: stdout + - 2: stderr + + ``SIZE1, SIZE2, SIZE3, SIZE4`` are the 4 bytes of the uint32 size encoded as big endian. + + **PAYLOAD** + + The payload is the raw stream. + + **IMPLEMENTATION** + + The simplest way to implement the Attach protocol is the following: + + 1) Read 8 bytes + 2) chose stdout or stderr depending on the first byte + 3) Extract the frame size from the last 4 byets + 4) Read the extracted size and output it on the correct output + 5) Goto 1) + + + +Wait a container +**************** + +.. http:post:: /containers/(id)/wait + + Block until container ``id`` stops, then returns the exit code + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/wait HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"StatusCode":0} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Remove a container +******************* + +.. http:delete:: /containers/(id) + + Remove the container ``id`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /containers/16253994b7c4?v=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false + :statuscode 204: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + +Copy files or folders from a container +************************************** + +.. http:post:: /containers/(id)/copy + + Copy files or folders of container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/4fa6e0f0c678/copy HTTP/1.1 + Content-Type: application/json + + { + "Resource":"test.txt" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +2.2 Images +---------- + +List Images +*********** + +.. http:get:: /images/(format) + + List images ``format`` could be json or viz (json default) + + **Example request**: + + .. sourcecode:: http + + GET /images/json?all=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "RepoTag": [ + "ubuntu:12.04", + "ubuntu:precise", + "ubuntu:latest" + ], + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Created": 1365714795, + "Size": 131506275, + "VirtualSize": 131506275 + }, + { + "RepoTag": [ + "ubuntu:12.10", + "ubuntu:quantal" + ], + "ParentId": "27cf784147099545", + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Created": 1364102658, + "Size": 24653, + "VirtualSize": 180116135 + } + ] + + +Create an image +*************** + +.. http:post:: /images/create + + Create an image, either by pull it from the registry or by importing it + + **Example request**: + + .. sourcecode:: http + + POST /images/create?fromImage=base HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pulling..."} + {"status":"Pulling", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... + + When using this endpoint to pull an image from the registry, + the ``X-Registry-Auth`` header can be used to include a + base64-encoded AuthConfig object. + + :query fromImage: name of the image to pull + :query fromSrc: source to import, - means stdin + :query repo: repository + :query tag: tag + :query registry: the registry to pull from + :statuscode 200: no error + :statuscode 500: server error + + +Insert a file in an image +************************* + +.. http:post:: /images/(name)/insert + + Insert a file from ``url`` in the image ``name`` at ``path`` + + **Example request**: + + .. sourcecode:: http + + POST /images/test/insert?path=/usr&url=myurl HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Inserting..."} + {"status":"Inserting", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... + + :statuscode 200: no error + :statuscode 500: server error + + +Inspect an image +**************** + +.. http:get:: /images/(name)/json + + Return low-level information on the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "parent":"27cf784147099545", + "created":"2013-03-23T22:24:18.818426-07:00", + "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", + "container_config": + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":false, + "AttachStderr":false, + "PortSpecs":null, + "Tty":true, + "OpenStdin":true, + "StdinOnce":false, + "Env":null, + "Cmd": ["/bin/bash"] + ,"Dns":null, + "Image":"base", + "Volumes":null, + "VolumesFrom":"", + "WorkingDir":"" + }, + "Size": 6824592 + } + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Get the history of an image +*************************** + +.. http:get:: /images/(name)/history + + Return the history of the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/history HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id":"b750fe79269d", + "Created":1364102658, + "CreatedBy":"/bin/bash" + }, + { + "Id":"27cf78414709", + "Created":1364068391, + "CreatedBy":"" + } + ] + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Push an image on the registry +***************************** + +.. http:post:: /images/(name)/push + + Push the image ``name`` on the registry + + **Example request**: + + .. sourcecode:: http + + POST /images/test/push HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pushing..."} + {"status":"Pushing", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... + + The ``X-Registry-Auth`` header can be used to include a + base64-encoded AuthConfig object. + + :query registry: the registry you wan to push, optional + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Tag an image into a repository +****************************** + +.. http:post:: /images/(name)/tag + + Tag the image ``name`` into a repository + + **Example request**: + + .. sourcecode:: http + + POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :query repo: The repository to tag in + :query force: 1/True/true or 0/False/false, default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such image + :statuscode 409: conflict + :statuscode 500: server error + + +Remove an image +*************** + +.. http:delete:: /images/(name) + + Remove the image ``name`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /images/test HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-type: application/json + + [ + {"Untagged":"3e2f21a89f"}, + {"Deleted":"3e2f21a89f"}, + {"Deleted":"53b4f83ac9"} + ] + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 409: conflict + :statuscode 500: server error + + +Search images +************* + +.. http:get:: /images/search + + Search for an image in the docker index + + **Example request**: + + .. sourcecode:: http + + GET /images/search?term=sshd HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Name":"cespare/sshd", + "Description":"" + }, + { + "Name":"johnfuller/sshd", + "Description":"" + }, + { + "Name":"dhrp/mongodb-sshd", + "Description":"" + } + ] + + :query term: term to search + :statuscode 200: no error + :statuscode 500: server error + + +2.3 Misc +-------- + +Build an image from Dockerfile via stdin +**************************************** + +.. http:post:: /build + + Build an image from Dockerfile via stdin + + **Example request**: + + .. sourcecode:: http + + POST /build HTTP/1.1 + + {{ STREAM }} + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + {{ STREAM }} + + + The stream must be a tar archive compressed with one of the following algorithms: + identity (no compression), gzip, bzip2, xz. The archive must include a file called + `Dockerfile` at its root. It may include any number of other files, which will be + accessible in the build context (See the ADD build command). + + The Content-type header should be set to "application/tar". + + :query t: repository name (and optionally a tag) to be applied to the resulting image in case of success + :query q: suppress verbose build output + :query nocache: do not use the cache when building the image + :statuscode 200: no error + :statuscode 500: server error + + +Check auth configuration +************************ + +.. http:post:: /auth + + Get the default username and email + + **Example request**: + + .. sourcecode:: http + + POST /auth HTTP/1.1 + Content-Type: application/json + + { + "username":"hannibal", + "password:"xxxx", + "email":"hannibal@a-team.com", + "serveraddress":"https://index.docker.io/v1/" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 204: no error + :statuscode 500: server error + + +Display system-wide information +******************************* + +.. http:get:: /info + + Display system-wide information + + **Example request**: + + .. sourcecode:: http + + GET /info HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Containers":11, + "Images":16, + "Debug":false, + "NFd": 11, + "NGoroutines":21, + "MemoryLimit":true, + "SwapLimit":false, + "IPv4Forwarding":true + } + + :statuscode 200: no error + :statuscode 500: server error + + +Show the docker version information +*********************************** + +.. http:get:: /version + + Show the docker version information + + **Example request**: + + .. sourcecode:: http + + GET /version HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Version":"0.2.2", + "GitCommit":"5a2a5cc+CHANGES", + "GoVersion":"go1.0.3" + } + + :statuscode 200: no error + :statuscode 500: server error + + +Create a new image from a container's changes +********************************************* + +.. http:post:: /commit + + Create a new image from a container's changes + + **Example request**: + + .. sourcecode:: http + + POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/vnd.docker.raw-stream + + {"Id":"596069db4bf5"} + + :query container: source container + :query repo: repository + :query tag: tag + :query m: commit message + :query author: author (eg. "John Hannibal Smith ") + :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]}) + :statuscode 201: no error + :statuscode 404: no such container + :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","from":"base:latest","time":1374067924} + {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} + {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} + + :query since: timestamp used for polling + :statuscode 200: no error + :statuscode 500: server error + + +3. Going further +================ + +3.1 Inside 'docker run' +----------------------- + +Here are the steps of 'docker run' : + +* Create the container +* If the status code is 404, it means the image doesn't exists: + * Try to pull it + * Then retry to create the container +* Start the container +* If you are not in detached mode: + * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1 +* If in detached mode or only stdin is attached: + * Display the container's id + + +3.2 Hijacking +------------- + +In this version of the API, /attach, uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future. + +3.3 CORS Requests +----------------- + +To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. + +.. code-block:: bash + + docker -d -H="192.168.1.9:4243" -api-enable-cors + diff --git a/components/engine/docs/sources/commandline/cli.rst b/components/engine/docs/sources/commandline/cli.rst index 6c2826567b..c064a3cef2 100644 --- a/components/engine/docs/sources/commandline/cli.rst +++ b/components/engine/docs/sources/commandline/cli.rst @@ -328,6 +328,36 @@ Displaying images visually .. image:: docker_images.gif :alt: Example inheritance graph of Docker images. + +Displaying image hierarchy +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + sudo docker images -tree + + |─8dbd9e392a96 Size: 131.5 MB (virtual 131.5 MB) Tags: ubuntu:12.04,ubuntu:latest,ubuntu:precise + └─27cf78414709 Size: 180.1 MB (virtual 180.1 MB) + └─b750fe79269d Size: 24.65 kB (virtual 180.1 MB) Tags: ubuntu:12.10,ubuntu:quantal + |─f98de3b610d5 Size: 12.29 kB (virtual 180.1 MB) + | └─7da80deb7dbf Size: 16.38 kB (virtual 180.1 MB) + | └─65ed2fee0a34 Size: 20.66 kB (virtual 180.2 MB) + | └─a2b9ea53dddc Size: 819.7 MB (virtual 999.8 MB) + | └─a29b932eaba8 Size: 28.67 kB (virtual 999.9 MB) + | └─e270a44f124d Size: 12.29 kB (virtual 999.9 MB) Tags: progrium/buildstep:latest + └─17e74ac162d8 Size: 53.93 kB (virtual 180.2 MB) + └─339a3f56b760 Size: 24.65 kB (virtual 180.2 MB) + └─904fcc40e34d Size: 96.7 MB (virtual 276.9 MB) + └─b1b0235328dd Size: 363.3 MB (virtual 640.2 MB) + └─7cb05d1acb3b Size: 20.48 kB (virtual 640.2 MB) + └─47bf6f34832d Size: 20.48 kB (virtual 640.2 MB) + └─f165104e82ed Size: 12.29 kB (virtual 640.2 MB) + └─d9cf85a47b7e Size: 1.911 MB (virtual 642.2 MB) + └─3ee562df86ca Size: 17.07 kB (virtual 642.2 MB) + └─b05fc2d00e4a Size: 24.96 kB (virtual 642.2 MB) + └─c96a99614930 Size: 12.29 kB (virtual 642.2 MB) + └─a6a357a48c49 Size: 12.29 kB (virtual 642.2 MB) Tags: ndj/mongodb:latest + .. _cli_import: ``import`` From 02bfc139c1a1b49a8bc0541b1214d5f54604b2e4 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Mon, 28 Oct 2013 04:35:24 +0000 Subject: [PATCH 037/184] fixup for rebase Upstream-commit: 22ef38ee79c2f353578f3642918a44126b4cb48c Component: engine --- components/engine/commands_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/commands_test.go b/components/engine/commands_test.go index 479e295dd8..6c6a8e975b 100644 --- a/components/engine/commands_test.go +++ b/components/engine/commands_test.go @@ -782,7 +782,7 @@ func TestImagesTree(t *testing.T) { "(?m)^ └─[0-9a-f]+", "(?m)^ └─[0-9a-f]+", "(?m)^ └─[0-9a-f]+", - fmt.Sprintf(" └─%s Size: \\d+.\\d+ kB \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)), + fmt.Sprintf(" └─%s Size: \\d+ B \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)), } compiledRegexps := []*regexp.Regexp{} From c0461f4ee438b056fc173133ce8fde7fd4a4656e Mon Sep 17 00:00:00 2001 From: Marc Kuo Date: Tue, 5 Nov 2013 20:48:16 -0800 Subject: [PATCH 038/184] Add -p when running the container Without it, docker was not mapping/exposing the private port Docker version 0.6.5, build 3ff8459 Upstream-commit: 3108f0526ef25596c7f6f545b07c95d90e966a7d Component: engine --- components/engine/docs/sources/examples/nodejs_web_app.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/examples/nodejs_web_app.rst b/components/engine/docs/sources/examples/nodejs_web_app.rst index 19b10f6cbb..68c073da7b 100644 --- a/components/engine/docs/sources/examples/nodejs_web_app.rst +++ b/components/engine/docs/sources/examples/nodejs_web_app.rst @@ -176,11 +176,11 @@ Run the image +++++++++++++ Running your image with ``-d`` runs the container in detached mode, leaving the -container running in the background. Run the image you previously built: +container running in the background. The ``-p`` flag redirects a public port to a private port in the container. Run the image you previously built: .. code-block:: bash - sudo docker run -d /centos-node-hello + sudo docker run -p 49160:8080 -d /centos-node-hello Print the output of your app: From f530e76f7fb6c14225e6d2c97c2a7835055247a6 Mon Sep 17 00:00:00 2001 From: Daniel Norberg Date: Tue, 5 Nov 2013 22:15:41 -0500 Subject: [PATCH 039/184] gograph: allow Walk() reentrance Hold the read lock while reading the child graph, then walk over the children without any lock, in order to avoid deadlock. Upstream-commit: 20881f1f787e093a6f3e9360624d8b436b9f7379 Component: engine --- components/engine/gograph/gograph.go | 101 +++++++++++++++------------ 1 file changed, 58 insertions(+), 43 deletions(-) diff --git a/components/engine/gograph/gograph.go b/components/engine/gograph/gograph.go index 626bf53ed8..aa6a4126a0 100644 --- a/components/engine/gograph/gograph.go +++ b/components/engine/gograph/gograph.go @@ -218,21 +218,28 @@ func (db *Database) List(name string, depth int) Entities { if err != nil { return out } - for c := range db.children(e, name, depth) { + + children, err := db.children(e, name, depth, nil) + if err != nil { + return out + } + + for _, c := range children { out[c.FullPath] = c.Entity } return out } +// Walk through the child graph of an entity, calling walkFunc for each child entity. +// It is safe for walkFunc to call graph functions. func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { - db.mux.RLock() - defer db.mux.RUnlock() - - e, err := db.get(name) + children, err := db.Children(name, depth) if err != nil { return err } - for c := range db.children(e, name, depth) { + + // Note: the database lock must not be held while calling walkFunc + for _, c := range children { if err := walkFunc(c.FullPath, c.Entity); err != nil { return err } @@ -240,6 +247,19 @@ func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { return nil } +// Return the children of the specified entity +func (db *Database) Children(name string, depth int) ([]WalkMeta, error) { + db.mux.RLock() + defer db.mux.RUnlock() + + e, err := db.get(name) + if err != nil { + return nil, err + } + + return db.children(e, name, depth, nil) +} + // Return the refrence count for a specified id func (db *Database) Refs(id string) int { db.mux.RLock() @@ -378,56 +398,51 @@ type WalkMeta struct { Edge *Edge } -func (db *Database) children(e *Entity, name string, depth int) <-chan WalkMeta { - out := make(chan WalkMeta) +func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) { if e == nil { - close(out) - return out + return entities, nil } - go func() { - rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id) - if err != nil { - close(out) + rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var entityId, entityName string + if err := rows.Scan(&entityId, &entityName); err != nil { + return nil, err + } + child := &Entity{entityId} + edge := &Edge{ + ParentID: e.id, + Name: entityName, + EntityID: child.id, } - defer rows.Close() - for rows.Next() { - var entityId, entityName string - if err := rows.Scan(&entityId, &entityName); err != nil { - // Log error - continue - } - child := &Entity{entityId} - edge := &Edge{ - ParentID: e.id, - Name: entityName, - EntityID: child.id, - } + meta := WalkMeta{ + Parent: e, + Entity: child, + FullPath: path.Join(name, edge.Name), + Edge: edge, + } - meta := WalkMeta{ - Parent: e, - Entity: child, - FullPath: path.Join(name, edge.Name), - Edge: edge, - } + entities = append(entities, meta) - out <- meta - if depth == 0 { - continue - } + if depth != 0 { nDepth := depth if depth != -1 { nDepth -= 1 } - sc := db.children(child, meta.FullPath, nDepth) - for c := range sc { - out <- c + entities, err = db.children(child, meta.FullPath, nDepth, entities) + if err != nil { + return nil, err } } - close(out) - }() - return out + } + + return entities, nil } // Return the entity based on the parent path and name From 563816191a6bc28c1559500aa1199f5e183a455e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 6 Nov 2013 07:37:43 -0800 Subject: [PATCH 040/184] Add debug to iptables Upstream-commit: 00f1398f7a8543e2448723635d93ea6ddf1eaadb Component: engine --- components/engine/iptables/iptables.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/engine/iptables/iptables.go b/components/engine/iptables/iptables.go index 8139b9881e..82ecf8bb5b 100644 --- a/components/engine/iptables/iptables.go +++ b/components/engine/iptables/iptables.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net" + "os" "os/exec" "strconv" "strings" @@ -122,10 +123,12 @@ func Raw(args ...string) ([]byte, error) { if err != nil { return nil, ErrIptablesNotFound } + if os.Getenv("DEBUG") != "" { + fmt.Printf("[DEBUG] [iptables]: %s, %v\n", path, args) + } output, err := exec.Command(path, args...).CombinedOutput() if err != nil { return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err) } return output, err - } From a135b385fd0008ef5e7609f3b068a944f2f9bc46 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 6 Nov 2013 10:00:24 -0800 Subject: [PATCH 041/184] Update documentation to reflect changes in Config and HostConfig Upstream-commit: 70f44d5531a5c6dcb96b7e608a96beb52e93b506 Component: engine --- .../docs/sources/api/docker_remote_api_v1.6.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/components/engine/docs/sources/api/docker_remote_api_v1.6.rst b/components/engine/docs/sources/api/docker_remote_api_v1.6.rst index defe145d06..09da63d8d9 100644 --- a/components/engine/docs/sources/api/docker_remote_api_v1.6.rst +++ b/components/engine/docs/sources/api/docker_remote_api_v1.6.rst @@ -121,8 +121,7 @@ Create a container "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, - "PortSpecs":null, - "Privileged": false, + "ExposedPorts":{}, "Tty":false, "OpenStdin":false, "StdinOnce":false, @@ -135,7 +134,6 @@ Create a container "Volumes":{}, "VolumesFrom":"", "WorkingDir":"" - } **Example response**: @@ -191,7 +189,7 @@ Inspect a container "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, - "PortSpecs": null, + "ExposedPorts": {}, "Tty": false, "OpenStdin": false, "StdinOnce": false, @@ -362,7 +360,12 @@ Start a container { "Binds":["/tmp:/tmp"], - "LxcConf":{"lxc.utsname":"docker"} + "LxcConf":{"lxc.utsname":"docker"}, + "ContainerIDFile": "", + "Privileged": false, + "PortBindings": {"22/tcp": [{HostIp:"", HostPort:""}]}, + "Links": [], + "PublishAllPorts": false } **Example response**: @@ -795,7 +798,7 @@ Inspect an image "AttachStdin":false, "AttachStdout":false, "AttachStderr":false, - "PortSpecs":null, + "ExposedPorts":{}, "Tty":true, "OpenStdin":true, "StdinOnce":false, @@ -1141,7 +1144,7 @@ Create a new image from a container's changes { "Cmd": ["cat", "/world"], - "PortSpecs":["22"] + "ExposedPorts":{"22/tcp":{}} } **Example response**: From 143de969f1b1e9b2f92e7b9fd768b3b223e9db85 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Wed, 6 Nov 2013 11:30:04 -0800 Subject: [PATCH 042/184] fix /images/json doc for 1.7 Upstream-commit: 16eba6bbb514fb4082e6779e175a29698a6734ce Component: engine --- components/engine/docs/sources/api/docker_remote_api_v1.7.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/engine/docs/sources/api/docker_remote_api_v1.7.rst b/components/engine/docs/sources/api/docker_remote_api_v1.7.rst index d1b094c872..3985f7aa0a 100644 --- a/components/engine/docs/sources/api/docker_remote_api_v1.7.rst +++ b/components/engine/docs/sources/api/docker_remote_api_v1.7.rst @@ -615,9 +615,7 @@ Copy files or folders from a container List Images *********** -.. http:get:: /images/(format) - - List images ``format`` could be json or viz (json default) +.. http:get:: /images/json **Example request**: From 58f4b4c19fc4f1cb9663a680176be09705f05dec Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 15:17:39 +0200 Subject: [PATCH 043/184] Utils: Add ShellQuoteArguments Upstream-commit: c5bc7d515836cffee90ba5bb342272bba64d9f37 Component: engine --- components/engine/utils/utils.go | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 4941eae42d..623f00296e 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -1139,6 +1139,41 @@ func (e *StatusError) Error() string { return fmt.Sprintf("Status: %d", e.Status) } +func quote(word string, buf *bytes.Buffer) { + // Bail out early for "simple" strings + if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { + buf.WriteString(word) + return + } + + buf.WriteString("'") + + for i := 0; i < len(word); i++ { + b := word[i] + if b == '\'' { + // Replace literal ' with a close ', a \', and a open ' + buf.WriteString("'\\''") + } else { + buf.WriteByte(b) + } + } + + buf.WriteString("'") +} + +// Take a list of strings and escape them so they will be handled right +// when passed as arguments to an program via a shell +func ShellQuoteArguments(args []string) string { + var buf bytes.Buffer + for i, arg := range args { + if i != 0 { + buf.WriteByte(' ') + } + quote(arg, &buf) + } + return buf.String() +} + func IsClosedError(err error) bool { /* This comparison is ugly, but unfortunately, net.go doesn't export errClosing. * See: From 5edb2719224f88f9410a65801f160edd2928012d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 29 Oct 2013 16:16:51 +0100 Subject: [PATCH 044/184] lxc: Work around lxc-start need for private mounts lxc-start requires / to be mounted private, otherwise the changes it does inside the container (both mounts and unmounts) will propagate out to the host. We work around this by starting up lxc-start in its own namespace where we set / to rshared. Unfortunately go can't really execute any code between clone and exec, so we can't do this in a nice way. Instead we have a horrible hack that use the unshare command, the shell and the mount command... Upstream-commit: 157d99a72786c454dfaad8b0800914cc80879aa8 Component: engine --- components/engine/container.go | 27 +++++++++++++++++++++++---- components/engine/utils.go | 15 +++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 50bf2ec674..ac21119d1a 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -863,7 +863,13 @@ func (container *Container) Start() (err error) { return err } + var lxcStart string = "lxc-start" + if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor { + lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined") + } + params := []string{ + lxcStart, "-n", container.ID, "-f", container.lxcConfigPath(), "--", @@ -956,11 +962,24 @@ func (container *Container) Start() (err error) { params = append(params, "--", container.Path) params = append(params, container.Args...) - var lxcStart string = "lxc-start" - if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor { - lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined") + if RootIsShared() { + // lxc-start really needs / to be non-shared, or all kinds of stuff break + // when lxc-start unmount things and those unmounts propagate to the main + // mount namespace. + // What we really want is to clone into a new namespace and then + // mount / MS_REC|MS_SLAVE, but since we can't really clone or fork + // without exec in go we have to do this horrible shell hack... + shellString := + "mount --make-rslave /; exec " + + utils.ShellQuoteArguments(params) + + params = []string{ + "unshare", "-m", "--", "/bin/sh", "-c", shellString, + } } - container.cmd = exec.Command(lxcStart, params...) + + container.cmd = exec.Command(params[0], params[1:]...) + // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { return err diff --git a/components/engine/utils.go b/components/engine/utils.go index 81715881ae..22d83d6bee 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/dotcloud/docker/namesgenerator" "github.com/dotcloud/docker/utils" + "io/ioutil" "strconv" "strings" ) @@ -301,6 +302,20 @@ func parseLink(rawLink string) (map[string]string, error) { return utils.PartParser("name:alias", rawLink) } +func RootIsShared() bool { + if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { + for _, line := range strings.Split(string(data), "\n") { + cols := strings.Split(line, " ") + if len(cols) >= 6 && cols[4] == "/" { + return strings.HasPrefix(cols[6], "shared") + } + } + } + + // No idea, probably safe to assume so + return true +} + type checker struct { runtime *Runtime } From 85bf6b92cb28738c6c55b0cef88763f08e09b995 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Wed, 6 Nov 2013 12:03:45 -0800 Subject: [PATCH 045/184] updating cli help doc for images Upstream-commit: 363738b8f21d02c2e4fac84bd2776c2d4a5e6755 Component: engine --- components/engine/docs/sources/commandline/cli.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/engine/docs/sources/commandline/cli.rst b/components/engine/docs/sources/commandline/cli.rst index c064a3cef2..6d56bccc38 100644 --- a/components/engine/docs/sources/commandline/cli.rst +++ b/components/engine/docs/sources/commandline/cli.rst @@ -315,8 +315,10 @@ Shell 1: (Again .. now showing events) List images -a=false: show all images + -notrunc=false: Don't truncate output -q=false: only show numeric IDs - -viz=false: output in graphviz format + -tree=false: output graph in tree format + -viz=false: output graph in graphviz format Displaying images visually ~~~~~~~~~~~~~~~~~~~~~~~~~~ From 9970f516fc2a3653f12f67800f24f5916b333b81 Mon Sep 17 00:00:00 2001 From: andy diller Date: Wed, 6 Nov 2013 12:37:01 -0500 Subject: [PATCH 046/184] updated create, showing how to set expose port and bind to public port for ssh Upstream-commit: 2d1155407a6e24b595d2627af6fa9f9f702483d2 Component: engine --- .../sources/api/docker_remote_api_v1.6.rst | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/components/engine/docs/sources/api/docker_remote_api_v1.6.rst b/components/engine/docs/sources/api/docker_remote_api_v1.6.rst index defe145d06..8585470735 100644 --- a/components/engine/docs/sources/api/docker_remote_api_v1.6.rst +++ b/components/engine/docs/sources/api/docker_remote_api_v1.6.rst @@ -157,6 +157,57 @@ Create a container :statuscode 406: impossible to attach (container not running) :statuscode 500: server error + **More Complex Example request, in 2 steps.** + **First, use create to expose a Private Port, which can be bound back to a Public Port at startup**: + + .. sourcecode:: http + + POST /containers/create HTTP/1.1 + Content-Type: application/json + + { + "Cmd":[ + "/usr/sbin/sshd","-D" + ], + "Image":"image-with-sshd", + "ExposedPorts":{"22/tcp":{}} + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/json + + { + "Id":"e90e34656806" + "Warnings":[] + } + + ** Second, start (using the ID returned above) the image we just created, mapping the ssh port 22 to something on the host**: + + .. sourcecode:: http + + POST /containers/e90e34656806/start HTTP/1.1 + Content-Type: application/json + + { + "PortBindings": { "22/tcp": [{ "HostPort": "11022" }]} + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 No Content + Content-Type: text/plain; charset=utf-8 + Content-Length: 0 + + ** Now you can ssh into your new container on port 11022.** + + + Inspect a container ******************* From 80db89881c137f0107033b669432c79fd070c4a5 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Wed, 6 Nov 2013 18:07:38 -0800 Subject: [PATCH 047/184] Fix bold Upstream-commit: 9990db2f5a762fdbc198cbfc5e96370d859739bb Component: engine --- components/engine/docs/sources/api/docker_remote_api_v1.6.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/api/docker_remote_api_v1.6.rst b/components/engine/docs/sources/api/docker_remote_api_v1.6.rst index 8585470735..461ffefd6b 100644 --- a/components/engine/docs/sources/api/docker_remote_api_v1.6.rst +++ b/components/engine/docs/sources/api/docker_remote_api_v1.6.rst @@ -185,7 +185,7 @@ Create a container "Warnings":[] } - ** Second, start (using the ID returned above) the image we just created, mapping the ssh port 22 to something on the host**: + **Second, start (using the ID returned above) the image we just created, mapping the ssh port 22 to something on the host**: .. sourcecode:: http @@ -204,7 +204,7 @@ Create a container Content-Type: text/plain; charset=utf-8 Content-Length: 0 - ** Now you can ssh into your new container on port 11022.** + **Now you can ssh into your new container on port 11022.** From 6b8ac9d25776b62f614f2a1c0b3196e5aeb0a401 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Fri, 18 Oct 2013 09:27:34 -0700 Subject: [PATCH 048/184] testing infrastructure, issue #1800: Refactor docker testing using Docker in Docker Upstream-commit: 50dd9791f75efef77a85bbe6556c85de0d845c0a Component: engine --- components/engine/hack/infrastructure/docker-ci/deployment.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/hack/infrastructure/docker-ci/deployment.py b/components/engine/hack/infrastructure/docker-ci/deployment.py index 655bdd43bc..453fad7590 100755 --- a/components/engine/hack/infrastructure/docker-ci/deployment.py +++ b/components/engine/hack/infrastructure/docker-ci/deployment.py @@ -138,6 +138,9 @@ sudo('curl -s https://phantomjs.googlecode.com/files/' # Preventively reboot docker-ci daily sudo('ln -s /sbin/reboot /etc/cron.daily') +# Preventively reboot docker-ci daily +sudo('ln -s /sbin/reboot /etc/cron.daily') + # Build docker-ci containers sudo('cd {}; docker build -t docker .'.format(DOCKER_PATH)) sudo('cd {}/nightlyrelease; docker build -t dockerbuilder .'.format( From aac90cd92e4389ce099b59cb7313755538ca4d48 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Sun, 27 Oct 2013 12:13:03 -0700 Subject: [PATCH 049/184] docker-ci 0.35. Push docker coverage and testing into docker-ci production. Update docker nightlyrelease Upstream-commit: 17172276366fc3114ae52eb6c83aafbec004d5f3 Component: engine --- .../hack/infrastructure/docker-ci/Dockerfile | 6 ++- .../docker-ci/buildbot/master.cfg | 27 ++++++---- .../infrastructure/docker-ci/deployment.py | 6 ++- .../docker-ci/docker-test/test_docker.sh | 15 ++---- .../functionaltests/test_registry.sh | 1 + .../docker-ci/nightlyrelease/Dockerfile | 18 +++---- .../docker-ci/nightlyrelease/dockerbuild | 50 ------------------- .../docker-ci/nightlyrelease/dockerbuild.sh | 40 +++++++++++++++ .../nightlyrelease/release_credentials.json | 1 - .../docker-ci/registry-coverage/Dockerfile | 18 +++++++ .../registry-coverage/registry_coverage.sh | 18 +++++++ 11 files changed, 113 insertions(+), 87 deletions(-) delete mode 100644 components/engine/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild create mode 100644 components/engine/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh delete mode 100644 components/engine/hack/infrastructure/docker-ci/nightlyrelease/release_credentials.json create mode 100644 components/engine/hack/infrastructure/docker-ci/registry-coverage/Dockerfile create mode 100755 components/engine/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh diff --git a/components/engine/hack/infrastructure/docker-ci/Dockerfile b/components/engine/hack/infrastructure/docker-ci/Dockerfile index 3ac8d90d24..bb49944d1b 100644 --- a/components/engine/hack/infrastructure/docker-ci/Dockerfile +++ b/components/engine/hack/infrastructure/docker-ci/Dockerfile @@ -33,8 +33,10 @@ from ubuntu:12.04 -run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list -run apt-get update; apt-get install -y python2.7 python-dev python-pip ssh rsync less vim +run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' \ + > /etc/apt/sources.list +run apt-get update; apt-get install -y git python2.7 python-dev libevent-dev \ + python-pip ssh rsync less vim run pip install boto fabric # Add deployment code and set default container command diff --git a/components/engine/hack/infrastructure/docker-ci/buildbot/master.cfg b/components/engine/hack/infrastructure/docker-ci/buildbot/master.cfg index 52bf495df3..9ca5fc035a 100644 --- a/components/engine/hack/infrastructure/docker-ci/buildbot/master.cfg +++ b/components/engine/hack/infrastructure/docker-ci/buildbot/master.cfg @@ -43,7 +43,7 @@ c['slavePortnum'] = PORT_MASTER # Schedulers c['schedulers'] = [ForceScheduler(name='trigger', builderNames=['docker', - 'index','registry','coverage','nightlyrelease'])] + 'index','registry','docker-coverage','registry-coverage','nightlyrelease'])] c['schedulers'] += [SingleBranchScheduler(name="all", treeStableTimer=None, change_filter=filter.ChangeFilter(branch='master', repository='https://github.com/dotcloud/docker'), builderNames=['docker'])] @@ -51,7 +51,7 @@ c['schedulers'] += [SingleBranchScheduler(name='pullrequest', change_filter=filter.ChangeFilter(category='github_pullrequest'), treeStableTimer=None, builderNames=['pullrequest'])] c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['nightlyrelease', - 'coverage'], hour=7, minute=00)] + 'docker-coverage','registry-coverage'], hour=7, minute=00)] c['schedulers'] += [Nightly(name='every4hrs', branch=None, builderNames=['registry','index'], hour=range(0,24,4), minute=15)] @@ -76,17 +76,25 @@ c['builders'] += [BuilderConfig(name='pullrequest',slavenames=['buildworker'], # Docker coverage test factory = BuildFactory() -factory.addStep(ShellCommand(description='Coverage', logEnviron=False, +factory.addStep(ShellCommand(description='docker-coverage', logEnviron=False, usePTY=True, command='{0}/docker-coverage/coverage-docker.sh'.format( DOCKER_CI_PATH))) -c['builders'] += [BuilderConfig(name='coverage',slavenames=['buildworker'], +c['builders'] += [BuilderConfig(name='docker-coverage',slavenames=['buildworker'], + factory=factory)] + +# Docker registry coverage test +factory = BuildFactory() +factory.addStep(ShellCommand(description='registry-coverage', logEnviron=False, + usePTY=True, command='docker run registry_coverage'.format( + DOCKER_CI_PATH))) +c['builders'] += [BuilderConfig(name='registry-coverage',slavenames=['buildworker'], factory=factory)] # Registry functional test factory = BuildFactory() factory.addStep(ShellCommand(description='registry', logEnviron=False, command='. {0}/master/credentials.cfg; ' - '/docker-ci/functionaltests/test_registry.sh'.format(BUILDBOT_PATH), + '{1}/functionaltests/test_registry.sh'.format(BUILDBOT_PATH, DOCKER_CI_PATH), usePTY=True)) c['builders'] += [BuilderConfig(name='registry',slavenames=['buildworker'], factory=factory)] @@ -95,16 +103,17 @@ c['builders'] += [BuilderConfig(name='registry',slavenames=['buildworker'], factory = BuildFactory() factory.addStep(ShellCommand(description='index', logEnviron=False, command='. {0}/master/credentials.cfg; ' - '/docker-ci/functionaltests/test_index.py'.format(BUILDBOT_PATH), + '{1}/functionaltests/test_index.py'.format(BUILDBOT_PATH, DOCKER_CI_PATH), usePTY=True)) c['builders'] += [BuilderConfig(name='index',slavenames=['buildworker'], factory=factory)] # Docker nightly release +nightlyrelease_cmd = ('docker version; docker run -i -t -privileged -e AWS_S3_BUCKET=' + 'test.docker.io dockerbuilder hack/dind dockerbuild.sh') factory = BuildFactory() -factory.addStep(ShellCommand(description='NightlyRelease', logEnviron=False, - usePTY=True, command='docker run -privileged' - ' -e AWS_S3_BUCKET=test.docker.io dockerbuilder')) +factory.addStep(ShellCommand(description='NightlyRelease',logEnviron=False, + usePTY=True, command=nightlyrelease_cmd)) c['builders'] += [BuilderConfig(name='nightlyrelease',slavenames=['buildworker'], factory=factory)] diff --git a/components/engine/hack/infrastructure/docker-ci/deployment.py b/components/engine/hack/infrastructure/docker-ci/deployment.py index 453fad7590..1a389d2771 100755 --- a/components/engine/hack/infrastructure/docker-ci/deployment.py +++ b/components/engine/hack/infrastructure/docker-ci/deployment.py @@ -100,8 +100,7 @@ sudo("echo '{}' >> /root/.ssh/authorized_keys".format(env['DOCKER_CI_PUB'])) credentials = { 'AWS_ACCESS_KEY': env['PKG_ACCESS_KEY'], 'AWS_SECRET_KEY': env['PKG_SECRET_KEY'], - 'GPG_PASSPHRASE': env['PKG_GPG_PASSPHRASE'], - 'INDEX_AUTH': env['INDEX_AUTH']} + 'GPG_PASSPHRASE': env['PKG_GPG_PASSPHRASE']} open(DOCKER_CI_PATH + '/nightlyrelease/release_credentials.json', 'w').write( base64.b64encode(json.dumps(credentials))) @@ -143,8 +142,11 @@ sudo('ln -s /sbin/reboot /etc/cron.daily') # Build docker-ci containers sudo('cd {}; docker build -t docker .'.format(DOCKER_PATH)) +sudo('cd {}; docker build -t docker-ci .'.format(DOCKER_CI_PATH)) sudo('cd {}/nightlyrelease; docker build -t dockerbuilder .'.format( DOCKER_CI_PATH)) +sudo('cd {}/registry-coverage; docker build -t registry_coverage .'.format( + DOCKER_CI_PATH)) # Download docker-ci testing container sudo('docker pull mzdaniel/test_docker') diff --git a/components/engine/hack/infrastructure/docker-ci/docker-test/test_docker.sh b/components/engine/hack/infrastructure/docker-ci/docker-test/test_docker.sh index 895e4d9642..c8cfe147e6 100755 --- a/components/engine/hack/infrastructure/docker-ci/docker-test/test_docker.sh +++ b/components/engine/hack/infrastructure/docker-ci/docker-test/test_docker.sh @@ -9,30 +9,21 @@ BRANCH=${3-master} DOCKER_PATH=/go/src/github.com/dotcloud/docker # Fetch latest master +cd / rm -rf /go -mkdir -p $DOCKER_PATH +git clone -q -b master http://github.com/dotcloud/docker $DOCKER_PATH cd $DOCKER_PATH -git init . -git fetch -q http://github.com/dotcloud/docker master -git reset --hard FETCH_HEAD # Merge commit -#echo FIXME. Temporarily skip TestPrivilegedCanMount until DinD works reliable on AWS -git pull -q https://github.com/mzdaniel/docker.git dind-aws || exit 1 - -# Merge commit in top of master git fetch -q "$REPO" "$BRANCH" git merge --no-edit $COMMIT || exit 1 # Test commit -go test -v; exit_status=$? +./hack/make.sh test; exit_status=$? # Display load if test fails if [ $exit_status -eq 1 ] ; then uptime; echo; free fi -# Cleanup testing directory -rm -rf $BASE_PATH - exit $exit_status diff --git a/components/engine/hack/infrastructure/docker-ci/functionaltests/test_registry.sh b/components/engine/hack/infrastructure/docker-ci/functionaltests/test_registry.sh index 8bcd355c7c..d175f66d16 100755 --- a/components/engine/hack/infrastructure/docker-ci/functionaltests/test_registry.sh +++ b/components/engine/hack/infrastructure/docker-ci/functionaltests/test_registry.sh @@ -12,6 +12,7 @@ export DOCKER_REGISTRY_CONFIG=config_test.yml # Get latest docker registry git clone -q https://github.com/dotcloud/docker-registry.git cd docker-registry +sed -Ei "s#(boto_bucket: ).+#\1_env:S3_BUCKET#" config_test.yml # Get dependencies pip install -q -r requirements.txt diff --git a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile b/components/engine/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile index 541f3a9584..953d7c11cc 100644 --- a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile +++ b/components/engine/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile @@ -1,5 +1,5 @@ -# VERSION: 1.2 -# DOCKER-VERSION 0.6.3 +# VERSION: 1.5 +# DOCKER-VERSION 0.6.4 # AUTHOR: Daniel Mizyrycki # DESCRIPTION: Build docker nightly release using Docker in Docker. # REFERENCES: This code reuses the excellent implementation of docker in docker @@ -7,11 +7,10 @@ # COMMENTS: # release_credentials.json is a base64 json encoded file containing: # { "AWS_ACCESS_KEY": "Test_docker_AWS_S3_bucket_id", -# "AWS_SECRET_KEY='Test_docker_AWS_S3_bucket_key' -# "GPG_PASSPHRASE='Test_docker_GPG_passphrase_signature' -# "INDEX_AUTH='Encripted_index_authentication' } +# "AWS_SECRET_KEY": "Test_docker_AWS_S3_bucket_key", +# "GPG_PASSPHRASE": "Test_docker_GPG_passphrase_signature" } # TO_BUILD: docker build -t dockerbuilder . -# TO_RELEASE: docker run -i -t -privileged -e AWS_S3_BUCKET="test.docker.io" dockerbuilder +# TO_RELEASE: docker run -i -t -privileged -e AWS_S3_BUCKET="test.docker.io" dockerbuilder hack/dind dockerbuild.sh from docker maintainer Daniel Mizyrycki @@ -24,11 +23,8 @@ run apt-get update; apt-get install -y -q wget python2.7 run wget -q -O /usr/bin/docker http://get.docker.io/builds/Linux/x86_64/docker-latest; chmod +x /usr/bin/docker # Add proto docker builder -add ./dockerbuild /usr/bin/dockerbuild -run chmod +x /usr/bin/dockerbuild +add ./dockerbuild.sh /usr/bin/dockerbuild.sh +run chmod +x /usr/bin/dockerbuild.sh # Add release credentials add ./release_credentials.json /root/release_credentials.json - -# Launch build process in a container -cmd dockerbuild diff --git a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild b/components/engine/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild deleted file mode 100644 index 83a7157a3e..0000000000 --- a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -# Variables AWS_ACCESS_KEY, AWS_SECRET_KEY, PG_PASSPHRASE and INDEX_AUTH -# are decoded from /root/release_credentials.json -# Variable AWS_S3_BUCKET is passed to the environment from docker run -e - -# Enable debugging -set -x - -# Fetch docker master branch -rm -rf /go/src/github.com/dotcloud/docker -cd / -git clone -q http://github.com/dotcloud/docker /go/src/github.com/dotcloud/docker -cd /go/src/github.com/dotcloud/docker - -# Launch docker daemon using dind inside the container -./hack/dind /usr/bin/docker -d & -sleep 5 - -# Add an uncommitted change to generate a timestamped release -date > timestamp - -# Build the docker package using /Dockerfile -docker build -t docker . - -# Run Docker unittests binary and Ubuntu package -docker run -privileged docker hack/make.sh -exit_status=$? - -# Display load if test fails -if [ $exit_status -eq 1 ] ; then - uptime; echo; free - exit 1 -fi - -# Commit binary and ubuntu bundles for release -docker commit -run '{"Env": ["PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin"], "WorkingDir": "/go/src/github.com/dotcloud/docker"}' $(docker ps -l -q) release - -# Turn debug off to load credentials from the environment -set +x -eval $(cat /root/release_credentials.json | python -c ' -import sys,json,base64; -d=json.loads(base64.b64decode(sys.stdin.read())); -exec("""for k in d: print "export {0}=\\"{1}\\"".format(k,d[k])""")') -set -x - -# Push docker nightly -echo docker run -i -t -privileged -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=XXXXX -e AWS_SECRET_KEY=XXXXX -e GPG_PASSPHRASE=XXXXX release hack/release.sh -set +x -docker run -i -t -privileged -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=$AWS_ACCESS_KEY -e AWS_SECRET_KEY=$AWS_SECRET_KEY -e GPG_PASSPHRASE=$GPG_PASSPHRASE release hack/release.sh diff --git a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh b/components/engine/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh new file mode 100644 index 0000000000..457db3f889 --- /dev/null +++ b/components/engine/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Variables AWS_ACCESS_KEY, AWS_SECRET_KEY and PG_PASSPHRASE are decoded +# from /root/release_credentials.json +# Variable AWS_S3_BUCKET is passed to the environment from docker run -e + +# Turn debug off to load credentials from the environment +set +x +eval $(cat /root/release_credentials.json | python -c ' +import sys,json,base64; +d=json.loads(base64.b64decode(sys.stdin.read())); +exec("""for k in d: print "export {0}=\\"{1}\\"".format(k,d[k])""")') + +# Fetch docker master branch +set -x +cd / +rm -rf /go +git clone -q -b master http://github.com/dotcloud/docker /go/src/github.com/dotcloud/docker +cd /go/src/github.com/dotcloud/docker + +# Launch docker daemon using dind inside the container +/usr/bin/docker version +/usr/bin/docker -d & +sleep 5 + +# Build Docker release container +docker build -t docker . + +# Test docker and if everything works well, release +echo docker run -i -t -privileged -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=XXXXX -e AWS_SECRET_KEY=XXXXX -e GPG_PASSPHRASE=XXXXX docker hack/release.sh +set +x +docker run -privileged -i -t -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=$AWS_ACCESS_KEY -e AWS_SECRET_KEY=$AWS_SECRET_KEY -e GPG_PASSPHRASE=$GPG_PASSPHRASE docker hack/release.sh +exit_status=$? + +# Display load if test fails +set -x +if [ $exit_status -eq 1 ] ; then + uptime; echo; free + exit 1 +fi diff --git a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/release_credentials.json b/components/engine/hack/infrastructure/docker-ci/nightlyrelease/release_credentials.json deleted file mode 100644 index ed6d53ecd1..0000000000 --- a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/release_credentials.json +++ /dev/null @@ -1 +0,0 @@ -eyAiQVdTX0FDQ0VTU19LRVkiOiAiIiwKICAiQVdTX1NFQ1JFVF9LRVkiOiAiIiwKICAiR1BHX1BBU1NQSFJBU0UiOiAiIiwKICAiSU5ERVhfQVVUSCI6ICIiIH0= diff --git a/components/engine/hack/infrastructure/docker-ci/registry-coverage/Dockerfile b/components/engine/hack/infrastructure/docker-ci/registry-coverage/Dockerfile new file mode 100644 index 0000000000..59c914fb2b --- /dev/null +++ b/components/engine/hack/infrastructure/docker-ci/registry-coverage/Dockerfile @@ -0,0 +1,18 @@ +# VERSION: 0.1 +# DOCKER-VERSION 0.6.4 +# AUTHOR: Daniel Mizyrycki +# DESCRIPTION: Docker registry coverage +# COMMENTS: Add registry coverage into the docker-ci image +# TO_BUILD: docker build -t registry_coverage . +# TO_RUN: docker run registry_coverage + +from docker-ci +maintainer Daniel Mizyrycki + +# Add registry_coverager.sh and dependencies +run pip install coverage flask pyyaml requests simplejson python-glanceclient \ + blinker redis gevent +add registry_coverage.sh /usr/bin/registry_coverage.sh +run chmod +x /usr/bin/registry_coverage.sh + +cmd "/usr/bin/registry_coverage.sh" diff --git a/components/engine/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh b/components/engine/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh new file mode 100755 index 0000000000..e9f0172651 --- /dev/null +++ b/components/engine/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -x + +# Compute test paths +REGISTRY_PATH=/data/docker-registry + +# Fetch latest docker-registry master +rm -rf $REGISTRY_PATH +git clone https://github.com/dotcloud/docker-registry -b master $REGISTRY_PATH +cd $REGISTRY_PATH + +# Generate coverage +export SETTINGS_FLAVOR=test +export DOCKER_REGISTRY_CONFIG=config_test.yml + +coverage run -m unittest discover test || exit 1 +coverage report --include='./*' --omit='./test/*' From 7a7c9f0f1d41534dd4afd66f7d8fa56e38ed94e1 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Mon, 28 Oct 2013 16:51:31 -0700 Subject: [PATCH 050/184] docker-ci 0.36. Patch hack/dind with latest code for nightly release to work. Upstream-commit: 0cbeda73910d6d1d3336da93c5f94df0bdd5fa44 Component: engine --- .../hack/infrastructure/docker-ci/nightlyrelease/Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile b/components/engine/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile index 953d7c11cc..6762cb4686 100644 --- a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile +++ b/components/engine/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile @@ -22,6 +22,11 @@ run apt-get update; apt-get install -y -q wget python2.7 # Add production docker binary run wget -q -O /usr/bin/docker http://get.docker.io/builds/Linux/x86_64/docker-latest; chmod +x /usr/bin/docker +#### FIXME. Temporarily install docker and dind with proper apparmor handling +run wget -q -O /usr/bin/docker http://test.docker.io/test/docker; chmod +x /usr/bin/docker +run wget -q -O /go/src/github.com/dotcloud/docker/hack/dind http://raw.github.com/jpetazzo/docker/escape-apparmor-confinement/hack/dind +run chmod +x /go/src/github.com/dotcloud/docker/hack/dind + # Add proto docker builder add ./dockerbuild.sh /usr/bin/dockerbuild.sh run chmod +x /usr/bin/dockerbuild.sh From 837c6b480c6859db6bf3f3fb044b070afb3f336d Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Tue, 29 Oct 2013 19:37:56 -0700 Subject: [PATCH 051/184] docker-ci 0.37. Patch hack/dind with latest code for docker-test. Upstream-commit: efb4c800a7c8167cf2202466a720c0fad7001a5d Component: engine --- .../infrastructure/docker-ci/docker-test/Dockerfile | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/components/engine/hack/infrastructure/docker-ci/docker-test/Dockerfile b/components/engine/hack/infrastructure/docker-ci/docker-test/Dockerfile index 66cb9762eb..229d696b87 100644 --- a/components/engine/hack/infrastructure/docker-ci/docker-test/Dockerfile +++ b/components/engine/hack/infrastructure/docker-ci/docker-test/Dockerfile @@ -17,13 +17,12 @@ from docker maintainer Daniel Mizyrycki -# Setup go environment. Extracted from /Dockerfile -env CGO_ENABLED 0 -env GOROOT /goroot -env PATH $PATH:/goroot/bin -env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor -volume /var/lib/docker -workdir /go/src/github.com/dotcloud/docker +#### FIXME. Temporarily install docker and dind with proper apparmor handling +run wget -q -O /go/src/github.com/dotcloud/docker/hack/dind http://raw.github.com/jpetazzo/docker/escape-apparmor-confinement/hack/dind +run chmod +x /go/src/github.com/dotcloud/docker/hack/dind + +# Setup go to the PATH. Extracted from /Dockerfile +env PATH /usr/local/go/bin:$PATH # Add test_docker.sh add test_docker.sh /usr/bin/test_docker.sh From 83a4080e0363cdd389311d22ee7e22f08c24aab3 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Tue, 29 Oct 2013 22:14:28 -0700 Subject: [PATCH 052/184] docker-ci 0.40. Migrate docker-ci to Digital Ocean. Upstream-commit: e7df38dbd0c7bf2d27632809fc49cacb0cc7f869 Component: engine --- .../hack/infrastructure/docker-ci/Dockerfile | 12 +- .../infrastructure/docker-ci/deployment.py | 112 ++++++++++-------- 2 files changed, 68 insertions(+), 56 deletions(-) diff --git a/components/engine/hack/infrastructure/docker-ci/Dockerfile b/components/engine/hack/infrastructure/docker-ci/Dockerfile index bb49944d1b..3f6c34441d 100644 --- a/components/engine/hack/infrastructure/docker-ci/Dockerfile +++ b/components/engine/hack/infrastructure/docker-ci/Dockerfile @@ -1,14 +1,16 @@ # VERSION: 0.22 # DOCKER-VERSION 0.6.3 # AUTHOR: Daniel Mizyrycki -# DESCRIPTION: Deploy docker-ci on Amazon EC2 +# DESCRIPTION: Deploy docker-ci on Digital Ocean # COMMENTS: # CONFIG_JSON is an environment variable json string loaded as: # # export CONFIG_JSON=' -# { "AWS_TAG": "EC2_instance_name", -# "AWS_ACCESS_KEY": "EC2_access_key", -# "AWS_SECRET_KEY": "EC2_secret_key", +# { "DROPLET_NAME": "docker-ci", +# "DO_CLIENT_ID": "Digital_Ocean_client_id", +# "DO_API_KEY": "Digital_Ocean_api_key", +# "DOCKER_KEY_ID": "Digital_Ocean_ssh_key_id", +# "DOCKER_CI_KEY_PATH": "docker-ci_private_key_path", # "DOCKER_CI_PUB": "$(cat docker-ci_ssh_public_key.pub)", # "DOCKER_CI_KEY": "$(cat docker-ci_ssh_private_key.key)", # "BUILDBOT_PWD": "Buildbot_server_password", @@ -37,7 +39,7 @@ run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' \ > /etc/apt/sources.list run apt-get update; apt-get install -y git python2.7 python-dev libevent-dev \ python-pip ssh rsync less vim -run pip install boto fabric +run pip install requests fabric # Add deployment code and set default container command add . /docker-ci diff --git a/components/engine/hack/infrastructure/docker-ci/deployment.py b/components/engine/hack/infrastructure/docker-ci/deployment.py index 1a389d2771..ee000eb7ba 100755 --- a/components/engine/hack/infrastructure/docker-ci/deployment.py +++ b/components/engine/hack/infrastructure/docker-ci/deployment.py @@ -1,11 +1,11 @@ #!/usr/bin/env python -import os, sys, re, json, base64 -from boto.ec2.connection import EC2Connection +import os, sys, re, json, requests, base64 from subprocess import call from fabric import api from fabric.api import cd, run, put, sudo from os import environ as env +from datetime import datetime from time import sleep # Remove SSH private key as it needs more processing @@ -20,42 +20,41 @@ for key in CONFIG: env['DOCKER_CI_KEY'] = re.sub('^.+"DOCKER_CI_KEY".+?"(.+?)".+','\\1', env['CONFIG_JSON'],flags=re.DOTALL) - -AWS_TAG = env.get('AWS_TAG','docker-ci') -AWS_KEY_NAME = 'dotcloud-dev' # Same as CONFIG_JSON['DOCKER_CI_PUB'] -AWS_AMI = 'ami-d582d6bc' # Ubuntu 13.04 -AWS_REGION = 'us-east-1' -AWS_TYPE = 'm1.small' -AWS_SEC_GROUPS = 'gateway' -AWS_IMAGE_USER = 'ubuntu' +DROPLET_NAME = env.get('DROPLET_NAME','docker-ci') +TIMEOUT = 120 # Seconds before timeout droplet creation +IMAGE_ID = 1004145 # Docker on Ubuntu 13.04 +REGION_ID = 4 # New York 2 +SIZE_ID = 62 # memory 2GB +DO_IMAGE_USER = 'root' # Image user on Digital Ocean +API_URL = 'https://api.digitalocean.com/' DOCKER_PATH = '/go/src/github.com/dotcloud/docker' DOCKER_CI_PATH = '/docker-ci' CFG_PATH = '{}/buildbot'.format(DOCKER_CI_PATH) -class AWS_EC2: - '''Amazon EC2''' - def __init__(self, access_key, secret_key): +class digital_ocean(): + + def __init__(self, key, client): '''Set default API parameters''' - self.handler = EC2Connection(access_key, secret_key) - def create_instance(self, tag, instance_type): - reservation = self.handler.run_instances(**instance_type) - instance = reservation.instances[0] - sleep(10) - while instance.state != 'running': - sleep(5) - instance.update() - print "Instance state: %s" % (instance.state) - instance.add_tag("Name",tag) - print "instance %s done!" % (instance.id) - return instance.ip_address - def get_instances(self): - return self.handler.get_all_instances() - def get_tags(self): - return dict([(i.instances[0].id, i.instances[0].tags['Name']) - for i in self.handler.get_all_instances() if i.instances[0].tags]) - def del_instance(self, instance_id): - self.handler.terminate_instances(instance_ids=[instance_id]) + self.key = key + self.client = client + self.api_url = API_URL + + def api(self, cmd_path, api_arg={}): + '''Make api call''' + api_arg.update({'api_key':self.key, 'client_id':self.client}) + resp = requests.get(self.api_url + cmd_path, params=api_arg).text + resp = json.loads(resp) + if resp['status'] != 'OK': + raise Exception(resp['error_message']) + return resp + + def droplet_data(self, name): + '''Get droplet data''' + data = self.api('droplets') + data = [droplet for droplet in data['droplets'] + if droplet['name'] == name] + return data[0] if data else {} def json_fmt(data): @@ -63,20 +62,36 @@ def json_fmt(data): return json.dumps(data, sort_keys = True, indent = 2) -# Create EC2 API handler -ec2 = AWS_EC2(env['AWS_ACCESS_KEY'], env['AWS_SECRET_KEY']) +do = digital_ocean(env['DO_API_KEY'], env['DO_CLIENT_ID']) -# Stop processing if AWS_TAG exists on EC2 -if AWS_TAG in ec2.get_tags().values(): - print ('Instance: {} already deployed. Not further processing.' - .format(AWS_TAG)) +# Get DROPLET_NAME data +data = do.droplet_data(DROPLET_NAME) + +# Stop processing if DROPLET_NAME exists on Digital Ocean +if data: + print ('Droplet: {} already deployed. Not further processing.' + .format(DROPLET_NAME)) exit(1) -ip = ec2.create_instance(AWS_TAG, {'image_id':AWS_AMI, 'instance_type':AWS_TYPE, - 'security_groups':[AWS_SEC_GROUPS], 'key_name':AWS_KEY_NAME}) +# Create droplet +do.api('droplets/new', {'name':DROPLET_NAME, 'region_id':REGION_ID, + 'image_id':IMAGE_ID, 'size_id':SIZE_ID, + 'ssh_key_ids':[env['DOCKER_KEY_ID']]}) -# Wait 30 seconds for the machine to boot -sleep(30) +# Wait for droplet to be created. +start_time = datetime.now() +while (data.get('status','') != 'active' and ( + datetime.now()-start_time).seconds < TIMEOUT): + data = do.droplet_data(DROPLET_NAME) + print data['status'] + sleep(3) + +# Wait for the machine to boot +sleep(15) + +# Get droplet IP +ip = str(data['ip_address']) +print 'droplet: {} ip: {}'.format(DROPLET_NAME, ip) # Create docker-ci ssh private key so docker-ci docker container can communicate # with its EC2 instance @@ -86,7 +101,7 @@ os.chmod('/root/.ssh/id_rsa',0600) open('/root/.ssh/config','w').write('StrictHostKeyChecking no\n') api.env.host_string = ip -api.env.user = AWS_IMAGE_USER +api.env.user = DO_IMAGE_USER api.env.key_filename = '/root/.ssh/id_rsa' # Correct timezone @@ -106,13 +121,11 @@ open(DOCKER_CI_PATH + '/nightlyrelease/release_credentials.json', 'w').write( # Transfer docker sudo('mkdir -p ' + DOCKER_CI_PATH) -sudo('chown {}.{} {}'.format(AWS_IMAGE_USER, AWS_IMAGE_USER, DOCKER_CI_PATH)) -call('/usr/bin/rsync -aH {} {}@{}:{}'.format(DOCKER_CI_PATH, AWS_IMAGE_USER, ip, +sudo('chown {}.{} {}'.format(DO_IMAGE_USER, DO_IMAGE_USER, DOCKER_CI_PATH)) +call('/usr/bin/rsync -aH {} {}@{}:{}'.format(DOCKER_CI_PATH, DO_IMAGE_USER, ip, os.path.dirname(DOCKER_CI_PATH)), shell=True) # Install Docker and Buildbot dependencies -sudo('addgroup docker') -sudo('usermod -a -G docker ubuntu') sudo('mkdir /mnt/docker; ln -s /mnt/docker /var/lib/docker') sudo('wget -q -O - https://get.docker.io/gpg | apt-key add -') sudo('echo deb https://get.docker.io/ubuntu docker main >' @@ -122,7 +135,7 @@ sudo('echo -e "deb http://archive.ubuntu.com/ubuntu raring main universe\n' ' > /etc/apt/sources.list; apt-get update') sudo('DEBIAN_FRONTEND=noninteractive apt-get install -q -y wget python-dev' ' python-pip supervisor git mercurial linux-image-extra-$(uname -r)' - ' aufs-tools make libfontconfig libevent-dev') + ' aufs-tools make libfontconfig libevent-dev libsqlite3-dev libssl-dev') sudo('wget -O - https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz | ' 'tar -v -C /usr/local -xz; ln -s /usr/local/go/bin/go /usr/bin/go') sudo('GOPATH=/go go get -d github.com/dotcloud/docker') @@ -137,9 +150,6 @@ sudo('curl -s https://phantomjs.googlecode.com/files/' # Preventively reboot docker-ci daily sudo('ln -s /sbin/reboot /etc/cron.daily') -# Preventively reboot docker-ci daily -sudo('ln -s /sbin/reboot /etc/cron.daily') - # Build docker-ci containers sudo('cd {}; docker build -t docker .'.format(DOCKER_PATH)) sudo('cd {}; docker build -t docker-ci .'.format(DOCKER_CI_PATH)) From ba4e682cde6ad56a7fe88c5e61c976e1db93338b Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Wed, 6 Nov 2013 20:05:26 -0800 Subject: [PATCH 053/184] docker-ci 0.4.5: Sync tests with progress in docker and docker-registry. Use revamped shiny DinD. Upstream-commit: 249f76bebdb3c24ae0a59e3be7ae73fd1d88c065 Component: engine --- components/engine/.gitignore | 1 + .../hack/infrastructure/docker-ci/Dockerfile | 6 +++--- .../engine/hack/infrastructure/docker-ci/VERSION | 1 + .../hack/infrastructure/docker-ci/deployment.py | 10 +++++----- .../docker-ci/docker-test/Dockerfile | 14 +++++--------- .../docker-ci/docker-test/test_docker.sh | 8 ++++++-- .../docker-ci/functionaltests/test_registry.sh | 2 +- .../docker-ci/nightlyrelease/Dockerfile | 13 ++++--------- .../docker-ci/nightlyrelease/dockerbuild.sh | 2 +- .../docker-ci/registry-coverage/Dockerfile | 2 +- .../registry-coverage/registry_coverage.sh | 8 ++++---- .../infrastructure/docker-ci/report/deployment.py | 2 +- 12 files changed, 33 insertions(+), 36 deletions(-) create mode 100644 components/engine/hack/infrastructure/docker-ci/VERSION diff --git a/components/engine/.gitignore b/components/engine/.gitignore index 8cf66168eb..00d66de3ed 100644 --- a/components/engine/.gitignore +++ b/components/engine/.gitignore @@ -18,3 +18,4 @@ bundles/ .hg/ .git/ vendor/pkg/ +pyenv diff --git a/components/engine/hack/infrastructure/docker-ci/Dockerfile b/components/engine/hack/infrastructure/docker-ci/Dockerfile index 3f6c34441d..d894330ffa 100644 --- a/components/engine/hack/infrastructure/docker-ci/Dockerfile +++ b/components/engine/hack/infrastructure/docker-ci/Dockerfile @@ -1,6 +1,6 @@ -# VERSION: 0.22 -# DOCKER-VERSION 0.6.3 -# AUTHOR: Daniel Mizyrycki +# VERSION: 0.25 +# DOCKER-VERSION 0.6.6 +# AUTHOR: Daniel Mizyrycki # DESCRIPTION: Deploy docker-ci on Digital Ocean # COMMENTS: # CONFIG_JSON is an environment variable json string loaded as: diff --git a/components/engine/hack/infrastructure/docker-ci/VERSION b/components/engine/hack/infrastructure/docker-ci/VERSION new file mode 100644 index 0000000000..0bfccb0804 --- /dev/null +++ b/components/engine/hack/infrastructure/docker-ci/VERSION @@ -0,0 +1 @@ +0.4.5 diff --git a/components/engine/hack/infrastructure/docker-ci/deployment.py b/components/engine/hack/infrastructure/docker-ci/deployment.py index ee000eb7ba..c04219d523 100755 --- a/components/engine/hack/infrastructure/docker-ci/deployment.py +++ b/components/engine/hack/infrastructure/docker-ci/deployment.py @@ -32,7 +32,7 @@ DOCKER_CI_PATH = '/docker-ci' CFG_PATH = '{}/buildbot'.format(DOCKER_CI_PATH) -class digital_ocean(): +class DigitalOcean(): def __init__(self, key, client): '''Set default API parameters''' @@ -62,7 +62,7 @@ def json_fmt(data): return json.dumps(data, sort_keys = True, indent = 2) -do = digital_ocean(env['DO_API_KEY'], env['DO_CLIENT_ID']) +do = DigitalOcean(env['DO_API_KEY'], env['DO_CLIENT_ID']) # Get DROPLET_NAME data data = do.droplet_data(DROPLET_NAME) @@ -147,9 +147,6 @@ sudo('curl -s https://phantomjs.googlecode.com/files/' 'phantomjs-1.9.1-linux-x86_64.tar.bz2 | tar jx -C /usr/bin' ' --strip-components=2 phantomjs-1.9.1-linux-x86_64/bin/phantomjs') -# Preventively reboot docker-ci daily -sudo('ln -s /sbin/reboot /etc/cron.daily') - # Build docker-ci containers sudo('cd {}; docker build -t docker .'.format(DOCKER_PATH)) sudo('cd {}; docker build -t docker-ci .'.format(DOCKER_CI_PATH)) @@ -169,3 +166,6 @@ sudo('{0}/setup.sh root {0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10}' env['SMTP_PWD'], env['EMAIL_RCP'], env['REGISTRY_USER'], env['REGISTRY_PWD'], env['REGISTRY_BUCKET'], env['REGISTRY_ACCESS_KEY'], env['REGISTRY_SECRET_KEY'])) + +# Preventively reboot docker-ci daily +sudo('ln -s /sbin/reboot /etc/cron.daily') diff --git a/components/engine/hack/infrastructure/docker-ci/docker-test/Dockerfile b/components/engine/hack/infrastructure/docker-ci/docker-test/Dockerfile index 229d696b87..0f3a63f5f1 100644 --- a/components/engine/hack/infrastructure/docker-ci/docker-test/Dockerfile +++ b/components/engine/hack/infrastructure/docker-ci/docker-test/Dockerfile @@ -1,6 +1,6 @@ -# VERSION: 0.3 -# DOCKER-VERSION 0.6.3 -# AUTHOR: Daniel Mizyrycki +# VERSION: 0.4 +# DOCKER-VERSION 0.6.6 +# AUTHOR: Daniel Mizyrycki # DESCRIPTION: Testing docker PRs and commits on top of master using # REFERENCES: This code reuses the excellent implementation of # Docker in Docker made by Jerome Petazzoni. @@ -15,13 +15,9 @@ # TO_RUN: docker run -privileged test_docker hack/dind test_docker.sh [commit] [repo] [branch] from docker -maintainer Daniel Mizyrycki +maintainer Daniel Mizyrycki -#### FIXME. Temporarily install docker and dind with proper apparmor handling -run wget -q -O /go/src/github.com/dotcloud/docker/hack/dind http://raw.github.com/jpetazzo/docker/escape-apparmor-confinement/hack/dind -run chmod +x /go/src/github.com/dotcloud/docker/hack/dind - -# Setup go to the PATH. Extracted from /Dockerfile +# Setup go in PATH. Extracted from /Dockerfile env PATH /usr/local/go/bin:$PATH # Add test_docker.sh diff --git a/components/engine/hack/infrastructure/docker-ci/docker-test/test_docker.sh b/components/engine/hack/infrastructure/docker-ci/docker-test/test_docker.sh index c8cfe147e6..cf8fdb90bb 100755 --- a/components/engine/hack/infrastructure/docker-ci/docker-test/test_docker.sh +++ b/components/engine/hack/infrastructure/docker-ci/docker-test/test_docker.sh @@ -8,6 +8,10 @@ BRANCH=${3-master} # Compute test paths DOCKER_PATH=/go/src/github.com/dotcloud/docker +# Timestamp +echo +date; echo + # Fetch latest master cd / rm -rf /go @@ -16,13 +20,13 @@ cd $DOCKER_PATH # Merge commit git fetch -q "$REPO" "$BRANCH" -git merge --no-edit $COMMIT || exit 1 +git merge --no-edit $COMMIT || exit 255 # Test commit ./hack/make.sh test; exit_status=$? # Display load if test fails -if [ $exit_status -eq 1 ] ; then +if [ $exit_status -ne 0 ] ; then uptime; echo; free fi diff --git a/components/engine/hack/infrastructure/docker-ci/functionaltests/test_registry.sh b/components/engine/hack/infrastructure/docker-ci/functionaltests/test_registry.sh index d175f66d16..58642529cc 100755 --- a/components/engine/hack/infrastructure/docker-ci/functionaltests/test_registry.sh +++ b/components/engine/hack/infrastructure/docker-ci/functionaltests/test_registry.sh @@ -8,6 +8,7 @@ rm -rf docker-registry # Setup the environment export SETTINGS_FLAVOR=test export DOCKER_REGISTRY_CONFIG=config_test.yml +export PYTHONPATH=$(pwd)/docker-registry/test # Get latest docker registry git clone -q https://github.com/dotcloud/docker-registry.git @@ -21,7 +22,6 @@ pip install -q tox # Run registry tests tox || exit 1 -export PYTHONPATH=$(pwd)/docker-registry python -m unittest discover -p s3.py -s test || exit 1 python -m unittest discover -p workflow.py -s test diff --git a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile b/components/engine/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile index 6762cb4686..2100a9e8e9 100644 --- a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile +++ b/components/engine/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile @@ -1,6 +1,6 @@ -# VERSION: 1.5 -# DOCKER-VERSION 0.6.4 -# AUTHOR: Daniel Mizyrycki +# VERSION: 1.6 +# DOCKER-VERSION 0.6.6 +# AUTHOR: Daniel Mizyrycki # DESCRIPTION: Build docker nightly release using Docker in Docker. # REFERENCES: This code reuses the excellent implementation of docker in docker # made by Jerome Petazzoni. https://github.com/jpetazzo/dind @@ -13,7 +13,7 @@ # TO_RELEASE: docker run -i -t -privileged -e AWS_S3_BUCKET="test.docker.io" dockerbuilder hack/dind dockerbuild.sh from docker -maintainer Daniel Mizyrycki +maintainer Daniel Mizyrycki # Add docker dependencies and downloading packages run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list @@ -22,11 +22,6 @@ run apt-get update; apt-get install -y -q wget python2.7 # Add production docker binary run wget -q -O /usr/bin/docker http://get.docker.io/builds/Linux/x86_64/docker-latest; chmod +x /usr/bin/docker -#### FIXME. Temporarily install docker and dind with proper apparmor handling -run wget -q -O /usr/bin/docker http://test.docker.io/test/docker; chmod +x /usr/bin/docker -run wget -q -O /go/src/github.com/dotcloud/docker/hack/dind http://raw.github.com/jpetazzo/docker/escape-apparmor-confinement/hack/dind -run chmod +x /go/src/github.com/dotcloud/docker/hack/dind - # Add proto docker builder add ./dockerbuild.sh /usr/bin/dockerbuild.sh run chmod +x /usr/bin/dockerbuild.sh diff --git a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh b/components/engine/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh index 457db3f889..80caaec25e 100644 --- a/components/engine/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh +++ b/components/engine/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh @@ -34,7 +34,7 @@ exit_status=$? # Display load if test fails set -x -if [ $exit_status -eq 1 ] ; then +if [ $exit_status -ne 0 ] ; then uptime; echo; free exit 1 fi diff --git a/components/engine/hack/infrastructure/docker-ci/registry-coverage/Dockerfile b/components/engine/hack/infrastructure/docker-ci/registry-coverage/Dockerfile index 59c914fb2b..e544645b67 100644 --- a/components/engine/hack/infrastructure/docker-ci/registry-coverage/Dockerfile +++ b/components/engine/hack/infrastructure/docker-ci/registry-coverage/Dockerfile @@ -11,7 +11,7 @@ maintainer Daniel Mizyrycki # Add registry_coverager.sh and dependencies run pip install coverage flask pyyaml requests simplejson python-glanceclient \ - blinker redis gevent + blinker redis boto gevent rsa mock add registry_coverage.sh /usr/bin/registry_coverage.sh run chmod +x /usr/bin/registry_coverage.sh diff --git a/components/engine/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh b/components/engine/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh index e9f0172651..e16cea8e3c 100755 --- a/components/engine/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh +++ b/components/engine/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh @@ -2,8 +2,11 @@ set -x -# Compute test paths +# Setup the environment REGISTRY_PATH=/data/docker-registry +export SETTINGS_FLAVOR=test +export DOCKER_REGISTRY_CONFIG=config_test.yml +export PYTHONPATH=$REGISTRY_PATH/test # Fetch latest docker-registry master rm -rf $REGISTRY_PATH @@ -11,8 +14,5 @@ git clone https://github.com/dotcloud/docker-registry -b master $REGISTRY_PATH cd $REGISTRY_PATH # Generate coverage -export SETTINGS_FLAVOR=test -export DOCKER_REGISTRY_CONFIG=config_test.yml - coverage run -m unittest discover test || exit 1 coverage report --include='./*' --omit='./test/*' diff --git a/components/engine/hack/infrastructure/docker-ci/report/deployment.py b/components/engine/hack/infrastructure/docker-ci/report/deployment.py index d5efb4a960..5b2eaf3cab 100755 --- a/components/engine/hack/infrastructure/docker-ci/report/deployment.py +++ b/components/engine/hack/infrastructure/docker-ci/report/deployment.py @@ -34,7 +34,7 @@ env['DOCKER_CI_KEY'] = open(env['DOCKER_CI_KEY_PATH']).read() DROPLET_NAME = env.get('DROPLET_NAME','report') TIMEOUT = 120 # Seconds before timeout droplet creation -IMAGE_ID = 894856 # Docker on Ubuntu 13.04 +IMAGE_ID = 1004145 # Docker on Ubuntu 13.04 REGION_ID = 4 # New York 2 SIZE_ID = 66 # memory 512MB DO_IMAGE_USER = 'root' # Image user on Digital Ocean From 9fe27fc82518da45c4886678eb5aaedb62a2d26c Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Thu, 7 Nov 2013 12:20:23 -0800 Subject: [PATCH 054/184] Fix 2585 and clean up warning in contributing.rst Upstream-commit: 97c3de7e6b92b13efa30c5c840ce2fab07b8794e Component: engine --- .../engine/docs/sources/api/remote_api_client_libraries.rst | 2 ++ components/engine/docs/sources/contributing/contributing.rst | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/api/remote_api_client_libraries.rst b/components/engine/docs/sources/api/remote_api_client_libraries.rst index bd8610eaf1..8243d02c80 100644 --- a/components/engine/docs/sources/api/remote_api_client_libraries.rst +++ b/components/engine/docs/sources/api/remote_api_client_libraries.rst @@ -35,3 +35,5 @@ and we will add the libraries here. +----------------------+----------------+--------------------------------------------+ | Go | go-dockerclient| https://github.com/fsouza/go-dockerclient | +----------------------+----------------+--------------------------------------------+ +| PHP | Alvine | http://pear.alvine.io/ (alpha) | ++----------------------+----------------+--------------------------------------------+ diff --git a/components/engine/docs/sources/contributing/contributing.rst b/components/engine/docs/sources/contributing/contributing.rst index 3cdb0b6f14..f4a6b472ff 100644 --- a/components/engine/docs/sources/contributing/contributing.rst +++ b/components/engine/docs/sources/contributing/contributing.rst @@ -10,13 +10,13 @@ Want to hack on Docker? Awesome! The repository includes `all the instructions you need to get started `_. -The developer environment `Dockerfile `_ +The `developer environment Dockerfile `_ specifies the tools and versions used to test and build Docker. If you're making changes to the documentation, see the `README.md `_. -The documentation environment `Dockerfile `_ +The `documentation environment Dockerfile `_ specifies the tools and versions used to build the Documentation. Further interesting details can be found in the `Packaging hints `_. From b5fe937fc97111aa19b628499645edd0608c74af Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 7 Nov 2013 12:23:29 -0800 Subject: [PATCH 055/184] fix Assign id as name when all else fails Upstream-commit: 26f846bf779c97a69e85db356dff8c1fff76161c Component: engine --- components/engine/runtime.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 0ef6786b21..671694fce5 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -286,13 +286,12 @@ func (runtime *Runtime) restore() error { // Any containers that are left over do not exist in the graph for _, container := range containers { // Try to set the default name for a container if it exists prior to links - name, err := generateRandomName(runtime) + container.Name, err = generateRandomName(runtime) if err != nil { container.Name = container.ShortID() } - container.Name = name - if _, err := runtime.containerGraph.Set(name, container.ID); err != nil { + if _, err := runtime.containerGraph.Set(container.Name, container.ID); err != nil { utils.Debugf("Setting default id - %s", err) } register(container) From 6d241424236f7887bc5bd8853f0e55ed3d2d816f Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 7 Nov 2013 12:19:24 -0800 Subject: [PATCH 056/184] go fmt Upstream-commit: 6998c3c387518ba8e06ceff00b10be583c95d6cd Component: engine --- components/engine/config.go | 2 +- components/engine/container.go | 6 +- components/engine/docker/docker.go | 2 +- components/engine/engine/engine.go | 28 +++++---- components/engine/engine/init_test.go | 10 ++-- components/engine/engine/job.go | 58 +++++++++---------- .../engine/namesgenerator/names-generator.go | 2 +- components/engine/netlink/netlink_darwin.go | 2 - components/engine/utils/daemon.go | 2 +- components/engine/utils/random.go | 2 +- components/engine/utils/utils.go | 4 +- components/engine/utils/utils_test.go | 26 ++++----- components/engine/utils_test.go | 2 +- 13 files changed, 71 insertions(+), 75 deletions(-) diff --git a/components/engine/config.go b/components/engine/config.go index 40c47e692c..42ae23a90f 100644 --- a/components/engine/config.go +++ b/components/engine/config.go @@ -1,8 +1,8 @@ package docker import ( - "net" "github.com/dotcloud/docker/engine" + "net" ) // FIXME: separate runtime configuration from http api configuration diff --git a/components/engine/container.go b/components/engine/container.go index 50bf2ec674..8238628131 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -394,9 +394,9 @@ func (container *Container) Inject(file io.Reader, pth string) error { if _, err := os.Stat(path.Join(container.rwPath(), pth)); err == nil { // Since err is nil, the path could be stat'd and it exists return fmt.Errorf("%s exists", pth) - } else if ! os.IsNotExist(err) { + } else if !os.IsNotExist(err) { // Expect err might be that the file doesn't exist, so - // if it's some other error, return that. + // if it's some other error, return that. return err } @@ -1086,7 +1086,7 @@ func (container *Container) allocateNetwork() error { Gateway: manager.bridgeNetwork.IP, manager: manager, } - if iface !=nil && iface.IPNet.IP != nil { + if iface != nil && iface.IPNet.IP != nil { ipNum := ipToInt(iface.IPNet.IP) manager.ipAllocator.inUse[ipNum] = struct{}{} } else { diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index c500633a71..877e7f67d0 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -4,9 +4,9 @@ import ( "flag" "fmt" "github.com/dotcloud/docker" + "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" - "github.com/dotcloud/docker/engine" "log" "os" "strings" diff --git a/components/engine/engine/engine.go b/components/engine/engine/engine.go index 8d67242ca2..10cc077253 100644 --- a/components/engine/engine/engine.go +++ b/components/engine/engine/engine.go @@ -2,13 +2,12 @@ package engine import ( "fmt" - "os" - "log" - "runtime" "github.com/dotcloud/docker/utils" + "log" + "os" + "runtime" ) - type Handler func(*Job) string var globalHandlers map[string]Handler @@ -25,8 +24,8 @@ func Register(name string, handler Handler) error { // It acts as a store for *containers*, and allows manipulation of these // containers by executing *jobs*. type Engine struct { - root string - handlers map[string]Handler + root string + handlers map[string]Handler } // New initializes a new engine managing the directory specified at `root`. @@ -56,8 +55,8 @@ func New(root string) (*Engine, error) { return nil, err } eng := &Engine{ - root: root, - handlers: globalHandlers, + root: root, + handlers: globalHandlers, } return eng, nil } @@ -66,12 +65,12 @@ func New(root string) (*Engine, error) { // This function mimics `Command` from the standard os/exec package. func (eng *Engine) Job(name string, args ...string) *Job { job := &Job{ - eng: eng, - Name: name, - Args: args, - Stdin: os.Stdin, - Stdout: os.Stdout, - Stderr: os.Stderr, + eng: eng, + Name: name, + Args: args, + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, } handler, exists := eng.handlers[name] if exists { @@ -79,4 +78,3 @@ func (eng *Engine) Job(name string, args ...string) *Job { } return job } - diff --git a/components/engine/engine/init_test.go b/components/engine/engine/init_test.go index 5c03ded87d..48d9009171 100644 --- a/components/engine/engine/init_test.go +++ b/components/engine/engine/init_test.go @@ -1,18 +1,18 @@ package engine import ( - "testing" + "fmt" + "github.com/dotcloud/docker/utils" + "io/ioutil" "runtime" "strings" - "fmt" - "io/ioutil" - "github.com/dotcloud/docker/utils" + "testing" ) var globalTestID string func init() { - Register("dummy", func(job *Job) string { return ""; }) + Register("dummy", func(job *Job) string { return "" }) } func mkEngine(t *testing.T) *Engine { diff --git a/components/engine/engine/job.go b/components/engine/engine/job.go index 0bde2a0beb..c2b14d55c2 100644 --- a/components/engine/engine/job.go +++ b/components/engine/engine/job.go @@ -1,11 +1,11 @@ package engine import ( + "encoding/json" + "fmt" + "github.com/dotcloud/docker/utils" "io" "strings" - "fmt" - "encoding/json" - "github.com/dotcloud/docker/utils" ) // A job is the fundamental unit of work in the docker engine. @@ -20,17 +20,17 @@ import ( // One slight variation is that jobs report their status as a string. The // string "0" indicates success, and any other strings indicates an error. // This allows for richer error reporting. -// +// type Job struct { - eng *Engine - Name string - Args []string - env []string - Stdin io.ReadCloser - Stdout io.WriteCloser - Stderr io.WriteCloser - handler func(*Job) string - status string + eng *Engine + Name string + Args []string + env []string + Stdin io.ReadCloser + Stdout io.WriteCloser + Stderr io.WriteCloser + handler func(*Job) string + status string } // Run executes the job and blocks until the job completes. @@ -57,21 +57,21 @@ func (job *Job) String() string { } func (job *Job) Getenv(key string) (value string) { - for _, kv := range job.env { - if strings.Index(kv, "=") == -1 { - continue - } - parts := strings.SplitN(kv, "=", 2) - if parts[0] != key { - continue - } - if len(parts) < 2 { - value = "" - } else { - value = parts[1] - } - } - return + for _, kv := range job.env { + if strings.Index(kv, "=") == -1 { + continue + } + parts := strings.SplitN(kv, "=", 2) + if parts[0] != key { + continue + } + if len(parts) < 2 { + value = "" + } else { + value = parts[1] + } + } + return } func (job *Job) GetenvBool(key string) (value bool) { @@ -109,5 +109,5 @@ func (job *Job) SetenvList(key string, value []string) error { } func (job *Job) Setenv(key, value string) { - job.env = append(job.env, key + "=" + value) + job.env = append(job.env, key+"="+value) } diff --git a/components/engine/namesgenerator/names-generator.go b/components/engine/namesgenerator/names-generator.go index 076bb1cba7..c0dd26830c 100644 --- a/components/engine/namesgenerator/names-generator.go +++ b/components/engine/namesgenerator/names-generator.go @@ -12,7 +12,7 @@ type NameChecker interface { var ( colors = [...]string{"white", "silver", "gray", "black", "blue", "green", "cyan", "yellow", "gold", "orange", "brown", "red", "violet", "pink", "magenta", "purple", "maroon", "crimson", "plum", "fuchsia", "lavender", "slate", "navy", "azure", "aqua", "olive", "teal", "lime", "beige", "tan", "sienna"} - animals = [...]string{"ant", "bear", "bird", "cat", "chicken", "cow", "deer", "dog", "donkey", "duck", "fish", "fox", "frog", "horse", "kangaroo", "koala", "lemur", "lion", "lizard", "monkey", "octopus", "pig", "shark", "sheep", "sloth", "spider", "squirrel", "tiger", "toad", "weasel", "whale", "wolf"} + animals = [...]string{"ant", "bear", "bird", "cat", "chicken", "cow", "deer", "dog", "donkey", "duck", "fish", "fox", "frog", "horse", "kangaroo", "koala", "lemur", "lion", "lizard", "monkey", "octopus", "pig", "shark", "sheep", "sloth", "spider", "squirrel", "tiger", "toad", "weasel", "whale", "wolf"} ) func GenerateRandomName(checker NameChecker) (string, error) { diff --git a/components/engine/netlink/netlink_darwin.go b/components/engine/netlink/netlink_darwin.go index becfb87759..9a972fe63f 100644 --- a/components/engine/netlink/netlink_darwin.go +++ b/components/engine/netlink/netlink_darwin.go @@ -9,7 +9,6 @@ func NetworkGetRoutes() ([]*net.IPNet, error) { return nil, fmt.Errorf("Not implemented") } - func NetworkLinkAdd(name string, linkType string) error { return fmt.Errorf("Not implemented") } @@ -18,7 +17,6 @@ func NetworkLinkUp(iface *net.Interface) error { return fmt.Errorf("Not implemented") } - func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { return fmt.Errorf("Not implemented") } diff --git a/components/engine/utils/daemon.go b/components/engine/utils/daemon.go index 179ff90e10..871122ed59 100644 --- a/components/engine/utils/daemon.go +++ b/components/engine/utils/daemon.go @@ -1,10 +1,10 @@ package utils import ( - "os" "fmt" "io/ioutil" "log" + "os" "strconv" ) diff --git a/components/engine/utils/random.go b/components/engine/utils/random.go index d5c37e44d0..d4d33c690a 100644 --- a/components/engine/utils/random.go +++ b/components/engine/utils/random.go @@ -1,9 +1,9 @@ package utils import ( - "io" "crypto/rand" "encoding/hex" + "io" ) func RandomString() string { diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 4941eae42d..152c41d86c 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -15,8 +15,8 @@ import ( "os" "os/exec" "path/filepath" - "runtime" "regexp" + "runtime" "strconv" "strings" "sync" @@ -904,7 +904,7 @@ func StripComments(input []byte, commentMarker []byte) []byte { return output } -// GetNameserversAsCIDR returns nameservers (if any) listed in +// GetNameserversAsCIDR returns nameservers (if any) listed in // /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32") // This function's output is intended for net.ParseCIDR func GetNameserversAsCIDR(resolvConf []byte) []string { diff --git a/components/engine/utils/utils_test.go b/components/engine/utils/utils_test.go index f10bb40d3b..5377c181c4 100644 --- a/components/engine/utils/utils_test.go +++ b/components/engine/utils/utils_test.go @@ -453,20 +453,20 @@ search example.com`: {"1.2.3.4/32", "4.3.2.1/32"}, `search example.com`: {}, `nameserver 1.2.3.4 search example.com -nameserver 4.3.2.1`: []string{"1.2.3.4/32", "4.3.2.1/32"}, - ``: []string{}, - ` nameserver 1.2.3.4 `: []string{"1.2.3.4/32"}, - `search example.com +nameserver 4.3.2.1`: {"1.2.3.4/32", "4.3.2.1/32"}, + ``: {}, + ` nameserver 1.2.3.4 `: {"1.2.3.4/32"}, + `search example.com nameserver 1.2.3.4 -#nameserver 4.3.2.1`: []string{"1.2.3.4/32"}, - `search example.com -nameserver 1.2.3.4 # not 4.3.2.1`: []string{"1.2.3.4/32"}, - } { - test := GetNameserversAsCIDR([]byte(resolv)) - if !StrSlicesEqual(test, result) { - t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv) - } - } +#nameserver 4.3.2.1`: {"1.2.3.4/32"}, + `search example.com +nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"}, + } { + test := GetNameserversAsCIDR([]byte(resolv)) + if !StrSlicesEqual(test, result) { + t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv) + } + } } func StrSlicesEqual(a, b []string) bool { diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 14cbd7c6be..87426f38ea 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -67,7 +67,7 @@ func newTestRuntime(prefix string) (runtime *Runtime, err error) { } config := &DaemonConfig{ - Root: root, + Root: root, AutoRestart: false, } runtime, err = NewRuntimeFromDirectory(config) From c81beb4bbc1304af17f188064b69e0ea7cdbeb72 Mon Sep 17 00:00:00 2001 From: Roberto Gandolfo Hashioka Date: Thu, 7 Nov 2013 14:06:49 -0800 Subject: [PATCH 057/184] - Fix the attach examples according to the new attach behavior - Add the option of adding the user to docker's group and avoid the usage of "sudo" before each command Upstream-commit: 304d39cc505ff51682dd7e1337f9f0ab7536eec3 Component: engine --- components/engine/docs/sources/examples/hello_world.rst | 3 ++- components/engine/docs/sources/examples/python_web_app.rst | 5 +++-- .../engine/docs/sources/examples/running_ssh_service.rst | 3 ++- components/engine/docs/sources/use/basics.rst | 7 +++++++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/components/engine/docs/sources/examples/hello_world.rst b/components/engine/docs/sources/examples/hello_world.rst index 6d6c1b28a6..d6f75de09c 100644 --- a/components/engine/docs/sources/examples/hello_world.rst +++ b/components/engine/docs/sources/examples/hello_world.rst @@ -127,10 +127,11 @@ Check the logs make sure it is working correctly. sudo docker attach $CONTAINER_ID -Attach to the container to see the results in realtime. +Attach to the container to see the results in real-time. - **"docker attach**" This will allow us to attach to a background process to see what is going on. +- **"-sig-proxy=true" Proxify all received signal to the process (even in non-tty mode) - **$CONTAINER_ID** The Id of the container we want to attach too. Exit from the container attachment by pressing Control-C. diff --git a/components/engine/docs/sources/examples/python_web_app.rst b/components/engine/docs/sources/examples/python_web_app.rst index c707fcdec6..71e7ae4e08 100644 --- a/components/engine/docs/sources/examples/python_web_app.rst +++ b/components/engine/docs/sources/examples/python_web_app.rst @@ -39,11 +39,12 @@ container. The ``BUILD_JOB`` environment variable will be set with the new conta .. code-block:: bash - sudo docker attach $BUILD_JOB + sudo docker attach -sig-proxy=false $BUILD_JOB [...] While this container is running, we can attach to the new container to -see what is going on. You can use Ctrl-C to disconnect. +see what is going on. The flag ``-sig-proxy`` set as ``false`` allows you to connect and +disconnect (Ctrl-C) to it without stopping the container. .. code-block:: bash diff --git a/components/engine/docs/sources/examples/running_ssh_service.rst b/components/engine/docs/sources/examples/running_ssh_service.rst index 59a80fbf6e..e0d406bc0e 100644 --- a/components/engine/docs/sources/examples/running_ssh_service.rst +++ b/components/engine/docs/sources/examples/running_ssh_service.rst @@ -21,7 +21,8 @@ smooth, but gives you a good idea. daemon is unprotected and available via a TCP port. When you run through the same steps in a newer version of Docker, you will need to add ``sudo`` in front of each ``docker`` command in order - to reach the daemon over its protected Unix socket. + to reach the daemon over its protected Unix socket or you can add + your user to docker's group: ``sudo usermod -a -G docker ``. .. raw:: html diff --git a/components/engine/docs/sources/use/basics.rst b/components/engine/docs/sources/use/basics.rst index 0097b6836d..8773fa9492 100644 --- a/components/engine/docs/sources/use/basics.rst +++ b/components/engine/docs/sources/use/basics.rst @@ -54,6 +54,13 @@ the daemon starts. The ``docker`` daemon must always run as root, but if you run the ``docker`` client as a user in the *docker* group then you don't need to add ``sudo`` to all the client commands. +Alternative to ``sudo``? +------------- + +You can add your current ```` to docker's group and get rid of +``sudo`` before each ``docker`` command. You just need to type: +``$ sudo usermod -a -G docker `` + .. code-block:: bash # Add the docker group From 07f796bd964dc308e5c1d97595456ef5b9c22ac6 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 1 Nov 2013 18:29:25 -0500 Subject: [PATCH 058/184] network: add iptables rules to explicitly allow forwarding Explicitly enable container networking for Fedora and other distros that have a REJECT all rule at the end of their FORWARD table. Upstream-commit: ec4657b28a3e97447921357d454df974e0979ac6 Component: engine --- components/engine/AUTHORS | 1 + components/engine/network.go | 38 ++++++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/components/engine/AUTHORS b/components/engine/AUTHORS index 64f2ce21aa..d002104e82 100644 --- a/components/engine/AUTHORS +++ b/components/engine/AUTHORS @@ -94,6 +94,7 @@ Jonathan Rudenberg Joost Cassee Jordan Arentsen Joseph Anthony Pasquale Holsten +Josh Poimboeuf Julien Barbier Jérôme Petazzoni Karan Lyons diff --git a/components/engine/network.go b/components/engine/network.go index 638fd94de0..3864cca8cd 100644 --- a/components/engine/network.go +++ b/components/engine/network.go @@ -168,12 +168,28 @@ func CreateBridgeIface(config *DaemonConfig) error { } if config.EnableIptables { + // Enable NAT if output, err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr, "!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil { return fmt.Errorf("Unable to enable network bridge NAT: %s", err) } else if len(output) != 0 { return fmt.Errorf("Error iptables postrouting: %s", output) } + + // Accept incoming packets for existing connections + if output, err := iptables.Raw("-I", "FORWARD", "-o", config.BridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"); err != nil { + return fmt.Errorf("Unable to allow incoming packets: %s", err) + } else if len(output) != 0 { + return fmt.Errorf("Error iptables allow incoming: %s", output) + } + + // Accept all non-intercontainer outgoing packets + if output, err := iptables.Raw("-I", "FORWARD", "-i", config.BridgeIface, "!", "-o", config.BridgeIface, "-j", "ACCEPT"); err != nil { + return fmt.Errorf("Unable to allow outgoing packets: %s", err) + } else if len(output) != 0 { + return fmt.Errorf("Error iptables allow outgoing: %s", output) + } + } return nil } @@ -680,20 +696,30 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { // Configure iptables for link support if config.EnableIptables { - args := []string{"FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"} + args := []string{"FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j"} + acceptArgs := append(args, "ACCEPT") + dropArgs := append(args, "DROP") if !config.InterContainerCommunication { - if !iptables.Exists(args...) { + iptables.Raw(append([]string{"-D"}, acceptArgs...)...) + if !iptables.Exists(dropArgs...) { utils.Debugf("Disable inter-container communication") - if output, err := iptables.Raw(append([]string{"-A"}, args...)...); err != nil { + if output, err := iptables.Raw(append([]string{"-I"}, dropArgs...)...); err != nil { return nil, fmt.Errorf("Unable to prevent intercontainer communication: %s", err) } else if len(output) != 0 { - return nil, fmt.Errorf("Error enabling iptables: %s", output) + return nil, fmt.Errorf("Error disabling intercontainer communication: %s", output) } } } else { - utils.Debugf("Enable inter-container communication") - iptables.Raw(append([]string{"-D"}, args...)...) + iptables.Raw(append([]string{"-D"}, dropArgs...)...) + if !iptables.Exists(acceptArgs...) { + utils.Debugf("Enable inter-container communication") + if output, err := iptables.Raw(append([]string{"-I"}, acceptArgs...)...); err != nil { + return nil, fmt.Errorf("Unable to allow intercontainer communication: %s", err) + } else if len(output) != 0 { + return nil, fmt.Errorf("Error enabling intercontainer communication: %s", output) + } + } } } From ce1bb0ce731fe44c5569fb3529e8550e4756dd1a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 7 Nov 2013 14:31:25 -0800 Subject: [PATCH 059/184] fix mergeConfig with new ports Upstream-commit: 49c4231f077cb0b4804768231396c596d070a22f Component: engine --- components/engine/utils.go | 9 +++++++++ components/engine/utils_test.go | 28 ++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/components/engine/utils.go b/components/engine/utils.go index 81715881ae..1393a5c0a8 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -89,6 +89,15 @@ func MergeConfig(userConf, imageConf *Config) error { } if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 { userConf.ExposedPorts = imageConf.ExposedPorts + } else if imageConf.ExposedPorts != nil { + if userConf.ExposedPorts == nil { + userConf.ExposedPorts = make(map[Port]struct{}) + } + for port := range imageConf.ExposedPorts { + if _, exists := userConf.ExposedPorts[port]; !exists { + userConf.ExposedPorts[port] = struct{}{} + } + } } if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 { diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 87426f38ea..589cd405ea 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -247,7 +247,9 @@ func TestMergeConfig(t *testing.T) { Volumes: volumesUser, } - MergeConfig(configUser, configImage) + if err := MergeConfig(configUser, configImage); err != nil { + t.Error(err) + } if len(configUser.Dns) != 3 { t.Fatalf("Expected 3 dns, 1.1.1.1, 2.2.2.2 and 3.3.3.3, found %d", len(configUser.Dns)) @@ -259,7 +261,7 @@ func TestMergeConfig(t *testing.T) { } if len(configUser.ExposedPorts) != 3 { - t.Fatalf("Expected 3 portSpecs, 1111, 2222 and 3333, found %d", len(configUser.PortSpecs)) + t.Fatalf("Expected 3 ExposedPorts, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts)) } for portSpecs := range configUser.ExposedPorts { if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" { @@ -287,6 +289,28 @@ func TestMergeConfig(t *testing.T) { if configUser.VolumesFrom != "1111" { t.Fatalf("Expected VolumesFrom to be 1111, found %s", configUser.VolumesFrom) } + + ports, _, err := parsePortSpecs([]string{"0000"}) + if err != nil { + t.Error(err) + } + configImage2 := &Config{ + ExposedPorts: ports, + } + + if err := MergeConfig(configUser, configImage2); err != nil { + t.Error(err) + } + + if len(configUser.ExposedPorts) != 4 { + t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts)) + } + for portSpecs := range configUser.ExposedPorts { + if portSpecs.Port() != "0000" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" { + t.Fatalf("Expected 0000 or 1111 or 2222 or 3333, found %s", portSpecs) + } + } + } func TestParseLxcConfOpt(t *testing.T) { From 163d4a5b8fc0afc02fcd1705d5fbba1169715512 Mon Sep 17 00:00:00 2001 From: Roberto Gandolfo Hashioka Date: Thu, 7 Nov 2013 14:44:07 -0800 Subject: [PATCH 060/184] - Included the sudo alternative as an example_header Upstream-commit: eba3c36b386f389953eda6315948577200c9bf2c Component: engine --- components/engine/docs/sources/examples/example_header.inc | 6 ++++++ .../engine/docs/sources/examples/running_ssh_service.rst | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/examples/example_header.inc b/components/engine/docs/sources/examples/example_header.inc index 607421fc13..7d0feddeb9 100644 --- a/components/engine/docs/sources/examples/example_header.inc +++ b/components/engine/docs/sources/examples/example_header.inc @@ -2,3 +2,9 @@ .. note:: This example assumes you have Docker running in daemon mode. For more information please see :ref:`running_examples` + +.. note:: + + These examples use ``sudo`` before each ``docker`` command you could also + add your current ```` to docker's group and get rid of ``sudo``. + You just need to type: ``$ sudo usermod -a -G docker `` diff --git a/components/engine/docs/sources/examples/running_ssh_service.rst b/components/engine/docs/sources/examples/running_ssh_service.rst index e0d406bc0e..59a80fbf6e 100644 --- a/components/engine/docs/sources/examples/running_ssh_service.rst +++ b/components/engine/docs/sources/examples/running_ssh_service.rst @@ -21,8 +21,7 @@ smooth, but gives you a good idea. daemon is unprotected and available via a TCP port. When you run through the same steps in a newer version of Docker, you will need to add ``sudo`` in front of each ``docker`` command in order - to reach the daemon over its protected Unix socket or you can add - your user to docker's group: ``sudo usermod -a -G docker ``. + to reach the daemon over its protected Unix socket. .. raw:: html From d03901e0ad057a2978368972bc2bd9b782875560 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Tue, 22 Oct 2013 21:48:10 +1000 Subject: [PATCH 061/184] Closes #2328 - allow the user to specify a string timestamp (not just a unix epoch) in the string format that the docker cli shows to the user Upstream-commit: 01fea3cf116b768720b542ab65cbd1c2695848d0 Component: engine --- components/engine/commands.go | 14 +++++++++++-- .../engine/docs/sources/commandline/cli.rst | 20 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index d41f0f86b8..00be9a60e1 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -1387,7 +1387,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error { func (cli *DockerCli) CmdEvents(args ...string) error { cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server") - since := cmd.String("since", "", "Show events previously created (used for polling).") + since := cmd.String("since", "", "Show previously created events and then stream.") if err := cmd.Parse(args); err != nil { return nil } @@ -1399,7 +1399,17 @@ func (cli *DockerCli) CmdEvents(args ...string) error { v := url.Values{} if *since != "" { - v.Set("since", *since) + loc := time.FixedZone(time.Now().Zone()) + format := "2006-01-02 15:04:05 -0700 MST" + if len(*since) < len(format) { + format = format[:len(*since)] + } + + if t, err := time.ParseInLocation(format, *since, loc); err == nil { + v.Set("since", strconv.FormatInt(t.Unix(), 10)) + } else { + v.Set("since", *since) + } } if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil { diff --git a/components/engine/docs/sources/commandline/cli.rst b/components/engine/docs/sources/commandline/cli.rst index 6d56bccc38..d6e47d1787 100644 --- a/components/engine/docs/sources/commandline/cli.rst +++ b/components/engine/docs/sources/commandline/cli.rst @@ -245,6 +245,9 @@ Full -run example Usage: docker events Get real time events from the server + + -since="": Show previously created events and then stream. + (either seconds since epoch, or date string as below) .. _cli_events_example: @@ -277,6 +280,23 @@ Shell 1: (Again .. now showing events) [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop +Show events in the past from a specified time +............................................. + +.. code-block:: bash + + $ sudo docker events -since 1378216169 + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop + + $ sudo docker events -since '2013-09-03' + [2013-09-03 15:49:26 +0200 CEST] 4386fb97867d: (from 12de384bfb10) start + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop + + $ sudo docker events -since '2013-09-03 15:49:29 +0200 CEST' + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop .. _cli_export: From 600bb37cb7415f1e383152f07dfd31893e310f35 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 7 Nov 2013 17:30:51 -0800 Subject: [PATCH 062/184] Deprecate old tagging format Upstream-commit: ef57752bce5c333d0e2b8352a84d071f21cce132 Component: engine --- components/engine/commands.go | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index 00be9a60e1..12cc713654 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -913,8 +913,16 @@ func (cli *DockerCli) CmdImport(args ...string) error { cmd.Usage() return nil } - src := cmd.Arg(0) - repository, tag := utils.ParseRepositoryTag(cmd.Arg(1)) + + var src, repository, tag string + + if cmd.NArg() == 3 { + fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' as been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n") + src, repository, tag = cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) + } else { + src = cmd.Arg(0) + repository, tag = utils.ParseRepositoryTag(cmd.Arg(1)) + } v := url.Values{} v.Set("repo", repository) v.Set("tag", tag) @@ -1349,8 +1357,16 @@ func (cli *DockerCli) CmdCommit(args ...string) error { if err := cmd.Parse(args); err != nil { return nil } - name := cmd.Arg(0) - repository, tag := utils.ParseRepositoryTag(cmd.Arg(1)) + + var name, repository, tag string + + if cmd.NArg() == 3 { + fmt.Fprintf(cli.err, "[DEPRECATED] The format 'CONTAINER [REPOSITORY [TAG]]' as been deprecated. Please use CONTAINER [REPOSITORY[:TAG]]\n") + name, repository, tag = cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) + } else { + name = cmd.Arg(0) + repository, tag = utils.ParseRepositoryTag(cmd.Arg(1)) + } if name == "" { cmd.Usage() @@ -1666,9 +1682,16 @@ func (cli *DockerCli) CmdTag(args ...string) error { return nil } - v := url.Values{} - repository, tag := utils.ParseRepositoryTag(cmd.Arg(1)) + var repository, tag string + if cmd.NArg() == 3 { + fmt.Fprintf(cli.err, "[DEPRECATED] The format 'IMAGE [REPOSITORY [TAG]]' as been deprecated. Please use IMAGE [REPOSITORY[:TAG]]\n") + repository, tag = cmd.Arg(1), cmd.Arg(2) + } else { + repository, tag = utils.ParseRepositoryTag(cmd.Arg(1)) + } + + v := url.Values{} v.Set("repo", repository) v.Set("tag", tag) From b89c66d72483cc552d7fb0d00b8050e2c5787e75 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 7 Nov 2013 18:54:00 -0800 Subject: [PATCH 063/184] prevent panic if you use API in a wrong way Upstream-commit: 49d7b87cfc4385470a5ecf181f92c13b8391c002 Component: engine --- components/engine/container.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/engine/container.go b/components/engine/container.go index 6cf35ec70e..419ba84d29 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -133,7 +133,11 @@ type PortBinding struct { type Port string func (p Port) Proto() string { - return strings.Split(string(p), "/")[1] + parts := strings.Split(string(p), "/") + if len(parts) == 1 { + return "tcp" + } + return parts[1] } func (p Port) Port() string { From 5090a5bd4b9472a450d86afe70b74f2aa3c180a0 Mon Sep 17 00:00:00 2001 From: David Sissitka Date: Fri, 20 Sep 2013 04:55:17 -0400 Subject: [PATCH 064/184] Make "docker insert" errors obvious Closes #1130 See also #1942 Upstream-commit: 5957dd909134fc3c3dc3e165b83559feb89d9f5b Component: engine --- components/engine/api.go | 9 +++------ components/engine/commands.go | 5 +---- components/engine/server.go | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/components/engine/api.go b/components/engine/api.go index 61252ab9af..8f31ab0282 100644 --- a/components/engine/api.go +++ b/components/engine/api.go @@ -479,15 +479,12 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht w.Header().Set("Content-Type", "application/json") } sf := utils.NewStreamFormatter(version > 1.0) - imgID, err := srv.ImageInsert(name, url, path, w, sf) + err := srv.ImageInsert(name, url, path, w, sf) if err != nil { - if sf.Used() { - w.Write(sf.FormatError(err)) - return nil - } + w.Write(sf.FormatError(err)) } - return writeJSON(w, http.StatusOK, &APIID{ID: imgID}) + return nil } func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/components/engine/commands.go b/components/engine/commands.go index 00be9a60e1..1dc82e3bdc 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -130,10 +130,7 @@ func (cli *DockerCli) CmdInsert(args ...string) error { v.Set("url", cmd.Arg(1)) v.Set("path", cmd.Arg(2)) - if err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out, nil); err != nil { - return err - } - return nil + return cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out, nil) } // mkBuildContext returns an archive of an empty context with the contents diff --git a/components/engine/server.go b/components/engine/server.go index 3441029027..eb4e74e0bf 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -198,39 +198,39 @@ func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) { return results.Results, nil } -func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) (string, error) { +func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) error { out = utils.NewWriteFlusher(out) img, err := srv.runtime.repositories.LookupImage(name) if err != nil { - return "", err + return err } file, err := utils.Download(url, out) if err != nil { - return "", err + return err } defer file.Body.Close() config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities) if err != nil { - return "", err + return err } c, _, err := srv.runtime.Create(config, "") if err != nil { - return "", err + return err } - if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("", "Downloading", "%8v/%v (%v)"), sf, true), path); err != nil { - return "", err + if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("", "Downloading", "%8v/%v (%v)"), sf, false), path); err != nil { + return err } // FIXME: Handle custom repo, tag comment, author img, err = srv.runtime.Commit(c, "", "", img.Comment, img.Author, nil) if err != nil { - return "", err + return err } - out.Write(sf.FormatStatus("", img.ID)) - return img.ShortID(), nil + out.Write(sf.FormatStatus(utils.TruncateID(img.ID), "Image created")) + return nil } func (srv *Server) ImagesViz(out io.Writer) error { From 807e6a9bd8bce9cf23ddb3e8ec8a6a6e8f5935d4 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Thu, 7 Nov 2013 23:34:54 -0600 Subject: [PATCH 065/184] Add ImageInsert tests Upstream-commit: bf8e0277bbd1c2df2310bc20ecc4003d1ed7a657 Component: engine --- components/engine/server_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/components/engine/server_test.go b/components/engine/server_test.go index 4072344f35..3376eebd6e 100644 --- a/components/engine/server_test.go +++ b/components/engine/server_test.go @@ -3,6 +3,7 @@ package docker import ( "github.com/dotcloud/docker/utils" "strings" + "io/ioutil" "testing" "time" ) @@ -521,3 +522,25 @@ func TestImagesFilter(t *testing.T) { t.Fatal("incorrect number of matches returned") } } + +func TestImageInsert(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + srv := &Server{runtime: runtime} + sf := utils.NewStreamFormatter(true) + + // bad image name fails + if err := srv.ImageInsert("foo", "https://www.docker.io/static/img/docker-top-logo.png", "/foo", ioutil.Discard, sf); err == nil { + t.Fatal("expected an error and got none") + } + + // bad url fails + if err := srv.ImageInsert(GetTestImage(runtime).ID, "http://bad_host_name_that_will_totally_fail.com/", "/foo", ioutil.Discard, sf); err == nil { + t.Fatal("expected an error and got none") + } + + // success returns nil + if err := srv.ImageInsert(GetTestImage(runtime).ID, "https://www.docker.io/static/img/docker-top-logo.png", "/foo", ioutil.Discard, sf); err != nil { + t.Fatalf("expected no error, but got %v", err) + } +} From e5498d2e589bb020deb9078c3e1cc65ee07ad882 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 Nov 2013 22:52:10 +0100 Subject: [PATCH 066/184] =?UTF-8?q?Return=20=E2=80=9Cerr=E2=80=9D=20instea?= =?UTF-8?q?d=20of=20=E2=80=9Cnil=E2=80=9D=20when=20MkdirAll()=20fails=20wh?= =?UTF-8?q?en=20binding=20a=20volume=20Upstream-commit:=20e7fdcc15c5eb3812?= =?UTF-8?q?c71dd61f22a8d77d3ae72e36=20Component:=20engine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/engine/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/container.go b/components/engine/container.go index 419ba84d29..f9247eccc8 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -837,7 +837,7 @@ func (container *Container) Start() (err error) { // Create the mountpoint rootVolPath := path.Join(container.RootfsPath(), volPath) if err := os.MkdirAll(rootVolPath, 0755); err != nil { - return nil + return err } // Do not copy or change permissions if we are mounting from the host From 8e910346a3edca75fb93c5cf83ac25c4e6e7ec0c Mon Sep 17 00:00:00 2001 From: Sean Cronin Date: Fri, 8 Nov 2013 17:17:39 -0500 Subject: [PATCH 067/184] Removes duplicate changelog entry Upstream-commit: ca174ae84d37d89af726fa41e25a11bdaeca2067 Component: engine --- components/engine/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/components/engine/CHANGELOG.md b/components/engine/CHANGELOG.md index f5c9617463..8c08cefaad 100644 --- a/components/engine/CHANGELOG.md +++ b/components/engine/CHANGELOG.md @@ -17,7 +17,6 @@ + Prevent DNS server conflicts in CreateBridgeIface + Validate bind mounts on the server side + Use parent image config in docker build -* Fix regression in /etc/hosts #### Client From 67c8d19777b74f65ff03bca146138af34fcd6c6b Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 8 Nov 2013 16:28:41 -0600 Subject: [PATCH 068/184] setup network when reconnecting to ghost container Re-adding the line to setup the network when reconnecting to a ghost container. It was inadvertently removed by commit 31638ab2ad2a5380d447780f05f7aa078c9421f5. Upstream-commit: 2448058ee23292e2a167bbd6a2e138ad99e8b3b8 Component: engine --- components/engine/runtime.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 671694fce5..581f6a8d83 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -172,6 +172,7 @@ func (runtime *Runtime) Register(container *Container) error { if !container.State.Running { close(container.waitLock) } else if !nomonitor { + container.allocateNetwork() go container.monitor() } return nil From db48d3cf28e87c98d7cc6844385f2962ec5bb426 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 8 Nov 2013 15:44:52 -0700 Subject: [PATCH 069/184] Update release script with proper support for non-*.docker.io bucket URLs Upstream-commit: b8e7ec1b74fdeaeab56a8ffab801dcd87b7361ca Component: engine --- components/engine/hack/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/hack/release.sh b/components/engine/hack/release.sh index 56538ea70a..23718dd63d 100755 --- a/components/engine/hack/release.sh +++ b/components/engine/hack/release.sh @@ -107,7 +107,7 @@ s3_url() { echo "https://$BUCKET" ;; *) - echo "http://$BUCKET.s3.amazonaws.com" + s3cmd ws-info s3://$BUCKET | awk -v 'FS=: +' '/http:\/\/'$BUCKET'/ { gsub(/\/+$/, "", $2); print $2 }' ;; esac } From 0ed69503c99279c624b8f63a252a71c7f8427b88 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 8 Nov 2013 15:45:18 -0700 Subject: [PATCH 070/184] Update release script to move https://get.docker.io/ubuntu/info to https://get.docker.io/ubuntu/ and provide a backwards-compatibility redirect (same for /builds/info) Upstream-commit: f56945d71bcce6e7d18b3471bf4744ddd70b1783 Component: engine --- components/engine/hack/release.sh | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/components/engine/hack/release.sh b/components/engine/hack/release.sh index 23718dd63d..06b76a1c7a 100755 --- a/components/engine/hack/release.sh +++ b/components/engine/hack/release.sh @@ -114,7 +114,7 @@ s3_url() { # Upload the 'ubuntu' bundle to S3: # 1. A full APT repository is published at $BUCKET/ubuntu/ -# 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/info +# 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/index release_ubuntu() { [ -e bundles/$VERSION/ubuntu ] || { echo >&2 './hack/make.sh must be run before release_ubuntu' @@ -168,7 +168,7 @@ EOF # Upload repo s3cmd --acl-public sync $APTDIR/ s3://$BUCKET/ubuntu/ - cat < /etc/apt/sources.list.d/docker.list # Then import the repository key @@ -180,7 +180,12 @@ apt-get update ; apt-get install -y lxc-docker # Alternatively, just use the curl-able install.sh script provided at $(s3_url) # EOF - echo "APT repository uploaded. Instructions available at $(s3_url)/ubuntu/info" + + # Add redirect at /ubuntu/info for URL-backwards-compatibility + rm -rf /tmp/emptyfile && touch /tmp/emptyfile + s3cmd --acl-public --add-header='x-amz-website-redirect-location:/ubuntu/' --mime-type='text/plain' put /tmp/emptyfile s3://$BUCKET/ubuntu/info + + echo "APT repository uploaded. Instructions available at $(s3_url)/ubuntu" } # Upload a static binary to S3 @@ -189,14 +194,20 @@ release_binary() { echo >&2 './hack/make.sh must be run before release_binary' exit 1 } + S3DIR=s3://$BUCKET/builds/Linux/x86_64 s3cmd --acl-public put bundles/$VERSION/binary/docker-$VERSION $S3DIR/docker-$VERSION - cat < Date: Fri, 8 Nov 2013 15:01:01 -0800 Subject: [PATCH 071/184] prevent deletion if image is used by a running container Upstream-commit: 45c2b5dc1a0d8f3658f012844f9d89fbb2290c42 Component: engine --- components/engine/server.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/components/engine/server.go b/components/engine/server.go index 3441029027..ceb3401f1d 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1257,6 +1257,26 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { } return nil, nil } + + // Prevent deletion if image is used by a running container + for _, container := range srv.runtime.List() { + if container.State.Running { + parent, err := srv.runtime.repositories.LookupImage(container.Image) + if err != nil { + return nil, err + } + + if err := parent.WalkHistory(func(p *Image) error { + if img.ID == p.ID { + return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it", name, container.ID) + } + return nil + }); err != nil { + return nil, err + } + } + } + if strings.Contains(img.ID, name) { //delete via ID return srv.deleteImage(img, "", "") From 1fdd529660efab480c11fd9fe01d2717d8cdec48 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 8 Nov 2013 15:41:45 -0700 Subject: [PATCH 072/184] Update ubuntu packaging script, especially to stop docker group deletion Upstream-commit: 498b6031b12da56c442d7c2b501f500766652b2d Component: engine --- components/engine/hack/make/ubuntu | 70 ++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/components/engine/hack/make/ubuntu b/components/engine/hack/make/ubuntu index 5834172fd1..d4b9fd0b30 100644 --- a/components/engine/hack/make/ubuntu +++ b/components/engine/hack/make/ubuntu @@ -10,7 +10,7 @@ fi PACKAGE_ARCHITECTURE="$(dpkg-architecture -qDEB_HOST_ARCH)" PACKAGE_URL="http://www.docker.io/" PACKAGE_MAINTAINER="docker@dotcloud.com" -PACKAGE_DESCRIPTION="lxc-docker is a Linux container runtime +PACKAGE_DESCRIPTION="Linux container runtime Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers. @@ -37,27 +37,51 @@ bundle_ubuntu() { # This will fail if the binary bundle hasn't been built cp $DEST/../binary/docker-$VERSION $DIR/usr/bin/docker - # Generate postinst/prerm scripts - cat >/tmp/postinst <<'EOF' + # Generate postinst/prerm/postrm scripts + cat > /tmp/postinst <<'EOF' #!/bin/sh -service docker stop || true -grep -q '^docker:' /etc/group || groupadd --system docker || true -service docker start -EOF - cat >/tmp/prerm <<'EOF' -#!/bin/sh -service docker stop || true +set -e +set -u -case "$1" in - purge|remove|abort-install) - groupdel docker || true - ;; - - upgrade|failed-upgrade|abort-upgrade) - # don't touch docker group - ;; -esac +getent group docker > /dev/null || groupadd --system docker || true + +update-rc.d docker defaults > /dev/null || true +if [ -n "$2" ]; then + _dh_action=restart +else + _dh_action=start +fi +service docker $_dh_action 2>/dev/null || true + +#DEBHELPER# EOF + cat > /tmp/prerm <<'EOF' +#!/bin/sh +set -e +set -u + +service docker stop 2>/dev/null || true + +#DEBHELPER# +EOF + cat > /tmp/postrm <<'EOF' +#!/bin/sh +set -e +set -u + +if [ "$1" = "purge" ] ; then + update-rc.d docker remove > /dev/null || true +fi + +# In case this system is running systemd, we make systemd reload the unit files +# to pick up changes. +if [ -d /run/systemd/system ] ; then + systemctl --system daemon-reload > /dev/null || true +fi + +#DEBHELPER# +EOF + # TODO swaths of these were borrowed from debhelper's auto-inserted stuff, because we're still using fpm - we need to use debhelper instead, and somehow reconcile Ubuntu that way chmod +x /tmp/postinst /tmp/prerm ( @@ -66,6 +90,7 @@ EOF --name lxc-docker-$VERSION --version $PKGVERSION \ --after-install /tmp/postinst \ --before-remove /tmp/prerm \ + --after-remove /tmp/postrm \ --architecture "$PACKAGE_ARCHITECTURE" \ --prefix / \ --depends lxc \ @@ -82,6 +107,8 @@ EOF --vendor "$PACKAGE_VENDOR" \ --config-files /etc/init/docker.conf \ --config-files /etc/init.d/docker \ + --config-files /etc/default/docker \ + --deb-compression xz \ -t deb . mkdir empty fpm -s dir -C empty \ @@ -92,7 +119,12 @@ EOF --maintainer "$PACKAGE_MAINTAINER" \ --url "$PACKAGE_URL" \ --vendor "$PACKAGE_VENDOR" \ + --config-files /etc/init/docker.conf \ + --config-files /etc/init.d/docker \ + --config-files /etc/default/docker \ + --deb-compression xz \ -t deb . + # note: the --config-files lines have to be duplicated to stop overwrite on package upgrade (since we have to use this funky virtual package) ) } From f4773a7d1fddbd0a12a94cb068830056cdff04a2 Mon Sep 17 00:00:00 2001 From: Roberto Gandolfo Hashioka Date: Fri, 8 Nov 2013 16:47:42 -0800 Subject: [PATCH 073/184] - Added delete all the containers example Upstream-commit: 403f9fc357d64ccbdf82e1c4cbad2946eb1d1080 Component: engine --- components/engine/docs/sources/commandline/cli.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/engine/docs/sources/commandline/cli.rst b/components/engine/docs/sources/commandline/cli.rst index 4e3d369e14..28fac978f5 100644 --- a/components/engine/docs/sources/commandline/cli.rst +++ b/components/engine/docs/sources/commandline/cli.rst @@ -622,6 +622,15 @@ This will remove the container referenced under the link ``/redis``. This will remove the underlying link between ``/webapp`` and the ``/redis`` containers removing all network communication. +.. code-block:: bash + + $ docker rm `docker ps -a -q` + + +This command will delete all the stopped containers. The command ``docker ps -a -q`` will return all +the existing container's id and the ``rm`` command takes those id's and delete them. The running containers +will not be deleted, even though they will appear on that id's list. + .. _cli_rmi: ``rmi`` From d772cfe67302b297748e3b1fe28e23df52f2c62e Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 25 Oct 2013 11:59:59 +1000 Subject: [PATCH 074/184] make all image ID and container ID API responses use the Long ID (Closes #2098) Upstream-commit: b3974abe4f01d408850e245c9b52c77f3571e0b2 Component: engine --- components/engine/AUTHORS | 1 + components/engine/commands_test.go | 128 +++++++++++++++++- components/engine/container.go | 16 +-- components/engine/container_test.go | 3 +- .../engine/docs/sources/terms/container.rst | 7 + .../engine/docs/sources/terms/image.rst | 8 ++ components/engine/docs/sources/use/basics.rst | 23 +++- components/engine/image.go | 4 - components/engine/runtime.go | 4 +- components/engine/server.go | 34 ++--- 10 files changed, 182 insertions(+), 46 deletions(-) diff --git a/components/engine/AUTHORS b/components/engine/AUTHORS index 64f2ce21aa..711c7718ff 100644 --- a/components/engine/AUTHORS +++ b/components/engine/AUTHORS @@ -165,6 +165,7 @@ Sridatta Thatipamala Sridhar Ratnakumar Steeve Morin Stefan Praszalowicz +Sven Dowideit Thatcher Peskens Thermionix Thijs Terlouw diff --git a/components/engine/commands_test.go b/components/engine/commands_test.go index 6c6a8e975b..1778f1b89f 100644 --- a/components/engine/commands_test.go +++ b/components/engine/commands_test.go @@ -6,6 +6,8 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" + "os" + "path" "regexp" "strings" "testing" @@ -381,8 +383,8 @@ func TestRunAttachStdin(t *testing.T) { if err != nil { t.Fatal(err) } - if cmdOutput != container.ShortID()+"\n" { - t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortID()+"\n", cmdOutput) + if cmdOutput != container.ID+"\n" { + t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ID+"\n", cmdOutput) } }) @@ -459,7 +461,7 @@ func TestRunDetach(t *testing.T) { }) } -// TestAttachDetach checks that attach in tty mode can be detached +// TestAttachDetach checks that attach in tty mode can be detached using the long container ID func TestAttachDetach(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() @@ -486,8 +488,8 @@ func TestAttachDetach(t *testing.T) { container = globalRuntime.List()[0] - if strings.Trim(string(buf[:n]), " \r\n") != container.ShortID() { - t.Fatalf("Wrong ID received. Expect %s, received %s", container.ShortID(), buf[:n]) + if strings.Trim(string(buf[:n]), " \r\n") != container.ID { + t.Fatalf("Wrong ID received. Expect %s, received %s", container.ID, buf[:n]) } }) setTimeout(t, "Starting container timed out", 10*time.Second, func() { @@ -501,7 +503,69 @@ func TestAttachDetach(t *testing.T) { ch = make(chan struct{}) go func() { defer close(ch) - if err := cli.CmdAttach(container.ShortID()); err != nil { + if err := cli.CmdAttach(container.ID); err != nil { + if err != io.ErrClosedPipe { + t.Fatal(err) + } + } + }() + + setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { + if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { + if err != io.ErrClosedPipe { + t.Fatal(err) + } + } + }) + + setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { + stdinPipe.Write([]byte{16, 17}) + if err := stdinPipe.Close(); err != nil { + t.Fatal(err) + } + }) + closeWrap(stdin, stdinPipe, stdout, stdoutPipe) + + // wait for CmdRun to return + setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() { + <-ch + }) + + time.Sleep(500 * time.Millisecond) + if !container.State.Running { + t.Fatal("The detached container should be still running") + } + + setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() { + container.Kill() + }) +} + +// TestAttachDetachTruncatedID checks that attach in tty mode can be detached +func TestAttachDetachTruncatedID(t *testing.T) { + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + + go stdout.Read(make([]byte, 1024)) + setTimeout(t, "Starting container timed out", 2*time.Second, func() { + if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil { + t.Fatal(err) + } + }) + + container := globalRuntime.List()[0] + + stdin, stdinPipe = io.Pipe() + stdout, stdoutPipe = io.Pipe() + cli = NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + + ch := make(chan struct{}) + go func() { + defer close(ch) + if err := cli.CmdAttach(utils.TruncateID(container.ID)); err != nil { if err != io.ErrClosedPipe { t.Fatal(err) } @@ -825,3 +889,55 @@ run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ] return image } + +// #2098 - Docker cidFiles only contain short version of the containerId +//sudo docker run -cidfile /tmp/docker_test.cid ubuntu echo "test" +// TestRunCidFile tests that run -cidfile returns the longid +func TestRunCidFile(t *testing.T) { + stdout, stdoutPipe := io.Pipe() + + tmpDir, err := ioutil.TempDir("", "TestRunCidFile") + if err != nil { + t.Fatal(err) + } + tmpCidFile := path.Join(tmpDir, "cid") + + cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + + c := make(chan struct{}) + go func() { + defer close(c) + if err := cli.CmdRun("-cidfile", tmpCidFile, unitTestImageID, "ls"); err != nil { + t.Fatal(err) + } + }() + + defer os.RemoveAll(tmpDir) + setTimeout(t, "Reading command output time out", 2*time.Second, func() { + cmdOutput, err := bufio.NewReader(stdout).ReadString('\n') + if err != nil { + t.Fatal(err) + } + if len(cmdOutput) < 1 { + t.Fatalf("'ls' should return something , not '%s'", cmdOutput) + } + //read the tmpCidFile + buffer, err := ioutil.ReadFile(tmpCidFile) + if err != nil { + t.Fatal(err) + } + id := string(buffer) + + if len(id) != len("2bf44ea18873287bd9ace8a4cb536a7cbe134bed67e805fdf2f58a57f69b320c") { + t.Fatalf("-cidfile should be a long id, not '%s'", id) + } + //test that its a valid cid? (though the container is gone..) + //remove the file and dir. + }) + + setTimeout(t, "CmdRun timed out", 5*time.Second, func() { + <-c + }) + +} diff --git a/components/engine/container.go b/components/engine/container.go index 419ba84d29..330ddb0d2d 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1235,7 +1235,7 @@ func (container *Container) monitor() { container.State.setStopped(exitCode) if container.runtime != nil && container.runtime.srv != nil { - container.runtime.srv.LogEvent("die", container.ShortID(), container.runtime.repositories.ImageName(container.Image)) + container.runtime.srv.LogEvent("die", container.ID, container.runtime.repositories.ImageName(container.Image)) } // Cleanup @@ -1302,7 +1302,7 @@ func (container *Container) kill(sig int) error { } if output, err := exec.Command("lxc-kill", "-n", container.ID, strconv.Itoa(sig)).CombinedOutput(); err != nil { - log.Printf("error killing container %s (%s, %s)", container.ShortID(), output, err) + log.Printf("error killing container %s (%s, %s)", utils.TruncateID(container.ID), output, err) return err } @@ -1322,9 +1322,9 @@ func (container *Container) Kill() error { // 2. Wait for the process to die, in last resort, try to kill the process directly if err := container.WaitTimeout(10 * time.Second); err != nil { if container.cmd == nil { - return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ShortID()) + return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", utils.TruncateID(container.ID)) } - log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", container.ShortID()) + log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", utils.TruncateID(container.ID)) if err := container.cmd.Process.Kill(); err != nil { return err } @@ -1460,14 +1460,6 @@ func (container *Container) Unmount() error { return Unmount(container.RootfsPath()) } -// ShortID returns a shorthand version of the container's id for convenience. -// A collision with other container shorthands is very unlikely, but possible. -// In case of a collision a lookup with Runtime.Get() will fail, and the caller -// will need to use a langer prefix, or the full-length container Id. -func (container *Container) ShortID() string { - return utils.TruncateID(container.ID) -} - func (container *Container) logPath(name string) string { return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.ID, name)) } diff --git a/components/engine/container_test.go b/components/engine/container_test.go index d51946ece3..26007a732d 100644 --- a/components/engine/container_test.go +++ b/components/engine/container_test.go @@ -3,6 +3,7 @@ package docker import ( "bufio" "fmt" + "github.com/dotcloud/docker/utils" "io" "io/ioutil" "math/rand" @@ -1005,7 +1006,7 @@ func TestEnv(t *testing.T) { "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOME=/", "container=lxc", - "HOSTNAME=" + container.ShortID(), + "HOSTNAME=" + utils.TruncateID(container.ID), "FALSE=true", "TRUE=false", "TRICKY=tri", diff --git a/components/engine/docs/sources/terms/container.rst b/components/engine/docs/sources/terms/container.rst index aeb7b1c3a9..206664bd82 100644 --- a/components/engine/docs/sources/terms/container.rst +++ b/components/engine/docs/sources/terms/container.rst @@ -38,3 +38,10 @@ was when the container was stopped. You can promote a container to an :ref:`image_def` with ``docker commit``. Once a container is an image, you can use it as a parent for new containers. + +Container IDs +............. +All containers are identified by a 64 hexadecimal digit string (internally a 256bit +value). To simplify their use, a short ID of the first 12 characters can be used +on the commandline. There is a small possibility of short id collisions, so the +docker server will always return the long ID. diff --git a/components/engine/docs/sources/terms/image.rst b/components/engine/docs/sources/terms/image.rst index dafda1f3fc..6d5c8b2e7c 100644 --- a/components/engine/docs/sources/terms/image.rst +++ b/components/engine/docs/sources/terms/image.rst @@ -36,3 +36,11 @@ Base Image .......... An image that has no parent is a **base image**. + +Image IDs +......... +All images are identified by a 64 hexadecimal digit string (internally a 256bit +value). To simplify their use, a short ID of the first 12 characters can be used +on the command line. There is a small possibility of short id collisions, so the +docker server will always return the long ID. + diff --git a/components/engine/docs/sources/use/basics.rst b/components/engine/docs/sources/use/basics.rst index 0097b6836d..d1ad081f99 100644 --- a/components/engine/docs/sources/use/basics.rst +++ b/components/engine/docs/sources/use/basics.rst @@ -22,22 +22,37 @@ specify the path to it and manually start it. # Run docker in daemon mode sudo /docker -d & - -Running an interactive shell ----------------------------- +Download a pre-built image +-------------------------- .. code-block:: bash # Download an ubuntu image sudo docker pull ubuntu +This will find the ``ubuntu`` image by name in the :ref:`Central Index +` and download it from the top-level Central +Repository to a local image cache. + +.. NOTE:: When the image has successfully downloaded, you will see a 12 +character hash ``539c0211cd76: Download complete`` which is the short +form of the image ID. These short image IDs are the first 12 characters +of the full image ID - which can be found using ``docker inspect`` or +``docker images -notrunc=true`` + +.. _dockergroup: + +Running an interactive shell +---------------------------- + +.. code-block:: bash + # Run an interactive shell in the ubuntu image, # allocate a tty, attach stdin and stdout # To detach the tty without exiting the shell, # use the escape sequence Ctrl-p + Ctrl-q sudo docker run -i -t ubuntu /bin/bash -.. _dockergroup: Why ``sudo``? ------------- diff --git a/components/engine/image.go b/components/engine/image.go index 94cccaac67..c600273c1a 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -202,10 +202,6 @@ func (image *Image) Changes(rw string) ([]Change, error) { return Changes(layers, rw) } -func (image *Image) ShortID() string { - return utils.TruncateID(image.ID) -} - func ValidateID(id string) error { if id == "" { return fmt.Errorf("Image id can't be empty") diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 671694fce5..6a3b76a595 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -181,7 +181,7 @@ func (runtime *Runtime) ensureName(container *Container) error { if container.Name == "" { name, err := generateRandomName(runtime) if err != nil { - name = container.ShortID() + name = utils.TruncateID(container.ID) } container.Name = name @@ -288,7 +288,7 @@ func (runtime *Runtime) restore() error { // Try to set the default name for a container if it exists prior to links container.Name, err = generateRandomName(runtime) if err != nil { - container.Name = container.ShortID() + container.Name = utils.TruncateID(container.ID) } if _, err := runtime.containerGraph.Set(container.Name, container.ID); err != nil { diff --git a/components/engine/server.go b/components/engine/server.go index 3441029027..2e23612621 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -154,7 +154,7 @@ func (srv *Server) ContainerKill(name string, sig int) error { if err := container.Kill(); err != nil { return fmt.Errorf("Cannot kill container %s: %s", name, err) } - srv.LogEvent("kill", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) + srv.LogEvent("kill", container.ID, srv.runtime.repositories.ImageName(container.Image)) } else { // Otherwise, just send the requested signal if err := container.kill(sig); err != nil { @@ -180,7 +180,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { if _, err := io.Copy(out, data); err != nil { return err } - srv.LogEvent("export", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) + srv.LogEvent("export", container.ID, srv.runtime.repositories.ImageName(container.Image)) return nil } return fmt.Errorf("No such container: %s", name) @@ -230,7 +230,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. return "", err } out.Write(sf.FormatStatus("", img.ID)) - return img.ShortID(), nil + return img.ID, nil } func (srv *Server) ImagesViz(out io.Writer) error { @@ -250,9 +250,9 @@ func (srv *Server) ImagesViz(out io.Writer) error { return fmt.Errorf("Error while getting parent image: %v", err) } if parentImage != nil { - out.Write([]byte(" \"" + parentImage.ShortID() + "\" -> \"" + image.ShortID() + "\"\n")) + out.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n")) } else { - out.Write([]byte(" base -> \"" + image.ShortID() + "\" [style=invis]\n")) + out.Write([]byte(" base -> \"" + image.ID + "\" [style=invis]\n")) } } @@ -465,7 +465,7 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API continue } if before != "" { - if container.ShortID() == before { + if container.ID == before || utils.TruncateID(container.ID) == before { foundBefore = true continue } @@ -476,7 +476,7 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API if displayed == n { break } - if container.ShortID() == since { + if container.ID == since || utils.TruncateID(container.ID) == since { break } displayed++ @@ -518,7 +518,7 @@ func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, conf if err != nil { return "", err } - return img.ShortID(), err + return img.ID, err } func (srv *Server) ContainerTag(name, repo, tag string, force bool) error { @@ -1017,7 +1017,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write return err } } - out.Write(sf.FormatStatus("", img.ShortID())) + out.Write(sf.FormatStatus("", img.ID)) return nil } @@ -1046,8 +1046,8 @@ func (srv *Server) ContainerCreate(config *Config, name string) (string, []strin } return "", nil, err } - srv.LogEvent("create", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) - return container.ShortID(), buildWarnings, nil + srv.LogEvent("create", container.ID, srv.runtime.repositories.ImageName(container.Image)) + return container.ID, buildWarnings, nil } func (srv *Server) ContainerRestart(name string, t int) error { @@ -1055,7 +1055,7 @@ func (srv *Server) ContainerRestart(name string, t int) error { if err := container.Restart(t); err != nil { return fmt.Errorf("Cannot restart container %s: %s", name, err) } - srv.LogEvent("restart", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) + srv.LogEvent("restart", container.ID, srv.runtime.repositories.ImageName(container.Image)) } else { return fmt.Errorf("No such container: %s", name) } @@ -1111,7 +1111,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) if err := srv.runtime.Destroy(container); err != nil { return fmt.Errorf("Cannot destroy container %s: %s", name, err) } - srv.LogEvent("destroy", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) + srv.LogEvent("destroy", container.ID, srv.runtime.repositories.ImageName(container.Image)) if removeVolume { // Retrieve all volumes from all remaining containers @@ -1228,8 +1228,8 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro return nil, err } if tagDeleted { - imgs = append(imgs, APIRmi{Untagged: img.ShortID()}) - srv.LogEvent("untag", img.ShortID(), "") + imgs = append(imgs, APIRmi{Untagged: img.ID}) + srv.LogEvent("untag", img.ID, "") } } if len(srv.runtime.repositories.ByID()[img.ID]) == 0 { @@ -1364,7 +1364,7 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { if err := container.Start(); err != nil { return fmt.Errorf("Cannot start container %s: %s", name, err) } - srv.LogEvent("start", container.ShortID(), runtime.repositories.ImageName(container.Image)) + srv.LogEvent("start", container.ID, runtime.repositories.ImageName(container.Image)) return nil } @@ -1374,7 +1374,7 @@ func (srv *Server) ContainerStop(name string, t int) error { if err := container.Stop(t); err != nil { return fmt.Errorf("Cannot stop container %s: %s", name, err) } - srv.LogEvent("stop", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) + srv.LogEvent("stop", container.ID, srv.runtime.repositories.ImageName(container.Image)) } else { return fmt.Errorf("No such container: %s", name) } From e4cf9cf4dc746fa53b2c7659b0247bae8a931f31 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 9 Nov 2013 02:28:04 -0700 Subject: [PATCH 075/184] Fix the display of get.docker.io in Firefox by making our index files text/plain Upstream-commit: 7f1b179c67476efa7dbafda55541b515fbe0f346 Component: engine --- components/engine/hack/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/hack/release.sh b/components/engine/hack/release.sh index 06b76a1c7a..931ab6f9af 100755 --- a/components/engine/hack/release.sh +++ b/components/engine/hack/release.sh @@ -97,7 +97,7 @@ write_to_s3() { DEST=$1 F=`mktemp` cat > $F - s3cmd --acl-public put $F $DEST + s3cmd --acl-public --mime-type='text/plain' put $F $DEST rm -f $F } From 311c5ef875c76b0935419a370ddb276d9a7111b1 Mon Sep 17 00:00:00 2001 From: Roberto Gandolfo Hashioka Date: Sat, 9 Nov 2013 11:08:43 -0800 Subject: [PATCH 076/184] - Updated description Upstream-commit: 4ec0b515786ce266234d350fee872764974d2218 Component: engine --- components/engine/docs/sources/commandline/cli.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/docs/sources/commandline/cli.rst b/components/engine/docs/sources/commandline/cli.rst index 28fac978f5..0267852149 100644 --- a/components/engine/docs/sources/commandline/cli.rst +++ b/components/engine/docs/sources/commandline/cli.rst @@ -627,9 +627,9 @@ network communication. $ docker rm `docker ps -a -q` -This command will delete all the stopped containers. The command ``docker ps -a -q`` will return all -the existing container's id and the ``rm`` command takes those id's and delete them. The running containers -will not be deleted, even though they will appear on that id's list. +This command will delete all stopped containers. The command ``docker ps -a -q`` will return all +existing container IDs and pass them to the ``rm`` command which will delete them. Any running +containers will not be deleted. .. _cli_rmi: From 862e9a4daef9c70644e7507855a178e91b816a68 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sat, 9 Nov 2013 19:31:08 -0800 Subject: [PATCH 077/184] Correctly express "any address" to iptables. Iptables interprets "-d 0.0.0.0" as "-d 0.0.0.0/32", not /0. This results in the DNAT rule never matching any traffic if not bound to a specific host IP. Fixes #2598 Upstream-commit: 8ba8783bcc0ec3a0c5391445d52e2b2a9d0a3f8a Component: engine --- components/engine/iptables/iptables.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/components/engine/iptables/iptables.go b/components/engine/iptables/iptables.go index 82ecf8bb5b..0438bcbd88 100644 --- a/components/engine/iptables/iptables.go +++ b/components/engine/iptables/iptables.go @@ -55,9 +55,16 @@ func RemoveExistingChain(name string) error { } func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr string, dest_port int) error { + daddr := ip.String() + if ip.IsUnspecified() { + // iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we + // want "0.0.0.0/0". "0/0" is correctly interpreted as "any + // value" by both iptables and ip6tables. + daddr = "0/0" + } if output, err := Raw("-t", "nat", fmt.Sprint(action), c.Name, "-p", proto, - "-d", ip.String(), + "-d", daddr, "--dport", strconv.Itoa(port), "!", "-i", c.Bridge, "-j", "DNAT", From 9fcdb45469ac17c2777987db17aa59eb14f9d371 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Sun, 10 Nov 2013 00:06:55 -0600 Subject: [PATCH 078/184] Edits after code review Return long image ID Return streamformatted error or "raw" error Upstream-commit: 8cc19765b48d1a429b840b731ed5fd5b81fbda3c Component: engine --- components/engine/api.go | 6 +++++- components/engine/server.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/components/engine/api.go b/components/engine/api.go index 8f31ab0282..ce46d50129 100644 --- a/components/engine/api.go +++ b/components/engine/api.go @@ -481,7 +481,11 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht sf := utils.NewStreamFormatter(version > 1.0) err := srv.ImageInsert(name, url, path, w, sf) if err != nil { - w.Write(sf.FormatError(err)) + if sf.Used() { + w.Write(sf.FormatError(err)) + return nil + } + return err } return nil diff --git a/components/engine/server.go b/components/engine/server.go index eb4e74e0bf..93ca61365a 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -229,7 +229,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. if err != nil { return err } - out.Write(sf.FormatStatus(utils.TruncateID(img.ID), "Image created")) + out.Write(sf.FormatStatus(img.ID, "Image created")) return nil } From a33628fd7cb09a0db56b49021034f2a49557606f Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 9 Nov 2013 14:10:00 -0500 Subject: [PATCH 079/184] Added status column to API client table Upstream-commit: 5a1bfd9aa9a0983b5141dd8e393cc67a68fc6590 Component: engine --- .../api/remote_api_client_libraries.rst | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/components/engine/docs/sources/api/remote_api_client_libraries.rst b/components/engine/docs/sources/api/remote_api_client_libraries.rst index 8243d02c80..f00ab1c2b5 100644 --- a/components/engine/docs/sources/api/remote_api_client_libraries.rst +++ b/components/engine/docs/sources/api/remote_api_client_libraries.rst @@ -12,28 +12,28 @@ compatibility. Please file issues with the library owners. If you find more library implementations, please list them in Docker doc bugs and we will add the libraries here. -+----------------------+----------------+--------------------------------------------+ -| Language/Framework | Name | Repository | -+======================+================+============================================+ -| Python | docker-py | https://github.com/dotcloud/docker-py | -+----------------------+----------------+--------------------------------------------+ -| Ruby | docker-client | https://github.com/geku/docker-client | -+----------------------+----------------+--------------------------------------------+ -| Ruby | docker-api | https://github.com/swipely/docker-api | -+----------------------+----------------+--------------------------------------------+ -| Javascript (NodeJS) | docker.io | https://github.com/appersonlabs/docker.io | -| | | Install via NPM: `npm install docker.io` | -+----------------------+----------------+--------------------------------------------+ -| Javascript | docker-js | https://github.com/dgoujard/docker-js | -+----------------------+----------------+--------------------------------------------+ -| Javascript (Angular) | dockerui | https://github.com/crosbymichael/dockerui | -| **WebUI** | | | -+----------------------+----------------+--------------------------------------------+ -| Java | docker-java | https://github.com/kpelykh/docker-java | -+----------------------+----------------+--------------------------------------------+ -| Erlang | erldocker | https://github.com/proger/erldocker | -+----------------------+----------------+--------------------------------------------+ -| Go | go-dockerclient| https://github.com/fsouza/go-dockerclient | -+----------------------+----------------+--------------------------------------------+ -| PHP | Alvine | http://pear.alvine.io/ (alpha) | -+----------------------+----------------+--------------------------------------------+ ++----------------------+----------------+--------------------------------------------+----------+ +| Language/Framework | Name | Repository | Status | ++======================+================+============================================+==========+ +| Python | docker-py | https://github.com/dotcloud/docker-py | Active | ++----------------------+----------------+--------------------------------------------+----------+ +| Ruby | docker-client | https://github.com/geku/docker-client | Outdated | ++----------------------+----------------+--------------------------------------------+----------+ +| Ruby | docker-api | https://github.com/swipely/docker-api | Active | ++----------------------+----------------+--------------------------------------------+----------+ +| Javascript (NodeJS) | docker.io | https://github.com/appersonlabs/docker.io | Active | +| | | Install via NPM: `npm install docker.io` | | ++----------------------+----------------+--------------------------------------------+----------+ +| Javascript | docker-js | https://github.com/dgoujard/docker-js | Active | ++----------------------+----------------+--------------------------------------------+----------+ +| Javascript (Angular) | dockerui | https://github.com/crosbymichael/dockerui | Active | +| **WebUI** | | | | ++----------------------+----------------+--------------------------------------------+----------+ +| Java | docker-java | https://github.com/kpelykh/docker-java | Active | ++----------------------+----------------+--------------------------------------------+----------+ +| Erlang | erldocker | https://github.com/proger/erldocker | Active | ++----------------------+----------------+--------------------------------------------+----------+ +| Go | go-dockerclient| https://github.com/fsouza/go-dockerclient | Active | ++----------------------+----------------+--------------------------------------------+----------+ +| PHP | Alvine | http://pear.alvine.io/ (alpha) | Active | ++----------------------+----------------+--------------------------------------------+----------+ From ab0419261e571ac6cdc37962064f58f6acca7610 Mon Sep 17 00:00:00 2001 From: Galen Sampson Date: Wed, 6 Nov 2013 19:16:56 -0800 Subject: [PATCH 080/184] Vagrantfile updates. - Remove the overrides config.vm.box and config.vm.box_url and use the same values for all providers. - Use the same private key path for all providers. It is still possible to set a different private key path through the environment variable SSH_PRIVKEY_PATH if desired (your AWS key may be different from your Virtualbox key). - Allow the environment variable AWS_INSTANCE_TYPE to specify the instance type of instead of hard coding the AWS instance type as 't1.micro'. 't1.micro' is still the default if unspecified. - Use the same environment variables for keys as the Amazon provided EC2 API tools. This allows people who already have the EC2 tools set up correctly to use 'vagrant up' with less environment configuration than before. - Rewrite the provisioning code. The goal is to be idempotent and to correctly install docker for all providers instead of just virtualbox. It will conditionally install the virtualbox guest additions if virtualbox is the provider. - Update the AWS install documentation to reflect the changes. Upstream-commit: ccbb5d34927dc1905984bead3ebb576c0ea20960 Component: engine --- components/engine/Vagrantfile | 163 +++++++++++++----- .../docs/sources/installation/amazon.rst | 35 +++- 2 files changed, 146 insertions(+), 52 deletions(-) diff --git a/components/engine/Vagrantfile b/components/engine/Vagrantfile index 93a2219fa0..a0bb38ca4f 100644 --- a/components/engine/Vagrantfile +++ b/components/engine/Vagrantfile @@ -4,65 +4,135 @@ BOX_NAME = ENV['BOX_NAME'] || "ubuntu" BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box" VF_BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64_vmware_fusion.box" +AWS_BOX_URI = ENV['BOX_URI'] || "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box" AWS_REGION = ENV['AWS_REGION'] || "us-east-1" -AWS_AMI = ENV['AWS_AMI'] || "ami-d0f89fb9" +AWS_AMI = ENV['AWS_AMI'] || "ami-69f5a900" +AWS_INSTANCE_TYPE = ENV['AWS_INSTANCE_TYPE'] || 't1.micro' + FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS'] +SSH_PRIVKEY_PATH = ENV["SSH_PRIVKEY_PATH"] + +# A script to upgrade from the 12.04 kernel to the raring backport kernel (3.8) +# and install docker. +$script = <