Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - feat: Support other volume mount modes #3334

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkg/cnab/provider/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"get.porter.sh/porter/pkg/tracing"
cnabaction "github.com/cnabio/cnab-go/action"
"github.com/cnabio/cnab-go/driver"
"github.com/docker/docker/api/types/mount"
"github.com/hashicorp/go-multierror"
"go.opentelemetry.io/otel/attribute"
"go.uber.org/zap/zapcore"
Expand All @@ -21,6 +22,7 @@ type HostVolumeMountSpec struct {
Source string
Target string
ReadOnly bool
Type mount.Type
}

// ActionArguments are the shared arguments for all bundle runs.
Expand Down
6 changes: 3 additions & 3 deletions pkg/cnab/provider/docker_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@ func (r *Runtime) mountDockerSocket(cfg *container.Config, hostCfg *container.Ho
dockerSockMount := mount.Mount{
Source: "/var/run/docker.sock",
Target: "/var/run/docker.sock",
Type: "bind",
Type: mount.TypeBind,
ReadOnly: false,
}
hostCfg.Mounts = append(hostCfg.Mounts, dockerSockMount)
return nil
}

func (r *Runtime) addVolumeMountToHostConfig(hostConfig *container.HostConfig, source string, target string, readOnly bool) error {
func (r *Runtime) addVolumeMountToHostConfig(hostConfig *container.HostConfig, source string, target string, readOnly bool, mountType mount.Type) error {
mount := mount.Mount{
Source: source,
Target: target,
Type: "bind",
Type: mountType,
ReadOnly: readOnly,
}
hostConfig.Mounts = append(hostConfig.Mounts, mount)
Expand Down
2 changes: 1 addition & 1 deletion pkg/cnab/provider/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (r *Runtime) dockerDriverWithHostAccess(config cnab.Docker, d *docker.Drive

func (r *Runtime) addVolumeMountsToHostConfig(hostConfig *container.HostConfig, mounts []HostVolumeMountSpec) error {
for _, mount := range mounts {
err := r.addVolumeMountToHostConfig(hostConfig, mount.Source, mount.Target, mount.ReadOnly)
err := r.addVolumeMountToHostConfig(hostConfig, mount.Source, mount.Target, mount.ReadOnly, mount.Type)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/cnab/provider/driver_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ func (r *Runtime) mountDockerSocket(cfg *container.Config, hostCfg *container.Ho
return nil
}

func (r *Runtime) addVolumeMountToHostConfig(hostConfig *container.HostConfig, source string, target string, readOnly bool) error {
func (r *Runtime) addVolumeMountToHostConfig(hostConfig *container.HostConfig, source string, target string, readOnly bool, mountType mount.Type) error {
mount := mount.Mount{
Source: source,
Target: target,
Type: "bind",
Type: mountType,
ReadOnly: readOnly,
}
hostConfig.Mounts = append(hostConfig.Mounts, mount)
Expand Down
131 changes: 63 additions & 68 deletions pkg/cnab/provider/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"get.porter.sh/porter/pkg/cnab"
"github.com/cnabio/cnab-go/driver/docker"
"github.com/docker/docker/api/types/mount"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -131,34 +132,36 @@ func TestNewDriver_Docker(t *testing.T) {
r.MockGetDockerGroupId()
defer r.Close()

r.Extensions[cnab.DockerExtensionKey] = cnab.Docker{}
_, err := r.FileSystem.Create("/var/run/docker.sock")
require.NoError(t, err)
_, err = r.FileSystem.Create("/sourceFolder")
require.NoError(t, err)
_, err = r.FileSystem.Create("/sourceFolder2")
require.NoError(t, err)
_, err = r.FileSystem.Create("/sourceFolder3")
require.NoError(t, err)
var hostVolumeMounts = []HostVolumeMountSpec{
{
Source: "/sourceFolder",
Target: "/targetFolder",
ReadOnly: false,
Type: mount.TypeBind,
},
{
Source: "/sourceFolder2",
Target: "/targetFolder2",
ReadOnly: true,
Type: mount.TypeBind,
},
{
Source: "/sourceFolder3",
Target: "/targetFolder3",
ReadOnly: false,
Type: mount.TypeBind,
},
{
Source: "volume",
Target: "/targetFolder4",
ReadOnly: true,
Type: mount.TypeVolume,
},
}

args := ActionArguments{
AllowDockerHostAccess: true,
HostVolumeMounts: []HostVolumeMountSpec{
{
Source: "/sourceFolder",
Target: "/targetFolder",
},
{
Source: "/sourceFolder2",
Target: "/targetFolder2",
ReadOnly: true,
},
{
Source: "/sourceFolder3",
Target: "/targetFolder3",
ReadOnly: false,
},
},
HostVolumeMounts: hostVolumeMounts,
}

driver, err := r.newDriver(DriverNameDocker, args)
Expand All @@ -175,16 +178,14 @@ func TestNewDriver_Docker(t *testing.T) {
require.NoError(t, err)
require.Equal(t, false, containerHostCfg.Privileged)

require.Len(t, containerHostCfg.Mounts, 4) //includes the docker socket mount
assert.Equal(t, "/sourceFolder", containerHostCfg.Mounts[1].Source)
assert.Equal(t, "/targetFolder", containerHostCfg.Mounts[1].Target)
assert.Equal(t, false, containerHostCfg.Mounts[1].ReadOnly)
assert.Equal(t, "/sourceFolder2", containerHostCfg.Mounts[2].Source)
assert.Equal(t, "/targetFolder2", containerHostCfg.Mounts[2].Target)
assert.Equal(t, true, containerHostCfg.Mounts[2].ReadOnly)
assert.Equal(t, "/sourceFolder3", containerHostCfg.Mounts[3].Source)
assert.Equal(t, "/targetFolder3", containerHostCfg.Mounts[3].Target)
assert.Equal(t, false, containerHostCfg.Mounts[3].ReadOnly)
require.Len(t, containerHostCfg.Mounts, 5) //includes the docker socket mount

for i, hostMount := range hostVolumeMounts {
assert.Equal(t, hostMount.Source, containerHostCfg.Mounts[i+1].Source)
assert.Equal(t, hostMount.Target, containerHostCfg.Mounts[i+1].Target)
assert.Equal(t, hostMount.ReadOnly, containerHostCfg.Mounts[i+1].ReadOnly)
assert.Equal(t, hostMount.Type, containerHostCfg.Mounts[i+1].Type)
}
})

t.Run("host volume mount, docker driver, with multiple mounts", func(t *testing.T) {
Expand All @@ -193,30 +194,25 @@ func TestNewDriver_Docker(t *testing.T) {
r := NewTestRuntime(t)
defer r.Close()

_, err := r.FileSystem.Create("/sourceFolder")
require.NoError(t, err)
_, err = r.FileSystem.Create("/sourceFolder2")
require.NoError(t, err)
_, err = r.FileSystem.Create("/sourceFolder3")
require.NoError(t, err)
var hostVolumeMounts = []HostVolumeMountSpec{
{
Source: "/sourceFolder",
Target: "/targetFolder",
},
{
Source: "/sourceFolder2",
Target: "/targetFolder2",
ReadOnly: true,
},
{
Source: "/sourceFolder3",
Target: "/targetFolder3",
ReadOnly: false,
},
}

args := ActionArguments{
HostVolumeMounts: []HostVolumeMountSpec{
{
Source: "/sourceFolder",
Target: "/targetFolder",
},
{
Source: "/sourceFolder2",
Target: "/targetFolder2",
ReadOnly: true,
},
{
Source: "/sourceFolder3",
Target: "/targetFolder3",
ReadOnly: false,
},
},
HostVolumeMounts: hostVolumeMounts,
}

driver, err := r.newDriver(DriverNameDocker, args)
Expand All @@ -234,16 +230,13 @@ func TestNewDriver_Docker(t *testing.T) {
require.NoError(t, err)

require.Len(t, containerHostCfg.Mounts, 3)
assert.Equal(t, "/sourceFolder", containerHostCfg.Mounts[0].Source)
assert.Equal(t, "/targetFolder", containerHostCfg.Mounts[0].Target)
assert.Equal(t, false, containerHostCfg.Mounts[0].ReadOnly)
assert.Equal(t, "/sourceFolder2", containerHostCfg.Mounts[1].Source)
assert.Equal(t, "/targetFolder2", containerHostCfg.Mounts[1].Target)
assert.Equal(t, true, containerHostCfg.Mounts[1].ReadOnly)
assert.Equal(t, "/sourceFolder3", containerHostCfg.Mounts[2].Source)
assert.Equal(t, "/targetFolder3", containerHostCfg.Mounts[2].Target)
assert.Equal(t, false, containerHostCfg.Mounts[2].ReadOnly)

for i, hostMount := range hostVolumeMounts {
assert.Equal(t, hostMount.Source, containerHostCfg.Mounts[i].Source)
assert.Equal(t, hostMount.Target, containerHostCfg.Mounts[i].Target)
assert.Equal(t, hostMount.ReadOnly, containerHostCfg.Mounts[i].ReadOnly)
assert.Equal(t, hostMount.Type, containerHostCfg.Mounts[i].Type)
}
})

t.Run("host volume mount, docker driver, with single mount", func(t *testing.T) {
Expand All @@ -252,14 +245,15 @@ func TestNewDriver_Docker(t *testing.T) {
r := NewTestRuntime(t)
defer r.Close()

_, err := r.FileSystem.Create("/sourceFolder")
require.NoError(t, err)
// _, err := r.FileSystem.Create("/sourceFolder")
//require.NoError(t, err)

args := ActionArguments{
HostVolumeMounts: []HostVolumeMountSpec{
{
Source: "/sourceFolder",
Target: "/targetFolder",
Type: mount.TypeBind,
},
},
}
Expand All @@ -282,6 +276,7 @@ func TestNewDriver_Docker(t *testing.T) {
assert.Equal(t, "/sourceFolder", containerHostCfg.Mounts[0].Source)
assert.Equal(t, "/targetFolder", containerHostCfg.Mounts[0].Target)
assert.Equal(t, false, containerHostCfg.Mounts[0].ReadOnly)
assert.Equal(t, mount.TypeBind, containerHostCfg.Mounts[0].Type)
})

t.Run("host volume mount, mismatch driver name", func(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/cnab/provider/driver_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ func (r *Runtime) mountDockerSocket(cfg *container.Config, hostCfg *container.Ho
return nil
}

func (r *Runtime) addVolumeMountToHostConfig(hostConfig *container.HostConfig, source string, target string, readOnly bool) error {
func (r *Runtime) addVolumeMountToHostConfig(hostConfig *container.HostConfig, source string, target string, readOnly bool, mountType mount.Type) error {
mount := mount.Mount{
Source: source,
Target: target,
Type: "bind",
Type: mountType,
ReadOnly: readOnly,
}
hostConfig.Mounts = append(hostConfig.Mounts, mount)
Expand Down
22 changes: 17 additions & 5 deletions pkg/porter/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"get.porter.sh/porter/pkg/secrets"
"get.porter.sh/porter/pkg/storage"
"get.porter.sh/porter/pkg/tracing"
"github.com/docker/docker/api/types/mount"
"github.com/opencontainers/go-digest"
"go.mongodb.org/mongo-driver/bson"
)
Expand Down Expand Up @@ -48,8 +49,9 @@ type BundleExecutionOptions struct {
AllowDockerHostAccess bool

// MountHostVolume mounts provides the bundle access to a host volume.
// This is the unparsed list of HOST_PATH:TARGET_PATH:OPTION
// This is the unparsed list of HOST_PATH:TARGET_PATH:OPTION:MOUNT_TYPE
// OPTION can be ro (read-only) or rw (read-write). Defaults to ro.
// MOUNT_TYPE can be bind, volume, or tmpfs. Defaults to bind.
HostVolumeMounts []string

// DebugMode indicates if the bundle should be run in debug mode.
Expand Down Expand Up @@ -106,10 +108,10 @@ func (o *BundleExecutionOptions) GetParameters() map[string]interface{} {
// Sets the final resolved set of host volumes to be made availabe to the bundle
func (o *BundleExecutionOptions) GetHostVolumeMounts() []cnabprovider.HostVolumeMountSpec {
var hostVolumeMounts []cnabprovider.HostVolumeMountSpec
for _, mount := range o.HostVolumeMounts {
for _, mountString := range o.HostVolumeMounts {
var mountType mount.Type = mount.TypeBind
var isReadOnlyMount bool
parts := strings.Split(mount, ":") // HOST_PATH:TARGET_PATH:OPTION

parts := strings.Split(mountString, ":") // HOST_PATH:TARGET_PATH:OPTION:MOUNT_TYPE
// if parts[0] is a single character, it's a drive letter on Windows
// so we need to join it with the next part
if runtime.GOOS == "windows" && len(parts) > 1 && len(parts[0]) == 1 && unicode.IsLetter(rune(parts[0][0])) {
Expand All @@ -118,7 +120,8 @@ func (o *BundleExecutionOptions) GetHostVolumeMounts() []cnabprovider.HostVolume
}

l := len(parts)
if l < 2 || l > 3 {
if l < 2 || l > 4 {
fmt.Printf("ERROR: invalid mount: %s\n", mountString)
continue
}

Expand All @@ -134,10 +137,19 @@ func (o *BundleExecutionOptions) GetHostVolumeMounts() []cnabprovider.HostVolume
isReadOnlyMount = true
}

if l == 4 {
mountType = mount.Type(parts[3])
if mountType != mount.TypeBind && mountType != mount.TypeVolume && mountType != mount.TypeTmpfs {
fmt.Printf("ERROR: invalid mount type: %s\n", mountType)
continue
}
}

hostVolumeMounts = append(hostVolumeMounts, cnabprovider.HostVolumeMountSpec{
Source: parts[0],
Target: parts[1],
ReadOnly: isReadOnlyMount,
Type: mountType,
})
}

Expand Down
24 changes: 24 additions & 0 deletions pkg/porter/lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"get.porter.sh/porter/tests"
"github.com/cnabio/cnab-go/secrets/host"
"github.com/cnabio/cnab-to-oci/relocation"
"github.com/docker/docker/api/types/mount"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -712,6 +713,7 @@ func TestBundleExecutionOptions_GetHostVolumeMounts(t *testing.T) {
"/host/path:/target/path:ro",
"/host/path:/target/path:rw",
"/host/path:/target/path",
"myvolume:/target/path:ro:volume",
},
}

Expand All @@ -720,16 +722,25 @@ func TestBundleExecutionOptions_GetHostVolumeMounts(t *testing.T) {
Source: "/host/path",
Target: "/target/path",
ReadOnly: true,
Type: mount.TypeBind,
},
{
Source: "/host/path",
Target: "/target/path",
ReadOnly: false,
Type: mount.TypeBind,
},
{
Source: "/host/path",
Target: "/target/path",
ReadOnly: true,
Type: mount.TypeBind,
},
{
Source: "myvolume",
Target: "/target/path",
ReadOnly: true,
Type: mount.TypeVolume,
},
}

Expand All @@ -748,6 +759,7 @@ func TestBundleExecutionOptions_GetHostVolumeMounts(t *testing.T) {
if expected[i].ReadOnly != actual[i].ReadOnly {
t.Errorf("expected %v but got %v", expected[i].ReadOnly, actual[i].ReadOnly)
}

}
})

Expand Down Expand Up @@ -781,6 +793,18 @@ func TestBundleExecutionOptions_GetHostVolumeMounts(t *testing.T) {
}

})

t.Run("invalid host volume mount type option value", func(t *testing.T) {
opts := &BundleExecutionOptions{
HostVolumeMounts: []string{
"/host/path:/target/path:ro:invalid-option",
},
}

actual := opts.GetHostVolumeMounts()

assert.Equal(t, 0, len(actual), "expected no host volume mounts")
})
}

func TestBundleExecutionOptions_GetHostVolumeMountsWindows(t *testing.T) {
Expand Down