Skip to content

Commit c1994c5

Browse files
authored
Merge pull request #3302 from jandubois/provision-data
Implement "data" provision mode
2 parents d82d125 + 63afc8a commit c1994c5

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)