diff --git a/lib/tiny-cloud/common b/lib/tiny-cloud/common index cb15325..b0500a7 100644 --- a/lib/tiny-cloud/common +++ b/lib/tiny-cloud/common @@ -9,7 +9,7 @@ : "${TINY_CLOUD_VAR:=$ROOT/var/lib/cloud}" log() { - local facility="kern" + local facility="local7" local stderr local tag=$(basename "$0") while [ "${1#-}" != "$1" ]; do diff --git a/lib/tiny-cloud/init b/lib/tiny-cloud/init index 7e590ff..cf51e98 100644 --- a/lib/tiny-cloud/init +++ b/lib/tiny-cloud/init @@ -8,19 +8,28 @@ : "${SKIP_INIT_ACTIONS:=}" : "${HOTPLUG_TYPE:=mdev}" -# TODO: default phase actions +### default phase actions (without leading 'init__') -# ensure existence of output directories -[ ! -d "$TINY_CLOUD_LOGS" ] && mkdir -p "$TINY_CLOUD_LOGS" -[ ! -d "$TINY_CLOUD_VAR" ] && mkdir -p "$TINY_CLOUD_VAR" +INIT_ACTIONS_EARLY=" + expand_root + install_hotplugs +" +INIT_ACTIONS_MAIN=" + save_userdata + set_hostname + set_ssh_keys +" +INIT_ACTIONS_FINAL=" + run_userdata +" -### NOTE: init-early functions... +# try to ensure existence of output directories, but otherwise don't panic +[ ! -d "$TINY_CLOUD_LOGS" ] && mkdir -p "$TINY_CLOUD_LOGS" || true +[ ! -d "$TINY_CLOUD_VAR" ] && mkdir -p "$TINY_CLOUD_VAR" || true -expand_root() { - skip_action expand_root && return - - echo "Expanding Root Volume/ Partition" +### init-early functions... +init__expand_root() { local dev=$(awk '$2 == "/" {print $1}' "$ROOT"/proc/mounts) local partition=$(cat "$ROOT/sys/class/block/${dev#/dev/}/partition" 2>/dev/null) @@ -35,36 +44,38 @@ expand_root() { $MOCK resize2fs "$dev" } -install_hotplugs() { - skip_action install_hotplugs && return +init__install_hotplugs() { + local level result rc=0 + [ ! -n "$HOTPLUG_MODULES" ] && return - - echo "Installing Cloud Hotplugs" - - local result rc=0 - if [ -f "$LIBDIR/tiny-cloud/$HOTPLUG_TYPE" ]; then - . "$LIBDIR/tiny-cloud/$HOTPLUG_TYPE" + . "$LIBDIR/tiny-cloud/$HOTPLUG_TYPE" fi + printf ' :' >&2 for module in $HOTPLUG_MODULES; do - result='-' - printf " $module" + result='?' + level='err' + printf "$module" >&2 + log info "$phase $ACTION $module" if type "mod__$module" | grep -q -w "function"; then - "mod__$module" && result='+' || { result='!'; rc=1; } + if "mod__$module"; then + result='+' + level='info' + else + result='!' + rc=1 + fi fi - printf "($result)" + printf '(%s) ' $result >&2 + log "$level" "$phase $ACTION $module ($result)" done return $rc } -### NOTE: init-main functions - -set_hostname() { - skip_action set_hostname && return - - echo "Setting Instance Hostname" +### init-main functions +init__set_hostname() { local fqdn=$(imds @hostname) local host="${fqdn%%\.*}" @@ -74,11 +85,7 @@ set_hostname() { echo -e "127.0.1.1\t$fqdn $host" >> "$ROOT"/etc/hosts } -set_ssh_keys() { - skip_action set_ssh_keys && return - - echo "Installing SSH Keys for $CLOUD_USER User" - +init__set_ssh_keys() { local user="$CLOUD_USER" local pwent="$(getent passwd "$user")" local group=$(echo "$pwent" | cut -d: -f4) @@ -96,87 +103,62 @@ set_ssh_keys() { imds @ssh-keys > "$keys_file" } -save_userdata() { - skip_action save_userdata && return - - # TODO: this trips save_userdata_* and run_userdata tests: "stdout not empty" - #echo "Saving Instance UserData" - +init__save_userdata() { local userdata="$TINY_CLOUD_VAR/user-data" local tmpfile=$(mktemp "$userdata.XXXXXX") - local cmd imds -e @userdata > "$tmpfile" - cmd="cat" - if ! skip_action decompress_userdata; then - if printf '\037\213\010' | cmp -s -n 3 "$tmpfile"; then - gzip -dc "$tmpfile" > "$userdata" - elif printf 'BZh' | cmp -s -n 3 "$tmpfile"; then - bzip2 -dc "$tmpfile" > "$userdata" - elif printf '\375\067\172\130\132\000' | cmp -s -n 6 "$tmpfile"; then - unxz -c "$tmpfile" > "$userdata" - elif printf '\135\000\000' | cmp -s -n 3 "$tmpfile"; then - lzma -dc "$tmpfile" > "$userdata" - elif printf '\211\114\132' | cmp -s -n 3 "$tmpfile"; then - lzop -dc "$tmpfile" > "$userdata" - elif printf '\004\042\115\030' | cmp -s -n 4 "$tmpfile"; then - lz4 -dc "$tmpfile" > "$userdata" - elif printf '(\265/\375' | cmp -s -n 4 "$tmpfile"; then - zstd -dc "$tmpfile" > "$userdata" - else - cp "$tmpfile" "$userdata" - fi + if printf '\037\213\010' | cmp -s -n 3 "$tmpfile"; then + gzip -dc "$tmpfile" > "$userdata" + elif printf 'BZh' | cmp -s -n 3 "$tmpfile"; then + bzip2 -dc "$tmpfile" > "$userdata" + elif printf '\375\067\172\130\132\000' | cmp -s -n 6 "$tmpfile"; then + unxz -c "$tmpfile" > "$userdata" + elif printf '\135\000\000' | cmp -s -n 3 "$tmpfile"; then + lzma -dc "$tmpfile" > "$userdata" + elif printf '\211\114\132' | cmp -s -n 3 "$tmpfile"; then + lzop -dc "$tmpfile" > "$userdata" + elif printf '\004\042\115\030' | cmp -s -n 4 "$tmpfile"; then + lz4 -dc "$tmpfile" > "$userdata" + elif printf '(\265/\375' | cmp -s -n 4 "$tmpfile"; then + zstd -dc "$tmpfile" > "$userdata" else cp "$tmpfile" "$userdata" fi rm "$tmpfile" } -### TODO: init-final functions - -run_userdata() { - skip_action run_userdata && return - if [ $(userdata_type) != "script" ]; then - echo "UserData is not executable" - return - fi - - echo "Executing UserData Script" +### init-final functions +init__run_userdata() { local log="$TINY_CLOUD_LOGS/user-data.log" local exit="$TINY_CLOUD_LOGS/user-data.exit" local userdata="$TINY_CLOUD_VAR/user-data" + if [ $(userdata_type) != "script" ]; then + printf '(Not Executable) ' >&2 + log info "$phase $ACTION - not exectutable" + return + fi + chmod +x "$userdata" { "$userdata" 2>& 1; echo $? > "$exit"; } | tee "$log" return $(cat "$exit") } +### potentially override the above, per cloud + # load cloud-specific init functions / vars if [ -f "$LIBDIR/tiny-cloud/cloud/$CLOUD/init" ]; then - . "$LIBDIR/tiny-cloud/cloud/$CLOUD/init" + . "$LIBDIR/tiny-cloud/cloud/$CLOUD/init" fi -# TODO: load user-data type-specific init functions / vars - -### non-overrideable functions - -# should we skip this action? -skip_action() { - local action="$1" - for i in $SKIP_INIT_ACTIONS; do - if [ "$i" = "$action" ]; then - printf " SKIPPING" - return 0 - fi - done - return 1 -} +# this should be non-overrideable, but need this before we... userdata_type() { if [ -f "$TINY_CLOUD_VAR/user-data" ]; then - header=$(head -n1 "$TINY_CLOUD_VAR/user-data") + header=$(head -n1 "$TINY_CLOUD_VAR/user-data" | sed -e 's/[[:space:]].*//g') case "$header" in '#cloud-config') echo cloud-config;; '#!'*) echo script;; @@ -186,3 +168,6 @@ userdata_type() { echo missing fi } + +# ...load user-data type-specific init functions / vars +# TODO diff --git a/sbin/tiny-cloud b/sbin/tiny-cloud index aa6884a..c9f89a0 100755 --- a/sbin/tiny-cloud +++ b/sbin/tiny-cloud @@ -10,22 +10,19 @@ set -e usage() { cat <&2 exit 1 fi @@ -36,11 +33,22 @@ while true; do -b|--bootstrap) shift case "$1" in complete) # indicate bootstrap is done - bootstrap_complete;; + init__bootstrap_complete + log warn 'bootstrap marked complete';; incomplete) # indicate bootstrap isn't done - bootstrap_incomplete;; + bootstrap_incomplete + log warn 'bootstrap marked incomplete';; *) usage >&2; exit 1;; esac + printf ' bootstrap marked "%s"\n' "$1" >&2 + exit 0;; + -s|--setup) # just openrc for now + for phase in -early '' -final; do + rc-update -a del "tiny-cloud$phase" || true + done + rc-update add tiny-cloud-early boot + rc-update add tiny-cloud default + rc-update add tiny-cloud-final default exit 0;; --) shift; break;; *) usage >&2; exit 1;; @@ -58,32 +66,56 @@ esac # is initial bootstrap already done? if [ -f "$TINY_CLOUD_VAR/.bootstrap-complete" ]; then - log -s "Already bootstrapped" + printf ' already bootstrapped\n' >&2 + log info "$phase - already bootstrapped" exit 0; fi -### default phase actions - -# TODO? represent as vars containing lists of funcs? - -early() { - expand_root - install_hotplugs -} - -main() { - save_userdata - set_hostname - set_ssh_keys -} - -final() { - run_userdata - bootstrap_complete -} - # load init functions . "$LIBDIR/tiny-cloud/init" -# TODO? for loop over list of funcs? -- better for ebegin/eend-ish output -echo $phase "$@" +### non-overrideable stuff + +# should we skip this action? +skip_action() { + local action="$1" + for i in $SKIP_INIT_ACTIONS; do + [ "$i" = "$action" ] && return 0 + done + return 1 +} + +# mandatory final action... +INIT_ACTIONS_FINAL="${INIT_ACTIONS_FINAL} bootstrap_complete" + +### let's do stuff! + +case "$phase" in + early) INIT_ACTIONS="$INIT_ACTIONS_EARLY";; + main) INIT_ACTIONS="$INIT_ACTIONS_MAIN";; + final) INIT_ACTIONS="$INIT_ACTIONS_FINAL";; + *) usage >&2; exit 1 +esac + +for ACTION in $INIT_ACTIONS; do + if skip_action "$ACTION"; then + printf '\n -- %s : [SKIP]' $ACTION >&2 + log warn "$phase - $ACTION - SKIPPED" + continue + fi + printf '\n ++ %s ' $ACTION >&2 + log info "$phase - $ACTION - START" + RESULT="UNKNOWN" + LEVEL="err" + if type "init__$ACTION" | grep -q -w "function"; then + if "init__$ACTION" "$@"; then + RESULT="DONE" + LEVEL="info" + else + RESULT="FAIL" + fi + fi + printf ': [%s]' $RESULT >&2 + log "$LEVEL" "$phase - $ACTION - $RESULT" +done +echo >&2 diff --git a/tests/init-early.test b/tests/init-early.test index b1ffae5..d640ace 100755 --- a/tests/init-early.test +++ b/tests/init-early.test @@ -9,33 +9,21 @@ lib="$srcdir"/lib/tiny-cloud/init init_tests \ expand_root \ expand_root_partition \ - expand_root_skip \ - install_hotplugs_skip \ install_hotplugs_fail PROVIDERS="aws azure gcp nocloud oci" expand_root_body() { - fake_bin test-expand-root <<-EOF - #!/bin/sh - . "$lib" - expand_root - EOF mkdir proc echo "/dev/xvda / ext4 rw,noatime 0 0" > proc/mounts for provider in $PROVIDERS; do CLOUD="$provider" atf_check \ -o match:"resize2fs /dev/xvda" \ - test-expand-root + sh -c ". $lib; init__expand_root" done } expand_root_partition_body() { - fake_bin test-expand-root <<-EOF - #!/bin/sh - . "$lib" - expand_root - EOF mkdir -p proc sys/class/block \ sys/devices/pci0000:00/0000:00:1b.0/0000:01:00.0/nvme/nvme0/nvme0n1/nvme0n1p2 \ sys/devices/pci0000:00/0000:00:1b.0/0000:01:00.0/nvme/nvme0/nvme0n1/device @@ -49,46 +37,12 @@ expand_root_partition_body() { -o match:"sfdisk .*/dev/nvme0n1" \ -o match:"partx .*/dev/nvme0n1" \ -o match:"resize2fs /dev/nvme0n1p2" \ - test-expand-root - done -} - -expand_root_skip_body() { - fake_bin test-expand-root <<-EOF - #!/bin/sh - SKIP_INIT_ACTIONS=expand_root - . "$lib" - expand_root - EOF - for provider in $PROVIDERS; do - CLOUD="$provider" atf_check \ - -o match:'^ SKIPPING$' \ - test-expand-root - done -} - -install_hotplugs_skip_body() { - fake_bin test-install-hotplugs <<-EOF - #!/bin/sh - SKIP_INIT_ACTIONS=install_hotplugs - . "$lib" - install_hotplugs - EOF - for provider in $PROVIDERS; do - CLOUD="$provider" atf_check \ - -o match:'^ SKIPPING$' \ - test-install-hotplugs + sh -c ". $lib; init__expand_root" done } install_hotplugs_fail_body() { - fake_bin test-install-hotplugs <<-EOF - #!/bin/sh - HOTPLUG_MODULES="vnic_eth_hotplug" - . "$lib" - install_hotplugs - EOF CLOUD=aws atf_check -s not-exit:0 \ - -o match:"^ vnic_eth_hotplug\(!\)$" \ - test-install-hotplugs + -e match:"vnic_eth_hotplug\(!\)" \ + sh -c ". $lib; HOTPLUG_MODULES='vnic_eth_hotplug'; init__install_hotplugs" } diff --git a/tests/init-final.test b/tests/init-final.test index 08b496f..2360965 100755 --- a/tests/init-final.test +++ b/tests/init-final.test @@ -38,10 +38,10 @@ run_userdata_body() { echo "hello from user-data" EOF CLOUD="nocloud" atf_check \ - sh -c ". \"$lib\"; save_userdata" + sh -c ". \"$lib\"; init__save_userdata" CLOUD="nocloud" atf_check \ -o match:"hello from user-data" \ - sh -c ". \"$lib\"; run_userdata" + sh -c ". \"$lib\"; init__run_userdata" grep "hello from user-data" var/log/user-data.log || atf_fail "user-data.log failed" grep -w "0" var/log/user-data.exit || atf_fail "user-data.exit failed" } diff --git a/tests/init-main.test b/tests/init-main.test index 6a27261..0cfe770 100755 --- a/tests/init-main.test +++ b/tests/init-main.test @@ -42,16 +42,8 @@ set_hostname_body() { CLOUD="nocloud" atf_check \ -o match:"hostname.*-F $PWD/etc/hostname" \ - sh -c ". \"$lib\"; set_hostname" + sh -c ". \"$lib\"; init__set_hostname" atf_check -o match:"^myhostname$" cat etc/hostname - - rm etc/hostname - CLOUD="nocloud" atf_check \ - -o not-match:"hostname.*-F $PWD/etc/hostname" \ - sh -c "SKIP_INIT_ACTIONS=set_hostname ; . \"$lib\"; set_hostname" - if test -f etc/hostname; then - atf_fail "etc/hostname should not exist" - fi } set_ssh_keys_body() { @@ -66,7 +58,7 @@ set_ssh_keys_body() { EOF CLOUD="nocloud" atf_check \ -o match:"chown.*/\.ssh" \ - sh -c ". \"$lib\"; set_ssh_keys" + sh -c ". \"$lib\"; init__set_ssh_keys" atf_check -o match:"^ssh-ed25519 keydata" \ -o match:"^ssh-rsa foobar" \ cat home/alpine/.ssh/authorized_keys @@ -77,7 +69,7 @@ save_userdata_plain_body() { #userdata EOF CLOUD="nocloud" atf_check \ - sh -c ". \"$lib\"; save_userdata" + sh -c ". \"$lib\"; init__save_userdata" atf_check -o match:"^#userdata" cat var/lib/cloud/user-data } @@ -88,7 +80,7 @@ save_userdata_compressed_body() { fake_userdata_nocloud < tmpfile CLOUD="nocloud" atf_check \ - sh -c ". \"$lib\"; save_userdata" + sh -c ". \"$lib\"; init__save_userdata" if ! grep "^#userdata" var/lib/cloud/user-data; then atf_fail "$comp failed"