From 6d27d2179aea1f39727c1a621ca7052add2b3cfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jake=20Buchholz=20G=C3=B6kt=C3=BCrk?= Date: Sun, 14 Jun 2026 19:04:27 -0700 Subject: [PATCH 1/3] support IPv6/multiple IMDS endpoints resolves #68 --- CHANGELOG.md | 4 +++ README.md | 9 +++++ bin/imds | 54 ++++++++++++++++++++++++++-- docs/imds.1.scd | 8 +++++ docs/tiny-cloud.conf.5.scd | 8 ++++- lib/tiny-cloud/cloud/aws/imds | 6 +++- lib/tiny-cloud/tiny-cloud.conf | 4 +++ tests/bin/wget | 9 +++-- tests/imds.test | 65 ++++++++++++++++++++++++++++++++++ 9 files changed, 160 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc0cca7..ff72b97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 2026-06-XX - Tiny Cloud v3.3.3 + +* Support IPv6 and multiple endpoints [#68](https://gitlab.alpinelinux.org/alpine/cloud/tiny-cloud/-/work_items/68) + ## 2026-06-08 - Tiny Cloud v3.3.2 * Fixes an autodetect regression introduced in v3.3.1 diff --git a/README.md b/README.md index 17df451..cba82e1 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,15 @@ The default endpoint is `169.254.169.254` for most cloud providers. This setting allows you to specify a custom IP address and optional port for the metadata service. +Multiple endpoints can be configured with `IMDS_ENDPOINTS`. The list is tried +in order, and the most recently working endpoint is tried first on later +queries. `IMDS_ENDPOINTS` defaults to `IMDS_ENDPOINT` when unset. IPv6 endpoints +must use URL-style brackets: + +```sh +IMDS_ENDPOINTS="169.254.169.254 [fd00:ec2::254]" +``` + ### Metadata API Version Each provider's API has a built-in default version. You can override the diff --git a/bin/imds b/bin/imds index 5d3c657..5c5613c 100755 --- a/bin/imds +++ b/bin/imds @@ -53,6 +53,8 @@ unset -f \ # Common to many clouds # Can be overridden in /etc/tiny-cloud.conf : "${IMDS_ENDPOINT:=169.254.169.254}" +: "${IMDS_ENDPOINTS:=$IMDS_ENDPOINT}" +: "${IMDS_ENDPOINT_CACHE:=$TINY_CLOUD_VAR/.imds-endpoint}" # Common to AWS and NoCloud(ish) IMDS_HOSTNAME="meta-data/hostname" @@ -68,10 +70,56 @@ IMDS_IPV6_NET="subnet-ipv6-cidr-blocks" IMDS_IPV4_PREFIX="ipv4-prefix" IMDS_IPV6_PREFIX="ipv6-prefix" +_imds_endpoints() { + local cached e + cached="$(cat "$IMDS_ENDPOINT_CACHE" 2>/dev/null)" || : + for e in $IMDS_ENDPOINTS; do + [ "$e" = "$cached" ] && echo "$e" + done + for e in $IMDS_ENDPOINTS; do + [ "$e" != "$cached" ] && echo "$e" + done +} + +_imds_host_port() { + local host port + case "$1" in + \[*\]:*) + host="${1#\[}" + host="${host%%\]*}" + port="${1##*\]:}" + ;; + \[*\]) + host="${1#\[}" + host="${host%\]}" + port=80 + ;; + *:*) + host="${1%:*}" + port="${1##*:}" + ;; + *) + host="$1" + port=80 + ;; + esac + echo "$host" + echo "$port" +} + _imds() { - wget --quiet --timeout 1 --output-document - \ - --header "$(_imds_header)" \ - "http://$IMDS_ENDPOINT/$IMDS_URI/$1$IMDS_QUERY" + local endpoint + for endpoint in $(_imds_endpoints); do + IMDS_ENDPOINT="$endpoint" + IMDS_CURRENT_ENDPOINT="$endpoint" + wget --quiet --timeout 1 --output-document - \ + --header "$(_imds_header)" \ + "http://$endpoint/$IMDS_URI/$1$IMDS_QUERY" && { + echo "$endpoint" > "$IMDS_ENDPOINT_CACHE" + return 0 + } + done + return 1 } _imds_userdata() { _imds "$IMDS_USERDATA"; } diff --git a/docs/imds.1.scd b/docs/imds.1.scd index 190a4e8..5fc3e9d 100644 --- a/docs/imds.1.scd +++ b/docs/imds.1.scd @@ -98,6 +98,14 @@ inside *@nic:* queries. *CLOUD* Cloud provider name. When set to *auto*, the autodetected provider is used. +*IMDS_ENDPOINT* + Single provider metadata endpoint. Used as the default value for + *IMDS_ENDPOINTS*. + +*IMDS_ENDPOINTS* + Whitespace-separated provider metadata endpoint list. IPv6 endpoints must use + brackets, for example *[fd00:ec2::254]*. + # EXIT STATUS *0* diff --git a/docs/tiny-cloud.conf.5.scd b/docs/tiny-cloud.conf.5.scd index e2dceb5..f8f16aa 100644 --- a/docs/tiny-cloud.conf.5.scd +++ b/docs/tiny-cloud.conf.5.scd @@ -37,10 +37,16 @@ Blank lines and shell comments are ignored. Provider's API version to use. Providers that have versioned APIs have built-in default values. -*IMDS_ENDPOINT*= +*IMDS_ENDPOINT*= Provider endpoint IP address to use. Defaults to 169.254.169.254 for many providers. +*IMDS_ENDPOINTS*= ... + Whitespace-separated provider endpoint list. Defaults to *IMDS_ENDPOINT*. + Endpoints are tried in order, with the most recently working endpoint tried + first on later queries. IPv6 endpoints must use brackets, for example + *[fd00:ec2::254]* or *[fd00:ec2::254]:80*. + *IMDS_TOKEN_TTL*= Metadata token lifetime in seconds for AWS metadata access. This is only used by the AWS provider. The default is *5*. diff --git a/lib/tiny-cloud/cloud/aws/imds b/lib/tiny-cloud/cloud/aws/imds index 613b635..f5d0fdd 100644 --- a/lib/tiny-cloud/cloud/aws/imds +++ b/lib/tiny-cloud/cloud/aws/imds @@ -9,14 +9,18 @@ IMDS_TOKEN_TTL_HEADER="X-aws-ec2-metadata-token-ttl-seconds" IMDS_URI="$IMDS_API_VERSION" _imds_token() { + local host port # Only try to get token if using IMDSv2 # IMDSv1: API versions 2009-04-04 and earlier (no token support) # IMDSv2: API versions 2009-04-05 and later, or 'latest' (requires token) expr "$IMDS_API_VERSION" "<=" "2009-04-04" > /dev/null && return + set -- $(_imds_host_port "${IMDS_CURRENT_ENDPOINT:-$IMDS_ENDPOINT}") + host="$1" + port="$2" # IMDSv2 - request token printf "PUT /latest/api/token HTTP/1.0\r\n%s: %s\r\n\r\n" \ "$IMDS_TOKEN_TTL_HEADER" "$IMDS_TOKEN_TTL" \ - | nc -w 1 "$IMDS_ENDPOINT" 80 | tail -n 1 + | nc -w 1 "$host" "$port" | tail -n 1 } _imds_header() { diff --git a/lib/tiny-cloud/tiny-cloud.conf b/lib/tiny-cloud/tiny-cloud.conf index 006e0e0..50c5222 100644 --- a/lib/tiny-cloud/tiny-cloud.conf +++ b/lib/tiny-cloud/tiny-cloud.conf @@ -13,6 +13,10 @@ # Useful for custom metadata services #IMDS_ENDPOINT=169.254.169.254 +# Ordered IMDS endpoint list. Defaults to IMDS_ENDPOINT. +# IPv6 endpoints must use brackets: [fd00:ec2::254] or [fd00:ec2::254]:80 +#IMDS_ENDPOINTS="169.254.169.254 [fd00:ec2::254]" + # IMDS API version # Most providers have a default version, overrideable here if necessary #IMDS_API_VERSION="" diff --git a/tests/bin/wget b/tests/bin/wget index b600f7d..f720c85 100755 --- a/tests/bin/wget +++ b/tests/bin/wget @@ -83,6 +83,12 @@ host="${host%%/*}" path="${url#http*://$host}" path="${path#${WGET_STRIP_PREFIX:-/}}" path="${path%\?*}" +[ -n "$WGET_HOST_LOG" ] && echo "$host" >> "$WGET_HOST_LOG" +case "$host" in + \[*\]:*) yaml_host="${host#\[}"; yaml_host="${yaml_host%%\]*}";; + \[*\]) yaml_host="${host#\[}"; yaml_host="${yaml_host%\]}";; + *) yaml_host="$host";; +esac if [ -z "$WGETCONTENT" ]; then @@ -93,7 +99,7 @@ if [ -z "$WGETCONTENT" ]; then ( IFS=/ set -- ${path#/} - yx -f "${WGET_YAML:-$host.yaml}" "$@" 2>/dev/null + yx -f "${WGET_YAML:-$yaml_host.yaml}" "$@" 2>/dev/null ) fi fi @@ -107,4 +113,3 @@ case "$outfile" in echo "$WGETCONTENT" > "$outfile" ;; esac - diff --git a/tests/imds.test b/tests/imds.test index b7b9c94..ff4558d 100755 --- a/tests/imds.test +++ b/tests/imds.test @@ -10,6 +10,9 @@ PROVIDERS="aws azure digitalocean gcp hetzner incus oci nocloud scaleway" init_tests \ imds_help \ imds_space \ + imds_endpoint_fallback \ + imds_endpoint_cache \ + imds_endpoint_ipv6 \ \ imds_hostname_aws \ imds_hostname_azure \ @@ -44,6 +47,7 @@ init_tests \ imds_aws_api_version_imdsv1 \ imds_aws_api_version_imdsv2_explicit \ imds_aws_api_version_imdsv2_latest \ + imds_aws_token_endpoint_port \ \ imds_nocloud_cmdline_local_hostname \ imds_nocloud_smbios_local_hostname \ @@ -63,6 +67,45 @@ imds_space_body() { done } +imds_endpoint_fallback_body() { + IMDS_API_VERSION=2009-04-04 CLOUD=aws fake_metadata aws <<-EOF + hostname: myhostname + EOF + IMDS_API_VERSION=2009-04-04 IMDS_ENDPOINTS="fail 169.254.169.254" CLOUD=aws atf_check \ + -o match:"myhostname" \ + imds @hostname + atf_check -o match:"^169.254.169.254$" cat var/lib/cloud/.imds-endpoint +} + +imds_endpoint_cache_body() { + mkdir -p var/lib/cloud + echo "cached.example" > var/lib/cloud/.imds-endpoint + cat > cached.example.yaml <<-EOF + hostname: cached-hostname + EOF + cat > first.example.yaml <<-EOF + hostname: first-hostname + EOF + IMDS_API_VERSION=2009-04-04 WGET_STRIP_PREFIX="/2009-04-04/meta-data" \ + WGET_HOST_LOG="$PWD/hosts.log" \ + IMDS_ENDPOINTS="first.example cached.example" CLOUD=aws atf_check \ + -o match:"cached-hostname" \ + imds @hostname + atf_check -o match:"^cached.example$" head -n 1 hosts.log +} + +imds_endpoint_ipv6_body() { + cat > "fd00:ec2::254.yaml" <<-EOF + hostname: ipv6-hostname + EOF + IMDS_API_VERSION=2009-04-04 WGET_STRIP_PREFIX="/2009-04-04/meta-data" \ + WGET_HOST_LOG="$PWD/hosts.log" \ + IMDS_ENDPOINTS="[fd00:ec2::254]" CLOUD=aws atf_check \ + -o match:"ipv6-hostname" \ + imds @hostname + atf_check -o match:"^\\[fd00:ec2::254\\]$" cat hosts.log +} + check_hostname() { fake_metadata "$1" <<-EOF # aws, digitalocean, hetzner, nocloud @@ -251,6 +294,28 @@ imds_aws_api_version_imdsv2_latest_body() { imds @hostname } +imds_aws_token_endpoint_port_body() { + cat > "fd00:ec2::254.yaml" <<-EOF + hostname: test-imdsv2-port + EOF + fake_bin nc <<-'NCEOF' + #!/bin/sh + while [ -n "$1" ]; do + case "$1" in + -w) shift 2;; + *) echo "$1" >> nc.args; shift;; + esac + done + cat > /dev/null + printf "HTTP/1.0 200 OK\r\n\r\nmock-token" + NCEOF + IMDS_API_VERSION=latest WGET_STRIP_PREFIX="/latest/meta-data" \ + IMDS_ENDPOINTS="[fd00:ec2::254]:8080" CLOUD=aws atf_check \ + -o match:"test-imdsv2-port" \ + imds @hostname + atf_check -o match:"^fd00:ec2::254 8080 $" sh -c "tr '\n' ' ' < nc.args" +} + imds_nocloud_cmdline_local_hostname_body() { atf_require_prog yx mkdir proc From 75871161ba1bea293024d53086f4d8995620a43a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jake=20Buchholz=20G=C3=B6kt=C3=BCrk?= Date: Sun, 14 Jun 2026 19:27:53 -0700 Subject: [PATCH 2/3] fix fake wget --- tests/bin/wget | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/bin/wget b/tests/bin/wget index f720c85..a8ef8b0 100755 --- a/tests/bin/wget +++ b/tests/bin/wget @@ -78,9 +78,11 @@ for url; do esac done -host="${url#http*://}" -host="${host%%/*}" -path="${url#http*://$host}" +url_no_scheme="${url#*://}" +case "$url_no_scheme" in + */*) host="${url_no_scheme%%/*}"; path="/${url_no_scheme#*/}";; + *) host="$url_no_scheme"; path=/;; +esac path="${path#${WGET_STRIP_PREFIX:-/}}" path="${path%\?*}" [ -n "$WGET_HOST_LOG" ] && echo "$host" >> "$WGET_HOST_LOG" From b9e1857a593b4c82a205308090707a6c85e2ecc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jake=20Buchholz=20G=C3=B6kt=C3=BCrk?= Date: Sun, 14 Jun 2026 20:50:57 -0700 Subject: [PATCH 3/3] add IMDS endpoint route checking --- CHANGELOG.md | 6 ++++- README.md | 4 ++++ bin/imds | 33 +++++++++++++++++++++++-- docs/imds.1.scd | 4 ++++ docs/tiny-cloud.conf.5.scd | 4 ++++ lib/tiny-cloud/tiny-cloud.conf | 4 ++++ tests/imds.test | 44 +++++++++++++++++++++++++++++++--- tests/test_env.sh | 1 + 8 files changed, 94 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff72b97..16bbc44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ ## 2026-06-XX - Tiny Cloud v3.3.3 -* Support IPv6 and multiple endpoints [#68](https://gitlab.alpinelinux.org/alpine/cloud/tiny-cloud/-/work_items/68) +* Support IPv6 and multiple endpoints + [#68](https://gitlab.alpinelinux.org/alpine/cloud/tiny-cloud/-/work_items/68) +* Check for IP routes to IMDS endpoints before trying them; retry if none are + routable. Fixes race condition between `dhcpcd` starting and attempting to + reach IMDS before routes are resolved. ## 2026-06-08 - Tiny Cloud v3.3.2 diff --git a/README.md b/README.md index cba82e1..e35d602 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,10 @@ must use URL-style brackets: IMDS_ENDPOINTS="169.254.169.254 [fd00:ec2::254]" ``` +Tiny Cloud checks for routes to configured IMDS endpoints before trying metadata +requests. `IMDS_ENDPOINT_WAIT_ATTEMPTS` controls how many route checks are made +before metadata requests are tried anyway. + ### Metadata API Version Each provider's API has a built-in default version. You can override the diff --git a/bin/imds b/bin/imds index 5c5613c..1d0efe1 100755 --- a/bin/imds +++ b/bin/imds @@ -55,6 +55,7 @@ unset -f \ : "${IMDS_ENDPOINT:=169.254.169.254}" : "${IMDS_ENDPOINTS:=$IMDS_ENDPOINT}" : "${IMDS_ENDPOINT_CACHE:=$TINY_CLOUD_VAR/.imds-endpoint}" +: "${IMDS_ENDPOINT_WAIT_ATTEMPTS:=10}" # Common to AWS and NoCloud(ish) IMDS_HOSTNAME="meta-data/hostname" @@ -107,9 +108,37 @@ _imds_host_port() { echo "$port" } +_imds_has_route() { + local host + set -- $(_imds_host_port "$1") + host="$1" + case "$host" in + *:*) ip -6 route get "$host" >/dev/null 2>&1 ;; + [0-9]*.[0-9]*.[0-9]*.[0-9]*) ip route get "$host" >/dev/null 2>&1 ;; + *) return 0 ;; + esac +} + _imds() { - local endpoint - for endpoint in $(_imds_endpoints); do + local endpoint endpoints routed attempts=1 + while :; do + endpoints= + routed= + for endpoint in $(_imds_endpoints); do + if _imds_has_route "$endpoint"; then + endpoints="$endpoints $endpoint" + routed=1 + fi + done + [ -n "$routed" ] && break + [ "$attempts" -ge "$IMDS_ENDPOINT_WAIT_ATTEMPTS" ] && { + endpoints="$(_imds_endpoints)" + break + } + sleep 1 + attempts=$((attempts + 1)) + done + for endpoint in $endpoints; do IMDS_ENDPOINT="$endpoint" IMDS_CURRENT_ENDPOINT="$endpoint" wget --quiet --timeout 1 --output-document - \ diff --git a/docs/imds.1.scd b/docs/imds.1.scd index 5fc3e9d..afaa608 100644 --- a/docs/imds.1.scd +++ b/docs/imds.1.scd @@ -106,6 +106,10 @@ inside *@nic:* queries. Whitespace-separated provider metadata endpoint list. IPv6 endpoints must use brackets, for example *[fd00:ec2::254]*. +*IMDS_ENDPOINT_WAIT_ATTEMPTS* + Number of times to check for routes to any IMDS endpoint before trying + metadata requests anyway. The default is *10*. + # EXIT STATUS *0* diff --git a/docs/tiny-cloud.conf.5.scd b/docs/tiny-cloud.conf.5.scd index f8f16aa..e50fc71 100644 --- a/docs/tiny-cloud.conf.5.scd +++ b/docs/tiny-cloud.conf.5.scd @@ -47,6 +47,10 @@ Blank lines and shell comments are ignored. first on later queries. IPv6 endpoints must use brackets, for example *[fd00:ec2::254]* or *[fd00:ec2::254]:80*. +*IMDS_ENDPOINT_WAIT_ATTEMPTS*= + Number of times to check for routes to any IMDS endpoint before trying + metadata requests anyway. The default is *10*. + *IMDS_TOKEN_TTL*= Metadata token lifetime in seconds for AWS metadata access. This is only used by the AWS provider. The default is *5*. diff --git a/lib/tiny-cloud/tiny-cloud.conf b/lib/tiny-cloud/tiny-cloud.conf index 50c5222..16528d6 100644 --- a/lib/tiny-cloud/tiny-cloud.conf +++ b/lib/tiny-cloud/tiny-cloud.conf @@ -17,6 +17,10 @@ # IPv6 endpoints must use brackets: [fd00:ec2::254] or [fd00:ec2::254]:80 #IMDS_ENDPOINTS="169.254.169.254 [fd00:ec2::254]" +# Number of times to check for routes to any IMDS endpoint before trying +# metadata requests anyway. +#IMDS_ENDPOINT_WAIT_ATTEMPTS=10 + # IMDS API version # Most providers have a default version, overrideable here if necessary #IMDS_API_VERSION="" diff --git a/tests/imds.test b/tests/imds.test index ff4558d..a8388a3 100755 --- a/tests/imds.test +++ b/tests/imds.test @@ -13,6 +13,8 @@ init_tests \ imds_endpoint_fallback \ imds_endpoint_cache \ imds_endpoint_ipv6 \ + imds_endpoint_route_skip \ + imds_endpoint_route_wait \ \ imds_hostname_aws \ imds_hostname_azure \ @@ -71,7 +73,8 @@ imds_endpoint_fallback_body() { IMDS_API_VERSION=2009-04-04 CLOUD=aws fake_metadata aws <<-EOF hostname: myhostname EOF - IMDS_API_VERSION=2009-04-04 IMDS_ENDPOINTS="fail 169.254.169.254" CLOUD=aws atf_check \ + IMDS_API_VERSION=2009-04-04 IMDS_ENDPOINT_WAIT_ATTEMPTS=0 \ + IMDS_ENDPOINTS="fail 169.254.169.254" CLOUD=aws atf_check \ -o match:"myhostname" \ imds @hostname atf_check -o match:"^169.254.169.254$" cat var/lib/cloud/.imds-endpoint @@ -86,7 +89,8 @@ imds_endpoint_cache_body() { cat > first.example.yaml <<-EOF hostname: first-hostname EOF - IMDS_API_VERSION=2009-04-04 WGET_STRIP_PREFIX="/2009-04-04/meta-data" \ + IMDS_API_VERSION=2009-04-04 IMDS_ENDPOINT_WAIT_ATTEMPTS=0 \ + WGET_STRIP_PREFIX="/2009-04-04/meta-data" \ WGET_HOST_LOG="$PWD/hosts.log" \ IMDS_ENDPOINTS="first.example cached.example" CLOUD=aws atf_check \ -o match:"cached-hostname" \ @@ -98,7 +102,8 @@ imds_endpoint_ipv6_body() { cat > "fd00:ec2::254.yaml" <<-EOF hostname: ipv6-hostname EOF - IMDS_API_VERSION=2009-04-04 WGET_STRIP_PREFIX="/2009-04-04/meta-data" \ + IMDS_API_VERSION=2009-04-04 IMDS_ENDPOINT_WAIT_ATTEMPTS=0 \ + WGET_STRIP_PREFIX="/2009-04-04/meta-data" \ WGET_HOST_LOG="$PWD/hosts.log" \ IMDS_ENDPOINTS="[fd00:ec2::254]" CLOUD=aws atf_check \ -o match:"ipv6-hostname" \ @@ -106,6 +111,39 @@ imds_endpoint_ipv6_body() { atf_check -o match:"^\\[fd00:ec2::254\\]$" cat hosts.log } +imds_endpoint_route_skip_body() { + IMDS_API_VERSION=2009-04-04 CLOUD=aws fake_metadata aws <<-EOF + hostname: myhostname + EOF + fake_bin ip <<-'EOF' + #!/bin/sh + [ "$3" = 169.254.169.254 ] + EOF + IMDS_API_VERSION=2009-04-04 \ + IMDS_ENDPOINTS="192.0.2.1 169.254.169.254" CLOUD=aws atf_check \ + -o match:"myhostname" \ + imds @hostname + atf_check -o match:"^169.254.169.254$" cat var/lib/cloud/.imds-endpoint +} + +imds_endpoint_route_wait_body() { + IMDS_API_VERSION=2009-04-04 CLOUD=aws fake_metadata aws <<-EOF + hostname: myhostname + EOF + fake_bin ip <<-'EOF' + #!/bin/sh + mkdir -p tmp + count=$(cat tmp/route-count 2>/dev/null || echo 0) + count=$((count + 1)) + echo "$count" > tmp/route-count + [ "$count" -gt 1 ] + EOF + IMDS_API_VERSION=2009-04-04 IMDS_ENDPOINT_WAIT_ATTEMPTS=5 CLOUD=aws atf_check \ + -o match:"myhostname" \ + imds @hostname + atf_check -o match:"^2$" cat tmp/route-count +} + check_hostname() { fake_metadata "$1" <<-EOF # aws, digitalocean, hetzner, nocloud diff --git a/tests/test_env.sh b/tests/test_env.sh index f96dc26..70dab9a 100644 --- a/tests/test_env.sh +++ b/tests/test_env.sh @@ -6,6 +6,7 @@ PATH="$atf_srcdir/bin:$srcdir/bin:$srcdir/sbin:$PATH" export TINY_CLOUD_BASEDIR="$srcdir" export ROOT="$PWD" +export IMDS_ENDPOINT_WAIT_ATTEMPTS=0 init_tests() {