Skip to content

Commit 70a3f13

Browse files
authored
Merge pull request #3937 from jandubois/custom-url-schemes
Add a plugin mechanism to provide custom locator URL schemes
2 parents a6c7773 + 677fa1f commit 70a3f13

File tree

19 files changed

+281
-191
lines changed

19 files changed

+281
-191
lines changed

cmd/apptainer.lima

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -eu
44
: "${APPTAINER_BINDPATH:=}"
55

66
if [ "$(limactl ls -q "$LIMA_INSTANCE" 2>/dev/null)" != "$LIMA_INSTANCE" ]; then
7-
echo "instance \"$LIMA_INSTANCE\" does not exist, run \`limactl create --name=$LIMA_INSTANCE template://apptainer\` to create a new instance" >&2
7+
echo "instance \"$LIMA_INSTANCE\" does not exist, run \`limactl create --name=$LIMA_INSTANCE template:apptainer\` to create a new instance" >&2
88
exit 1
99
elif [ "$(limactl ls -f '{{ .Status }}' "$LIMA_INSTANCE" 2>/dev/null)" != "Running" ]; then
1010
echo "instance \"$LIMA_INSTANCE\" is not running, run \`limactl start $LIMA_INSTANCE\` to start the existing instance" >&2

cmd/docker.lima

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ set -eu
88
: "${DOCKER:=docker}"
99

1010
if [ "$(limactl ls -q "$LIMA_INSTANCE" 2>/dev/null)" != "$LIMA_INSTANCE" ]; then
11-
echo "instance \"$LIMA_INSTANCE\" does not exist, run \`limactl create --name=$LIMA_INSTANCE template://docker\` to create a new instance" >&2
11+
echo "instance \"$LIMA_INSTANCE\" does not exist, run \`limactl create --name=$LIMA_INSTANCE template:docker\` to create a new instance" >&2
1212
exit 1
1313
elif [ "$(limactl ls -f '{{ .Status }}' "$LIMA_INSTANCE" 2>/dev/null)" != "Running" ]; then
1414
echo "instance \"$LIMA_INSTANCE\" is not running, run \`limactl start $LIMA_INSTANCE\` to start the existing instance" >&2

cmd/kubectl.lima

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ if [ -z "$LIMA_INSTANCE" ]; then
1414
LIMA_INSTANCE=k8s
1515
else
1616
echo "No k3s or k8s existing instances found. Either create one with" >&2
17-
echo "limactl create --name=k3s template://k3s" >&2
18-
echo "limactl create --name=k8s template://k8s" >&2
17+
echo "limactl create --name=k3s template:k3s" >&2
18+
echo "limactl create --name=k8s template:k8s" >&2
1919
echo "or set LIMA_INSTANCE to the name of your Kubernetes instance" >&2
2020
exit 1
2121
fi

cmd/limactl/completion.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func bashCompleteTemplateNames(_ *cobra.Command, toComplete string) ([]string, c
2828
var comp []string
2929
if templates, err := templatestore.Templates(); err == nil {
3030
for _, f := range templates {
31-
name := "template://" + f.Name
31+
name := "template:" + f.Name
3232
if !strings.HasPrefix(name, toComplete) {
3333
continue
3434
}

cmd/limactl/main.go

Lines changed: 78 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
"github.com/lima-vm/lima/v2/pkg/fsutil"
2222
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
2323
"github.com/lima-vm/lima/v2/pkg/osutil"
24-
"github.com/lima-vm/lima/v2/pkg/plugin"
24+
"github.com/lima-vm/lima/v2/pkg/plugins"
2525
"github.com/lima-vm/lima/v2/pkg/version"
2626
)
2727

@@ -47,15 +47,49 @@ func main() {
4747
}
4848
}
4949
}
50-
rootCmd := newApp()
51-
err := executeWithPluginSupport(rootCmd, os.Args[1:])
50+
err := newApp().Execute()
5251
server.StopAllExternalDrivers()
5352
osutil.HandleExitError(err)
5453
if err != nil {
5554
logrus.Fatal(err)
5655
}
5756
}
5857

58+
func processGlobalFlags(rootCmd *cobra.Command) error {
59+
// --log-level will override --debug, but will not reset debugutil.Debug
60+
if debug, _ := rootCmd.Flags().GetBool("debug"); debug {
61+
logrus.SetLevel(logrus.DebugLevel)
62+
debugutil.Debug = true
63+
}
64+
65+
l, _ := rootCmd.Flags().GetString("log-level")
66+
if l != "" {
67+
lvl, err := logrus.ParseLevel(l)
68+
if err != nil {
69+
return err
70+
}
71+
logrus.SetLevel(lvl)
72+
}
73+
74+
logFormat, _ := rootCmd.Flags().GetString("log-format")
75+
switch logFormat {
76+
case "json":
77+
formatter := new(logrus.JSONFormatter)
78+
logrus.StandardLogger().SetFormatter(formatter)
79+
case "text":
80+
// logrus use text format by default.
81+
if runtime.GOOS == "windows" && isatty.IsCygwinTerminal(os.Stderr.Fd()) {
82+
formatter := new(logrus.TextFormatter)
83+
// the default setting does not recognize cygwin on windows
84+
formatter.ForceColors = true
85+
logrus.StandardLogger().SetFormatter(formatter)
86+
}
87+
default:
88+
return fmt.Errorf("unsupported log-format: %q", logFormat)
89+
}
90+
return nil
91+
}
92+
5993
func newApp() *cobra.Command {
6094
templatesDir := "$PREFIX/share/lima/templates"
6195
if exe, err := os.Executable(); err == nil {
@@ -92,30 +126,8 @@ func newApp() *cobra.Command {
92126
rootCmd.PersistentFlags().Bool("tty", isatty.IsTerminal(os.Stdout.Fd()), "Enable TUI interactions such as opening an editor. Defaults to true when stdout is a terminal. Set to false for automation.")
93127
rootCmd.PersistentFlags().BoolP("yes", "y", false, "Alias of --tty=false")
94128
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
95-
l, _ := cmd.Flags().GetString("log-level")
96-
if l != "" {
97-
lvl, err := logrus.ParseLevel(l)
98-
if err != nil {
99-
return err
100-
}
101-
logrus.SetLevel(lvl)
102-
}
103-
104-
logFormat, _ := cmd.Flags().GetString("log-format")
105-
switch logFormat {
106-
case "json":
107-
formatter := new(logrus.JSONFormatter)
108-
logrus.StandardLogger().SetFormatter(formatter)
109-
case "text":
110-
// logrus use text format by default.
111-
if runtime.GOOS == "windows" && isatty.IsCygwinTerminal(os.Stderr.Fd()) {
112-
formatter := new(logrus.TextFormatter)
113-
// the default setting does not recognize cygwin on windows
114-
formatter.ForceColors = true
115-
logrus.StandardLogger().SetFormatter(formatter)
116-
}
117-
default:
118-
return fmt.Errorf("unsupported log-format: %q", logFormat)
129+
if err := processGlobalFlags(rootCmd); err != nil {
130+
return err
119131
}
120132

121133
if osutil.IsBeingRosettaTranslated() && cmd.Parent().Name() != "completion" && cmd.Name() != "generate-doc" && cmd.Name() != "validate" {
@@ -191,47 +203,61 @@ func newApp() *cobra.Command {
191203
newNetworkCommand(),
192204
newCloneCommand(),
193205
)
206+
addPluginCommands(rootCmd)
194207

195208
return rootCmd
196209
}
197210

198-
func executeWithPluginSupport(rootCmd *cobra.Command, args []string) error {
199-
rootCmd.SetArgs(args)
200-
201-
if err := rootCmd.ParseFlags(args); err == nil {
202-
if debug, _ := rootCmd.Flags().GetBool("debug"); debug {
203-
logrus.SetLevel(logrus.DebugLevel)
204-
debugutil.Debug = true
205-
}
211+
func addPluginCommands(rootCmd *cobra.Command) {
212+
// The global options are only processed when rootCmd.Execute() is called.
213+
// Let's take a sneak peek here to help debug the plugin discovery code.
214+
if len(os.Args) > 1 && os.Args[1] == "--debug" {
215+
logrus.SetLevel(logrus.DebugLevel)
206216
}
207217

208-
addPluginCommands(rootCmd)
209-
210-
return rootCmd.Execute()
211-
}
212-
213-
func addPluginCommands(rootCmd *cobra.Command) {
214-
plugins, err := plugin.DiscoverPlugins()
218+
allPlugins, err := plugins.Discover()
215219
if err != nil {
216220
logrus.Warnf("Failed to discover plugins: %v", err)
217221
return
218222
}
219223

220-
for _, p := range plugins {
221-
pluginName := p.Name
224+
for _, plugin := range allPlugins {
222225
pluginCmd := &cobra.Command{
223-
Use: pluginName,
224-
Short: p.Description,
226+
Use: plugin.Name,
227+
Short: plugin.Description,
225228
GroupID: "plugin",
226229
DisableFlagParsing: true,
227-
Run: func(cmd *cobra.Command, args []string) {
228-
plugin.RunExternalPlugin(cmd.Context(), pluginName, args)
230+
SilenceErrors: true,
231+
SilenceUsage: true,
232+
PreRunE: func(*cobra.Command, []string) error {
233+
for i, arg := range os.Args {
234+
if arg == plugin.Name {
235+
// parse global options but ignore plugin options
236+
err := rootCmd.ParseFlags(os.Args[1:i])
237+
if err == nil {
238+
err = processGlobalFlags(rootCmd)
239+
}
240+
return err
241+
}
242+
}
243+
// unreachable
244+
return nil
245+
},
246+
Run: func(cmd *cobra.Command, _ []string) {
247+
for i, arg := range os.Args {
248+
if arg == plugin.Name {
249+
// ignore global options
250+
plugin.Run(cmd.Context(), os.Args[i+1:])
251+
// plugin.Run() never returns because it calls os.Exit()
252+
}
253+
}
254+
// unreachable
229255
},
230256
}
231-
232-
pluginCmd.SilenceUsage = true
233-
pluginCmd.SilenceErrors = true
234-
257+
// Don't show the url scheme helper in the help output.
258+
if strings.HasPrefix(plugin.Name, "url-") {
259+
pluginCmd.Hidden = true
260+
}
235261
rootCmd.AddCommand(pluginCmd)
236262
}
237263
}

cmd/limactl/start.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func newCreateCommand() *cobra.Command {
4949
$ limactl create
5050
5151
To create an instance "default" from a template "docker":
52-
$ limactl create --name=default template://docker
52+
$ limactl create --name=default template:docker
5353
5454
To create an instance "default" with modified parameters:
5555
$ limactl create --cpus=2 --memory=2
@@ -87,7 +87,7 @@ func newStartCommand() *cobra.Command {
8787
$ limactl start
8888
8989
To create an instance "default" from a template "docker", and start it:
90-
$ limactl start --name=default template://docker
90+
$ limactl start --name=default template:docker
9191
`,
9292
Short: "Start an instance of Lima",
9393
Args: WrapArgsError(cobra.MaximumNArgs(1)),
@@ -234,19 +234,19 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (*
234234
if isTemplateURL, templateName := limatmpl.SeemsTemplateURL(arg); isTemplateURL {
235235
switch templateName {
236236
case "experimental/vz":
237-
logrus.Warn("template://experimental/vz was merged into the default template in Lima v1.0. See also <https://lima-vm.io/docs/config/vmtype/>.")
237+
logrus.Warn("template:experimental/vz was merged into the default template in Lima v1.0. See also <https://lima-vm.io/docs/config/vmtype/>.")
238238
case "experimental/riscv64":
239-
logrus.Warn("template://experimental/riscv64 was merged into the default template in Lima v1.0. Use `limactl create --arch=riscv64 template://default` instead.")
239+
logrus.Warn("template:experimental/riscv64 was merged into the default template in Lima v1.0. Use `limactl create --arch=riscv64 template:default` instead.")
240240
case "experimental/armv7l":
241-
logrus.Warn("template://experimental/armv7l was merged into the default template in Lima v1.0. Use `limactl create --arch=armv7l template://default` instead.")
241+
logrus.Warn("template:experimental/armv7l was merged into the default template in Lima v1.0. Use `limactl create --arch=armv7l template:default` instead.")
242242
case "vmnet":
243-
logrus.Warn("template://vmnet was removed in Lima v1.0. Use `limactl create --network=lima:shared template://default` instead. See also <https://lima-vm.io/docs/config/network/>.")
243+
logrus.Warn("template:vmnet was removed in Lima v1.0. Use `limactl create --network=lima:shared template:default` instead. See also <https://lima-vm.io/docs/config/network/>.")
244244
case "experimental/net-user-v2":
245-
logrus.Warn("template://experimental/net-user-v2 was removed in Lima v1.0. Use `limactl create --network=lima:user-v2 template://default` instead. See also <https://lima-vm.io/docs/config/network/>.")
245+
logrus.Warn("template:experimental/net-user-v2 was removed in Lima v1.0. Use `limactl create --network=lima:user-v2 template:default` instead. See also <https://lima-vm.io/docs/config/network/>.")
246246
case "experimental/9p":
247-
logrus.Warn("template://experimental/9p was removed in Lima v1.0. Use `limactl create --vm-type=qemu --mount-type=9p template://default` instead. See also <https://lima-vm.io/docs/config/mount/>.")
247+
logrus.Warn("template:experimental/9p was removed in Lima v1.0. Use `limactl create --vm-type=qemu --mount-type=9p template:default` instead. See also <https://lima-vm.io/docs/config/mount/>.")
248248
case "experimental/virtiofs-linux":
249-
logrus.Warn("template://experimental/virtiofs-linux was removed in Lima v1.0. Use `limactl create --mount-type=virtiofs template://default` instead. See also <https://lima-vm.io/docs/config/mount/>.")
249+
logrus.Warn("template:experimental/virtiofs-linux was removed in Lima v1.0. Use `limactl create --mount-type=virtiofs template:default` instead. See also <https://lima-vm.io/docs/config/mount/>.")
250250
}
251251
}
252252
if arg == "-" {
@@ -298,8 +298,8 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (*
298298
return nil, err
299299
}
300300
if arg != "" && arg != DefaultInstanceName {
301-
logrus.Infof("Creating an instance %q from template://default (Not from template://%s)", tmpl.Name, tmpl.Name)
302-
logrus.Warnf("This form is deprecated. Use `limactl create --name=%s template://default` instead", tmpl.Name)
301+
logrus.Infof("Creating an instance %q from template:default (Not from template:%s)", tmpl.Name, tmpl.Name)
302+
logrus.Warnf("This form is deprecated. Use `limactl create --name=%s template:default` instead", tmpl.Name)
303303
}
304304
// Read the default template for creating a new instance
305305
tmpl.Bytes, err = templatestore.Read(templatestore.Default)

cmd/limactl/template.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func newTemplateCommand() *cobra.Command {
4040
newTemplateCopyCommand(),
4141
newTemplateValidateCommand(),
4242
newTemplateYQCommand(),
43+
newTemplateURLCommand(),
4344
)
4445
return templateCommand
4546
}
@@ -51,13 +52,13 @@ func newValidateCommand() *cobra.Command {
5152
return validateCommand
5253
}
5354

54-
var templateCopyExample = ` Template locators are local files, file://, https://, or template:// URLs
55+
var templateCopyExample = ` Template locators are local files, file://, https://, or template: URLs
5556
5657
# Copy default template to STDOUT
57-
limactl template copy template://default -
58+
limactl template copy template:default -
5859
5960
# Copy template from web location to local file and embed all external references
60-
# (this does not embed template:// references)
61+
# (this does not embed template: references)
6162
limactl template copy --embed https://example.com/lima.yaml mighty-machine.yaml
6263
`
6364

@@ -176,10 +177,10 @@ External references are embedded and default values are filled in
176177
before the YQ expression is evaluated.
177178
178179
Example:
179-
limactl template yq template://default '.images[].location'
180+
limactl template yq template:default '.images[].location'
180181
181182
The example command is equivalent to using an external yq command like this:
182-
limactl template copy --fill template://default - | yq '.images[].location'
183+
limactl template copy --fill template:default - | yq '.images[].location'
183184
`
184185

185186
func newTemplateYQCommand() *cobra.Command {
@@ -281,3 +282,22 @@ func templateValidateAction(cmd *cobra.Command, args []string) error {
281282

282283
return nil
283284
}
285+
286+
func newTemplateURLCommand() *cobra.Command {
287+
templateURLCommand := &cobra.Command{
288+
Use: "url CUSTOM_URL",
289+
Short: "Transform custom template URLs to regular file or https URLs",
290+
Args: WrapArgsError(cobra.ExactArgs(1)),
291+
RunE: templateURLAction,
292+
}
293+
return templateURLCommand
294+
}
295+
296+
func templateURLAction(cmd *cobra.Command, args []string) error {
297+
url, err := limatmpl.TransformCustomURL(cmd.Context(), args[0])
298+
if err != nil {
299+
return err
300+
}
301+
_, err = fmt.Fprintln(cmd.OutOrStdout(), url)
302+
return err
303+
}

cmd/podman.lima

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -eu
44
: "${PODMAN:=podman}"
55

66
if [ "$(limactl ls -q "$LIMA_INSTANCE" 2>/dev/null)" != "$LIMA_INSTANCE" ]; then
7-
echo "instance \"$LIMA_INSTANCE\" does not exist, run \`limactl create --name=$LIMA_INSTANCE template://podman\` to create a new instance" >&2
7+
echo "instance \"$LIMA_INSTANCE\" does not exist, run \`limactl create --name=$LIMA_INSTANCE template:podman\` to create a new instance" >&2
88
exit 1
99
elif [ "$(limactl ls -f '{{ .Status }}' "$LIMA_INSTANCE" 2>/dev/null)" != "Running" ]; then
1010
echo "instance \"$LIMA_INSTANCE\" is not running, run \`limactl start $LIMA_INSTANCE\` to start the existing instance" >&2

hack/bats/tests/preserve-env.bats

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ local_setup_file() {
1717
limactl delete --force "$NAME" || :
1818
# Make sure that the host agent doesn't inherit file handles 3 or 4.
1919
# Otherwise bats will not finish until the host agent exits.
20-
limactl start --yes --name "$NAME" template://default 3>&- 4>&-
20+
limactl start --yes --name "$NAME" template:default 3>&- 4>&-
2121
}
2222

2323
local_teardown_file() {

hack/common.inc.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ _IPERF3=iperf3
3131
: "${IPERF3:=$_IPERF3}"
3232

3333
# Setup LIMA_TEMPLATES_PATH because the templates are not installed, but reference base templates
34-
# via template://_images/* and template://_default/*.
34+
# via template:_images/* and template:_default/*.
3535
templates_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../templates" && pwd)"
3636
: "${LIMA_TEMPLATES_PATH:-$templates_dir}"
3737
export LIMA_TEMPLATES_PATH

0 commit comments

Comments
 (0)