diff --git a/container_test.go b/container_test.go index ca91f4e7b08..a77eea45fe9 100644 --- a/container_test.go +++ b/container_test.go @@ -372,12 +372,12 @@ func TestBindMount(t *testing.T) { { name: "/var/run/docker.sock:/var/run/docker.sock", args: args{hostPath: "/var/run/docker.sock", mountTarget: "/var/run/docker.sock"}, - want: ContainerMount{Source: BindMountSource{HostPath: "/var/run/docker.sock"}, Target: "/var/run/docker.sock"}, + want: ContainerMount{Source: GenericBindMountSource{HostPath: "/var/run/docker.sock"}, Target: "/var/run/docker.sock"}, }, { name: "/var/lib/app/data:/data", args: args{hostPath: "/var/lib/app/data", mountTarget: "/data"}, - want: ContainerMount{Source: BindMountSource{HostPath: "/var/lib/app/data"}, Target: "/data"}, + want: ContainerMount{Source: GenericBindMountSource{HostPath: "/var/lib/app/data"}, Target: "/data"}, }, } for _, tt := range tests { @@ -400,12 +400,12 @@ func TestVolumeMount(t *testing.T) { { name: "sample-data:/data", args: args{volumeName: "sample-data", mountTarget: "/data"}, - want: ContainerMount{Source: VolumeMountSource{Name: "sample-data"}, Target: "/data"}, + want: ContainerMount{Source: GenericVolumeMountSource{Name: "sample-data"}, Target: "/data"}, }, { name: "web:/var/nginx/html", args: args{volumeName: "web", mountTarget: "/var/nginx/html"}, - want: ContainerMount{Source: VolumeMountSource{Name: "web"}, Target: "/var/nginx/html"}, + want: ContainerMount{Source: GenericVolumeMountSource{Name: "web"}, Target: "/var/nginx/html"}, }, } for _, tt := range tests { diff --git a/docker.go b/docker.go index d9daaab3447..36418986004 100644 --- a/docker.go +++ b/docker.go @@ -745,7 +745,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque } // prepare mounts - mounts := req.Mounts.PrepareMounts() + mounts := mapToDockerMounts(req.Mounts) hostConfig := &container.HostConfig{ PortBindings: exposedPortMap, diff --git a/docker_mounts.go b/docker_mounts.go new file mode 100644 index 00000000000..cf7823ff77c --- /dev/null +++ b/docker_mounts.go @@ -0,0 +1,116 @@ +package testcontainers + +import "github.com/docker/docker/api/types/mount" + +var ( + mountTypeMapping = map[MountType]mount.Type{ + MountTypeBind: mount.TypeBind, + MountTypeVolume: mount.TypeVolume, + MountTypeTmpfs: mount.TypeTmpfs, + MountTypePipe: mount.TypeNamedPipe, + } +) + +// BindMounter can optionally be implemented by mount sources +// to support advanced scenarios based on mount.BindOptions +type BindMounter interface { + GetBindOptions() *mount.BindOptions +} + +// VolumeMounter can optionally be implemented by mount sources +// to support advanced scenarios based on mount.VolumeOptions +type VolumeMounter interface { + GetVolumeOptions() *mount.VolumeOptions +} + +// TmpfsMounter can optionally be implemented by mount sources +// to support advanced scenarios based on mount.TmpfsOptions +type TmpfsMounter interface { + GetTmpfsOptions() *mount.TmpfsOptions +} + +type DockerBindMountSource struct { + *mount.BindOptions + + // HostPath is the path mounted into the container + // the same host path might be mounted to multiple locations withing a single container + HostPath string +} + +func (s DockerBindMountSource) Source() string { + return s.HostPath +} + +func (DockerBindMountSource) Type() MountType { + return MountTypeBind +} + +func (s DockerBindMountSource) GetBindOptions() *mount.BindOptions { + return s.BindOptions +} + +type DockerVolumeMountSource struct { + *mount.VolumeOptions + + // Name refers to the name of the volume to be mounted + // the same volume might be mounted to multiple locations within a single container + Name string +} + +func (s DockerVolumeMountSource) Source() string { + return s.Name +} + +func (DockerVolumeMountSource) Type() MountType { + return MountTypeVolume +} + +func (s DockerVolumeMountSource) GetVolumeOptions() *mount.VolumeOptions { + return s.VolumeOptions +} + +type DockerTmpfsMountSource struct { + GenericTmpfsMountSource + *mount.TmpfsOptions +} + +func (s DockerTmpfsMountSource) GetTmpfsOptions() *mount.TmpfsOptions { + return s.TmpfsOptions +} + +// mapToDockerMounts maps the given []ContainerMount to the corresponding +// []mount.Mount for further processing +func mapToDockerMounts(containerMounts ContainerMounts) []mount.Mount { + mounts := make([]mount.Mount, 0, len(containerMounts)) + + for idx := range containerMounts { + m := containerMounts[idx] + + var mountType mount.Type + if mt, ok := mountTypeMapping[m.Source.Type()]; ok { + mountType = mt + } else { + continue + } + + containerMount := mount.Mount{ + Type: mountType, + Source: m.Source.Source(), + ReadOnly: m.ReadOnly, + Target: m.Target.Target(), + } + + switch typedMounter := m.Source.(type) { + case BindMounter: + containerMount.BindOptions = typedMounter.GetBindOptions() + case VolumeMounter: + containerMount.VolumeOptions = typedMounter.GetVolumeOptions() + case TmpfsMounter: + containerMount.TmpfsOptions = typedMounter.GetTmpfsOptions() + } + + mounts = append(mounts, containerMount) + } + + return mounts +} diff --git a/mounts.go b/mounts.go index 2a1beac8a90..47d08e67201 100644 --- a/mounts.go +++ b/mounts.go @@ -1,26 +1,23 @@ package testcontainers -import ( - "github.com/docker/docker/api/types/mount" +const ( + MountTypeBind MountType = iota + MountTypeVolume + MountTypeTmpfs + MountTypePipe ) -// BindMounter can optionally be implemented by mount sources -// to support advanced scenarios based on mount.BindOptions -type BindMounter interface { - GetBindOptions() *mount.BindOptions -} - -// VolumeMounter can optionally be implemented by mount sources -// to support advanced scenarios based on mount.VolumeOptions -type VolumeMounter interface { - GetVolumeOptions() *mount.VolumeOptions -} +var ( + _ ContainerMountSource = (*GenericBindMountSource)(nil) + _ ContainerMountSource = (*GenericVolumeMountSource)(nil) + _ ContainerMountSource = (*GenericTmpfsMountSource)(nil) +) -// TmpfsMounter can optionally be implemented by mount sources -// to support advanced scenarios based on mount.TmpfsOptions -type TmpfsMounter interface { - GetTmpfsOptions() *mount.TmpfsOptions -} +type ( + // ContainerMounts represents a collection of mounts for a container + ContainerMounts []ContainerMount + MountType uint +) // ContainerMountSource is the base for all mount sources type ContainerMountSource interface { @@ -30,69 +27,51 @@ type ContainerMountSource interface { // Type determines the final mount type // possible options are limited by the Docker API - Type() mount.Type + Type() MountType } -// BindMountSource implements ContainerMountSource and represents a bind mount +// GenericBindMountSource implements ContainerMountSource and represents a bind mount // Optionally mount.BindOptions might be added for advanced scenarios -type BindMountSource struct { - *mount.BindOptions - +type GenericBindMountSource struct { // HostPath is the path mounted into the container // the same host path might be mounted to multiple locations withing a single container HostPath string } -func (s BindMountSource) Source() string { +func (s GenericBindMountSource) Source() string { return s.HostPath } -func (BindMountSource) Type() mount.Type { - return mount.TypeBind +func (GenericBindMountSource) Type() MountType { + return MountTypeBind } -func (s BindMountSource) GetBindOptions() *mount.BindOptions { - return s.BindOptions -} - -// VolumeMountSource implements ContainerMountSource and represents a volume mount -// Optionally mount.VolumeOptions might be added for advanced scenarios -type VolumeMountSource struct { - *mount.VolumeOptions - +// GenericVolumeMountSource implements ContainerMountSource and represents a volume mount +type GenericVolumeMountSource struct { // Name refers to the name of the volume to be mounted // the same volume might be mounted to multiple locations within a single container Name string } -func (s VolumeMountSource) Source() string { +func (s GenericVolumeMountSource) Source() string { return s.Name } -func (VolumeMountSource) Type() mount.Type { - return mount.TypeVolume +func (GenericVolumeMountSource) Type() MountType { + return MountTypeVolume } -func (s VolumeMountSource) GetVolumeOptions() *mount.VolumeOptions { - return s.VolumeOptions -} - -// TmpfsMountSource implements ContainerMountSource and represents a TmpFS mount +// GenericTmpfsMountSource implements ContainerMountSource and represents a TmpFS mount // Optionally mount.TmpfsOptions might be added for advanced scenarios -type TmpfsMountSource struct { - *mount.TmpfsOptions +type GenericTmpfsMountSource struct { } -func (s TmpfsMountSource) Source() string { +func (s GenericTmpfsMountSource) Source() string { return "" } -func (TmpfsMountSource) Type() mount.Type { - return mount.TypeTmpfs -} - -func (s TmpfsMountSource) GetTmpfsOptions() *mount.TmpfsOptions { - return s.TmpfsOptions +func (GenericTmpfsMountSource) Type() MountType { + return MountTypeTmpfs } // ContainerMountTarget represents the target path within a container where the mount will be available @@ -103,20 +82,20 @@ func (t ContainerMountTarget) Target() string { return string(t) } -// BindMount returns a new ContainerMount with a BindMountSource as source +// BindMount returns a new ContainerMount with a GenericBindMountSource as source // This is a convenience method to cover typical use cases. func BindMount(hostPath string, mountTarget ContainerMountTarget) ContainerMount { return ContainerMount{ - Source: BindMountSource{HostPath: hostPath}, + Source: GenericBindMountSource{HostPath: hostPath}, Target: mountTarget, } } -// VolumeMount returns a new ContainerMount with a VolumeMountSource as source +// VolumeMount returns a new ContainerMount with a GenericVolumeMountSource as source // This is a convenience method to cover typical use cases. func VolumeMount(volumeName string, mountTarget ContainerMountTarget) ContainerMount { return ContainerMount{ - Source: VolumeMountSource{Name: volumeName}, + Source: GenericVolumeMountSource{Name: volumeName}, Target: mountTarget, } } @@ -128,42 +107,10 @@ func Mounts(mounts ...ContainerMount) ContainerMounts { // ContainerMount models a mount into a container type ContainerMount struct { - // Source is typically either a BindMountSource or a VolumeMountSource + // Source is typically either a GenericBindMountSource or a GenericVolumeMountSource Source ContainerMountSource // Target is the path where the mount should be mounted within the container Target ContainerMountTarget // ReadOnly determines if the mount should be read-only ReadOnly bool } - -// ContainerMounts represents a collection of mounts for a container -type ContainerMounts []ContainerMount - -// PrepareMounts maps the given []ContainerMount to the corresponding -// []mount.Mount for further processing -func (m ContainerMounts) PrepareMounts() []mount.Mount { - mounts := make([]mount.Mount, 0, len(m)) - - for idx := range m { - m := m[idx] - containerMount := mount.Mount{ - Type: m.Source.Type(), - Source: m.Source.Source(), - ReadOnly: m.ReadOnly, - Target: m.Target.Target(), - } - - switch typedMounter := m.Source.(type) { - case BindMounter: - containerMount.BindOptions = typedMounter.GetBindOptions() - case VolumeMounter: - containerMount.VolumeOptions = typedMounter.GetVolumeOptions() - case TmpfsMounter: - containerMount.TmpfsOptions = typedMounter.GetTmpfsOptions() - } - - mounts = append(mounts, containerMount) - } - - return mounts -} diff --git a/mounts_test.go b/mounts_test.go index 61f22ac7074..952868b0578 100644 --- a/mounts_test.go +++ b/mounts_test.go @@ -21,7 +21,7 @@ func TestContainerMounts_PrepareMounts(t *testing.T) { }, { name: "Single bind mount", - mounts: ContainerMounts{{Source: BindMountSource{HostPath: "/var/lib/app/data"}, Target: "/data"}}, + mounts: ContainerMounts{{Source: GenericBindMountSource{HostPath: "/var/lib/app/data"}, Target: "/data"}}, want: []mount.Mount{ { Type: mount.TypeBind, @@ -32,7 +32,7 @@ func TestContainerMounts_PrepareMounts(t *testing.T) { }, { name: "Single bind mount - read-only", - mounts: ContainerMounts{{Source: BindMountSource{HostPath: "/var/lib/app/data"}, Target: "/data", ReadOnly: true}}, + mounts: ContainerMounts{{Source: GenericBindMountSource{HostPath: "/var/lib/app/data"}, Target: "/data", ReadOnly: true}}, want: []mount.Mount{ { Type: mount.TypeBind, @@ -46,7 +46,7 @@ func TestContainerMounts_PrepareMounts(t *testing.T) { name: "Single bind mount - with options", mounts: ContainerMounts{ { - Source: BindMountSource{ + Source: DockerBindMountSource{ HostPath: "/var/lib/app/data", BindOptions: &mount.BindOptions{ Propagation: mount.PropagationPrivate, @@ -68,7 +68,7 @@ func TestContainerMounts_PrepareMounts(t *testing.T) { }, { name: "Single volume mount", - mounts: ContainerMounts{{Source: VolumeMountSource{Name: "app-data"}, Target: "/data"}}, + mounts: ContainerMounts{{Source: GenericVolumeMountSource{Name: "app-data"}, Target: "/data"}}, want: []mount.Mount{ { Type: mount.TypeVolume, @@ -79,7 +79,7 @@ func TestContainerMounts_PrepareMounts(t *testing.T) { }, { name: "Single volume mount - read-only", - mounts: ContainerMounts{{Source: VolumeMountSource{Name: "app-data"}, Target: "/data", ReadOnly: true}}, + mounts: ContainerMounts{{Source: GenericVolumeMountSource{Name: "app-data"}, Target: "/data", ReadOnly: true}}, want: []mount.Mount{ { Type: mount.TypeVolume, @@ -93,7 +93,7 @@ func TestContainerMounts_PrepareMounts(t *testing.T) { name: "Single volume mount - with options", mounts: ContainerMounts{ { - Source: VolumeMountSource{ + Source: DockerVolumeMountSource{ Name: "app-data", VolumeOptions: &mount.VolumeOptions{ NoCopy: true, @@ -122,7 +122,7 @@ func TestContainerMounts_PrepareMounts(t *testing.T) { { name: "Single tmpfs mount", - mounts: ContainerMounts{{Source: TmpfsMountSource{}, Target: "/data"}}, + mounts: ContainerMounts{{Source: GenericTmpfsMountSource{}, Target: "/data"}}, want: []mount.Mount{ { Type: mount.TypeTmpfs, @@ -132,7 +132,7 @@ func TestContainerMounts_PrepareMounts(t *testing.T) { }, { name: "Single volume mount - read-only", - mounts: ContainerMounts{{Source: TmpfsMountSource{}, Target: "/data", ReadOnly: true}}, + mounts: ContainerMounts{{Source: GenericTmpfsMountSource{}, Target: "/data", ReadOnly: true}}, want: []mount.Mount{ { Type: mount.TypeTmpfs, @@ -145,7 +145,7 @@ func TestContainerMounts_PrepareMounts(t *testing.T) { name: "Single volume mount - with options", mounts: ContainerMounts{ { - Source: TmpfsMountSource{ + Source: DockerTmpfsMountSource{ TmpfsOptions: &mount.TmpfsOptions{ SizeBytes: 50 * 1024 * 1024, Mode: 0o644, @@ -170,7 +170,7 @@ func TestContainerMounts_PrepareMounts(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - assert.Equalf(t, tt.want, tt.mounts.PrepareMounts(), "PrepareMounts()") + assert.Equalf(t, tt.want, mapToDockerMounts(tt.mounts), "PrepareMounts()") }) } }