Use compose volume spec parser for container volume flag
Restore testcases for Volume spec parsing.
And correctly interpret the parsed volume.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
Upstream-commit: 732261f774
Component: cli
This commit is contained in:
@@ -564,7 +564,7 @@ func transformStringSourceMap(data interface{}) (interface{}, error) {
|
||||
func transformServiceVolumeConfig(data interface{}) (interface{}, error) {
|
||||
switch value := data.(type) {
|
||||
case string:
|
||||
return parseVolume(value)
|
||||
return ParseVolume(value)
|
||||
case map[string]interface{}:
|
||||
return data, nil
|
||||
default:
|
||||
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
|
||||
const endOfSpec = rune(0)
|
||||
|
||||
func parseVolume(spec string) (types.ServiceVolumeConfig, error) {
|
||||
// ParseVolume parses a volume spec without any knowledge of the target platform
|
||||
func ParseVolume(spec string) (types.ServiceVolumeConfig, error) {
|
||||
volume := types.ServiceVolumeConfig{}
|
||||
|
||||
switch len(spec) {
|
||||
@@ -31,6 +32,7 @@ func parseVolume(spec string) (types.ServiceVolumeConfig, error) {
|
||||
buffer = append(buffer, char)
|
||||
case char == ':' || char == endOfSpec:
|
||||
if err := populateFieldFromBuffer(char, buffer, &volume); err != nil {
|
||||
populateType(&volume)
|
||||
return volume, errors.Wrapf(err, "invalid spec: %s", spec)
|
||||
}
|
||||
buffer = []rune{}
|
||||
@@ -38,6 +40,7 @@ func parseVolume(spec string) (types.ServiceVolumeConfig, error) {
|
||||
buffer = append(buffer, char)
|
||||
}
|
||||
}
|
||||
|
||||
populateType(&volume)
|
||||
return volume, nil
|
||||
}
|
||||
@@ -75,9 +78,8 @@ func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolu
|
||||
default:
|
||||
if isBindOption(option) {
|
||||
volume.Bind = &types.ServiceVolumeBind{Propagation: option}
|
||||
} else {
|
||||
return errors.Errorf("unknown option: %s", option)
|
||||
}
|
||||
// ignore unknown options
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/compose/types"
|
||||
@@ -10,7 +11,7 @@ import (
|
||||
|
||||
func TestParseVolumeAnonymousVolume(t *testing.T) {
|
||||
for _, path := range []string{"/path", "/path/foo"} {
|
||||
volume, err := parseVolume(path)
|
||||
volume, err := ParseVolume(path)
|
||||
expected := types.ServiceVolumeConfig{Type: "volume", Target: path}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, volume)
|
||||
@@ -19,7 +20,7 @@ func TestParseVolumeAnonymousVolume(t *testing.T) {
|
||||
|
||||
func TestParseVolumeAnonymousVolumeWindows(t *testing.T) {
|
||||
for _, path := range []string{"C:\\path", "Z:\\path\\foo"} {
|
||||
volume, err := parseVolume(path)
|
||||
volume, err := ParseVolume(path)
|
||||
expected := types.ServiceVolumeConfig{Type: "volume", Target: path}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, volume)
|
||||
@@ -27,13 +28,13 @@ func TestParseVolumeAnonymousVolumeWindows(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseVolumeTooManyColons(t *testing.T) {
|
||||
_, err := parseVolume("/foo:/foo:ro:foo")
|
||||
_, err := ParseVolume("/foo:/foo:ro:foo")
|
||||
assert.EqualError(t, err, "invalid spec: /foo:/foo:ro:foo: too many colons")
|
||||
}
|
||||
|
||||
func TestParseVolumeShortVolumes(t *testing.T) {
|
||||
for _, path := range []string{".", "/a"} {
|
||||
volume, err := parseVolume(path)
|
||||
volume, err := ParseVolume(path)
|
||||
expected := types.ServiceVolumeConfig{Type: "volume", Target: path}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, volume)
|
||||
@@ -42,14 +43,14 @@ func TestParseVolumeShortVolumes(t *testing.T) {
|
||||
|
||||
func TestParseVolumeMissingSource(t *testing.T) {
|
||||
for _, spec := range []string{":foo", "/foo::ro"} {
|
||||
_, err := parseVolume(spec)
|
||||
_, err := ParseVolume(spec)
|
||||
testutil.ErrorContains(t, err, "empty section between colons")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVolumeBindMount(t *testing.T) {
|
||||
for _, path := range []string{"./foo", "~/thing", "../other", "/foo", "/home/user"} {
|
||||
volume, err := parseVolume(path + ":/target")
|
||||
volume, err := ParseVolume(path + ":/target")
|
||||
expected := types.ServiceVolumeConfig{
|
||||
Type: "bind",
|
||||
Source: path,
|
||||
@@ -67,7 +68,7 @@ func TestParseVolumeRelativeBindMountWindows(t *testing.T) {
|
||||
"../other",
|
||||
"D:\\path", "/home/user",
|
||||
} {
|
||||
volume, err := parseVolume(path + ":d:\\target")
|
||||
volume, err := ParseVolume(path + ":d:\\target")
|
||||
expected := types.ServiceVolumeConfig{
|
||||
Type: "bind",
|
||||
Source: path,
|
||||
@@ -79,7 +80,7 @@ func TestParseVolumeRelativeBindMountWindows(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseVolumeWithBindOptions(t *testing.T) {
|
||||
volume, err := parseVolume("/source:/target:slave")
|
||||
volume, err := ParseVolume("/source:/target:slave")
|
||||
expected := types.ServiceVolumeConfig{
|
||||
Type: "bind",
|
||||
Source: "/source",
|
||||
@@ -91,7 +92,7 @@ func TestParseVolumeWithBindOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseVolumeWithBindOptionsWindows(t *testing.T) {
|
||||
volume, err := parseVolume("C:\\source\\foo:D:\\target:ro,rprivate")
|
||||
volume, err := ParseVolume("C:\\source\\foo:D:\\target:ro,rprivate")
|
||||
expected := types.ServiceVolumeConfig{
|
||||
Type: "bind",
|
||||
Source: "C:\\source\\foo",
|
||||
@@ -104,12 +105,12 @@ func TestParseVolumeWithBindOptionsWindows(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseVolumeWithInvalidVolumeOptions(t *testing.T) {
|
||||
_, err := parseVolume("name:/target:bogus")
|
||||
assert.EqualError(t, err, "invalid spec: name:/target:bogus: unknown option: bogus")
|
||||
_, err := ParseVolume("name:/target:bogus")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestParseVolumeWithVolumeOptions(t *testing.T) {
|
||||
volume, err := parseVolume("name:/target:nocopy")
|
||||
volume, err := ParseVolume("name:/target:nocopy")
|
||||
expected := types.ServiceVolumeConfig{
|
||||
Type: "volume",
|
||||
Source: "name",
|
||||
@@ -122,7 +123,7 @@ func TestParseVolumeWithVolumeOptions(t *testing.T) {
|
||||
|
||||
func TestParseVolumeWithReadOnly(t *testing.T) {
|
||||
for _, path := range []string{"./foo", "/home/user"} {
|
||||
volume, err := parseVolume(path + ":/target:ro")
|
||||
volume, err := ParseVolume(path + ":/target:ro")
|
||||
expected := types.ServiceVolumeConfig{
|
||||
Type: "bind",
|
||||
Source: path,
|
||||
@@ -136,7 +137,7 @@ func TestParseVolumeWithReadOnly(t *testing.T) {
|
||||
|
||||
func TestParseVolumeWithRW(t *testing.T) {
|
||||
for _, path := range []string{"./foo", "/home/user"} {
|
||||
volume, err := parseVolume(path + ":/target:rw")
|
||||
volume, err := ParseVolume(path + ":/target:rw")
|
||||
expected := types.ServiceVolumeConfig{
|
||||
Type: "bind",
|
||||
Source: path,
|
||||
@@ -151,3 +152,51 @@ func TestParseVolumeWithRW(t *testing.T) {
|
||||
func TestIsFilePath(t *testing.T) {
|
||||
assert.False(t, isFilePath("a界"))
|
||||
}
|
||||
|
||||
// Preserve the test cases for VolumeSplitN
|
||||
func TestParseVolumeSplitCases(t *testing.T) {
|
||||
for casenumber, x := range []struct {
|
||||
input string
|
||||
n int
|
||||
expected []string
|
||||
}{
|
||||
{`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}},
|
||||
{`:C:\foo:d:`, -1, nil},
|
||||
{`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}},
|
||||
{`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}},
|
||||
{`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}},
|
||||
{`d:\`, -1, []string{`d:\`}},
|
||||
{`d:`, -1, []string{`d:`}},
|
||||
{`d:\path`, -1, []string{`d:\path`}},
|
||||
{`d:\path with space`, -1, []string{`d:\path with space`}},
|
||||
{`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}},
|
||||
|
||||
{`c:\:d:\`, -1, []string{`c:\`, `d:\`}},
|
||||
{`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}},
|
||||
{`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}},
|
||||
{`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}},
|
||||
{`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}},
|
||||
{`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}},
|
||||
{`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}},
|
||||
{`name:D:`, -1, []string{`name`, `D:`}},
|
||||
{`name:D::rW`, -1, []string{`name`, `D:`, `rW`}},
|
||||
{`name:D::RW`, -1, []string{`name`, `D:`, `RW`}},
|
||||
|
||||
{`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}},
|
||||
{`c:\Windows`, -1, []string{`c:\Windows`}},
|
||||
{`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}},
|
||||
{``, -1, nil},
|
||||
{`.`, -1, []string{`.`}},
|
||||
{`..\`, -1, []string{`..\`}},
|
||||
{`c:\:..\`, -1, []string{`c:\`, `..\`}},
|
||||
{`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}},
|
||||
// Cover directories with one-character name
|
||||
{`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}},
|
||||
} {
|
||||
parsed, _ := ParseVolume(x.input)
|
||||
|
||||
expected := len(x.expected) > 1
|
||||
msg := fmt.Sprintf("Case %d: %s", casenumber, x.input)
|
||||
assert.Equal(t, expected, parsed.Source != "", msg)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user