Skip to content

Commit b029e31

Browse files
committed
Add optional support for specifying digests for template locators
Instead of `base: template.yaml` the user can write: ```yaml base: - url: template.yaml digest: decafbad ``` Same thing for `file` properties of provisoning scripts and probes. The digest values are currently being ignored; verification will happen in a later PR. Signed-off-by: Jan Dubois <[email protected]>
1 parent 253c7ca commit b029e31

File tree

8 files changed

+62
-41
lines changed

8 files changed

+62
-41
lines changed

Diff for: pkg/limatmpl/abs.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func (tmpl *Template) useAbsLocators() error {
2626
return err
2727
}
2828
for i, baseLocator := range tmpl.Config.Base {
29-
locator, err := absPath(baseLocator, basePath)
29+
locator, err := absPath(baseLocator.URL, basePath)
3030
if err != nil {
3131
return err
3232
}
@@ -40,7 +40,7 @@ func (tmpl *Template) useAbsLocators() error {
4040
}
4141
for i, p := range tmpl.Config.Probes {
4242
if p.File != nil {
43-
locator, err := absPath(*p.File, basePath)
43+
locator, err := absPath(p.File.URL, basePath)
4444
if err != nil {
4545
return err
4646
}
@@ -49,7 +49,7 @@ func (tmpl *Template) useAbsLocators() error {
4949
}
5050
for i, p := range tmpl.Config.Provision {
5151
if p.File != nil {
52-
locator, err := absPath(*p.File, basePath)
52+
locator, err := absPath(p.File.URL, basePath)
5353
if err != nil {
5454
return err
5555
}
@@ -63,7 +63,7 @@ func (tmpl *Template) useAbsLocators() error {
6363
// On Windows filepath.Abs() only returns a "rooted" name, but does not add the volume name.
6464
// withVolume also normalizes all path separators to the platform native one.
6565
func withVolume(path string) (string, error) {
66-
if runtime.GOOS == "windows" && len(filepath.VolumeName(path)) == 0 {
66+
if runtime.GOOS == "windows" && filepath.VolumeName(path) == "" {
6767
root, err := filepath.Abs("/")
6868
if err != nil {
6969
return "", err

Diff for: pkg/limatmpl/abs_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ func TestAbsPath(t *testing.T) {
225225
assert.ErrorContains(t, err, "volume")
226226
})
227227
}
228-
228+
229229
t.Run("", func(t *testing.T) {
230230
actual, err := absPath("foo", "template://")
231231
assert.NilError(t, err)

Diff for: pkg/limatmpl/embed.go

+16-14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"sync"
1010

1111
"github.com/coreos/go-semver/semver"
12+
"github.com/lima-vm/lima/pkg/limayaml"
1213
"github.com/lima-vm/lima/pkg/store/dirnames"
1314
"github.com/lima-vm/lima/pkg/store/filenames"
1415
"github.com/lima-vm/lima/pkg/version/versionutil"
@@ -19,6 +20,7 @@ import (
1920
var warnBaseIsExperimental = sync.OnceFunc(func() {
2021
logrus.Warn("`base` is experimental")
2122
})
23+
2224
var warnFileIsExperimental = sync.OnceFunc(func() {
2325
logrus.Warn("`provision[*].file` and `probes[*].file` are experimental")
2426
})
@@ -69,23 +71,23 @@ func (tmpl *Template) embedAllBases(ctx context.Context, embedAll, defaultBase b
6971
break
7072
}
7173
baseLocator := tmpl.Config.Base[0]
72-
isTemplate, _ := SeemsTemplateURL(baseLocator)
74+
isTemplate, _ := SeemsTemplateURL(baseLocator.URL)
7375
if isTemplate && !embedAll {
7476
// Once we skip a template:// URL we can no longer embed any other base template
7577
for i := 1; i < len(tmpl.Config.Base); i++ {
76-
isTemplate, _ = SeemsTemplateURL(tmpl.Config.Base[i])
78+
isTemplate, _ = SeemsTemplateURL(tmpl.Config.Base[i].URL)
7779
if !isTemplate {
78-
return fmt.Errorf("cannot embed template %q after not embedding %q", tmpl.Config.Base[i], baseLocator)
80+
return fmt.Errorf("cannot embed template %q after not embedding %q", tmpl.Config.Base[i].URL, baseLocator.URL)
7981
}
8082
}
8183
break
8284
// TODO should we track embedding of template:// URLs so we can warn if we embed a non-template:// URL afterwards?
8385
}
8486

85-
if seen[baseLocator] {
86-
return fmt.Errorf("base template loop detected: template %q already included", baseLocator)
87+
if seen[baseLocator.URL] {
88+
return fmt.Errorf("base template loop detected: template %q already included", baseLocator.URL)
8789
}
88-
seen[baseLocator] = true
90+
seen[baseLocator.URL] = true
8991

9092
// remove base[0] from template before merging
9193
if err := tmpl.embedBase(ctx, baseLocator, embedAll, seen); err != nil {
@@ -101,13 +103,13 @@ func (tmpl *Template) embedAllBases(ctx context.Context, embedAll, defaultBase b
101103
return nil
102104
}
103105

104-
func (tmpl *Template) embedBase(ctx context.Context, baseLocator string, embedAll bool, seen map[string]bool) error {
106+
func (tmpl *Template) embedBase(ctx context.Context, baseLocator limayaml.LocatorWithDigest, embedAll bool, seen map[string]bool) error {
105107
warnBaseIsExperimental()
106-
logrus.Debugf("Embedding base %q in template %q", baseLocator, tmpl.Locator)
108+
logrus.Debugf("Embedding base %q in template %q", baseLocator.URL, tmpl.Locator)
107109
if err := tmpl.Unmarshal(); err != nil {
108110
return err
109111
}
110-
base, err := Read(ctx, "", baseLocator)
112+
base, err := Read(ctx, "", baseLocator.URL)
111113
if err != nil {
112114
return err
113115
}
@@ -308,7 +310,7 @@ func (tmpl *Template) deleteListEntry(list string, idx int) {
308310
tmpl.expr.WriteString(fmt.Sprintf("| del($a.%s[%d], $b.%s[%d])\n", list, idx, list, idx))
309311
}
310312

311-
// upgradeListEntryStringToMapField turns list[idx] from a string to a {field: list[idx]} map
313+
// upgradeListEntryStringToMapField turns list[idx] from a string to a {field: list[idx]} map.
312314
func (tmpl *Template) upgradeListEntryStringToMapField(list string, idx int, field string) {
313315
// TODO the head_comment on the string becomes duplicated as a foot_comment on the new field; could be a yq bug?
314316
tmpl.expr.WriteString(fmt.Sprintf("| ($a.%s[%d] | select(type == \"!!str\")) |= {\"%s\": .}\n", list, idx, field))
@@ -557,9 +559,9 @@ func (tmpl *Template) embedAllScripts(ctx context.Context, embedAll bool) error
557559
// Don't overwrite existing script. This should throw an error during validation.
558560
if p.File != nil && p.Script == "" {
559561
warnFileIsExperimental()
560-
isTemplate, _ := SeemsTemplateURL(*p.File)
562+
isTemplate, _ := SeemsTemplateURL(p.File.URL)
561563
if embedAll || !isTemplate {
562-
scriptTmpl, err := Read(ctx, "", *p.File)
564+
scriptTmpl, err := Read(ctx, "", p.File.URL)
563565
if err != nil {
564566
return err
565567
}
@@ -570,9 +572,9 @@ func (tmpl *Template) embedAllScripts(ctx context.Context, embedAll bool) error
570572
for i, p := range tmpl.Config.Provision {
571573
if p.File != nil && p.Script == "" {
572574
warnFileIsExperimental()
573-
isTemplate, _ := SeemsTemplateURL(*p.File)
575+
isTemplate, _ := SeemsTemplateURL(p.File.URL)
574576
if embedAll || !isTemplate {
575-
scriptTmpl, err := Read(ctx, "", *p.File)
577+
scriptTmpl, err := Read(ctx, "", p.File.URL)
576578
if err != nil {
577579
return err
578580
}

Diff for: pkg/limatmpl/embed_test.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ package limatmpl_test
33
import (
44
"context"
55
"fmt"
6-
"github.com/sirupsen/logrus"
76
"os"
87
"reflect"
98
"strings"
109
"testing"
1110

1211
"github.com/lima-vm/lima/pkg/limatmpl"
1312
"github.com/lima-vm/lima/pkg/limayaml"
13+
"github.com/sirupsen/logrus"
1414
"gotest.tools/v3/assert"
1515
"gotest.tools/v3/assert/cmp"
1616
)
@@ -179,13 +179,16 @@ mounts:
179179
},
180180
{
181181
"template:// URLs are not embedded when embedAll is false",
182+
// also tests file.url format
182183
``,
183184
`
184185
base: template://default
185186
provision:
186-
- file: template://provision.sh
187+
- file:
188+
url: template://provision.sh
187189
probes:
188-
- file: template://probe.sh
190+
- file:
191+
url: template://probe.sh
189192
`,
190193
`
191194
base: template://default
@@ -228,7 +231,7 @@ base: baseX.yaml`,
228231
"Bases are embedded depth-first",
229232
`#`,
230233
`
231-
base: [base1.yaml, base2.yaml]
234+
base: [base1.yaml, {url: base2.yaml}] # also test file.url format
232235
additionalDisks: [disk0]
233236
---
234237
base: base3.yaml

Diff for: pkg/limatmpl/locator.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func Read(ctx context.Context, name, locator string) (*Template, error) {
7272
return nil, err
7373
}
7474
}
75-
logrus.Debugf("interpreting argument %q as a file url for instance %q", locator, tmpl.Name)
75+
logrus.Debugf("interpreting argument %q as a file URL for instance %q", locator, tmpl.Name)
7676
filePath := strings.TrimPrefix(locator, "file://")
7777
if !filepath.IsAbs(filePath) {
7878
return nil, fmt.Errorf("file URL %q is not an absolute path", locator)

Diff for: pkg/limayaml/limayaml.go

+16-11
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ type LimaYAML struct {
5151
User User `yaml:"user,omitempty" json:"user,omitempty"`
5252
}
5353

54-
type BaseTemplates []string
54+
type BaseTemplates []LocatorWithDigest
55+
56+
type LocatorWithDigest struct {
57+
URL string `yaml:"url" json:"url"`
58+
Digest *string `yaml:"digest,omitempty" json:"digest,omitempty" jsonschema:"nullable"` // TODO currently unused
59+
}
5560

5661
type (
5762
OS = string
@@ -215,11 +220,11 @@ const (
215220
)
216221

217222
type Provision struct {
218-
Mode ProvisionMode `yaml:"mode,omitempty" json:"mode,omitempty" jsonschema:"default=system"`
219-
SkipDefaultDependencyResolution *bool `yaml:"skipDefaultDependencyResolution,omitempty" json:"skipDefaultDependencyResolution,omitempty"`
220-
Script string `yaml:"script" json:"script"`
221-
File *string `yaml:"file,omitempty" json:"file,omitempty" jsonschema:"nullable"`
222-
Playbook string `yaml:"playbook,omitempty" json:"playbook,omitempty"`
223+
Mode ProvisionMode `yaml:"mode,omitempty" json:"mode,omitempty" jsonschema:"default=system"`
224+
SkipDefaultDependencyResolution *bool `yaml:"skipDefaultDependencyResolution,omitempty" json:"skipDefaultDependencyResolution,omitempty"`
225+
Script string `yaml:"script" json:"script"`
226+
File *LocatorWithDigest `yaml:"file,omitempty" json:"file,omitempty" jsonschema:"nullable"`
227+
Playbook string `yaml:"playbook,omitempty" json:"playbook,omitempty"`
223228
}
224229

225230
type Containerd struct {
@@ -235,11 +240,11 @@ const (
235240
)
236241

237242
type Probe struct {
238-
Mode ProbeMode `yaml:"mode,omitempty" json:"mode,omitempty" jsonschema:"default=readiness"`
239-
Description string `yaml:"description,omitempty" json:"description,omitempty"`
240-
Script string `yaml:"script,omitempty" json:"script,omitempty"`
241-
File *string `yaml:"file,omitempty" json:"file,omitempty" jsonschema:"nullable"`
242-
Hint string `yaml:"hint,omitempty" json:"hint,omitempty"`
243+
Mode ProbeMode `yaml:"mode,omitempty" json:"mode,omitempty" jsonschema:"default=readiness"`
244+
Description string `yaml:"description,omitempty" json:"description,omitempty"`
245+
Script string `yaml:"script,omitempty" json:"script,omitempty"`
246+
File *LocatorWithDigest `yaml:"file,omitempty" json:"file,omitempty" jsonschema:"nullable"`
247+
Hint string `yaml:"hint,omitempty" json:"hint,omitempty"`
243248
}
244249

245250
type Proto = string

Diff for: pkg/limayaml/marshal.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,21 @@ func unmarshalDisk(dst *Disk, b []byte) error {
3535
return yaml.Unmarshal(b, dst)
3636
}
3737

38-
// unmarshalBaseTemplates unmarshalls `base` which is either a string or a list of strings.
38+
// unmarshalBaseTemplates unmarshalls `base` which is either a string or a list of Locators.
3939
func unmarshalBaseTemplates(dst *BaseTemplates, b []byte) error {
4040
var s string
4141
if err := yaml.Unmarshal(b, &s); err == nil {
42-
*dst = BaseTemplates{s}
42+
*dst = BaseTemplates{LocatorWithDigest{URL: s}}
43+
return nil
44+
}
45+
return yaml.UnmarshalWithOptions(b, dst, yaml.CustomUnmarshaler[LocatorWithDigest](unmarshalLocatorWithDigest))
46+
}
47+
48+
// unmarshalLocator unmarshalls a locator which is either a string or a Locator struct.
49+
func unmarshalLocatorWithDigest(dst *LocatorWithDigest, b []byte) error {
50+
var s string
51+
if err := yaml.Unmarshal(b, &s); err == nil {
52+
*dst = LocatorWithDigest{URL: s}
4353
return nil
4454
}
4555
return yaml.Unmarshal(b, dst)
@@ -49,6 +59,7 @@ func Unmarshal(data []byte, v any, comment string) error {
4959
opts := []yaml.DecodeOption{
5060
yaml.CustomUnmarshaler[BaseTemplates](unmarshalBaseTemplates),
5161
yaml.CustomUnmarshaler[Disk](unmarshalDisk),
62+
yaml.CustomUnmarshaler[LocatorWithDigest](unmarshalLocatorWithDigest),
5263
}
5364
if err := yaml.UnmarshalWithOptions(data, v, opts...); err != nil {
5465
return fmt.Errorf("failed to unmarshal YAML (%s): %w", comment, err)

Diff for: pkg/limayaml/validate.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ func Validate(y *LimaYAML, warn bool) error {
206206
// y.Firmware.LegacyBIOS is ignored for aarch64, but not a fatal error.
207207

208208
for i, p := range y.Provision {
209-
if p.File != nil && *p.File != "" {
210-
return fmt.Errorf("field `provision[%d].file` must be empty during validation (script should already be embedded)", i)
209+
if p.File != nil && p.File.URL != "" {
210+
return fmt.Errorf("field `provision[%d].file.url` must be empty during validation (script should already be embedded)", i)
211211
}
212212
switch p.Mode {
213213
case ProvisionModeSystem, ProvisionModeUser, ProvisionModeBoot:
@@ -249,8 +249,8 @@ func Validate(y *LimaYAML, warn bool) error {
249249
}
250250
}
251251
for i, p := range y.Probes {
252-
if p.File != nil && *p.File != "" {
253-
return fmt.Errorf("field `probes[%d].file` must be empty during validation (script should already be embedded)", i)
252+
if p.File != nil && p.File.URL != "" {
253+
return fmt.Errorf("field `probes[%d].file.url` must be empty during validation (script should already be embedded)", i)
254254
}
255255
if !strings.HasPrefix(p.Script, "#!") {
256256
return fmt.Errorf("field `probe[%d].script` must start with a '#!' line", i)

0 commit comments

Comments
 (0)