Skip to content

Commit 63afc8a

Browse files
committed
Implement "data" provision mode
These are data files copied to the guest filesystem. They are not scripts and are not being executed. Signed-off-by: Jan Dubois <[email protected]>
1 parent d82d125 commit 63afc8a

File tree

14 files changed

+264
-40
lines changed

14 files changed

+264
-40
lines changed

hack/test-templates.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ declare -A CHECKS=(
5555
["user-v2"]=""
5656
["mount-path-with-spaces"]=""
5757
["provision-ansible"]=""
58+
["provision-data"]=""
5859
["param-env-variables"]=""
5960
["set-user"]=""
6061
)
@@ -82,6 +83,7 @@ case "$NAME" in
8283
CHECKS["snapshot-offline"]="1"
8384
CHECKS["mount-path-with-spaces"]="1"
8485
CHECKS["provision-ansible"]="1"
86+
CHECKS["provision-data"]="1"
8587
CHECKS["param-env-variables"]="1"
8688
CHECKS["set-user"]="1"
8789
;;
@@ -194,13 +196,19 @@ if [[ -n ${CHECKS["provision-ansible"]} ]]; then
194196
limactl shell "$NAME" test -e /tmp/ansible
195197
fi
196198

199+
if [[ -n ${CHECKS["provision-data"]} ]]; then
200+
INFO 'Testing that /etc/sysctl.d/99-inotify.conf was created successfully on provision'
201+
limactl shell "$NAME" grep -q fs.inotify.max_user_watches /etc/sysctl.d/99-inotify.conf
202+
fi
203+
197204
if [[ -n ${CHECKS["param-env-variables"]} ]]; then
198205
INFO 'Testing that PARAM env variables are exported to all types of provisioning scripts and probes'
199206
limactl shell "$NAME" test -e /tmp/param-boot
200207
limactl shell "$NAME" test -e /tmp/param-dependency
201208
limactl shell "$NAME" test -e /tmp/param-probe
202209
limactl shell "$NAME" test -e /tmp/param-system
203-
limactl shell "$NAME" test -e /tmp/param-user
210+
# TODO re-enable once https://github.com/lima-vm/lima/issues/3308 is fixed
211+
# limactl shell "$NAME" test -e /tmp/param-user
204212
fi
205213

206214
if [[ -n ${CHECKS["set-user"]} ]]; then

hack/test-templates/test-misc.yaml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ param:
1818
DEPENDENCY: dependency
1919
PROBE: probe
2020
SYSTEM: system
21-
USER: user
21+
# TODO re-enable once https://github.com/lima-vm/lima/issues/3308 is fixed
22+
# USER: user
2223

2324
provision:
2425
- mode: ansible
@@ -29,8 +30,14 @@ provision:
2930
script: "touch /tmp/param-$PARAM_DEPENDENCY"
3031
- mode: system
3132
script: "touch /tmp/param-$PARAM_SYSTEM"
32-
- mode: user
33-
script: "touch /tmp/param-$PARAM_USER"
33+
# TODO re-enable once https://github.com/lima-vm/lima/issues/3308 is fixed
34+
# - mode: user
35+
# script: "touch /tmp/param-$PARAM_USER"
36+
- mode: data
37+
path: /etc/sysctl.d/99-inotify.conf
38+
content: |
39+
fs.inotify.max_user_watches = 524288
40+
fs.inotify.max_user_instances = 512
3441
3542
probes:
3643
- mode: readiness

pkg/cidata/cidata.TEMPLATE.d/boot.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,31 @@ else
5252
done
5353
fi
5454

55+
# indirect variable lookup, like ${!var} in bash
56+
deref() {
57+
eval echo \$"$1"
58+
}
59+
60+
if [ -d "${LIMA_CIDATA_MNT}"/provision.data ]; then
61+
for f in "${LIMA_CIDATA_MNT}"/provision.data/*; do
62+
filename=$(basename "$f")
63+
overwrite=$(deref "LIMA_CIDATA_DATAFILE_${filename}_OVERWRITE")
64+
owner=$(deref "LIMA_CIDATA_DATAFILE_${filename}_OWNER")
65+
path=$(deref "LIMA_CIDATA_DATAFILE_${filename}_PATH")
66+
permissions=$(deref "LIMA_CIDATA_DATAFILE_${filename}_PERMISSIONS")
67+
if [ -e "$path" ] && [ "$overwrite" = "false" ]; then
68+
INFO "Not overwriting $path"
69+
else
70+
INFO "Copying $f to $path"
71+
# intermediate directories will be owned by root, regardless of OWNER setting
72+
mkdir -p "$(dirname "$path")"
73+
cp "$f" "$path"
74+
chown "$owner" "$path"
75+
chmod "$permissions" "$path"
76+
fi
77+
done
78+
fi
79+
5580
if [ -d "${LIMA_CIDATA_MNT}"/provision.system ]; then
5681
for f in "${LIMA_CIDATA_MNT}"/provision.system/*; do
5782
INFO "Executing $f"

pkg/cidata/cidata.TEMPLATE.d/lima.env

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ LIMA_CIDATA_DISK_{{$i}}_FORMAT={{$disk.Format}}
1919
LIMA_CIDATA_DISK_{{$i}}_FSTYPE={{$disk.FSType}}
2020
LIMA_CIDATA_DISK_{{$i}}_FSARGS={{range $j, $arg := $disk.FSArgs}}{{if $j}} {{end}}{{$arg}}{{end}}
2121
{{- end}}
22+
{{- range $dataFile := .DataFiles}}
23+
LIMA_CIDATA_DATAFILE_{{$dataFile.FileName}}_OVERWRITE={{$dataFile.Overwrite}}
24+
LIMA_CIDATA_DATAFILE_{{$dataFile.FileName}}_OWNER={{$dataFile.Owner}}
25+
LIMA_CIDATA_DATAFILE_{{$dataFile.FileName}}_PATH={{$dataFile.Path}}
26+
LIMA_CIDATA_DATAFILE_{{$dataFile.FileName}}_PERMISSIONS={{$dataFile.Permissions}}
27+
{{- end}}
2228
LIMA_CIDATA_GUEST_INSTALL_PREFIX={{ .GuestInstallPrefix }}
2329
{{- if .Containerd.User}}
2430
LIMA_CIDATA_CONTAINERD_USER=1

pkg/cidata/cidata.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"path"
1515
"path/filepath"
1616
"slices"
17+
"strconv"
1718
"strings"
1819
"time"
1920

@@ -322,10 +323,19 @@ func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.L
322323

323324
args.BootCmds = getBootCmds(instConfig.Provision)
324325

325-
for _, f := range instConfig.Provision {
326+
for i, f := range instConfig.Provision {
326327
if f.Mode == limayaml.ProvisionModeDependency && *f.SkipDefaultDependencyResolution {
327328
args.SkipDefaultDependencyResolution = true
328329
}
330+
if f.Mode == limayaml.ProvisionModeData {
331+
args.DataFiles = append(args.DataFiles, DataFile{
332+
FileName: fmt.Sprintf("%08d", i),
333+
Overwrite: strconv.FormatBool(*f.Overwrite),
334+
Owner: *f.Owner,
335+
Path: *f.Path,
336+
Permissions: *f.Permissions,
337+
})
338+
}
329339
}
330340

331341
return &args, nil
@@ -376,6 +386,11 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS
376386
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
377387
Reader: strings.NewReader(f.Script),
378388
})
389+
case limayaml.ProvisionModeData:
390+
layout = append(layout, iso9660util.Entry{
391+
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
392+
Reader: strings.NewReader(*f.Content),
393+
})
379394
case limayaml.ProvisionModeBoot:
380395
continue
381396
case limayaml.ProvisionModeAnsible:

pkg/cidata/template.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ type Mount struct {
5050
type BootCmds struct {
5151
Lines []string
5252
}
53+
54+
type DataFile struct {
55+
FileName string
56+
Overwrite string
57+
Owner string
58+
Path string
59+
Permissions string
60+
}
61+
5362
type Disk struct {
5463
Name string
5564
Device string
@@ -84,6 +93,7 @@ type TemplateArgs struct {
8493
Env map[string]string
8594
Param map[string]string
8695
BootScripts bool
96+
DataFiles []DataFile
8797
DNSAddresses []string
8898
CACerts CACerts
8999
HostHomeMountPoint string

pkg/limatmpl/embed.go

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -552,11 +552,11 @@ func (tmpl *Template) combineNetworks() {
552552
}
553553
}
554554

555-
// updateScript replaces a "file" property with the actual script and then renames the field to "script".
556-
func (tmpl *Template) updateScript(field string, idx int, script string) {
555+
// updateScript replaces a "file" property with the actual script and then renames the field to newName ("script" or "content").
556+
func (tmpl *Template) updateScript(field string, idx int, newName, script string) {
557557
entry := fmt.Sprintf("$a.%s[%d].file", field, idx)
558558
// Assign script to the "file" field and then rename it to "script".
559-
tmpl.expr.WriteString(fmt.Sprintf("| (%s) = %q | (%s | key) = \"script\"\n", entry, script, entry))
559+
tmpl.expr.WriteString(fmt.Sprintf("| (%s) = %q | (%s | key) = %q\n", entry, script, entry, newName))
560560
}
561561

562562
// embedAllScripts replaces all "provision" and "probes" file references with the actual script.
@@ -565,30 +565,47 @@ func (tmpl *Template) embedAllScripts(ctx context.Context, embedAll bool) error
565565
return err
566566
}
567567
for i, p := range tmpl.Config.Probes {
568+
if p.File == nil {
569+
continue
570+
}
571+
warnFileIsExperimental()
568572
// Don't overwrite existing script. This should throw an error during validation.
569-
if p.File != nil && p.Script == "" {
570-
warnFileIsExperimental()
571-
isTemplate, _ := SeemsTemplateURL(p.File.URL)
572-
if embedAll || !isTemplate {
573-
scriptTmpl, err := Read(ctx, "", p.File.URL)
574-
if err != nil {
575-
return err
576-
}
577-
tmpl.updateScript("probes", i, string(scriptTmpl.Bytes))
573+
if p.Script != "" {
574+
continue
575+
}
576+
isTemplate, _ := SeemsTemplateURL(p.File.URL)
577+
if embedAll || !isTemplate {
578+
scriptTmpl, err := Read(ctx, "", p.File.URL)
579+
if err != nil {
580+
return err
578581
}
582+
tmpl.updateScript("probes", i, "script", string(scriptTmpl.Bytes))
579583
}
580584
}
581585
for i, p := range tmpl.Config.Provision {
582-
if p.File != nil && p.Script == "" {
583-
warnFileIsExperimental()
584-
isTemplate, _ := SeemsTemplateURL(p.File.URL)
585-
if embedAll || !isTemplate {
586-
scriptTmpl, err := Read(ctx, "", p.File.URL)
587-
if err != nil {
588-
return err
589-
}
590-
tmpl.updateScript("provision", i, string(scriptTmpl.Bytes))
586+
if p.File == nil {
587+
continue
588+
}
589+
warnFileIsExperimental()
590+
newName := "script"
591+
switch p.Mode {
592+
case limayaml.ProvisionModeData:
593+
newName = "content"
594+
if p.Content != nil {
595+
continue
596+
}
597+
default:
598+
if p.Script != "" {
599+
continue
600+
}
601+
}
602+
isTemplate, _ := SeemsTemplateURL(p.File.URL)
603+
if embedAll || !isTemplate {
604+
scriptTmpl, err := Read(ctx, "", p.File.URL)
605+
if err != nil {
606+
return err
591607
}
608+
tmpl.updateScript("provision", i, newName, string(scriptTmpl.Bytes))
592609
}
593610
}
594611
return tmpl.evalExpr()

pkg/limatmpl/embed_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,20 +339,32 @@ networks:
339339
provision:
340340
# This script will be merged from an external file
341341
- file: base1.sh # This comment will move to the "script" key
342+
# This is just a data file
343+
- mode: data
344+
file: base1.sh # This comment will move to the "content" key
345+
path: /tmp/data
342346
`,
343347
`
344348
# base0.yaml is ignored
345349
---
346350
#!/usr/bin/env bash
347351
echo "This is base1.sh"
348352
`,
353+
// TODO: the empty line after the `path` is unexpected
349354
`
350355
# Hi There!
351356
provision:
352357
# This script will be merged from an external file
353358
- script: |- # This comment will move to the "script" key
354359
#!/usr/bin/env bash
355360
echo "This is base1.sh"
361+
# This is just a data file
362+
- mode: data
363+
content: |- # This comment will move to the "content" key
364+
#!/usr/bin/env bash
365+
echo "This is base1.sh"
366+
path: /tmp/data
367+
356368
# base0.yaml is ignored
357369
`,
358370
},

pkg/limayaml/defaults.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -487,10 +487,47 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) {
487487
if provision.Mode == ProvisionModeDependency && provision.SkipDefaultDependencyResolution == nil {
488488
provision.SkipDefaultDependencyResolution = ptr.Of(false)
489489
}
490-
if out, err := executeGuestTemplate(provision.Script, instDir, y.User, y.Param); err == nil {
491-
provision.Script = out.String()
492-
} else {
493-
logrus.WithError(err).Warnf("Couldn't process provisioning script %q as a template", provision.Script)
490+
if provision.Mode == ProvisionModeData {
491+
if provision.Content == nil {
492+
provision.Content = ptr.Of("")
493+
} else {
494+
if out, err := executeGuestTemplate(*provision.Content, instDir, y.User, y.Param); err == nil {
495+
provision.Content = ptr.Of(out.String())
496+
} else {
497+
logrus.WithError(err).Warnf("Couldn't process data content %q as a template", *provision.Content)
498+
}
499+
}
500+
if provision.Overwrite == nil {
501+
provision.Overwrite = ptr.Of(true)
502+
}
503+
if provision.Owner == nil {
504+
provision.Owner = ptr.Of("root:root")
505+
} else {
506+
if out, err := executeGuestTemplate(*provision.Owner, instDir, y.User, y.Param); err == nil {
507+
provision.Owner = ptr.Of(out.String())
508+
} else {
509+
logrus.WithError(err).Warnf("Couldn't owner %q as a template", *provision.Owner)
510+
}
511+
}
512+
// Path is required; validation will throw an error when it is nil
513+
if provision.Path != nil {
514+
if out, err := executeGuestTemplate(*provision.Path, instDir, y.User, y.Param); err == nil {
515+
provision.Path = ptr.Of(out.String())
516+
} else {
517+
logrus.WithError(err).Warnf("Couldn't process path %q as a template", *provision.Path)
518+
}
519+
}
520+
if provision.Permissions == nil {
521+
provision.Permissions = ptr.Of("644")
522+
}
523+
}
524+
// TODO Turn Script into a pointer; it is a plain string for historical reasons only
525+
if provision.Script != "" {
526+
if out, err := executeGuestTemplate(provision.Script, instDir, y.User, y.Param); err == nil {
527+
provision.Script = out.String()
528+
} else {
529+
logrus.WithError(err).Warnf("Couldn't process provisioning script %q as a template", provision.Script)
530+
}
494531
}
495532
}
496533

pkg/limayaml/limayaml.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ const (
221221
ProvisionModeBoot ProvisionMode = "boot"
222222
ProvisionModeDependency ProvisionMode = "dependency"
223223
ProvisionModeAnsible ProvisionMode = "ansible"
224+
ProvisionModeData ProvisionMode = "data"
224225
)
225226

226227
type Provision struct {
@@ -229,6 +230,16 @@ type Provision struct {
229230
Script string `yaml:"script" json:"script"`
230231
File *LocatorWithDigest `yaml:"file,omitempty" json:"file,omitempty" jsonschema:"nullable"`
231232
Playbook string `yaml:"playbook,omitempty" json:"playbook,omitempty"`
233+
// All ProvisionData fields must be nil unless Mode is ProvisionModeData
234+
ProvisionData `yaml:",inline"` // Flatten fields for "strict" YAML mode
235+
}
236+
237+
type ProvisionData struct {
238+
Content *string `yaml:"content,omitempty" json:"content,omitempty" jsonschema:"nullable"`
239+
Overwrite *bool `yaml:"overwrite,omitempty" json:"overwrite,omitempty" jsonschema:"nullable"`
240+
Owner *string `yaml:"owner,omitempty" json:"owner,omitempty"` // any owner string supported by `chown`, defaults to "root:root"
241+
Path *string `yaml:"path,omitempty" json:"path,omitempty"`
242+
Permissions *string `yaml:"permissions,omitempty" json:"permissions,omitempty"`
232243
}
233244

234245
type Containerd struct {

0 commit comments

Comments
 (0)