Skip to content

Commit 97a9d84

Browse files
committed
Add a plugin mechanism to provide custom locator URL schemes
The template reader will attempt to call `limactl-url-$SCHEME` to rewrite a custom URL as either a local filename, or a URL with a supported URL like https. Signed-off-by: Jan Dubois <jan.dubois@suse.com>
1 parent 1055f75 commit 97a9d84

File tree

4 files changed

+103
-3
lines changed

4 files changed

+103
-3
lines changed

cmd/limactl/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,10 @@ func addPluginCommands(rootCmd *cobra.Command) {
254254
// unreachable
255255
},
256256
}
257-
257+
// Don't show the url scheme helper in the help output.
258+
if strings.HasPrefix(plugin.Name, "url-") {
259+
pluginCmd.Hidden = true
260+
}
258261
rootCmd.AddCommand(pluginCmd)
259262
}
260263
}

cmd/limactl/template.go

Lines changed: 20 additions & 0 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
}
@@ -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+
}

pkg/limatmpl/locator.go

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ package limatmpl
55

66
import (
77
"context"
8+
"errors"
89
"fmt"
910
"io"
1011
"net/http"
1112
"net/url"
1213
"os"
14+
"os/exec"
1315
"path"
1416
"path/filepath"
1517
"regexp"
@@ -22,18 +24,23 @@ import (
2224
"github.com/lima-vm/lima/v2/pkg/ioutilx"
2325
"github.com/lima-vm/lima/v2/pkg/limatype"
2426
"github.com/lima-vm/lima/v2/pkg/limayaml"
27+
"github.com/lima-vm/lima/v2/pkg/plugins"
2528
"github.com/lima-vm/lima/v2/pkg/templatestore"
2629
)
2730

2831
const yBytesLimit = 4 * 1024 * 1024 // 4MiB
2932

3033
func Read(ctx context.Context, name, locator string) (*Template, error) {
31-
var err error
3234
tmpl := &Template{
3335
Name: name,
3436
Locator: locator,
3537
}
3638

39+
locator, err := TransformCustomURL(ctx, locator)
40+
if err != nil {
41+
return nil, err
42+
}
43+
3744
if imageTemplate(tmpl, locator) {
3845
return tmpl, nil
3946
}
@@ -241,7 +248,10 @@ func SeemsTemplateURL(arg string) (isTemplate bool, templateName string) {
241248
return false, ""
242249
}
243250
if u.Scheme == "template" {
244-
return true, path.Join(u.Host, u.Path)
251+
if u.Opaque == "" {
252+
return true, path.Join(u.Host, u.Path)
253+
}
254+
return true, u.Opaque
245255
}
246256
return false, ""
247257
}
@@ -285,3 +295,55 @@ func InstNameFromYAMLPath(yamlPath string) (string, error) {
285295
}
286296
return s, nil
287297
}
298+
299+
func TransformCustomURL(ctx context.Context, locator string) (string, error) {
300+
u, err := url.Parse(locator)
301+
if err != nil || len(u.Scheme) <= 1 {
302+
return locator, nil
303+
}
304+
305+
if u.Scheme == "template" {
306+
if u.Opaque != "" {
307+
return locator, nil
308+
}
309+
// Fix malformed "template://" URLs.
310+
newLocator := "template:" + path.Join(u.Host, u.Path)
311+
logrus.Warnf("Template locator %q should be written %q", locator, newLocator)
312+
return newLocator, nil
313+
}
314+
315+
plugin, err := plugins.Find("url-" + u.Scheme)
316+
if err != nil {
317+
return "", err
318+
}
319+
if plugin == nil {
320+
return locator, nil
321+
}
322+
323+
currentPath := os.Getenv("PATH")
324+
defer os.Setenv("PATH", currentPath)
325+
err = plugins.UpdatePath()
326+
if err != nil {
327+
return "", err
328+
}
329+
330+
cmd := exec.CommandContext(ctx, plugin.Path, strings.TrimPrefix(u.String(), u.Scheme+":"))
331+
cmd.Env = os.Environ()
332+
333+
stdout, err := cmd.Output()
334+
if err != nil {
335+
var exitErr *exec.ExitError
336+
if errors.As(err, &exitErr) {
337+
stderrMsg := string(exitErr.Stderr)
338+
if stderrMsg != "" {
339+
return "", fmt.Errorf("command %q failed: %s", cmd.String(), strings.TrimSpace(stderrMsg))
340+
}
341+
}
342+
return "", fmt.Errorf("command %q failed: %w", cmd.String(), err)
343+
}
344+
newLocator := strings.TrimSpace(string(stdout))
345+
if newLocator != locator {
346+
logrus.Debugf("Custom locator %q replaced with %q", locator, newLocator)
347+
}
348+
return newLocator, nil
349+
}

pkg/plugins/plugins.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,21 @@ func extractDescFromScript(path string) string {
192192
return desc
193193
}
194194

195+
// Find locates a plugin by name and returns a pointer to a copy.
196+
func Find(name string) (*Plugin, error) {
197+
allPlugins, err := Discover()
198+
if err != nil {
199+
return nil, err
200+
}
201+
for _, plugin := range allPlugins {
202+
if name == plugin.Name {
203+
pluginCopy := plugin
204+
return &pluginCopy, nil
205+
}
206+
}
207+
return nil, nil
208+
}
209+
195210
func UpdatePath() error {
196211
pluginDirs := getPluginDirectories()
197212
newPath := strings.Join(pluginDirs, string(filepath.ListSeparator))

0 commit comments

Comments
 (0)