Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/limatmpl/abs.go
2609 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package limatmpl
5
6
import (
7
"errors"
8
"fmt"
9
"net/url"
10
"path"
11
"path/filepath"
12
"runtime"
13
"strings"
14
15
"github.com/lima-vm/lima/v2/pkg/localpathutil"
16
)
17
18
// UseAbsLocators will replace all relative template locators with absolute ones, so this template
19
// can be stored anywhere and still reference the same base templates and files.
20
func (tmpl *Template) UseAbsLocators() error {
21
err := tmpl.useAbsLocators()
22
return tmpl.ClearOnError(err)
23
}
24
25
func (tmpl *Template) useAbsLocators() error {
26
if err := tmpl.Unmarshal(); err != nil {
27
return err
28
}
29
basePath, err := basePath(tmpl.Locator)
30
if err != nil {
31
return err
32
}
33
for i, baseLocator := range tmpl.Config.Base {
34
absLocator, err := absPath(baseLocator.URL, basePath)
35
if err != nil {
36
return err
37
}
38
if i == 0 {
39
// base can be either a single string (URL), or a single locator object, or a list whose first element can be either a string or an object
40
tmpl.expr.WriteString(fmt.Sprintf("| ($a.base | select(type == \"!!str\")) |= %q\n", absLocator))
41
tmpl.expr.WriteString(fmt.Sprintf("| ($a.base | select(type == \"!!map\") | .url) |= %q\n", absLocator))
42
tmpl.expr.WriteString(fmt.Sprintf("| ($a.base | select(type == \"!!seq\" and (.[0] | type) == \"!!str\") | .[0]) |= %q\n", absLocator))
43
tmpl.expr.WriteString(fmt.Sprintf("| ($a.base | select(type == \"!!seq\" and (.[0] | type) == \"!!map\") | .[0].url) |= %q\n", absLocator))
44
} else {
45
tmpl.expr.WriteString(fmt.Sprintf("| ($a.base[%d] | select(type == \"!!str\")) |= %q\n", i, absLocator))
46
tmpl.expr.WriteString(fmt.Sprintf("| ($a.base[%d] | select(type == \"!!map\") | .url) |= %q\n", i, absLocator))
47
}
48
}
49
for i, p := range tmpl.Config.Probes {
50
if p.File != nil {
51
absLocator, err := absPath(p.File.URL, basePath)
52
if err != nil {
53
return err
54
}
55
tmpl.expr.WriteString(fmt.Sprintf("| ($a.probes[%d].file | select(type == \"!!str\")) = %q\n", i, absLocator))
56
tmpl.expr.WriteString(fmt.Sprintf("| ($a.probes[%d].file | select(type == \"!!map\") | .url) = %q\n", i, absLocator))
57
}
58
}
59
for i, p := range tmpl.Config.Provision {
60
if p.File != nil {
61
absLocator, err := absPath(p.File.URL, basePath)
62
if err != nil {
63
return err
64
}
65
tmpl.expr.WriteString(fmt.Sprintf("| ($a.provision[%d].file | select(type == \"!!str\")) = %q\n", i, absLocator))
66
tmpl.expr.WriteString(fmt.Sprintf("| ($a.provision[%d].file | select(type == \"!!map\") | .url) = %q\n", i, absLocator))
67
}
68
}
69
return tmpl.evalExpr()
70
}
71
72
// withVolume adds the volume name of the current working directory to a path without volume name.
73
// On Windows filepath.Abs() only returns a "rooted" name, but does not add the volume name.
74
// withVolume also normalizes all path separators to the platform native one.
75
func withVolume(path string) (string, error) {
76
if runtime.GOOS == "windows" && filepath.VolumeName(path) == "" {
77
root, err := filepath.Abs("/")
78
if err != nil {
79
return "", err
80
}
81
path = filepath.VolumeName(root) + path
82
}
83
return filepath.Clean(path), nil
84
}
85
86
// basePath returns the locator in absolute format, but without the filename part.
87
func basePath(locator string) (string, error) {
88
u, err := url.Parse(locator)
89
// Single-letter schemes will be drive names on Windows, e.g. "c:/foo"
90
if err == nil && len(u.Scheme) > 1 {
91
// path.Dir("") returns ".", which must be removed for url.JoinPath() to do the right thing later
92
if u.Opaque != "" {
93
return u.Scheme + ":" + strings.TrimSuffix(path.Dir(u.Opaque), "."), nil
94
}
95
return u.Scheme + "://" + strings.TrimSuffix(path.Dir(path.Join(u.Host, u.Path)), "."), nil
96
}
97
base, err := filepath.Abs(filepath.Dir(locator))
98
if err != nil {
99
return "", err
100
}
101
return withVolume(base)
102
}
103
104
// absPath either returns the locator directly, or combines it with the basePath if the locator is a relative path.
105
func absPath(locator, basePath string) (string, error) {
106
if locator == "" {
107
return "", errors.New("locator is empty")
108
}
109
u, err := url.Parse(locator)
110
if err == nil && len(u.Scheme) > 1 {
111
return locator, nil
112
}
113
// Don't expand relative path to absolute. Tilde paths however are absolute paths already.
114
if localpathutil.IsTildePath(locator) {
115
locator, err = localpathutil.Expand(locator)
116
if err != nil {
117
return "", err
118
}
119
}
120
// Check for rooted locator; filepath.IsAbs() returns false on Windows when the volume name is missing
121
volumeLen := len(filepath.VolumeName(locator))
122
if locator[volumeLen] != '/' && locator[volumeLen] != filepath.Separator {
123
switch {
124
case basePath == "":
125
return "", errors.New("basePath is empty")
126
case basePath == "-":
127
return "", errors.New("can't use relative paths when reading template from STDIN")
128
case strings.Contains(locator, "../"):
129
return "", fmt.Errorf("relative locator path %q must not contain '../' segments", locator)
130
case volumeLen != 0:
131
return "", fmt.Errorf("relative locator path %q must not include a volume name", locator)
132
}
133
u, err = url.Parse(basePath)
134
if err != nil {
135
return "", err
136
}
137
if len(u.Scheme) > 1 {
138
// Treat empty "template:" URL as opaque
139
if u.Opaque != "" || (u.Scheme == "template" && u.Host == "") {
140
return u.Scheme + ":" + path.Join(u.Opaque, locator), nil
141
}
142
return u.JoinPath(locator).String(), nil
143
}
144
locator = filepath.Join(basePath, locator)
145
}
146
return withVolume(locator)
147
}
148
149