diff --git a/api.go b/api.go index dd33f40a66..de4113231c 100644 --- a/api.go +++ b/api.go @@ -551,11 +551,20 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R } func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + hostConfig := &HostConfig{} + + // allow a nil body for backwards compatibility + if r.Body != nil { + if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil { + return err + } + } + if vars == nil { return fmt.Errorf("Missing parameter") } name := vars["name"] - if err := srv.ContainerStart(name); err != nil { + if err := srv.ContainerStart(name, hostConfig); err != nil { return err } w.WriteHeader(http.StatusNoContent) diff --git a/api_test.go b/api_test.go index 12ea5ee962..c2e7db4bd0 100644 --- a/api_test.go +++ b/api_test.go @@ -873,7 +873,8 @@ func TestPostContainersKill(t *testing.T) { } defer runtime.Destroy(container) - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } @@ -917,7 +918,8 @@ func TestPostContainersRestart(t *testing.T) { } defer runtime.Destroy(container) - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } @@ -973,8 +975,15 @@ func TestPostContainersStart(t *testing.T) { } defer runtime.Destroy(container) + hostConfigJSON, err := json.Marshal(&HostConfig{}) + + req, err := http.NewRequest("POST", "/containers/"+container.ID+"/start", bytes.NewReader(hostConfigJSON)) + if err != nil { + t.Fatal(err) + } + r := httptest.NewRecorder() - if err := postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { + if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { @@ -989,7 +998,7 @@ func TestPostContainersStart(t *testing.T) { } r = httptest.NewRecorder() - if err = postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err == nil { + if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil { t.Fatalf("A running containter should be able to be started") } @@ -1019,7 +1028,8 @@ func TestPostContainersStop(t *testing.T) { } defer runtime.Destroy(container) - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } @@ -1068,7 +1078,8 @@ func TestPostContainersWait(t *testing.T) { } defer runtime.Destroy(container) - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } @@ -1113,7 +1124,8 @@ func TestPostContainersAttach(t *testing.T) { defer runtime.Destroy(container) // Start the process - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } diff --git a/buildfile.go b/buildfile.go index 9cbaac4e76..63625e1a40 100644 --- a/buildfile.go +++ b/buildfile.go @@ -87,7 +87,7 @@ func (b *buildFile) CmdRun(args string) error { if b.image == "" { return fmt.Errorf("Please provide a source image with `from` prior to run") } - config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil) + config, _, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil) if err != nil { return err } @@ -263,7 +263,8 @@ func (b *buildFile) run() (string, error) { fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID)) //start the container - if err := c.Start(); err != nil { + hostConfig := &HostConfig{} + if err := c.Start(hostConfig); err != nil { return "", err } diff --git a/commands.go b/commands.go index 7175c07b0b..6e83a79c60 100644 --- a/commands.go +++ b/commands.go @@ -1235,7 +1235,7 @@ func (cli *DockerCli) CmdTag(args ...string) error { } func (cli *DockerCli) CmdRun(args ...string) error { - config, cmd, err := ParseRun(args, nil) + config, hostConfig, cmd, err := ParseRun(args, nil) if err != nil { return err } @@ -1274,7 +1274,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } //start the container - if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", nil); err != nil { + if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil { return err } diff --git a/container.go b/container.go index d9fcf1c76e..45bb728b96 100644 --- a/container.go +++ b/container.go @@ -52,6 +52,9 @@ type Container struct { waitLock chan struct{} Volumes map[string]string + // Store rw/ro in a separate structure to preserve reserve-compatibility on-disk. + // Easier than migrating older container configs :) + VolumesRW map[string]bool } type Config struct { @@ -75,7 +78,17 @@ type Config struct { VolumesFrom string } -func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) { +type HostConfig struct { + Binds []string +} + +type BindMap struct { + SrcPath string + DstPath string + Mode string +} + +func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) { cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container") if len(args) > 0 && args[0] != "--help" { cmd.SetOutput(ioutil.Discard) @@ -111,11 +124,14 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container") + var flBinds ListOpts + cmd.Var(&flBinds, "b", "Bind mount a volume from the host (e.g. -b /host:/container)") + if err := cmd.Parse(args); err != nil { - return nil, cmd, err + return nil, nil, cmd, err } if *flDetach && len(flAttach) > 0 { - return nil, cmd, fmt.Errorf("Conflicting options: -a and -d") + return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d") } // If neither -d or -a are set, attach to everything by default if len(flAttach) == 0 && !*flDetach { @@ -127,6 +143,14 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet } } } + + // add any bind targets to the list of container volumes + for _, bind := range flBinds { + arr := strings.Split(bind, ":") + dstDir := arr[1] + flVolumes[dstDir] = struct{}{} + } + parsedArgs := cmd.Args() runCmd := []string{} image := "" @@ -154,6 +178,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet Volumes: flVolumes, VolumesFrom: *flVolumesFrom, } + hostConfig := &HostConfig{ + Binds: flBinds, + } if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") @@ -164,7 +191,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet if config.OpenStdin && config.AttachStdin { config.StdinOnce = true } - return config, cmd, nil + return config, hostConfig, cmd, nil } type NetworkSettings struct { @@ -430,7 +457,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s }) } -func (container *Container) Start() error { +func (container *Container) Start(hostConfig *HostConfig) error { container.State.lock() defer container.State.unlock() @@ -454,17 +481,71 @@ func (container *Container) Start() error { container.Config.MemorySwap = -1 } container.Volumes = make(map[string]string) + container.VolumesRW = make(map[string]bool) + // Create the requested bind mounts + binds := make(map[string]BindMap) + // Define illegal container destinations + illegal_dsts := []string{"/", "."} + + for _, bind := range hostConfig.Binds { + // FIXME: factorize bind parsing in parseBind + var src, dst, mode string + arr := strings.Split(bind, ":") + if len(arr) == 2 { + src = arr[0] + dst = arr[1] + mode = "rw" + } else if len(arr) == 3 { + src = arr[0] + dst = arr[1] + mode = arr[2] + } else { + return fmt.Errorf("Invalid bind specification: %s", bind) + } + + // Bail if trying to mount to an illegal destination + for _, illegal := range illegal_dsts { + if dst == illegal { + return fmt.Errorf("Illegal bind destination: %s", dst) + } + } + + bindMap := BindMap{ + SrcPath: src, + DstPath: dst, + Mode: mode, + } + binds[path.Clean(dst)] = bindMap + } + + // FIXME: evaluate volumes-from before individual volumes, so that the latter can override the former. // Create the requested volumes volumes for volPath := range container.Config.Volumes { - c, err := container.runtime.volumes.Create(nil, container, "", "", nil) - if err != nil { - return err + volPath = path.Clean(volPath) + // If an external bind is defined for this volume, use that as a source + if bindMap, exists := binds[volPath]; exists { + container.Volumes[volPath] = bindMap.SrcPath + if strings.ToLower(bindMap.Mode) == "rw" { + container.VolumesRW[volPath] = true + } + // Otherwise create an directory in $ROOT/volumes/ and use that + } else { + c, err := container.runtime.volumes.Create(nil, container, "", "", nil) + if err != nil { + return err + } + srcPath, err := c.layer() + if err != nil { + return err + } + container.Volumes[volPath] = srcPath + container.VolumesRW[volPath] = true // RW by default } + // Create the mountpoint if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { return nil } - container.Volumes[volPath] = c.ID } if container.Config.VolumesFrom != "" { @@ -552,7 +633,8 @@ func (container *Container) Start() error { } func (container *Container) Run() error { - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { return err } container.Wait() @@ -565,7 +647,8 @@ func (container *Container) Output() (output []byte, err error) { return nil, err } defer pipe.Close() - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { return nil, err } output, err = ioutil.ReadAll(pipe) @@ -768,7 +851,8 @@ func (container *Container) Restart(seconds int) error { if err := container.Stop(seconds); err != nil { return err } - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { return err } return nil @@ -891,22 +975,6 @@ func (container *Container) RootfsPath() string { return path.Join(container.root, "rootfs") } -func (container *Container) GetVolumes() (map[string]string, error) { - ret := make(map[string]string) - for volPath, id := range container.Volumes { - volume, err := container.runtime.volumes.Get(id) - if err != nil { - return nil, err - } - root, err := volume.root() - if err != nil { - return nil, err - } - ret[volPath] = path.Join(root, "layer") - } - return ret, nil -} - func (container *Container) rwPath() string { return path.Join(container.root, "rw") } diff --git a/container_test.go b/container_test.go index 736f6814d0..cb33dcf983 100644 --- a/container_test.go +++ b/container_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "math/rand" "os" + "path" "regexp" "sort" "strings" @@ -15,10 +16,7 @@ import ( ) func TestIDFormat(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) container1, err := NewBuilder(runtime).Create( &Config{ @@ -39,10 +37,7 @@ func TestIDFormat(t *testing.T) { } func TestMultipleAttachRestart(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) container, err := NewBuilder(runtime).Create( &Config{ @@ -70,7 +65,8 @@ func TestMultipleAttachRestart(t *testing.T) { if err != nil { t.Fatal(err) } - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } l1, err := bufio.NewReader(stdout1).ReadString('\n') @@ -111,7 +107,7 @@ func TestMultipleAttachRestart(t *testing.T) { if err != nil { t.Fatal(err) } - if err := container.Start(); err != nil { + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } @@ -142,10 +138,7 @@ func TestMultipleAttachRestart(t *testing.T) { } func TestDiff(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) builder := NewBuilder(runtime) @@ -251,10 +244,7 @@ func TestDiff(t *testing.T) { } func TestCommitAutoRun(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) builder := NewBuilder(runtime) @@ -306,7 +296,8 @@ func TestCommitAutoRun(t *testing.T) { if err != nil { t.Fatal(err) } - if err := container2.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container2.Start(hostConfig); err != nil { t.Fatal(err) } container2.Wait() @@ -330,10 +321,7 @@ func TestCommitAutoRun(t *testing.T) { } func TestCommitRun(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) builder := NewBuilder(runtime) @@ -388,7 +376,8 @@ func TestCommitRun(t *testing.T) { if err != nil { t.Fatal(err) } - if err := container2.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container2.Start(hostConfig); err != nil { t.Fatal(err) } container2.Wait() @@ -412,10 +401,7 @@ func TestCommitRun(t *testing.T) { } func TestStart(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) container, err := NewBuilder(runtime).Create( &Config{ @@ -436,7 +422,8 @@ func TestStart(t *testing.T) { t.Fatal(err) } - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } @@ -446,7 +433,7 @@ func TestStart(t *testing.T) { if !container.State.Running { t.Errorf("Container should be running") } - if err := container.Start(); err == nil { + if err := container.Start(hostConfig); err == nil { t.Fatalf("A running containter should be able to be started") } @@ -456,10 +443,7 @@ func TestStart(t *testing.T) { } func TestRun(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) container, err := NewBuilder(runtime).Create( &Config{ @@ -484,10 +468,7 @@ func TestRun(t *testing.T) { } func TestOutput(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) container, err := NewBuilder(runtime).Create( &Config{ @@ -509,10 +490,7 @@ func TestOutput(t *testing.T) { } func TestKillDifferentUser(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).ID, @@ -528,7 +506,8 @@ func TestKillDifferentUser(t *testing.T) { if container.State.Running { t.Errorf("Container shouldn't be running") } - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } @@ -558,13 +537,10 @@ func TestKillDifferentUser(t *testing.T) { // Test that creating a container with a volume doesn't crash. Regression test for #995. func TestCreateVolume(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) - config, _, err := ParseRun([]string{"-v", "/var/lib/data", GetTestImage(runtime).ID, "echo", "hello", "world"}, nil) + config, hc, _, err := ParseRun([]string{"-v", "/var/lib/data", GetTestImage(runtime).ID, "echo", "hello", "world"}, nil) if err != nil { t.Fatal(err) } @@ -573,7 +549,7 @@ func TestCreateVolume(t *testing.T) { t.Fatal(err) } defer runtime.Destroy(c) - if err := c.Start(); err != nil { + if err := c.Start(hc); err != nil { t.Fatal(err) } c.WaitTimeout(500 * time.Millisecond) @@ -581,10 +557,7 @@ func TestCreateVolume(t *testing.T) { } func TestKill(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).ID, @@ -599,7 +572,8 @@ func TestKill(t *testing.T) { if container.State.Running { t.Errorf("Container shouldn't be running") } - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } @@ -626,10 +600,7 @@ func TestKill(t *testing.T) { } func TestExitCode(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) builder := NewBuilder(runtime) @@ -666,10 +637,7 @@ func TestExitCode(t *testing.T) { } func TestRestart(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).ID, @@ -699,10 +667,7 @@ func TestRestart(t *testing.T) { } func TestRestartStdin(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).ID, @@ -724,7 +689,8 @@ func TestRestartStdin(t *testing.T) { if err != nil { t.Fatal(err) } - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } if _, err := io.WriteString(stdin, "hello world"); err != nil { @@ -754,7 +720,7 @@ func TestRestartStdin(t *testing.T) { if err != nil { t.Fatal(err) } - if err := container.Start(); err != nil { + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } if _, err := io.WriteString(stdin, "hello world #2"); err != nil { @@ -777,10 +743,7 @@ func TestRestartStdin(t *testing.T) { } func TestUser(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) builder := NewBuilder(runtime) @@ -887,10 +850,7 @@ func TestUser(t *testing.T) { } func TestMultipleContainers(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) builder := NewBuilder(runtime) @@ -916,10 +876,11 @@ func TestMultipleContainers(t *testing.T) { defer runtime.Destroy(container2) // Start both containers - if err := container1.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container1.Start(hostConfig); err != nil { t.Fatal(err) } - if err := container2.Start(); err != nil { + if err := container2.Start(hostConfig); err != nil { t.Fatal(err) } @@ -946,10 +907,7 @@ func TestMultipleContainers(t *testing.T) { } func TestStdin(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).ID, @@ -971,7 +929,8 @@ func TestStdin(t *testing.T) { if err != nil { t.Fatal(err) } - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } defer stdin.Close() @@ -993,10 +952,7 @@ func TestStdin(t *testing.T) { } func TestTty(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).ID, @@ -1018,7 +974,8 @@ func TestTty(t *testing.T) { if err != nil { t.Fatal(err) } - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } defer stdin.Close() @@ -1040,10 +997,7 @@ func TestTty(t *testing.T) { } func TestEnv(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).ID, @@ -1060,7 +1014,8 @@ func TestEnv(t *testing.T) { t.Fatal(err) } defer stdout.Close() - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { t.Fatal(err) } container.Wait() @@ -1109,10 +1064,7 @@ func grepFile(t *testing.T, path string, pattern string) { } func TestLXCConfig(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) // Memory is allocated randomly for testing rand.Seed(time.Now().UTC().UnixNano()) @@ -1196,7 +1148,8 @@ func BenchmarkRunParallel(b *testing.B) { return } defer runtime.Destroy(container) - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { complete <- err return } @@ -1225,3 +1178,35 @@ func BenchmarkRunParallel(b *testing.B) { b.Fatal(errors) } } + +func tempDir(t *testing.T) string { + tmpDir, err := ioutil.TempDir("", "docker-test") + if err != nil { + t.Fatal(err) + } + return tmpDir +} + +func TestBindMounts(t *testing.T) { + r := mkRuntime(t) + defer nuke(r) + tmpDir := tempDir(t) + defer os.RemoveAll(tmpDir) + writeFile(path.Join(tmpDir, "touch-me"), "", t) + + // Test reading from a read-only bind mount + stdout, _ := runContainer(r, []string{"-b", fmt.Sprintf("%s:/tmp:ro", tmpDir), "_", "ls", "/tmp"}, t) + if !strings.Contains(stdout, "touch-me") { + t.Fatal("Container failed to read from bind mount") + } + + // test writing to bind mount + runContainer(r, []string{"-b", fmt.Sprintf("%s:/tmp:rw", tmpDir), "_", "touch", "/tmp/holla"}, t) + readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist + + // test mounting to an illegal destination directory + if _, err := runContainer(r, []string{"-b", fmt.Sprintf("%s:.", tmpDir), "ls", "."}, nil); err == nil { + t.Fatal("Container bind mounted illegal directory") + + } +} diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 5c3a7ba2cb..c6e50a3f06 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -42,6 +42,9 @@ List containers (/containers/json): - You can use size=1 to get the size of the containers +Start containers (/containers//start): + +- You can now pass host-specific configuration (e.g. bind mounts) in the POST body for start calls :doc:`docker_remote_api_v1.2` ***************************** diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index a8e14ec461..8eeb010d98 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -294,23 +294,30 @@ Start a container .. http:post:: /containers/(id)/start - Start the container ``id`` + Start the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/e90e34656806/start HTTP/1.1 - - **Example response**: + POST /containers/(id)/start HTTP/1.1 + Content-Type: application/json - .. sourcecode:: http + { + "Binds":["/tmp:/tmp"] + } - HTTP/1.1 200 OK - - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 No Content + Content-Type: text/plain + + :jsonparam hostConfig: the container's host configuration (optional) + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error Stop a contaier diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index 8227d9f310..c119ce11e2 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -25,3 +25,4 @@ -d=[]: Set custom dns servers for the container -v=[]: Creates a new volume and mounts it at the specified path. -volumes-from="": Mount all volumes from the given container. + -b=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro] diff --git a/lxc_template.go b/lxc_template.go index 45408d4bfb..93b795e901 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -84,8 +84,9 @@ lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0 # In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container lxc.mount.entry = {{.ResolvConfPath}} {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0 {{if .Volumes}} -{{range $virtualPath, $realPath := .GetVolumes}} -lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,rw 0 0 +{{ $rw := .VolumesRW }} +{{range $virtualPath, $realPath := .Volumes}} +lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,{{ if index $rw $virtualPath }}rw{{else}}ro{{end}} 0 0 {{end}} {{end}} diff --git a/runtime.go b/runtime.go index 29f5067931..06b1f8e1b9 100644 --- a/runtime.go +++ b/runtime.go @@ -144,7 +144,9 @@ func (runtime *Runtime) Register(container *Container) error { utils.Debugf("Restarting") container.State.Ghost = false container.State.setStopped(0) - if err := container.Start(); err != nil { + // assume empty host config + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { return err } nomonitor = true diff --git a/runtime_test.go b/runtime_test.go index 23b1be0257..61a8f346f7 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -327,7 +327,8 @@ func findAvailalblePort(runtime *Runtime, port int) (*Container, error) { if err != nil { return nil, err } - if err := container.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { if strings.Contains(err.Error(), "address already in use") { return nil, nil } @@ -437,7 +438,8 @@ func TestRestore(t *testing.T) { defer runtime1.Destroy(container2) // Start the container non blocking - if err := container2.Start(); err != nil { + hostConfig := &HostConfig{} + if err := container2.Start(hostConfig); err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index ee023044a0..cdc01c6b09 100644 --- a/server.go +++ b/server.go @@ -87,7 +87,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. } defer file.Body.Close() - config, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities) + config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities) if err != nil { return "", err } @@ -934,9 +934,9 @@ func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error) return nil, nil } -func (srv *Server) ContainerStart(name string) error { +func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { if container := srv.runtime.Get(name); container != nil { - if err := container.Start(); err != nil { + if err := container.Start(hostConfig); err != nil { return fmt.Errorf("Error starting container %s: %s", name, err.Error()) } } else { diff --git a/server_test.go b/server_test.go index 7fdec18f61..8d1ea8be94 100644 --- a/server_test.go +++ b/server_test.go @@ -65,7 +65,7 @@ func TestCreateRm(t *testing.T) { srv := &Server{runtime: runtime} - config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) + config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) if err != nil { t.Fatal(err) } @@ -98,7 +98,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { srv := &Server{runtime: runtime} - config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil) + config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil) if err != nil { t.Fatal(err) } @@ -112,7 +112,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { t.Errorf("Expected 1 container, %v found", len(runtime.List())) } - err = srv.ContainerStart(id) + err = srv.ContainerStart(id, hostConfig) if err != nil { t.Fatal(err) } @@ -127,7 +127,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { t.Fatal(err) } - err = srv.ContainerStart(id) + err = srv.ContainerStart(id, hostConfig) if err != nil { t.Fatal(err) } diff --git a/utils_test.go b/utils_test.go new file mode 100644 index 0000000000..5958aa70fe --- /dev/null +++ b/utils_test.go @@ -0,0 +1,103 @@ +package docker + +import ( + "io" + "io/ioutil" + "os" + "path" + "strings" + "testing" +) + +// This file contains utility functions for docker's unit test suite. +// It has to be named XXX_test.go, apparently, in other to access private functions +// from other XXX_test.go functions. + +// Create a temporary runtime suitable for unit testing. +// Call t.Fatal() at the first error. +func mkRuntime(t *testing.T) *Runtime { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + return runtime +} + +// Write `content` to the file at path `dst`, creating it if necessary, +// as well as any missing directories. +// The file is truncated if it already exists. +// Call t.Fatal() at the first error. +func writeFile(dst, content string, t *testing.T) { + // Create subdirectories if necessary + if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) { + t.Fatal(err) + } + f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) + if err != nil { + t.Fatal(err) + } + // Write content (truncate if it exists) + if _, err := io.Copy(f, strings.NewReader(content)); err != nil { + t.Fatal(err) + } +} + +// Return the contents of file at path `src`. +// Call t.Fatal() at the first error (including if the file doesn't exist) +func readFile(src string, t *testing.T) (content string) { + f, err := os.Open(src) + if err != nil { + t.Fatal(err) + } + data, err := ioutil.ReadAll(f) + if err != nil { + t.Fatal(err) + } + return string(data) +} + +// Create a test container from the given runtime `r` and run arguments `args`. +// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image. +// The caller is responsible for destroying the container. +// Call t.Fatal() at the first error. +func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig) { + config, hostConfig, _, err := ParseRun(args, nil) + if err != nil { + t.Fatal(err) + } + config.Image = GetTestImage(r).ID + c, err := NewBuilder(r).Create(config) + if err != nil { + t.Fatal(err) + } + return c, hostConfig +} + +// Create a test container, start it, wait for it to complete, destroy it, +// and return its standard output as a string. +// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image. +// If t is not nil, call t.Fatal() at the first error. Otherwise return errors normally. +func runContainer(r *Runtime, args []string, t *testing.T) (output string, err error) { + defer func() { + if err != nil && t != nil { + t.Fatal(err) + } + }() + container, hostConfig := mkContainer(r, args, t) + defer r.Destroy(container) + stdout, err := container.StdoutPipe() + if err != nil { + return "", err + } + defer stdout.Close() + if err := container.Start(hostConfig); err != nil { + return "", err + } + container.Wait() + data, err := ioutil.ReadAll(stdout) + if err != nil { + return "", err + } + output = string(data) + return +}