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

feat: Add BindMount handler for Linux #1119

Merged
merged 1 commit into from
Oct 3, 2024
Merged
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
67 changes: 67 additions & 0 deletions cmd/finch/nerdctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ var argHandlerMap = map[string]map[string]argHandler{
"image build": {
"--load": handleDockerBuildLoad,
},
"container run": {
"--mount": handleBindMounts,
},
}

var cmdFlagSetMap = map[string]map[string]sets.Set[string]{
Expand Down Expand Up @@ -339,3 +342,67 @@ func handleDockerCompatInspect(_ NerdctlCommandSystemDeps, fc *config.Finch, cmd

return nil
}

// handles the argument & value of --mount option
//
// invokes OS specific path handler for source path of the bind mount
// and removes the consistency key-value entity from value
func handleBindMounts(systemDeps NerdctlCommandSystemDeps, _ *config.Finch, nerdctlCmdArgs []string, index int) error {
prefix := nerdctlCmdArgs[index]
var (
v string
found bool
before string
)
if strings.Contains(nerdctlCmdArgs[index], "=") {
before, v, found = strings.Cut(prefix, "=")
} else {
if (index + 1) < len(nerdctlCmdArgs) {
v = nerdctlCmdArgs[index+1]
} else {
return fmt.Errorf("invalid positional parameter for %s", prefix)
}
}

// eg --mount type=bind,source="$(pwd)"/target,target=/app,readonly
// eg --mount type=bind, source=${pwd}/source_dir, target=<path>/target_dir, consistency=cached
// https://docs.docker.com/storage/bind-mounts/#choose-the--v-or---mount-flag order does not matter, so convert to a map
entries := strings.Split(v, ",")
m := make(map[string]string)
ro := []string{}
for _, e := range entries {
parts := strings.Split(e, "=")
if len(parts) < 2 {
ro = append(ro, parts...)
} else {
m[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
}
// Check if type is bind mount, else return
if m["type"] != "bind" {
return nil
}

// Remove 'consistency' key-value pair, if present
delete(m, "consistency")

// Invoke the OS specific path handler
err := handleBindMountPath(systemDeps, m)
if err != nil {
return err
}

// Convert to string representation
s := mapToString(m)
// append read-only key if present
if len(ro) > 0 {
s = s + "," + strings.Join(ro, ",")
}
if found {
nerdctlCmdArgs[index] = fmt.Sprintf("%s=%s", before, s)
} else {
nerdctlCmdArgs[index+1] = s
}

return nil
}
67 changes: 3 additions & 64 deletions cmd/finch/nerdctl_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/lima-vm/lima/pkg/networks"

"github.com/runfinch/finch/pkg/command"
"github.com/runfinch/finch/pkg/config"
"github.com/runfinch/finch/pkg/flog"
)

Expand All @@ -23,11 +22,7 @@ func convertToWSLPath(_ NerdctlCommandSystemDeps, _ string) (string, error) {

var osAliasMap = map[string]string{}

var osArgHandlerMap = map[string]map[string]argHandler{
"container run": {
"--mount": handleBindMounts,
},
}
var osArgHandlerMap = map[string]map[string]argHandler{}

var osCommandHandlerMap = map[string]commandHandler{}

Expand All @@ -50,64 +45,8 @@ func resolveIP(host string, logger flog.Logger, _ command.Creator) (string, erro
return host, nil
}

// removes the consistency key-value entity from --mount.
func handleBindMounts(_ NerdctlCommandSystemDeps, _ *config.Finch, nerdctlCmdArgs []string, index int) error {
prefix := nerdctlCmdArgs[index]
var (
v string
found bool
before string
)
if strings.Contains(nerdctlCmdArgs[index], "=") {
before, v, found = strings.Cut(prefix, "=")
} else {
if (index + 1) < len(nerdctlCmdArgs) {
v = nerdctlCmdArgs[index+1]
} else {
return fmt.Errorf("invalid positional parameter for %s", prefix)
}
}

// This is where the 'consistency=cached' strings should be removed....
// "consistency will be one of the keys in the following map"

// eg --mount type=bind,source="$(pwd)"/target,target=/app,readonly
// eg --mount type=bind,
// source=/Users/stchew/projs/arbtest_devcontainers_extensions,
// target=/workspaces/arbtest_devcontainers_extensions,
// consistency=cached
// https://docs.docker.com/storage/bind-mounts/#choose-the--v-or---mount-flag order does not matter, so convert to a map
entries := strings.Split(v, ",")
m := make(map[string]string)
ro := []string{}
for _, e := range entries {
parts := strings.Split(e, "=")
if len(parts) < 2 {
ro = append(ro, parts...)
} else {
m[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
}
// Check if type is bind mount, else return
if m["type"] != "bind" {
return nil
}

// Remove 'consistency' key-value pair
delete(m, "consistency")

// Convert to string representation
s := mapToString(m)
// append read-only key if present
if len(ro) > 0 {
s = s + "," + strings.Join(ro, ",")
}
if found {
nerdctlCmdArgs[index] = fmt.Sprintf("%s=%s", before, s)
} else {
nerdctlCmdArgs[index+1] = s
}

func handleBindMountPath(_ NerdctlCommandSystemDeps, _ map[string]string) error {
// Do nothing by default
return nil
}

Expand Down
86 changes: 86 additions & 0 deletions cmd/finch/nerdctl_darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"

"github.com/golang/mock/gomock"
Expand Down Expand Up @@ -1208,6 +1209,36 @@ func TestNerdctlCommand_run(t *testing.T) {
c.EXPECT().Run()
},
},
{
name: "bindmount with src and consistency",
cmdName: "run",
fc: &config.Finch{},
args: []string{"--mount", "type=bind,src=./src,consistency=cached", "alpine:latest"},
wantErr: nil,
mockSvc: func(
_ *testing.T,
lcc *mocks.NerdctlCmdCreator,
_ *mocks.CommandCreator,
ncsd *mocks.NerdctlCommandSystemDeps,
logger *mocks.Logger,
ctrl *gomock.Controller,
_ afero.Fs,
) {
getVMStatusC := mocks.NewCommand(ctrl)
lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC)
getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil)
logger.EXPECT().Debugf("Status of virtual machine: %s", "Running")
ncsd.EXPECT().LookupEnv("AWS_ACCESS_KEY_ID").Return("", false)
ncsd.EXPECT().LookupEnv("AWS_SECRET_ACCESS_KEY").Return("", false)
ncsd.EXPECT().LookupEnv("AWS_SESSION_TOKEN").Return("", false)
ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false)
ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false)
c := mocks.NewCommand(ctrl)
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "container", "run",
"--mount", ContainsMultipleStrs([]string{"bind", "type", "!consistency"}), "alpine:latest").Return(c)
c.EXPECT().Run()
},
},
}

for _, tc := range testCases {
Expand Down Expand Up @@ -1664,3 +1695,58 @@ func TestNerdctlCommand_run_miscCommand(t *testing.T) {
})
}
}

type ContainsSubstring struct {
substr string
}

func (m *ContainsSubstring) Matches(x interface{}) bool {
s, ok := x.(string)
if !ok {
return false
}
return strings.Contains(s, m.substr)
}

func (m *ContainsSubstring) String() string {
return fmt.Sprintf("contains substring %q", m.substr)
}

func ContainsStr(substr string) gomock.Matcher {
return &ContainsSubstring{substr: substr}
}

type ContainsMultipleSubstrings struct {
substrs []string
}

func (m *ContainsMultipleSubstrings) Matches(x interface{}) bool {
s, ok := x.(string)
if !ok {
return false
}
// Check if each substrings is present in the input string
// except strings that start with "!"
passTest := true
for _, substr := range m.substrs {
if substr[0] == '!' {
if strings.Contains(s, substr[1:]) {
passTest = false
}
continue
}

if !strings.Contains(s, substr) {
passTest = false
}
}
return passTest
}

func (m *ContainsMultipleSubstrings) String() string {
return fmt.Sprintf("contains substrings %q", m.substrs)
}

func ContainsMultipleStrs(substrs []string) gomock.Matcher {
return &ContainsMultipleSubstrings{substrs: substrs}
}
27 changes: 19 additions & 8 deletions cmd/finch/nerdctl_native.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
)

func (nc *nerdctlCommand) run(cmdName string, args []string) error {

var (
hasCmdHandler, hasArgHandler bool
cmdHandler commandHandler
Expand Down Expand Up @@ -70,14 +69,12 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error {
}
}

// TODO: Extra manipulation if overwriting cmdName with alias
//splitName := strings.Split(cmdName, " ")
//cmdArgs := append([]string{splitName[0]}, splitName[1:]...)
//cmdArgs = append(cmdArgs, args...)

cmdArgs := append([]string{cmdName}, args...)
// Extra manipulation for cases that overwrite cmdName with alias
splitName := strings.Split(cmdName, " ")
cmdArgs := append([]string{splitName[0]}, splitName[1:]...)
cmdArgs = append(cmdArgs, args...)

if nc.shouldReplaceForHelp(cmdName, args) {
if nc.shouldReplaceForHelp(splitName[0], args) {
return nc.ncc.RunWithReplacingStdout(
[]command.Replacement{{Source: "nerdctl", Target: "finch"}},
cmdArgs...,
Expand All @@ -92,3 +89,17 @@ var osAliasMap = map[string]string{}
var osArgHandlerMap = map[string]map[string]argHandler{}

var osCommandHandlerMap = map[string]commandHandler{}

func mapToString(m map[string]string) string {
chews93319 marked this conversation as resolved.
Show resolved Hide resolved
var parts []string
for k, v := range m {
part := fmt.Sprintf("%s=%s", k, v)
parts = append(parts, part)
}
return strings.Join(parts, ",")
}

func handleBindMountPath(_ NerdctlCommandSystemDeps, _ map[string]string) error {
// Do nothing by default
return nil
}
Loading
Loading