test to cover preference for bind API
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
c83f1285a8
commit
a3f88a0a1d
@ -39,7 +39,6 @@ import (
|
|||||||
"github.com/docker/docker/api/types/blkiodev"
|
"github.com/docker/docker/api/types/blkiodev"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/image"
|
|
||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/api/types/strslice"
|
"github.com/docker/docker/api/types/strslice"
|
||||||
@ -828,7 +827,6 @@ func getDependentServiceFromMode(mode string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
|
||||||
func (s *composeService) buildContainerVolumes(
|
func (s *composeService) buildContainerVolumes(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
p types.Project,
|
p types.Project,
|
||||||
@ -838,13 +836,7 @@ func (s *composeService) buildContainerVolumes(
|
|||||||
var mounts []mount.Mount
|
var mounts []mount.Mount
|
||||||
var binds []string
|
var binds []string
|
||||||
|
|
||||||
img := api.GetImageNameOrDefault(service, p.Name)
|
mountOptions, err := s.buildContainerMountOptions(ctx, p, service, inherit)
|
||||||
imgInspect, err := s.apiClient().ImageInspect(ctx, img)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
mountOptions, err := buildContainerMountOptions(p, service, imgInspect, inherit)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -857,11 +849,10 @@ func (s *composeService) buildContainerVolumes(
|
|||||||
// see https://github.com/moby/moby/issues/43483
|
// see https://github.com/moby/moby/issues/43483
|
||||||
v := findVolumeByTarget(service.Volumes, m.Target)
|
v := findVolumeByTarget(service.Volumes, m.Target)
|
||||||
if v != nil {
|
if v != nil {
|
||||||
switch {
|
if v.Type != types.VolumeTypeBind {
|
||||||
case v.Type != types.VolumeTypeBind:
|
|
||||||
v.Source = m.Source
|
v.Source = m.Source
|
||||||
fallthrough
|
}
|
||||||
case !requireMountAPI(v.Bind):
|
if !bindRequiresMountAPI(v.Bind) {
|
||||||
source := m.Source
|
source := m.Source
|
||||||
if vol := findVolumeByName(p.Volumes, m.Source); vol != nil {
|
if vol := findVolumeByName(p.Volumes, m.Source); vol != nil {
|
||||||
source = m.Source
|
source = m.Source
|
||||||
@ -874,8 +865,8 @@ func (s *composeService) buildContainerVolumes(
|
|||||||
v := findVolumeByTarget(service.Volumes, m.Target)
|
v := findVolumeByTarget(service.Volumes, m.Target)
|
||||||
vol := findVolumeByName(p.Volumes, m.Source)
|
vol := findVolumeByName(p.Volumes, m.Source)
|
||||||
if v != nil && vol != nil {
|
if v != nil && vol != nil {
|
||||||
if _, ok := vol.DriverOpts["device"]; ok && vol.Driver == "local" && vol.DriverOpts["o"] == "bind" {
|
// Prefer the bind API if no advanced option is used, to preserve backward compatibility
|
||||||
// Looks like a volume, but actually a bind mount which requires the bind API
|
if !volumeRequiresMountAPI(v.Volume) {
|
||||||
binds = append(binds, toBindString(vol.Name, v))
|
binds = append(binds, toBindString(vol.Name, v))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -930,9 +921,9 @@ func findVolumeByTarget(volumes []types.ServiceVolumeConfig, target string) *typ
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// requireMountAPI check if Bind declaration can be implemented by the plain old Bind API or uses any of the advanced
|
// bindRequiresMountAPI check if Bind declaration can be implemented by the plain old Bind API or uses any of the advanced
|
||||||
// options which require use of Mount API
|
// options which require use of Mount API
|
||||||
func requireMountAPI(bind *types.ServiceVolumeBind) bool {
|
func bindRequiresMountAPI(bind *types.ServiceVolumeBind) bool {
|
||||||
switch {
|
switch {
|
||||||
case bind == nil:
|
case bind == nil:
|
||||||
return false
|
return false
|
||||||
@ -947,7 +938,24 @@ func requireMountAPI(bind *types.ServiceVolumeBind) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img image.InspectResponse, inherit *container.Summary) ([]mount.Mount, error) {
|
// volumeRequiresMountAPI check if Volume declaration can be implemented by the plain old Bind API or uses any of the advanced
|
||||||
|
// options which require use of Mount API
|
||||||
|
func volumeRequiresMountAPI(vol *types.ServiceVolumeVolume) bool {
|
||||||
|
switch {
|
||||||
|
case vol == nil:
|
||||||
|
return false
|
||||||
|
case len(vol.Labels) > 0:
|
||||||
|
return true
|
||||||
|
case vol.Subpath != "":
|
||||||
|
return true
|
||||||
|
case vol.NoCopy:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) buildContainerMountOptions(ctx context.Context, p types.Project, service types.ServiceConfig, inherit *container.Summary) ([]mount.Mount, error) {
|
||||||
mounts := map[string]mount.Mount{}
|
mounts := map[string]mount.Mount{}
|
||||||
if inherit != nil {
|
if inherit != nil {
|
||||||
for _, m := range inherit.Mounts {
|
for _, m := range inherit.Mounts {
|
||||||
@ -959,6 +967,11 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img imag
|
|||||||
src = m.Name
|
src = m.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img, err := s.apiClient().ImageInspect(ctx, api.GetImageNameOrDefault(service, p.Name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if img.Config != nil {
|
if img.Config != nil {
|
||||||
if _, ok := img.Config.Volumes[m.Destination]; ok {
|
if _, ok := img.Config.Volumes[m.Destination]; ok {
|
||||||
// inherit previous container's anonymous volume
|
// inherit previous container's anonymous volume
|
||||||
@ -971,7 +984,7 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img imag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
volumes := []types.ServiceVolumeConfig{}
|
volumes := []types.ServiceVolumeConfig{}
|
||||||
for _, v := range s.Volumes {
|
for _, v := range service.Volumes {
|
||||||
if v.Target != m.Destination || v.Source != "" {
|
if v.Target != m.Destination || v.Source != "" {
|
||||||
volumes = append(volumes, v)
|
volumes = append(volumes, v)
|
||||||
continue
|
continue
|
||||||
@ -984,11 +997,11 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img imag
|
|||||||
ReadOnly: !m.RW,
|
ReadOnly: !m.RW,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.Volumes = volumes
|
service.Volumes = volumes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mounts, err := fillBindMounts(p, s, mounts)
|
mounts, err := fillBindMounts(p, service, mounts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,16 @@
|
|||||||
package compose
|
package compose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
composeloader "github.com/compose-spec/compose-go/v2/loader"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
"gotest.tools/v3/assert/cmp"
|
"gotest.tools/v3/assert/cmp"
|
||||||
|
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
@ -154,7 +157,16 @@ func TestBuildContainerMountOptions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
mounts, err := buildContainerMountOptions(project, project.Services["myService"], image.InspectResponse{}, inherit)
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mock, cli := prepareMocks(mockCtrl)
|
||||||
|
s := composeService{
|
||||||
|
dockerCli: cli,
|
||||||
|
}
|
||||||
|
mock.EXPECT().ImageInspect(gomock.Any(), "myProject-myService").AnyTimes().Return(image.InspectResponse{}, nil)
|
||||||
|
|
||||||
|
mounts, err := s.buildContainerMountOptions(context.TODO(), project, project.Services["myService"], inherit)
|
||||||
sort.Slice(mounts, func(i, j int) bool {
|
sort.Slice(mounts, func(i, j int) bool {
|
||||||
return mounts[i].Target < mounts[j].Target
|
return mounts[i].Target < mounts[j].Target
|
||||||
})
|
})
|
||||||
@ -166,7 +178,7 @@ func TestBuildContainerMountOptions(t *testing.T) {
|
|||||||
assert.Equal(t, mounts[2].VolumeOptions.Subpath, "etc")
|
assert.Equal(t, mounts[2].VolumeOptions.Subpath, "etc")
|
||||||
assert.Equal(t, mounts[3].Target, "\\\\.\\pipe\\docker_engine")
|
assert.Equal(t, mounts[3].Target, "\\\\.\\pipe\\docker_engine")
|
||||||
|
|
||||||
mounts, err = buildContainerMountOptions(project, project.Services["myService"], image.InspectResponse{}, inherit)
|
mounts, err = s.buildContainerMountOptions(context.TODO(), project, project.Services["myService"], inherit)
|
||||||
sort.Slice(mounts, func(i, j int) bool {
|
sort.Slice(mounts, func(i, j int) bool {
|
||||||
return mounts[i].Target < mounts[j].Target
|
return mounts[i].Target < mounts[j].Target
|
||||||
})
|
})
|
||||||
@ -321,3 +333,123 @@ func TestCreateEndpointSettings(t *testing.T) {
|
|||||||
IPv6Gateway: "fdb4:7a7f:373a:3f0c::42",
|
IPv6Gateway: "fdb4:7a7f:373a:3f0c::42",
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_buildContainerVolumes(t *testing.T) {
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
yaml string
|
||||||
|
binds []string
|
||||||
|
mounts []mountTypes.Mount
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bind mount local path",
|
||||||
|
yaml: `
|
||||||
|
services:
|
||||||
|
test:
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
`,
|
||||||
|
binds: []string{filepath.Join(pwd, "data") + ":/data:rw"},
|
||||||
|
mounts: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bind mount, not create host path",
|
||||||
|
yaml: `
|
||||||
|
services:
|
||||||
|
test:
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: ./data
|
||||||
|
target: /data
|
||||||
|
bind:
|
||||||
|
create_host_path: false
|
||||||
|
`,
|
||||||
|
binds: nil,
|
||||||
|
mounts: []mountTypes.Mount{
|
||||||
|
{
|
||||||
|
Type: "bind",
|
||||||
|
Source: filepath.Join(pwd, "data"),
|
||||||
|
Target: "/data",
|
||||||
|
BindOptions: &mountTypes.BindOptions{CreateMountpoint: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mount volume",
|
||||||
|
yaml: `
|
||||||
|
services:
|
||||||
|
test:
|
||||||
|
volumes:
|
||||||
|
- data:/data
|
||||||
|
volumes:
|
||||||
|
data:
|
||||||
|
name: my_volume
|
||||||
|
`,
|
||||||
|
binds: []string{"my_volume:/data:rw"},
|
||||||
|
mounts: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mount volume, readonly",
|
||||||
|
yaml: `
|
||||||
|
services:
|
||||||
|
test:
|
||||||
|
volumes:
|
||||||
|
- data:/data:ro
|
||||||
|
volumes:
|
||||||
|
data:
|
||||||
|
name: my_volume
|
||||||
|
`,
|
||||||
|
binds: []string{"my_volume:/data:ro"},
|
||||||
|
mounts: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mount volume subpath",
|
||||||
|
yaml: `
|
||||||
|
services:
|
||||||
|
test:
|
||||||
|
volumes:
|
||||||
|
- type: volume
|
||||||
|
source: data
|
||||||
|
target: /data
|
||||||
|
volume:
|
||||||
|
subpath: test/
|
||||||
|
volumes:
|
||||||
|
data:
|
||||||
|
name: my_volume
|
||||||
|
`,
|
||||||
|
binds: nil,
|
||||||
|
mounts: []mountTypes.Mount{
|
||||||
|
{
|
||||||
|
Type: "volume",
|
||||||
|
Source: "my_volume",
|
||||||
|
Target: "/data",
|
||||||
|
VolumeOptions: &mountTypes.VolumeOptions{Subpath: "test/"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
p, err := composeloader.LoadWithContext(context.TODO(), composetypes.ConfigDetails{
|
||||||
|
ConfigFiles: []composetypes.ConfigFile{
|
||||||
|
{
|
||||||
|
Filename: "test",
|
||||||
|
Content: []byte(tt.yaml),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, func(options *composeloader.Options) {
|
||||||
|
options.SkipValidation = true
|
||||||
|
options.SkipConsistencyCheck = true
|
||||||
|
})
|
||||||
|
assert.NilError(t, err)
|
||||||
|
s := &composeService{}
|
||||||
|
binds, mounts, err := s.buildContainerVolumes(context.TODO(), *p, p.Services["test"], nil)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, tt.binds, binds)
|
||||||
|
assert.DeepEqual(t, tt.mounts, mounts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user