From ee1f88fe1bee85ea1fffafe12499b3a036e2fb21 Mon Sep 17 00:00:00 2001 From: shin- Date: Mon, 14 Apr 2014 20:32:47 +0200 Subject: [PATCH 01/37] Added support for multiple endpoints in X-Docker-Endpoints header Docker-DCO-1.1-Signed-off-by: Joffrey F (github: shin-) Upstream-commit: 720f3447046355329b5ba5d850caca84328182d5 Component: engine --- components/engine/registry/registry.go | 33 ++++++++++++++----- .../engine/registry/registry_mock_test.go | 2 +- components/engine/registry/registry_test.go | 15 ++++++++- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index 817c08afa9..3656032e92 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/registry/registry.go @@ -297,6 +297,25 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ return nil, fmt.Errorf("Could not reach any registry endpoint") } +func buildEndpointsList(headers []string, indexEp string) ([]string, error) { + var endpoints []string + parsedUrl, err := url.Parse(indexEp) + if err != nil { + return nil, err + } + var urlScheme = parsedUrl.Scheme + // The Registry's URL scheme has to match the Index' + for _, ep := range headers { + epList := strings.Split(ep, ",") + for _, epListElement := range epList { + endpoints = append( + endpoints, + fmt.Sprintf("%s://%s/v1/", urlScheme, strings.TrimSpace(epListElement))) + } + } + return endpoints, nil +} + func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { indexEp := r.indexEndpoint repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote) @@ -332,11 +351,10 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { } var endpoints []string - var urlScheme = indexEp[:strings.Index(indexEp, ":")] if res.Header.Get("X-Docker-Endpoints") != "" { - // The Registry's URL scheme has to match the Index' - for _, ep := range res.Header["X-Docker-Endpoints"] { - endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep)) + endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], indexEp) + if err != nil { + return nil, err } } else { return nil, fmt.Errorf("Index response didn't contain any endpoints") @@ -565,7 +583,6 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat } var tokens, endpoints []string - var urlScheme = indexEp[:strings.Index(indexEp, ":")] if !validate { if res.StatusCode != 200 && res.StatusCode != 201 { errBody, err := ioutil.ReadAll(res.Body) @@ -582,9 +599,9 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat } if res.Header.Get("X-Docker-Endpoints") != "" { - // The Registry's URL scheme has to match the Index' - for _, ep := range res.Header["X-Docker-Endpoints"] { - endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep)) + endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], indexEp) + if err != nil { + return nil, err } } else { return nil, fmt.Errorf("Index response didn't contain any endpoints") diff --git a/components/engine/registry/registry_mock_test.go b/components/engine/registry/registry_mock_test.go index dd5da6bd50..6b00751318 100644 --- a/components/engine/registry/registry_mock_test.go +++ b/components/engine/registry/registry_mock_test.go @@ -291,7 +291,7 @@ func handlerUsers(w http.ResponseWriter, r *http.Request) { func handlerImages(w http.ResponseWriter, r *http.Request) { u, _ := url.Parse(testHttpServer.URL) - w.Header().Add("X-Docker-Endpoints", u.Host) + w.Header().Add("X-Docker-Endpoints", fmt.Sprintf("%s , %s ", u.Host, "test.example.com")) w.Header().Add("X-Docker-Token", fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())) if r.Method == "PUT" { if strings.HasSuffix(r.URL.Path, "images") { diff --git a/components/engine/registry/registry_test.go b/components/engine/registry/registry_test.go index c072da41c5..ad64fb1f4c 100644 --- a/components/engine/registry/registry_test.go +++ b/components/engine/registry/registry_test.go @@ -1,7 +1,9 @@ package registry import ( + "fmt" "github.com/dotcloud/docker/utils" + "net/url" "strings" "testing" ) @@ -99,12 +101,23 @@ func TestGetRemoteTags(t *testing.T) { func TestGetRepositoryData(t *testing.T) { r := spawnTestRegistry(t) + parsedUrl, err := url.Parse(makeURL("/v1/")) + if err != nil { + t.Fatal(err) + } + host := "http://" + parsedUrl.Host + "/v1/" data, err := r.GetRepositoryData("foo42/bar") if err != nil { t.Fatal(err) } assertEqual(t, len(data.ImgList), 2, "Expected 2 images in ImgList") - assertEqual(t, len(data.Endpoints), 1, "Expected one endpoint in Endpoints") + assertEqual(t, len(data.Endpoints), 2, + fmt.Sprintf("Expected 2 endpoints in Endpoints, found %d instead", len(data.Endpoints))) + assertEqual(t, data.Endpoints[0], host, + fmt.Sprintf("Expected first endpoint to be %s but found %s instead", host, data.Endpoints[0])) + assertEqual(t, data.Endpoints[1], "http://test.example.com/v1/", + fmt.Sprintf("Expected first endpoint to be http://test.example.com/v1/ but found %s instead", data.Endpoints[1])) + } func TestPushImageJSONRegistry(t *testing.T) { From 291ddfb05abde4b80f644c1f9d0feedd832b49a0 Mon Sep 17 00:00:00 2001 From: Daniel Norberg Date: Tue, 22 Apr 2014 16:56:18 -0400 Subject: [PATCH 02/37] avoid suicide container.Kill() might read a pid of 0 from container.State.Pid due to losing a race with container.monitor() calling container.State.SetStopped(). Sending a SIGKILL to pid 0 is undesirable as "If pid equals 0, then sig is sent to every process in the process group of the calling process." Docker-DCO-1.1-Signed-off-by: Daniel Norberg (github: danielnorberg) Upstream-commit: b3ddc31b9581665eb15dedd0aa45bd37c1eb6815 Component: engine --- components/engine/daemon/container.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index 6f63d565f2..c06fd2c074 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/daemon/container.go @@ -654,9 +654,12 @@ 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 { - log.Printf("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", utils.TruncateID(container.ID)) - if err := syscall.Kill(container.State.Pid, 9); err != nil { - return err + // Ensure that we don't kill ourselves + if pid := container.State.Pid; pid != 0 { + log.Printf("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", utils.TruncateID(container.ID)) + if err := syscall.Kill(pid, 9); err != nil { + return err + } } } From 4ac92608333e618ec4940cc6094045e3b5aaa1f6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 23 Apr 2014 13:47:56 -0700 Subject: [PATCH 03/37] Add exported status code from a job This allows the job's status code to be consumed externally so that we can use it as an exit code or saving to a state file. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: f90029611fec082a41b5629e43a88a39f0674fe2 Component: engine --- components/engine/engine/job.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/engine/engine/job.go b/components/engine/engine/job.go index 50d64011f9..b56155ac1c 100644 --- a/components/engine/engine/job.go +++ b/components/engine/engine/job.go @@ -208,3 +208,7 @@ func (job *Job) Error(err error) Status { fmt.Fprintf(job.Stderr, "%s\n", err) return StatusErr } + +func (job *Job) StatusCode() int { + return int(job.status) +} From adad81a2c2575f56d750ba6d9d349da1767b5e39 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 23 Apr 2014 11:03:03 +0000 Subject: [PATCH 04/37] initial version of installation on ubuntu 14.04 LTS Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) Upstream-commit: da8f6ffdeb54a2d99a70deb4ffc58fdff76d5880 Component: engine --- .../docs/sources/installation/ubuntulinux.md | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/components/engine/docs/sources/installation/ubuntulinux.md b/components/engine/docs/sources/installation/ubuntulinux.md index 40dc541b6a..04173cf917 100644 --- a/components/engine/docs/sources/installation/ubuntulinux.md +++ b/components/engine/docs/sources/installation/ubuntulinux.md @@ -4,10 +4,6 @@ page_keywords: Docker, Docker documentation, requirements, virtualbox, vagrant, # Ubuntu -> **Warning**: -> These instructions have changed for 0.6. If you are upgrading from an -> earlier version, you will need to follow them again. - > **Note**: > Docker is still under heavy development! We don't recommend using it in > production yet, but we're getting closer with each release. Please see @@ -16,6 +12,7 @@ page_keywords: Docker, Docker documentation, requirements, virtualbox, vagrant, Docker is supported on the following versions of Ubuntu: + - [*Ubuntu Trusty 14.04 (LTS) (64-bit)*](#ubuntu-trusty-1404-lts-64-bit) - [*Ubuntu Precise 12.04 (LTS) (64-bit)*](#ubuntu-precise-1204-lts-64-bit) - [*Ubuntu Raring 13.04 and Saucy 13.10 (64 bit)*](#ubuntu-raring-1304-and-saucy-1310-64-bit) @@ -23,6 +20,30 @@ Docker is supported on the following versions of Ubuntu: Please read [*Docker and UFW*](#docker-and-ufw), if you plan to use [UFW (Uncomplicated Firewall)](https://help.ubuntu.com/community/UFW) +## Ubuntu Trusty 14.04 (LTS) (64-bit) + +Ubuntu Trusty comes with a 3.13.0 Linux kernel, and a `docker.io` package which +installs all its prerequisites from Ubuntu's repository. + +> **Note**: +> Ubuntu (and Debian) contain a much older KDE3/GNOME2 package called ``docker``, so the +> package and the executable are called ``docker.io``. + +### Installation + +To install the latest Ubuntu package (may not be the latest Docker release): + + sudo apt-get update + sudo apt-get install docker.io + sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker + +To verify that everything has worked as expected: + + sudo docker run -i -t ubuntu /bin/bash + +Which should download the `ubuntu` image, and then start `bash` in a container. + + ## Ubuntu Precise 12.04 (LTS) (64-bit) This installation path should work at all times. From 4b4b1b7313abf1f099198ede7891298598e1a98b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Apr 2014 21:41:31 +0000 Subject: [PATCH 05/37] Move apparmor into security sub dir Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: d26ea78e42ebf18219b88e01c6252f30aa764aa2 Component: engine --- components/engine/pkg/libcontainer/nsinit/init.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index 0e85c0e4be..ce51db37f9 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/pkg/libcontainer/nsinit/init.go @@ -13,6 +13,7 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/network" + "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/user" From 9da373d6b125423b79fce1d647c7ca52576c6234 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Apr 2014 23:03:52 +0000 Subject: [PATCH 06/37] Add restrictions to proc in libcontainer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 60a90970bc4add3547064004f08c19ab5027141b Component: engine --- .../engine/daemon/execdriver/native/create.go | 3 + .../engine/daemon/execdriver/native/driver.go | 7 ++ .../engine/pkg/libcontainer/nsinit/init.go | 2 +- .../engine/pkg/libcontainer/nsinit/mount.go | 67 +++++++++++-------- .../security/restrict/restrict.go | 46 +++++++++++++ 5 files changed, 96 insertions(+), 29 deletions(-) create mode 100644 components/engine/pkg/libcontainer/security/restrict/restrict.go diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index ef17ce7042..1edbd17ad3 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/daemon/execdriver/native/create.go @@ -25,6 +25,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container container.Cgroups.Name = c.ID // check to see if we are running in ramdisk to disable pivot root container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" + container.Context["restriction_path"] = d.restrictionPath if err := d.createNetwork(container, c); err != nil { return nil, err @@ -81,6 +82,8 @@ func (d *driver) setPrivileged(container *libcontainer.Container) error { c.Enabled = true } container.Cgroups.DeviceAccess = true + delete(container.Context, "restriction_path") + if apparmor.IsEnabled() { container.Context["apparmor_profile"] = "unconfined" } diff --git a/components/engine/daemon/execdriver/native/driver.go b/components/engine/daemon/execdriver/native/driver.go index ab82cdcc65..31a2eb0dae 100644 --- a/components/engine/daemon/execdriver/native/driver.go +++ b/components/engine/daemon/execdriver/native/driver.go @@ -62,6 +62,7 @@ type driver struct { root string initPath string activeContainers map[string]*exec.Cmd + restrictionPath string } func NewDriver(root, initPath string) (*driver, error) { @@ -72,8 +73,14 @@ func NewDriver(root, initPath string) (*driver, error) { if err := apparmor.InstallDefaultProfile(filepath.Join(root, "../..", BackupApparmorProfilePath)); err != nil { return nil, err } + restrictionPath := filepath.Join(root, "empty") + if err := os.MkdirAll(restrictionPath, 0700); err != nil { + return nil, err + } + return &driver{ root: root, + restrictionPath: restrictionPath, initPath: initPath, activeContainers: make(map[string]*exec.Cmd), }, nil diff --git a/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index ce51db37f9..fb3a895a78 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/pkg/libcontainer/nsinit/init.go @@ -61,7 +61,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol label.Init() ns.logger.Println("setup mount namespace") - if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot, container.Context["mount_label"]); err != nil { + if err := setupNewMountNamespace(rootfs, console, container); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := system.Sethostname(container.Hostname); err != nil { diff --git a/components/engine/pkg/libcontainer/nsinit/mount.go b/components/engine/pkg/libcontainer/nsinit/mount.go index dd6b1c8a43..e4869a0ecb 100644 --- a/components/engine/pkg/libcontainer/nsinit/mount.go +++ b/components/engine/pkg/libcontainer/nsinit/mount.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/system" "io/ioutil" "os" @@ -21,9 +22,9 @@ const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NOD // // There is no need to unmount the new mounts because as soon as the mount namespace // is no longer in use, the mounts will be removed automatically -func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, console string, readonly, noPivotRoot bool, mountLabel string) error { +func setupNewMountNamespace(rootfs, console string, container *libcontainer.Container) error { flag := syscall.MS_PRIVATE - if noPivotRoot { + if container.NoPivotRoot { flag = syscall.MS_SLAVE } if err := system.Mount("", "/", "", uintptr(flag|syscall.MS_REC), ""); err != nil { @@ -32,44 +33,28 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mouting %s as bind %s", rootfs, err) } - if err := mountSystem(rootfs, mountLabel); err != nil { + if err := mountSystem(rootfs, container.Context["mount_label"]); err != nil { return fmt.Errorf("mount system %s", err) } - - for _, m := range bindMounts { - var ( - flags = syscall.MS_BIND | syscall.MS_REC - dest = filepath.Join(rootfs, m.Destination) - ) - if !m.Writable { - flags = flags | syscall.MS_RDONLY - } - if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil { - return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err) - } - if !m.Writable { - if err := system.Mount(m.Source, dest, "bind", uintptr(flags|syscall.MS_REMOUNT), ""); err != nil { - return fmt.Errorf("remounting %s into %s %s", m.Source, dest, err) - } - } - if m.Private { - if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil { - return fmt.Errorf("mounting %s private %s", dest, err) - } + if err := setupBindmounts(rootfs, container.Mounts); err != nil { + return fmt.Errorf("bind mounts %s", err) + } + if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" { + if err := restrict.Restrict(rootfs, restrictionPath); err != nil { + return fmt.Errorf("restrict %s", err) } } - if err := copyDevNodes(rootfs); err != nil { return fmt.Errorf("copy dev nodes %s", err) } - if err := setupPtmx(rootfs, console, mountLabel); err != nil { + if err := setupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } if err := system.Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) } - if noPivotRoot { + if container.NoPivotRoot { if err := rootMsMove(rootfs); err != nil { return err } @@ -79,7 +64,7 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons } } - if readonly { + if container.ReadonlyFs { if err := system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mounting %s as readonly %s", rootfs, err) } @@ -263,3 +248,29 @@ func remountSys() error { } return nil } + +func setupBindmounts(rootfs string, bindMounts []libcontainer.Mount) error { + for _, m := range bindMounts { + var ( + flags = syscall.MS_BIND | syscall.MS_REC + dest = filepath.Join(rootfs, m.Destination) + ) + if !m.Writable { + flags = flags | syscall.MS_RDONLY + } + if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil { + return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err) + } + if !m.Writable { + if err := system.Mount(m.Source, dest, "bind", uintptr(flags|syscall.MS_REMOUNT), ""); err != nil { + return fmt.Errorf("remounting %s into %s %s", m.Source, dest, err) + } + } + if m.Private { + if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil { + return fmt.Errorf("mounting %s private %s", dest, err) + } + } + } + return nil +} diff --git a/components/engine/pkg/libcontainer/security/restrict/restrict.go b/components/engine/pkg/libcontainer/security/restrict/restrict.go new file mode 100644 index 0000000000..d5c1dbbe26 --- /dev/null +++ b/components/engine/pkg/libcontainer/security/restrict/restrict.go @@ -0,0 +1,46 @@ +package restrict + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "path/filepath" + "syscall" +) + +const flags = syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY + +var restrictions = map[string]string{ + // dirs + "/proc/sys": "", + "/proc/irq": "", + "/proc/acpi": "", + + // files + "/proc/sysrq-trigger": "/dev/null", + "/proc/kcore": "/dev/null", +} + +// Restrict locks down access to many areas of proc +// by using the asumption that the user does not have mount caps to +// revert the changes made here +func Restrict(rootfs, empty string) error { + for dest, source := range restrictions { + dest = filepath.Join(rootfs, dest) + + // we don't have a "/dev/null" for dirs so have the requester pass a dir + // for us to bind mount + switch source { + case "": + source = empty + default: + source = filepath.Join(rootfs, source) + } + if err := system.Mount(source, dest, "bind", flags, ""); err != nil { + return fmt.Errorf("unable to mount %s over %s %s", source, dest, err) + } + if err := system.Mount("", dest, "bind", flags|syscall.MS_REMOUNT, ""); err != nil { + return fmt.Errorf("unable to mount %s over %s %s", source, dest, err) + } + } + return nil +} From bdab73285cff7e90ae30f1ed2d1654af246839ad Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Apr 2014 23:27:27 +0000 Subject: [PATCH 07/37] Add lxc support for restricting proc Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 0779a8c3287fbf7ff1938df10897b551b839cbee Component: engine --- .../engine/daemon/execdriver/lxc/driver.go | 34 ++++++++++++------- .../daemon/execdriver/lxc/lxc_template.go | 9 ++++- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/components/engine/daemon/execdriver/lxc/driver.go b/components/engine/daemon/execdriver/lxc/driver.go index 1ebb73e807..1232d608a3 100644 --- a/components/engine/daemon/execdriver/lxc/driver.go +++ b/components/engine/daemon/execdriver/lxc/driver.go @@ -59,9 +59,10 @@ func init() { } type driver struct { - root string // root path for the driver to use - apparmor bool - sharedRoot bool + root string // root path for the driver to use + apparmor bool + sharedRoot bool + restrictionPath string } func NewDriver(root string, apparmor bool) (*driver, error) { @@ -69,10 +70,15 @@ func NewDriver(root string, apparmor bool) (*driver, error) { if err := linkLxcStart(root); err != nil { return nil, err } + restrictionPath := filepath.Join(root, "empty") + if err := os.MkdirAll(restrictionPath, 0700); err != nil { + return nil, err + } return &driver{ - apparmor: apparmor, - root: root, - sharedRoot: rootIsShared(), + apparmor: apparmor, + root: root, + sharedRoot: rootIsShared(), + restrictionPath: restrictionPath, }, nil } @@ -403,14 +409,16 @@ func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) { if err := LxcTemplateCompiled.Execute(fo, struct { *execdriver.Command - AppArmor bool - ProcessLabel string - MountLabel string + AppArmor bool + ProcessLabel string + MountLabel string + RestrictionSource string }{ - Command: c, - AppArmor: d.apparmor, - ProcessLabel: process, - MountLabel: mount, + Command: c, + AppArmor: d.apparmor, + ProcessLabel: process, + MountLabel: mount, + RestrictionSource: d.restrictionPath, }); err != nil { return "", err } diff --git a/components/engine/daemon/execdriver/lxc/lxc_template.go b/components/engine/daemon/execdriver/lxc/lxc_template.go index f4cb3d19eb..25c227ef15 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_template.go +++ b/components/engine/daemon/execdriver/lxc/lxc_template.go @@ -109,8 +109,15 @@ lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabS {{if .AppArmor}} lxc.aa_profile = unconfined {{else}} -#lxc.aa_profile = unconfined +# not unconfined {{end}} +{{else}} +# restrict access to proc +lxc.mount.entry = {{.RestrictionSource}} {{escapeFstabSpaces $ROOTFS}}/proc/sys none bind,ro 0 0 +lxc.mount.entry = {{.RestrictionSource}} {{escapeFstabSpaces $ROOTFS}}/proc/irq none bind,ro 0 0 +lxc.mount.entry = {{.RestrictionSource}} {{escapeFstabSpaces $ROOTFS}}/proc/acpi none bind,ro 0 0 +lxc.mount.entry = {{escapeFstabSpaces $ROOTFS}}/dev/null {{escapeFstabSpaces $ROOTFS}}/proc/sysrq-trigger none bind,ro 0 0 +lxc.mount.entry = {{escapeFstabSpaces $ROOTFS}}/dev/null {{escapeFstabSpaces $ROOTFS}}/proc/kcore none bind,ro 0 0 {{end}} # limits From 20ba5d97daae7349f523952af421d97ba7342840 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 11:45:39 +0000 Subject: [PATCH 08/37] No not mount sysfs by default for non privilged containers Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 81e5026a6afb282589704fd5f6bcac9ed50108ea Component: engine --- .../daemon/execdriver/lxc/lxc_template.go | 2 + .../engine/daemon/execdriver/native/create.go | 11 +- .../integration-cli/docker_cli_run_test.go | 275 +----------------- .../engine/pkg/libcontainer/container.go | 27 +- .../engine/pkg/libcontainer/nsinit/mount.go | 42 ++- 5 files changed, 67 insertions(+), 290 deletions(-) diff --git a/components/engine/daemon/execdriver/lxc/lxc_template.go b/components/engine/daemon/execdriver/lxc/lxc_template.go index 25c227ef15..bc94e7a19d 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_template.go +++ b/components/engine/daemon/execdriver/lxc/lxc_template.go @@ -88,7 +88,9 @@ lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noex # WARNING: sysfs is a known attack vector and should probably be disabled # if your userspace allows it. eg. see http://bit.ly/T9CkqJ +{{if .Privileged}} lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0 +{{end}} {{if .Tty}} lxc.mount.entry = {{.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bind,rw 0 0 diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index 1edbd17ad3..e26ff8d2b8 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/daemon/execdriver/native/create.go @@ -82,6 +82,9 @@ func (d *driver) setPrivileged(container *libcontainer.Container) error { c.Enabled = true } container.Cgroups.DeviceAccess = true + + // add sysfs as a mount for privileged containers + container.Mounts = append(container.Mounts, libcontainer.Mount{Type: "sysfs"}) delete(container.Context, "restriction_path") if apparmor.IsEnabled() { @@ -101,7 +104,13 @@ func (d *driver) setupCgroups(container *libcontainer.Container, c *execdriver.C func (d *driver) setupMounts(container *libcontainer.Container, c *execdriver.Command) error { for _, m := range c.Mounts { - container.Mounts = append(container.Mounts, libcontainer.Mount{m.Source, m.Destination, m.Writable, m.Private}) + container.Mounts = append(container.Mounts, libcontainer.Mount{ + Type: "bind", + Source: m.Source, + Destination: m.Destination, + Writable: m.Writable, + Private: m.Private, + }) } return nil } diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index d356f5f4de..40781294ae 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -389,279 +389,24 @@ func TestMultipleVolumesFrom(t *testing.T) { logDone("run - multiple volumes from") } -// this tests verifies the ID format for the container -func TestVerifyContainerID(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") - out, exit, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err) - } - if exit != 0 { - t.Fatalf("expected exit code 0 received %d", exit) - } - match, err := regexp.MatchString("^[0-9a-f]{64}$", strings.TrimSuffix(out, "\n")) - if err != nil { - t.Fatal(err) - } - if !match { - t.Fatalf("Invalid container ID: %s", out) +func TestSysNotAvaliableInNonPrivilegedContainers(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "ls", "/sys/kernel") + if code, err := runCommand(cmd); err == nil || code == 0 { + t.Fatal("sys should not be available in a non privileged container") } deleteAllContainers() - logDone("run - verify container ID") + logDone("run - sys not avaliable in non privileged container") } -// Test that creating a container with a volume doesn't crash. Regression test for #995. -func TestCreateVolume(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-v", "/var/lib/data", "busybox", "true") - if _, err := runCommand(cmd); err != nil { - t.Fatal(err) +func TestSysAvaliableInPrivilegedContainers(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "ls", "/sys/kernel") + if code, err := runCommand(cmd); err != nil || code != 0 { + t.Fatalf("sys should be available in privileged container") } deleteAllContainers() - logDone("run - create docker mangaed volume") -} - -func TestExitCode(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "/bin/sh", "-c", "exit 72") - - exit, err := runCommand(cmd) - if err == nil { - t.Fatal("should not have a non nil error") - } - if exit != 72 { - t.Fatalf("expected exit code 72 received %d", exit) - } - - deleteAllContainers() - - logDone("run - correct exit code") -} - -func TestUserDefaultsToRoot(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "id") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - if !strings.Contains(out, "uid=0(root) gid=0(root)") { - t.Fatalf("expected root user got %s", out) - } - deleteAllContainers() - - logDone("run - default user") -} - -func TestUserByName(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-u", "root", "busybox", "id") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - if !strings.Contains(out, "uid=0(root) gid=0(root)") { - t.Fatalf("expected root user got %s", out) - } - deleteAllContainers() - - logDone("run - user by name") -} - -func TestUserByID(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-u", "1", "busybox", "id") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { - t.Fatalf("expected daemon user got %s", out) - } - deleteAllContainers() - - logDone("run - user by id") -} - -func TestUserNotFound(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-u", "notme", "busybox", "id") - - _, err := runCommand(cmd) - if err == nil { - t.Fatal("unknown user should cause container to fail") - } - deleteAllContainers() - - logDone("run - user not found") -} - -func TestRunTwoConcurrentContainers(t *testing.T) { - group := sync.WaitGroup{} - group.Add(2) - - for i := 0; i < 2; i++ { - go func() { - defer group.Done() - cmd := exec.Command(dockerBinary, "run", "busybox", "sleep", "2") - if _, err := runCommand(cmd); err != nil { - t.Fatal(err) - } - }() - } - - group.Wait() - - deleteAllContainers() - - logDone("run - two concurrent containers") -} - -func TestEnvironment(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-h", "testing", "-e=FALSE=true", "-e=TRUE", "-e=TRICKY", "busybox", "env") - cmd.Env = append(os.Environ(), - "TRUE=false", - "TRICKY=tri\ncky\n", - ) - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - - actualEnv := strings.Split(out, "\n") - if actualEnv[len(actualEnv)-1] == "" { - actualEnv = actualEnv[:len(actualEnv)-1] - } - sort.Strings(actualEnv) - - goodEnv := []string{ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "HOME=/", - "HOSTNAME=testing", - "FALSE=true", - "TRUE=false", - "TRICKY=tri", - "cky", - "", - } - sort.Strings(goodEnv) - if len(goodEnv) != len(actualEnv) { - t.Fatalf("Wrong environment: should be %d variables, not: '%s'\n", len(goodEnv), strings.Join(actualEnv, ", ")) - } - for i := range goodEnv { - if actualEnv[i] != goodEnv[i] { - t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) - } - } - - deleteAllContainers() - - logDone("run - verify environment") -} - -func TestContainerNetwork(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "ping", "-c", "1", "127.0.0.1") - if _, err := runCommand(cmd); err != nil { - t.Fatal(err) - } - - deleteAllContainers() - - logDone("run - test container network via ping") -} - -// Issue #4681 -func TestLoopbackWhenNetworkDisabled(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ping", "-c", "1", "127.0.0.1") - if _, err := runCommand(cmd); err != nil { - t.Fatal(err) - } - - deleteAllContainers() - - logDone("run - test container loopback when networking disabled") -} - -func TestLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ip", "a", "show", "up") - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - - interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(out, -1) - if len(interfaces) != 1 { - t.Fatalf("Wrong interface count in test container: expected [*: lo], got %s", interfaces) - } - if !strings.HasSuffix(interfaces[0], ": lo") { - t.Fatalf("Wrong interface in test container: expected [*: lo], got %s", interfaces) - } - - deleteAllContainers() - - logDone("run - test loopback only exists when networking disabled") -} - -func TestPrivilegedCanMknod(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err) - } - - if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) - } - deleteAllContainers() - - logDone("run - test privileged can mknod") -} - -func TestUnPrivilegedCanMknod(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err) - } - - if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) - } - deleteAllContainers() - - logDone("run - test un-privileged can mknod") -} - -func TestPrivilegedCanMount(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err) - } - - if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) - } - deleteAllContainers() - - logDone("run - test privileged can mount") -} - -func TestUnPrivilegedCannotMount(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") - - out, _, err := runCommandWithOutput(cmd) - if err == nil { - t.Fatal(err, out) - } - - if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) - } - deleteAllContainers() - - logDone("run - test un-privileged cannot mount") + logDone("run - sys avaliable in privileged container") } diff --git a/components/engine/pkg/libcontainer/container.go b/components/engine/pkg/libcontainer/container.go index c7cac35428..1e032c0642 100644 --- a/components/engine/pkg/libcontainer/container.go +++ b/components/engine/pkg/libcontainer/container.go @@ -23,7 +23,7 @@ type Container struct { Networks []*Network `json:"networks,omitempty"` // nil for host's network stack Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` // cgroups Context Context `json:"context,omitempty"` // generic context for specific options (apparmor, selinux) - Mounts []Mount `json:"mounts,omitempty"` + Mounts Mounts `json:"mounts,omitempty"` } // Network defines configuration for a container's networking stack @@ -38,11 +38,22 @@ type Network struct { Mtu int `json:"mtu,omitempty"` } -// Bind mounts from the host system to the container -// -type Mount struct { - Source string `json:"source"` // Source path, in the host namespace - Destination string `json:"destination"` // Destination path, in the container - Writable bool `json:"writable"` - Private bool `json:"private"` +type Mounts []Mount + +func (s Mounts) OfType(t string) Mounts { + out := Mounts{} + for _, m := range s { + if m.Type == t { + out = append(out, m) + } + } + return out +} + +type Mount struct { + Type string `json:"type,omitempty"` + Source string `json:"source,omitempty"` // Source path, in the host namespace + Destination string `json:"destination,omitempty"` // Destination path, in the container + Writable bool `json:"writable,omitempty"` + Private bool `json:"private,omitempty"` } diff --git a/components/engine/pkg/libcontainer/nsinit/mount.go b/components/engine/pkg/libcontainer/nsinit/mount.go index e4869a0ecb..ee480328c0 100644 --- a/components/engine/pkg/libcontainer/nsinit/mount.go +++ b/components/engine/pkg/libcontainer/nsinit/mount.go @@ -17,6 +17,14 @@ import ( // default mount point flags const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV +type mount struct { + source string + path string + device string + flags int + data string +} + // setupNewMountNamespace is used to initialize a new mount namespace for an new // container in the rootfs that is specified. // @@ -33,7 +41,7 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mouting %s as bind %s", rootfs, err) } - if err := mountSystem(rootfs, container.Context["mount_label"]); err != nil { + if err := mountSystem(rootfs, container); err != nil { return fmt.Errorf("mount system %s", err) } if err := setupBindmounts(rootfs, container.Mounts); err != nil { @@ -183,19 +191,8 @@ func setupConsole(rootfs, console string, mountLabel string) error { // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace -func mountSystem(rootfs string, mountLabel string) error { - for _, m := range []struct { - source string - path string - device string - flags int - data string - }{ - {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, - {source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}, - {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, - {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, - } { +func mountSystem(rootfs string, container *libcontainer.Container) error { + for _, m := range newSystemMounts(rootfs, container.Context["mount_label"], container.Mounts) { if err := os.MkdirAll(m.path, 0755); err != nil && !os.IsExist(err) { return fmt.Errorf("mkdirall %s %s", m.path, err) } @@ -249,8 +246,8 @@ func remountSys() error { return nil } -func setupBindmounts(rootfs string, bindMounts []libcontainer.Mount) error { - for _, m := range bindMounts { +func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { + for _, m := range bindMounts.OfType("bind") { var ( flags = syscall.MS_BIND | syscall.MS_REC dest = filepath.Join(rootfs, m.Destination) @@ -274,3 +271,16 @@ func setupBindmounts(rootfs string, bindMounts []libcontainer.Mount) error { } return nil } + +func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount { + systemMounts := []mount{ + {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, + {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, + } + + if len(mounts.OfType("sysfs")) == 1 { + systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}) + } + return systemMounts +} From a750afc31e47db097d307a9f1e9f51587f46d67c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 14:30:09 +0000 Subject: [PATCH 09/37] Mount over dev and only copy allowed nodes in Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 5ba1242bdc309352c2b0b9a1ef9e07fe835e4857 Component: engine --- .../engine/daemon/execdriver/native/create.go | 2 ++ .../engine/pkg/libcontainer/nsinit/mount.go | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index e26ff8d2b8..76d65bd15c 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/daemon/execdriver/native/create.go @@ -34,6 +34,8 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container if err := d.setPrivileged(container); err != nil { return nil, err } + } else { + container.Mounts = append(container.Mounts, libcontainer.Mount{Type: "devtmpfs"}) } if err := d.setupCgroups(container, c); err != nil { return nil, err diff --git a/components/engine/pkg/libcontainer/nsinit/mount.go b/components/engine/pkg/libcontainer/nsinit/mount.go index ee480328c0..81229ef2d3 100644 --- a/components/engine/pkg/libcontainer/nsinit/mount.go +++ b/components/engine/pkg/libcontainer/nsinit/mount.go @@ -47,14 +47,14 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont if err := setupBindmounts(rootfs, container.Mounts); err != nil { return fmt.Errorf("bind mounts %s", err) } + if err := copyDevNodes(rootfs); err != nil { + return fmt.Errorf("copy dev nodes %s", err) + } if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" { if err := restrict.Restrict(rootfs, restrictionPath); err != nil { return fmt.Errorf("restrict %s", err) } } - if err := copyDevNodes(rootfs); err != nil { - return fmt.Errorf("copy dev nodes %s", err) - } if err := setupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } @@ -273,12 +273,20 @@ func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { } func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount { - systemMounts := []mount{ - {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + devMounts := []mount{ {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, } + systemMounts := []mount{ + {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + } + + if len(mounts.OfType("devtmpfs")) == 1 { + systemMounts = append(systemMounts, mount{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: "mode=755"}) + } + systemMounts = append(systemMounts, devMounts...) + if len(mounts.OfType("sysfs")) == 1 { systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}) } From 0cfbbc14e750033b875da2ef98a9c9eb8904958b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 14:42:53 +0000 Subject: [PATCH 10/37] Move console into its own package Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: de3d51b0a824e31d7e245aed958d53f436456699 Component: engine --- .../pkg/libcontainer/console/console.go | 46 +++++++++++++++++++ .../engine/pkg/libcontainer/nsinit/mount.go | 41 ++--------------- 2 files changed, 50 insertions(+), 37 deletions(-) create mode 100644 components/engine/pkg/libcontainer/console/console.go diff --git a/components/engine/pkg/libcontainer/console/console.go b/components/engine/pkg/libcontainer/console/console.go new file mode 100644 index 0000000000..deee544184 --- /dev/null +++ b/components/engine/pkg/libcontainer/console/console.go @@ -0,0 +1,46 @@ +// +build linux + +package console + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/label" + "github.com/dotcloud/docker/pkg/system" + "os" + "path/filepath" + "syscall" +) + +// Setup initializes the proper /dev/console inside the rootfs path +func Setup(rootfs, consolePath, mountLabel string) error { + oldMask := system.Umask(0000) + defer system.Umask(oldMask) + + stat, err := os.Stat(consolePath) + if err != nil { + return fmt.Errorf("stat console %s %s", consolePath, err) + } + var ( + st = stat.Sys().(*syscall.Stat_t) + dest = filepath.Join(rootfs, "dev/console") + ) + if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("remove %s %s", dest, err) + } + if err := os.Chmod(consolePath, 0600); err != nil { + return err + } + if err := os.Chown(consolePath, 0, 0); err != nil { + return err + } + if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { + return fmt.Errorf("mknod %s %s", dest, err) + } + if err := label.SetFileLabel(consolePath, mountLabel); err != nil { + return fmt.Errorf("set file label %s %s", dest, err) + } + if err := system.Mount(consolePath, dest, "bind", syscall.MS_BIND, ""); err != nil { + return fmt.Errorf("bind %s to %s %s", consolePath, dest, err) + } + return nil +} diff --git a/components/engine/pkg/libcontainer/nsinit/mount.go b/components/engine/pkg/libcontainer/nsinit/mount.go index 81229ef2d3..c85058a9f6 100644 --- a/components/engine/pkg/libcontainer/nsinit/mount.go +++ b/components/engine/pkg/libcontainer/nsinit/mount.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/system" "io/ioutil" @@ -155,40 +156,6 @@ func copyDevNode(rootfs, node string) error { return nil } -// setupConsole ensures that the container has a proper /dev/console setup -func setupConsole(rootfs, console string, mountLabel string) error { - oldMask := system.Umask(0000) - defer system.Umask(oldMask) - - stat, err := os.Stat(console) - if err != nil { - return fmt.Errorf("stat console %s %s", console, err) - } - var ( - st = stat.Sys().(*syscall.Stat_t) - dest = filepath.Join(rootfs, "dev/console") - ) - if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("remove %s %s", dest, err) - } - if err := os.Chmod(console, 0600); err != nil { - return err - } - if err := os.Chown(console, 0, 0); err != nil { - return err - } - if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { - return fmt.Errorf("mknod %s %s", dest, err) - } - if err := label.SetFileLabel(console, mountLabel); err != nil { - return fmt.Errorf("SetFileLabel Failed %s %s", dest, err) - } - if err := system.Mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil { - return fmt.Errorf("bind %s to %s %s", console, dest, err) - } - return nil -} - // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace func mountSystem(rootfs string, container *libcontainer.Container) error { @@ -205,7 +172,7 @@ func mountSystem(rootfs string, container *libcontainer.Container) error { // setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and // finishes setting up /dev/console -func setupPtmx(rootfs, console string, mountLabel string) error { +func setupPtmx(rootfs, consolePath, mountLabel string) error { ptmx := filepath.Join(rootfs, "dev/ptmx") if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { return err @@ -213,8 +180,8 @@ func setupPtmx(rootfs, console string, mountLabel string) error { if err := os.Symlink("pts/ptmx", ptmx); err != nil { return fmt.Errorf("symlink dev ptmx %s", err) } - if console != "" { - if err := setupConsole(rootfs, console, mountLabel); err != nil { + if consolePath != "" { + if err := console.Setup(rootfs, consolePath, mountLabel); err != nil { return err } } From 0099e7d236ffa0b3c0fa0a8f14f6e6f433590c45 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:06:56 +0000 Subject: [PATCH 11/37] Refactor mounts into pkg to make changes easier Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 05b611574f85c7ff7d479e04e01ac2b57b233591 Component: engine --- .../{nsinit/mount.go => mount/init.go} | 153 +++--------------- .../pkg/libcontainer/mount/msmoveroot.go | 19 +++ .../pkg/libcontainer/mount/nodes/nodes.go | 49 ++++++ .../pkg/libcontainer/mount/pivotroot.go | 31 ++++ .../engine/pkg/libcontainer/mount/ptmx.go | 26 +++ .../engine/pkg/libcontainer/mount/readonly.go | 12 ++ .../engine/pkg/libcontainer/mount/remount.go | 31 ++++ .../engine/pkg/libcontainer/nsinit/execin.go | 5 +- .../engine/pkg/libcontainer/nsinit/init.go | 3 +- 9 files changed, 191 insertions(+), 138 deletions(-) rename components/engine/pkg/libcontainer/{nsinit/mount.go => mount/init.go} (51%) create mode 100644 components/engine/pkg/libcontainer/mount/msmoveroot.go create mode 100644 components/engine/pkg/libcontainer/mount/nodes/nodes.go create mode 100644 components/engine/pkg/libcontainer/mount/pivotroot.go create mode 100644 components/engine/pkg/libcontainer/mount/ptmx.go create mode 100644 components/engine/pkg/libcontainer/mount/readonly.go create mode 100644 components/engine/pkg/libcontainer/mount/remount.go diff --git a/components/engine/pkg/libcontainer/nsinit/mount.go b/components/engine/pkg/libcontainer/mount/init.go similarity index 51% rename from components/engine/pkg/libcontainer/nsinit/mount.go rename to components/engine/pkg/libcontainer/mount/init.go index c85058a9f6..2a5e47a4a4 100644 --- a/components/engine/pkg/libcontainer/nsinit/mount.go +++ b/components/engine/pkg/libcontainer/mount/init.go @@ -1,15 +1,14 @@ // +build linux -package nsinit +package mount import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/console" + "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/system" - "io/ioutil" "os" "path/filepath" "syscall" @@ -26,13 +25,13 @@ type mount struct { data string } -// setupNewMountNamespace is used to initialize a new mount namespace for an new -// container in the rootfs that is specified. -// -// There is no need to unmount the new mounts because as soon as the mount namespace -// is no longer in use, the mounts will be removed automatically -func setupNewMountNamespace(rootfs, console string, container *libcontainer.Container) error { - flag := syscall.MS_PRIVATE +// InitializeMountNamespace setups up the devices, mount points, and filesystems for use inside a +// new mount namepsace +func InitializeMountNamespace(rootfs, console string, container *libcontainer.Container) error { + var ( + err error + flag = syscall.MS_PRIVATE + ) if container.NoPivotRoot { flag = syscall.MS_SLAVE } @@ -48,7 +47,7 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont if err := setupBindmounts(rootfs, container.Mounts); err != nil { return fmt.Errorf("bind mounts %s", err) } - if err := copyDevNodes(rootfs); err != nil { + if err := nodes.CopyN(rootfs, nodes.DefaultNodes); err != nil { return fmt.Errorf("copy dev nodes %s", err) } if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" { @@ -56,7 +55,7 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont return fmt.Errorf("restrict %s", err) } } - if err := setupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { + if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } if err := system.Chdir(rootfs); err != nil { @@ -64,18 +63,17 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont } if container.NoPivotRoot { - if err := rootMsMove(rootfs); err != nil { - return err - } + err = MsMoveRoot(rootfs) } else { - if err := rootPivot(rootfs); err != nil { - return err - } + err = PivotRoot(rootfs) + } + if err != nil { + return err } if container.ReadonlyFs { - if err := system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { - return fmt.Errorf("mounting %s as readonly %s", rootfs, err) + if err := SetReadonly(); err != nil { + return fmt.Errorf("set readonly %s", err) } } @@ -84,78 +82,6 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont return nil } -// use a pivot root to setup the rootfs -func rootPivot(rootfs string) error { - pivotDir, err := ioutil.TempDir(rootfs, ".pivot_root") - if err != nil { - return fmt.Errorf("can't create pivot_root dir %s", pivotDir, err) - } - if err := system.Pivotroot(rootfs, pivotDir); err != nil { - return fmt.Errorf("pivot_root %s", err) - } - if err := system.Chdir("/"); err != nil { - return fmt.Errorf("chdir / %s", err) - } - // path to pivot dir now changed, update - pivotDir = filepath.Join("/", filepath.Base(pivotDir)) - if err := system.Unmount(pivotDir, syscall.MNT_DETACH); err != nil { - return fmt.Errorf("unmount pivot_root dir %s", err) - } - if err := os.Remove(pivotDir); err != nil { - return fmt.Errorf("remove pivot_root dir %s", err) - } - return nil -} - -// use MS_MOVE and chroot to setup the rootfs -func rootMsMove(rootfs string) error { - if err := system.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { - return fmt.Errorf("mount move %s into / %s", rootfs, err) - } - if err := system.Chroot("."); err != nil { - return fmt.Errorf("chroot . %s", err) - } - if err := system.Chdir("/"); err != nil { - return fmt.Errorf("chdir / %s", err) - } - return nil -} - -// copyDevNodes mknods the hosts devices so the new container has access to them -func copyDevNodes(rootfs string) error { - oldMask := system.Umask(0000) - defer system.Umask(oldMask) - - for _, node := range []string{ - "null", - "zero", - "full", - "random", - "urandom", - "tty", - } { - if err := copyDevNode(rootfs, node); err != nil { - return err - } - } - return nil -} - -func copyDevNode(rootfs, node string) error { - stat, err := os.Stat(filepath.Join("/dev", node)) - if err != nil { - return err - } - var ( - dest = filepath.Join(rootfs, "dev", node) - st = stat.Sys().(*syscall.Stat_t) - ) - if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { - return fmt.Errorf("copy %s %s", node, err) - } - return nil -} - // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace func mountSystem(rootfs string, container *libcontainer.Container) error { @@ -170,49 +96,6 @@ func mountSystem(rootfs string, container *libcontainer.Container) error { return nil } -// setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and -// finishes setting up /dev/console -func setupPtmx(rootfs, consolePath, mountLabel string) error { - ptmx := filepath.Join(rootfs, "dev/ptmx") - if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { - return err - } - if err := os.Symlink("pts/ptmx", ptmx); err != nil { - return fmt.Errorf("symlink dev ptmx %s", err) - } - if consolePath != "" { - if err := console.Setup(rootfs, consolePath, mountLabel); err != nil { - return err - } - } - return nil -} - -// remountProc is used to detach and remount the proc filesystem -// commonly needed with running a new process inside an existing container -func remountProc() error { - if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil { - return err - } - if err := system.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), ""); err != nil { - return err - } - return nil -} - -func remountSys() error { - if err := system.Unmount("/sys", syscall.MNT_DETACH); err != nil { - if err != syscall.EINVAL { - return err - } - } else { - if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaultMountFlags), ""); err != nil { - return err - } - } - return nil -} - func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { for _, m := range bindMounts.OfType("bind") { var ( diff --git a/components/engine/pkg/libcontainer/mount/msmoveroot.go b/components/engine/pkg/libcontainer/mount/msmoveroot.go new file mode 100644 index 0000000000..b336c86495 --- /dev/null +++ b/components/engine/pkg/libcontainer/mount/msmoveroot.go @@ -0,0 +1,19 @@ +// +build linux + +package mount + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "syscall" +) + +func MsMoveRoot(rootfs string) error { + if err := system.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { + return fmt.Errorf("mount move %s into / %s", rootfs, err) + } + if err := system.Chroot("."); err != nil { + return fmt.Errorf("chroot . %s", err) + } + return system.Chdir("/") +} diff --git a/components/engine/pkg/libcontainer/mount/nodes/nodes.go b/components/engine/pkg/libcontainer/mount/nodes/nodes.go new file mode 100644 index 0000000000..5022f85b0b --- /dev/null +++ b/components/engine/pkg/libcontainer/mount/nodes/nodes.go @@ -0,0 +1,49 @@ +// +build linux + +package nodes + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "os" + "path/filepath" + "syscall" +) + +// Default list of device nodes to copy +var DefaultNodes = []string{ + "null", + "zero", + "full", + "random", + "urandom", + "tty", +} + +// CopyN copies the device node from the host into the rootfs +func CopyN(rootfs string, nodesToCopy []string) error { + oldMask := system.Umask(0000) + defer system.Umask(oldMask) + + for _, node := range nodesToCopy { + if err := Copy(rootfs, node); err != nil { + return err + } + } + return nil +} + +func Copy(rootfs, node string) error { + stat, err := os.Stat(filepath.Join("/dev", node)) + if err != nil { + return err + } + var ( + dest = filepath.Join(rootfs, "dev", node) + st = stat.Sys().(*syscall.Stat_t) + ) + if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { + return fmt.Errorf("copy %s %s", node, err) + } + return nil +} diff --git a/components/engine/pkg/libcontainer/mount/pivotroot.go b/components/engine/pkg/libcontainer/mount/pivotroot.go new file mode 100644 index 0000000000..447f5904b2 --- /dev/null +++ b/components/engine/pkg/libcontainer/mount/pivotroot.go @@ -0,0 +1,31 @@ +// +build linux + +package mount + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "io/ioutil" + "os" + "path/filepath" + "syscall" +) + +func PivotRoot(rootfs string) error { + pivotDir, err := ioutil.TempDir(rootfs, ".pivot_root") + if err != nil { + return fmt.Errorf("can't create pivot_root dir %s", pivotDir, err) + } + if err := system.Pivotroot(rootfs, pivotDir); err != nil { + return fmt.Errorf("pivot_root %s", err) + } + if err := system.Chdir("/"); err != nil { + return fmt.Errorf("chdir / %s", err) + } + // path to pivot dir now changed, update + pivotDir = filepath.Join("/", filepath.Base(pivotDir)) + if err := system.Unmount(pivotDir, syscall.MNT_DETACH); err != nil { + return fmt.Errorf("unmount pivot_root dir %s", err) + } + return os.Remove(pivotDir) +} diff --git a/components/engine/pkg/libcontainer/mount/ptmx.go b/components/engine/pkg/libcontainer/mount/ptmx.go new file mode 100644 index 0000000000..f6ca534637 --- /dev/null +++ b/components/engine/pkg/libcontainer/mount/ptmx.go @@ -0,0 +1,26 @@ +// +build linux + +package mount + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer/console" + "os" + "path/filepath" +) + +func SetupPtmx(rootfs, consolePath, mountLabel string) error { + ptmx := filepath.Join(rootfs, "dev/ptmx") + if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { + return err + } + if err := os.Symlink("pts/ptmx", ptmx); err != nil { + return fmt.Errorf("symlink dev ptmx %s", err) + } + if consolePath != "" { + if err := console.Setup(rootfs, consolePath, mountLabel); err != nil { + return err + } + } + return nil +} diff --git a/components/engine/pkg/libcontainer/mount/readonly.go b/components/engine/pkg/libcontainer/mount/readonly.go new file mode 100644 index 0000000000..0658358ad6 --- /dev/null +++ b/components/engine/pkg/libcontainer/mount/readonly.go @@ -0,0 +1,12 @@ +// +build linux + +package mount + +import ( + "github.com/dotcloud/docker/pkg/system" + "syscall" +) + +func SetReadonly() error { + return system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, "") +} diff --git a/components/engine/pkg/libcontainer/mount/remount.go b/components/engine/pkg/libcontainer/mount/remount.go new file mode 100644 index 0000000000..3e00509ae0 --- /dev/null +++ b/components/engine/pkg/libcontainer/mount/remount.go @@ -0,0 +1,31 @@ +// +build linux + +package mount + +import ( + "github.com/dotcloud/docker/pkg/system" + "syscall" +) + +func RemountProc() error { + if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil { + return err + } + if err := system.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), ""); err != nil { + return err + } + return nil +} + +func RemountSys() error { + if err := system.Unmount("/sys", syscall.MNT_DETACH); err != nil { + if err != syscall.EINVAL { + return err + } + } else { + if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaultMountFlags), ""); err != nil { + return err + } + } + return nil +} diff --git a/components/engine/pkg/libcontainer/nsinit/execin.go b/components/engine/pkg/libcontainer/nsinit/execin.go index 9017af06e9..b79881015f 100644 --- a/components/engine/pkg/libcontainer/nsinit/execin.go +++ b/components/engine/pkg/libcontainer/nsinit/execin.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/system" "os" "path/filepath" @@ -63,10 +64,10 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s if err := system.Unshare(syscall.CLONE_NEWNS); err != nil { return -1, err } - if err := remountProc(); err != nil { + if err := mount.RemountProc(); err != nil { return -1, fmt.Errorf("remount proc %s", err) } - if err := remountSys(); err != nil { + if err := mount.RemountSys(); err != nil { return -1, fmt.Errorf("remount sys %s", err) } goto dropAndExec diff --git a/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index fb3a895a78..6e6b0e5a8e 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/pkg/libcontainer/nsinit/init.go @@ -12,6 +12,7 @@ import ( "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" + "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/utils" @@ -61,7 +62,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol label.Init() ns.logger.Println("setup mount namespace") - if err := setupNewMountNamespace(rootfs, console, container); err != nil { + if err := mount.InitializeMountNamespace(rootfs, console, container); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := system.Sethostname(container.Hostname); err != nil { From c05360ff3bf6044b57bc920b4151ae0e5ff6fb14 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:15:28 +0000 Subject: [PATCH 12/37] Move rest of console functions to pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: a949d39f195e7b87288b10b0ef31843e6a3d8eb0 Component: engine --- .../pkg/libcontainer/console/console.go | 14 ++++++++ .../engine/pkg/libcontainer/nsinit/init.go | 34 +++++-------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/components/engine/pkg/libcontainer/console/console.go b/components/engine/pkg/libcontainer/console/console.go index deee544184..05cd08a92e 100644 --- a/components/engine/pkg/libcontainer/console/console.go +++ b/components/engine/pkg/libcontainer/console/console.go @@ -44,3 +44,17 @@ func Setup(rootfs, consolePath, mountLabel string) error { } return nil } + +func OpenAndDup(consolePath string) error { + slave, err := system.OpenTerminal(consolePath, syscall.O_RDWR) + if err != nil { + return fmt.Errorf("open terminal %s", err) + } + if err := system.Dup2(slave.Fd(), 0); err != nil { + return err + } + if err := system.Dup2(slave.Fd(), 1); err != nil { + return err + } + return system.Dup2(slave.Fd(), 2) +} diff --git a/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index 6e6b0e5a8e..9aac9a40eb 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/pkg/libcontainer/nsinit/init.go @@ -12,6 +12,7 @@ import ( "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" + "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" @@ -22,7 +23,7 @@ import ( // Init is the init process that first runs inside a new namespace to setup mounts, users, networking, // and other options required for the new container. -func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, console string, syncPipe *SyncPipe, args []string) error { +func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consolePath string, syncPipe *SyncPipe, args []string) error { rootfs, err := utils.ResolveRootfs(uncleanRootfs) if err != nil { return err @@ -38,20 +39,16 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol ns.logger.Println("received context from parent") syncPipe.Close() - if console != "" { - ns.logger.Printf("setting up %s as console\n", console) - slave, err := system.OpenTerminal(console, syscall.O_RDWR) - if err != nil { - return fmt.Errorf("open terminal %s", err) - } - if err := dupSlave(slave); err != nil { - return fmt.Errorf("dup2 slave %s", err) + if consolePath != "" { + ns.logger.Printf("setting up %s as console\n", consolePath) + if err := console.OpenAndDup(consolePath); err != nil { + return err } } if _, err := system.Setsid(); err != nil { return fmt.Errorf("setsid %s", err) } - if console != "" { + if consolePath != "" { if err := system.Setctty(); err != nil { return fmt.Errorf("setctty %s", err) } @@ -62,7 +59,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol label.Init() ns.logger.Println("setup mount namespace") - if err := mount.InitializeMountNamespace(rootfs, console, container); err != nil { + if err := mount.InitializeMountNamespace(rootfs, consolePath, container); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := system.Sethostname(container.Hostname); err != nil { @@ -116,21 +113,6 @@ func setupUser(container *libcontainer.Container) error { return nil } -// dupSlave dup2 the pty slave's fd into stdout and stdin and ensures that -// the slave's fd is 0, or stdin -func dupSlave(slave *os.File) error { - if err := system.Dup2(slave.Fd(), 0); err != nil { - return err - } - if err := system.Dup2(slave.Fd(), 1); err != nil { - return err - } - if err := system.Dup2(slave.Fd(), 2); err != nil { - return err - } - return nil -} - // setupVethNetwork uses the Network config if it is not nil to initialize // the new veth interface inside the container for use by changing the name to eth0 // setting the MTU and IP address along with the default gateway From 28044eef7b2349a001d2d13d792fc5136d026550 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:26:23 +0000 Subject: [PATCH 13/37] Move mounts into types.go Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 156987c118f6f4067794e09e90aabeee0002d05c Component: engine --- .../engine/pkg/libcontainer/container.go | 20 ------------------- .../engine/pkg/libcontainer/mount/init.go | 11 +++++----- components/engine/pkg/libcontainer/types.go | 20 +++++++++++++++++++ 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/components/engine/pkg/libcontainer/container.go b/components/engine/pkg/libcontainer/container.go index 1e032c0642..ddcc6cab70 100644 --- a/components/engine/pkg/libcontainer/container.go +++ b/components/engine/pkg/libcontainer/container.go @@ -37,23 +37,3 @@ type Network struct { Gateway string `json:"gateway,omitempty"` Mtu int `json:"mtu,omitempty"` } - -type Mounts []Mount - -func (s Mounts) OfType(t string) Mounts { - out := Mounts{} - for _, m := range s { - if m.Type == t { - out = append(out, m) - } - } - return out -} - -type Mount struct { - Type string `json:"type,omitempty"` - Source string `json:"source,omitempty"` // Source path, in the host namespace - Destination string `json:"destination,omitempty"` // Destination path, in the container - Writable bool `json:"writable,omitempty"` - Private bool `json:"private,omitempty"` -} diff --git a/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index 2a5e47a4a4..06b2c82f56 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/pkg/libcontainer/mount/init.go @@ -122,12 +122,9 @@ func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { return nil } +// TODO: this is crappy right now and should be cleaned up with a better way of handling system and +// standard bind mounts allowing them to be more dymanic func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount { - devMounts := []mount{ - {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, - {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, - } - systemMounts := []mount{ {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, } @@ -135,7 +132,9 @@ func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mo if len(mounts.OfType("devtmpfs")) == 1 { systemMounts = append(systemMounts, mount{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: "mode=755"}) } - systemMounts = append(systemMounts, devMounts...) + systemMounts = append(systemMounts, + mount{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, + mount{source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}) if len(mounts.OfType("sysfs")) == 1 { systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}) diff --git a/components/engine/pkg/libcontainer/types.go b/components/engine/pkg/libcontainer/types.go index d4818c3ffe..ade3c32f1d 100644 --- a/components/engine/pkg/libcontainer/types.go +++ b/components/engine/pkg/libcontainer/types.go @@ -11,6 +11,26 @@ var ( ErrUnsupported = errors.New("Unsupported method") ) +type Mounts []Mount + +func (s Mounts) OfType(t string) Mounts { + out := Mounts{} + for _, m := range s { + if m.Type == t { + out = append(out, m) + } + } + return out +} + +type Mount struct { + Type string `json:"type,omitempty"` + Source string `json:"source,omitempty"` // Source path, in the host namespace + Destination string `json:"destination,omitempty"` // Destination path, in the container + Writable bool `json:"writable,omitempty"` + Private bool `json:"private,omitempty"` +} + // namespaceList is used to convert the libcontainer types // into the names of the files located in /proc//ns/* for // each namespace From 8ab9384720a789ea15075fa0522c443cc5a91e06 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:28:56 +0000 Subject: [PATCH 14/37] Move capabilities into security pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 7a0b3610664c2269fd5932f294adae72e6e54020 Component: engine --- components/engine/pkg/libcontainer/nsinit/init.go | 2 +- .../libcontainer/{ => security}/capabilities/capabilities.go | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename components/engine/pkg/libcontainer/{ => security}/capabilities/capabilities.go (100%) diff --git a/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index 9aac9a40eb..22fdfbeeb7 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/pkg/libcontainer/nsinit/init.go @@ -11,11 +11,11 @@ import ( "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" + "github.com/dotcloud/docker/pkg/libcontainer/security/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/user" diff --git a/components/engine/pkg/libcontainer/capabilities/capabilities.go b/components/engine/pkg/libcontainer/security/capabilities/capabilities.go similarity index 100% rename from components/engine/pkg/libcontainer/capabilities/capabilities.go rename to components/engine/pkg/libcontainer/security/capabilities/capabilities.go From b3bc92caaff68c74689cabc2ea846da70fc223b9 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:31:45 +0000 Subject: [PATCH 15/37] Increment native driver version with these changes Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 2d6c3674349c09318e8d1fb3ce43dbabc15c97da Component: engine --- components/engine/daemon/execdriver/native/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/daemon/execdriver/native/driver.go b/components/engine/daemon/execdriver/native/driver.go index 31a2eb0dae..8b374d9938 100644 --- a/components/engine/daemon/execdriver/native/driver.go +++ b/components/engine/daemon/execdriver/native/driver.go @@ -23,7 +23,7 @@ import ( const ( DriverName = "native" - Version = "0.1" + Version = "0.2" BackupApparmorProfilePath = "apparmor/docker.back" // relative to docker root ) From bccf8f7f866d5d25b9936790df6a880a0810e849 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:44:11 +0000 Subject: [PATCH 16/37] Update container.json and readme Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 2d31aeb911fc94baa88f975110c5ccd45d041acb Component: engine --- components/engine/pkg/libcontainer/README.md | 193 ++++++++++++------ .../engine/pkg/libcontainer/container.json | 192 ++++++++++++----- 2 files changed, 277 insertions(+), 108 deletions(-) diff --git a/components/engine/pkg/libcontainer/README.md b/components/engine/pkg/libcontainer/README.md index d6d0fbae44..31031b26cd 100644 --- a/components/engine/pkg/libcontainer/README.md +++ b/components/engine/pkg/libcontainer/README.md @@ -16,76 +16,149 @@ process are specified in this file. The configuration is used for each process Sample `container.json` file: ```json { + "mounts" : [ + { + "type" : "devtmpfs" + } + ], + "tty" : true, + "environment" : [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm-256color" + ], "hostname" : "koye", + "cgroups" : { + "parent" : "docker", + "name" : "docker-koye" + }, + "capabilities_mask" : [ + { + "value" : 8, + "key" : "SETPCAP", + "enabled" : false + }, + { + "enabled" : false, + "value" : 16, + "key" : "SYS_MODULE" + }, + { + "value" : 17, + "key" : "SYS_RAWIO", + "enabled" : false + }, + { + "key" : "SYS_PACCT", + "value" : 20, + "enabled" : false + }, + { + "value" : 21, + "key" : "SYS_ADMIN", + "enabled" : false + }, + { + "value" : 23, + "key" : "SYS_NICE", + "enabled" : false + }, + { + "value" : 24, + "key" : "SYS_RESOURCE", + "enabled" : false + }, + { + "key" : "SYS_TIME", + "value" : 25, + "enabled" : false + }, + { + "enabled" : false, + "value" : 26, + "key" : "SYS_TTY_CONFIG" + }, + { + "key" : "AUDIT_WRITE", + "value" : 29, + "enabled" : false + }, + { + "value" : 30, + "key" : "AUDIT_CONTROL", + "enabled" : false + }, + { + "enabled" : false, + "key" : "MAC_OVERRIDE", + "value" : 32 + }, + { + "enabled" : false, + "key" : "MAC_ADMIN", + "value" : 33 + }, + { + "key" : "NET_ADMIN", + "value" : 12, + "enabled" : false + }, + { + "value" : 27, + "key" : "MKNOD", + "enabled" : true + } + ], "networks" : [ { - "gateway" : "172.17.42.1", + "mtu" : 1500, + "address" : "127.0.0.1/0", + "type" : "loopback", + "gateway" : "localhost" + }, + { + "mtu" : 1500, + "address" : "172.17.42.2/16", + "type" : "veth", "context" : { "bridge" : "docker0", "prefix" : "veth" }, - "address" : "172.17.0.2/16", - "type" : "veth", - "mtu" : 1500 - } - ], - "cgroups" : { - "parent" : "docker", - "name" : "11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620" - }, - "tty" : true, - "environment" : [ - "HOME=/", - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "HOSTNAME=11bb30683fb0", - "TERM=xterm" - ], - "capabilities_mask" : [ - "SETPCAP", - "SYS_MODULE", - "SYS_RAWIO", - "SYS_PACCT", - "SYS_ADMIN", - "SYS_NICE", - "SYS_RESOURCE", - "SYS_TIME", - "SYS_TTY_CONFIG", - "MKNOD", - "AUDIT_WRITE", - "AUDIT_CONTROL", - "MAC_OVERRIDE", - "MAC_ADMIN", - "NET_ADMIN" - ], - "context" : { - "apparmor_profile" : "docker-default" - }, - "mounts" : [ - { - "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/resolv.conf", - "writable" : false, - "destination" : "/etc/resolv.conf", - "private" : true - }, - { - "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/hostname", - "writable" : false, - "destination" : "/etc/hostname", - "private" : true - }, - { - "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/hosts", - "writable" : false, - "destination" : "/etc/hosts", - "private" : true + "gateway" : "172.17.42.1" } ], "namespaces" : [ - "NEWNS", - "NEWUTS", - "NEWIPC", - "NEWPID", - "NEWNET" + { + "key" : "NEWNS", + "value" : 131072, + "enabled" : true, + "file" : "mnt" + }, + { + "key" : "NEWUTS", + "value" : 67108864, + "enabled" : true, + "file" : "uts" + }, + { + "enabled" : true, + "file" : "ipc", + "key" : "NEWIPC", + "value" : 134217728 + }, + { + "file" : "pid", + "enabled" : true, + "value" : 536870912, + "key" : "NEWPID" + }, + { + "enabled" : true, + "file" : "net", + "key" : "NEWNET", + "value" : 1073741824 + } ] } ``` diff --git a/components/engine/pkg/libcontainer/container.json b/components/engine/pkg/libcontainer/container.json index f045315a41..f15a49ab05 100644 --- a/components/engine/pkg/libcontainer/container.json +++ b/components/engine/pkg/libcontainer/container.json @@ -1,50 +1,146 @@ { - "hostname": "koye", - "tty": true, - "environment": [ - "HOME=/", - "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", - "container=docker", - "TERM=xterm-256color" - ], - "namespaces": [ - "NEWIPC", - "NEWNS", - "NEWPID", - "NEWUTS", - "NEWNET" - ], - "capabilities_mask": [ - "SETPCAP", - "SYS_MODULE", - "SYS_RAWIO", - "SYS_PACCT", - "SYS_ADMIN", - "SYS_NICE", - "SYS_RESOURCE", - "SYS_TIME", - "SYS_TTY_CONFIG", - "MKNOD", - "AUDIT_WRITE", - "AUDIT_CONTROL", - "MAC_OVERRIDE", - "MAC_ADMIN", - "NET_ADMIN" - ], - "networks": [{ - "type": "veth", - "context": { - "bridge": "docker0", - "prefix": "dock" - }, - "address": "172.17.0.100/16", - "gateway": "172.17.42.1", - "mtu": 1500 - } - ], - "cgroups": { - "name": "docker-koye", - "parent": "docker", - "memory": 5248000 - } + "mounts" : [ + { + "type" : "devtmpfs" + } + ], + "tty" : true, + "environment" : [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm-256color" + ], + "hostname" : "koye", + "cgroups" : { + "parent" : "docker", + "name" : "docker-koye" + }, + "capabilities_mask" : [ + { + "value" : 8, + "key" : "SETPCAP", + "enabled" : false + }, + { + "enabled" : false, + "value" : 16, + "key" : "SYS_MODULE" + }, + { + "value" : 17, + "key" : "SYS_RAWIO", + "enabled" : false + }, + { + "key" : "SYS_PACCT", + "value" : 20, + "enabled" : false + }, + { + "value" : 21, + "key" : "SYS_ADMIN", + "enabled" : false + }, + { + "value" : 23, + "key" : "SYS_NICE", + "enabled" : false + }, + { + "value" : 24, + "key" : "SYS_RESOURCE", + "enabled" : false + }, + { + "key" : "SYS_TIME", + "value" : 25, + "enabled" : false + }, + { + "enabled" : false, + "value" : 26, + "key" : "SYS_TTY_CONFIG" + }, + { + "key" : "AUDIT_WRITE", + "value" : 29, + "enabled" : false + }, + { + "value" : 30, + "key" : "AUDIT_CONTROL", + "enabled" : false + }, + { + "enabled" : false, + "key" : "MAC_OVERRIDE", + "value" : 32 + }, + { + "enabled" : false, + "key" : "MAC_ADMIN", + "value" : 33 + }, + { + "key" : "NET_ADMIN", + "value" : 12, + "enabled" : false + }, + { + "value" : 27, + "key" : "MKNOD", + "enabled" : true + } + ], + "networks" : [ + { + "mtu" : 1500, + "address" : "127.0.0.1/0", + "type" : "loopback", + "gateway" : "localhost" + }, + { + "mtu" : 1500, + "address" : "172.17.42.2/16", + "type" : "veth", + "context" : { + "bridge" : "docker0", + "prefix" : "veth" + }, + "gateway" : "172.17.42.1" + } + ], + "namespaces" : [ + { + "key" : "NEWNS", + "value" : 131072, + "enabled" : true, + "file" : "mnt" + }, + { + "key" : "NEWUTS", + "value" : 67108864, + "enabled" : true, + "file" : "uts" + }, + { + "enabled" : true, + "file" : "ipc", + "key" : "NEWIPC", + "value" : 134217728 + }, + { + "file" : "pid", + "enabled" : true, + "value" : 536870912, + "key" : "NEWPID" + }, + { + "enabled" : true, + "file" : "net", + "key" : "NEWNET", + "value" : 1073741824 + } + ] } From 8bd8490f439db14a635cf329a45ca608f03adc7f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 18 Apr 2014 14:35:16 -0700 Subject: [PATCH 17/37] Update create with apparmor import Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 90678b31331de54598c7a6665c3e7a78bfe6ed63 Component: engine --- .../integration-cli/docker_cli_run_test.go | 277 ++++++++++++++++++ 1 file changed, 277 insertions(+) diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index 40781294ae..5973f2fe1b 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -389,6 +389,283 @@ func TestMultipleVolumesFrom(t *testing.T) { logDone("run - multiple volumes from") } +// this tests verifies the ID format for the container +func TestVerifyContainerID(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + out, exit, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + if exit != 0 { + t.Fatalf("expected exit code 0 received %d", exit) + } + match, err := regexp.MatchString("^[0-9a-f]{64}$", strings.TrimSuffix(out, "\n")) + if err != nil { + t.Fatal(err) + } + if !match { + t.Fatalf("Invalid container ID: %s", out) + } + + deleteAllContainers() + + logDone("run - verify container ID") +} + +// Test that creating a container with a volume doesn't crash. Regression test for #995. +func TestCreateVolume(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-v", "/var/lib/data", "busybox", "true") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - create docker mangaed volume") +} + +func TestExitCode(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "/bin/sh", "-c", "exit 72") + + exit, err := runCommand(cmd) + if err == nil { + t.Fatal("should not have a non nil error") + } + if exit != 72 { + t.Fatalf("expected exit code 72 received %d", exit) + } + + deleteAllContainers() + + logDone("run - correct exit code") +} + +func TestUserDefaultsToRoot(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "id") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=0(root) gid=0(root)") { + t.Fatalf("expected root user got %s", out) + } + deleteAllContainers() + + logDone("run - default user") +} + +func TestUserByName(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-u", "root", "busybox", "id") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=0(root) gid=0(root)") { + t.Fatalf("expected root user got %s", out) + } + deleteAllContainers() + + logDone("run - user by name") +} + +func TestUserByID(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-u", "1", "busybox", "id") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { + t.Fatalf("expected daemon user got %s", out) + } + deleteAllContainers() + + logDone("run - user by id") +} + +func TestUserNotFound(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-u", "notme", "busybox", "id") + + _, err := runCommand(cmd) + if err == nil { + t.Fatal("unknown user should cause container to fail") + } + deleteAllContainers() + + logDone("run - user not found") +} + +func TestRunTwoConcurrentContainers(t *testing.T) { + group := sync.WaitGroup{} + group.Add(2) + + for i := 0; i < 2; i++ { + go func() { + defer group.Done() + cmd := exec.Command(dockerBinary, "run", "busybox", "sleep", "2") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + }() + } + + group.Wait() + + deleteAllContainers() + + logDone("run - two concurrent containers") +} + +func TestEnvironment(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-h", "testing", "-e=FALSE=true", "-e=TRUE", "-e=TRICKY", "busybox", "env") + cmd.Env = append(os.Environ(), + "TRUE=false", + "TRICKY=tri\ncky\n", + ) + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + actualEnv := strings.Split(out, "\n") + if actualEnv[len(actualEnv)-1] == "" { + actualEnv = actualEnv[:len(actualEnv)-1] + } + sort.Strings(actualEnv) + + goodEnv := []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "HOME=/", + "HOSTNAME=testing", + "FALSE=true", + "TRUE=false", + "TRICKY=tri", + "cky", + "", + } + sort.Strings(goodEnv) + if len(goodEnv) != len(actualEnv) { + t.Fatalf("Wrong environment: should be %d variables, not: '%s'\n", len(goodEnv), strings.Join(actualEnv, ", ")) + } + for i := range goodEnv { + if actualEnv[i] != goodEnv[i] { + t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) + } + } + + deleteAllContainers() + + logDone("run - verify environment") +} + +func TestContainerNetwork(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "ping", "-c", "1", "127.0.0.1") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - test container network via ping") +} + +// Issue #4681 +func TestLoopbackWhenNetworkDisabled(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ping", "-c", "1", "127.0.0.1") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - test container loopback when networking disabled") +} + +func TestLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ip", "a", "show", "up") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(out, -1) + if len(interfaces) != 1 { + t.Fatalf("Wrong interface count in test container: expected [*: lo], got %s", interfaces) + } + if !strings.HasSuffix(interfaces[0], ": lo") { + t.Fatalf("Wrong interface in test container: expected [*: lo], got %s", interfaces) + } + + deleteAllContainers() + + logDone("run - test loopback only exists when networking disabled") +} + +func TestPrivilegedCanMknod(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + + if actual := strings.Trim(out, "\r\n"); actual != "ok" { + t.Fatalf("expected output ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test privileged can mknod") +} + +func TestUnPrivilegedCanMknod(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + + if actual := strings.Trim(out, "\r\n"); actual != "ok" { + t.Fatalf("expected output ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test un-privileged can mknod") +} + +func TestPrivilegedCanMount(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + + if actual := strings.Trim(out, "\r\n"); actual != "ok" { + t.Fatalf("expected output ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test privileged can mount") +} + +func TestUnPrivilegedCannotMount(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") + + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatal(err, out) + } + + if actual := strings.Trim(out, "\r\n"); actual == "ok" { + t.Fatalf("expected output not ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test un-privileged cannot mount") +} + func TestSysNotAvaliableInNonPrivilegedContainers(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "busybox", "ls", "/sys/kernel") if code, err := runCommand(cmd); err == nil || code == 0 { From 72d2138bac2a6964c0e542ee1a0ee9b7c2e90a27 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 21 Apr 2014 12:07:07 -0700 Subject: [PATCH 18/37] Update init for new apparmor import path Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: fa5cabf9fe9e257d64638043ca2fd08a7bf96cb3 Component: engine --- components/engine/pkg/libcontainer/nsinit/init.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index 22fdfbeeb7..67095fdba1 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/pkg/libcontainer/nsinit/init.go @@ -14,7 +14,6 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" - "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/security/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" From 4269c4b6a65200121fe25fb26f50699399a976b6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 23 Apr 2014 18:12:07 -0700 Subject: [PATCH 19/37] Ignore isnot exists errors for proc paths Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: d5c9f61ecc1c8167322a8cc3b41f29a35c80b9b8 Component: engine --- .../engine/pkg/libcontainer/security/restrict/restrict.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/engine/pkg/libcontainer/security/restrict/restrict.go b/components/engine/pkg/libcontainer/security/restrict/restrict.go index d5c1dbbe26..291d6ca5dc 100644 --- a/components/engine/pkg/libcontainer/security/restrict/restrict.go +++ b/components/engine/pkg/libcontainer/security/restrict/restrict.go @@ -2,9 +2,11 @@ package restrict import ( "fmt" - "github.com/dotcloud/docker/pkg/system" + "os" "path/filepath" "syscall" + + "github.com/dotcloud/docker/pkg/system" ) const flags = syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY @@ -36,6 +38,9 @@ func Restrict(rootfs, empty string) error { source = filepath.Join(rootfs, source) } if err := system.Mount(source, dest, "bind", flags, ""); err != nil { + if os.IsNotExist(err) { + continue + } return fmt.Errorf("unable to mount %s over %s %s", source, dest, err) } if err := system.Mount("", dest, "bind", flags|syscall.MS_REMOUNT, ""); err != nil { From 6e7ac095e3f92cd7359e13282909b5ddcbefc5f1 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Thu, 24 Apr 2014 22:59:37 +0000 Subject: [PATCH 20/37] Add memory usage and max usage stats. Docker-DCO-1.1-Signed-off-by: Victor Marmol (github: vmarmol) Upstream-commit: ad924959a9879af7477fa23c19d570882c1f378f Component: engine --- components/engine/pkg/cgroups/fs/memory.go | 22 +++++- components/engine/pkg/cgroups/fs/utils.go | 11 +++ .../engine/pkg/cgroups/fs/utils_test.go | 68 +++++++++++++++++++ 3 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 components/engine/pkg/cgroups/fs/utils_test.go diff --git a/components/engine/pkg/cgroups/fs/memory.go b/components/engine/pkg/cgroups/fs/memory.go index 5315291197..837640c088 100644 --- a/components/engine/pkg/cgroups/fs/memory.go +++ b/components/engine/pkg/cgroups/fs/memory.go @@ -2,6 +2,7 @@ package fs import ( "bufio" + "fmt" "os" "path/filepath" "strconv" @@ -56,13 +57,14 @@ func (s *memoryGroup) Stats(d *data) (map[string]float64, error) { return nil, err } - f, err := os.Open(filepath.Join(path, "memory.stat")) + // Set stats from memory.stat. + statsFile, err := os.Open(filepath.Join(path, "memory.stat")) if err != nil { return nil, err } - defer f.Close() + defer statsFile.Close() - sc := bufio.NewScanner(f) + sc := bufio.NewScanner(statsFile) for sc.Scan() { t, v, err := getCgroupParamKeyValue(sc.Text()) if err != nil { @@ -70,5 +72,19 @@ func (s *memoryGroup) Stats(d *data) (map[string]float64, error) { } paramData[t] = v } + + // Set memory usage and max historical usage. + params := []string{ + "usage_in_bytes", + "max_usage_in_bytes", + } + for _, param := range params { + value, err := getCgroupParamFloat64(path, fmt.Sprintf("memory.%s", param)) + if err != nil { + return nil, err + } + paramData[param] = value + } + return paramData, nil } diff --git a/components/engine/pkg/cgroups/fs/utils.go b/components/engine/pkg/cgroups/fs/utils.go index f4c4846b8c..8be65c97ea 100644 --- a/components/engine/pkg/cgroups/fs/utils.go +++ b/components/engine/pkg/cgroups/fs/utils.go @@ -3,6 +3,8 @@ package fs import ( "errors" "fmt" + "io/ioutil" + "path/filepath" "strconv" "strings" ) @@ -27,3 +29,12 @@ func getCgroupParamKeyValue(t string) (string, float64, error) { return "", 0.0, ErrNotValidFormat } } + +// Gets a single float64 value from the specified cgroup file. +func getCgroupParamFloat64(cgroupPath, cgroupFile string) (float64, error) { + contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile)) + if err != nil { + return -1.0, err + } + return strconv.ParseFloat(strings.TrimSpace(string(contents)), 64) +} diff --git a/components/engine/pkg/cgroups/fs/utils_test.go b/components/engine/pkg/cgroups/fs/utils_test.go new file mode 100644 index 0000000000..c8f1b0172b --- /dev/null +++ b/components/engine/pkg/cgroups/fs/utils_test.go @@ -0,0 +1,68 @@ +package fs + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +const ( + cgroupFile = "cgroup.file" + floatValue = 2048.0 + floatString = "2048" +) + +func TestGetCgroupParamsFloat64(t *testing.T) { + // Setup tempdir. + tempDir, err := ioutil.TempDir("", "cgroup_utils_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + tempFile := filepath.Join(tempDir, cgroupFile) + + // Success. + err = ioutil.WriteFile(tempFile, []byte(floatString), 0755) + if err != nil { + t.Fatal(err) + } + value, err := getCgroupParamFloat64(tempDir, cgroupFile) + if err != nil { + t.Fatal(err) + } else if value != floatValue { + t.Fatalf("Expected %f to equal %f", value, floatValue) + } + + // Success with new line. + err = ioutil.WriteFile(tempFile, []byte(floatString+"\n"), 0755) + if err != nil { + t.Fatal(err) + } + value, err = getCgroupParamFloat64(tempDir, cgroupFile) + if err != nil { + t.Fatal(err) + } else if value != floatValue { + t.Fatalf("Expected %f to equal %f", value, floatValue) + } + + // Not a float. + err = ioutil.WriteFile(tempFile, []byte("not-a-float"), 0755) + if err != nil { + t.Fatal(err) + } + _, err = getCgroupParamFloat64(tempDir, cgroupFile) + if err == nil { + t.Fatal("Expecting error, got none") + } + + // Unknown file. + err = os.Remove(tempFile) + if err != nil { + t.Fatal(err) + } + _, err = getCgroupParamFloat64(tempDir, cgroupFile) + if err == nil { + t.Fatal("Expecting error, got none") + } +} From 72e7e8f048e98e0d6994961636e2e0236e793135 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 25 Apr 2014 20:36:31 +1000 Subject: [PATCH 21/37] small api doc formatting fixup Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) Upstream-commit: c7bd1f4e648c6615eafebbfcaeb1c30020fc0aba Component: engine --- .../reference/api/docker_remote_api.md | 144 +++++++++--------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/components/engine/docs/sources/reference/api/docker_remote_api.md b/components/engine/docs/sources/reference/api/docker_remote_api.md index 3c58b1b990..a6aafbeee8 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api.md +++ b/components/engine/docs/sources/reference/api/docker_remote_api.md @@ -127,83 +127,83 @@ 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: +Instead of: - HTTP/1.1 200 OK - Content-Type: application/json + 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" - } - ] + [ + { + "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: +The returned json looks like this: - HTTP/1.1 200 OK - Content-Type: application/json + HTTP/1.1 200 OK + Content-Type: application/json - [ - { - "RepoTags": [ - "ubuntu:12.04", - "ubuntu:precise", - "ubuntu:latest" - ], - "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", - "Created": 1365714795, - "Size": 131506275, - "VirtualSize": 131506275 - }, - { - "RepoTags": [ - "ubuntu:12.10", - "ubuntu:quantal" - ], - "ParentId": "27cf784147099545", - "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "Created": 1364102658, - "Size": 24653, - "VirtualSize": 180116135 - } - ] + [ + { + "RepoTags": [ + "ubuntu:12.04", + "ubuntu:precise", + "ubuntu:latest" + ], + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Created": 1365714795, + "Size": 131506275, + "VirtualSize": 131506275 + }, + { + "RepoTags": [ + "ubuntu:12.10", + "ubuntu:quantal" + ], + "ParentId": "27cf784147099545", + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Created": 1364102658, + "Size": 24653, + "VirtualSize": 180116135 + } + ] `GET /images/viz` From 8fdb34e01b32b3d80189bc6b2a35fe1aa70215d5 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 22 Apr 2014 16:51:06 -0700 Subject: [PATCH 22/37] engine.Installer: a standard interface for "installable" services Installer is a standard interface for objects which can "install" themselves an engine by registering handlers. This can be used as an entrypoint for external plugins etc. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) Upstream-commit: 9422451ac3f541a17daba0d5f6dc8b40a6edc9e9 Component: engine --- components/engine/engine/engine.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/engine/engine/engine.go b/components/engine/engine/engine.go index 58c37ab933..aaf5c1f595 100644 --- a/components/engine/engine/engine.go +++ b/components/engine/engine/engine.go @@ -10,6 +10,13 @@ import ( "strings" ) +// Installer is a standard interface for objects which can "install" themselves +// on an engine by registering handlers. +// This can be used as an entrypoint for external plugins etc. +type Installer interface { + Install(*Engine) error +} + type Handler func(*Job) Status var globalHandlers map[string]Handler From 35fee9d895a96ad095d9c0a1fa1d4ec2bbccbb12 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 24 Apr 2014 00:36:21 -0700 Subject: [PATCH 23/37] engine: allow registering a "catchall" handler which receives all commands Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) Upstream-commit: 68d3e757503fab422fc96a00d511336a3fdfd619 Component: engine --- components/engine/engine/engine.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/components/engine/engine/engine.go b/components/engine/engine/engine.go index aaf5c1f595..dc1984ccb5 100644 --- a/components/engine/engine/engine.go +++ b/components/engine/engine/engine.go @@ -43,6 +43,7 @@ func unregister(name string) { // containers by executing *jobs*. type Engine struct { handlers map[string]Handler + catchall Handler hack Hack // data for temporary hackery (see hack.go) id string Stdout io.Writer @@ -60,6 +61,10 @@ func (eng *Engine) Register(name string, handler Handler) error { return nil } +func (eng *Engine) RegisterCatchall(catchall Handler) { + eng.catchall = catchall +} + // New initializes a new engine. func New() *Engine { eng := &Engine{ @@ -113,9 +118,13 @@ func (eng *Engine) Job(name string, args ...string) *Job { if eng.Logging { job.Stderr.Add(utils.NopWriteCloser(eng.Stderr)) } - handler, exists := eng.handlers[name] - if exists { - job.handler = handler + if eng.catchall != nil { + job.handler = eng.catchall + } else { + handler, exists := eng.handlers[name] + if exists { + job.handler = handler + } } return job } From 2e9c0c6ca2203bd118ab3b650b2f5470b213917e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 24 Apr 2014 00:46:32 -0700 Subject: [PATCH 24/37] Remote communication between engines using beam Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) Upstream-commit: 3c1d5ca33ecbd644d4e8d864ff59b389f4a4a555 Component: engine --- components/engine/engine/remote.go | 109 ++++++++++++++++++++++++ components/engine/engine/remote_test.go | 3 + 2 files changed, 112 insertions(+) create mode 100644 components/engine/engine/remote.go create mode 100644 components/engine/engine/remote_test.go diff --git a/components/engine/engine/remote.go b/components/engine/engine/remote.go new file mode 100644 index 0000000000..1e8777a4b7 --- /dev/null +++ b/components/engine/engine/remote.go @@ -0,0 +1,109 @@ +package engine + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/beam" + "github.com/dotcloud/docker/pkg/beam/data" + "io" + "os" + "strconv" + "sync" +) + +type Sender struct { + beam.Sender +} + +func NewSender(s beam.Sender) *Sender { + return &Sender{s} +} + +func (s *Sender) Install(eng *Engine) error { + // FIXME: this doesn't exist yet. + eng.RegisterCatchall(s.Handle) + return nil +} + +func (s *Sender) Handle(job *Job) Status { + msg := data.Empty().Set("cmd", append([]string{job.Name}, job.Args...)...) + peer, err := beam.SendConn(s, msg.Bytes()) + if err != nil { + return job.Errorf("beamsend: %v", err) + } + defer peer.Close() + var tasks sync.WaitGroup + defer tasks.Wait() + r := beam.NewRouter(nil) + r.NewRoute().KeyStartsWith("cmd", "log", "stdout").HasAttachment().Handler(func(p []byte, stdout *os.File) error { + tasks.Add(1) + io.Copy(job.Stdout, stdout) + tasks.Done() + return nil + }) + r.NewRoute().KeyStartsWith("cmd", "log", "stderr").HasAttachment().Handler(func(p []byte, stderr *os.File) error { + tasks.Add(1) + io.Copy(job.Stderr, stderr) + tasks.Done() + return nil + }) + var status int + r.NewRoute().KeyStartsWith("cmd", "status").Handler(func(p []byte, f *os.File) error { + cmd := data.Message(p).Get("cmd") + if len(cmd) != 3 { + return fmt.Errorf("usage: %s <0-127>", cmd[0]) + } + s, err := strconv.ParseUint(cmd[2], 10, 8) + if err != nil { + return fmt.Errorf("usage: %s <0-127>", cmd[0]) + } + status = int(s) + return nil + + }) + if _, err := beam.Copy(r, peer); err != nil { + return job.Errorf("%v", err) + } + return Status(status) +} + +type Receiver struct { + *Engine + peer beam.Receiver +} + +func NewReceiver(peer beam.Receiver) *Receiver { + return &Receiver{Engine: New(), peer: peer} +} + +func (rcv *Receiver) Run() error { + r := beam.NewRouter(nil) + r.NewRoute().KeyExists("cmd").Handler(func(p []byte, f *os.File) error { + // Use the attachment as a beam return channel + peer, err := beam.FileConn(f) + if err != nil { + f.Close() + return err + } + cmd := data.Message(p).Get("cmd") + job := rcv.Engine.Job(cmd[0], cmd[1:]...) + stdout, err := beam.SendPipe(peer, data.Empty().Set("cmd", "log", "stdout").Bytes()) + if err != nil { + return err + } + job.Stdout.Add(stdout) + stderr, err := beam.SendPipe(peer, data.Empty().Set("cmd", "log", "stderr").Bytes()) + if err != nil { + return err + } + job.Stderr.Add(stderr) + // ignore error because we pass the raw status + job.Run() + err = peer.Send(data.Empty().Set("cmd", "status", fmt.Sprintf("%d", job.status)).Bytes(), nil) + if err != nil { + return err + } + return nil + }) + _, err := beam.Copy(r, rcv.peer) + return err +} diff --git a/components/engine/engine/remote_test.go b/components/engine/engine/remote_test.go new file mode 100644 index 0000000000..54092ec934 --- /dev/null +++ b/components/engine/engine/remote_test.go @@ -0,0 +1,3 @@ +package engine + +import () From bceccfa5cfe56134ce9a2a1a807bbbe04bb187f9 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 25 Apr 2014 16:47:03 -0700 Subject: [PATCH 25/37] engine.Sender and engine.Receiver support stdin Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) Upstream-commit: b63b98ee2766321e2ca6f3b159c2bfb303870105 Component: engine --- components/engine/engine/remote.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/engine/engine/remote.go b/components/engine/engine/remote.go index 1e8777a4b7..48638e4383 100644 --- a/components/engine/engine/remote.go +++ b/components/engine/engine/remote.go @@ -46,6 +46,12 @@ func (s *Sender) Handle(job *Job) Status { tasks.Done() return nil }) + r.NewRoute().KeyStartsWith("cmd", "log", "stdin").HasAttachment().Handler(func(p []byte, stdin *os.File) error { + tasks.Add(1) + io.Copy(stdin, job.Stdin) + tasks.Done() + return nil + }) var status int r.NewRoute().KeyStartsWith("cmd", "status").Handler(func(p []byte, f *os.File) error { cmd := data.Message(p).Get("cmd") @@ -96,6 +102,11 @@ func (rcv *Receiver) Run() error { return err } job.Stderr.Add(stderr) + stdin, err := beam.SendPipe(peer, data.Empty().Set("cmd", "log", "stdin").Bytes()) + if err != nil { + return err + } + job.Stdin.Add(stdin) // ignore error because we pass the raw status job.Run() err = peer.Send(data.Empty().Set("cmd", "status", fmt.Sprintf("%d", job.status)).Bytes(), nil) From e77e02d78a4e13816e8c4e3875ce7f8e51a9c123 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 25 Apr 2014 16:48:16 -0700 Subject: [PATCH 26/37] engine: 'rengine' is a small command-line utility to debug remote engine Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) Upstream-commit: 7e3624a498b8b96a4e8a0f1d59fc2c50bf48efb3 Component: engine --- components/engine/engine/rengine/main.go | 43 ++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 components/engine/engine/rengine/main.go diff --git a/components/engine/engine/rengine/main.go b/components/engine/engine/rengine/main.go new file mode 100644 index 0000000000..b4fa01d39c --- /dev/null +++ b/components/engine/engine/rengine/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/pkg/beam" + "net" + "os" +) + +func main() { + eng := engine.New() + + c, err := net.Dial("unix", "beam.sock") + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + defer c.Close() + f, err := c.(*net.UnixConn).File() + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + + child, err := beam.FileConn(f) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + defer child.Close() + + sender := engine.NewSender(child) + sender.Install(eng) + + cmd := eng.Job(os.Args[1], os.Args[2:]...) + cmd.Stdout.Add(os.Stdout) + cmd.Stderr.Add(os.Stderr) + if err := cmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} From 2e76f7d9a49a7f503f6b70a6ef12f24a57f2fd7f Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Fri, 25 Apr 2014 17:44:40 +0000 Subject: [PATCH 27/37] Adding a test for blkio stats. Also adds a test utility we can use for other cgroup tests. Docker-DCO-1.1-Signed-off-by: Victor Marmol (github: vmarmol) Upstream-commit: f4055ee2a467458d4848ad6f8e79fc0162d377f9 Component: engine --- .../engine/pkg/cgroups/fs/blkio_test.go | 169 ++++++++++++++++++ components/engine/pkg/cgroups/fs/test_util.go | 75 ++++++++ 2 files changed, 244 insertions(+) create mode 100644 components/engine/pkg/cgroups/fs/blkio_test.go create mode 100644 components/engine/pkg/cgroups/fs/test_util.go diff --git a/components/engine/pkg/cgroups/fs/blkio_test.go b/components/engine/pkg/cgroups/fs/blkio_test.go new file mode 100644 index 0000000000..5279ac437b --- /dev/null +++ b/components/engine/pkg/cgroups/fs/blkio_test.go @@ -0,0 +1,169 @@ +package fs + +import ( + "testing" +) + +const ( + sectorsRecursiveContents = `8:0 1024` + serviceBytesRecursiveContents = `8:0 Read 100 +8:0 Write 400 +8:0 Sync 200 +8:0 Async 300 +8:0 Total 500 +Total 500` + servicedRecursiveContents = `8:0 Read 10 +8:0 Write 40 +8:0 Sync 20 +8:0 Async 30 +8:0 Total 50 +Total 50` + queuedRecursiveContents = `8:0 Read 1 +8:0 Write 4 +8:0 Sync 2 +8:0 Async 3 +8:0 Total 5 +Total 5` +) + +func TestBlkioStats(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + stats, err := blkio.Stats(helper.CgroupData) + if err != nil { + t.Fatal(err) + } + + // Verify expected stats. + expectedStats := map[string]float64{ + "blkio.sectors_recursive:8:0": 1024.0, + + // Serviced bytes. + "io_service_bytes_recursive:8:0:Read": 100.0, + "io_service_bytes_recursive:8:0:Write": 400.0, + "io_service_bytes_recursive:8:0:Sync": 200.0, + "io_service_bytes_recursive:8:0:Async": 300.0, + "io_service_bytes_recursive:8:0:Total": 500.0, + + // Serviced requests. + "io_serviced_recursive:8:0:Read": 10.0, + "io_serviced_recursive:8:0:Write": 40.0, + "io_serviced_recursive:8:0:Sync": 20.0, + "io_serviced_recursive:8:0:Async": 30.0, + "io_serviced_recursive:8:0:Total": 50.0, + + // Queued requests. + "io_queued_recursive:8:0:Read": 1.0, + "io_queued_recursive:8:0:Write": 4.0, + "io_queued_recursive:8:0:Sync": 2.0, + "io_queued_recursive:8:0:Async": 3.0, + "io_queued_recursive:8:0:Total": 5.0, + } + expectStats(t, expectedStats, stats) +} + +func TestBlkioStatsNoSectorsFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsNoServiceBytesFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsNoServicedFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsNoQueuedFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": "8:0 Read 100 100", + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsUnexpectedFieldType(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": "8:0 Read Write", + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} diff --git a/components/engine/pkg/cgroups/fs/test_util.go b/components/engine/pkg/cgroups/fs/test_util.go new file mode 100644 index 0000000000..11b90b21d6 --- /dev/null +++ b/components/engine/pkg/cgroups/fs/test_util.go @@ -0,0 +1,75 @@ +/* +Utility for testing cgroup operations. + +Creates a mock of the cgroup filesystem for the duration of the test. +*/ +package fs + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "testing" +) + +type cgroupTestUtil struct { + // data to use in tests. + CgroupData *data + + // Path to the mock cgroup directory. + CgroupPath string + + // Temporary directory to store mock cgroup filesystem. + tempDir string + t *testing.T +} + +// Creates a new test util for the specified subsystem +func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil { + d := &data{} + tempDir, err := ioutil.TempDir("", fmt.Sprintf("%s_cgroup_test", subsystem)) + if err != nil { + t.Fatal(err) + } + d.root = tempDir + testCgroupPath, err := d.path(subsystem) + if err != nil { + t.Fatal(err) + } + + // Ensure the full mock cgroup path exists. + err = os.MkdirAll(testCgroupPath, 0755) + if err != nil { + t.Fatal(err) + } + return &cgroupTestUtil{CgroupData: d, CgroupPath: testCgroupPath, tempDir: tempDir, t: t} +} + +func (c *cgroupTestUtil) cleanup() { + os.RemoveAll(c.tempDir) +} + +// Write the specified contents on the mock of the specified cgroup files. +func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) { + for file, contents := range fileContents { + err := writeFile(c.CgroupPath, file, contents) + if err != nil { + c.t.Fatal(err) + } + } +} + +// Expect the specified stats. +func expectStats(t *testing.T, expected, actual map[string]float64) { + for stat, expectedValue := range expected { + actualValue, ok := actual[stat] + if !ok { + log.Printf("Expected stat %s to exist: %s", stat, actual) + t.Fail() + } else if actualValue != expectedValue { + log.Printf("Expected stats %s to have value %f but had %f instead", stat, expectedValue, actualValue) + t.Fail() + } + } +} From 7eb9afe4a1bc4008d8acc86790a6bf30442f591e Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 26 Apr 2014 08:10:19 -0400 Subject: [PATCH 28/37] Updated Docs README Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) Upstream-commit: 7744f63159cee9cfa5d9e9307e5c29a4f589837f Component: engine --- components/engine/docs/README.md | 74 +++++++++++++++----------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/components/engine/docs/README.md b/components/engine/docs/README.md index a113cb9edd..e5474d524d 100755 --- a/components/engine/docs/README.md +++ b/components/engine/docs/README.md @@ -7,64 +7,60 @@ Overview The source for Docker documentation is here under ``sources/`` and uses extended Markdown, as implemented by [mkdocs](http://mkdocs.org). -The HTML files are built and hosted on https://docs.docker.io, and update -automatically after each change to the master or release branch of the -[docker files on GitHub](https://github.com/dotcloud/docker) thanks to -post-commit hooks. The "release" branch maps to the "latest" -documentation and the "master" (unreleased development) branch maps to the "master" -documentation. +The HTML files are built and hosted on `https://docs.docker.io`, and +update automatically after each change to the master or release branch +of [Docker on GitHub](https://github.com/dotcloud/docker) +thanks to post-commit hooks. The "docs" branch maps to the "latest" +documentation and the "master" (unreleased development) branch maps to +the "master" documentation. ## Branches **There are two branches related to editing docs**: ``master`` and a -``docs`` branch. You should always edit -docs on a local branch of the ``master`` branch, and send a PR against ``master``. -That way your fixes -will automatically get included in later releases, and docs maintainers -can easily cherry-pick your changes into the ``docs`` release branch. -In the rare case where your change is not forward-compatible, -you may need to base your changes on the ``docs`` branch. +``docs`` branch. You should always edit documentation on a local branch +of the ``master`` branch, and send a PR against ``master``. -Now that we have a ``docs`` branch, we can keep the [http://docs.docker.io](http://docs.docker.io) docs -up to date with any bugs found between ``docker`` code releases. +That way your fixes will automatically get included in later releases, +and docs maintainers can easily cherry-pick your changes into the +``docs`` release branch. In the rare case where your change is not +forward-compatible, you may need to base your changes on the ``docs`` +branch. -**Warning**: When *reading* the docs, the [http://beta-docs.docker.io](http://beta-docs.docker.io) documentation may -include features not yet part of any official docker -release. The ``beta-docs`` site should be used only for understanding -bleeding-edge development and ``docs.docker.io`` (which points to the ``docs`` +Also, now that we have a ``docs`` branch, we can keep the +[http://docs.docker.io](http://docs.docker.io) docs up to date with any +bugs found between ``docker`` code releases. + +**Warning**: When *reading* the docs, the +[http://beta-docs.docker.io](http://beta-docs.docker.io) documentation +may include features not yet part of any official docker release. The +``beta-docs`` site should be used only for understanding bleeding-edge +development and ``docs.docker.io`` (which points to the ``docs`` branch``) should be used for the latest official release. Getting Started --------------- -Docker documentation builds are done in a docker container, which installs all -the required tools, adds the local ``docs/`` directory and builds the HTML -docs. It then starts a HTTP server on port 8000 so that you can connect -and see your changes. +Docker documentation builds are done in a Docker container, which +installs all the required tools, adds the local ``docs/`` directory and +builds the HTML docs. It then starts a HTTP server on port 8000 so that +you can connect and see your changes. -In the ``docker`` source directory, run: - ```make docs``` +In the root of the ``docker`` source directory: + + cd docker + +Run: + + make docs If you have any issues you need to debug, you can use ``make docs-shell`` and then run ``mkdocs serve`` # Contributing -## Normal Case: - * Follow the contribution guidelines ([see ``../CONTRIBUTING.md``](../CONTRIBUTING.md)). * [Remember to sign your work!](../CONTRIBUTING.md#sign-your-work) -* Work in your own fork of the code, we accept pull requests. -* Change the ``.md`` files with your favorite editor -- try to keep the - lines short (80 chars) and respect Markdown conventions. -* Run ``make clean docs`` to clean up old files and generate new ones, - or just ``make docs`` to update after small changes. -* Your static website can now be found in the ``_build`` directory. -* To preview what you have generated run ``make server`` and open - http://localhost:8000/ in your favorite browser. - -``make clean docs`` must complete without any warnings or errors. Working using GitHub's file editor ---------------------------------- @@ -87,7 +83,7 @@ Publishing Documentation ------------------------ To publish a copy of the documentation you need a ``docs/awsconfig`` -file containing AWS settings to deploy to. The release script will +file containing AWS settings to deploy to. The release script will create an s3 if needed, and will then push the files to it. ``` @@ -97,7 +93,7 @@ aws_secret_access_key = OIUYSADJHLKUHQWIUHE...... region = ap-southeast-2 ``` -The ``profile`` name must be the same as the name of the bucket you are +The ``profile`` name must be the same as the name of the bucket you are deploying to - which you call from the docker directory: ``make AWS_S3_BUCKET=dowideit-docs docs-release`` From 97fe9aac5f1ed6f6c19bba02f0356ce46caa28c6 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 26 Apr 2014 09:48:07 -0400 Subject: [PATCH 29/37] Fixed Dockerise to Dockerize Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) Upstream-commit: df018bc8012ad4181ac16e1329c970fe213f1f87 Component: engine --- .../engine/docs/sources/introduction/understanding-docker.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/introduction/understanding-docker.md b/components/engine/docs/sources/introduction/understanding-docker.md index 1c979d5810..5920da5bca 100644 --- a/components/engine/docs/sources/introduction/understanding-docker.md +++ b/components/engine/docs/sources/introduction/understanding-docker.md @@ -87,14 +87,14 @@ to you. *Docker is made for humans.* It's easy to get started and easy to build and deploy applications with -Docker: or as we say "*dockerise*" them! As much of Docker as possible +Docker: or as we say "*dockerize*" them! As much of Docker as possible uses plain English for commands and tries to be as lightweight and transparent as possible. We want to get out of the way so you can build and deploy your applications. ### Docker is Portable -*Dockerise And Go!* +*Dockerize And Go!* Docker containers are highly portable. Docker provides a standard container format to hold your applications: From fdb0b2b56e7cd37d2705751c4aef5e53b3b480cb Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 26 Apr 2014 09:53:17 -0400 Subject: [PATCH 30/37] Replaced all double backticks in README with singles Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) Upstream-commit: 723d314f097fe7b1e15f27c1d6f05794b6c5f411 Component: engine --- components/engine/docs/README.md | 56 +++++++++++++++----------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/components/engine/docs/README.md b/components/engine/docs/README.md index e5474d524d..bbc741d593 100755 --- a/components/engine/docs/README.md +++ b/components/engine/docs/README.md @@ -4,7 +4,7 @@ Docker Documentation Overview -------- -The source for Docker documentation is here under ``sources/`` and uses +The source for Docker documentation is here under `sources/` and uses extended Markdown, as implemented by [mkdocs](http://mkdocs.org). The HTML files are built and hosted on `https://docs.docker.io`, and @@ -16,36 +16,36 @@ the "master" documentation. ## Branches -**There are two branches related to editing docs**: ``master`` and a -``docs`` branch. You should always edit documentation on a local branch -of the ``master`` branch, and send a PR against ``master``. +**There are two branches related to editing docs**: `master` and a +`docs` branch. You should always edit documentation on a local branch +of the `master` branch, and send a PR against `master`. That way your fixes will automatically get included in later releases, and docs maintainers can easily cherry-pick your changes into the -``docs`` release branch. In the rare case where your change is not -forward-compatible, you may need to base your changes on the ``docs`` +`docs` release branch. In the rare case where your change is not +forward-compatible, you may need to base your changes on the `docs` branch. -Also, now that we have a ``docs`` branch, we can keep the +Also, now that we have a `docs` branch, we can keep the [http://docs.docker.io](http://docs.docker.io) docs up to date with any -bugs found between ``docker`` code releases. +bugs found between `docker` code releases. **Warning**: When *reading* the docs, the [http://beta-docs.docker.io](http://beta-docs.docker.io) documentation may include features not yet part of any official docker release. The -``beta-docs`` site should be used only for understanding bleeding-edge -development and ``docs.docker.io`` (which points to the ``docs`` -branch``) should be used for the latest official release. +`beta-docs` site should be used only for understanding bleeding-edge +development and `docs.docker.io` (which points to the `docs` +branch`) should be used for the latest official release. Getting Started --------------- Docker documentation builds are done in a Docker container, which -installs all the required tools, adds the local ``docs/`` directory and +installs all the required tools, adds the local `docs/` directory and builds the HTML docs. It then starts a HTTP server on port 8000 so that you can connect and see your changes. -In the root of the ``docker`` source directory: +In the root of the `docker` source directory: cd docker @@ -53,13 +53,13 @@ Run: make docs -If you have any issues you need to debug, you can use ``make docs-shell`` and -then run ``mkdocs serve`` +If you have any issues you need to debug, you can use `make docs-shell` and +then run `mkdocs serve` # Contributing * Follow the contribution guidelines ([see - ``../CONTRIBUTING.md``](../CONTRIBUTING.md)). + `../CONTRIBUTING.md`](../CONTRIBUTING.md)). * [Remember to sign your work!](../CONTRIBUTING.md#sign-your-work) Working using GitHub's file editor @@ -67,7 +67,7 @@ Working using GitHub's file editor Alternatively, for small changes and typos you might want to use GitHub's built in file editor. It allows you to preview your changes -right online (though there can be some differences between GitHub +right on-line (though there can be some differences between GitHub Markdown and mkdocs Markdown). Just be careful not to create many commits. And you must still [sign your work!](../CONTRIBUTING.md#sign-your-work) @@ -75,26 +75,24 @@ Images ------ When you need to add images, try to make them as small as possible -(e.g. as gif). Usually images should go in the same directory as the -.md file which references them, or in a subdirectory if one already +(e.g. as gifs). Usually images should go in the same directory as the +`.md` file which references them, or in a subdirectory if one already exists. Publishing Documentation ------------------------ -To publish a copy of the documentation you need a ``docs/awsconfig`` +To publish a copy of the documentation you need a `docs/awsconfig` file containing AWS settings to deploy to. The release script will create an s3 if needed, and will then push the files to it. -``` -[profile dowideit-docs] -aws_access_key_id = IHOIUAHSIDH234rwf.... -aws_secret_access_key = OIUYSADJHLKUHQWIUHE...... -region = ap-southeast-2 -``` + [profile dowideit-docs] + aws_access_key_id = IHOIUAHSIDH234rwf.... + aws_secret_access_key = OIUYSADJHLKUHQWIUHE...... + region = ap-southeast-2 -The ``profile`` name must be the same as the name of the bucket you are -deploying to - which you call from the docker directory: +The `profile` name must be the same as the name of the bucket you are +deploying to - which you call from the `docker` directory: -``make AWS_S3_BUCKET=dowideit-docs docs-release`` + make AWS_S3_BUCKET=dowideit-docs docs-release From 62e9a76b63a8cb56d04e77a8b5ef963d25ceacd9 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 25 Apr 2014 13:21:28 -0700 Subject: [PATCH 31/37] Freeze ./integration and explain where to contribute new tests Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) Upstream-commit: e83fc70d362bf0035811bbcc5566020c6cc9893f Component: engine --- components/engine/integration/MAINTAINERS | 4 ++++ components/engine/integration/README.md | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 components/engine/integration/MAINTAINERS create mode 100644 components/engine/integration/README.md diff --git a/components/engine/integration/MAINTAINERS b/components/engine/integration/MAINTAINERS new file mode 100644 index 0000000000..2d47d7a711 --- /dev/null +++ b/components/engine/integration/MAINTAINERS @@ -0,0 +1,4 @@ +Solomon Hykes +# WE ARE LOOKING FOR VOLUNTEERS TO HELP CLEAN THIS UP. +# TO VOLUNTEER PLEASE OPEN A PULL REQUEST ADDING YOURSELF TO THIS FILE. +# WE WILL HELP YOU GET STARTED. THANKS! diff --git a/components/engine/integration/README.md b/components/engine/integration/README.md new file mode 100644 index 0000000000..41f43a4ba7 --- /dev/null +++ b/components/engine/integration/README.md @@ -0,0 +1,23 @@ +## Legacy integration tests + +`./integration` contains Docker's legacy integration tests. +It is DEPRECATED and will eventually be removed. + +### If you are a *CONTRIBUTOR* and want to add a test: + +* Consider mocking out side effects and contributing a *unit test* in the subsystem +you're modifying. For example, the remote API has unit tests in `./api/server/server_unit_tests.go`. +The events subsystem has unit tests in `./events/events_test.go`. And so on. + +* For end-to-end integration tests, please contribute to `./integration-cli`. + + +### If you are a *MAINTAINER* + +Please don't allow patches adding new tests to `./integration`. + +### If you are *LOOKING FOR A WAY TO HELP* + +Please consider porting tests away from `./integration` and into either unit tests or CLI tests. + +Any help will be greatly appreciated! From 268409eaa5aa58c739efb4d5c6d11083547a5361 Mon Sep 17 00:00:00 2001 From: Soulou Date: Tue, 11 Mar 2014 09:30:51 +0000 Subject: [PATCH 32/37] [Documentation - API] Add missing 'signal' parameter for /containers/:id/kill endpoint Docker-DCO-1.1-Signed-off-by: Leo Unbekandt (github: Soulou) Upstream-commit: 91deb591c8a52a974612b4c6885d989251f819bd Component: engine --- .../sources/reference/api/archive/docker_remote_api_v1.7.rst | 1 + .../sources/reference/api/archive/docker_remote_api_v1.8.rst | 1 + .../docs/sources/reference/api/docker_remote_api_v1.10.md | 5 +++++ .../docs/sources/reference/api/docker_remote_api_v1.10.rst | 1 + .../docs/sources/reference/api/docker_remote_api_v1.11.md | 5 +++++ .../docs/sources/reference/api/docker_remote_api_v1.11.rst | 1 + .../docs/sources/reference/api/docker_remote_api_v1.9.md | 5 +++++ .../docs/sources/reference/api/docker_remote_api_v1.9.rst | 1 + 8 files changed, 20 insertions(+) diff --git a/components/engine/docs/sources/reference/api/archive/docker_remote_api_v1.7.rst b/components/engine/docs/sources/reference/api/archive/docker_remote_api_v1.7.rst index 1bafaddfc5..7a4f688d8f 100644 --- a/components/engine/docs/sources/reference/api/archive/docker_remote_api_v1.7.rst +++ b/components/engine/docs/sources/reference/api/archive/docker_remote_api_v1.7.rst @@ -454,6 +454,7 @@ Kill a container HTTP/1.1 204 OK + :query signal: Signal to send to the container (integer). When not set, SIGKILL is assumed and the call will waits for the container to exit. :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error diff --git a/components/engine/docs/sources/reference/api/archive/docker_remote_api_v1.8.rst b/components/engine/docs/sources/reference/api/archive/docker_remote_api_v1.8.rst index 16492dde76..4f1b266bb6 100644 --- a/components/engine/docs/sources/reference/api/archive/docker_remote_api_v1.8.rst +++ b/components/engine/docs/sources/reference/api/archive/docker_remote_api_v1.8.rst @@ -482,6 +482,7 @@ Kill a container HTTP/1.1 204 OK + :query signal: Signal to send to the container (integer). When not set, SIGKILL is assumed and the call will waits for the container to exit. :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error diff --git a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md index c07f96f384..749ff8e383 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md @@ -459,6 +459,11 @@ Kill the container `id` HTTP/1.1 204 OK + Query Parameters + + - **signal** - Signal to send to the container: integer or string like "SIGINT". + When not set, SIGKILL is assumed and the call will waits for the container to exit. + Status Codes: - **204** – no error diff --git a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.rst b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.rst index 83e2c3c15b..8635ec4826 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.rst +++ b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.rst @@ -466,6 +466,7 @@ Kill a container HTTP/1.1 204 OK + :query signal: Signal to send to the container, must be integer or string (i.e. SIGINT). When not set, SIGKILL is assumed and the call will waits for the container to exit. :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error diff --git a/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md index 5e3fdcb0a8..baabade455 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md @@ -461,6 +461,11 @@ Kill the container `id` HTTP/1.1 204 OK + Query Parameters + + - **signal** - Signal to send to the container: integer or string like "SIGINT". + When not set, SIGKILL is assumed and the call will waits for the container to exit. + Status Codes: - **204** – no error diff --git a/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.rst b/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.rst index 556491c49a..d66b4b1410 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.rst +++ b/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.rst @@ -468,6 +468,7 @@ Kill a container HTTP/1.1 204 OK + :query signal: Signal to send to the container, must be integer or string (i.e. SIGINT). When not set, SIGKILL is assumed and the call will waits for the container to exit. :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error diff --git a/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md index 74e85a7ee6..1fe154a3da 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md @@ -482,6 +482,11 @@ Kill the container `id` HTTP/1.1 204 OK + Query Parameters + + - **signal** - Signal to send to the container: integer or string like "SIGINT". + When not set, SIGKILL is assumed and the call will waits for the container to exit. + Status Codes: - **204** – no error diff --git a/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.rst b/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.rst index 4bbffcbd36..db0e3bfdae 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.rst +++ b/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.rst @@ -480,6 +480,7 @@ Kill a container HTTP/1.1 204 OK + :query signal: Signal to send to the container, must be integer or string (i.e. SIGINT). When not set, SIGKILL is assumed and the call will waits for the container to exit. :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error From a11d66ea079ebd8658bd90f679dbabb32cf29910 Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Sun, 27 Apr 2014 11:17:48 -0700 Subject: [PATCH 33/37] Added back OAuth and Accounts API docs pages Removed a now unused endpoint from the accounts API. Updated some of the accounts links to point to www.docker.io as the account signup and resend-email-confirmation links should no longer point to the index. Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) Upstream-commit: c6060a3b25b94ae4738ac27bbf62cbb914af7ced Component: engine --- components/engine/docs/mkdocs.yml | 2 + .../engine/docs/sources/index/accounts.md | 4 +- .../reference/api/docker_io_accounts_api.md | 73 +------------------ 3 files changed, 5 insertions(+), 74 deletions(-) diff --git a/components/engine/docs/mkdocs.yml b/components/engine/docs/mkdocs.yml index 3538642717..0b526e016b 100755 --- a/components/engine/docs/mkdocs.yml +++ b/components/engine/docs/mkdocs.yml @@ -106,6 +106,8 @@ pages: - ['reference/api/docker_remote_api_v1.10.md', 'Reference', 'Docker Remote API v1.10'] - ['reference/api/docker_remote_api_v1.9.md', 'Reference', 'Docker Remote API v1.9'] - ['reference/api/remote_api_client_libraries.md', 'Reference', 'Docker Remote API Client Libraries'] +- ['reference/api/docker_io_oauth_api.md', 'Reference', 'Docker IO OAuth API'] +- ['reference/api/docker_io_accounts_api.md', 'Reference', 'Docker IO Accounts API'] # Contribute: - ['contributing/index.md', '**HIDDEN**'] diff --git a/components/engine/docs/sources/index/accounts.md b/components/engine/docs/sources/index/accounts.md index c3138b61da..54d015ac2a 100644 --- a/components/engine/docs/sources/index/accounts.md +++ b/components/engine/docs/sources/index/accounts.md @@ -14,7 +14,7 @@ to need a [Docker IO](https://www.docker.io) account. ### Registration for a Docker IO Account You can get a Docker IO account by [signing up for one here]( -https://index.docker.io/account/signup/). A valid email address is required to +https://www.docker.io/account/signup/). A valid email address is required to register, which you will need to verify for account activation. ### Email activation process @@ -22,7 +22,7 @@ register, which you will need to verify for account activation. You need to have at least one verified email address to be able to use your Docker IO account. If you can't find the validation email, you can request another by visiting the [Resend Email Confirmation]( -https://index.docker.io/account/resend-email-confirmation/) page. +https://www.docker.io/account/resend-email-confirmation/) page. ### Password reset process diff --git a/components/engine/docs/sources/reference/api/docker_io_accounts_api.md b/components/engine/docs/sources/reference/api/docker_io_accounts_api.md index 8186e306f8..b9f76ba92c 100644 --- a/components/engine/docs/sources/reference/api/docker_io_accounts_api.md +++ b/components/engine/docs/sources/reference/api/docker_io_accounts_api.md @@ -237,78 +237,7 @@ automatically sent. "primary": false } -### 1.5 Update an email address for a user - -`PATCH /api/v1.1/users/:username/emails/` - -Update an email address for the specified user to either verify an -email address or set it as the primary email for the user. You -cannot use this endpoint to un-verify an email address. You cannot -use this endpoint to unset the primary email, only set another as -the primary. - - Parameters: - - - **username** – username of the user whose email info is being - updated. - - Json Parameters: - -   - - - **email** (*string*) – the email address to be updated. - - **verified** (*boolean*) – (optional) whether the email address - is verified, must be `true` or absent. - - **primary** (*boolean*) – (optional) whether to set the email - address as the primary email, must be `true` - or absent. - - Request Headers: - -   - - - **Authorization** – required authentication credentials of - either type HTTP Basic or OAuth Bearer Token. - - **Content-Type** – MIME Type of post data. JSON, url-encoded - form data, etc. - - Status Codes: - - - **200** – success, user's email updated. - - **400** – data validation error. - - **401** – authentication error. - - **403** – permission error, authenticated user must be the user - whose data is being updated, OAuth access tokens must have - `email_write` scope. - - **404** – the specified username or email address does not - exist. - - **Example request**: - - Once you have independently verified an email address. - - PATCH /api/v1.1/users/janedoe/emails/ HTTP/1.1 - Host: www.docker.io - Accept: application/json - Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ= - - { - "email": "jane.doe+other@example.com", - "verified": true, - } - - **Example response**: - - HTTP/1.1 200 OK - Content-Type: application/json - - { - "email": "jane.doe+other@example.com", - "verified": true, - "primary": false - } - -### 1.6 Delete email address for a user +### 1.5 Delete email address for a user `DELETE /api/v1.1/users/:username/emails/` From 823835353ed22940825f57a47fcefe2fb539849f Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 26 Apr 2014 18:24:39 -0700 Subject: [PATCH 34/37] Fix bug in engine.Sender Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) Upstream-commit: 9236e088eb5a9a6d662b08ef7983dbecf01e6ef0 Component: engine --- components/engine/engine/remote.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/engine/remote.go b/components/engine/engine/remote.go index 48638e4383..60aad243c5 100644 --- a/components/engine/engine/remote.go +++ b/components/engine/engine/remote.go @@ -55,10 +55,10 @@ func (s *Sender) Handle(job *Job) Status { var status int r.NewRoute().KeyStartsWith("cmd", "status").Handler(func(p []byte, f *os.File) error { cmd := data.Message(p).Get("cmd") - if len(cmd) != 3 { + if len(cmd) != 2 { return fmt.Errorf("usage: %s <0-127>", cmd[0]) } - s, err := strconv.ParseUint(cmd[2], 10, 8) + s, err := strconv.ParseUint(cmd[1], 10, 8) if err != nil { return fmt.Errorf("usage: %s <0-127>", cmd[0]) } From da3f4afb39dfd96f74f7c13666b1f81d15023ef6 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 26 Apr 2014 18:47:20 -0700 Subject: [PATCH 35/37] engine/spawn: run an engine in a subprocess, remote-controlled by Beam Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) Upstream-commit: b4b83ef8ae788cb7e016fbf90f0c1b890af7b23d Component: engine --- components/engine/engine/spawn/spawn.go | 119 ++++++++++++++++++ .../engine/engine/spawn/subengine/main.go | 61 +++++++++ 2 files changed, 180 insertions(+) create mode 100644 components/engine/engine/spawn/spawn.go create mode 100644 components/engine/engine/spawn/subengine/main.go diff --git a/components/engine/engine/spawn/spawn.go b/components/engine/engine/spawn/spawn.go new file mode 100644 index 0000000000..6680845bc1 --- /dev/null +++ b/components/engine/engine/spawn/spawn.go @@ -0,0 +1,119 @@ +package spawn + +import ( + "fmt" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/pkg/beam" + "github.com/dotcloud/docker/utils" + "os" + "os/exec" +) + +var initCalled bool + +// Init checks if the current process has been created by Spawn. +// +// If no, it returns nil and the original program can continue +// unmodified. +// +// If no, it hijacks the process to run as a child worker controlled +// by its parent over a beam connection, with f exposed as a remote +// service. In this case Init never returns. +// +// The hijacking process takes place as follows: +// - Open file descriptor 3 as a beam endpoint. If this fails, +// terminate the current process. +// - Start a new engine. +// - Call f.Install on the engine. Any handlers registered +// will be available for remote invocation by the parent. +// - Listen for beam messages from the parent and pass them to +// the handlers. +// - When the beam endpoint is closed by the parent, terminate +// the current process. +// +// NOTE: Init must be called at the beginning of the same program +// calling Spawn. This is because Spawn approximates a "fork" by +// re-executing the current binary - where it expects spawn.Init +// to intercept the control flow and execute the worker code. +func Init(f engine.Installer) error { + initCalled = true + if os.Getenv("ENGINESPAWN") != "1" { + return nil + } + fmt.Printf("[%d child]\n", os.Getpid()) + // Hijack the process + childErr := func() error { + fd3 := os.NewFile(3, "beam-introspect") + introsp, err := beam.FileConn(fd3) + if err != nil { + return fmt.Errorf("beam introspection error: %v", err) + } + fd3.Close() + defer introsp.Close() + eng := engine.NewReceiver(introsp) + if err := f.Install(eng.Engine); err != nil { + return err + } + if err := eng.Run(); err != nil { + return err + } + return nil + }() + if childErr != nil { + os.Exit(1) + } + os.Exit(0) + return nil // Never reached +} + +// Spawn starts a new Engine in a child process and returns +// a proxy Engine through which it can be controlled. +// +// The commands available on the child engine are determined +// by an earlier call to Init. It is important that Init be +// called at the very beginning of the current program - this +// allows it to be called as a re-execution hook in the child +// process. +// +// Long story short, if you want to expose `myservice` in a child +// process, do this: +// +// func main() { +// spawn.Init(myservice) +// [..] +// child, err := spawn.Spawn() +// [..] +// child.Job("dosomething").Run() +// } +func Spawn() (*engine.Engine, error) { + if !initCalled { + return nil, fmt.Errorf("spawn.Init must be called at the top of the main() function") + } + cmd := exec.Command(utils.SelfPath()) + cmd.Env = append(cmd.Env, "ENGINESPAWN=1") + local, remote, err := beam.SocketPair() + if err != nil { + return nil, err + } + child, err := beam.FileConn(local) + if err != nil { + local.Close() + remote.Close() + return nil, err + } + local.Close() + cmd.ExtraFiles = append(cmd.ExtraFiles, remote) + // FIXME: the beam/engine glue has no way to inform the caller + // of the child's termination. The next call will simply return + // an error. + if err := cmd.Start(); err != nil { + child.Close() + return nil, err + } + eng := engine.New() + if err := engine.NewSender(child).Install(eng); err != nil { + child.Close() + return nil, err + } + return eng, nil +} diff --git a/components/engine/engine/spawn/subengine/main.go b/components/engine/engine/spawn/subengine/main.go new file mode 100644 index 0000000000..3be7520a67 --- /dev/null +++ b/components/engine/engine/spawn/subengine/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/engine/spawn" + "log" + "os" + "os/exec" + "strings" +) + +func main() { + fmt.Printf("[%d] MAIN\n", os.Getpid()) + spawn.Init(&Worker{}) + fmt.Printf("[%d parent] spawning\n", os.Getpid()) + eng, err := spawn.Spawn() + if err != nil { + log.Fatal(err) + } + fmt.Printf("[parent] spawned\n") + job := eng.Job(os.Args[1], os.Args[2:]...) + job.Stdout.Add(os.Stdout) + job.Stderr.Add(os.Stderr) + job.Run() + // FIXME: use the job's status code + os.Exit(0) +} + +type Worker struct { +} + +func (w *Worker) Install(eng *engine.Engine) error { + eng.Register("exec", w.Exec) + eng.Register("cd", w.Cd) + eng.Register("echo", w.Echo) + return nil +} + +func (w *Worker) Exec(job *engine.Job) engine.Status { + fmt.Printf("--> %v\n", job.Args) + cmd := exec.Command(job.Args[0], job.Args[1:]...) + cmd.Stdout = job.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return job.Errorf("%v\n", err) + } + return engine.StatusOK +} + +func (w *Worker) Cd(job *engine.Job) engine.Status { + if err := os.Chdir(job.Args[0]); err != nil { + return job.Errorf("%v\n", err) + } + return engine.StatusOK +} + +func (w *Worker) Echo(job *engine.Job) engine.Status { + fmt.Fprintf(job.Stdout, "%s\n", strings.Join(job.Args, " ")) + return engine.StatusOK +} From cdda6fcadaaf1b6515a2c7880f2577ba4543e780 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Apr 2014 23:57:41 -0700 Subject: [PATCH 36/37] engine.Len returns the number of keys in an env Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) Upstream-commit: 9b23178f585f65b9dc9558694d6de4207c479801 Component: engine --- components/engine/engine/env.go | 7 +++++++ components/engine/engine/env_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/components/engine/engine/env.go b/components/engine/engine/env.go index c43a5ec971..f96795f48c 100644 --- a/components/engine/engine/env.go +++ b/components/engine/engine/env.go @@ -36,6 +36,13 @@ func (env *Env) Exists(key string) bool { return exists } +// Len returns the number of keys in the environment. +// Note that len(env) might be different from env.Len(), +// because the same key might be set multiple times. +func (env *Env) Len() int { + return len(env.Map()) +} + func (env *Env) Init(src *Env) { (*env) = make([]string, 0, len(*src)) for _, val := range *src { diff --git a/components/engine/engine/env_test.go b/components/engine/engine/env_test.go index c7079ff942..0c66cea04e 100644 --- a/components/engine/engine/env_test.go +++ b/components/engine/engine/env_test.go @@ -4,6 +4,34 @@ import ( "testing" ) +func TestEnvLenZero(t *testing.T) { + env := &Env{} + if env.Len() != 0 { + t.Fatalf("%d", env.Len()) + } +} + +func TestEnvLenNotZero(t *testing.T) { + env := &Env{} + env.Set("foo", "bar") + env.Set("ga", "bu") + if env.Len() != 2 { + t.Fatalf("%d", env.Len()) + } +} + +func TestEnvLenDup(t *testing.T) { + env := &Env{ + "foo=bar", + "foo=baz", + "a=b", + } + // len(env) != env.Len() + if env.Len() != 2 { + t.Fatalf("%d", env.Len()) + } +} + func TestNewJob(t *testing.T) { job := mkJob(t, "dummy", "--level=awesome") if job.Name != "dummy" { From cb6c5cfbb8fa1d4073edf9a1824a096da9737c59 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Wed, 9 Apr 2014 21:50:46 -0400 Subject: [PATCH 37/37] Fixes permissions on volumes when dir in container is empty Docker-DCO-1.1-Signed-off-by: Brian Goff (github: cpuguy83) Upstream-commit: ff7b52abd3f26d9650c2e674400d58fbe8157ad8 Component: engine --- components/engine/daemon/volumes.go | 30 +++++++++---------- .../engine/integration/container_test.go | 28 ++++++++++++++++- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/components/engine/daemon/volumes.go b/components/engine/daemon/volumes.go index 4a5c4475b7..19bb7cab3f 100644 --- a/components/engine/daemon/volumes.go +++ b/components/engine/daemon/volumes.go @@ -246,22 +246,22 @@ func createVolumes(container *Container) error { if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil { return err } + } + } - var stat syscall.Stat_t - if err := syscall.Stat(rootVolPath, &stat); err != nil { - return err - } - var srcStat syscall.Stat_t - if err := syscall.Stat(srcPath, &srcStat); err != nil { - return err - } - // Change the source volume's ownership if it differs from the root - // files that were just copied - if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid { - if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil { - return err - } - } + var stat syscall.Stat_t + if err := syscall.Stat(rootVolPath, &stat); err != nil { + return err + } + var srcStat syscall.Stat_t + if err := syscall.Stat(srcPath, &srcStat); err != nil { + return err + } + // Change the source volume's ownership if it differs from the root + // files that were just copied + if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid { + if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil { + return err } } } diff --git a/components/engine/integration/container_test.go b/components/engine/integration/container_test.go index 67b2783ce9..8fe52a3cd6 100644 --- a/components/engine/integration/container_test.go +++ b/components/engine/integration/container_test.go @@ -407,7 +407,7 @@ func TestCopyVolumeUidGid(t *testing.T) { defer r.Nuke() // Add directory not owned by root - container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test.txt && chown daemon.daemon /hello"}, t) + container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test && chown daemon.daemon /hello"}, t) defer r.Destroy(container1) if container1.State.IsRunning() { @@ -432,6 +432,32 @@ func TestCopyVolumeUidGid(t *testing.T) { if !strings.Contains(stdout1, "daemon daemon") { t.Fatal("Container failed to transfer uid and gid to volume") } + + container2, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && chown daemon.daemon /hello"}, t) + defer r.Destroy(container1) + + if container2.State.IsRunning() { + t.Errorf("Container shouldn't be running") + } + if err := container2.Run(); err != nil { + t.Fatal(err) + } + if container2.State.IsRunning() { + t.Errorf("Container shouldn't be running") + } + + img2, err := r.Commit(container2, "", "", "unit test commited image", "", nil) + if err != nil { + t.Error(err) + } + + // Test that the uid and gid is copied from the image to the volume + tmpDir2 := tempDir(t) + defer os.RemoveAll(tmpDir2) + stdout2, _ := runContainer(eng, r, []string{"-v", "/hello", img2.ID, "stat", "-c", "%U %G", "/hello"}, t) + if !strings.Contains(stdout2, "daemon daemon") { + t.Fatal("Container failed to transfer uid and gid to volume") + } } // Test for #1582