Skip to content

Commit 19780a1

Browse files
committed
container/opts: Add bind-create-mountpoint mount option
Add support for the `bind-create-mountpoint` option in bind mounts, which instructs the daemon to create the mountpoint directory inside the container if it doesn't exist. This allows to replace the legacy `-v /src/dir:/dst` with the `--mount`. Usage: --mount type=bind,src=/host/path,dst=/container/path,bind-create-mountpoint --mount type=bind,src=/host/path,dst=/container/path,bind-create-mountpoint=true Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
1 parent 3e466c8 commit 19780a1

File tree

3 files changed

+85
-1
lines changed

3 files changed

+85
-1
lines changed

e2e/container/run_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"bytes"
55
"io"
66
"math/rand"
7+
"os"
78
"os/exec"
9+
"path/filepath"
810
"strings"
911
"syscall"
1012
"testing"
@@ -160,6 +162,39 @@ func TestMountSubvolume(t *testing.T) {
160162
}
161163
}
162164

165+
func TestMountBindCreateMountpoint(t *testing.T) {
166+
environment.SkipIfDaemonNotLinux(t)
167+
skip.If(t, environment.RemoteDaemon())
168+
169+
for _, tc := range []struct {
170+
name string
171+
value string
172+
expectExist bool
173+
}{
174+
{name: "flag only", value: "bind-create-mountpoint", expectExist: true},
175+
{name: "true", value: "bind-create-mountpoint=true", expectExist: true},
176+
{name: "1", value: "bind-create-mountpoint=1", expectExist: true},
177+
{name: "false", value: "bind-create-mountpoint=false", expectExist: false},
178+
{name: "0", value: "bind-create-mountpoint=0", expectExist: false},
179+
} {
180+
t.Run(tc.name, func(t *testing.T) {
181+
srcPath := filepath.Join(t.TempDir(), "does", "not", "exist")
182+
result := icmd.RunCommand("docker", "run", "--rm",
183+
"--mount", "type=bind,src="+srcPath+",dst=/mnt,"+tc.value,
184+
fixtures.AlpineImage, "cat", "/proc/mounts")
185+
_, err := os.Stat(srcPath)
186+
if tc.expectExist {
187+
assert.NilError(t, err, "source path should exist on host")
188+
result.Assert(t, icmd.Success)
189+
} else {
190+
assert.Check(t, os.IsNotExist(err), "source path should not exist on host")
191+
result.Assert(t, icmd.Expected{ExitCode: 125})
192+
assert.Check(t, is.Contains(result.Stdout(), "/mnt"))
193+
}
194+
})
195+
}
196+
}
197+
163198
func TestProcessTermination(t *testing.T) {
164199
var out bytes.Buffer
165200
cmd := icmd.Command("docker", "run", "--rm", "-i", fixtures.AlpineImage,

opts/mount.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func (m *MountOpt) Set(value string) error {
5757

5858
if !hasValue {
5959
switch key {
60-
case "readonly", "ro", "volume-nocopy", "bind-nonrecursive":
60+
case "readonly", "ro", "volume-nocopy", "bind-nonrecursive", "bind-create-mountpoint":
6161
// boolean values
6262
default:
6363
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
@@ -102,6 +102,11 @@ func (m *MountOpt) Set(value string) error {
102102
default:
103103
return fmt.Errorf(`invalid value for %s: %s (must be "enabled", "disabled", "writable", or "readonly")`, key, val)
104104
}
105+
case "bind-create-mountpoint":
106+
ensureBindOptions(&mount).CreateMountpoint, err = parseBoolValue(key, val, hasValue)
107+
if err != nil {
108+
return err
109+
}
105110
case "volume-subpath":
106111
ensureVolumeOptions(&mount).Subpath = val
107112
case "volume-nocopy":

opts/mount_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,50 @@ func TestMountOptSetTmpfsNoError(t *testing.T) {
475475
}
476476
}
477477

478+
func TestMountOptSetBindCreateMountpoint(t *testing.T) {
479+
tests := []struct {
480+
value string
481+
exp bool
482+
expErr string
483+
}{
484+
{value: "", exp: false},
485+
{value: "bind-create-mountpoint", exp: true},
486+
{value: "bind-create-mountpoint=", expErr: `invalid value for 'bind-create-mountpoint': value is empty`},
487+
{value: "bind-create-mountpoint= true", expErr: `invalid value for 'bind-create-mountpoint' in 'bind-create-mountpoint= true': value should not have whitespace`},
488+
{value: "bind-create-mountpoint=no", expErr: `invalid value for 'bind-create-mountpoint': invalid boolean value ("no"): must be one of "true", "1", "false", or "0" (default "true")`},
489+
{value: "bind-create-mountpoint=1", exp: true},
490+
{value: "bind-create-mountpoint=true", exp: true},
491+
{value: "bind-create-mountpoint=0", exp: false},
492+
{value: "bind-create-mountpoint=false", exp: false},
493+
}
494+
495+
for _, tc := range tests {
496+
name := tc.value
497+
if name == "" {
498+
name = "not set"
499+
}
500+
t.Run(name, func(t *testing.T) {
501+
val := "type=bind,target=/foo,source=/foo"
502+
if tc.value != "" {
503+
val += "," + tc.value
504+
}
505+
var m MountOpt
506+
err := m.Set(val)
507+
if tc.expErr != "" {
508+
assert.Error(t, err, tc.expErr)
509+
return
510+
}
511+
assert.NilError(t, err)
512+
if tc.value == "" {
513+
assert.Check(t, is.Nil(m.values[0].BindOptions))
514+
} else {
515+
assert.Check(t, m.values[0].BindOptions != nil)
516+
assert.Check(t, is.Equal(m.values[0].BindOptions.CreateMountpoint, tc.exp))
517+
}
518+
})
519+
}
520+
}
521+
478522
func TestMountOptSetBindRecursive(t *testing.T) {
479523
t.Run("enabled", func(t *testing.T) {
480524
var m MountOpt

0 commit comments

Comments
 (0)