mirror of https://github.com/docker/docs.git
Merge pull request #602 from gabrtv/111-bind-mounts
+ Runtime: mount volumes from a host directory with 'docker run -b'
This commit is contained in:
commit
3e29695c1f
11
api.go
11
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)
|
||||
|
|
26
api_test.go
26
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
118
container.go
118
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 {
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@ List containers (/containers/json):
|
|||
|
||||
- You can use size=1 to get the size of the containers
|
||||
|
||||
Start containers (/containers/<id>/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`
|
||||
*****************************
|
||||
|
|
|
@ -300,14 +300,21 @@ Start a container
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /containers/e90e34656806/start HTTP/1.1
|
||||
POST /containers/(id)/start HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Binds":["/tmp:/tmp"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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}}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue