diff --git a/e2e/container/run_test.go b/e2e/container/run_test.go index d52230e238..6b35127b47 100644 --- a/e2e/container/run_test.go +++ b/e2e/container/run_test.go @@ -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, diff --git a/opts/mount.go b/opts/mount.go index 2f252aaca5..60ca36665d 100644 --- a/opts/mount.go +++ b/opts/mount.go @@ -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": diff --git a/opts/mount_test.go b/opts/mount_test.go index 9520948b9a..2014c98d55 100644 --- a/opts/mount_test.go +++ b/opts/mount_test.go @@ -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