Repository: compose-spec/compose-go
Pull request #206 (2021-11-22 10:54:25)

Fix volume trailing slash
Cleans the volume target paths.

This makes sure we don’t have e.g. a trailing space.
Partly fixes https://github.com/docker/compose/issues/8558


Patch:
diff --git a/loader/full-example.yml b/loader/full-example.yml
index 9463445..529d28b 100644
--- a/loader/full-example.yml
+++ b/loader/full-example.yml
@@ -268,7 +268,7 @@ services:
       - .:/code
       - ./static:/var/www/html
       # User-relative path
-      - ~/configs:/etc/configs/:ro
+      - ~/configs:/etc/configs:ro
       # Named volume
       - datavolume:/var/lib/mysql
       - type: bind
diff --git a/loader/volume.go b/loader/volume.go
index 173b76b..0162aee 100644
--- a/loader/volume.go
+++ b/loader/volume.go
@@ -17,6 +17,7 @@
 package loader
 
 import (
+	"path"
 	"strings"
 	"unicode"
 	"unicode/utf8"
@@ -36,11 +37,11 @@ func ParseVolume(spec string) (types.ServiceVolumeConfig, error) {
 		return volume, errors.New("invalid empty volume spec")
 	case 1, 2:
 		volume.Target = spec
-		volume.Type = string(types.VolumeTypeVolume)
+		volume.Type = types.VolumeTypeVolume
 		return volume, nil
 	}
 
-	buffer := []rune{}
+	var buffer []rune
 	for _, char := range spec + string(endOfSpec) {
 		switch {
 		case isWindowsDrive(buffer, char):
@@ -50,12 +51,13 @@ func ParseVolume(spec string) (types.ServiceVolumeConfig, error) {
 				populateType(&volume)
 				return volume, errors.Wrapf(err, "invalid spec: %s", spec)
 			}
-			buffer = []rune{}
+			buffer = nil
 		default:
 			buffer = append(buffer, char)
 		}
 	}
 
+	volume.Target = path.Clean(volume.Target)
 	populateType(&volume)
 	return volume, nil
 }


Test patch:
diff --git a/loader/full-struct_test.go b/loader/full-struct_test.go
index a81e047..3ffcc76 100644
--- a/loader/full-struct_test.go
+++ b/loader/full-struct_test.go
@@ -399,7 +399,7 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
 				{Source: "/opt/data", Target: "/var/lib/mysql", Type: "bind", Bind: &types.ServiceVolumeBind{CreateHostPath: true}},
 				{Source: workingDir, Target: "/code", Type: "bind", Bind: &types.ServiceVolumeBind{CreateHostPath: true}},
 				{Source: filepath.Join(workingDir, "static"), Target: "/var/www/html", Type: "bind", Bind: &types.ServiceVolumeBind{CreateHostPath: true}},
-				{Source: filepath.Join(homeDir, "/configs"), Target: "/etc/configs/", Type: "bind", ReadOnly: true, Bind: &types.ServiceVolumeBind{CreateHostPath: true}},
+				{Source: filepath.Join(homeDir, "/configs"), Target: "/etc/configs", Type: "bind", ReadOnly: true, Bind: &types.ServiceVolumeBind{CreateHostPath: true}},
 				{Source: "datavolume", Target: "/var/lib/mysql", Type: "volume", Volume: &types.ServiceVolumeVolume{}},
 				{Source: filepath.Join(workingDir, "opt"), Target: "/opt", Consistency: "cached", Type: "bind"},
 				{Target: "/opt", Type: "tmpfs", Tmpfs: &types.ServiceVolumeTmpfs{
@@ -851,7 +851,7 @@ func fullExampleYAML(workingDir, homeDir string) string {
         create_host_path: true
     - type: bind
       source: %s
-      target: /etc/configs/
+      target: /etc/configs
       read_only: true
       bind:
         create_host_path: true
@@ -1481,7 +1481,7 @@ func fullExampleJSON(workingDir, homeDir string) string {
         {
           "type": "bind",
           "source": "%s",
-          "target": "/etc/configs/",
+          "target": "/etc/configs",
           "read_only": true,
           "bind": {
             "create_host_path": true
diff --git a/loader/volume_test.go b/loader/volume_test.go
index ca41867..cad2bb0 100644
--- a/loader/volume_test.go
+++ b/loader/volume_test.go
@@ -34,6 +34,13 @@ func TestParseVolumeAnonymousVolume(t *testing.T) {
 	}
 }
 
+func TestParseVolumeCleanTarget(t *testing.T) {
+	volume, err := ParseVolume("/path/")
+	expected := types.ServiceVolumeConfig{Type: "volume", Target: "/path", Volume: &types.ServiceVolumeVolume{}}
+	assert.NilError(t, err)
+	assert.Check(t, is.DeepEqual(expected, volume))
+}
+
 func TestParseVolumeAnonymousVolumeWindows(t *testing.T) {
 	for _, path := range []string{"C:\\path", "Z:\\path\\foo"} {
 		volume, err := ParseVolume(path)