Skip to content

Commit b0474bb

Browse files
committed
Allow an image URL instead of a template reference
The template.Read() method will guess the architecture based on the image filename and construct a template dynamically. It also strips generic tags from the image name to generate an instance name candidate. Signed-off-by: Jan Dubois <[email protected]>
1 parent 9fd8519 commit b0474bb

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

pkg/limatmpl/locator.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ import (
1212
"os"
1313
"path"
1414
"path/filepath"
15+
"regexp"
16+
"runtime"
1517
"strings"
1618
"unicode"
1719

1820
"github.com/containerd/containerd/identifiers"
1921
"github.com/lima-vm/lima/pkg/ioutilx"
22+
"github.com/lima-vm/lima/pkg/limayaml"
2023
"github.com/lima-vm/lima/pkg/templatestore"
2124
"github.com/sirupsen/logrus"
2225
)
@@ -30,6 +33,10 @@ func Read(ctx context.Context, name, locator string) (*Template, error) {
3033
Locator: locator,
3134
}
3235

36+
if imageTemplate(tmpl, locator) {
37+
return tmpl, nil
38+
}
39+
3340
isTemplateURL, templateURL := SeemsTemplateURL(locator)
3441
switch {
3542
case isTemplateURL:
@@ -121,6 +128,91 @@ func Read(ctx context.Context, name, locator string) (*Template, error) {
121128
return tmpl, nil
122129
}
123130

131+
// Locators with an image file format extension, optionally followed by a compression method.
132+
// This regex is also used to remove the file format suffix from the instance name.
133+
var imageURLRegex = regexp.MustCompile(`\.(img|qcow2|raw|iso)(\.(gz|xz|bz2|zstd))?$`)
134+
135+
// Image architecture will be guessed based on the presence of arch keywords.
136+
var archKeywords = map[string]limayaml.Arch{
137+
"aarch64": limayaml.AARCH64,
138+
"amd64": limayaml.X8664,
139+
"arm64": limayaml.AARCH64,
140+
"armhf": limayaml.ARMV7L,
141+
"armv7l": limayaml.ARMV7L,
142+
"riscv64": limayaml.RISCV64,
143+
"x86_64": limayaml.X8664,
144+
}
145+
146+
// These generic tags will be stripped from an image name before turning it into an instance name.
147+
var genericTags = []string{
148+
"base", // Fedora, Rocky
149+
"cloud", // Fedora, openSUSE
150+
"cloudimg", // Ubuntu, Arch
151+
"cloudinit", // Alpine
152+
"default", // Gentoo
153+
"generic", // Fedora
154+
"genericcloud", // CentOS, Debian, Rocky, Alma
155+
"kvm", // Oracle
156+
"latest", // Gentoo, CentOS, Rocky, Alma
157+
"linux", // Arch
158+
"minimal", // openSUSE
159+
"openstack", // Gentoo
160+
"server", // Ubuntu
161+
"std", // Alpine-Lima
162+
"stream", // CentOS
163+
"uefi", // Alpine
164+
"vm", // openSUSE
165+
}
166+
167+
// imageTemplate checks if the locator specifies an image URL.
168+
// It will create a minimal template with the image URL and arch derived from the image name
169+
// and also set the default instance name to the image name, but stripped of generic tags.
170+
func imageTemplate(tmpl *Template, locator string) bool {
171+
if !imageURLRegex.MatchString(locator) {
172+
return false
173+
}
174+
175+
var imageArch limayaml.Arch
176+
for keyword, arch := range archKeywords {
177+
pattern := fmt.Sprintf(`\b%s\b`, keyword)
178+
if regexp.MustCompile(pattern).MatchString(locator) {
179+
imageArch = arch
180+
break
181+
}
182+
}
183+
if imageArch == "" {
184+
imageArch = limayaml.NewArch(runtime.GOARCH)
185+
logrus.Warnf("cannot determine image arch from URL %q; assuming %q", locator, imageArch)
186+
}
187+
template := `arch: %q
188+
images:
189+
- location: %q
190+
arch: %q
191+
`
192+
tmpl.Bytes = []byte(fmt.Sprintf(template, imageArch, locator, imageArch))
193+
tmpl.Name = InstNameFromImageURL(locator, imageArch)
194+
return true
195+
}
196+
197+
func InstNameFromImageURL(locator, imageArch string) string {
198+
// We intentionally call both path.Base and filepath.Base in case we are running on Windows.
199+
name := strings.ToLower(filepath.Base(path.Base(locator)))
200+
// Remove file format and compression file types
201+
name = imageURLRegex.ReplaceAllString(name, "")
202+
for _, tag := range genericTags {
203+
re := regexp.MustCompile(fmt.Sprintf(`[-_.]%s\b`, tag))
204+
name = re.ReplaceAllString(name, "")
205+
}
206+
// The Alpine "nocloud_" prefix does not fit the genericTags pattern
207+
name = strings.TrimPrefix(name, "nocloud_")
208+
// Remove imageArch as well if it is the native arch
209+
if limayaml.IsNativeArch(imageArch) {
210+
re := regexp.MustCompile(fmt.Sprintf(`[-_.]%s\b`, imageArch))
211+
name = re.ReplaceAllString(name, "")
212+
}
213+
return name
214+
}
215+
124216
func SeemsTemplateURL(arg string) (bool, *url.URL) {
125217
u, err := url.Parse(arg)
126218
if err != nil {

pkg/limatmpl/locator_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package limatmpl_test
5+
6+
import (
7+
"fmt"
8+
"runtime"
9+
"testing"
10+
11+
"github.com/lima-vm/lima/pkg/limatmpl"
12+
"github.com/lima-vm/lima/pkg/limayaml"
13+
"gotest.tools/v3/assert"
14+
)
15+
16+
func TestInstNameFromImageURL(t *testing.T) {
17+
t.Run("strips image format and compression method", func(t *testing.T) {
18+
name := limatmpl.InstNameFromImageURL("linux.iso.bz2", "unknown")
19+
assert.Equal(t, name, "linux")
20+
})
21+
t.Run("removes generic tags", func(t *testing.T) {
22+
name := limatmpl.InstNameFromImageURL("linux-linux_cloudimg.base-x86_64.raw", "unknown")
23+
assert.Equal(t, name, "linux-x86_64")
24+
})
25+
t.Run("removes native arch", func(t *testing.T) {
26+
arch := limayaml.NewArch(runtime.GOARCH)
27+
image := fmt.Sprintf("linux_cloudimg.base-%s.qcow2.gz", arch)
28+
name := limatmpl.InstNameFromImageURL(image, arch)
29+
assert.Equal(t, name, "linux")
30+
})
31+
}

0 commit comments

Comments
 (0)