1
0
mirror of https://gitlab.alpinelinux.org/alpine/cloud/tiny-cloud.git synced 2025-12-14 19:02:45 +03:00

Consolidate init functionality in /sbin/tiny-cloud

This commit is contained in:
Jake Buchholz Göktürk 2023-04-30 03:37:11 +00:00
parent 762fe36bb8
commit 81869cefcc
17 changed files with 352 additions and 243 deletions

View File

@ -1,10 +1,25 @@
# NEXT
# CHANGELOG
* `nvme-ebs-symlinks` hase been _deprecated_ and disabled by default. The **mdev-conf** package, as of v4.4 is now responsible for maintaining NVMe device symlinks for AWS.
## 2023-04-XX - Tiny Cloud v3.0.0
***WARNING:*** The behavior of **mdev-conf** is slightly different -- only **/dev/sd** or **/dev/xvd** symlinks are created, *not both*!
* Tiny Cloud init functionality has been consolidated into **/sbin/tiny-cloud**
and init scripts should use `tiny-cloud <phase>` to indicate whether `early`,
`main`, or `final` actions should be taken. Additionally, it is now possible
for clouds to specify their own (or supercede the default) init functions
and/or change which init phase they are executed in.
The example OpenRC init scripts been updated and moved to **dist/openrc/**.
* Tiny Cloud configuration has moved to **/etc/tiny-cloud.conf**.
* `nvme-ebs-symlinks` has been _deprecated_ and disabled by default. The
**mdev-conf** package, as of v4.4 is now responsible for maintaining NVMe
device symlinks for AWS.
***WARNING:*** The behavior of **mdev-conf** is slightly different -- either
**/dev/sd** or **/dev/xvd** symlinks are created as indicated in NVMe device
metadata, *but NOT both*!
----
_CHANGELOG begins 2023-04-29_

View File

@ -1,4 +1,4 @@
Copyright (c) 2017-2022 Jake Buchholz Göktürk, Michael Crute
Copyright (c) 2017-2023 Jake Buchholz Göktürk, Michael Crute
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@ -11,11 +11,13 @@ core:
bin/imds
install -Dm644 -t "$(PREFIX)"/lib/tiny-cloud \
lib/tiny-cloud/common \
lib/tiny-cloud/init-* \
lib/tiny-cloud/init \
lib/tiny-cloud/mdev \
lib/tiny-cloud/tiny-cloud.conf
install -Dm644 lib/tiny-cloud/tiny-cloud.conf \
"$(PREFIX)"/etc/tiny-cloud.conf
install -Dm755 -t "$(PREFIX)"/sbin \
sbin/tiny-cloud
network:
install -Dm644 -t "$(PREFIX)"/etc/network/interfaces.d \
@ -23,7 +25,8 @@ network:
install -Dm755 -t "$(PREFIX)"/lib/mdev \
lib/mdev/vnic-eth-hotplug
install -Dm755 -t "$(PREFIX)"/sbin \
sbin/*
sbin/assemble-interfaces \
sbin/imds-net-sync
install -Dm755 -t "$(PREFIX)"/usr/libexec/ifupdown-ng \
usr/libexec/ifupdown-ng/imds

View File

@ -31,7 +31,7 @@ instance:
Optional features, which may not be universally necessary:
* manage hotpluggable network interfaces
* sync IMDS-provided secondary IPv4 and IPv6 addresses network interfaces
* manage symlinks from NVMe block devices to `/dev/xvd` and `/dev/sd` devices
* manage symlinks from NVMe block devices to `/dev/xvd` or `/dev/sd` devices
(i.e. AWS Nitro instances)
Also included is a handy `imds` client script for easy access to an instance's

18
TODO.md Normal file
View File

@ -0,0 +1,18 @@
# TODO
## Tiny Cloud v3.0.0
* Support for Alpine Linux ISO auto-install via NoCloud `CIDATA` volumes, which
have pre-network access to UserData and MetaData. Adjust phase actions as
appropriate.
* Detect UserData content type. In addition to handling `#!` scripts and raw
data, provide basic handling a subset of `#cloud-config` directives.
## FUTURE
* `imds-net-sync` improvements
* Feature parity with current [amazon-ec2-net-utils](
https://github.com/amazonlinux/amazon-ec2-net-utils)
* Support for non-AWS clouds
* daemonize to pick up IMDS network changes between reboots

View File

@ -4,31 +4,17 @@
description="Tiny Cloud Bootstrap - main phase"
extra_commands="complete incomplete"
: "${LIBDIR:=$PREFIX/lib}"
. "$LIBDIR"/tiny-cloud/init-main
depend() {
need net
before sshd
}
start() {
is_bootstrapped && return 0
ebegin "Saving Instance UserData"
save_userdata
eend $?
ebegin "Setting Instance Hostname"
set_hostname
eend $?
ebegin "Installing SSH Keys for $CLOUD_USER User"
set_ssh_keys "$CLOUD_USER"
ebegin "Tiny Cloud - main phase"
tiny-cloud main
eend $?
}
# allow setting / unsetting of bootstrap state
complete() { bootstrap_complete ; }
incomplete() { bootstrap_incomplete ; }
# allow setting / unsetting of bootstrapped state
complete() { tiny-cloud --bootstrap complete ; }
incomplete() { tiny-cloud --bootstrap incomplete ; }

View File

@ -3,25 +3,13 @@
description="Tiny Cloud Bootstrap - early phase"
: "${LIBDIR:=$PREFIX/lib}"
depend() {
after root
after root logger
before net
}
start() {
. "$LIBDIR"/tiny-cloud/init-early
is_bootstrapped && return 0
ebegin "Expanding Root Volume/Partition"
expand_root
ebegin "Tiny Cloud - early phase"
tiny-cloud early
eend $?
if has_cloud_hotplugs; then
ebegin "Installing Cloud Hotplugs"
install_hotplugs
eend $?
fi
}

View File

@ -3,25 +3,13 @@
description="Tiny Cloud Bootstrap - final phase"
: "${LIBDIR:=$PREFIX/lib}"
depend() {
after *
provide cloud-final
}
start() {
. "$LIBDIR"/tiny-cloud/init-final
is_bootstrapped && return 0
if is_userdata_script; then
ebegin "Executing UserData Script"
run_userdata
eend $?
fi
ebegin "Marking Instance Bootstrap Complete"
bootstrap_complete
ebegin "Tiny Cloud - final phase"
tiny-cloud final
eend $?
}

188
lib/tiny-cloud/init Normal file
View File

@ -0,0 +1,188 @@
# Tiny Cloud - Init Functions
# vim:set ts=4 et ft=sh:
# set defaults
: "${LIBDIR:=$PREFIX/lib}"
. "$LIBDIR"/tiny-cloud/common
: "${SKIP_INIT_ACTIONS:=}"
: "${HOTPLUG_TYPE:=mdev}"
# TODO: default phase actions
# ensure existence of output directories
[ ! -d "$TINY_CLOUD_LOGS" ] && mkdir -p "$TINY_CLOUD_LOGS"
[ ! -d "$TINY_CLOUD_VAR" ] && mkdir -p "$TINY_CLOUD_VAR"
### NOTE: init-early functions...
expand_root() {
skip_action expand_root && return
echo "Expanding Root Volume/ Partition"
local dev=$(awk '$2 == "/" {print $1}' "$ROOT"/proc/mounts)
local partition=$(cat "$ROOT/sys/class/block/${dev#/dev/}/partition" 2>/dev/null)
if [ -n "$partition" ]; then
# it's a partition, resize it
local volume=$(readlink -f "$ROOT/sys/class/block/${dev#/dev/}/..")
volume="/dev/${volume##*/}"
echo ", +" | $MOCK sfdisk -q --no-reread -N "$partition" "$volume"
$MOCK partx -u "$volume"
fi
# resize filesystem
$MOCK resize2fs "$dev"
}
install_hotplugs() {
skip_action install_hotplugs && return
[ ! -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"
fi
for module in $HOTPLUG_MODULES; do
result='-'
printf " $module"
if type "mod__$module" | grep -q -w "function"; then
"mod__$module" && result='+' || { result='!'; rc=1; }
fi
printf "($result)"
done
return $rc
}
### NOTE: init-main functions
set_hostname() {
skip_action set_hostname && return
echo "Setting Instance Hostname"
local fqdn=$(imds @hostname)
local host="${fqdn%%\.*}"
mkdir -p "$ROOT"/etc
echo "$host" > "$ROOT"/etc/hostname
$MOCK hostname -F "$ROOT"/etc/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"
local user="$CLOUD_USER"
local pwent="$(getent passwd "$user")"
local group=$(echo "$pwent" | cut -d: -f4)
local ssh_dir="${ROOT}$(echo "$pwent" | cut -d: -f6)/.ssh"
local keys_file="$ssh_dir/authorized_keys"
if [ ! -d "$ssh_dir" ]; then
mkdir -p "$ssh_dir"
chmod 700 "$ssh_dir"
fi
touch "$keys_file"
chmod 600 "$keys_file"
$MOCK chown -R "$user:$group" "$ssh_dir"
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"
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
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"
local log="$TINY_CLOUD_LOGS/user-data.log"
local exit="$TINY_CLOUD_LOGS/user-data.exit"
local userdata="$TINY_CLOUD_VAR/user-data"
chmod +x "$userdata"
{ "$userdata" 2>& 1; echo $? > "$exit"; } | tee "$log"
return $(cat "$exit")
}
# load cloud-specific init functions / vars
: "${LIBDIR:=$PREFIX/lib}"
if [ -f "$LIBDIR"/tiny-cloud/"$CLOUD"/init ]; then
. "$LIBDIR"/tiny-cloud/"$CLOUD"/init
fi
### 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
}
userdata_type() {
if [ -f "$TINY_CLOUD_VAR/user-data" ]; then
header=$(head -n1 "$TINY_CLOUD_VAR/user-data")
case "$header" in
'#cloud-config') echo cloud-config;;
'#!'*) echo script;;
*) echo unknown;;
esac
else
echo missing
fi
}

View File

@ -1,29 +0,0 @@
# Tiny Cloud - Common Initialization
# vim:set ts=4 et ft=sh:
# set defaults
: "${LIBDIR:=$PREFIX/lib}"
. "$LIBDIR"/tiny-cloud/common
: "${SKIP_INIT_ACTIONS:=}"
# is initial bootstrap already done?
is_bootstrapped() { [ -f "$TINY_CLOUD_VAR"/.bootstrap-complete ] ; }
# indicate bootstrap is done
bootstrap_complete() { touch "$TINY_CLOUD_VAR"/.bootstrap-complete ; }
# indicate bootstrap isn't done
bootstrap_incomplete() { rm -f "$TINY_CLOUD_VAR"/.bootstrap-complete ; }
# 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
}

View File

@ -1,45 +0,0 @@
# Tiny Cloud - Early Phase Functions
# vim:set ts=4 et ft=sh:
: "${LIBDIR:=$PREFIX/lib}"
. "$LIBDIR"/tiny-cloud/init-common
expand_root() {
skip_action expand_root && return
local dev=$(awk '$2 == "/" {print $1}' "$ROOT"/proc/mounts)
local partition=$(cat "$ROOT/sys/class/block/${dev#/dev/}/partition" 2>/dev/null)
if [ -n "$partition" ]; then
# it's a partition, resize it
local volume=$(readlink -f "$ROOT/sys/class/block/${dev#/dev/}/..")
volume="/dev/${volume##*/}"
echo ", +" | $MOCK sfdisk -q --no-reread -N "$partition" "$volume"
$MOCK partx -u "$volume"
fi
# resize filesystem
$MOCK resize2fs "$dev"
}
has_cloud_hotplugs() { [ -n "$HOTPLUG_MODULES" ]; }
install_hotplugs() {
skip_action install_hotplugs && return
local result rc=0
for module in $HOTPLUG_MODULES; do
result='-'
printf " $module"
if type "mod__$module" | grep -q -w "function"; then
"mod__$module" && result='+' || { result='!'; rc=1; }
fi
printf "($result)"
done
return $rc
}
: "${HOTPLUG_TYPE:=mdev}"
if [ -f "$LIBDIR"/tiny-cloud/"$HOTPLUG_TYPE" ]; then
. "$LIBDIR"/tiny-cloud/"$HOTPLUG_TYPE"
fi

View File

@ -1,22 +0,0 @@
# Tiny Cloud - Final Phase Functions
# vim:set ts=4 et ft=sh:
: "${LIBDIR:=$PREFIX/lib}"
. "$LIBDIR"/tiny-cloud/init-common
is_userdata_script() {
head -n1 "$TINY_CLOUD_VAR/user-data" | grep -q "#!/"
}
run_userdata() {
skip_action run_userdata && return
local log="$TINY_CLOUD_LOGS/user-data.log"
local exit="$TINY_CLOUD_LOGS/user-data.exit"
local userdata="$TINY_CLOUD_VAR/user-data"
chmod +x "$userdata"
{ "$userdata" 2>& 1; echo $? > "$exit"; } | tee "$log"
return $(cat "$exit")
}

View File

@ -1,74 +0,0 @@
# Tiny Cloud - Main Phase Functions
# vim:set ts=4 et ft=sh:
: "${LIBDIR:=$PREFIX/lib}"
. "$LIBDIR"/tiny-cloud/init-common
# ensure existence of output directories
[ ! -d "$TINY_CLOUD_LOGS" ] && mkdir -p "$TINY_CLOUD_LOGS"
[ ! -d "$TINY_CLOUD_VAR" ] && mkdir -p "$TINY_CLOUD_VAR"
set_hostname() {
skip_action set_hostname && return
local fqdn=$(imds @hostname)
local host="${fqdn%%\.*}"
mkdir -p "$ROOT"/etc
echo "$host" > "$ROOT"/etc/hostname
$MOCK hostname -F "$ROOT"/etc/hostname
echo -e "127.0.1.1\t$fqdn $host" >> "$ROOT"/etc/hosts
}
set_ssh_keys() {
skip_action set_ssh_keys && return
local user="$CLOUD_USER"
local pwent="$(getent passwd "$user")"
local group=$(echo "$pwent" | cut -d: -f4)
local ssh_dir="${ROOT}$(echo "$pwent" | cut -d: -f6)/.ssh"
local keys_file="$ssh_dir/authorized_keys"
if [ ! -d "$ssh_dir" ]; then
mkdir -p "$ssh_dir"
chmod 700 "$ssh_dir"
fi
touch "$keys_file"
chmod 600 "$keys_file"
$MOCK chown -R "$user:$group" "$ssh_dir"
imds @ssh-keys > "$keys_file"
}
save_userdata() {
skip_action save_userdata && return
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
else
cp "$tmpfile" "$userdata"
fi
rm "$tmpfile"
}

89
sbin/tiny-cloud Executable file
View File

@ -0,0 +1,89 @@
#!/bin/sh
# vim:set ts=4 et ft=sh:
# MacOS testing
getopt=/opt/homebrew/Cellar/gnu-getopt/2.38.1/bin/getopt
# Tiny Cloud
set -e
: "${LIBDIR:=$PREFIX/lib}"
. "$LIBDIR"/tiny-cloud/common
usage() {
cat <<EOF
Usage: ${0##*/} [-h | --help] { early | main | final | --bootstrap {complete|incomplete} }
EOF
}
bootstrap_complete() {
echo "Marking Instance Bootstrap Complete"
touch "$TINY_CLOUD_VAR"/.bootstrap-complete
}
bootstrap_incomplete() {
echo "Marking Instance Bootstrap Incomplete"
rm -f "$TINY_CLOUD_VAR"/.bootstrap-complete
}
args=$($getopt -o hb: --long help,bootstrap: -n ${0##*/} -- "$@")
if [ $? -ne 0 ]; then
usage >&2
exit 1
fi
eval set -- "$args"
while true; do
case "$1" in
-h|--help) usage; exit 0;;
-b|--bootstrap) shift
case "$1" in
complete) # indicate bootstrap is done
bootstrap_complete;;
incomplete) # indicate bootstrap isn't done
bootstrap_incomplete;;
*) usage >&2; exit 1;;
esac
exit 0;;
--) shift; break;;
*) usage >&2; exit 1;;
esac
shift
done
phase="$1"
shift
case "$phase" in
early|main|final) ;;
*) usage >&2; exit 1;;
esac
# is initial bootstrap already done?
if [ -f "$TINY_CLOUD_VAR"/.bootstrap-complete ]; then
log -s "Already bootstrapped"
exit 0;
fi
### default phase actions
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
echo $phase "$@"

View File

@ -4,6 +4,7 @@
export PREFIX="$srcdir"
export MOCK=echo
lib="$srcdir"/lib/tiny-cloud/init
init_tests \
expand_root \
@ -17,7 +18,7 @@ PROVIDERS="aws azure gcp nocloud oci"
expand_root_body() {
fake_bin test-expand-root <<-EOF
#!/bin/sh
. "$srcdir"/lib/tiny-cloud/init-early
. "$lib"
expand_root
EOF
mkdir proc
@ -32,7 +33,7 @@ expand_root_body() {
expand_root_partition_body() {
fake_bin test-expand-root <<-EOF
#!/bin/sh
. "$srcdir"/lib/tiny-cloud/init-early
. "$lib"
expand_root
EOF
mkdir -p proc sys/class/block \
@ -56,7 +57,7 @@ expand_root_skip_body() {
fake_bin test-expand-root <<-EOF
#!/bin/sh
SKIP_INIT_ACTIONS=expand_root
. "$srcdir"/lib/tiny-cloud/init-early
. "$lib"
expand_root
EOF
for provider in $PROVIDERS; do
@ -70,7 +71,7 @@ install_hotplugs_skip_body() {
fake_bin test-install-hotplugs <<-EOF
#!/bin/sh
SKIP_INIT_ACTIONS=install_hotplugs
. "$srcdir"/lib/tiny-cloud/init-early
. "$lib"
install_hotplugs
EOF
for provider in $PROVIDERS; do
@ -84,11 +85,10 @@ install_hotplugs_fail_body() {
fake_bin test-install-hotplugs <<-EOF
#!/bin/sh
HOTPLUG_MODULES="vnic_eth_hotplug"
. "$srcdir"/lib/tiny-cloud/init-early
. "$lib"
install_hotplugs
EOF
CLOUD=aws atf_check -s not-exit:0 \
-o match:"^ vnic_eth_hotplug\(!\)$" \
test-install-hotplugs
}

View File

@ -4,25 +4,31 @@
export PREFIX="$srcdir"
export MOCK=echo
lib="$srcdir"/lib/tiny-cloud/init-final
init_main="$srcdir"/lib/tiny-cloud/init-main
lib="$srcdir"/lib/tiny-cloud/init
PROVIDERS="aws azure gcp nocloud oci"
init_tests \
is_userdata_script \
userdata_type \
run_userdata
is_userdata_script_body() {
userdata_type_body() {
mkdir -p var/lib/cloud
for c in $PROVIDERS; do
rm -f var/lib/cloud/user-data
CLOUD="$c" atf_check \
-o match:"missing" \
sh -c ". \"$lib\"; userdata_type"
echo "#tiny-cloud-config" > var/lib/cloud/user-data
CLOUD="$c" atf_check -s not-exit:0 \
sh -c ". \"$lib\"; is_userdata_script"
CLOUD="$c" atf_check \
-o match:"unknown" \
sh -c ". \"$lib\"; userdata_type"
echo "#!/bin/sh" > var/lib/cloud/user-data
CLOUD="$c" atf_check -s exit:0 \
sh -c ". \"$lib\"; is_userdata_script"
-o match:"script" \
sh -c ". \"$lib\"; userdata_type"
done
}
@ -32,11 +38,10 @@ run_userdata_body() {
echo "hello from user-data"
EOF
CLOUD="nocloud" atf_check \
sh -c ". \"$init_main\"; save_userdata"
sh -c ". \"$lib\"; save_userdata"
CLOUD="nocloud" atf_check \
-o match:"hello from user-data" \
sh -c ". \"$lib\"; 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"
}

View File

@ -4,7 +4,7 @@
export PREFIX="$srcdir"
export MOCK=echo
lib="$srcdir"/lib/tiny-cloud/init-main
lib="$srcdir"/lib/tiny-cloud/init
init_tests \
set_hostname \
@ -95,4 +95,3 @@ save_userdata_compressed_body() {
fi
done
}