Skip to content

Commit cb0d6f1

Browse files
committed
limactl start: support limactl start template://TEMPLATE
e.g., `limactl start --name=default template://docker` Signed-off-by: Akihiro Suda <[email protected]>
1 parent 05e1e3f commit cb0d6f1

File tree

2 files changed

+92
-16
lines changed

2 files changed

+92
-16
lines changed

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,25 @@ Detailed usage:
143143

144144
- Run `limactl start <INSTANCE> [--tty=false]` to start the Linux instance.
145145
The default instance name is "default".
146-
Lima automatically opens an editor (`vi`) for reviewing and modifying the configuration.
147146
Wait until "READY" to be printed on the host terminal.
148147
`--tty=false` disables the interactive prompt to open an editor.
149148

149+
```
150+
Create an instance "default" (if not created yet) from the default Ubuntu template, and start it:
151+
$ limactl start
152+
153+
Create an instance "default" from a template "docker":
154+
$ limactl start --name=default template://docker
155+
156+
Create an instance "default" from a local file:
157+
$ limactl start --name=default /usr/local/share/lima/examples/fedora.yaml
158+
159+
Create an instance "default" from a remote URL (use carefully, with a trustable source):
160+
$ limactl start --name=default https://raw.githubusercontent.com/lima-vm/lima/master/examples/alpine.yaml
161+
```
162+
NOTE: `limactl start template://TEMPLATE` requires Lima v0.9.0 or later.
163+
Older releases require `limactl start /usr/local/share/doc/lima/examples/TEMPLATE.yaml` instead.
164+
150165
- Run `limactl shell <INSTANCE> <COMMAND>` to launch `<COMMAND>` on Linux.
151166
For the "default" instance, this command can be shortened as `lima <COMMAND>`.
152167
The `lima` command also accepts the instance name as the environment variable `$LIMA_INSTANCE`.

cmd/limactl/start.go

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,46 @@ import (
3030

3131
func newStartCommand() *cobra.Command {
3232
var startCommand = &cobra.Command{
33-
Use: "start NAME|FILE.yaml|URL",
33+
Use: "start NAME|FILE.yaml|URL",
34+
Example: `
35+
Create an instance "default" (if not created yet) from the default Ubuntu template, and start it:
36+
$ limactl start
37+
38+
Create an instance "default" from a template "docker":
39+
$ limactl start --name=default template://docker
40+
41+
Create an instance "default" from a local file:
42+
$ limactl start --name=default /usr/local/share/lima/examples/fedora.yaml
43+
44+
Create an instance "default" from a remote URL (use carefully, with a trustable source):
45+
$ limactl start --name=default https://raw.githubusercontent.com/lima-vm/lima/master/examples/alpine.yaml
46+
`,
3447
Short: "Start an instance of Lima",
3548
Args: cobra.MaximumNArgs(1),
3649
ValidArgsFunction: startBashComplete,
3750
RunE: startAction,
3851
}
3952
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")
53+
startCommand.Flags().String("name", "", "override the instance name")
4054
return startCommand
4155
}
4256

43-
func readDefaultTemplate() ([]byte, error) {
57+
func readTemplate(name string) ([]byte, error) {
4458
dir, err := usrlocalsharelima.Dir()
4559
if err != nil {
4660
return nil, err
4761
}
48-
defaultYAMLPath := filepath.Join(dir, "examples", "default.yaml")
62+
if strings.Contains(name, string(os.PathSeparator)) {
63+
return nil, fmt.Errorf("invalid template name %q", name)
64+
}
65+
defaultYAMLPath := filepath.Join(dir, "examples", name+".yaml")
4966
return os.ReadFile(defaultYAMLPath)
5067
}
5168

69+
func readDefaultTemplate() ([]byte, error) {
70+
return readTemplate("default")
71+
}
72+
5273
func loadOrCreateInstance(cmd *cobra.Command, args []string) (*store.Instance, error) {
5374
var arg string
5475
if len(args) == 0 {
@@ -61,13 +82,29 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string) (*store.Instance, e
6182
st = &creatorState{}
6283
err error
6384
)
85+
st.instName, err = cmd.Flags().GetString("name")
86+
if err != nil {
87+
return nil, err
88+
}
6489
const yBytesLimit = 4 * 1024 * 1024 // 4MiB
6590

66-
if argSeemsHTTPURL(arg) {
67-
st.instName, err = instNameFromURL(arg)
91+
if ok, u := argSeemsTemplateURL(arg); ok {
92+
templateName := u.Host
93+
logrus.Debugf("interpreting argument %q as a template name %q", arg, templateName)
94+
if st.instName == "" {
95+
st.instName = templateName
96+
}
97+
st.yBytes, err = readTemplate(templateName)
6898
if err != nil {
6999
return nil, err
70100
}
101+
} else if argSeemsHTTPURL(arg) {
102+
if st.instName == "" {
103+
st.instName, err = instNameFromURL(arg)
104+
if err != nil {
105+
return nil, err
106+
}
107+
}
71108
logrus.Debugf("interpreting argument %q as a http url for instance %q", arg, st.instName)
72109
resp, err := http.Get(arg)
73110
if err != nil {
@@ -79,9 +116,11 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string) (*store.Instance, e
79116
return nil, err
80117
}
81118
} else if argSeemsFileURL(arg) {
82-
st.instName, err = instNameFromURL(arg)
83-
if err != nil {
84-
return nil, err
119+
if st.instName == "" {
120+
st.instName, err = instNameFromURL(arg)
121+
if err != nil {
122+
return nil, err
123+
}
85124
}
86125
logrus.Debugf("interpreting argument %q as a file url for instance %q", arg, st.instName)
87126
r, err := os.Open(strings.TrimPrefix(arg, "file://"))
@@ -94,9 +133,11 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string) (*store.Instance, e
94133
return nil, err
95134
}
96135
} else if argSeemsYAMLPath(arg) {
97-
st.instName, err = instNameFromYAMLPath(arg)
98-
if err != nil {
99-
return nil, err
136+
if st.instName == "" {
137+
st.instName, err = instNameFromYAMLPath(arg)
138+
if err != nil {
139+
return nil, err
140+
}
100141
}
101142
logrus.Debugf("interpreting argument %q as a file path for instance %q", arg, st.instName)
102143
r, err := os.Open(arg)
@@ -109,10 +150,13 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string) (*store.Instance, e
109150
return nil, err
110151
}
111152
} else {
153+
logrus.Debugf("interpreting argument %q as an instance name", arg)
154+
if st.instName != "" && st.instName != arg {
155+
return nil, fmt.Errorf("instance name %q and CLI flag --name=%q cannot be specified together", arg, st.instName)
156+
}
112157
st.instName = arg
113-
logrus.Debugf("interpreting argument %q as an instance name %q", arg, st.instName)
114158
if err := identifiers.Validate(st.instName); err != nil {
115-
return nil, fmt.Errorf("argument must be either an instance name or a YAML file path, got %q: %w", st.instName, err)
159+
return nil, fmt.Errorf("argument must be either an instance name, a YAML file path, or a URL, got %q: %w", st.instName, err)
116160
}
117161
if inst, err := store.Inspect(st.instName); err == nil {
118162
logrus.Infof("Using the existing instance %q", st.instName)
@@ -121,6 +165,10 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string) (*store.Instance, e
121165
if !errors.Is(err, os.ErrNotExist) {
122166
return nil, err
123167
}
168+
if st.instName != DefaultInstanceName {
169+
logrus.Infof("Creating an instance %q from template://default (Not from template://%s)", st.instName, st.instName)
170+
logrus.Warnf("This form is deprecated. Use `limactl start --name=%s template://default` instead", st.instName)
171+
}
124172
// Read the default template for creating a new instance
125173
st.yBytes, err = readDefaultTemplate()
126174
if err != nil {
@@ -402,6 +450,14 @@ func startAction(cmd *cobra.Command, args []string) error {
402450
return start.Start(ctx, inst)
403451
}
404452

453+
func argSeemsTemplateURL(arg string) (bool, *url.URL) {
454+
u, err := url.Parse(arg)
455+
if err != nil {
456+
return false, u
457+
}
458+
return u.Scheme == "template", u
459+
}
460+
405461
func argSeemsHTTPURL(arg string) bool {
406462
u, err := url.Parse(arg)
407463
if err != nil {
@@ -448,8 +504,13 @@ func instNameFromYAMLPath(yamlPath string) (string, error) {
448504
}
449505

450506
func startBashComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
451-
instances, _ := bashCompleteInstanceNames(cmd)
452-
return instances, cobra.ShellCompDirectiveDefault
507+
comp, _ := bashCompleteInstanceNames(cmd)
508+
if templates, err := listTemplateYAMLs(); err == nil {
509+
for _, f := range templates {
510+
comp = append(comp, "template://"+f.Name)
511+
}
512+
}
513+
return comp, cobra.ShellCompDirectiveDefault
453514
}
454515

455516
func readAtMaximum(r io.Reader, n int64) ([]byte, error) {

0 commit comments

Comments
 (0)