Skip to content

Commit

Permalink
feat: Add BindMount handler for Linux (#1119)
Browse files Browse the repository at this point in the history
Add BindMount handler for Linux

Add handleBindMount to remove consistency entity\nRevise comments for linting and clarity

Signed-off-by: Sam Chew <[email protected]>
  • Loading branch information
chews93319 authored Oct 3, 2024
1 parent c004516 commit a49df29
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 150 deletions.
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 {
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

0 comments on commit a49df29

Please sign in to comment.