From 81f7fdaf5be74d2c639b3d204e98676beac4061c Mon Sep 17 00:00:00 2001 From: Jan Dubois Date: Fri, 6 Sep 2024 11:16:46 -0700 Subject: [PATCH] Add limayaml param settings to provisioning script environment They will be prefixed with `PARAM_`, so `param.FOO` becomes `PARAM_FOO`. This is useful because parameter substitution happens when a template is instantiated, so `[ "{{.Param.ROOTFUL}}" = true ]` becomes `[ "true" = true ]` in the cloud-init-output.log. This mechanism also works better when the parameter contains quotes, which would break a simplistic `FOO="{{.Param.FOO}}"`. Signed-off-by: Jan Dubois --- examples/default.yaml | 6 +++ hack/test-templates.sh | 11 +++++ hack/test-templates/test-misc.yaml | 23 +++++++++- pkg/cidata/cidata.TEMPLATE.d/boot.sh | 7 ++- pkg/cidata/cidata.TEMPLATE.d/param.env | 3 ++ pkg/cidata/cidata.TEMPLATE.d/user-data | 6 +++ pkg/cidata/cidata.go | 1 + pkg/cidata/template.go | 1 + pkg/hostagent/requirements.go | 45 ++++++++++++++++++- pkg/limayaml/validate.go | 24 ++++++++-- pkg/limayaml/validate_test.go | 1 + .../content/en/docs/dev/internals/_index.md | 1 + 12 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 pkg/cidata/cidata.TEMPLATE.d/param.env diff --git a/examples/default.yaml b/examples/default.yaml index 8c31da87990d..5eb02b54ba87 100644 --- a/examples/default.yaml +++ b/examples/default.yaml @@ -237,6 +237,7 @@ containerd: # playbook: playbook.yaml # Probe scripts to check readiness. +# The scripts run in user mode. They must start with a '#!' line. # The scripts can use the following template variables: {{.Home}}, {{.UID}}, {{.User}}, and {{.Param.Key}} # 🟢 Builtin default: null # probes: @@ -422,7 +423,12 @@ networks: # KEY: value # Defines variables used for customizing the functionality. +# Key names must start with an uppercase or lowercase letter followed by +# any number of letters, numbers, and underscores. +# Values must not contain non-printable characters except for spaces and tabs. # These variables can be referenced as {{.Param.Key}} in lima.yaml. +# In provisioning scripts and probes they are also available as predefined +# environment variables, prefixed with "PARAM` (so `Key` → `$PARAM_Key`). # param: # Key: value diff --git a/hack/test-templates.sh b/hack/test-templates.sh index 28dd1490fc30..e36eafe8a6e0 100755 --- a/hack/test-templates.sh +++ b/hack/test-templates.sh @@ -36,6 +36,7 @@ declare -A CHECKS=( ["user-v2"]="" ["mount-path-with-spaces"]="" ["provision-ansible"]="" + ["param-env-variables"]="" ) case "$NAME" in @@ -64,6 +65,7 @@ case "$NAME" in CHECKS["snapshot-offline"]="1" CHECKS["mount-path-with-spaces"]="1" CHECKS["provision-ansible"]="1" + CHECKS["param-env-variables"]="1" ;; "net-user-v2") CHECKS["port-forwards"]="" @@ -152,6 +154,15 @@ if [[ -n ${CHECKS["provision-ansible"]} ]]; then limactl shell "$NAME" test -e /tmp/ansible fi +if [[ -n ${CHECKS["param-env-variables"]} ]]; then + INFO 'Testing that PARAM env variables are exported to all types of provisioning scripts and probes' + limactl shell "$NAME" test -e /tmp/param-boot + limactl shell "$NAME" test -e /tmp/param-dependency + limactl shell "$NAME" test -e /tmp/param-probe + limactl shell "$NAME" test -e /tmp/param-system + limactl shell "$NAME" test -e /tmp/param-user +fi + INFO "Testing proxy settings are imported" got=$(limactl shell "$NAME" env | grep FTP_PROXY) # Expected: FTP_PROXY is set in addition to ftp_proxy, localhost is replaced diff --git a/hack/test-templates/test-misc.yaml b/hack/test-templates/test-misc.yaml index 2bf6cc8bd1ae..2f2c17cae4be 100644 --- a/hack/test-templates/test-misc.yaml +++ b/hack/test-templates/test-misc.yaml @@ -2,7 +2,7 @@ # - disk # - (More to come) # -# This template requires Lima v0.14.0 or later. +# This template requires Lima v1.0.0-alpha.0 or later. images: # Try to use release-yyyyMMdd image if available. Note that release-yyyyMMdd will be removed after several months. - location: "https://cloud-images.ubuntu.com/releases/22.04/release-20220902/ubuntu-22.04-server-cloudimg-amd64.img" @@ -26,9 +26,30 @@ mounts: - location: "/tmp/lima" writable: true +param: + BOOT: boot + DEPENDENCY: dependency + PROBE: probe + SYSTEM: system + USER: user + provision: - mode: ansible playbook: ./hack/ansible-test.yaml +- mode: boot + script: "touch /tmp/param-$PARAM_BOOT" +- mode: dependency + script: "touch /tmp/param-$PARAM_DEPENDENCY" +- mode: system + script: "touch /tmp/param-$PARAM_SYSTEM" +- mode: user + script: "touch /tmp/param-$PARAM_USER" + +probes: +- mode: readiness + script: | + #!/bin/sh + touch /tmp/param-$PARAM_PROBE # in order to use this example, you must first create the disk "data". run: # $ limactl disk create data --size 10G diff --git a/pkg/cidata/cidata.TEMPLATE.d/boot.sh b/pkg/cidata/cidata.TEMPLATE.d/boot.sh index 5d9c867f5a4e..ab65c7192b71 100644 --- a/pkg/cidata/cidata.TEMPLATE.d/boot.sh +++ b/pkg/cidata/cidata.TEMPLATE.d/boot.sh @@ -10,7 +10,9 @@ WARNING() { } # shellcheck disable=SC2163 -while read -r line; do export "$line"; done <"${LIMA_CIDATA_MNT}"/lima.env +while read -r line; do [ -n "$line" ] && export "$line"; done <"${LIMA_CIDATA_MNT}"/lima.env +# shellcheck disable=SC2163 +while read -r line; do [ -n "$line" ] && export "$line"; done <"${LIMA_CIDATA_MNT}"/param.env # shellcheck disable=SC2163 while read -r line; do @@ -61,12 +63,13 @@ if [ -d "${LIMA_CIDATA_MNT}"/provision.user ]; then if [ ! -f /sbin/openrc-run ]; then until [ -e "/run/user/${LIMA_CIDATA_UID}/systemd/private" ]; do sleep 3; done fi + params=$(grep -o '^PARAM_[^=]*' "${LIMA_CIDATA_MNT}"/param.env | paste -sd ,) for f in "${LIMA_CIDATA_MNT}"/provision.user/*; do INFO "Executing $f (as user ${LIMA_CIDATA_USER})" cp "$f" "${USER_SCRIPT}" chown "${LIMA_CIDATA_USER}" "${USER_SCRIPT}" chmod 755 "${USER_SCRIPT}" - if ! sudo -iu "${LIMA_CIDATA_USER}" "XDG_RUNTIME_DIR=/run/user/${LIMA_CIDATA_UID}" "${USER_SCRIPT}"; then + if ! sudo -iu "${LIMA_CIDATA_USER}" "--preserve-env=${params}" "XDG_RUNTIME_DIR=/run/user/${LIMA_CIDATA_UID}" "${USER_SCRIPT}"; then WARNING "Failed to execute $f (as user ${LIMA_CIDATA_USER})" CODE=1 fi diff --git a/pkg/cidata/cidata.TEMPLATE.d/param.env b/pkg/cidata/cidata.TEMPLATE.d/param.env new file mode 100644 index 000000000000..e003b1c4ad3c --- /dev/null +++ b/pkg/cidata/cidata.TEMPLATE.d/param.env @@ -0,0 +1,3 @@ +{{range $key, $val := .Param -}} +PARAM_{{ $key }}={{ $val }} +{{end -}} diff --git a/pkg/cidata/cidata.TEMPLATE.d/user-data b/pkg/cidata/cidata.TEMPLATE.d/user-data index 1f7c150f7650..255c898822e1 100644 --- a/pkg/cidata/cidata.TEMPLATE.d/user-data +++ b/pkg/cidata/cidata.TEMPLATE.d/user-data @@ -84,6 +84,12 @@ ca_certs: bootcmd: {{- range $cmd := $.BootCmds }} - | + # We need to embed the params.env as a here-doc because /mnt/lima-cidata is not yet mounted + while read -r line; do [ -n "$line" ] && export "$line"; done <<'EOT' + {{- range $key, $val := $.Param }} + PARAM_{{ $key }}={{ $val }} + {{- end }} + EOT {{- range $line := $cmd.Lines }} {{ $line }} {{- end }} diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go index 71096c1de76c..5742cc548d9d 100644 --- a/pkg/cidata/cidata.go +++ b/pkg/cidata/cidata.go @@ -140,6 +140,7 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort VirtioPort: virtioPort, Plain: *y.Plain, TimeZone: *y.TimeZone, + Param: y.Param, } firstUsernetIndex := limayaml.FirstUsernetIndex(y) diff --git a/pkg/cidata/template.go b/pkg/cidata/template.go index b9aa96188206..aedc5caaa3bc 100644 --- a/pkg/cidata/template.go +++ b/pkg/cidata/template.go @@ -73,6 +73,7 @@ type TemplateArgs struct { UDPDNSLocalPort int TCPDNSLocalPort int Env map[string]string + Param map[string]string DNSAddresses []string CACerts CACerts HostHomeMountPoint string diff --git a/pkg/hostagent/requirements.go b/pkg/hostagent/requirements.go index 952e1e6d8187..843a13eb54b4 100644 --- a/pkg/hostagent/requirements.go +++ b/pkg/hostagent/requirements.go @@ -41,9 +41,52 @@ func (a *HostAgent) waitForRequirements(label string, requirements []requirement return errors.Join(errs...) } +// prefixExportParam will modify a script to be executed by ssh.ExecuteScript so that it exports +// all the variables from /mnt/lima-cidata/param.env before invoking the actual interpreter. +// +// * The script is executed in user mode, so needs to read the file using `sudo`. +// +// - `sudo cat param.env | while …; do export …; done` does not work because the piping +// creates a subshell, and the exported variables are not visible to the parent process. +// +// - The `<<<"$string"` redirection is not available on alpine-lima, where /bin/bash is +// just a wrapper around busybox ash. +// +// A script that will start with `#!/usr/bin/env ruby` will be modified to look like this: +// +// while read -r line; do +// [ -n "$line" ] && export "$line" +// done<--.tar.gz`](https://github.com/containerd/nerdctl/releases) - `boot.sh`: Boot script