Skip to content

Commit a3f88a0

Browse files
ndeloofglours
authored andcommitted
test to cover preference for bind API
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
1 parent c83f128 commit a3f88a0

2 files changed

Lines changed: 168 additions & 23 deletions

File tree

pkg/compose/create.go

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import (
3939
"github.com/docker/docker/api/types/blkiodev"
4040
"github.com/docker/docker/api/types/container"
4141
"github.com/docker/docker/api/types/filters"
42-
"github.com/docker/docker/api/types/image"
4342
"github.com/docker/docker/api/types/mount"
4443
"github.com/docker/docker/api/types/network"
4544
"github.com/docker/docker/api/types/strslice"
@@ -828,7 +827,6 @@ func getDependentServiceFromMode(mode string) string {
828827
return ""
829828
}
830829

831-
//nolint:gocyclo
832830
func (s *composeService) buildContainerVolumes(
833831
ctx context.Context,
834832
p types.Project,
@@ -838,13 +836,7 @@ func (s *composeService) buildContainerVolumes(
838836
var mounts []mount.Mount
839837
var binds []string
840838

841-
img := api.GetImageNameOrDefault(service, p.Name)
842-
imgInspect, err := s.apiClient().ImageInspect(ctx, img)
843-
if err != nil {
844-
return nil, nil, err
845-
}
846-
847-
mountOptions, err := buildContainerMountOptions(p, service, imgInspect, inherit)
839+
mountOptions, err := s.buildContainerMountOptions(ctx, p, service, inherit)
848840
if err != nil {
849841
return nil, nil, err
850842
}
@@ -857,11 +849,10 @@ func (s *composeService) buildContainerVolumes(
857849
// see https://github.com/moby/moby/issues/43483
858850
v := findVolumeByTarget(service.Volumes, m.Target)
859851
if v != nil {
860-
switch {
861-
case v.Type != types.VolumeTypeBind:
852+
if v.Type != types.VolumeTypeBind {
862853
v.Source = m.Source
863-
fallthrough
864-
case !requireMountAPI(v.Bind):
854+
}
855+
if !bindRequiresMountAPI(v.Bind) {
865856
source := m.Source
866857
if vol := findVolumeByName(p.Volumes, m.Source); vol != nil {
867858
source = m.Source
@@ -874,8 +865,8 @@ func (s *composeService) buildContainerVolumes(
874865
v := findVolumeByTarget(service.Volumes, m.Target)
875866
vol := findVolumeByName(p.Volumes, m.Source)
876867
if v != nil && vol != nil {
877-
if _, ok := vol.DriverOpts["device"]; ok && vol.Driver == "local" && vol.DriverOpts["o"] == "bind" {
878-
// Looks like a volume, but actually a bind mount which requires the bind API
868+
// Prefer the bind API if no advanced option is used, to preserve backward compatibility
869+
if !volumeRequiresMountAPI(v.Volume) {
879870
binds = append(binds, toBindString(vol.Name, v))
880871
continue
881872
}
@@ -930,9 +921,9 @@ func findVolumeByTarget(volumes []types.ServiceVolumeConfig, target string) *typ
930921
return nil
931922
}
932923

933-
// requireMountAPI check if Bind declaration can be implemented by the plain old Bind API or uses any of the advanced
924+
// bindRequiresMountAPI check if Bind declaration can be implemented by the plain old Bind API or uses any of the advanced
934925
// options which require use of Mount API
935-
func requireMountAPI(bind *types.ServiceVolumeBind) bool {
926+
func bindRequiresMountAPI(bind *types.ServiceVolumeBind) bool {
936927
switch {
937928
case bind == nil:
938929
return false
@@ -947,7 +938,24 @@ func requireMountAPI(bind *types.ServiceVolumeBind) bool {
947938
}
948939
}
949940

950-
func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img image.InspectResponse, inherit *container.Summary) ([]mount.Mount, error) {
941+
// volumeRequiresMountAPI check if Volume declaration can be implemented by the plain old Bind API or uses any of the advanced
942+
// options which require use of Mount API
943+
func volumeRequiresMountAPI(vol *types.ServiceVolumeVolume) bool {
944+
switch {
945+
case vol == nil:
946+
return false
947+
case len(vol.Labels) > 0:
948+
return true
949+
case vol.Subpath != "":
950+
return true
951+
case vol.NoCopy:
952+
return true
953+
default:
954+
return false
955+
}
956+
}
957+
958+
func (s *composeService) buildContainerMountOptions(ctx context.Context, p types.Project, service types.ServiceConfig, inherit *container.Summary) ([]mount.Mount, error) {
951959
mounts := map[string]mount.Mount{}
952960
if inherit != nil {
953961
for _, m := range inherit.Mounts {
@@ -959,6 +967,11 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img imag
959967
src = m.Name
960968
}
961969

970+
img, err := s.apiClient().ImageInspect(ctx, api.GetImageNameOrDefault(service, p.Name))
971+
if err != nil {
972+
return nil, err
973+
}
974+
962975
if img.Config != nil {
963976
if _, ok := img.Config.Volumes[m.Destination]; ok {
964977
// inherit previous container's anonymous volume
@@ -971,7 +984,7 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img imag
971984
}
972985
}
973986
volumes := []types.ServiceVolumeConfig{}
974-
for _, v := range s.Volumes {
987+
for _, v := range service.Volumes {
975988
if v.Target != m.Destination || v.Source != "" {
976989
volumes = append(volumes, v)
977990
continue
@@ -984,11 +997,11 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img imag
984997
ReadOnly: !m.RW,
985998
}
986999
}
987-
s.Volumes = volumes
1000+
service.Volumes = volumes
9881001
}
9891002
}
9901003

991-
mounts, err := fillBindMounts(p, s, mounts)
1004+
mounts, err := fillBindMounts(p, service, mounts)
9921005
if err != nil {
9931006
return nil, err
9941007
}

pkg/compose/create_test.go

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@
1717
package compose
1818

1919
import (
20+
"context"
2021
"os"
2122
"path/filepath"
2223
"sort"
2324
"testing"
2425

26+
composeloader "github.com/compose-spec/compose-go/v2/loader"
2527
"github.com/docker/docker/api/types/container"
2628
"github.com/docker/docker/api/types/image"
29+
"go.uber.org/mock/gomock"
2730
"gotest.tools/v3/assert/cmp"
2831

2932
"github.com/docker/compose/v2/pkg/api"
@@ -154,7 +157,16 @@ func TestBuildContainerMountOptions(t *testing.T) {
154157
},
155158
}
156159

157-
mounts, err := buildContainerMountOptions(project, project.Services["myService"], image.InspectResponse{}, inherit)
160+
mockCtrl := gomock.NewController(t)
161+
defer mockCtrl.Finish()
162+
163+
mock, cli := prepareMocks(mockCtrl)
164+
s := composeService{
165+
dockerCli: cli,
166+
}
167+
mock.EXPECT().ImageInspect(gomock.Any(), "myProject-myService").AnyTimes().Return(image.InspectResponse{}, nil)
168+
169+
mounts, err := s.buildContainerMountOptions(context.TODO(), project, project.Services["myService"], inherit)
158170
sort.Slice(mounts, func(i, j int) bool {
159171
return mounts[i].Target < mounts[j].Target
160172
})
@@ -166,7 +178,7 @@ func TestBuildContainerMountOptions(t *testing.T) {
166178
assert.Equal(t, mounts[2].VolumeOptions.Subpath, "etc")
167179
assert.Equal(t, mounts[3].Target, "\\\\.\\pipe\\docker_engine")
168180

169-
mounts, err = buildContainerMountOptions(project, project.Services["myService"], image.InspectResponse{}, inherit)
181+
mounts, err = s.buildContainerMountOptions(context.TODO(), project, project.Services["myService"], inherit)
170182
sort.Slice(mounts, func(i, j int) bool {
171183
return mounts[i].Target < mounts[j].Target
172184
})
@@ -321,3 +333,123 @@ func TestCreateEndpointSettings(t *testing.T) {
321333
IPv6Gateway: "fdb4:7a7f:373a:3f0c::42",
322334
}))
323335
}
336+
337+
func Test_buildContainerVolumes(t *testing.T) {
338+
pwd, err := os.Getwd()
339+
assert.NilError(t, err)
340+
341+
tests := []struct {
342+
name string
343+
yaml string
344+
binds []string
345+
mounts []mountTypes.Mount
346+
}{
347+
{
348+
name: "bind mount local path",
349+
yaml: `
350+
services:
351+
test:
352+
volumes:
353+
- ./data:/data
354+
`,
355+
binds: []string{filepath.Join(pwd, "data") + ":/data:rw"},
356+
mounts: nil,
357+
},
358+
{
359+
name: "bind mount, not create host path",
360+
yaml: `
361+
services:
362+
test:
363+
volumes:
364+
- type: bind
365+
source: ./data
366+
target: /data
367+
bind:
368+
create_host_path: false
369+
`,
370+
binds: nil,
371+
mounts: []mountTypes.Mount{
372+
{
373+
Type: "bind",
374+
Source: filepath.Join(pwd, "data"),
375+
Target: "/data",
376+
BindOptions: &mountTypes.BindOptions{CreateMountpoint: false},
377+
},
378+
},
379+
},
380+
{
381+
name: "mount volume",
382+
yaml: `
383+
services:
384+
test:
385+
volumes:
386+
- data:/data
387+
volumes:
388+
data:
389+
name: my_volume
390+
`,
391+
binds: []string{"my_volume:/data:rw"},
392+
mounts: nil,
393+
},
394+
{
395+
name: "mount volume, readonly",
396+
yaml: `
397+
services:
398+
test:
399+
volumes:
400+
- data:/data:ro
401+
volumes:
402+
data:
403+
name: my_volume
404+
`,
405+
binds: []string{"my_volume:/data:ro"},
406+
mounts: nil,
407+
},
408+
{
409+
name: "mount volume subpath",
410+
yaml: `
411+
services:
412+
test:
413+
volumes:
414+
- type: volume
415+
source: data
416+
target: /data
417+
volume:
418+
subpath: test/
419+
volumes:
420+
data:
421+
name: my_volume
422+
`,
423+
binds: nil,
424+
mounts: []mountTypes.Mount{
425+
{
426+
Type: "volume",
427+
Source: "my_volume",
428+
Target: "/data",
429+
VolumeOptions: &mountTypes.VolumeOptions{Subpath: "test/"},
430+
},
431+
},
432+
},
433+
}
434+
for _, tt := range tests {
435+
t.Run(tt.name, func(t *testing.T) {
436+
p, err := composeloader.LoadWithContext(context.TODO(), composetypes.ConfigDetails{
437+
ConfigFiles: []composetypes.ConfigFile{
438+
{
439+
Filename: "test",
440+
Content: []byte(tt.yaml),
441+
},
442+
},
443+
}, func(options *composeloader.Options) {
444+
options.SkipValidation = true
445+
options.SkipConsistencyCheck = true
446+
})
447+
assert.NilError(t, err)
448+
s := &composeService{}
449+
binds, mounts, err := s.buildContainerVolumes(context.TODO(), *p, p.Services["test"], nil)
450+
assert.NilError(t, err)
451+
assert.DeepEqual(t, tt.binds, binds)
452+
assert.DeepEqual(t, tt.mounts, mounts)
453+
})
454+
}
455+
}

0 commit comments

Comments
 (0)