Skip to content

Commit bb0337b

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 --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 `--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 # 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 d4f4911 commit bb0337b

File tree

24 files changed

+512
-46
lines changed

24 files changed

+512
-46
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: 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_MNT}"/provision.tool/yq
82+
if [ -x "${yq}" ]; 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}" --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: 38 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,15 @@ 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+
Format: *f.Format,
336+
Path: *f.Path,
337+
Permissions: *f.Permissions,
338+
User: *f.User,
339+
})
340+
}
331341
}
332342

333343
return &args, nil
@@ -356,7 +366,7 @@ func GenerateCloudConfig(ctx context.Context, instDir, name string, instConfig *
356366
return os.WriteFile(filepath.Join(instDir, filenames.CloudConfig), config, 0o444)
357367
}
358368

359-
func GenerateISO9660(ctx context.Context, instDir, name string, instConfig *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, guestAgentBinary, nerdctlArchive string, vsockPort int, virtioPort string) error {
369+
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 {
360370
args, err := templateArgs(ctx, true, instDir, name, instConfig, udpDNSLocalPort, tcpDNSLocalPort, vsockPort, virtioPort)
361371
if err != nil {
362372
return err
@@ -383,6 +393,11 @@ func GenerateISO9660(ctx context.Context, instDir, name string, instConfig *lima
383393
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
384394
Reader: strings.NewReader(*f.Content),
385395
})
396+
case limayaml.ProvisionModeYQ:
397+
layout = append(layout, iso9660util.Entry{
398+
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
399+
Reader: strings.NewReader(*f.Expression),
400+
})
386401
case limayaml.ProvisionModeBoot:
387402
continue
388403
case limayaml.ProvisionModeAnsible:
@@ -432,6 +447,28 @@ func GenerateISO9660(ctx context.Context, instDir, name string, instConfig *lima
432447
Reader: nftgzR,
433448
})
434449
}
450+
for name, path := range provisionTool {
451+
f, err := os.Open(path)
452+
if err != nil {
453+
return err
454+
}
455+
buf := new(bytes.Buffer)
456+
_, err = io.Copy(buf, f)
457+
if err != nil {
458+
_ = f.Close()
459+
return err
460+
}
461+
_ = f.Close()
462+
layout = append(layout, iso9660util.Entry{
463+
// Place it at "provision.tool/<name>" instead of "util/<name>".
464+
// Because "util" is in PATH while "boot.sh" is executing,
465+
// that may override <name> with the Linux distribution providing binaries in VM.
466+
// If some provision scripts install <name> from the distribution,
467+
// it may lead to unexpected behavior for the provision scripts.
468+
Path: fmt.Sprintf("provision.tool/%s", name),
469+
Reader: buf,
470+
})
471+
}
435472

436473
if args.VMType == limayaml.WSL2 {
437474
layout = append(layout, iso9660util.Entry{

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