api/client/service: mount option defaults and aliases

Simplifies the mount option usage by providing common aliases for
`source` and `target`. The default mount type is now volume.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
Stephen J Day 2016-07-20 16:34:45 -07:00
parent 0a8a6f2dd7
commit 634f54a047
No known key found for this signature in database
GPG key ID: FB5F6B2905D7ECF3
2 changed files with 43 additions and 25 deletions

View file

@ -176,32 +176,37 @@ func (m *MountOpt) Set(value string) error {
}
}
mount.Type = swarm.MountTypeVolume // default to volume mounts
// Set writable as the default
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
if len(parts) == 1 && strings.ToLower(parts[0]) == "readonly" {
mount.ReadOnly = true
continue
}
key := strings.ToLower(parts[0])
if len(parts) == 1 && strings.ToLower(parts[0]) == "volume-nocopy" {
volumeOptions().NoCopy = true
continue
if len(parts) == 1 {
if key == "readonly" || key == "ro" {
mount.ReadOnly = true
continue
}
if key == "volume-nocopy" {
volumeOptions().NoCopy = true
continue
}
}
if len(parts) != 2 {
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
}
key, value := parts[0], parts[1]
switch strings.ToLower(key) {
value := parts[1]
switch key {
case "type":
mount.Type = swarm.MountType(strings.ToLower(value))
case "source":
case "source", "name", "src":
mount.Source = value
case "target":
case "target", "dst", "dest", "destination", "path":
mount.Target = value
case "readonly":
case "readonly", "ro":
ro, err := strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("invalid value for readonly: %s", value)

View file

@ -77,21 +77,33 @@ func TestMountOptString(t *testing.T) {
}
func TestMountOptSetNoError(t *testing.T) {
var mount MountOpt
assert.NilError(t, mount.Set("type=bind,target=/target,source=/foo"))
for _, testcase := range []string{
// tests several aliases that should have same result.
"type=bind,target=/target,source=/source",
"type=bind,src=/source,dst=/target",
"type=bind,name=/source,dst=/target",
"type=bind,name=/source,path=/target",
} {
var mount MountOpt
mounts := mount.Value()
assert.Equal(t, len(mounts), 1)
assert.Equal(t, mounts[0], swarm.Mount{
Type: swarm.MountTypeBind,
Source: "/foo",
Target: "/target",
})
assert.NilError(t, mount.Set(testcase))
mounts := mount.Value()
assert.Equal(t, len(mounts), 1)
assert.Equal(t, mounts[0], swarm.Mount{
Type: swarm.MountTypeBind,
Source: "/source",
Target: "/target",
})
}
}
func TestMountOptSetErrorNoType(t *testing.T) {
// TestMountOptDefaultType ensures that a mount without the type defaults to a
// volume mount.
func TestMountOptDefaultType(t *testing.T) {
var mount MountOpt
assert.Error(t, mount.Set("target=/target,source=/foo"), "type is required")
assert.NilError(t, mount.Set("target=/target,source=/foo"))
assert.Equal(t, mount.values[0].Type, swarm.MountTypeVolume)
}
func TestMountOptSetErrorNoTarget(t *testing.T) {
@ -109,12 +121,13 @@ func TestMountOptSetErrorInvalidField(t *testing.T) {
assert.Error(t, mount.Set("type=volume,bogus"), "invalid field 'bogus'")
}
func TestMountOptSetErrorInvalidWritable(t *testing.T) {
func TestMountOptSetErrorInvalidReadOnly(t *testing.T) {
var mount MountOpt
assert.Error(t, mount.Set("type=volume,readonly=no"), "invalid value for readonly: no")
assert.Error(t, mount.Set("type=volume,readonly=invalid"), "invalid value for readonly: invalid")
}
func TestMountOptDefaultEnableWritable(t *testing.T) {
func TestMountOptDefaultEnableReadOnly(t *testing.T) {
var m MountOpt
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo"))
assert.Equal(t, m.values[0].ReadOnly, false)