From 59c5c3ac0fbb0b53b4a6ac6e1087b69869475253 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Thu, 2 Apr 2015 08:31:47 -0400 Subject: [PATCH 1/2] Add support for Premount and Postmount commands. We want to allow docker to mount tmpfs directories over existing directories in the image. We will use this patch to pass commands from docker to libcontainer. The first command we will use is the tar command to gather all of the contents of the destination directory before mounting, then after we mount the post mount command will untar the content. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- configs/mount.go | 13 +++++++++++++ rootfs_linux.go | 24 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/configs/mount.go b/configs/mount.go index 7b3dea33..5a69f815 100644 --- a/configs/mount.go +++ b/configs/mount.go @@ -18,4 +18,17 @@ type Mount struct { // Relabel source if set, "z" indicates shared, "Z" indicates unshared. Relabel string `json:"relabel"` + + // Optional Command to be run before Source is mounted. + PremountCmds []Command `json:"premount_cmds"` + + // Optional Command to be run after Source is mounted. + PostmountCmds []Command `json:"postmount_cmds"` +} + +type Command struct { + Path string `json:"path"` + Args []string `json:"args"` + Env []string `json:"env"` + Dir string `json:"dir"` } diff --git a/rootfs_linux.go b/rootfs_linux.go index 091dc431..1e2ec104 100644 --- a/rootfs_linux.go +++ b/rootfs_linux.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" "path/filepath" "strings" "syscall" @@ -24,9 +25,20 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) { return newSystemError(err) } for _, m := range config.Mounts { + for _, precmd := range m.PremountCmds { + if err := mountCmd(precmd); err != nil { + return newSystemError(err) + } + } if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil { return newSystemError(err) } + + for _, postcmd := range m.PostmountCmds { + if err := mountCmd(postcmd); err != nil { + return newSystemError(err) + } + } } if err := createDevices(config); err != nil { return newSystemError(err) @@ -62,6 +74,18 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) { return nil } +func mountCmd(cmd configs.Command) error { + + command := exec.Command(cmd.Path, cmd.Args[:]...) + command.Env = cmd.Env + command.Dir = cmd.Dir + if out, err := command.CombinedOutput(); err != nil { + return fmt.Errorf("%s failed: %s: %v", cmd, string(out), err) + } + + return nil +} + func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { var ( dest = m.Destination From dc480bc3adfce27c6c356439bf1cb8c2e71e16f0 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Thu, 16 Apr 2015 16:55:12 -0400 Subject: [PATCH 2/2] add integration test for premount/postmount hooks Docker-DCO-1.1-Signed-off-by: Daniel, Dao Quang Minh (github: rhatdan) Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- integration/exec_test.go | 75 ++++++++++++++++++++++++++++++++++++++++ rootfs_linux.go | 2 +- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/integration/exec_test.go b/integration/exec_test.go index 8f6719d0..9570701d 100644 --- a/integration/exec_test.go +++ b/integration/exec_test.go @@ -4,8 +4,10 @@ import ( "bytes" "io/ioutil" "os" + "path/filepath" "strconv" "strings" + "syscall" "testing" "github.com/docker/libcontainer" @@ -613,3 +615,76 @@ func TestPassExtraFiles(t *testing.T) { t.Fatalf("expected second pipe to receive '2', got '%s'", out2) } } + +func TestMountCmds(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + tmpDir, err := ioutil.TempDir("", "tmpdir") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + config := newTemplateConfig(rootfs) + config.Mounts = append(config.Mounts, &configs.Mount{ + Source: tmpDir, + Destination: filepath.Join(rootfs, "tmp"), + Device: "bind", + Flags: syscall.MS_BIND | syscall.MS_REC, + PremountCmds: []configs.Command{ + {Path: "touch", Args: []string{filepath.Join(tmpDir, "hello")}}, + {Path: "touch", Args: []string{filepath.Join(tmpDir, "world")}}, + }, + PostmountCmds: []configs.Command{ + {Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "hello"), filepath.Join(rootfs, "tmp", "hello-backup")}}, + {Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "world"), filepath.Join(rootfs, "tmp", "world-backup")}}, + }, + }) + + factory, err := libcontainer.New(root, libcontainer.Cgroupfs) + if err != nil { + t.Fatal(err) + } + + container, err := factory.Create("test", config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + pconfig := libcontainer.Process{ + Args: []string{"sh", "-c", "env"}, + Env: standardEnvironment, + } + err = container.Start(&pconfig) + if err != nil { + t.Fatal(err) + } + + // Wait for process + waitProcess(&pconfig, t) + + entries, err := ioutil.ReadDir(tmpDir) + if err != nil { + t.Fatal(err) + } + expected := []string{"hello", "hello-backup", "world", "world-backup"} + for i, e := range entries { + if e.Name() != expected[i] { + t.Errorf("Got(%s), expect %s", e.Name(), expected[i]) + } + } +} diff --git a/rootfs_linux.go b/rootfs_linux.go index 1e2ec104..e198bbd2 100644 --- a/rootfs_linux.go +++ b/rootfs_linux.go @@ -80,7 +80,7 @@ func mountCmd(cmd configs.Command) error { command.Env = cmd.Env command.Dir = cmd.Dir if out, err := command.CombinedOutput(); err != nil { - return fmt.Errorf("%s failed: %s: %v", cmd, string(out), err) + return fmt.Errorf("%#v failed: %s: %v", cmd, string(out), err) } return nil