# Scaleway Instance MetaData Service variables and functions
# vim:set filetype=sh:
# shellcheck shell=sh

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

IMDS_HOSTNAME="HOSTNAME"
IMDS_LOCAL_HOSTNAME="$IMDS_HOSTNAME"
IMDS_SSH_KEYS="SSH_PUBLIC_KEYS_[0-9]+_KEY"
IMDS_NICS="conf.json"
IMDS_MAC="mac_address"
IMDS_IPV4="public_ips_v4"
IMDS_IPV6="public_ips_v6"

_imds_fetch_privileged() {
	local path="$1" destination="$2"
	local endpoint host port response status rv=1

	response=$(mktemp "$IMDS_CACHE_DIR/response.XXXXXX") || return
	# Scaleway requires user/vendor data requests to use a source port below
	# 1024, which wget cannot select.
	for endpoint in $(_imds_endpoints); do
		status=
		set -- $(imds_host_port "$endpoint") || continue
		host="$1"
		port="$2"
		printf 'GET /%s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\n\r\n' \
			"$path" "$endpoint" |
			nc -w 1 -p 1000 "$host" "$port" > "$response" || continue
		status=$(awk 'NR == 1 { print $2; exit }' "$response")
		if [ "$status" != 200 ]; then
			case "$status" in 4??) break;; esac
			continue
		fi
		_imds_copy_body "$response" "$destination" || continue
		_imds_cache_endpoint "$endpoint"
		rv=0
		break
	done
	rm -f "$response"
	if [ "$rv" -ne 0 ]; then
		echo "imds: unable to fetch '$path'${status:+ (HTTP $status)}" >&2
	fi
	return "$rv"
}

_imds_fetch() {
	local path="$1" destination="$2" IMDS_OUTPUT="$2"

	case "$path" in
		conf|conf\?*) _imds_http "$path" $(_imds_endpoints);;
		*) _imds_fetch_privileged "$path" "$destination";;
	esac
}

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

_imds_init() { _imds_cache_init; }

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

	case "$path" in
		conf.json|conf.json/*|\
		user_data.json|user_data.json/*|\
		vendor_data.json|vendor_data.json/*)
			document="${path%%/*}"
			_imds_fetch_cached "$IMDS_CACHE_DIR/$document" \
				_imds_fetch "${document%.json}?format=json" || return
			imds_document "$IMDS_CACHE_DIR" "$path"
			;;
		conf|user_data|vendor_data)
			_imds_fetch_cached "$IMDS_CACHE_DIR/$path" _imds_fetch "$path" || return
			cat "$IMDS_CACHE_DIR/$path"
			;;
		conf/|user_data/|vendor_data/)
			document="${path%/}"
			_imds_fetch_cached "$IMDS_CACHE_DIR/$document" \
				_imds_fetch "$document" || return
			sed -n 's/^\([^=]*\)=.*/\1/p' "$IMDS_CACHE_DIR/$document"
			;;
		user_data/*|vendor_data/*)
			document="${path%%/*}"
			key="${path#*/}"
			case "$key" in
				''|.|..|*[!A-Za-z0-9_.~-]*)
					echo "imds: invalid payload key '$key'" >&2
					return 1
					;;
			esac
			_imds_fetch_cached "$IMDS_CACHE_DIR/payloads/$document/$key" \
				_imds_fetch "$path" || return
			cat "$IMDS_CACHE_DIR/payloads/$document/$key"
			;;
		*)
			# Preserve existing VAR/ERE queries, optionally under conf/.
			regex="${path#conf/}"
			_imds_fetch_cached "$IMDS_CACHE_DIR/conf" _imds_fetch conf || return
			grep -E "^${regex}=" "$IMDS_CACHE_DIR/conf" | cut -d= -f2-
			;;
	esac
}

_imds_ssh_keys() {
	imds "$IMDS_SSH_KEYS" | tr -d "'" | sort -u
}

_imds_userdata() {
	_imds user_data/cloud-init
}

_imds_nics() {
	local file mac indexes index
	file=$(_imds_nic_data) || return 1
	mac=$(_imds_optional_value "$file" mac_address) || return 1
	[ -z "$mac" ] || echo primary
	indexes=$(_imds_optional_value "$file" private_nics) || return 1
	for index in $indexes; do
		printf 'private_nics/%s\n' "$index"
	done
}

_imds_nic_index() {
	local file mac address indexes index
	mac=$(cat "$SYS/class/net/$1/address") || return 1
	mac=$(printf '%s' "$mac" | lower)
	[ -n "$mac" ] || return 1
	file=$(_imds_nic_data) || return 1
	address=$(_imds_optional_value "$file" mac_address) || return 1
	address=$(printf '%s' "$address" | lower)
	if [ "$address" = "$mac" ]; then
		echo primary
		return 0
	fi
	indexes=$(_imds_optional_value "$file" private_nics) || return 1
	for index in $indexes; do
		address=$(_imds_optional_value "$file" mac_address private_nics "$index") || return 1
		address=$(printf '%s' "$address" | lower)
		[ "$address" = "$mac" ] || continue
		printf 'private_nics/%s\n' "$index"
		return 0
	done
	return 1
}

_imds_nic_value() {
	local nic="$1" alias="$2" key="$3" file address indexes index keys
	file=$(_imds_nic_data) || return 1
	case "$alias" in
		@mac)
			if [ "$nic" = primary ]; then
				address=$(_imds_optional_value "$file" mac_address) || return 1
			else
				address=$(_imds_optional_value "$file" mac_address private_nics "${nic#*/}") || return 1
			fi
			printf '%s\n' "$address" | lower
			;;
		@ipv4|@ipv6)
			[ "$nic" = primary ] || {
				_imds_unsupported "$alias on $nic"
				return 1
			}
			keys=$(yx -f "$file") || return 1
			if printf '%s\n' "$keys" | grep -qxF "$key"; then
				indexes=$(_imds_optional_value "$file" "$key") || return 1
				for index in $indexes; do
					address=$(yx -f "$file" -- "$key" "$index" address) || return 1
					[ -z "$address" ] || printf '%s\n' "$address"
				done
			else
				if [ "$alias" = @ipv4 ]; then
					address=$(_imds_optional_value "$file" private_ip) || return 1
					if [ -z "$address" ]; then
						address=$(_imds_optional_value "$file" public_ip) || return 1
						[ -z "$address" ] || address=$(yx -f "$file" -- public_ip address) || return 1
					fi
				else
					address=$(_imds_optional_value "$file" ipv6) || return 1
					[ -z "$address" ] || address=$(yx -f "$file" -- ipv6 address) || return 1
				fi
				[ -z "$address" ] || printf '%s\n' "$address"
			fi
			;;
		*)
			[ "$nic" = primary ] || key="$nic${key:+/$key}"
			imds_document "$IMDS_CACHE_DIR" "conf.json/$key"
			;;
	esac
}
