Skip to content

Commit 8f59a28

Browse files
committed
feature: "yq" provision mode
```yaml # `yq` creates or edits a file in the guest filesystem by using `${LIMA_CIDATA_GUEST_INSTALL_PREFIX}/bin/lima-guestagent yq`. # `${LIMA_CIDATA_GUEST_INSTALL_PREFIX}/bin/lima-guestagent yq` has the same functionality as https://github.com/mikefarah/yq. # The file specified by `path` will be updated with the specified `expression` using `yq --inplace`. # If the file does not exist, it will be created using `yq --null-input`. # `format` is optional and defaults to "auto", which is passed to `yq` by `--input-format <format>`, `--output-format <format>`. # One of the following is expected to work: # "auto", "a", "ini", "i", "json", "j", "props", "p", "xml", "x", "yaml", "y" # `yq` v4.47.1 can write .ini, .json, .properties, .xml, .yaml, and .yml files. # See https://github.com/mikefarah/yq for more info. # Any missing directories will be created as needed using `mkdir -p`. # The file permissions will be set to the specified value using `chmod`. # The file and directory creation will be performed with the specified user privileges. # If the existing file is not writable by the specified user, the operation will fail. # `path` and `expression` are required. # `user` and `permissions` are optional. Defaults to "root" and 644. - mode: yq path: "{{.Home}}/.config/docker/daemon.json" expression: ".features.containerd-snapshotter = {{.Param.containerdSnapshotter}}" format: auto user: "{{.User}}" permissions: 644 Signed-off-by: Norio Nomura <norio.nomura@gmail.com>
1 parent 172fe4d commit 8f59a28

File tree

16 files changed

+259
-20
lines changed

16 files changed

+259
-20
lines changed

hack/test-templates.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ declare -A CHECKS=(
5757
["mount-path-with-spaces"]=""
5858
["provision-data"]=""
5959
["param-env-variables"]=""
60+
["provision-yq"]=""
6061
["set-user"]=""
6162
["preserve-env"]="1"
6263
)
@@ -91,6 +92,7 @@ case "$NAME" in
9192
CHECKS["mount-path-with-spaces"]="1"
9293
CHECKS["provision-data"]="1"
9394
CHECKS["param-env-variables"]="1"
95+
CHECKS["provision-yq"]="1"
9496
CHECKS["set-user"]="1"
9597
;;
9698
"docker")
@@ -209,6 +211,11 @@ if [[ -n ${CHECKS["param-env-variables"]} ]]; then
209211
limactl shell "$NAME" test -e /tmp/param-user
210212
fi
211213

214+
if [[ -n ${CHECKS["provision-yq"]} ]]; then
215+
INFO 'Testing that /tmp/param-yq.json was created successfully on provision'
216+
limactl shell "$NAME" grep -q '"YQ": "yq"' /tmp/param-yq.json
217+
fi
218+
212219
if [[ -n ${CHECKS["set-user"]} ]]; then
213220
INFO 'Testing that user settings can be provided by lima.yaml'
214221
limactl shell "$NAME" grep "^john:x:4711:4711:John Doe:/home/john-john:/usr/bin/bash" /etc/passwd

hack/test-templates/test-misc.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ param:
2020
PROBE: probe
2121
SYSTEM: system
2222
USER: user
23+
YQ: yq
2324

2425
provision:
2526
- mode: ansible
@@ -37,6 +38,10 @@ provision:
3738
content: |
3839
fs.inotify.max_user_watches = 524288
3940
fs.inotify.max_user_instances = 512
41+
- mode: yq
42+
path: "/tmp/param-{{.Param.YQ}}.json"
43+
expression: .YQ = "{{.Param.YQ}}"
44+
user: "{{.User}}"
4045

4146
probes:
4247
- mode: readiness

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,64 @@ if [ -d "${LIMA_CIDATA_MNT}"/provision.data ]; then
7777
done
7878
fi
7979

80+
if [ -d "${LIMA_CIDATA_MNT}"/provision.yq ]; then
81+
yq="${LIMA_CIDATA_GUEST_INSTALL_PREFIX}/bin/lima-guestagent yq"
82+
if ${yq} --version >/dev/null; then
83+
for expression in "${LIMA_CIDATA_MNT}"/provision.yq/*; do
84+
filename=$(basename "${expression}")
85+
format=$(deref "LIMA_CIDATA_YQ_PROVISION_${filename}_FORMAT")
86+
path=$(deref "LIMA_CIDATA_YQ_PROVISION_${filename}_PATH")
87+
permissions=$(deref "LIMA_CIDATA_YQ_PROVISION_${filename}_PERMISSIONS")
88+
user=$(deref "LIMA_CIDATA_YQ_PROVISION_${filename}_USER")
89+
if ! sudo -iu "${user}" mkdir -p "$(dirname "${path}")"; then
90+
WARNING "Failed to create directory for ${path} (as user ${user})"
91+
CODE=1
92+
continue
93+
fi
94+
# Since CIDATA is mounted with dmode=700,fmode=700,
95+
# provision.tool/yq cannot be executed by non-root users,
96+
# and provision.yq/* files cannot be read by non-root users.
97+
if [ -f "${path}" ]; then
98+
INFO "Updating ${path}"
99+
# Relies on the fact that yq does not change the owner of the existing file.
100+
if ! ${yq} --inplace --from-file "${expression}" --input-format "${format}" --output-format "${format}" "${path}"; then
101+
WARNING "Failed to update ${path} (as user ${user})"
102+
CODE=1
103+
continue
104+
fi
105+
else
106+
if [ "${format}" = "auto" ]; then
107+
# yq can't determine the output format from non-existing files
108+
case "${path}" in
109+
*.ini) format=ini ;;
110+
*.json) format=json ;;
111+
*.properties) format=properties ;;
112+
*.xml) format=xml ;;
113+
*.yaml | *.yml) format=yaml ;;
114+
*)
115+
format=yaml
116+
WARNING "Cannot determine file type for ${path}, using yaml format"
117+
;;
118+
esac
119+
fi
120+
INFO "Creating ${path}"
121+
if ! ${yq} --null-input --from-file "${expression}" --output-format "${format}" | sudo -iu "${user}" tee "${path}"; then
122+
WARNING "Failed to create ${path} (as user ${user})"
123+
CODE=1
124+
continue
125+
fi
126+
fi
127+
if ! sudo -iu "${user}" chmod "${permissions}" "${path}"; then
128+
WARNING "Failed to set permissions for ${path} (as user ${user})"
129+
CODE=1
130+
fi
131+
done
132+
else
133+
WARNING "${yq} is not executable, skipping yq provisioning"
134+
CODE=1
135+
fi
136+
fi
137+
80138
if [ -d "${LIMA_CIDATA_MNT}"/provision.system ]; then
81139
for f in "${LIMA_CIDATA_MNT}"/provision.system/*; do
82140
INFO "Executing $f"

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ LIMA_CIDATA_DATAFILE_{{$dataFile.FileName}}_OWNER={{$dataFile.Owner}}
2525
LIMA_CIDATA_DATAFILE_{{$dataFile.FileName}}_PATH={{$dataFile.Path}}
2626
LIMA_CIDATA_DATAFILE_{{$dataFile.FileName}}_PERMISSIONS={{$dataFile.Permissions}}
2727
{{- end}}
28+
{{- range $yqProvision := .YQProvisions}}
29+
LIMA_CIDATA_YQ_PROVISION_{{$yqProvision.FileName}}_FORMAT={{$yqProvision.Format}}
30+
LIMA_CIDATA_YQ_PROVISION_{{$yqProvision.FileName}}_PATH={{$yqProvision.Path}}
31+
LIMA_CIDATA_YQ_PROVISION_{{$yqProvision.FileName}}_PERMISSIONS={{$yqProvision.Permissions}}
32+
LIMA_CIDATA_YQ_PROVISION_{{$yqProvision.FileName}}_USER={{$yqProvision.User}}
33+
{{- end}}
2834
LIMA_CIDATA_GUEST_INSTALL_PREFIX={{ .GuestInstallPrefix }}
2935
{{- if .Containerd.User}}
3036
LIMA_CIDATA_CONTAINERD_USER=1

pkg/cidata/cidata.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,15 @@ func templateArgs(ctx context.Context, bootScripts bool, instDir, name string, i
335335
Permissions: *f.Permissions,
336336
})
337337
}
338+
if f.Mode == limatype.ProvisionModeYQ {
339+
args.YQProvisions = append(args.YQProvisions, YQProvision{
340+
FileName: fmt.Sprintf("%08d", i),
341+
Format: *f.Format,
342+
Path: *f.Path,
343+
Permissions: *f.Permissions,
344+
User: *f.User,
345+
})
346+
}
338347
}
339348

340349
return &args, nil
@@ -402,6 +411,11 @@ func GenerateISO9660(ctx context.Context, drv driver.Driver, instDir, name strin
402411
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
403412
Reader: strings.NewReader(*f.Content),
404413
})
414+
case limatype.ProvisionModeYQ:
415+
layout = append(layout, iso9660util.Entry{
416+
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
417+
Reader: strings.NewReader(*f.Expression),
418+
})
405419
case limatype.ProvisionModeBoot:
406420
continue
407421
case limatype.ProvisionModeAnsible:

pkg/cidata/template.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ type DataFile struct {
5858
Permissions string
5959
}
6060

61+
type YQProvision struct {
62+
FileName string
63+
Format string
64+
Path string
65+
Permissions string
66+
User string
67+
}
68+
6169
type Disk struct {
6270
Name string
6371
Device string
@@ -93,6 +101,7 @@ type TemplateArgs struct {
93101
Param map[string]string
94102
BootScripts bool
95103
DataFiles []DataFile
104+
YQProvisions []YQProvision
96105
DNSAddresses []string
97106
CACerts CACerts
98107
HostHomeMountPoint string

pkg/limatmpl/embed.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,11 @@ func (tmpl *Template) embedAllScripts(ctx context.Context, embedAll bool) error
648648
if p.Content != nil {
649649
continue
650650
}
651+
case limatype.ProvisionModeYQ:
652+
newName = "expression"
653+
if p.Expression != nil {
654+
continue
655+
}
651656
default:
652657
if p.Script != "" {
653658
continue

pkg/limatmpl/embed_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,9 @@ provision:
343343
- mode: data
344344
file: base1.sh # This comment will move to the "content" key
345345
path: /tmp/data
346+
- mode: yq
347+
file: base1.sh # This comment will move to the "expression" key
348+
path: /tmp/yq
346349
`,
347350
`
348351
# base0.yaml is ignored
@@ -364,6 +367,11 @@ provision:
364367
#!/usr/bin/env bash
365368
echo "This is base1.sh"
366369
path: /tmp/data
370+
- mode: yq
371+
expression: |- # This comment will move to the "expression" key
372+
#!/usr/bin/env bash
373+
echo "This is base1.sh"
374+
path: /tmp/yq
367375
368376
# base0.yaml is ignored
369377
`,

pkg/limatype/lima_yaml.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ const (
235235
ProvisionModeDependency ProvisionMode = "dependency"
236236
ProvisionModeAnsible ProvisionMode = "ansible" // DEPRECATED
237237
ProvisionModeData ProvisionMode = "data"
238+
ProvisionModeYQ ProvisionMode = "yq"
238239
)
239240

240241
type Provision struct {
@@ -245,6 +246,10 @@ type Provision struct {
245246
Playbook string `yaml:"playbook,omitempty" json:"playbook,omitempty"` // DEPRECATED
246247
// All ProvisionData fields must be nil unless Mode is ProvisionModeData
247248
ProvisionData `yaml:",inline"` // Flatten fields for "strict" YAML mode
249+
// ProvisionModeYQ borrows Path and Permissions from ProvisionData
250+
Expression *string `yaml:"expression,omitempty" json:"expression,omitempty" jsonschema:"nullable"`
251+
User *string `yaml:"user,omitempty" json:"user,omitempty" jsonschema:"nullable"`
252+
Format *string `yaml:"format,omitempty" json:"format,omitempty" jsonschema:"nullable"`
248253
}
249254

250255
type ProvisionData struct {

pkg/limayaml/defaults.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,29 @@ func FillDefault(ctx context.Context, y, d, o *limatype.LimaYAML, filePath strin
414414
logrus.WithError(err).Warnf("Couldn't owner %q as a template", *provision.Owner)
415415
}
416416
}
417+
}
418+
if provision.Mode == limatype.ProvisionModeYQ {
419+
if provision.Expression != nil {
420+
if out, err := executeGuestTemplate(*provision.Expression, instDir, y.User, y.Param); err == nil {
421+
provision.Expression = ptr.Of(out.String())
422+
} else {
423+
logrus.WithError(err).Warnf("Couldn't process expression %q as a template", *provision.Expression)
424+
}
425+
}
426+
if provision.Format == nil {
427+
provision.Format = ptr.Of("auto")
428+
}
429+
if provision.User == nil {
430+
provision.User = ptr.Of("root")
431+
} else {
432+
if out, err := executeGuestTemplate(*provision.User, instDir, y.User, y.Param); err == nil {
433+
provision.User = ptr.Of(out.String())
434+
} else {
435+
logrus.WithError(err).Warnf("Couldn't process user %q as a template", *provision.User)
436+
}
437+
}
438+
}
439+
if provision.Mode == limatype.ProvisionModeData || provision.Mode == limatype.ProvisionModeYQ {
417440
// Path is required; validation will throw an error when it is nil
418441
if provision.Path != nil {
419442
if out, err := executeGuestTemplate(*provision.Path, instDir, y.User, y.Param); err == nil {

0 commit comments

Comments
 (0)