From ff31d3c77b930806c68d3f051c77994b6d158a5e Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Tue, 27 Jan 2026 14:45:32 +0100 Subject: [PATCH 01/16] test retry loops to create ephemeral instances --- .github/workflows/ephemeral.yml | 5 +- ephemeral/shutdown/action.yml | 48 +++++++++---- ephemeral/startup/action.yml | 120 +++++++++++++++++++++++++------- 3 files changed, 136 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ephemeral.yml b/.github/workflows/ephemeral.yml index 1da24a3..4c4fc09 100644 --- a/.github/workflows/ephemeral.yml +++ b/.github/workflows/ephemeral.yml @@ -1,6 +1,9 @@ name: LocalStack Ephemeral Instance Test on: - workflow_dispatch: + pull_request: + paths-ignore: + - ./*.md + - LICENSE jobs: preview-test: diff --git a/ephemeral/shutdown/action.yml b/ephemeral/shutdown/action.yml index 6b7910b..89bae14 100644 --- a/ephemeral/shutdown/action.yml +++ b/ephemeral/shutdown/action.yml @@ -1,9 +1,10 @@ name: Shutdown Ephemeral Instance +description: 'Shutdowns an Ephemeral Instance (PR Preview)' inputs: - localstack-api-key: - description: 'LocalStack API key used to access the platform api' - required: true + localstack-auth-token: + description: 'LocalStack Auth Token used to access the platform api' + required: false github-token: description: 'Github token used to create PR comments' required: true @@ -34,16 +35,39 @@ runs: - name: Shutdown ephemeral instance shell: bash run: | - response=$(curl -X DELETE \ - -s -o /dev/null -w "%{http_code}" \ - -H "ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-api-key }}}}" \ - -H "content-type: application/json" \ - https://api.localstack.cloud/v1/compute/instances/$previewName) - if [[ "$response" -ne 200 ]]; then - # In case the deletion fails, e.g. if the instance cannot be found, we raise a proper error on the platform - echo "Unable to delete preview environment. API response: $response" - exit 1 + AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-auth-token }}}}" + CONTENT_TYPE_HEADER="content-type: application/json" + API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" + + retry() { + local retries=5 + local count=0 + local wait=5 + while [ $count -lt $retries ]; do + "$@" + local exit_code=$? + if [ $exit_code -eq 0 ]; then + return 0 + fi + count=$((count + 1)) + echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" + sleep $wait + done + echo "Command failed after $retries retries." + return 1 + } + + shutdown_instance() { + http_code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ + -H "$AUTH_HEADER" \ + -H "$CONTENT_TYPE_HEADER" \ + "$API_URL_BASE/$previewName") + if [[ "$http_code" -ne 200 && "$http_code" -ne 404 ]]; then + echo "Unable to delete preview environment. API returned HTTP code: $http_code" + return 1 + fi fi + retry shutdown_instance - name: Update status comment uses: actions-cool/maintain-one-comment@v3.1.1 diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index fb4bce1..02f8180 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -1,11 +1,12 @@ name: Create PR Preview +description: 'Spins up an Ephemeral Instance for a PR Preview' inputs: github-token: description: 'Github token used to create PR comments' required: true - localstack-api-key: - description: 'LocalStack API key used to create the preview environment' + localstack-auth-token: + description: 'LocalStack Auth Token used to create the preview environment' required: false preview-cmd: description: 'Command(s) used to create a preview of the PR (can use $AWS_ENDPOINT_URL)' @@ -68,33 +69,78 @@ runs: shell: bash id: create-instance run: | + AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-auth-token }}}}" + CONTENT_TYPE_HEADER="content-type: application/json" + API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" + + retry() { + local retries=5 + local count=0 + local wait=5 + while [ $count -lt $retries ]; do + "$@" + local exit_code=$? + if [ $exit_code -eq 0 ]; then + return 0 + fi + count=$((count + 1)) + echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" + sleep $wait + done + echo "Command failed after $retries retries." + return 1 + } + + fetch_instances() { + list_response=$(curl --fail-with-body -s -X GET \ + -H "$AUTH_HEADER" \ + -H "$CONTENT_TYPE_HEADER" \ + "$API_URL_BASE") + if echo "$list_response" | jq -e '.error == true' > /dev/null; then + echo "API returned an error: $list_response" + return 1 + fi + } + + retry fetch_instances + autoLoadPod="${AUTO_LOAD_POD:-${{ inputs.auto-load-pod }}}" extensionAutoInstall="${EXTENSION_AUTO_INSTALL:-${{ inputs.extension-auto-install }}}" lifetime="${{ inputs.lifetime }}" - list_response=$(curl -X GET \ - -H "ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-api-key }}}}" \ - -H "content-type: application/json" \ - https://api.localstack.cloud/v1/compute/instances) - instance_exists=$(echo "$list_response" | jq --arg NAME "$previewName" '.[] | select(.instance_name == $NAME)') + delete_instance() { + # We expect a 200 on success or 404 if it's already gone. Other codes are errors. + http_code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ + -H "$AUTH_HEADER" \ + -H "$CONTENT_TYPE_HEADER" \ + "$API_URL_BASE/$previewName") + if [ "$http_code" -ne 200 ] && [ "$http_code" -ne 404 ]; then + echo "Error deleting instance, HTTP code: $http_code" + return 1 + fi + } + if [ -n "$instance_exists" ]; then - del_response=$(curl -X DELETE \ - -H "ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-api-key }}}}" \ - -H "content-type: application/json" \ - https://api.localstack.cloud/v1/compute/instances/$previewName) + retry delete_instance fi - response=$(curl -X POST -d "{\"instance_name\": \"${previewName}\", \"lifetime\": ${lifetime} ,\"env_vars\": {\"AUTO_LOAD_POD\": \"${autoLoadPod}\", \"EXTENSION_AUTO_INSTALL\": \"${extensionAutoInstall}\"}}"\ - -H "ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-api-key }}}}" \ - -H "content-type: application/json" \ - https://api.localstack.cloud/v1/compute/instances) - endpointUrl=$(echo "$response" | jq -r .endpoint_url) - if [ "$endpointUrl" = "null" ] || [ "$endpointUrl" = "" ]; then - echo "Unable to create preview environment. API response: $response" - exit 1 + create_instance_func() { + response=$(curl --fail-with-body -s -X POST -d "{\"instance_name\": \"${previewName}\", \"lifetime\": ${lifetime} ,\"env_vars\": {\"AUTO_LOAD_POD\": \"${autoLoadPod}\", \"EXTENSION_AUTO_INSTALL\": \"${extensionAutoInstall}\"}}"\ + -H "$AUTH_HEADER" \ + -H "$CONTENT_TYPE_HEADER" \ + "$API_URL_BASE") + + endpointUrl=$(echo "$response" | jq -r .endpoint_url) + if [ "$endpointUrl" = "null" ] || [ "$endpointUrl" = "" ]; then + echo "Unable to create preview environment. API response: $response" + return 1 + fi fi + + retry create_instance_func + echo "Created preview environment with endpoint URL: $endpointUrl" echo $endpointUrl > ./ls-preview-url.txt @@ -117,9 +163,35 @@ runs: if: ${{ !cancelled() && steps.create-instance.outcome == 'success' }} shell: bash run: | - log_response=$(curl -X GET \ - -H "ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-api-key }}}}" \ - -H "content-type: application/json" \ - https://api.localstack.cloud/v1/compute/instances/$previewName/logs) - + AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-auth-token }}}}" + CONTENT_TYPE_HEADER="content-type: application/json" + API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" + + # TODO code duplication + retry() { + local retries=5 + local count=0 + local wait=5 + while [ $count -lt $retries ]; do + "$@" + local exit_code=$? + if [ $exit_code -eq 0 ]; then + return 0 + fi + count=$((count + 1)) + echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" + sleep $wait + done + echo "Command failed after $retries retries." + return 1 + } + + fetch_logs() { + log_response=$(curl --fail-with-body -s -X GET \ + -H "$AUTH_HEADER" \ + -H "$CONTENT_TYPE_HEADER" \ + "$API_URL_BASE/$previewName/logs") + } + + retry fetch_logs echo "$log_response" | jq -r '.[].content' From 3ca264ff5cc388c715f038fad88b780e091f20b2 Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Tue, 27 Jan 2026 14:53:07 +0100 Subject: [PATCH 02/16] fixup --- ephemeral/shutdown/action.yml | 2 +- ephemeral/startup/action.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ephemeral/shutdown/action.yml b/ephemeral/shutdown/action.yml index 89bae14..e444ce0 100644 --- a/ephemeral/shutdown/action.yml +++ b/ephemeral/shutdown/action.yml @@ -66,7 +66,7 @@ runs: echo "Unable to delete preview environment. API returned HTTP code: $http_code" return 1 fi - fi + } retry shutdown_instance - name: Update status comment diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index 02f8180..e5d55f9 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -96,7 +96,7 @@ runs: -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE") - if echo "$list_response" | jq -e '.error == true' > /dev/null; then + if ! echo "$list_response" | jq -e 'type == "array"' > /dev/null; then echo "API returned an error: $list_response" return 1 fi @@ -137,7 +137,7 @@ runs: echo "Unable to create preview environment. API response: $response" return 1 fi - fi + } retry create_instance_func From dc0474e4e48eb84947d55246ba15d57fcdffc528 Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Tue, 27 Jan 2026 15:49:12 +0100 Subject: [PATCH 03/16] fixup --- ephemeral/startup/action.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index e5d55f9..c016094 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -58,12 +58,14 @@ runs: - name: Setup preview name shell: bash + id: preview-name run: | prId=$(> $GITHUB_ENV + echo "name=$previewName" >> $GITHUB_OUTPUT - name: Create preview environment shell: bash @@ -92,7 +94,7 @@ runs: } fetch_instances() { - list_response=$(curl --fail-with-body -s -X GET \ + list_response=$(curl --fail-with-body -X GET \ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE") @@ -112,7 +114,7 @@ runs: delete_instance() { # We expect a 200 on success or 404 if it's already gone. Other codes are errors. - http_code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ + http_code=$(curl -o /dev/null -w "%{http_code}" -X DELETE \ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE/$previewName") @@ -127,7 +129,7 @@ runs: fi create_instance_func() { - response=$(curl --fail-with-body -s -X POST -d "{\"instance_name\": \"${previewName}\", \"lifetime\": ${lifetime} ,\"env_vars\": {\"AUTO_LOAD_POD\": \"${autoLoadPod}\", \"EXTENSION_AUTO_INSTALL\": \"${extensionAutoInstall}\"}}"\ + response=$(curl --fail-with-body -X POST -d "{\"instance_name\": \"${previewName}\", \"lifetime\": ${lifetime} ,\"env_vars\": {\"AUTO_LOAD_POD\": \"${autoLoadPod}\", \"EXTENSION_AUTO_INSTALL\": \"${extensionAutoInstall}\"}}"\ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE") @@ -156,12 +158,14 @@ runs: - name: Run preview deployment if: ${{ inputs.preview-cmd != '' }} shell: bash - run: + run: | ${{ inputs.preview-cmd }} - name: Print logs of ephemeral instance if: ${{ !cancelled() && steps.create-instance.outcome == 'success' }} shell: bash + env: + previewName: ${{ steps.preview-name.outputs.name }} run: | AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-auth-token }}}}" CONTENT_TYPE_HEADER="content-type: application/json" From 363f2879fe9f5896d185a1cba4b68164421d0524 Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Tue, 27 Jan 2026 16:05:54 +0100 Subject: [PATCH 04/16] fixup --- ephemeral/startup/action.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index c016094..4a02bea 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -114,7 +114,7 @@ runs: delete_instance() { # We expect a 200 on success or 404 if it's already gone. Other codes are errors. - http_code=$(curl -o /dev/null -w "%{http_code}" -X DELETE \ + http_code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE/$previewName") @@ -125,11 +125,12 @@ runs: } if [ -n "$instance_exists" ]; then + echo "Found existing instance using '$previewName', trying to delete the old one..." retry delete_instance fi create_instance_func() { - response=$(curl --fail-with-body -X POST -d "{\"instance_name\": \"${previewName}\", \"lifetime\": ${lifetime} ,\"env_vars\": {\"AUTO_LOAD_POD\": \"${autoLoadPod}\", \"EXTENSION_AUTO_INSTALL\": \"${extensionAutoInstall}\"}}"\ + response=$(curl --fail-with-body -s -X POST -d "{\"instance_name\": \"${previewName}\", \"lifetime\": ${lifetime} ,\"env_vars\": {\"AUTO_LOAD_POD\": \"${autoLoadPod}\", \"EXTENSION_AUTO_INSTALL\": \"${extensionAutoInstall}\"}}"\ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE") @@ -141,6 +142,7 @@ runs: fi } + echo "Creating preview environment ..." retry create_instance_func echo "Created preview environment with endpoint URL: $endpointUrl" @@ -196,6 +198,6 @@ runs: -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE/$previewName/logs") } - + echo "Fetching logs for $previewName ..." retry fetch_logs - echo "$log_response" | jq -r '.[].content' + echo "Logs:\n$log_response" | jq -r '.[].content' From b7d3d582b7fcd507d1eb8f9fb50f18b89a1fb96e Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Tue, 27 Jan 2026 16:13:22 +0100 Subject: [PATCH 05/16] set +e inside the retry loop --- ephemeral/shutdown/action.yml | 2 +- ephemeral/startup/action.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ephemeral/shutdown/action.yml b/ephemeral/shutdown/action.yml index e444ce0..0721d18 100644 --- a/ephemeral/shutdown/action.yml +++ b/ephemeral/shutdown/action.yml @@ -44,7 +44,7 @@ runs: local count=0 local wait=5 while [ $count -lt $retries ]; do - "$@" + ( set +e; "$@"; ) # Run command in subshell with set -e disabled local exit_code=$? if [ $exit_code -eq 0 ]; then return 0 diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index 4a02bea..1a6e084 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -80,7 +80,7 @@ runs: local count=0 local wait=5 while [ $count -lt $retries ]; do - "$@" + ( set +e; "$@"; ) # Run command in subshell with set -e disabled local exit_code=$? if [ $exit_code -eq 0 ]; then return 0 @@ -94,7 +94,7 @@ runs: } fetch_instances() { - list_response=$(curl --fail-with-body -X GET \ + list_response=$(curl --fail-with-body -s -X GET \ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE") From 44a4b1e1fd7064e662c632affdf889a4d645ed73 Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Tue, 27 Jan 2026 16:40:26 +0100 Subject: [PATCH 06/16] add more debug messages --- ephemeral/shutdown/action.yml | 14 ++++++++---- ephemeral/startup/action.yml | 40 +++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/ephemeral/shutdown/action.yml b/ephemeral/shutdown/action.yml index 0721d18..84e7423 100644 --- a/ephemeral/shutdown/action.yml +++ b/ephemeral/shutdown/action.yml @@ -58,13 +58,19 @@ runs: } shutdown_instance() { - http_code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ + # The API returns a 200 on successful deletion. + # We use --fail-with-body so curl fails on server errors (5xx) and triggers the retry. + # We allow a 404 since that means the instance is already gone. + http_code=$(curl --fail-with-body -s -o /dev/null -w "%{http_code}" -X DELETE \ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE/$previewName") - if [[ "$http_code" -ne 200 && "$http_code" -ne 404 ]]; then - echo "Unable to delete preview environment. API returned HTTP code: $http_code" - return 1 + if [ $? -eq 0 ]; then + if [ "$http_code" -eq 200 ]; then + echo "Instance '$previewName' deleted successfully." + elif [ "$http_code" -eq 404 ]; then + echo "Instance '$previewName' was already deleted (not found)." + fi fi } retry shutdown_instance diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index 1a6e084..995e100 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -94,17 +94,19 @@ runs: } fetch_instances() { + local list_response list_response=$(curl --fail-with-body -s -X GET \ - -H "$AUTH_HEADER" \ - -H "$CONTENT_TYPE_HEADER" \ - "$API_URL_BASE") - if ! echo "$list_response" | jq -e 'type == "array"' > /dev/null; then - echo "API returned an error: $list_response" - return 1 + -H "$AUTH_HEADER" \ + -H "$CONTENT_TYPE_HEADER" \ + "$API_URL_BASE") + if [ $? -ne 0 ]; then + echo "API returned an error: $list_response" + return 1 fi + echo "$list_response" } - retry fetch_instances + list_response=$(retry fetch_instances) autoLoadPod="${AUTO_LOAD_POD:-${{ inputs.auto-load-pod }}}" extensionAutoInstall="${EXTENSION_AUTO_INSTALL:-${{ inputs.extension-auto-install }}}" @@ -130,20 +132,21 @@ runs: fi create_instance_func() { + local response response=$(curl --fail-with-body -s -X POST -d "{\"instance_name\": \"${previewName}\", \"lifetime\": ${lifetime} ,\"env_vars\": {\"AUTO_LOAD_POD\": \"${autoLoadPod}\", \"EXTENSION_AUTO_INSTALL\": \"${extensionAutoInstall}\"}}"\ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE") - - endpointUrl=$(echo "$response" | jq -r .endpoint_url) - if [ "$endpointUrl" = "null" ] || [ "$endpointUrl" = "" ]; then + if [ $? -ne 0 ] || ! echo "$response" | jq -e 'has("endpoint_url") and (.endpoint_url | test(".+"))' > /dev/null; then echo "Unable to create preview environment. API response: $response" return 1 fi + echo "$response" } echo "Creating preview environment ..." - retry create_instance_func + response=$(retry create_instance_func) + endpointUrl=$(echo "$response" | jq -r .endpoint_url) echo "Created preview environment with endpoint URL: $endpointUrl" @@ -193,11 +196,22 @@ runs: } fetch_logs() { + local log_response log_response=$(curl --fail-with-body -s -X GET \ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE/$previewName/logs") + if [ $? -ne 0 ]; then + echo "API returned an error while fetching logs: $log_response" + return 1 + fi + if [ -z "$log_response" ]; then + echo "API returned an empty response when fetching logs." + return 1 + fi + echo "$log_response" } echo "Fetching logs for $previewName ..." - retry fetch_logs - echo "Logs:\n$log_response" | jq -r '.[].content' + log_response=$(retry fetch_logs) + echo "Logs:" + echo "$log_response" | jq -r '.[].content' From 8d3353d17955e22586f2364b7994b8e9ecf205af Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Tue, 27 Jan 2026 16:53:04 +0100 Subject: [PATCH 07/16] retry retrieving logs --- ephemeral/startup/action.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index 995e100..77e19ee 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -209,6 +209,16 @@ runs: echo "API returned an empty response when fetching logs." return 1 fi + + # Now, check if jq extracts any content + local extracted_content + extracted_content=$(echo "$log_response" | jq -r '.[].content') + if [ -z "$extracted_content" ]; then + echo "No content found in logs or logs are empty after extraction." + echo "Raw API response for logs: $log_response" # This will be printed on retry attempts + return 1 + fi + echo "$log_response" } echo "Fetching logs for $previewName ..." From 89ed7049a580bd6c9477c93165f5ccafce4fd833 Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Tue, 27 Jan 2026 17:06:19 +0100 Subject: [PATCH 08/16] adding more debug information for logs --- ephemeral/shutdown/action.yml | 4 ++-- ephemeral/startup/action.yml | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ephemeral/shutdown/action.yml b/ephemeral/shutdown/action.yml index 84e7423..2b5d579 100644 --- a/ephemeral/shutdown/action.yml +++ b/ephemeral/shutdown/action.yml @@ -50,10 +50,10 @@ runs: return 0 fi count=$((count + 1)) - echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" + echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" > /dev/tty sleep $wait done - echo "Command failed after $retries retries." + echo "Command failed after $retries retries." > /dev/tty return 1 } diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index 77e19ee..e736538 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -86,10 +86,10 @@ runs: return 0 fi count=$((count + 1)) - echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" + echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" > /dev/tty sleep $wait done - echo "Command failed after $retries retries." + echo "Command failed after $retries retries." > /dev/tty return 1 } @@ -188,10 +188,10 @@ runs: return 0 fi count=$((count + 1)) - echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" + echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" > /dev/tty sleep $wait done - echo "Command failed after $retries retries." + echo "Command failed after $retries retries." > /dev/tty return 1 } @@ -223,5 +223,9 @@ runs: } echo "Fetching logs for $previewName ..." log_response=$(retry fetch_logs) + if [ -z "$log_response" ]; then + echo "Error: Failed to fetch logs after multiple retries." + exit 1 + fi echo "Logs:" echo "$log_response" | jq -r '.[].content' From 8f70f753b48440e8c563049afb6863ede3fb5dd3 Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Tue, 27 Jan 2026 17:17:21 +0100 Subject: [PATCH 09/16] refactored retry function into separate script --- ephemeral/shutdown/action.yml | 24 ++++----------- ephemeral/startup/action.yml | 47 ++++++----------------------- ephemeral/startup/retry-function.sh | 22 ++++++++++++++ 3 files changed, 38 insertions(+), 55 deletions(-) create mode 100644 ephemeral/startup/retry-function.sh diff --git a/ephemeral/shutdown/action.yml b/ephemeral/shutdown/action.yml index 2b5d579..4145efc 100644 --- a/ephemeral/shutdown/action.yml +++ b/ephemeral/shutdown/action.yml @@ -33,30 +33,18 @@ runs: echo "previewName=$previewName" >> $GITHUB_ENV - name: Shutdown ephemeral instance + # Source shared retry function + # GH_ACTION_ROOT is set in the main action.yml and points to the root of the action + # This ensures the script is found regardless of where the action is run from. + env: + SHARED_SCRIPTS_PATH: ${{ env.GH_ACTION_ROOT }}/.github/actions/shared-scripts shell: bash run: | AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-auth-token }}}}" CONTENT_TYPE_HEADER="content-type: application/json" API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" - retry() { - local retries=5 - local count=0 - local wait=5 - while [ $count -lt $retries ]; do - ( set +e; "$@"; ) # Run command in subshell with set -e disabled - local exit_code=$? - if [ $exit_code -eq 0 ]; then - return 0 - fi - count=$((count + 1)) - echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" > /dev/tty - sleep $wait - done - echo "Command failed after $retries retries." > /dev/tty - return 1 - } - + source "$SHARED_SCRIPTS_PATH/retry-function.sh" shutdown_instance() { # The API returns a 200 on successful deletion. # We use --fail-with-body so curl fails on server errors (5xx) and triggers the retry. diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index e736538..008becf 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -68,6 +68,11 @@ runs: echo "name=$previewName" >> $GITHUB_OUTPUT - name: Create preview environment + # Source shared retry function + # GH_ACTION_ROOT is set in the main action.yml and points to the root of the action + # This ensures the script is found regardless of where the action is run from. + env: + SHARED_SCRIPTS_PATH: ${{ env.GH_ACTION_ROOT }}/.github/actions/shared-scripts shell: bash id: create-instance run: | @@ -75,24 +80,7 @@ runs: CONTENT_TYPE_HEADER="content-type: application/json" API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" - retry() { - local retries=5 - local count=0 - local wait=5 - while [ $count -lt $retries ]; do - ( set +e; "$@"; ) # Run command in subshell with set -e disabled - local exit_code=$? - if [ $exit_code -eq 0 ]; then - return 0 - fi - count=$((count + 1)) - echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" > /dev/tty - sleep $wait - done - echo "Command failed after $retries retries." > /dev/tty - return 1 - } - + source "$SHARED_SCRIPTS_PATH/retry-function.sh" fetch_instances() { local list_response list_response=$(curl --fail-with-body -s -X GET \ @@ -171,30 +159,15 @@ runs: shell: bash env: previewName: ${{ steps.preview-name.outputs.name }} + # Source shared retry function + # GH_ACTION_ROOT is set in the main action.yml and points to the root of the action + SHARED_SCRIPTS_PATH: ${{ env.GH_ACTION_ROOT }}/.github/actions/shared-scripts run: | AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-auth-token }}}}" CONTENT_TYPE_HEADER="content-type: application/json" API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" - # TODO code duplication - retry() { - local retries=5 - local count=0 - local wait=5 - while [ $count -lt $retries ]; do - "$@" - local exit_code=$? - if [ $exit_code -eq 0 ]; then - return 0 - fi - count=$((count + 1)) - echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" > /dev/tty - sleep $wait - done - echo "Command failed after $retries retries." > /dev/tty - return 1 - } - + source "$SHARED_SCRIPTS_PATH/retry-function.sh" fetch_logs() { local log_response log_response=$(curl --fail-with-body -s -X GET \ diff --git a/ephemeral/startup/retry-function.sh b/ephemeral/startup/retry-function.sh new file mode 100644 index 0000000..59daf1f --- /dev/null +++ b/ephemeral/startup/retry-function.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# retry() function: Retries a given command up to 'retries' times with a 'wait' interval. +# Usage: retry +# Example: retry my_api_call_function +retry() { + local retries=5 + local count=0 + local wait=5 + while [ $count -lt $retries ]; do + ( set +e; "$@"; ) # Run command in subshell with set -e disabled + local exit_code=$? + if [ $exit_code -eq 0 ]; then + return 0 + fi + count=$((count + 1)) + echo "Command failed with exit code $exit_code. Retrying in $wait seconds... ($count/$retries)" >&2 + sleep $wait + done + echo "Command failed after $retries retries." >&2 + return 1 +} \ No newline at end of file From b3ad5bc0a9a412fd846ba862c0777e45f9d11703 Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Tue, 27 Jan 2026 17:24:04 +0100 Subject: [PATCH 10/16] fixup --- ephemeral/shutdown/action.yml | 7 +------ ephemeral/startup/action.yml | 24 ++---------------------- 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/ephemeral/shutdown/action.yml b/ephemeral/shutdown/action.yml index 4145efc..593d0b0 100644 --- a/ephemeral/shutdown/action.yml +++ b/ephemeral/shutdown/action.yml @@ -33,18 +33,13 @@ runs: echo "previewName=$previewName" >> $GITHUB_ENV - name: Shutdown ephemeral instance - # Source shared retry function - # GH_ACTION_ROOT is set in the main action.yml and points to the root of the action - # This ensures the script is found regardless of where the action is run from. - env: - SHARED_SCRIPTS_PATH: ${{ env.GH_ACTION_ROOT }}/.github/actions/shared-scripts shell: bash run: | AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-auth-token }}}}" CONTENT_TYPE_HEADER="content-type: application/json" API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" - source "$SHARED_SCRIPTS_PATH/retry-function.sh" + source ${{ github.action_path }}/retry-function.sh shutdown_instance() { # The API returns a 200 on successful deletion. # We use --fail-with-body so curl fails on server errors (5xx) and triggers the retry. diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index 008becf..5e46a8f 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -29,18 +29,6 @@ inputs: runs: using: composite steps: - - run: > - echo "GH_ACTION_ROOT=$( - ls -d $( - ls -d ./../../_actions/* | - grep -i localstack | - tail -n1 - )/setup-localstack/* | - grep -v completed | - tail -n1 - )" >> $GITHUB_ENV - shell: bash - - name: Initial PR comment if: inputs.github-token uses: jenseng/dynamic-uses@5175289a9a87978dcfcb9cf512b821d23b2a53eb # v1 @@ -68,11 +56,6 @@ runs: echo "name=$previewName" >> $GITHUB_OUTPUT - name: Create preview environment - # Source shared retry function - # GH_ACTION_ROOT is set in the main action.yml and points to the root of the action - # This ensures the script is found regardless of where the action is run from. - env: - SHARED_SCRIPTS_PATH: ${{ env.GH_ACTION_ROOT }}/.github/actions/shared-scripts shell: bash id: create-instance run: | @@ -80,7 +63,7 @@ runs: CONTENT_TYPE_HEADER="content-type: application/json" API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" - source "$SHARED_SCRIPTS_PATH/retry-function.sh" + source ${{ github.action_path }}/retry-function.sh fetch_instances() { local list_response list_response=$(curl --fail-with-body -s -X GET \ @@ -159,15 +142,12 @@ runs: shell: bash env: previewName: ${{ steps.preview-name.outputs.name }} - # Source shared retry function - # GH_ACTION_ROOT is set in the main action.yml and points to the root of the action - SHARED_SCRIPTS_PATH: ${{ env.GH_ACTION_ROOT }}/.github/actions/shared-scripts run: | AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-auth-token }}}}" CONTENT_TYPE_HEADER="content-type: application/json" API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" - source "$SHARED_SCRIPTS_PATH/retry-function.sh" + source ${{ github.action_path }}/retry-function.sh fetch_logs() { local log_response log_response=$(curl --fail-with-body -s -X GET \ From 5f7b1696f98cb7aea1aa139f03e088be8ca67adb Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Tue, 27 Jan 2026 17:42:25 +0100 Subject: [PATCH 11/16] fixup --- ephemeral/{startup => }/retry-function.sh | 0 ephemeral/shutdown/action.yml | 18 +++++++----- ephemeral/startup/action.yml | 34 ++++++++++++++++------- 3 files changed, 35 insertions(+), 17 deletions(-) rename ephemeral/{startup => }/retry-function.sh (100%) diff --git a/ephemeral/startup/retry-function.sh b/ephemeral/retry-function.sh similarity index 100% rename from ephemeral/startup/retry-function.sh rename to ephemeral/retry-function.sh diff --git a/ephemeral/shutdown/action.yml b/ephemeral/shutdown/action.yml index 593d0b0..9f7ca63 100644 --- a/ephemeral/shutdown/action.yml +++ b/ephemeral/shutdown/action.yml @@ -39,21 +39,25 @@ runs: CONTENT_TYPE_HEADER="content-type: application/json" API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" - source ${{ github.action_path }}/retry-function.sh + source ${{ github.action_path }}/../retry-function.sh shutdown_instance() { # The API returns a 200 on successful deletion. # We use --fail-with-body so curl fails on server errors (5xx) and triggers the retry. # We allow a 404 since that means the instance is already gone. + local http_code http_code=$(curl --fail-with-body -s -o /dev/null -w "%{http_code}" -X DELETE \ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE/$previewName") - if [ $? -eq 0 ]; then - if [ "$http_code" -eq 200 ]; then - echo "Instance '$previewName' deleted successfully." - elif [ "$http_code" -eq 404 ]; then - echo "Instance '$previewName' was already deleted (not found)." - fi + local exit_code=$? + if [ $exit_code -ne 0 ]; then + echo "Error deleting instance, curl failed with exit code $exit_code. API response: $http_code" >&2 + return 1 + fi + if [ "$http_code" -eq 200 ]; then + echo "Instance '$previewName' deleted successfully." + elif [ "$http_code" -eq 404 ]; then + echo "Instance '$previewName' was already deleted (not found)." fi } retry shutdown_instance diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index 5e46a8f..f711697 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -63,7 +63,7 @@ runs: CONTENT_TYPE_HEADER="content-type: application/json" API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" - source ${{ github.action_path }}/retry-function.sh + source ${{ github.action_path }}/../retry-function.sh fetch_instances() { local list_response list_response=$(curl --fail-with-body -s -X GET \ @@ -77,7 +77,10 @@ runs: echo "$list_response" } - list_response=$(retry fetch_instances) + if ! list_response=$(retry fetch_instances); then + echo "Error: Failed to fetch instances after multiple retries." + exit 1 + fi autoLoadPod="${AUTO_LOAD_POD:-${{ inputs.auto-load-pod }}}" extensionAutoInstall="${EXTENSION_AUTO_INSTALL:-${{ inputs.extension-auto-install }}}" @@ -87,19 +90,27 @@ runs: delete_instance() { # We expect a 200 on success or 404 if it's already gone. Other codes are errors. - http_code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ + local http_code + http_code=$(curl --fail-with-body -s -o /dev/null -w "%{http_code}" -X DELETE \ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE/$previewName") - if [ "$http_code" -ne 200 ] && [ "$http_code" -ne 404 ]; then - echo "Error deleting instance, HTTP code: $http_code" + local exit_code=$? + if [ $exit_code -ne 0 ]; then + echo "Error deleting instance, curl failed with exit code $exit_code. API response: $http_code" >&2 return 1 fi + if [ "$http_code" -eq 200 ]; then + echo "Instance '$previewName' deleted successfully." + fi } if [ -n "$instance_exists" ]; then echo "Found existing instance using '$previewName', trying to delete the old one..." - retry delete_instance + if ! retry delete_instance; then + echo "Error: Failed to delete existing instance after multiple retries." + exit 1 + fi fi create_instance_func() { @@ -116,7 +127,11 @@ runs: } echo "Creating preview environment ..." - response=$(retry create_instance_func) + if ! response=$(retry create_instance_func); then + echo "Error: Failed to create preview environment after multiple retries." + exit 1 + fi + endpointUrl=$(echo "$response" | jq -r .endpoint_url) echo "Created preview environment with endpoint URL: $endpointUrl" @@ -147,7 +162,7 @@ runs: CONTENT_TYPE_HEADER="content-type: application/json" API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" - source ${{ github.action_path }}/retry-function.sh + source ${{ github.action_path }}/../retry-function.sh fetch_logs() { local log_response log_response=$(curl --fail-with-body -s -X GET \ @@ -175,8 +190,7 @@ runs: echo "$log_response" } echo "Fetching logs for $previewName ..." - log_response=$(retry fetch_logs) - if [ -z "$log_response" ]; then + if ! log_response=$(retry fetch_logs); then echo "Error: Failed to fetch logs after multiple retries." exit 1 fi From 3ba8a36c8d2338bf12c3191bf18a0cb2b296675b Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Thu, 29 Jan 2026 16:53:57 +0100 Subject: [PATCH 12/16] debug log result --- ephemeral/startup/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index f711697..460938d 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -194,5 +194,6 @@ runs: echo "Error: Failed to fetch logs after multiple retries." exit 1 fi + echo "raw logs: $log_response" echo "Logs:" echo "$log_response" | jq -r '.[].content' From 0ff0b96c521547f5307080734cea58f9c2cbed63 Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Thu, 29 Jan 2026 17:18:53 +0100 Subject: [PATCH 13/16] more debug messages, fail if the response contains error key --- ephemeral/retry-function.sh | 12 +++++++++ ephemeral/shutdown/action.yml | 16 ++++++++---- ephemeral/startup/action.yml | 48 +++++++++++++++-------------------- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/ephemeral/retry-function.sh b/ephemeral/retry-function.sh index 59daf1f..d107e18 100644 --- a/ephemeral/retry-function.sh +++ b/ephemeral/retry-function.sh @@ -19,4 +19,16 @@ retry() { done echo "Command failed after $retries retries." >&2 return 1 +} + +# Helper function to check for a JSON error response from the API +# Usage: check_for_api_error "" "" +check_for_api_error() { + local response="$1" + local context_message="$2" + if echo "$response" | jq -e 'if type == "object" and has("error") then true else false end' > /dev/null; then + echo "API error during '$context_message': $response" >&2 + return 1 + fi + return 0 } \ No newline at end of file diff --git a/ephemeral/shutdown/action.yml b/ephemeral/shutdown/action.yml index 9f7ca63..3e886e8 100644 --- a/ephemeral/shutdown/action.yml +++ b/ephemeral/shutdown/action.yml @@ -43,16 +43,21 @@ runs: shutdown_instance() { # The API returns a 200 on successful deletion. # We use --fail-with-body so curl fails on server errors (5xx) and triggers the retry. - # We allow a 404 since that means the instance is already gone. - local http_code - http_code=$(curl --fail-with-body -s -o /dev/null -w "%{http_code}" -X DELETE \ + local response + response=$(curl --fail-with-body -s -w "\n%{http_code}" -X DELETE \ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE/$previewName") local exit_code=$? + local http_code=$(echo "$response" | tail -n1) + local body=$(echo "$response" | sed '$d') + if [ $exit_code -ne 0 ]; then - echo "Error deleting instance, curl failed with exit code $exit_code. API response: $http_code" >&2 - return 1 + # A 404 means it's already gone, which is a success case for shutdown. + if [ "$http_code" -ne 404 ]; then + echo "Error deleting instance, curl failed with exit code $exit_code. API response: $body" >&2 + return 1 + fi fi if [ "$http_code" -eq 200 ]; then echo "Instance '$previewName' deleted successfully." @@ -60,6 +65,7 @@ runs: echo "Instance '$previewName' was already deleted (not found)." fi } + retry shutdown_instance - name: Update status comment diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index 460938d..e3c8da8 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -64,16 +64,15 @@ runs: API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" source ${{ github.action_path }}/../retry-function.sh + fetch_instances() { local list_response list_response=$(curl --fail-with-body -s -X GET \ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE") - if [ $? -ne 0 ]; then - echo "API returned an error: $list_response" - return 1 - fi + if [ $? -ne 0 ]; then echo "curl command failed while fetching instances. Response: $list_response" >&2; return 1; fi + if ! check_for_api_error "$list_response" "fetch instances"; then return 1; fi echo "$list_response" } @@ -90,16 +89,18 @@ runs: delete_instance() { # We expect a 200 on success or 404 if it's already gone. Other codes are errors. - local http_code - http_code=$(curl --fail-with-body -s -o /dev/null -w "%{http_code}" -X DELETE \ + local response + response=$(curl --fail-with-body -s -w "\n%{http_code}" -X DELETE \ -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE/$previewName") local exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "Error deleting instance, curl failed with exit code $exit_code. API response: $http_code" >&2 - return 1 - fi + local http_code=$(echo "$response" | tail -n1) + local body=$(echo "$response" | sed '$d') + + if [ $exit_code -ne 0 ]; then echo "curl command failed while deleting instance. Response: $body" >&2; return 1; fi + if ! check_for_api_error "$body" "delete instance"; then return 1; fi + if [ "$http_code" -eq 200 ]; then echo "Instance '$previewName' deleted successfully." fi @@ -119,9 +120,10 @@ runs: -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE") - if [ $? -ne 0 ] || ! echo "$response" | jq -e 'has("endpoint_url") and (.endpoint_url | test(".+"))' > /dev/null; then - echo "Unable to create preview environment. API response: $response" - return 1 + if [ $? -ne 0 ]; then echo "curl command failed while creating instance. Response: $response" >&2; return 1; fi + if ! check_for_api_error "$response" "create instance"; then return 1; fi + if ! echo "$response" | jq -e 'has("endpoint_url") and (.endpoint_url | test(".+"))' > /dev/null; then + echo "Invalid response from instance creation API: $response" >&2; return 1; fi echo "$response" } @@ -169,22 +171,12 @@ runs: -H "$AUTH_HEADER" \ -H "$CONTENT_TYPE_HEADER" \ "$API_URL_BASE/$previewName/logs") - if [ $? -ne 0 ]; then - echo "API returned an error while fetching logs: $log_response" - return 1 - fi - if [ -z "$log_response" ]; then - echo "API returned an empty response when fetching logs." - return 1 - fi + if [ $? -ne 0 ]; then echo "curl command failed while fetching logs. Response: $log_response" >&2; return 1; fi + if ! check_for_api_error "$log_response" "fetch logs"; then return 1; fi - # Now, check if jq extracts any content - local extracted_content - extracted_content=$(echo "$log_response" | jq -r '.[].content') - if [ -z "$extracted_content" ]; then - echo "No content found in logs or logs are empty after extraction." - echo "Raw API response for logs: $log_response" # This will be printed on retry attempts - return 1 + # A valid log response must be a JSON array. + if ! echo "$log_response" | jq -e 'if type == "array" then true else false end' > /dev/null; then + echo "Invalid response from logs API (expected a JSON array): $log_response" >&2; return 1; fi echo "$log_response" From dba16e56980fd2cfa8c6395edd613c2ee8c88f41 Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Thu, 29 Jan 2026 17:38:14 +0100 Subject: [PATCH 14/16] fixup --- ephemeral/retry-function.sh | 6 +++++- ephemeral/startup/action.yml | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ephemeral/retry-function.sh b/ephemeral/retry-function.sh index d107e18..0ee20e1 100644 --- a/ephemeral/retry-function.sh +++ b/ephemeral/retry-function.sh @@ -7,10 +7,13 @@ retry() { local retries=5 local count=0 local wait=5 + local output while [ $count -lt $retries ]; do - ( set +e; "$@"; ) # Run command in subshell with set -e disabled + # We disable set -e for the command and capture its output. + output=$(set +e; "$@") local exit_code=$? if [ $exit_code -eq 0 ]; then + echo "$output" return 0 fi count=$((count + 1)) @@ -18,6 +21,7 @@ retry() { sleep $wait done echo "Command failed after $retries retries." >&2 + echo "$output" # Also return the output of the last failed attempt for debugging return 1 } diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index e3c8da8..f3f1a30 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -179,6 +179,12 @@ runs: echo "Invalid response from logs API (expected a JSON array): $log_response" >&2; return 1; fi + # Check if the logs contain the "Ready." message, indicating the instance is fully started. + if ! echo "$log_response" | jq -e '.[] | select(.content | contains("Ready."))' > /dev/null; then + echo "Instance is not ready yet, waiting for 'Ready.' message in logs..." >&2 + return 1 + fi + echo "$log_response" } echo "Fetching logs for $previewName ..." From 9036a17c5ca701d1d4c0cd1767d87cdccd60988e Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Thu, 29 Jan 2026 17:45:18 +0100 Subject: [PATCH 15/16] fixup --- ephemeral/startup/action.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index f3f1a30..72f5e69 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -192,6 +192,5 @@ runs: echo "Error: Failed to fetch logs after multiple retries." exit 1 fi - echo "raw logs: $log_response" - echo "Logs:" + echo "$previewName logs:" echo "$log_response" | jq -r '.[].content' From e88acfcf15363ca3b80e8a1b3381db34bd7c33ab Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Thu, 29 Jan 2026 18:08:19 +0100 Subject: [PATCH 16/16] fixup --- ephemeral/shutdown/action.yml | 4 ++-- ephemeral/startup/action.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ephemeral/shutdown/action.yml b/ephemeral/shutdown/action.yml index 3e886e8..637962b 100644 --- a/ephemeral/shutdown/action.yml +++ b/ephemeral/shutdown/action.yml @@ -2,7 +2,7 @@ name: Shutdown Ephemeral Instance description: 'Shutdowns an Ephemeral Instance (PR Preview)' inputs: - localstack-auth-token: + localstack-api-key: description: 'LocalStack Auth Token used to access the platform api' required: false github-token: @@ -35,7 +35,7 @@ runs: - name: Shutdown ephemeral instance shell: bash run: | - AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-auth-token }}}}" + AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-api-key }}}}" CONTENT_TYPE_HEADER="content-type: application/json" API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" diff --git a/ephemeral/startup/action.yml b/ephemeral/startup/action.yml index 72f5e69..eb802be 100644 --- a/ephemeral/startup/action.yml +++ b/ephemeral/startup/action.yml @@ -5,7 +5,7 @@ inputs: github-token: description: 'Github token used to create PR comments' required: true - localstack-auth-token: + localstack-api-key: description: 'LocalStack Auth Token used to create the preview environment' required: false preview-cmd: @@ -59,7 +59,7 @@ runs: shell: bash id: create-instance run: | - AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-auth-token }}}}" + AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-api-key }}}}" CONTENT_TYPE_HEADER="content-type: application/json" API_URL_BASE="https://api.localstack.cloud/v1/compute/instances" @@ -160,7 +160,7 @@ runs: env: previewName: ${{ steps.preview-name.outputs.name }} run: | - AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-auth-token }}}}" + AUTH_HEADER="ls-api-key: ${LOCALSTACK_AUTH_TOKEN:-${LOCALSTACK_API_KEY:-${{ inputs.localstack-api-key }}}}" CONTENT_TYPE_HEADER="content-type: application/json" API_URL_BASE="https://api.localstack.cloud/v1/compute/instances"