mirror of
https://github.com/docker/cli.git
synced 2026-08-24 02:24:17 -05:00
container/opts: Add bind-create-src mount option
Add support for the `bind-create-src` option in bind mounts, which instructs the daemon to create the source mountpoint on the host 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-src --mount type=bind,src=/host/path,dst=/container/path,bind-create-src=true Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
@@ -160,6 +161,36 @@ func TestMountSubvolume(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountBindCreateMountpoint(t *testing.T) {
|
||||
environment.SkipIfDaemonNotLinux(t)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
value string
|
||||
expectSuccess bool
|
||||
}{
|
||||
{name: "flag only", value: "bind-create-src", expectSuccess: true},
|
||||
{name: "true", value: "bind-create-src=true", expectSuccess: true},
|
||||
{name: "1", value: "bind-create-src=1", expectSuccess: true},
|
||||
{name: "false", value: "bind-create-src=false", expectSuccess: false},
|
||||
{name: "0", value: "bind-create-src=0", expectSuccess: false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
srcPath := filepath.Join("/tmp", t.Name(), "does", "not", "exist")
|
||||
result := icmd.RunCommand("docker", "run", "--rm",
|
||||
"--mount", "type=bind,src="+srcPath+",dst=/mnt,"+tc.value,
|
||||
fixtures.AlpineImage, "cat", "/proc/mounts")
|
||||
if tc.expectSuccess {
|
||||
result.Assert(t, icmd.Success)
|
||||
assert.Check(t, is.Contains(result.Stdout(), "/mnt"))
|
||||
} else {
|
||||
result.Assert(t, icmd.Expected{ExitCode: 125})
|
||||
assert.Check(t, is.Contains(result.Stderr(), srcPath))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessTermination(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
cmd := icmd.Command("docker", "run", "--rm", "-i", fixtures.AlpineImage,
|
||||
|
||||
+6
-1
@@ -57,7 +57,7 @@ func (m *MountOpt) Set(value string) error {
|
||||
|
||||
if !hasValue {
|
||||
switch key {
|
||||
case "readonly", "ro", "volume-nocopy", "bind-nonrecursive":
|
||||
case "readonly", "ro", "volume-nocopy", "bind-nonrecursive", "bind-create-src":
|
||||
// boolean values
|
||||
default:
|
||||
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
||||
@@ -102,6 +102,11 @@ func (m *MountOpt) Set(value string) error {
|
||||
default:
|
||||
return fmt.Errorf(`invalid value for %s: %s (must be "enabled", "disabled", "writable", or "readonly")`, key, val)
|
||||
}
|
||||
case "bind-create-src":
|
||||
ensureBindOptions(&mount).CreateMountpoint, err = parseBoolValue(key, val, hasValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "volume-subpath":
|
||||
ensureVolumeOptions(&mount).Subpath = val
|
||||
case "volume-nocopy":
|
||||
|
||||
@@ -475,6 +475,50 @@ func TestMountOptSetTmpfsNoError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountOptSetBindCreateSrc(t *testing.T) {
|
||||
tests := []struct {
|
||||
value string
|
||||
exp bool
|
||||
expErr string
|
||||
}{
|
||||
{value: "", exp: false},
|
||||
{value: "bind-create-src", exp: true},
|
||||
{value: "bind-create-src=", expErr: `invalid value for 'bind-create-src': value is empty`},
|
||||
{value: "bind-create-src= true", expErr: `invalid value for 'bind-create-src' in 'bind-create-src= true': value should not have whitespace`},
|
||||
{value: "bind-create-src=no", expErr: `invalid value for 'bind-create-src': invalid boolean value ("no"): must be one of "true", "1", "false", or "0" (default "true")`},
|
||||
{value: "bind-create-src=1", exp: true},
|
||||
{value: "bind-create-src=true", exp: true},
|
||||
{value: "bind-create-src=0", exp: false},
|
||||
{value: "bind-create-src=false", exp: false},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
name := tc.value
|
||||
if name == "" {
|
||||
name = "not set"
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
val := "type=bind,target=/foo,source=/foo"
|
||||
if tc.value != "" {
|
||||
val += "," + tc.value
|
||||
}
|
||||
var m MountOpt
|
||||
err := m.Set(val)
|
||||
if tc.expErr != "" {
|
||||
assert.Error(t, err, tc.expErr)
|
||||
return
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
if tc.value == "" {
|
||||
assert.Check(t, is.Nil(m.values[0].BindOptions))
|
||||
} else {
|
||||
assert.Check(t, m.values[0].BindOptions != nil)
|
||||
assert.Check(t, is.Equal(m.values[0].BindOptions.CreateMountpoint, tc.exp))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountOptSetBindRecursive(t *testing.T) {
|
||||
t.Run("enabled", func(t *testing.T) {
|
||||
var m MountOpt
|
||||
|
||||
Reference in New Issue
Block a user