diff --git a/.gitignore b/.gitignore index 900bda8..124293e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.bak *.swp .vscode/ +Kyuafile diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..1519c23 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,33 @@ +test-default: + image: alpine:latest + stage: test + script: + - apk add make kyua yx + - make -j $(nproc) check + tags: + - docker-alpine + - x86_64 + +test-dash: + extends: test-default + before_script: + - apk add dash + - ln -sf /usr/bin/dash /bin/sh + +test-oksh: + extends: test-default + before_script: + - apk add oksh + - ln -sf /bin/oksh /bin/sh + +test-yash: + extends: test-default + before_script: + - apk add yash + - ln -sf /usr/bin/yash /bin/sh + +test-zsh: + extends: test-default + before_script: + - apk add zsh + - ln -sf /bin/zsh /bin/sh diff --git a/Makefile b/Makefile index 6c0f45c..b44e107 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PREFIX?=/ SUBPACKAGES = core network openrc aws azure gcp oci nocloud -.PHONY: install $(SUBPACKAGES) +.PHONY: check install $(SUBPACKAGES) install: $(SUBPACKAGES) @@ -52,3 +52,21 @@ oci: nocloud: install -Dm644 -t $(PREFIX)/lib/tiny-cloud/nocloud \ lib/tiny-cloud/nocloud/* + +check: tests/Kyuafile Kyuafile + kyua test || (kyua report --verbose && exit 1) + +tests/Kyuafile: $(wildcard tests/*.test) + echo "syntax(2)" > $@.tmp + echo "test_suite('tiny-cloud')" >> $@.tmp + for i in $(notdir $(wildcard tests/*.test)); do \ + echo "atf_test_program{name='$$i',timeout=5}" >> $@.tmp ; \ + done + mv $@.tmp $@ + +Kyuafile: + echo "syntax(2)" > $@.tmp + echo "test_suite('tiny-cloud')" >> $@.tmp + echo "include('tests/Kyuafile')" >> $@.tmp + mv $@.tmp $@ + diff --git a/bin/imds b/bin/imds index 725506b..591737d 100755 --- a/bin/imds +++ b/bin/imds @@ -5,7 +5,34 @@ ### configuration, common functions -source /lib/tiny-cloud/common +: "${LIBDIR:=$PREFIX/lib}" +. "$LIBDIR"/tiny-cloud/common + +if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then + cat < | } ... + -h : help + -e / +e : ignore / catch errors + +n / +s / +t : insert newline / space / tab + :- + hostname : instance hostname + ssh-keys : instance SSH keys + userdata : instance user data + nics : instance NICs + nic:[, ...] : specific NIC interface + : network interface (i.e. eth1) + :- { -e | +e | +n | +s | +t | @ | } + :- + mac : mac address + ipv4 : ipv4 address(es) + ipv6 : ipv6 address(es) + ipv4-net : subnet ipv4 network(s) + ipv6-net : subnet ipv6 network(s) + ipv4-prefix : delegated ipv4 CIDR(s) + ipv6-prefix : delegated ipv6 CIDR(s) +EOT + exit 0 +fi ### cloud-specific variables/functions @@ -57,20 +84,15 @@ _imds_nic_index() { cat "/sys/class/net/$1/address"; } ### load cloud-specific variables and functions -if [ ! -d /lib/tiny-cloud/"$CLOUD" ]; then +if [ ! -d "$LIBDIR"/tiny-cloud/"$CLOUD" ]; then echo "ERROR: Unknown Cloud '$CLOUD'" >&2 - exit 1 fi -source /lib/tiny-cloud/"$CLOUD"/imds +. "$LIBDIR"/tiny-cloud/"$CLOUD"/imds ### non-overrideable functions imds() { local cmd args key rv err=1 - if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then - _imds_help - return - fi while [ -n "$1" ]; do cmd=_imds args= @@ -129,30 +151,4 @@ _imds_nic_args() { done } -_imds_help() { - cat < | } ... - -h : help - -e / +e : ignore / catch errors - +n / +s / +t : insert newline / space / tab - :- - hostname : instance hostname - ssh-keys : instance SSH keys - userdata : instance user data - nics : instance NICs - nic:[, ...] : specific NIC interface - : network interface (i.e. eth1) - :- { -e | +e | +n | +s | +t | @ | } - :- - mac : mac address - ipv4 : ipv4 address(es) - ipv6 : ipv6 address(es) - ipv4-net : subnet ipv4 network(s) - ipv6-net : subnet ipv6 network(s) - ipv4-prefix : delegated ipv4 CIDR(s) - ipv6-prefix : delegated ipv6 CIDR(s) -EOT -} - - imds "$@" diff --git a/etc/init.d/tiny-cloud b/etc/init.d/tiny-cloud index 96d5239..e578c2d 100755 --- a/etc/init.d/tiny-cloud +++ b/etc/init.d/tiny-cloud @@ -4,7 +4,8 @@ description="Tiny Cloud Bootstrap - main phase" extra_commands="complete incomplete" -source /lib/tiny-cloud/init-main +: "${LIBDIR:=$PREFIX/lib}" +. "$LIBDIR"/tiny-cloud/init-main depend() { need net diff --git a/etc/init.d/tiny-cloud-early b/etc/init.d/tiny-cloud-early index 0c9cb83..f93377a 100755 --- a/etc/init.d/tiny-cloud-early +++ b/etc/init.d/tiny-cloud-early @@ -3,12 +3,14 @@ description="Tiny Cloud Bootstrap - early phase" +: "${LIBDIR:=$PREFIX/lib}" + depend() { before mdev } start() { - source /lib/tiny-cloud/init-early + . "$LIBDIR"/tiny-cloud/init-early is_bootstrapped && return 0 diff --git a/etc/init.d/tiny-cloud-final b/etc/init.d/tiny-cloud-final index 3fab8cc..5cdbffe 100755 --- a/etc/init.d/tiny-cloud-final +++ b/etc/init.d/tiny-cloud-final @@ -3,13 +3,15 @@ description="Tiny Cloud Bootstrap - final phase" +: "${LIBDIR:=$PREFIX/lib}" + depend() { after * provide cloud-final } start() { - source /lib/tiny-cloud/init-final + . "$LIBDIR"/tiny-cloud/init-final is_bootstrapped && return 0 diff --git a/lib/mdev/nvme-ebs-links b/lib/mdev/nvme-ebs-links index cca7990..d13d9ca 100755 --- a/lib/mdev/nvme-ebs-links +++ b/lib/mdev/nvme-ebs-links @@ -1,7 +1,8 @@ #!/bin/sh # vim:set ts=2 et: -source /lib/tiny-cloud/common +: "${LIBDIR:=$PREFIX/lib}" +. "$LIBDIR"/tiny-cloud/common # nvme tool not installed? [ -x /usr/sbin/nvme ] || log crit "nvme cli not installed" diff --git a/lib/mdev/vnic-eth-hotplug b/lib/mdev/vnic-eth-hotplug index a8adb9f..d3a0484 100755 --- a/lib/mdev/vnic-eth-hotplug +++ b/lib/mdev/vnic-eth-hotplug @@ -3,7 +3,8 @@ set -e -source /lib/tiny-cloud/common +: "${LIBDIR:=$PREFIX/lib}" +. "$LIBDIR"/tiny-cloud/common if [ -z "$MDEV" ] || [ -z "$ACTION" ]; then log crit "MDEV or ACTION undefined, aborting" diff --git a/lib/tiny-cloud/common b/lib/tiny-cloud/common index f633c44..0b9b325 100644 --- a/lib/tiny-cloud/common +++ b/lib/tiny-cloud/common @@ -2,17 +2,17 @@ # vim: ts=4 et ft=sh: # set defaults -[ -f /etc/conf.d/tiny-cloud ] && source /etc/conf.d/tiny-cloud +[ -f "$ROOT"/etc/conf.d/tiny-cloud ] && . "$ROOT"/etc/conf.d/tiny-cloud : "${CLOUD:=unknown}" : "${CLOUD_USER:=alpine}" -: "${TINY_CLOUD_LOGS:=/var/log}" -: "${TINY_CLOUD_VAR:=/var/lib/cloud}" +: "${TINY_CLOUD_LOGS:=$ROOT/var/log}" +: "${TINY_CLOUD_VAR:=$ROOT/var/lib/cloud}" log() { local facility="kern" local stderr local tag=$(basename "$0") - while [ "${1:0:1}" = '-' ]; do + while [ "${1#-}" != "$1" ]; do case "$1" in -f) facility="$2"; shift ;; -s) stderr=-s ;; diff --git a/lib/tiny-cloud/init-common b/lib/tiny-cloud/init-common index d19d27b..1622bd1 100644 --- a/lib/tiny-cloud/init-common +++ b/lib/tiny-cloud/init-common @@ -2,7 +2,9 @@ # vim:set ts=4 et ft=sh: # set defaults -source /lib/tiny-cloud/common +: "${LIBDIR:=$PREFIX/lib}" +. "$LIBDIR"/tiny-cloud/common + : "${SKIP_INIT_ACTIONS:=}" # is initial bootstrap already done? diff --git a/lib/tiny-cloud/init-early b/lib/tiny-cloud/init-early index 0728861..652aac2 100644 --- a/lib/tiny-cloud/init-early +++ b/lib/tiny-cloud/init-early @@ -1,7 +1,8 @@ # Tiny Cloud - Early Phase Functions # vim:set ts=4 et ft=sh: -source /lib/tiny-cloud/init-common +: "${LIBDIR:=$PREFIX/lib}" +. "$LIBDIR"/tiny-cloud/init-common expand_root() { skip_action expand_root && return @@ -41,4 +42,7 @@ install_hotplugs() { done } -[ -f /lib/tiny-cloud/"${HOTPLUG_TYPE:=mdev}" ] && source /lib/tiny-cloud/"$HOTPLUG_TYPE" +: "${HOTPLUG_TYPE:=mdev}" +if [ -f "$LIBDIR"/tiny-cloud/"$HOTPLUG_TYPE" ]; then + . "$LIBDIR"/tiny-cloud/"$HOTPLUG_TYPE" +fi diff --git a/lib/tiny-cloud/init-final b/lib/tiny-cloud/init-final index e9abd7f..9c19c57 100644 --- a/lib/tiny-cloud/init-final +++ b/lib/tiny-cloud/init-final @@ -1,7 +1,8 @@ # Tiny Cloud - Final Phase Functions # vim:set ts=4 et ft=sh: -source /lib/tiny-cloud/init-common +: "${LIBDIR:=$PREFIX/lib}" +. "$LIBDIR"/tiny-cloud/init-common is_userdata_script() { head -n1 "$TINY_CLOUD_VAR/user-data" | grep -q "#!/" diff --git a/lib/tiny-cloud/init-main b/lib/tiny-cloud/init-main index 55ae0be..31c4f79 100644 --- a/lib/tiny-cloud/init-main +++ b/lib/tiny-cloud/init-main @@ -1,7 +1,8 @@ # Tiny Cloud - Main Phase Functions # vim:set ts=4 et ft=sh: -source /lib/tiny-cloud/init-common +: "${LIBDIR:=$PREFIX/lib}" +. "$LIBDIR"/tiny-cloud/init-common # ensure existence of output directories [ ! -d "$TINY_CLOUD_LOGS" ] && mkdir -p "$TINY_CLOUD_LOGS" diff --git a/lib/tiny-cloud/mdev b/lib/tiny-cloud/mdev index 345a945..8cc3a15 100644 --- a/lib/tiny-cloud/mdev +++ b/lib/tiny-cloud/mdev @@ -33,5 +33,7 @@ mod__vnic_eth_hotplug() { } # load cloud-specific functions - -[ -f /lib/tiny-cloud/"$CLOUD"/mdev ] && source /lib/tiny-cloud/"$CLOUD"/mdev +: "${LIBDIR:=$PREFIX/lib}" +if [ -f "$LIBDIR"/lib/tiny-cloud/"$CLOUD"/mdev ]; then + "$LIBDIR"/tiny-cloud/"$CLOUD"/mdev +fi diff --git a/lib/tiny-cloud/nocloud/imds b/lib/tiny-cloud/nocloud/imds index 04597ce..5b6bec1 100644 --- a/lib/tiny-cloud/nocloud/imds +++ b/lib/tiny-cloud/nocloud/imds @@ -8,17 +8,17 @@ is_nocloud_loaded() { [ -f "$TINY_CLOUD_VAR/.nocloud_loaded" ]; } _load_nocloud_cmdline() { local kopt kv k v data - for kopt in $(cat /proc/cmdline); do + for kopt in $(cat "$ROOT"/proc/cmdline 2>/dev/null); do echo "$kopt" | grep -qE '(^|=)ds=nocloud(-net)?;' || continue for kv in $(echo "${kopt#*;}" | tr \; ' '); do k=$(echo "$kv" | cut -d= -f1) v=$(echo "$kv" | cut -d= -f2-) case "$k" in h|hostname) - echo -e "\nhostname: $v" >> "$TINY_CLOUD_VAR/meta-data" + printf "\nhostname: %s" "$v" >> "$TINY_CLOUD_VAR/meta-data" ;; i|instance-id) - echo -e "\ninstance-id: $v" >> "$TINY_CLOUD_VAR/meta-data" + printf "\ninstance-id: %s" "$v" >> "$TINY_CLOUD_VAR/meta-data" ;; s|seedfrom) for data in $NOCLOUD_FILES; do @@ -46,7 +46,7 @@ _load_nocloud_cmdline() { } _load_nocloud_volume() { - local mntdir=$(mktemp -d /mnt/cidata-XXXXXX) + local mntdir=$(mktemp -d "$ROOT"/mnt/cidata-XXXXXX) local data mounted mkdir -p "$mntdir" @@ -60,7 +60,7 @@ _load_nocloud_volume() { if [ -n "$mounted" ]; then for data in $NOCLOUD_FILES; do # lack of source results in empty target - cat "$mntdir/$data" > "$TINY_CLOUD_VAR/$data" + cat "$mntdir/$data" > "$TINY_CLOUD_VAR/$data" 2>/dev/null done umount "$mntdir" else @@ -86,6 +86,7 @@ load_nocloud() { } _imds() { + mkdir -p "$TINY_CLOUD_VAR" local file="$TINY_CLOUD_VAR"/$(echo "$1" | cut -d/ -f1) local keypath=$(echo "$1" | cut -d/ -f2- | tr / ' ') diff --git a/sbin/assemble-interfaces b/sbin/assemble-interfaces index e2683f0..0d02766 100755 --- a/sbin/assemble-interfaces +++ b/sbin/assemble-interfaces @@ -3,7 +3,7 @@ set -e -IFACE_CFG=/etc/network/interfaces +IFACE_CFG="$ROOT"/etc/network/interfaces IFACE_DIR="${IFACE_CFG}.d" cd "$IFACE_DIR" @@ -15,7 +15,7 @@ cat > "$IFACE_CFG.new" < etc/network/interfaces.d/DEFAULT <<-EOF + auto %% + iface %% inet dhcp + EOF + atf_check assemble-interfaces + + atf_check \ + -o match:"# NOTE:" \ + -o match:"auto eth0" \ + -o match:"iface eth0 inet dhcp" \ + cat etc/network/interfaces +} + +# test what happens if etc/network/interfaces.d is missing +assemble_missing_interfaces_d_body() { + atf_check -s not-exit:0 \ + -e match:"([Nn]o such file or directory|can't cd to)" \ + assemble-interfaces + + if [ -f etc/network/interfaces ]; then + atf_fail "should not create etc/network/interfaces" + fi +} diff --git a/tests/imds.test b/tests/imds.test new file mode 100755 index 0000000..9bd9d0c --- /dev/null +++ b/tests/imds.test @@ -0,0 +1,52 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +export PREFIX="$srcdir" + +init_tests \ + imds_help \ + imds_nocloud_cmdline_hostname \ + imds_nocloud_cidata_hostname + +imds_help_body() { + atf_check -o match:"Usage: imds" imds -h +} + +imds_nocloud_cmdline_hostname_body() { + atf_require_prog yx + mkdir proc + for key in h hostname; do + echo "BOOT_IMAGE=/boot/vmlinuz-lts ro ds=nocloud;$key=myhostname" > proc/cmdline + CLOUD=nocloud atf_check \ + -o match:'^myhostname$' \ + imds @hostname + done +} + +imds_nocloud_cidata_hostname_body() { + atf_require_prog yx + fake_bin mount <<-EOF + #!/bin/sh + # find last arg which is the mount dir + while ! [ -d "\$1" ]; do + shift + done + printf "#cloud-config\nhostname: myhostname\n" \ + > "\$1"/meta-data + EOF + + fake_bin umount <<-EOF + #!/bin/sh + while ! [ -d "\$1" ]; do + shift + done + rm -f "\$1"/meta-data + EOF + mkdir -p mnt + + CLOUD=nocloud atf_check \ + -o match:'^myhostname$' \ + imds @hostname +} + diff --git a/tests/test_env.sh b/tests/test_env.sh new file mode 100644 index 0000000..7bc17d6 --- /dev/null +++ b/tests/test_env.sh @@ -0,0 +1,30 @@ +# shellcheck shell=sh + +srcdir="$(atf_get_srcdir)/.." +PATH="$srcdir/bin:$srcdir/sbin:$PATH" + +export TINY_CLOUD_BASEDIR="$srcdir" +export ROOT="$PWD" + +init_tests() { + TESTS= + for t; do + TESTS="$TESTS $t" + atf_test_case "$t" + done + export TESTS +} + +atf_init_test_cases() { + for t in $TESTS; do + atf_add_test_case "$t" + done +} + +fake_bin() { + mkdir -p bin + cat > bin/"$1" + chmod +x bin/"$1" + PATH="$PWD/bin:$PATH" +} +