mirror of
https://github.com/docker/cli.git
synced 2026-08-24 10:05:37 -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"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"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) {
|
func TestProcessTermination(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
cmd := icmd.Command("docker", "run", "--rm", "-i", fixtures.AlpineImage,
|
cmd := icmd.Command("docker", "run", "--rm", "-i", fixtures.AlpineImage,
|
||||||
|
|||||||
+6
-1
@@ -57,7 +57,7 @@ func (m *MountOpt) Set(value string) error {
|
|||||||
|
|
||||||
if !hasValue {
|
if !hasValue {
|
||||||
switch key {
|
switch key {
|
||||||
case "readonly", "ro", "volume-nocopy", "bind-nonrecursive":
|
case "readonly", "ro", "volume-nocopy", "bind-nonrecursive", "bind-create-src":
|
||||||
// boolean values
|
// boolean values
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
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:
|
default:
|
||||||
return fmt.Errorf(`invalid value for %s: %s (must be "enabled", "disabled", "writable", or "readonly")`, key, val)
|
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":
|
case "volume-subpath":
|
||||||
ensureVolumeOptions(&mount).Subpath = val
|
ensureVolumeOptions(&mount).Subpath = val
|
||||||
case "volume-nocopy":
|
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) {
|
func TestMountOptSetBindRecursive(t *testing.T) {
|
||||||
t.Run("enabled", func(t *testing.T) {
|
t.Run("enabled", func(t *testing.T) {
|
||||||
var m MountOpt
|
var m MountOpt
|
||||||
|
|||||||
Reference in New Issue
Block a user