# Incus Instance Metadata
# vim:set filetype=sh:
# shellcheck shell=sh

# https://linuxcontainers.org/incus/docs/main/dev-incus/

. "$LIBDIR/tiny-cloud/imds-document"
. "$LIBDIR/tiny-cloud/imds-http"

: "${IMDS_API_VERSION:=1.0}"
IMDS_URI="/$IMDS_API_VERSION"
IMDS_HOSTNAME="meta-data/local-hostname"
IMDS_LOCAL_HOSTNAME="$IMDS_HOSTNAME"
IMDS_NICS="devices"
IMDS_MAC="hwaddr"
IMDS_IPV4="ipv4.address"
IMDS_IPV6="ipv6.address"

_imds_fetch() {
	local path="$1" destination="$2" response status rv=1

	response=$(mktemp "$IMDS_CACHE_DIR/response.XXXXXX") || return
	if printf 'GET %s HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n' "$path" |
		nc -w 1 "$IMDS_ENDPOINT" > "$response"; then
		status=$(awk 'NR == 1 && /^HTTP\// { print $2; exit }' "$response")
		if [ "$status" = 200 ]; then
			_imds_copy_body "$response" "$destination"
			rv=$?
		fi
	fi
	rm -f "$response"
	if [ "$rv" -ne 0 ]; then
		echo "imds: unable to fetch '$path'${status:+ (HTTP $status)}" >&2
	fi
	return "$rv"
}

_imds_nic_data() {
	_imds "$IMDS_NICS" >/dev/null || return 1
	yx -f "$IMDS_CACHE_DIR/$IMDS_NICS" >/dev/null || return 1
	printf '%s\n' "$IMDS_CACHE_DIR/$IMDS_NICS"
}

_imds_init() { _imds_cache_init; }

_imds() {
	local path="$1" document key

	case "$path" in
		''|/*) echo "imds: invalid document path '$path'" >&2; return 1;;
	esac
	case "/$path/" in
		*/../*|*/./*) echo "imds: invalid document path '$path'" >&2; return 1;;
	esac
	case "$path" in
		meta-data|meta-data/*|devices|devices/*|config|config/)
			document="${path%%/*}"
			_imds_fetch_cached "$IMDS_CACHE_DIR/$document" \
				_imds_fetch "$IMDS_URI/$document" || return
			imds_document "$IMDS_CACHE_DIR" "$path"
			;;
		config/*)
			key="${path#config/}"
			case "$key" in
				*[!A-Za-z0-9_.~-]*)
					echo "imds: invalid configuration key '$key'" >&2
					return 1
					;;
			esac
			_imds_fetch_cached "$IMDS_CACHE_DIR/config-values/$key" \
				_imds_fetch "$IMDS_URI/config/$key" || return
			cat "$IMDS_CACHE_DIR/config-values/$key"
			;;
		*) _imds "meta-data/$path";;
	esac
}

_imds_ssh_keys() {
	local data
	# extract ssh_authorized_keys from user-data
	data=$(_imds_userdata) || return
	printf '%s\n' "$data" \
		| awk '/^ssh_authorized_keys:/ {flag=1; next} /^[a-z]/ {flag=0} flag && /^ *- / {sub(/^ *- /, ""); print}'
}

_imds_userdata() {
	_imds config/user.user-data
}

_imds_nics() {
	local file devices device type
	file=$(_imds_nic_data) || return 1
	devices=$(yx -f "$file") || return 1
	for device in $devices; do
		type=$(yx -f "$file" -- "$device" type) || return 1
		[ "$type" != nic ] || printf '%s\n' "$device"
	done
}

_imds_nic_index() {
	local file mac devices device address
	mac=$(cat "$SYS/class/net/$1/address") || return 1
	mac=$(printf '%s' "$mac" | lower)
	[ -n "$mac" ] || return 1
	file=$(_imds_nic_data) || return 1
	devices=$(_imds_nics) || return 1
	for device in $devices; do
		address=$(_imds_optional_value "$file" hwaddr "$device") || return 1
		address=$(printf '%s' "$address" | lower)
		[ "$address" = "$mac" ] || continue
		printf '%s\n' "$device"
		return 0
	done
	return 1
}

_imds_nic_value() {
	local file value address
	case "$2" in
		@mac|@ipv4|@ipv6)
			file=$(_imds_nic_data) || return 1
			value=$(_imds_optional_value "$file" "$3" "$1") || return 1
			;;
		*) _imds "$IMDS_NICS/$1${3:+/$3}"; return $?;;
	esac
	case "$2" in
		@mac) [ -z "$value" ] || printf '%s\n' "$value" | lower;;
		*)
			value=$(printf '%s' "$value" | tr ',' ' ')
			for address in $value; do
				case "$address" in none|null) continue;; esac
				printf '%s\n' "${address%/*}"
			done
			;;
	esac
}
