From 2637311ccae183c2ddf6eed710c3d5ea4a6be4bd Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Wed, 3 Sep 2025 14:29:26 +0900 Subject: [PATCH] editflags: add `--mount-only` flag Similar to `--mount`, but overrides the existing mounts. e.g., ``` limactl start --mount-only "$(pwd)" ```` This will only make the current directory visible to the VM. The host home directory is not visible (unless pwd is the home). Signed-off-by: Akihiro Suda --- cmd/limactl/editflags/editflags.go | 64 ++++++++++++++++++++----- cmd/limactl/editflags/editflags_test.go | 44 +++++++++++++++++ 2 files changed, 95 insertions(+), 13 deletions(-) diff --git a/cmd/limactl/editflags/editflags.go b/cmd/limactl/editflags/editflags.go index 6a2a9994b9b..d6b875471f1 100644 --- a/cmd/limactl/editflags/editflags.go +++ b/cmd/limactl/editflags/editflags.go @@ -44,6 +44,8 @@ func RegisterEdit(cmd *cobra.Command, commentPrefix string) { }) flags.StringSlice("mount", nil, commentPrefix+"Directories to mount, suffix ':w' for writable (Do not specify directories that overlap with the existing mounts)") // colima-compatible + flags.StringSlice("mount-only", nil, commentPrefix+"Similar to --mount, but overrides the existing mounts") + flags.Bool("mount-none", false, commentPrefix+"Remove all mounts") flags.String("mount-type", "", commentPrefix+"Mount type (reverse-sshfs, 9p, virtiofs)") // Similar to colima's --mount-type=(sshfs|9p|virtiofs), but "reverse-sshfs" is Lima is called "sshfs" in colima @@ -167,6 +169,20 @@ func BuildPortForwardExpression(portForwards []string) (string, error) { return expr, nil } +func buildMountListExpression(ss []string) (string, error) { + expr := `[` + for i, s := range ss { + writable := strings.HasSuffix(s, ":w") + loc := strings.TrimSuffix(s, ":w") + expr += fmt.Sprintf(`{"location": %q, "writable": %v}`, loc, writable) + if i < len(ss)-1 { + expr += "," + } + } + expr += `]` + return expr, nil +} + // YQExpressions returns YQ expressions. func YQExpressions(flags *flag.FlagSet, newInstance bool) ([]string, error) { type def struct { @@ -207,30 +223,52 @@ func YQExpressions(flags *flag.FlagSet, newInstance bool) ([]string, error) { if err != nil { return "", err } - expr := `.mounts += [` - for i, s := range ss { - writable := strings.HasSuffix(s, ":w") - loc := strings.TrimSuffix(s, ":w") - expr += fmt.Sprintf(`{"location": %q, "writable": %v}`, loc, writable) - if i < len(ss)-1 { - expr += "," - } + mountListExpr, err := buildMountListExpression(ss) + if err != nil { + return "", err + } + expr := `.mounts += ` + mountListExpr + ` | .mounts |= unique_by(.location)` + mountOnly, err := flags.GetStringSlice("mount-only") + if err != nil { + return "", err + } + if len(mountOnly) > 0 { + return "", errors.New("flag `--mount` conflicts with `--mount-only`") } - expr += `] | .mounts |= unique_by(.location)` return expr, nil }, false, false, }, { - "mount-none", + "mount-only", func(_ *flag.Flag) (string, error) { - ss, err := flags.GetStringSlice("mount") + ss, err := flags.GetStringSlice("mount-only") + if err != nil { + return "", err + } + mountListExpr, err := buildMountListExpression(ss) if err != nil { return "", err } - if len(ss) > 0 { - return "", errors.New("flag `--mount-none` conflicts with `--mount`") + expr := `.mounts = ` + mountListExpr + return expr, nil + }, + false, + false, + }, + { + "mount-none", + func(_ *flag.Flag) (string, error) { + incompatibleFlagNames := []string{"mount", "mount-only"} + for _, name := range incompatibleFlagNames { + ss, err := flags.GetStringSlice(name) + if err != nil { + return "", err + } + if len(ss) > 0 { + return "", errors.New("flag `--mount-none` conflicts with `" + name + "`") + } } return ".mounts = null", nil }, diff --git a/cmd/limactl/editflags/editflags_test.go b/cmd/limactl/editflags/editflags_test.go index 1634da8211a..7c6c2908f3f 100644 --- a/cmd/limactl/editflags/editflags_test.go +++ b/cmd/limactl/editflags/editflags_test.go @@ -6,6 +6,7 @@ package editflags import ( "testing" + "github.com/spf13/cobra" "gotest.tools/v3/assert" ) @@ -157,3 +158,46 @@ func TestParsePortForward(t *testing.T) { }) } } + +func TestYQExpressions(t *testing.T) { + tests := []struct { + name string + args []string + newInstance bool + expected []string + expectError string + }{ + { + name: "mount", + args: []string{"--mount", "/foo", "--mount", "/bar:w"}, + newInstance: false, + expected: []string{`.mounts += [{"location": "/foo", "writable": false},{"location": "/bar", "writable": true}] | .mounts |= unique_by(.location)`}, + }, + { + name: "mount-only", + args: []string{"--mount-only", "/foo", "--mount-only", "/bar:w"}, + newInstance: false, + expected: []string{`.mounts = [{"location": "/foo", "writable": false},{"location": "/bar", "writable": true}]`}, + }, + { + name: "mixture of mount and mount-only", + args: []string{"--mount", "/foo", "--mount-only", "/bar:w"}, + newInstance: false, + expectError: "flag `--mount` conflicts with `--mount-only`", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := &cobra.Command{} + RegisterEdit(cmd, "") + assert.NilError(t, cmd.ParseFlags(tt.args)) + expr, err := YQExpressions(cmd.Flags(), tt.newInstance) + if tt.expectError != "" { + assert.ErrorContains(t, err, tt.expectError) + } else { + assert.NilError(t, err) + assert.DeepEqual(t, tt.expected, expr) + } + }) + } +}