Skip to content

Commit cc10a18

Browse files
committed
feature: "yq" provision mode
```yaml # `yq` creates or edits a file in the guest filesystem by using `/mnt/lima-cidata/provision/tool/yq`. # `/mnt/lima-cidata/provision/tool/yq` is installed if `mode: yq` is specified in `provision`. # The file specified by `path` will be updated with the specified `expression` using `yq -i`. # If the file does not exist, it will be created using `yq -n`. # `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}}" user: "{{.User}}" permissions: 644 # Override provisionTool # 🟢 Builtin default: hard-coded URL with hard-coded digest (see the output of `limactl info | jq .defaultTemplate.provisionTool`) provisionTool: yq: - location: "~/Downloads/yq_linux_amd64" arch: "x86_64" digest: "sha256:..." ``` Signed-off-by: Norio Nomura <norio.nomura@gmail.com>
1 parent 2d7d6cc commit cc10a18

File tree

24 files changed

+484
-36
lines changed

24 files changed

+484
-36
lines changed

cmd/limactl/hostagent.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func newHostagentCommand() *cobra.Command {
3535
hostagentCommand.Flags().Bool("run-gui", false, "Run GUI synchronously within hostagent")
3636
hostagentCommand.Flags().String("guestagent", "", "Local file path (not URL) of lima-guestagent.OS-ARCH[.gz]")
3737
hostagentCommand.Flags().String("nerdctl-archive", "", "Local file path (not URL) of nerdctl-full-VERSION-GOOS-GOARCH.tar.gz")
38+
hostagentCommand.Flags().String("provision-tool", "", "<tool name>=<local file path>[,<tool name>=<local file path>[,...]]")
3839
hostagentCommand.Flags().Bool("progress", false, "Show provision script progress by monitoring cloud-init logs")
3940
return hostagentCommand
4041
}
@@ -96,6 +97,13 @@ func hostagentAction(cmd *cobra.Command, args []string) error {
9697
if nerdctlArchive != "" {
9798
opts = append(opts, hostagent.WithNerdctlArchive(nerdctlArchive))
9899
}
100+
provisionTool, err := cmd.Flags().GetString("provision-tool")
101+
if err != nil {
102+
return err
103+
}
104+
if provisionTool != "" {
105+
opts = append(opts, hostagent.WithProvisionTool(provisionTool))
106+
}
99107
showProgress, err := cmd.Flags().GetBool("progress")
100108
if err != nil {
101109
return err

cmd/limactl/prune.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ func locationsFromLimaYAML(y *limayaml.LimaYAML) map[string]limayaml.File {
113113
for _, f := range y.Containerd.Archives {
114114
locations[downloader.CacheKey(f.Location)] = f
115115
}
116+
for _, files := range y.ProvisionTool {
117+
for _, f := range files {
118+
locations[downloader.CacheKey(f.Location)] = f
119+
}
120+
}
116121
for _, f := range y.Firmware.Images {
117122
locations[downloader.CacheKey(f.Location)] = f.File
118123
}

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
)
6263

@@ -90,6 +91,7 @@ case "$NAME" in
9091
CHECKS["mount-path-with-spaces"]="1"
9192
CHECKS["provision-data"]="1"
9293
CHECKS["param-env-variables"]="1"
94+
CHECKS["provision-yq"]="1"
9395
CHECKS["set-user"]="1"
9496
;;
9597
"docker")
@@ -208,6 +210,11 @@ if [[ -n ${CHECKS["param-env-variables"]} ]]; then
208210
limactl shell "$NAME" test -e /tmp/param-user
209211
fi
210212

213+
if [[ -n ${CHECKS["provision-yq"]} ]]; then
214+
INFO 'Testing that /tmp/param-yq.json was created successfully on provision'
215+
limactl shell "$NAME" grep -q '"YQ": "yq"' /tmp/param-yq.json
216+
fi
217+
211218
if [[ -n ${CHECKS["set-user"]} ]]; then
212219
INFO 'Testing that user settings can be provided by lima.yaml'
213220
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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,58 @@ 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_MNT}"/provision/tool/yq
82+
if [ -x "${yq}" ]; then
83+
for expression in "${LIMA_CIDATA_MNT}"/provision/yq/*; do
84+
filename=$(basename "${expression}")
85+
path=$(deref "LIMA_CIDATA_YQ_PROVISION_${filename}_PATH")
86+
permissions=$(deref "LIMA_CIDATA_YQ_PROVISION_${filename}_PERMISSIONS")
87+
user=$(deref "LIMA_CIDATA_YQ_PROVISION_${filename}_USER")
88+
if ! sudo -iu "${user}" mkdir -p "$(dirname "${path}")"; then
89+
WARNING "Failed to create directory for ${path} (as user ${user})"
90+
CODE=1
91+
continue
92+
fi
93+
# Since CIDATA is mounted with dmode=700,fmode=700,
94+
# provision/tool/yq cannot be executed by non-root users,
95+
# and provision/yq/* files cannot be read by non-root users.
96+
if [ -f "${path}" ]; then
97+
INFO "Updating ${path}"
98+
# Relies on the fact that yq does not change the owner of the existing file.
99+
if ! "${yq}" --inplace --from-file "${expression}" "${path}"; then
100+
WARNING "Failed to update ${path} (as user ${user})"
101+
CODE=1
102+
continue
103+
fi
104+
else
105+
# follow file extension
106+
case "${path}" in
107+
*.ini) output=ini ;;
108+
*.json) output=json ;;
109+
*.properties) output=properties ;;
110+
*.xml) output=xml ;;
111+
*.yaml | *.yml) output=yaml ;;
112+
*) output=auto ;;
113+
esac
114+
INFO "Creating ${path}"
115+
if ! "${yq}" --null-input --from-file "${expression}" --output-format "${output}" | sudo -iu "${user}" tee "${path}"; then
116+
WARNING "Failed to create ${path} (as user ${user})"
117+
CODE=1
118+
continue
119+
fi
120+
fi
121+
if ! sudo -iu "${user}" chmod "${permissions}" "${path}"; then
122+
WARNING "Failed to set permissions for ${path} (as user ${user})"
123+
CODE=1
124+
fi
125+
done
126+
else
127+
WARNING "${yq} is not executable, skipping yq provisioning"
128+
CODE=1
129+
fi
130+
fi
131+
80132
if [ -d "${LIMA_CIDATA_MNT}"/provision/system ]; then
81133
for f in "${LIMA_CIDATA_MNT}"/provision/system/*; do
82134
INFO "Executing $f"

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ 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}}_PATH={{$yqProvision.Path}}
30+
LIMA_CIDATA_YQ_PROVISION_{{$yqProvision.FileName}}_PERMISSIONS={{$yqProvision.Permissions}}
31+
LIMA_CIDATA_YQ_PROVISION_{{$yqProvision.FileName}}_USER={{$yqProvision.User}}
32+
{{- end}}
2833
LIMA_CIDATA_GUEST_INSTALL_PREFIX={{ .GuestInstallPrefix }}
2934
{{- if .Containerd.User}}
3035
LIMA_CIDATA_CONTAINERD_USER=1

pkg/cidata/cidata.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package cidata
55

66
import (
7+
"bytes"
78
"compress/gzip"
89
"context"
910
"errors"
@@ -328,6 +329,14 @@ func templateArgs(ctx context.Context, bootScripts bool, instDir, name string, i
328329
Permissions: *f.Permissions,
329330
})
330331
}
332+
if f.Mode == limayaml.ProvisionModeYQ {
333+
args.YQProvisions = append(args.YQProvisions, YQProvision{
334+
FileName: fmt.Sprintf("%08d", i),
335+
Path: *f.Path,
336+
Permissions: *f.Permissions,
337+
User: *f.User,
338+
})
339+
}
331340
}
332341

333342
return &args, nil
@@ -356,7 +365,7 @@ func GenerateCloudConfig(ctx context.Context, instDir, name string, instConfig *
356365
return os.WriteFile(filepath.Join(instDir, filenames.CloudConfig), config, 0o444)
357366
}
358367

359-
func GenerateISO9660(ctx context.Context, instDir, name string, instConfig *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, guestAgentBinary, nerdctlArchive string, vsockPort int, virtioPort string) error {
368+
func GenerateISO9660(ctx context.Context, instDir, name string, instConfig *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, guestAgentBinary, nerdctlArchive string, provisionTool map[string]string, vsockPort int, virtioPort string) error {
360369
args, err := templateArgs(ctx, true, instDir, name, instConfig, udpDNSLocalPort, tcpDNSLocalPort, vsockPort, virtioPort)
361370
if err != nil {
362371
return err
@@ -383,6 +392,11 @@ func GenerateISO9660(ctx context.Context, instDir, name string, instConfig *lima
383392
Path: fmt.Sprintf("provision/%s/%08d", f.Mode, i),
384393
Reader: strings.NewReader(*f.Content),
385394
})
395+
case limayaml.ProvisionModeYQ:
396+
layout = append(layout, iso9660util.Entry{
397+
Path: fmt.Sprintf("provision/%s/%08d", f.Mode, i),
398+
Reader: strings.NewReader(*f.Expression),
399+
})
386400
case limayaml.ProvisionModeBoot:
387401
continue
388402
case limayaml.ProvisionModeAnsible:
@@ -432,6 +446,28 @@ func GenerateISO9660(ctx context.Context, instDir, name string, instConfig *lima
432446
Reader: nftgzR,
433447
})
434448
}
449+
for name, path := range provisionTool {
450+
f, err := os.Open(path)
451+
if err != nil {
452+
return err
453+
}
454+
buf := new(bytes.Buffer)
455+
_, err = io.Copy(buf, f)
456+
if err != nil {
457+
_ = f.Close()
458+
return err
459+
}
460+
_ = f.Close()
461+
layout = append(layout, iso9660util.Entry{
462+
// Place it at "provision/tool/<name>" instead of "util/<name>".
463+
// Because "util" is in PATH while "boot.sh" is executing,
464+
// that may override <name> with the Linux distribution providing binaries in VM.
465+
// If some provision scripts install <name> from the distribution,
466+
// it may lead to unexpected behavior for the provision scripts.
467+
Path: fmt.Sprintf("provision/tool/%s", name),
468+
Reader: buf,
469+
})
470+
}
435471

436472
if args.VMType == limayaml.WSL2 {
437473
layout = append(layout, iso9660util.Entry{

pkg/cidata/template.go

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

61+
type YQProvision struct {
62+
FileName string
63+
Path string
64+
Permissions string
65+
User string
66+
}
67+
6168
type Disk struct {
6269
Name string
6370
Device string
@@ -93,6 +100,7 @@ type TemplateArgs struct {
93100
Param map[string]string
94101
BootScripts bool
95102
DataFiles []DataFile
103+
YQProvisions []YQProvision
96104
DNSAddresses []string
97105
CACerts CACerts
98106
HostHomeMountPoint string

pkg/driver/vz/vz_driver_darwin.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ var knownYamlProperties = []string{
5757
"Probes",
5858
"PropagateProxyEnv",
5959
"Provision",
60+
"ProvisionTool",
6061
"Rosetta",
6162
"SSH",
6263
"TimeZone",

pkg/driver/wsl2/wsl_driver_windows.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ var knownYamlProperties = []string{
4040
"Probes",
4141
"PropagateProxyEnv",
4242
"Provision",
43+
"ProvisionTool",
4344
"SSH",
4445
"VMType",
4546
}

0 commit comments

Comments
 (0)