From 7d9a280a67c3983ddd218e2513dec6b892505005 Mon Sep 17 00:00:00 2001 From: Jussi Nummelin Date: Thu, 18 May 2023 13:31:11 +0000 Subject: [PATCH] Add support for write_files instructions --- lib/tiny-cloud/user-data/alpine-config | 98 +++++++++++++++++++++----- tests/tiny-cloud-alpine.test | 66 +++++++++++++++++ 2 files changed, 148 insertions(+), 16 deletions(-) diff --git a/lib/tiny-cloud/user-data/alpine-config b/lib/tiny-cloud/user-data/alpine-config index ec9f9ea..3dcd64c 100644 --- a/lib/tiny-cloud/user-data/alpine-config +++ b/lib/tiny-cloud/user-data/alpine-config @@ -3,25 +3,31 @@ # shellcheck shell=sh INIT_ACTIONS_MAIN="$(insert_after set_hostname \ - "userdata_bootcmd userdata_ntp userdata_apk_cache userdata_apk_repositories userdata_packages" \ + "userdata_bootcmd userdata_write_files userdata_ntp userdata_apk_cache userdata_apk_repositories userdata_packages" \ $INIT_ACTIONS_MAIN)" INIT_ACTIONS_FINAL="$INIT_ACTIONS_FINAL userdata_runcmd" +get_userdata() { + IFS="/" + yx -f "$TINY_CLOUD_VAR/user-data" $1 2>/dev/null + unset IFS +} + init__userdata_bootcmd() { # run bootcmd - local bootcmds="$(imds user-data/bootcmd)" + local bootcmds="$(get_userdata bootcmd)" for i in $bootcmds; do - local cmd="$(imds user-data/bootcmd/"$i")" + local cmd="$(get_userdata bootcmd/"$i")" sh -c "$cmd" done } init__userdata_ntp() { - local ntp_enabled="$(imds user-data/ntp/enabled)" + local ntp_enabled="$(get_userdata ntp/enabled)" if [ "$ntp_enabled" != "yes" ] && [ "$ntp_enabled" != "true" ]; then return fi - local ntp_client="$(imds user-data/ntp/ntp_client)" + local ntp_client="$(get_userdata ntp/ntp_client)" local svc= pkg= case "$ntp_client" in busybox) @@ -46,7 +52,7 @@ init__userdata_ntp() { } init__userdata_apk_cache() { - local cache="$(imds user-data/apk/cache)" + local cache="$(get_userdata apk/cache)" if [ -z "$cache" ]; then return fi @@ -60,7 +66,7 @@ init__userdata_apk_cache() { } init__userdata_apk_cache() { - local cache="$(imds user-data/apk/cache)" + local cache="$(get_userdata apk/cache)" if [ -z "$cache" ]; then return fi @@ -74,12 +80,12 @@ init__userdata_apk_cache() { } init__userdata_apk_repositories() { - local repositories="$(imds user-data/apk/repositories)" + local repositories="$(get_userdata apk/repositories)" mkdir -p "$ROOT"/etc/apk for r in $repositories; do - local baseurl="$(imds user-data/apk/repositories/$r/base_url)" - local repos="$(imds user-data/apk/repositories/$r/repos)" - local version="$(imds user-data/apk/repositories/$r/version)" + local baseurl="$(get_userdata apk/repositories/$r/base_url)" + local repos="$(get_userdata apk/repositories/$r/repos)" + local version="$(get_userdata apk/repositories/$r/version)" if [ -z "$version" ]; then local version_id=$( . "$ROOT"/etc/os-release 2>/dev/null && echo "$VERSION_ID") case "$version_id" in @@ -91,17 +97,17 @@ init__userdata_apk_repositories() { baseurl="${baseurl%/}/$version" fi for repo in $repos; do - local uri="${baseurl%/}/$(imds user-data/apk/repositories/$r/repos/$repo)" + local uri="${baseurl%/}/$(get_userdata apk/repositories/$r/repos/$repo)" add_once "$ROOT"/etc/apk/repositories "$uri" done done } init__userdata_packages() { - local packages="$(imds user-data/packages)" + local packages="$(get_userdata packages)" local pkgs= for i in $packages; do - pkgs="$pkgs $(imds user-data/packages/$i)" + pkgs="$pkgs $(get_userdata packages/$i)" done if [ -n "$pkgs" ]; then $MOCK apk add $pkgs @@ -109,9 +115,69 @@ init__userdata_packages() { } init__userdata_runcmd() { - local runcmds="$(imds user-data/runcmd)" + local runcmds="$(get_userdata runcmd)" for i in $runcmds; do - local cmd="$(imds user-data/runcmd/$i)" + local cmd="$(get_userdata runcmd/$i)" sh -c "$cmd" done } + +# write_file +write_file() { + # Defaults used are the same as for full cloud-init "spec": + # https://cloudinit.readthedocs.io/en/latest/reference/modules.html#write-files + local path="$1" + local mode="${2:-0644}" + local owner="${3:-root:root}" + local encoding="${4:-text/plain}" + local append="${5:-false}" + + if [ "$append" != "true" ] && [ "$append" != "false" ]; then + log err "append must be true or false" + return + fi + + local tmpfile="$(mktemp $TINY_CLOUD_VAR/user-data.write_files.XXXXXX)" + + case "$encoding" in + gzip|gz|gz+base64|gzip+base64|gz+b64|gzip+b64) + base64 -d | gzip -d > "$tmpfile" + ;; + base64|b64) + base64 -d > "$tmpfile" + ;; + text/plain) + cat > "$tmpfile" + ;; + esac + + if [ "$append" = "true" ]; then + cat "$tmpfile" >> "$path" + else + cat "$tmpfile" > "$path" + fi + rm -f "$tmpfile" + + chmod "$mode" "$path" + # mocked as we do not know which users we could use in testing + # this way we can check the proper invocation at least + $MOCK chown "$owner" "$path" +} + +init__userdata_write_files() { + local files="$(get_userdata write_files)" + + for i in $files; do + local path="$(get_userdata write_files/$i/path)" + if [ -z "$path" ]; then + continue + fi + + mkdir -p "$(dirname "$ROOT/$path")" + get_userdata write_files/$i/content | write_file "$ROOT/$path" \ + "$(get_userdata write_files/$i/permissions)" \ + "$(get_userdata write_files/$i/owner)" \ + "$(get_userdata write_files/$i/encoding)" \ + "$(get_userdata write_files/$i/append)" + done +} diff --git a/tests/tiny-cloud-alpine.test b/tests/tiny-cloud-alpine.test index 0d1e82d..26ed76b 100755 --- a/tests/tiny-cloud-alpine.test +++ b/tests/tiny-cloud-alpine.test @@ -12,6 +12,7 @@ init_tests \ set_network_config_network_interfaces \ set_network_config_auto \ userdata_bootcmd \ + userdata_write_files \ userdata_ntp \ userdata_ntp_busybox \ userdata_ntp_openntpd \ @@ -240,3 +241,68 @@ userdata_runcmd_body() { -o match:"^foo$" -o match:"^bar$" \ tiny-cloud final } + +userdata_write_files_body() { + fake_userdata_nocloud <<-EOF + #alpine-config + write_files: + - path: /etc/motd + content: | + Hello world + - path: /etc/foo + encoding: text/plain + permissions: '0755' + content: | + Hello world + - path: /etc/bar + owner: foo:bar + content: | + Hello world + - path: /etc/gzipped + encoding: gzip + content: !!binary | + H4sIAAAAAAAAA/NIzcnJVyjPL8pJ4QIA1eA5twwAAAA= + - path: /foo/bar/hello + content: | + Hello world + - path: /foo/bar/appended + content: | + Hello + - path: /foo/bar/appended + append: true + content: | + world + EOF + # fetch user-data + atf_check -e ignore -o ignore tiny-cloud net + + atf_check \ + -e match:"userdata_write_files: done" \ + -o match:"chown foo:bar.*etc/bar" \ + -o match:"chown root:root.*etc/motd" \ + tiny-cloud main + + if [ "$(cat etc/motd)" != "Hello world" ]; then + atf_fail "content of etc/motd was not 'Hello world'" + fi + # check that etc/motd permissions are the defaults + atf_check -o match:"644" stat -c %a etc/motd + + if [ "$(cat etc/foo)" != "Hello world" ]; then + atf_fail "content of etc/foo was not 'Hello world'" + fi + atf_check -o match:"755" stat -c %a etc/foo + + if [ "$(cat etc/gzipped)" != "Hello world" ]; then + atf_fail "content of etc/foo was not 'Hello world'" + fi + + if [ "$(cat foo/bar/hello)" != "Hello world" ]; then + atf_fail "content of foo/bar/hello was not 'Hello world'" + fi + + atf_check diff -u foo/bar/appended - <<-EOF + Hello + world + EOF +}