diff --git a/local/compose.go b/local/compose.go index a6653e332..2fb7cc5e2 100644 --- a/local/compose.go +++ b/local/compose.go @@ -800,7 +800,10 @@ func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, number i StopTimeout: toSeconds(s.StopGracePeriod), } - mountOptions := buildContainerMountOptions(p, s, inherit) + mountOptions, err := buildContainerMountOptions(p, s, inherit) + if err != nil { + return nil, nil, nil, err + } bindings := buildContainerBindingOptions(s) networkMode := getNetworkMode(p, s) @@ -844,7 +847,7 @@ func buildContainerBindingOptions(s types.ServiceConfig) nat.PortMap { return bindings } -func buildContainerMountOptions(p *types.Project, s types.ServiceConfig, inherit *moby.Container) []mount.Mount { +func buildContainerMountOptions(p *types.Project, s types.ServiceConfig, inherit *moby.Container) ([]mount.Mount, error) { mounts := []mount.Mount{} var inherited []string if inherit != nil { @@ -870,24 +873,36 @@ func buildContainerMountOptions(p *types.Project, s types.ServiceConfig, inherit if contains(inherited, v.Target) { continue } - source := v.Source - if v.Type == "bind" && !filepath.IsAbs(source) { - // FIXME handle ~/ - source = filepath.Join(p.WorkingDir, source) + mount, err := buildMount(v) + if err != nil { + return nil, err } - - mounts = append(mounts, mount.Mount{ - Type: mount.Type(v.Type), - Source: source, - Target: v.Target, - ReadOnly: v.ReadOnly, - Consistency: mount.Consistency(v.Consistency), - BindOptions: buildBindOption(v.Bind), - VolumeOptions: buildVolumeOptions(v.Volume), - TmpfsOptions: buildTmpfsOptions(v.Tmpfs), - }) + mounts = append(mounts, mount) } - return mounts + return mounts, nil +} + +func buildMount(volume types.ServiceVolumeConfig) (mount.Mount, error) { + source := volume.Source + if volume.Type == "bind" && !filepath.IsAbs(source) { + // volume source has already been prefixed with workdir if required, by compose-go project loader + var err error + source, err = filepath.Abs(source) + if err != nil { + return mount.Mount{}, err + } + } + + return mount.Mount{ + Type: mount.Type(volume.Type), + Source: source, + Target: volume.Target, + ReadOnly: volume.ReadOnly, + Consistency: mount.Consistency(volume.Consistency), + BindOptions: buildBindOption(volume.Bind), + VolumeOptions: buildVolumeOptions(volume.Volume), + TmpfsOptions: buildTmpfsOptions(volume.Tmpfs), + }, nil } func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions { diff --git a/local/compose_test.go b/local/compose_test.go index 74de0745b..fa406709c 100644 --- a/local/compose_test.go +++ b/local/compose_test.go @@ -19,9 +19,13 @@ package local import ( + "os" + "path/filepath" "testing" + composetypes "github.com/compose-spec/compose-go/types" "github.com/docker/docker/api/types" + mountTypes "github.com/docker/docker/api/types/mount" "gotest.tools/v3/assert" "github.com/docker/compose-cli/api/compose" @@ -107,3 +111,17 @@ func TestStacksMixedStatus(t *testing.T) { assert.Equal(t, combinedStatus([]string{"running", "running", "running"}), "running(3)") assert.Equal(t, combinedStatus([]string{"running", "exited", "running"}), "exited(1), running(2)") } + +func TestBuildBindMount(t *testing.T) { + volume := composetypes.ServiceVolumeConfig{ + Type: composetypes.VolumeTypeBind, + Source: "e2e/volume-test", + Target: "/data", + } + mount, err := buildMount(volume) + assert.NilError(t, err) + assert.Assert(t, filepath.IsAbs(mount.Source)) + _, err = os.Stat(mount.Source) + assert.NilError(t, err) + assert.Equal(t, mount.Type, mountTypes.TypeBind) +} diff --git a/local/e2e/compose_test.go b/local/e2e/compose_test.go index 9e5ea535f..9a2211ecb 100644 --- a/local/e2e/compose_test.go +++ b/local/e2e/compose_test.go @@ -28,7 +28,7 @@ import ( . "github.com/docker/compose-cli/tests/framework" ) -func TestLocalBackendComposeUp(t *testing.T) { +func TestLocalComposeUp(t *testing.T) { c := NewParallelE2eCLI(t, binDir) c.RunDockerCmd("context", "create", "local", "test-context").Assert(t, icmd.Success) c.RunDockerCmd("context", "use", "test-context").Assert(t, icmd.Success) @@ -45,7 +45,7 @@ func TestLocalBackendComposeUp(t *testing.T) { }) t.Run("up", func(t *testing.T) { - c.RunDockerCmd("compose", "up", "-f", "../../tests/composefiles/demo_multi_port.yaml", "--project-name", projectName, "-d") + c.RunDockerCmd("compose", "up", "-d", "-f", "../../tests/composefiles/demo_multi_port.yaml", "--project-name", projectName, "-d") }) t.Run("check running project", func(t *testing.T) { @@ -86,3 +86,20 @@ func TestLocalBackendComposeUp(t *testing.T) { assert.Equal(t, networkList.Stdout(), networksAfterDown.Stdout()) }) } + +func TestLocalComposeVolume(t *testing.T) { + c := NewParallelE2eCLI(t, binDir) + c.RunDockerCmd("context", "create", "local", "test-context").Assert(t, icmd.Success) + c.RunDockerCmd("context", "use", "test-context").Assert(t, icmd.Success) + + const projectName = "compose-e2e-volume" + + t.Run("up with volume", func(t *testing.T) { + c.RunDockerCmd("compose", "up", "-d", "--workdir", "volume-test", "--project-name", projectName) + + output := HTTPGetWithRetry(t, "http://localhost:8090", http.StatusOK, 2*time.Second, 20*time.Second) + assert.Assert(t, strings.Contains(output, "Hello from Nginx container")) + + _ = c.RunDockerCmd("compose", "down", "--project-name", projectName) + }) +} diff --git a/local/e2e/volume-test/docker-compose.yml b/local/e2e/volume-test/docker-compose.yml new file mode 100644 index 000000000..09afab385 --- /dev/null +++ b/local/e2e/volume-test/docker-compose.yml @@ -0,0 +1,7 @@ +services: + nginx: + image: nginx + volumes: + - ./static:/usr/share/nginx/html + ports: + - 8090:80 diff --git a/local/e2e/volume-test/static/index.html b/local/e2e/volume-test/static/index.html new file mode 100644 index 000000000..0c7642bcb --- /dev/null +++ b/local/e2e/volume-test/static/index.html @@ -0,0 +1,10 @@ + + +
+ +