Skip to content

Commit b1e2616

Browse files
committed
limactl: add --cpus, --memory, --mount-type, --vm-type, ...
Signed-off-by: Akihiro Suda <[email protected]>
1 parent 79a126a commit b1e2616

File tree

5 files changed

+216
-12
lines changed

5 files changed

+216
-12
lines changed

cmd/limactl/edit.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import (
66
"fmt"
77
"os"
88
"path/filepath"
9+
"strings"
910

1011
"github.com/AlecAivazis/survey/v2"
12+
"github.com/lima-vm/lima/cmd/limactl/editflags"
1113
"github.com/lima-vm/lima/pkg/editutil"
1214
"github.com/lima-vm/lima/pkg/limayaml"
1315
networks "github.com/lima-vm/lima/pkg/networks/reconcile"
@@ -30,7 +32,7 @@ func newEditCommand() *cobra.Command {
3032
}
3133
// TODO: "survey" does not support using cygwin terminal on windows yet
3234
editCommand.Flags().Bool("tty", isatty.IsTerminal(os.Stdout.Fd()), "enable TUI interactions such as opening an editor, defaults to true when stdout is a terminal")
33-
editCommand.Flags().String("set", "", "modify the template inplace, using yq syntax")
35+
editflags.RegisterEdit(editCommand)
3436
return editCommand
3537
}
3638

@@ -57,17 +59,18 @@ func editAction(cmd *cobra.Command, args []string) error {
5759
if err != nil {
5860
return err
5961
}
60-
tty, err := cmd.Flags().GetBool("tty")
62+
flags := cmd.Flags()
63+
tty, err := flags.GetBool("tty")
6164
if err != nil {
6265
return err
6366
}
64-
yq, err := cmd.Flags().GetString("set")
67+
yqExprs, err := editflags.YQExpressions(flags)
6568
if err != nil {
6669
return err
6770
}
6871
var yBytes []byte
69-
if yq != "" {
70-
logrus.Warn("`--set` is experimental")
72+
if len(yqExprs) > 0 {
73+
yq := strings.Join(yqExprs, " | ")
7174
yBytes, err = yqutil.EvaluateExpression(yq, yContent)
7275
if err != nil {
7376
return err

cmd/limactl/editflags/editflags.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package editflags
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/sirupsen/logrus"
8+
"github.com/spf13/cobra"
9+
flag "github.com/spf13/pflag"
10+
)
11+
12+
// RegisterEdit registers flags related to in-place YAML modification.
13+
func RegisterEdit(cmd *cobra.Command) {
14+
flags := cmd.Flags()
15+
16+
flags.Int("cpus", 0, "number of CPUs") // Similar to colima's --cpu, but the flag name is slightly different (cpu vs cpus)
17+
_ = cmd.RegisterFlagCompletionFunc("cpus", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
18+
return []string{"1", "2", "4", "8"}, cobra.ShellCompDirectiveNoFileComp
19+
})
20+
21+
flags.IPSlice("dns", nil, "specify custom DNS (disable host resolver)") // colima-compatible
22+
23+
flags.Float32("memory", 0, "memory in GiB") // colima-compatible
24+
_ = cmd.RegisterFlagCompletionFunc("memory", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
25+
return []string{"1", "2", "4", "8", "16", "32"}, cobra.ShellCompDirectiveNoFileComp
26+
})
27+
28+
flags.StringSlice("mount", nil, "directories to mount, suffix ':w' for writable (Do not specify directories that overlap with the existing mounts)") // colima-compatible
29+
30+
flags.String("mount-type", "", "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
31+
_ = cmd.RegisterFlagCompletionFunc("mount-type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
32+
return []string{"reverse-sshfs", "9p", "virtiofs"}, cobra.ShellCompDirectiveNoFileComp
33+
})
34+
35+
flags.Bool("mount-writable", false, "make all mounts writable")
36+
37+
flags.StringSlice("network", nil, "additional networks, e.g., \"lima:shared\" to assign vmnet IP")
38+
_ = cmd.RegisterFlagCompletionFunc("network", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
39+
// TODO: retrieve the network list from networks.yaml
40+
return []string{"lima:shared", "lima:bridged", "lima:host", "lima:user-v2"}, cobra.ShellCompDirectiveNoFileComp
41+
})
42+
43+
flags.String("set", "", "modify the template inplace, using yq syntax")
44+
}
45+
46+
// RegisterStart registers flags related to in-place YAML modification.
47+
func RegisterStart(cmd *cobra.Command) {
48+
RegisterEdit(cmd)
49+
flags := cmd.Flags()
50+
51+
flags.String("arch", "", "machine architecture (x86_64, aarch64, riscv64)") // colima-compatible
52+
_ = cmd.RegisterFlagCompletionFunc("arch", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
53+
return []string{"x86_64", "aarch64", "riscv64"}, cobra.ShellCompDirectiveNoFileComp
54+
})
55+
56+
flags.String("containerd", "", "containerd mode (user, system, user+system, none)")
57+
_ = cmd.RegisterFlagCompletionFunc("vm-type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
58+
return []string{"user", "system", "user+system", "none"}, cobra.ShellCompDirectiveNoFileComp
59+
})
60+
61+
flags.Float32("disk", 0, "disk size in GiB") // colima-compatible
62+
_ = cmd.RegisterFlagCompletionFunc("memory", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
63+
return []string{"10", "30", "50", "100", "200"}, cobra.ShellCompDirectiveNoFileComp
64+
})
65+
66+
flags.String("vm-type", "", "virtual machine type (qemu, vz)") // colima-compatible
67+
_ = cmd.RegisterFlagCompletionFunc("vm-type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
68+
return []string{"qemu", "vz"}, cobra.ShellCompDirectiveNoFileComp
69+
})
70+
}
71+
72+
func defaultExprFunc(expr string) func(v *flag.Flag) (string, error) {
73+
return func(v *flag.Flag) (string, error) {
74+
return fmt.Sprintf(expr, v.Value), nil
75+
}
76+
}
77+
78+
// YQExpressions returns YQ expressions.
79+
func YQExpressions(flags *flag.FlagSet) ([]string, error) {
80+
type def struct {
81+
flagName string
82+
exprFunc func(*flag.Flag) (string, error)
83+
experimental bool
84+
}
85+
d := defaultExprFunc
86+
defs := []def{
87+
{"cpus", d(".cpus = %s"), false},
88+
{"dns",
89+
func(_ *flag.Flag) (string, error) {
90+
ipSlice, err := flags.GetIPSlice("dns")
91+
if err != nil {
92+
return "", err
93+
}
94+
expr := `.dns += [`
95+
for i, ip := range ipSlice {
96+
expr += fmt.Sprintf("%q", ip)
97+
if i < len(ipSlice)-1 {
98+
expr += ","
99+
}
100+
}
101+
expr += `] | .dns |= unique | .hostResolver.enabled=false`
102+
logrus.Warnf("Disabling HostResolver, as custom DNS addresses (%v) are specified", ipSlice)
103+
return expr, nil
104+
},
105+
false},
106+
{"memory", d(".memory = \"%sGiB\""), false},
107+
{"mount",
108+
func(_ *flag.Flag) (string, error) {
109+
ss, err := flags.GetStringSlice("mount")
110+
if err != nil {
111+
return "", err
112+
}
113+
expr := `.mounts += [`
114+
for i, s := range ss {
115+
writable := strings.HasSuffix(s, ":w")
116+
loc := strings.TrimSuffix(s, ":w")
117+
expr += fmt.Sprintf(`{"location": %q, "writable": %v}`, loc, writable)
118+
if i < len(ss)-1 {
119+
expr += ","
120+
}
121+
}
122+
expr += `] | .mounts |= unique_by(.location)`
123+
return expr, nil
124+
},
125+
false},
126+
{"mount-type", d(".mountType = %q"), false},
127+
{"mount-writable", d(".mounts[].writable = %s"), false},
128+
{"network",
129+
func(_ *flag.Flag) (string, error) {
130+
ss, err := flags.GetStringSlice("network")
131+
if err != nil {
132+
return "", err
133+
}
134+
expr := `.networks += [`
135+
for i, s := range ss {
136+
// CLI syntax is still experimental (YAML syntax is out of experimental)
137+
if !strings.HasPrefix(s, "lima:") {
138+
return "", fmt.Errorf("network name must have \"lima:\" prefix, got %q", s)
139+
}
140+
network := strings.TrimPrefix(s, "lima:")
141+
expr += fmt.Sprintf(`{"lima": %q}`, network)
142+
if i < len(ss)-1 {
143+
expr += ","
144+
}
145+
}
146+
expr += `] | .networks |= unique_by(.lima)`
147+
return expr, nil
148+
},
149+
true},
150+
{"set", d("%s"), true},
151+
{"arch", d(".arch = %q"), false},
152+
{"containerd",
153+
func(_ *flag.Flag) (string, error) {
154+
s, err := flags.GetString("containerd")
155+
if err != nil {
156+
return "", err
157+
}
158+
switch s {
159+
case "user":
160+
return `.containerd.user = true | .containerd.system = false`, nil
161+
case "system":
162+
return `.containerd.user = false | .containerd.system = true`, nil
163+
case "user+system", "system+user":
164+
return `.containerd.user = true | .containerd.system = true`, nil
165+
case "none":
166+
return `.containerd.user = false | .containerd.system = false`, nil
167+
default:
168+
return "", fmt.Errorf(`expected one of ["user", "system", "user+system", "none"], got %q`, s)
169+
}
170+
},
171+
false},
172+
173+
{"disk", d(".disk= \"%sGiB\""), false},
174+
{"vm-type", d(".vmType = %q"), false},
175+
}
176+
var exprs []string
177+
for _, def := range defs {
178+
v := flags.Lookup(def.flagName)
179+
if v != nil && v.Changed {
180+
if def.experimental {
181+
logrus.Warnf("`--%s` is experimental", def.flagName)
182+
}
183+
expr, err := def.exprFunc(v)
184+
if err != nil {
185+
return exprs, fmt.Errorf("error while processing flag %q: %w", def.flagName, err)
186+
}
187+
exprs = append(exprs, expr)
188+
}
189+
}
190+
return exprs, nil
191+
}

cmd/limactl/start.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/AlecAivazis/survey/v2"
1313
"github.com/containerd/containerd/identifiers"
14+
"github.com/lima-vm/lima/cmd/limactl/editflags"
1415
"github.com/lima-vm/lima/cmd/limactl/guessarg"
1516
"github.com/lima-vm/lima/pkg/editutil"
1617
"github.com/lima-vm/lima/pkg/ioutilx"
@@ -38,6 +39,9 @@ To create an instance "default" from a template "docker":
3839
$ limactl start --name=default template://docker
3940
4041
To create an instance "default" with modified parameters:
42+
$ limactl start --cpus=2 --memory=2
43+
44+
To create an instance "default" with yq expressions:
4145
$ limactl start --set='.cpus = 2 | .memory = "2GiB"'
4246
4347
To see the template list:
@@ -59,8 +63,8 @@ $ cat template.yaml | limactl start --name=local -
5963
}
6064
// TODO: "survey" does not support using cygwin terminal on windows yet
6165
startCommand.Flags().Bool("tty", isatty.IsTerminal(os.Stdout.Fd()), "enable TUI interactions such as opening an editor, defaults to true when stdout is a terminal")
66+
editflags.RegisterStart(startCommand)
6267
startCommand.Flags().String("name", "", "override the instance name")
63-
startCommand.Flags().String("set", "", "modify the template inplace, using yq syntax")
6468
startCommand.Flags().Bool("list-templates", false, "list available templates and exit")
6569
startCommand.Flags().Duration("timeout", start.DefaultWatchHostAgentEventsTimeout, "duration to wait for the instance to be running before timing out")
6670
return startCommand
@@ -77,20 +81,26 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string) (*store.Instance, e
7781
err error
7882
)
7983

84+
flags := cmd.Flags()
85+
8086
// Create an instance, with menu TUI when TTY is available
81-
tty, err := cmd.Flags().GetBool("tty")
87+
tty, err := flags.GetBool("tty")
8288
if err != nil {
8389
return nil, err
8490
}
8591

86-
st.instName, err = cmd.Flags().GetString("name")
92+
st.instName, err = flags.GetString("name")
8793
if err != nil {
8894
return nil, err
8995
}
90-
st.yq, err = cmd.Flags().GetString("set")
96+
97+
yqExprs, err := editflags.YQExpressions(flags)
9198
if err != nil {
9299
return nil, err
93100
}
101+
if len(yqExprs) > 0 {
102+
st.yq = strings.Join(yqExprs, " | ")
103+
}
94104
const yBytesLimit = 4 * 1024 * 1024 // 4MiB
95105

96106
if ok, u := guessarg.SeemsTemplateURL(arg); ok {
@@ -280,7 +290,6 @@ func modifyInPlace(st *creatorState) error {
280290
if st.yq == "" {
281291
return nil
282292
}
283-
logrus.Warn("`--set` is experimental")
284293
out, err := yqutil.EvaluateExpression(st.yq, st.yBytes)
285294
if err != nil {
286295
return err

docs/experimental.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ The following features are experimental and subject to change:
1111

1212
The following flags are experimental and subject to change:
1313

14-
- `start --set`, yq expression
15-
- `edit --set`, yq expression
14+
- `limactl (start|edit) --set=<YQ EXPRESSION>`
15+
- `limactl (start|edit) --network=<NETWORK>`

pkg/yqutil/yqutil.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
// EvaluateExpression evaluates the yq expression, and returns the modified yaml.
1414
func EvaluateExpression(expression string, content []byte) ([]byte, error) {
15+
logrus.Debugf("Evaluating yq expression: %q", expression)
1516
tmpYAMLFile, err := os.CreateTemp("", "lima-yq-*.yaml")
1617
if err != nil {
1718
return nil, err

0 commit comments

Comments
 (0)