Skip to content

Commit

Permalink
Generify mount sources
Browse files Browse the repository at this point in the history
- introduce Docker specific mount sources and generic counterparts
- move Docker specific code to mounts_docker.go
  • Loading branch information
prskr committed Dec 13, 2021
1 parent b8502a5 commit 91dc436
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 104 deletions.
8 changes: 4 additions & 4 deletions container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
116 changes: 116 additions & 0 deletions docker_mounts.go
Original file line number Diff line number Diff line change
@@ -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
}
125 changes: 36 additions & 89 deletions mounts.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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
Expand All @@ -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,
}
}
Expand All @@ -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
}
Loading

0 comments on commit 91dc436

Please sign in to comment.