# OpenStack Nova Instance Metadata
# vim:set filetype=sh:
# shellcheck shell=sh

: "${IMDS_FORMAT:=auto}"
unset IMDS_SELECTED_FORMAT IMDS_SELECTED_SOURCE IMDS_DOCUMENT_DIR

_openstack_select_format() (
	local format lock="$TINY_CLOUD_RUN/openstack.lock" attempts=0 tmpdir
	local marker="$TINY_CLOUD_RUN/openstack-format"

	case "$IMDS_FORMAT" in
		auto|native|ec2) ;;
		*) echo "imds: invalid OpenStack format '$IMDS_FORMAT'" >&2
			return 1;;
	esac

	exec 9>"$lock" || return
	while ! flock -n 9; do
		attempts=$((attempts + 1))
		if [ "$attempts" -ge 300 ]; then
			echo 'imds: timed out waiting for OpenStack selection' >&2
			return 1
		fi
		sleep 0.1
	done
	if [ "${IMDS_SOURCE:-auto}" = auto ] && [ -f "$marker" ]; then
		format=$(cat "$marker") || return
	else
		tmpdir=$(mktemp -d "$TINY_CLOUD_RUN/openstack-select.XXXXXX") || return
		trap 'rm -rf "$tmpdir"' 0
		trap 'exit 1' HUP INT TERM
		format="$IMDS_FORMAT"
		if [ "$format" = auto ]; then
			IMDS_URI=openstack
			if _imds '' /dev/null 2>"$tmpdir/error"; then
				format=native
			elif [ "$WGET_STATUS" = 404 ]; then
				format=ec2
			else
				cat "$tmpdir/error" >&2
				return 1
			fi
			if [ "$format" = ec2 ]; then
				IMDS_URI="${IMDS_API_VERSION:-latest}"
				_imds '' /dev/null || return
			fi
		fi
		if [ "${IMDS_SOURCE:-auto}" = auto ]; then
			printf '%s\n' "$format" > "$tmpdir/format" &&
				mv "$tmpdir/format" "$marker" || return
		fi
	fi
	case "$format" in
		native|ec2) printf '%s\n' "$format";;
		*) echo 'imds: invalid saved OpenStack format' >&2; return 1;;
	esac
)

_imds_init() {
	[ -z "$IMDS_SELECTED_FORMAT" ] || return 0

	case "$IMDS_FORMAT" in
		auto|native|ec2) ;;
		*) echo "imds: invalid OpenStack format '$IMDS_FORMAT'" >&2; return 1;;
	esac
	IMDS_SELECTED_SOURCE=$(_openstack_select_source) || return
	if [ "$IMDS_SELECTED_SOURCE" = configdrive ]; then
		IMDS_DOCUMENT_DIR=$(configdrive_directory openstack "$IMDS_FORMAT" \
			"${IMDS_API_VERSION:-latest}") || return
		IMDS_SELECTED_FORMAT=$(cat "$IMDS_DOCUMENT_DIR/format") || return
		# Resolve disk paths locally; never fill missing files from HTTP.
		_imds() {
			local path="$1"
			if [ "$IMDS_SELECTED_FORMAT" = ec2 ]; then
				case "$path" in
					meta-data) path=meta-data.json/;;
					meta-data/*) path="meta-data.json/${path#meta-data/}";;
				esac
			fi
			_imds_document "$path"
		}
	else
		IMDS_SELECTED_FORMAT=$(_openstack_select_format) || return
	fi
	case "$IMDS_SELECTED_FORMAT" in
		native)
			IMDS_URI="openstack/${IMDS_API_VERSION:-latest}"
			IMDS_HOSTNAME="meta_data.json/hostname"
			IMDS_LOCAL_HOSTNAME="$IMDS_HOSTNAME"
			IMDS_SSH_KEYS="meta_data.json/public_keys"
			IMDS_USERDATA="user_data"
			if [ "$IMDS_SELECTED_SOURCE" = http ]; then
				IMDS_DOCUMENT_DIR=$(mktemp -d "$TINY_CLOUD_RUN/openstack.XXXXXX") || {
					unset IMDS_SELECTED_FORMAT
					return 1
				}
				trap 'rm -rf "$IMDS_DOCUMENT_DIR"' 0
				trap 'exit 1' HUP INT TERM
			fi
			;;
		ec2)
			IMDS_URI="${IMDS_API_VERSION:-latest}"
			;;
	esac
	if [ -n "$IMDS_DOCUMENT_DIR" ]; then
		[ "$IMDS_SELECTED_FORMAT" != ec2 ] || IMDS_SSH_KEYS=meta-data.json/public-keys

		_imds_ssh_keys() {
			if [ "$IMDS_SELECTED_SOURCE" = http ]; then
				_imds_document "${IMDS_SSH_KEYS%%/*}" >/dev/null || return
			fi
			configdrive_ssh_keys "$IMDS_DOCUMENT_DIR" "$IMDS_SSH_KEYS" \
				"$IMDS_SELECTED_FORMAT"
		}
	fi
}

_imds_document() {
	local path="$1" document file tmpfile

	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
	document="${path%%/*}"
	case "$document" in
		*[!A-Za-z0-9_.-]*)
			echo "imds: invalid document name '$document'" >&2; return 1;;
	esac
	file="$IMDS_DOCUMENT_DIR/$document"
	if [ ! -f "$file" ]; then
		[ "$IMDS_SELECTED_SOURCE" != configdrive ] || return 1
		tmpfile=$(mktemp "$file.XXXXXX") || return
		_imds "$document" "$tmpfile" && mv "$tmpfile" "$file" || {
			rm -f "$tmpfile"
			return 1
		}
	fi
	configdrive_document "$IMDS_DOCUMENT_DIR" "$path"
}

# Nova does not expose AWS's per-interface metadata tree.
unset \
	IMDS_NICS \
	IMDS_MAC \
	IMDS_IPV4 \
	IMDS_IPV6 \
	IMDS_IPV4_NET \
	IMDS_IPV6_NET \
	IMDS_IPV4_PREFIX \
	IMDS_IPV6_PREFIX
