| ... | ... |
@@ -551,11 +551,20 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R |
| 551 | 551 |
} |
| 552 | 552 |
|
| 553 | 553 |
func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| 554 |
+ hostConfig := &HostConfig{}
|
|
| 555 |
+ |
|
| 556 |
+ // allow a nil body for backwards compatibility |
|
| 557 |
+ if r.Body != nil {
|
|
| 558 |
+ if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
|
|
| 559 |
+ return err |
|
| 560 |
+ } |
|
| 561 |
+ } |
|
| 562 |
+ |
|
| 554 | 563 |
if vars == nil {
|
| 555 | 564 |
return fmt.Errorf("Missing parameter")
|
| 556 | 565 |
} |
| 557 | 566 |
name := vars["name"] |
| 558 |
- if err := srv.ContainerStart(name); err != nil {
|
|
| 567 |
+ if err := srv.ContainerStart(name, hostConfig); err != nil {
|
|
| 559 | 568 |
return err |
| 560 | 569 |
} |
| 561 | 570 |
w.WriteHeader(http.StatusNoContent) |
| ... | ... |
@@ -873,7 +873,8 @@ func TestPostContainersKill(t *testing.T) {
|
| 873 | 873 |
} |
| 874 | 874 |
defer runtime.Destroy(container) |
| 875 | 875 |
|
| 876 |
- if err := container.Start(); err != nil {
|
|
| 876 |
+ hostConfig := &HostConfig{}
|
|
| 877 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 877 | 878 |
t.Fatal(err) |
| 878 | 879 |
} |
| 879 | 880 |
|
| ... | ... |
@@ -917,7 +918,8 @@ func TestPostContainersRestart(t *testing.T) {
|
| 917 | 917 |
} |
| 918 | 918 |
defer runtime.Destroy(container) |
| 919 | 919 |
|
| 920 |
- if err := container.Start(); err != nil {
|
|
| 920 |
+ hostConfig := &HostConfig{}
|
|
| 921 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 921 | 922 |
t.Fatal(err) |
| 922 | 923 |
} |
| 923 | 924 |
|
| ... | ... |
@@ -973,8 +975,15 @@ func TestPostContainersStart(t *testing.T) {
|
| 973 | 973 |
} |
| 974 | 974 |
defer runtime.Destroy(container) |
| 975 | 975 |
|
| 976 |
+ hostConfigJSON, err := json.Marshal(&HostConfig{})
|
|
| 977 |
+ |
|
| 978 |
+ req, err := http.NewRequest("POST", "/containers/"+container.ID+"/start", bytes.NewReader(hostConfigJSON))
|
|
| 979 |
+ if err != nil {
|
|
| 980 |
+ t.Fatal(err) |
|
| 981 |
+ } |
|
| 982 |
+ |
|
| 976 | 983 |
r := httptest.NewRecorder() |
| 977 |
- if err := postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
|
|
| 984 |
+ if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
|
|
| 978 | 985 |
t.Fatal(err) |
| 979 | 986 |
} |
| 980 | 987 |
if r.Code != http.StatusNoContent {
|
| ... | ... |
@@ -989,7 +998,7 @@ func TestPostContainersStart(t *testing.T) {
|
| 989 | 989 |
} |
| 990 | 990 |
|
| 991 | 991 |
r = httptest.NewRecorder() |
| 992 |
- if err = postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err == nil {
|
|
| 992 |
+ if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil {
|
|
| 993 | 993 |
t.Fatalf("A running containter should be able to be started")
|
| 994 | 994 |
} |
| 995 | 995 |
|
| ... | ... |
@@ -1019,7 +1028,8 @@ func TestPostContainersStop(t *testing.T) {
|
| 1019 | 1019 |
} |
| 1020 | 1020 |
defer runtime.Destroy(container) |
| 1021 | 1021 |
|
| 1022 |
- if err := container.Start(); err != nil {
|
|
| 1022 |
+ hostConfig := &HostConfig{}
|
|
| 1023 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 1023 | 1024 |
t.Fatal(err) |
| 1024 | 1025 |
} |
| 1025 | 1026 |
|
| ... | ... |
@@ -1068,7 +1078,8 @@ func TestPostContainersWait(t *testing.T) {
|
| 1068 | 1068 |
} |
| 1069 | 1069 |
defer runtime.Destroy(container) |
| 1070 | 1070 |
|
| 1071 |
- if err := container.Start(); err != nil {
|
|
| 1071 |
+ hostConfig := &HostConfig{}
|
|
| 1072 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 1072 | 1073 |
t.Fatal(err) |
| 1073 | 1074 |
} |
| 1074 | 1075 |
|
| ... | ... |
@@ -1113,7 +1124,8 @@ func TestPostContainersAttach(t *testing.T) {
|
| 1113 | 1113 |
defer runtime.Destroy(container) |
| 1114 | 1114 |
|
| 1115 | 1115 |
// Start the process |
| 1116 |
- if err := container.Start(); err != nil {
|
|
| 1116 |
+ hostConfig := &HostConfig{}
|
|
| 1117 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 1117 | 1118 |
t.Fatal(err) |
| 1118 | 1119 |
} |
| 1119 | 1120 |
|
| ... | ... |
@@ -87,7 +87,7 @@ func (b *buildFile) CmdRun(args string) error {
|
| 87 | 87 |
if b.image == "" {
|
| 88 | 88 |
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
| 89 | 89 |
} |
| 90 |
- config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
|
|
| 90 |
+ config, _, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
|
|
| 91 | 91 |
if err != nil {
|
| 92 | 92 |
return err |
| 93 | 93 |
} |
| ... | ... |
@@ -263,7 +263,8 @@ func (b *buildFile) run() (string, error) {
|
| 263 | 263 |
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID)) |
| 264 | 264 |
|
| 265 | 265 |
//start the container |
| 266 |
- if err := c.Start(); err != nil {
|
|
| 266 |
+ hostConfig := &HostConfig{}
|
|
| 267 |
+ if err := c.Start(hostConfig); err != nil {
|
|
| 267 | 268 |
return "", err |
| 268 | 269 |
} |
| 269 | 270 |
|
| ... | ... |
@@ -1235,7 +1235,7 @@ func (cli *DockerCli) CmdTag(args ...string) error {
|
| 1235 | 1235 |
} |
| 1236 | 1236 |
|
| 1237 | 1237 |
func (cli *DockerCli) CmdRun(args ...string) error {
|
| 1238 |
- config, cmd, err := ParseRun(args, nil) |
|
| 1238 |
+ config, hostConfig, cmd, err := ParseRun(args, nil) |
|
| 1239 | 1239 |
if err != nil {
|
| 1240 | 1240 |
return err |
| 1241 | 1241 |
} |
| ... | ... |
@@ -1274,7 +1274,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
| 1274 | 1274 |
} |
| 1275 | 1275 |
|
| 1276 | 1276 |
//start the container |
| 1277 |
- if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", nil); err != nil {
|
|
| 1277 |
+ if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
|
|
| 1278 | 1278 |
return err |
| 1279 | 1279 |
} |
| 1280 | 1280 |
|
| ... | ... |
@@ -52,6 +52,8 @@ type Container struct {
|
| 52 | 52 |
|
| 53 | 53 |
waitLock chan struct{}
|
| 54 | 54 |
Volumes map[string]string |
| 55 |
+ |
|
| 56 |
+ Binds []BindMap |
|
| 55 | 57 |
} |
| 56 | 58 |
|
| 57 | 59 |
type Config struct {
|
| ... | ... |
@@ -75,7 +77,17 @@ type Config struct {
|
| 75 | 75 |
VolumesFrom string |
| 76 | 76 |
} |
| 77 | 77 |
|
| 78 |
-func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) {
|
|
| 78 |
+type HostConfig struct {
|
|
| 79 |
+ Binds []string |
|
| 80 |
+} |
|
| 81 |
+ |
|
| 82 |
+type BindMap struct {
|
|
| 83 |
+ SrcPath string |
|
| 84 |
+ DstPath string |
|
| 85 |
+ Mode string |
|
| 86 |
+} |
|
| 87 |
+ |
|
| 88 |
+func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
|
| 79 | 89 |
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
|
| 80 | 90 |
if len(args) > 0 && args[0] != "--help" {
|
| 81 | 91 |
cmd.SetOutput(ioutil.Discard) |
| ... | ... |
@@ -111,11 +123,14 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet |
| 111 | 111 |
|
| 112 | 112 |
flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
|
| 113 | 113 |
|
| 114 |
+ var flBinds ListOpts |
|
| 115 |
+ cmd.Var(&flBinds, "b", "Bind mount a volume from the host (e.g. -b /host:/container)") |
|
| 116 |
+ |
|
| 114 | 117 |
if err := cmd.Parse(args); err != nil {
|
| 115 |
- return nil, cmd, err |
|
| 118 |
+ return nil, nil, cmd, err |
|
| 116 | 119 |
} |
| 117 | 120 |
if *flDetach && len(flAttach) > 0 {
|
| 118 |
- return nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
|
|
| 121 |
+ return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
|
|
| 119 | 122 |
} |
| 120 | 123 |
// If neither -d or -a are set, attach to everything by default |
| 121 | 124 |
if len(flAttach) == 0 && !*flDetach {
|
| ... | ... |
@@ -127,6 +142,15 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet |
| 127 | 127 |
} |
| 128 | 128 |
} |
| 129 | 129 |
} |
| 130 |
+ |
|
| 131 |
+ // add any bind targets to the list of container volumes |
|
| 132 |
+ type empty struct{}
|
|
| 133 |
+ for _, bind := range flBinds {
|
|
| 134 |
+ arr := strings.Split(bind, ":") |
|
| 135 |
+ dstDir := arr[1] |
|
| 136 |
+ flVolumes[dstDir] = empty{}
|
|
| 137 |
+ } |
|
| 138 |
+ |
|
| 130 | 139 |
parsedArgs := cmd.Args() |
| 131 | 140 |
runCmd := []string{}
|
| 132 | 141 |
image := "" |
| ... | ... |
@@ -154,6 +178,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet |
| 154 | 154 |
Volumes: flVolumes, |
| 155 | 155 |
VolumesFrom: *flVolumesFrom, |
| 156 | 156 |
} |
| 157 |
+ hostConfig := &HostConfig{
|
|
| 158 |
+ Binds: flBinds, |
|
| 159 |
+ } |
|
| 157 | 160 |
|
| 158 | 161 |
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
|
| 159 | 162 |
//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 |
| 164 | 164 |
if config.OpenStdin && config.AttachStdin {
|
| 165 | 165 |
config.StdinOnce = true |
| 166 | 166 |
} |
| 167 |
- return config, cmd, nil |
|
| 167 |
+ return config, hostConfig, cmd, nil |
|
| 168 | 168 |
} |
| 169 | 169 |
|
| 170 | 170 |
type NetworkSettings struct {
|
| ... | ... |
@@ -430,7 +457,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s |
| 430 | 430 |
}) |
| 431 | 431 |
} |
| 432 | 432 |
|
| 433 |
-func (container *Container) Start() error {
|
|
| 433 |
+func (container *Container) Start(hostConfig *HostConfig) error {
|
|
| 434 | 434 |
container.State.lock() |
| 435 | 435 |
defer container.State.unlock() |
| 436 | 436 |
|
| ... | ... |
@@ -483,6 +510,42 @@ func (container *Container) Start() error {
|
| 483 | 483 |
} |
| 484 | 484 |
} |
| 485 | 485 |
|
| 486 |
+ // Create the requested bind mounts |
|
| 487 |
+ binds := []BindMap{}
|
|
| 488 |
+ // Define illegal container destinations |
|
| 489 |
+ illegal_dsts := []string{"/", "."}
|
|
| 490 |
+ |
|
| 491 |
+ for _, bind := range hostConfig.Binds {
|
|
| 492 |
+ var src, dst, mode string |
|
| 493 |
+ arr := strings.Split(bind, ":") |
|
| 494 |
+ if len(arr) == 2 {
|
|
| 495 |
+ src = arr[0] |
|
| 496 |
+ dst = arr[1] |
|
| 497 |
+ mode = "rw" |
|
| 498 |
+ } else if len(arr) == 3 {
|
|
| 499 |
+ src = arr[0] |
|
| 500 |
+ dst = arr[1] |
|
| 501 |
+ mode = arr[2] |
|
| 502 |
+ } else {
|
|
| 503 |
+ return fmt.Errorf("Invalid bind specification: %s", bind)
|
|
| 504 |
+ } |
|
| 505 |
+ |
|
| 506 |
+ // Bail if trying to mount to an illegal destination |
|
| 507 |
+ for _, illegal := range illegal_dsts {
|
|
| 508 |
+ if dst == illegal {
|
|
| 509 |
+ return fmt.Errorf("Illegal bind destination: %s", dst)
|
|
| 510 |
+ } |
|
| 511 |
+ } |
|
| 512 |
+ |
|
| 513 |
+ bindMap := BindMap{
|
|
| 514 |
+ SrcPath: src, |
|
| 515 |
+ DstPath: dst, |
|
| 516 |
+ Mode: mode, |
|
| 517 |
+ } |
|
| 518 |
+ binds = append(binds, bindMap) |
|
| 519 |
+ } |
|
| 520 |
+ container.Binds = binds |
|
| 521 |
+ |
|
| 486 | 522 |
if err := container.generateLXCConfig(); err != nil {
|
| 487 | 523 |
return err |
| 488 | 524 |
} |
| ... | ... |
@@ -552,7 +615,8 @@ func (container *Container) Start() error {
|
| 552 | 552 |
} |
| 553 | 553 |
|
| 554 | 554 |
func (container *Container) Run() error {
|
| 555 |
- if err := container.Start(); err != nil {
|
|
| 555 |
+ hostConfig := &HostConfig{}
|
|
| 556 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 556 | 557 |
return err |
| 557 | 558 |
} |
| 558 | 559 |
container.Wait() |
| ... | ... |
@@ -565,7 +629,8 @@ func (container *Container) Output() (output []byte, err error) {
|
| 565 | 565 |
return nil, err |
| 566 | 566 |
} |
| 567 | 567 |
defer pipe.Close() |
| 568 |
- if err := container.Start(); err != nil {
|
|
| 568 |
+ hostConfig := &HostConfig{}
|
|
| 569 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 569 | 570 |
return nil, err |
| 570 | 571 |
} |
| 571 | 572 |
output, err = ioutil.ReadAll(pipe) |
| ... | ... |
@@ -768,7 +833,8 @@ func (container *Container) Restart(seconds int) error {
|
| 768 | 768 |
if err := container.Stop(seconds); err != nil {
|
| 769 | 769 |
return err |
| 770 | 770 |
} |
| 771 |
- if err := container.Start(); err != nil {
|
|
| 771 |
+ hostConfig := &HostConfig{}
|
|
| 772 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 772 | 773 |
return err |
| 773 | 774 |
} |
| 774 | 775 |
return nil |
| ... | ... |
@@ -7,6 +7,7 @@ import ( |
| 7 | 7 |
"io/ioutil" |
| 8 | 8 |
"math/rand" |
| 9 | 9 |
"os" |
| 10 |
+ "path" |
|
| 10 | 11 |
"regexp" |
| 11 | 12 |
"sort" |
| 12 | 13 |
"strings" |
| ... | ... |
@@ -70,7 +71,8 @@ func TestMultipleAttachRestart(t *testing.T) {
|
| 70 | 70 |
if err != nil {
|
| 71 | 71 |
t.Fatal(err) |
| 72 | 72 |
} |
| 73 |
- if err := container.Start(); err != nil {
|
|
| 73 |
+ hostConfig := &HostConfig{}
|
|
| 74 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 74 | 75 |
t.Fatal(err) |
| 75 | 76 |
} |
| 76 | 77 |
l1, err := bufio.NewReader(stdout1).ReadString('\n')
|
| ... | ... |
@@ -111,7 +113,7 @@ func TestMultipleAttachRestart(t *testing.T) {
|
| 111 | 111 |
if err != nil {
|
| 112 | 112 |
t.Fatal(err) |
| 113 | 113 |
} |
| 114 |
- if err := container.Start(); err != nil {
|
|
| 114 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 115 | 115 |
t.Fatal(err) |
| 116 | 116 |
} |
| 117 | 117 |
|
| ... | ... |
@@ -306,7 +308,8 @@ func TestCommitAutoRun(t *testing.T) {
|
| 306 | 306 |
if err != nil {
|
| 307 | 307 |
t.Fatal(err) |
| 308 | 308 |
} |
| 309 |
- if err := container2.Start(); err != nil {
|
|
| 309 |
+ hostConfig := &HostConfig{}
|
|
| 310 |
+ if err := container2.Start(hostConfig); err != nil {
|
|
| 310 | 311 |
t.Fatal(err) |
| 311 | 312 |
} |
| 312 | 313 |
container2.Wait() |
| ... | ... |
@@ -388,7 +391,8 @@ func TestCommitRun(t *testing.T) {
|
| 388 | 388 |
if err != nil {
|
| 389 | 389 |
t.Fatal(err) |
| 390 | 390 |
} |
| 391 |
- if err := container2.Start(); err != nil {
|
|
| 391 |
+ hostConfig := &HostConfig{}
|
|
| 392 |
+ if err := container2.Start(hostConfig); err != nil {
|
|
| 392 | 393 |
t.Fatal(err) |
| 393 | 394 |
} |
| 394 | 395 |
container2.Wait() |
| ... | ... |
@@ -436,7 +440,8 @@ func TestStart(t *testing.T) {
|
| 436 | 436 |
t.Fatal(err) |
| 437 | 437 |
} |
| 438 | 438 |
|
| 439 |
- if err := container.Start(); err != nil {
|
|
| 439 |
+ hostConfig := &HostConfig{}
|
|
| 440 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 440 | 441 |
t.Fatal(err) |
| 441 | 442 |
} |
| 442 | 443 |
|
| ... | ... |
@@ -446,7 +451,7 @@ func TestStart(t *testing.T) {
|
| 446 | 446 |
if !container.State.Running {
|
| 447 | 447 |
t.Errorf("Container should be running")
|
| 448 | 448 |
} |
| 449 |
- if err := container.Start(); err == nil {
|
|
| 449 |
+ if err := container.Start(hostConfig); err == nil {
|
|
| 450 | 450 |
t.Fatalf("A running containter should be able to be started")
|
| 451 | 451 |
} |
| 452 | 452 |
|
| ... | ... |
@@ -528,7 +533,8 @@ func TestKillDifferentUser(t *testing.T) {
|
| 528 | 528 |
if container.State.Running {
|
| 529 | 529 |
t.Errorf("Container shouldn't be running")
|
| 530 | 530 |
} |
| 531 |
- if err := container.Start(); err != nil {
|
|
| 531 |
+ hostConfig := &HostConfig{}
|
|
| 532 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 532 | 533 |
t.Fatal(err) |
| 533 | 534 |
} |
| 534 | 535 |
|
| ... | ... |
@@ -599,7 +605,8 @@ func TestKill(t *testing.T) {
|
| 599 | 599 |
if container.State.Running {
|
| 600 | 600 |
t.Errorf("Container shouldn't be running")
|
| 601 | 601 |
} |
| 602 |
- if err := container.Start(); err != nil {
|
|
| 602 |
+ hostConfig := &HostConfig{}
|
|
| 603 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 603 | 604 |
t.Fatal(err) |
| 604 | 605 |
} |
| 605 | 606 |
|
| ... | ... |
@@ -724,7 +731,8 @@ func TestRestartStdin(t *testing.T) {
|
| 724 | 724 |
if err != nil {
|
| 725 | 725 |
t.Fatal(err) |
| 726 | 726 |
} |
| 727 |
- if err := container.Start(); err != nil {
|
|
| 727 |
+ hostConfig := &HostConfig{}
|
|
| 728 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 728 | 729 |
t.Fatal(err) |
| 729 | 730 |
} |
| 730 | 731 |
if _, err := io.WriteString(stdin, "hello world"); err != nil {
|
| ... | ... |
@@ -754,7 +762,7 @@ func TestRestartStdin(t *testing.T) {
|
| 754 | 754 |
if err != nil {
|
| 755 | 755 |
t.Fatal(err) |
| 756 | 756 |
} |
| 757 |
- if err := container.Start(); err != nil {
|
|
| 757 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 758 | 758 |
t.Fatal(err) |
| 759 | 759 |
} |
| 760 | 760 |
if _, err := io.WriteString(stdin, "hello world #2"); err != nil {
|
| ... | ... |
@@ -916,10 +924,11 @@ func TestMultipleContainers(t *testing.T) {
|
| 916 | 916 |
defer runtime.Destroy(container2) |
| 917 | 917 |
|
| 918 | 918 |
// Start both containers |
| 919 |
- if err := container1.Start(); err != nil {
|
|
| 919 |
+ hostConfig := &HostConfig{}
|
|
| 920 |
+ if err := container1.Start(hostConfig); err != nil {
|
|
| 920 | 921 |
t.Fatal(err) |
| 921 | 922 |
} |
| 922 |
- if err := container2.Start(); err != nil {
|
|
| 923 |
+ if err := container2.Start(hostConfig); err != nil {
|
|
| 923 | 924 |
t.Fatal(err) |
| 924 | 925 |
} |
| 925 | 926 |
|
| ... | ... |
@@ -971,7 +980,8 @@ func TestStdin(t *testing.T) {
|
| 971 | 971 |
if err != nil {
|
| 972 | 972 |
t.Fatal(err) |
| 973 | 973 |
} |
| 974 |
- if err := container.Start(); err != nil {
|
|
| 974 |
+ hostConfig := &HostConfig{}
|
|
| 975 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 975 | 976 |
t.Fatal(err) |
| 976 | 977 |
} |
| 977 | 978 |
defer stdin.Close() |
| ... | ... |
@@ -1018,7 +1028,8 @@ func TestTty(t *testing.T) {
|
| 1018 | 1018 |
if err != nil {
|
| 1019 | 1019 |
t.Fatal(err) |
| 1020 | 1020 |
} |
| 1021 |
- if err := container.Start(); err != nil {
|
|
| 1021 |
+ hostConfig := &HostConfig{}
|
|
| 1022 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 1022 | 1023 |
t.Fatal(err) |
| 1023 | 1024 |
} |
| 1024 | 1025 |
defer stdin.Close() |
| ... | ... |
@@ -1060,7 +1071,8 @@ func TestEnv(t *testing.T) {
|
| 1060 | 1060 |
t.Fatal(err) |
| 1061 | 1061 |
} |
| 1062 | 1062 |
defer stdout.Close() |
| 1063 |
- if err := container.Start(); err != nil {
|
|
| 1063 |
+ hostConfig := &HostConfig{}
|
|
| 1064 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 1064 | 1065 |
t.Fatal(err) |
| 1065 | 1066 |
} |
| 1066 | 1067 |
container.Wait() |
| ... | ... |
@@ -1196,7 +1208,8 @@ func BenchmarkRunParallel(b *testing.B) {
|
| 1196 | 1196 |
return |
| 1197 | 1197 |
} |
| 1198 | 1198 |
defer runtime.Destroy(container) |
| 1199 |
- if err := container.Start(); err != nil {
|
|
| 1199 |
+ hostConfig := &HostConfig{}
|
|
| 1200 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 1200 | 1201 |
complete <- err |
| 1201 | 1202 |
return |
| 1202 | 1203 |
} |
| ... | ... |
@@ -1225,3 +1238,98 @@ func BenchmarkRunParallel(b *testing.B) {
|
| 1225 | 1225 |
b.Fatal(errors) |
| 1226 | 1226 |
} |
| 1227 | 1227 |
} |
| 1228 |
+ |
|
| 1229 |
+func TestBindMounts(t *testing.T) {
|
|
| 1230 |
+ runtime, err := newTestRuntime() |
|
| 1231 |
+ if err != nil {
|
|
| 1232 |
+ t.Fatal(err) |
|
| 1233 |
+ } |
|
| 1234 |
+ defer nuke(runtime) |
|
| 1235 |
+ tmpDir, err := ioutil.TempDir("", "docker-test")
|
|
| 1236 |
+ if err != nil {
|
|
| 1237 |
+ t.Fatal(err) |
|
| 1238 |
+ } |
|
| 1239 |
+ tmpFile := path.Join(tmpDir, "touch-me") |
|
| 1240 |
+ _, err = os.Create(tmpFile) |
|
| 1241 |
+ if err != nil {
|
|
| 1242 |
+ t.Fatal(err) |
|
| 1243 |
+ } |
|
| 1244 |
+ // test reading from bind mount |
|
| 1245 |
+ bind_str := fmt.Sprintf("%s:/tmp:ro", tmpDir)
|
|
| 1246 |
+ container, err := NewBuilder(runtime).Create(&Config{
|
|
| 1247 |
+ Image: GetTestImage(runtime).ID, |
|
| 1248 |
+ Cmd: []string{"ls", "/tmp"},
|
|
| 1249 |
+ }, |
|
| 1250 |
+ ) |
|
| 1251 |
+ if err != nil {
|
|
| 1252 |
+ t.Fatal(err) |
|
| 1253 |
+ } |
|
| 1254 |
+ defer runtime.Destroy(container) |
|
| 1255 |
+ |
|
| 1256 |
+ stdout, err := container.StdoutPipe() |
|
| 1257 |
+ if err != nil {
|
|
| 1258 |
+ t.Fatal(err) |
|
| 1259 |
+ } |
|
| 1260 |
+ defer stdout.Close() |
|
| 1261 |
+ hostConfig := &HostConfig{
|
|
| 1262 |
+ Binds: []string{bind_str},
|
|
| 1263 |
+ } |
|
| 1264 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 1265 |
+ t.Fatal(err) |
|
| 1266 |
+ } |
|
| 1267 |
+ container.Wait() |
|
| 1268 |
+ output, err := ioutil.ReadAll(stdout) |
|
| 1269 |
+ if err != nil {
|
|
| 1270 |
+ t.Fatal(err) |
|
| 1271 |
+ } |
|
| 1272 |
+ if !strings.Contains(string(output), "touch-me") {
|
|
| 1273 |
+ t.Fatal("Container failed to read from bind mount")
|
|
| 1274 |
+ } |
|
| 1275 |
+ // test writing to bind mount |
|
| 1276 |
+ bind_str2 := fmt.Sprintf("%s:/tmp:rw", tmpDir)
|
|
| 1277 |
+ container2, err := NewBuilder(runtime).Create(&Config{
|
|
| 1278 |
+ Image: GetTestImage(runtime).ID, |
|
| 1279 |
+ Cmd: []string{"touch", "/tmp/holla"},
|
|
| 1280 |
+ }, |
|
| 1281 |
+ ) |
|
| 1282 |
+ if err != nil {
|
|
| 1283 |
+ t.Fatal(err) |
|
| 1284 |
+ } |
|
| 1285 |
+ defer runtime.Destroy(container2) |
|
| 1286 |
+ |
|
| 1287 |
+ hostConfig2 := &HostConfig{
|
|
| 1288 |
+ Binds: []string{bind_str2},
|
|
| 1289 |
+ } |
|
| 1290 |
+ if err := container2.Start(hostConfig2); err != nil {
|
|
| 1291 |
+ t.Fatal(err) |
|
| 1292 |
+ } |
|
| 1293 |
+ container2.Wait() |
|
| 1294 |
+ _, err = ioutil.ReadFile(tmpDir + "/holla") |
|
| 1295 |
+ if err != nil {
|
|
| 1296 |
+ t.Fatal("Container failed to write to bind mount")
|
|
| 1297 |
+ } |
|
| 1298 |
+ // test mounting to an illegal destination directory |
|
| 1299 |
+ bind_str3 := fmt.Sprintf("%s:.", tmpDir)
|
|
| 1300 |
+ container3, err := NewBuilder(runtime).Create(&Config{
|
|
| 1301 |
+ Image: GetTestImage(runtime).ID, |
|
| 1302 |
+ Cmd: []string{"ls", "."},
|
|
| 1303 |
+ }, |
|
| 1304 |
+ ) |
|
| 1305 |
+ if err != nil {
|
|
| 1306 |
+ t.Fatal(err) |
|
| 1307 |
+ } |
|
| 1308 |
+ defer runtime.Destroy(container3) |
|
| 1309 |
+ |
|
| 1310 |
+ stdout3, err := container3.StdoutPipe() |
|
| 1311 |
+ if err != nil {
|
|
| 1312 |
+ t.Fatal(err) |
|
| 1313 |
+ } |
|
| 1314 |
+ defer stdout3.Close() |
|
| 1315 |
+ hostConfig3 := &HostConfig{
|
|
| 1316 |
+ Binds: []string{bind_str3},
|
|
| 1317 |
+ } |
|
| 1318 |
+ if err := container3.Start(hostConfig3); err == nil {
|
|
| 1319 |
+ t.Fatal("Container bind mounted illegal directory")
|
|
| 1320 |
+ } |
|
| 1321 |
+ |
|
| 1322 |
+} |
| ... | ... |
@@ -42,6 +42,9 @@ List containers (/containers/json): |
| 42 | 42 |
|
| 43 | 43 |
- You can use size=1 to get the size of the containers |
| 44 | 44 |
|
| 45 |
+Start containers (/containers/<id>/start): |
|
| 46 |
+ |
|
| 47 |
+- You can now pass host-specific configuration (e.g. bind mounts) in the POST body for start calls |
|
| 45 | 48 |
|
| 46 | 49 |
:doc:`docker_remote_api_v1.2` |
| 47 | 50 |
***************************** |
| ... | ... |
@@ -294,23 +294,30 @@ Start a container |
| 294 | 294 |
|
| 295 | 295 |
.. http:post:: /containers/(id)/start |
| 296 | 296 |
|
| 297 |
- Start the container ``id`` |
|
| 297 |
+ Start the container ``id`` |
|
| 298 | 298 |
|
| 299 |
- **Example request**: |
|
| 299 |
+ **Example request**: |
|
| 300 | 300 |
|
| 301 |
- .. sourcecode:: http |
|
| 301 |
+ .. sourcecode:: http |
|
| 302 | 302 |
|
| 303 |
- POST /containers/e90e34656806/start HTTP/1.1 |
|
| 304 |
- |
|
| 305 |
- **Example response**: |
|
| 303 |
+ POST /containers/(id)/start HTTP/1.1 |
|
| 304 |
+ Content-Type: application/json |
|
| 306 | 305 |
|
| 307 |
- .. sourcecode:: http |
|
| 306 |
+ {
|
|
| 307 |
+ "Binds":["/tmp:/tmp"] |
|
| 308 |
+ } |
|
| 308 | 309 |
|
| 309 |
- HTTP/1.1 200 OK |
|
| 310 |
- |
|
| 311 |
- :statuscode 200: no error |
|
| 312 |
- :statuscode 404: no such container |
|
| 313 |
- :statuscode 500: server error |
|
| 310 |
+ **Example response**: |
|
| 311 |
+ |
|
| 312 |
+ .. sourcecode:: http |
|
| 313 |
+ |
|
| 314 |
+ HTTP/1.1 204 No Content |
|
| 315 |
+ Content-Type: text/plain |
|
| 316 |
+ |
|
| 317 |
+ :jsonparam hostConfig: the container's host configuration (optional) |
|
| 318 |
+ :statuscode 200: no error |
|
| 319 |
+ :statuscode 404: no such container |
|
| 320 |
+ :statuscode 500: server error |
|
| 314 | 321 |
|
| 315 | 322 |
|
| 316 | 323 |
Stop a contaier |
| ... | ... |
@@ -88,6 +88,10 @@ lxc.mount.entry = {{.ResolvConfPath}} {{$ROOTFS}}/etc/resolv.conf none bind,ro 0
|
| 88 | 88 |
lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,rw 0 0
|
| 89 | 89 |
{{end}}
|
| 90 | 90 |
{{end}}
|
| 91 |
+{{if .Binds}}# User-defined bind mounts
|
|
| 92 |
+{{range $bindMap := .Binds}}lxc.mount.entry = {{$bindMap.SrcPath}} {{$ROOTFS}}{{$bindMap.DstPath}} none bind,{{$bindMap.Mode}} 0 0
|
|
| 93 |
+{{end}}
|
|
| 94 |
+{{end}}
|
|
| 91 | 95 |
|
| 92 | 96 |
# drop linux capabilities (apply mainly to the user root in the container) |
| 93 | 97 |
# (Note: 'lxc.cap.keep' is coming soon and should replace this under the |
| ... | ... |
@@ -144,7 +144,9 @@ func (runtime *Runtime) Register(container *Container) error {
|
| 144 | 144 |
utils.Debugf("Restarting")
|
| 145 | 145 |
container.State.Ghost = false |
| 146 | 146 |
container.State.setStopped(0) |
| 147 |
- if err := container.Start(); err != nil {
|
|
| 147 |
+ // assume empty host config |
|
| 148 |
+ hostConfig := &HostConfig{}
|
|
| 149 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 148 | 150 |
return err |
| 149 | 151 |
} |
| 150 | 152 |
nomonitor = true |
| ... | ... |
@@ -327,7 +327,8 @@ func findAvailalblePort(runtime *Runtime, port int) (*Container, error) {
|
| 327 | 327 |
if err != nil {
|
| 328 | 328 |
return nil, err |
| 329 | 329 |
} |
| 330 |
- if err := container.Start(); err != nil {
|
|
| 330 |
+ hostConfig := &HostConfig{}
|
|
| 331 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 331 | 332 |
if strings.Contains(err.Error(), "address already in use") {
|
| 332 | 333 |
return nil, nil |
| 333 | 334 |
} |
| ... | ... |
@@ -437,7 +438,8 @@ func TestRestore(t *testing.T) {
|
| 437 | 437 |
defer runtime1.Destroy(container2) |
| 438 | 438 |
|
| 439 | 439 |
// Start the container non blocking |
| 440 |
- if err := container2.Start(); err != nil {
|
|
| 440 |
+ hostConfig := &HostConfig{}
|
|
| 441 |
+ if err := container2.Start(hostConfig); err != nil {
|
|
| 441 | 442 |
t.Fatal(err) |
| 442 | 443 |
} |
| 443 | 444 |
|
| ... | ... |
@@ -87,7 +87,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. |
| 87 | 87 |
} |
| 88 | 88 |
defer file.Body.Close() |
| 89 | 89 |
|
| 90 |
- config, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities)
|
|
| 90 |
+ config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities)
|
|
| 91 | 91 |
if err != nil {
|
| 92 | 92 |
return "", err |
| 93 | 93 |
} |
| ... | ... |
@@ -934,9 +934,9 @@ func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error) |
| 934 | 934 |
return nil, nil |
| 935 | 935 |
} |
| 936 | 936 |
|
| 937 |
-func (srv *Server) ContainerStart(name string) error {
|
|
| 937 |
+func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
|
|
| 938 | 938 |
if container := srv.runtime.Get(name); container != nil {
|
| 939 |
- if err := container.Start(); err != nil {
|
|
| 939 |
+ if err := container.Start(hostConfig); err != nil {
|
|
| 940 | 940 |
return fmt.Errorf("Error starting container %s: %s", name, err.Error())
|
| 941 | 941 |
} |
| 942 | 942 |
} else {
|
| ... | ... |
@@ -65,7 +65,7 @@ func TestCreateRm(t *testing.T) {
|
| 65 | 65 |
|
| 66 | 66 |
srv := &Server{runtime: runtime}
|
| 67 | 67 |
|
| 68 |
- config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
|
|
| 68 |
+ config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
|
|
| 69 | 69 |
if err != nil {
|
| 70 | 70 |
t.Fatal(err) |
| 71 | 71 |
} |
| ... | ... |
@@ -98,7 +98,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
|
| 98 | 98 |
|
| 99 | 99 |
srv := &Server{runtime: runtime}
|
| 100 | 100 |
|
| 101 |
- config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
|
|
| 101 |
+ config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
|
|
| 102 | 102 |
if err != nil {
|
| 103 | 103 |
t.Fatal(err) |
| 104 | 104 |
} |
| ... | ... |
@@ -112,7 +112,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
|
| 112 | 112 |
t.Errorf("Expected 1 container, %v found", len(runtime.List()))
|
| 113 | 113 |
} |
| 114 | 114 |
|
| 115 |
- err = srv.ContainerStart(id) |
|
| 115 |
+ err = srv.ContainerStart(id, hostConfig) |
|
| 116 | 116 |
if err != nil {
|
| 117 | 117 |
t.Fatal(err) |
| 118 | 118 |
} |
| ... | ... |
@@ -127,7 +127,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
|
| 127 | 127 |
t.Fatal(err) |
| 128 | 128 |
} |
| 129 | 129 |
|
| 130 |
- err = srv.ContainerStart(id) |
|
| 130 |
+ err = srv.ContainerStart(id, hostConfig) |
|
| 131 | 131 |
if err != nil {
|
| 132 | 132 |
t.Fatal(err) |
| 133 | 133 |
} |