mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 03:43:11 +00:00
Some checks are pending
Validate and deploy / Validate deployable source bundle (push) Waiting to run
Validate and deploy / Deploy shoc-backend-dev through Terraform (push) Blocked by required conditions
Validate and deploy / Deploy shoc-backend-staging to Elastic Beanstalk (push) Blocked by required conditions
* feat(deploy): move dev application CD through Terraform GitHub creates the immutable Elastic Beanstalk version; HCP Terraform is the only UpdateEnvironment caller via a guarded version_label run. * fix: add permissions block for dependency-review workflow Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix(terraform): stop pinning the generated dev instance SG --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
592 lines
24 KiB
YAML
592 lines
24 KiB
YAML
name: Validate and deploy
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [dev, staging, main]
|
|
push:
|
|
branches: [dev]
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
validate:
|
|
name: Validate deployable source bundle
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Set up .NET
|
|
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
|
|
with:
|
|
dotnet-version: "8.0.x"
|
|
|
|
- name: Repository quality gate
|
|
run: bash scripts/governance-check.sh
|
|
|
|
- name: Build Elastic Beanstalk source bundle
|
|
run: bash scripts/package-elastic-beanstalk.sh
|
|
|
|
- name: Inspect source bundle contract
|
|
run: bash scripts/validate-elastic-beanstalk-bundle.sh
|
|
|
|
deploy-dev:
|
|
name: Deploy shoc-backend-dev through Terraform
|
|
if: >
|
|
(github.event_name == 'push' && github.ref == 'refs/heads/dev' &&
|
|
vars.TERRAFORM_APP_CD_ENABLED == 'true') ||
|
|
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/dev')
|
|
needs: validate
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 180
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
environment:
|
|
name: dev
|
|
concurrency:
|
|
group: deploy-dev
|
|
cancel-in-progress: false
|
|
env:
|
|
TF_CLOUD_ORGANIZATION: seahaven
|
|
TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }}
|
|
EB_APPLICATION_NAME: shoc-backend
|
|
EB_ENVIRONMENT_NAME: shoc-backend-dev
|
|
SMOKE_URL: https://api.dev.seahaven.com
|
|
EB_BUCKET: elasticbeanstalk-us-east-1-396287094661
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Set up .NET
|
|
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
|
|
with:
|
|
dotnet-version: "8.0.x"
|
|
|
|
- name: Build Elastic Beanstalk source bundle
|
|
run: bash scripts/package-elastic-beanstalk.sh
|
|
|
|
- name: Validate exact release bundle
|
|
run: bash scripts/validate-elastic-beanstalk-bundle.sh
|
|
|
|
- name: Configure AWS credentials (OIDC)
|
|
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
|
|
with:
|
|
role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
aws-region: us-east-1
|
|
audience: sts.amazonaws.com
|
|
|
|
- name: Capture current environment version
|
|
run: |
|
|
set -euo pipefail
|
|
prev="$(aws elasticbeanstalk describe-environments \
|
|
--environment-names "${EB_ENVIRONMENT_NAME}" \
|
|
--region us-east-1 \
|
|
--query 'Environments[0].VersionLabel' \
|
|
--output text)"
|
|
echo "$prev" > .artifacts/elastic-beanstalk/previous-version.txt
|
|
echo "Previous version label: $prev"
|
|
|
|
- name: Assign immutable release identity
|
|
id: release
|
|
run: |
|
|
set -euo pipefail
|
|
version_label="${GITHUB_SHA}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
|
s3_key="shoc-backend/releases/dev/${GITHUB_SHA}/${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}/site.zip"
|
|
{
|
|
echo "version_label=${version_label}"
|
|
echo "s3_key=${s3_key}"
|
|
} >> "${GITHUB_OUTPUT}"
|
|
|
|
- name: Upload immutable bundle
|
|
run: |
|
|
set -euo pipefail
|
|
aws s3 cp .artifacts/elastic-beanstalk/site.zip \
|
|
"s3://${EB_BUCKET}/${{ steps.release.outputs.s3_key }}" \
|
|
--region us-east-1
|
|
|
|
- name: Create Elastic Beanstalk application version
|
|
run: |
|
|
set -euo pipefail
|
|
aws elasticbeanstalk create-application-version \
|
|
--application-name "${EB_APPLICATION_NAME}" \
|
|
--version-label "${{ steps.release.outputs.version_label }}" \
|
|
--description "GitHub Actions ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID} attempt ${GITHUB_RUN_ATTEMPT}" \
|
|
--source-bundle "S3Bucket=${EB_BUCKET},S3Key=${{ steps.release.outputs.s3_key }}" \
|
|
--process \
|
|
--region us-east-1
|
|
|
|
status="UNPROCESSED"
|
|
for _ in $(seq 1 36); do
|
|
status="$(aws elasticbeanstalk describe-application-versions \
|
|
--application-name "${EB_APPLICATION_NAME}" \
|
|
--version-labels "${{ steps.release.outputs.version_label }}" \
|
|
--region us-east-1 \
|
|
--query 'ApplicationVersions[0].Status' \
|
|
--output text)"
|
|
echo "application version status: $status"
|
|
if [ "$status" = "PROCESSED" ]; then
|
|
exit 0
|
|
fi
|
|
if [ "$status" = "FAILED" ]; then
|
|
echo "Elastic Beanstalk failed to process ${{ steps.release.outputs.version_label }}." >&2
|
|
exit 1
|
|
fi
|
|
sleep 5
|
|
done
|
|
echo "Application version did not become PROCESSED." >&2
|
|
exit 1
|
|
|
|
- name: Create Terraform release run
|
|
id: release-run
|
|
uses: hashicorp/tfc-workflows-github/actions/create-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
|
|
env:
|
|
TF_VAR_release_version_label: '"${{ steps.release.outputs.version_label }}"'
|
|
with:
|
|
workspace: shoc-backend-dev
|
|
message: "Release ${{ steps.release.outputs.version_label }} from GitHub Actions"
|
|
|
|
- name: Read Terraform release plan counts
|
|
id: release-plan
|
|
uses: hashicorp/tfc-workflows-github/actions/plan-output@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
|
|
with:
|
|
plan: ${{ steps.release-run.outputs.plan_id }}
|
|
|
|
- name: Reject non-version-only resource counts
|
|
env:
|
|
PLAN_ADD: ${{ steps.release-plan.outputs.add }}
|
|
PLAN_CHANGE: ${{ steps.release-plan.outputs.change }}
|
|
PLAN_DESTROY: ${{ steps.release-plan.outputs.destroy }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "$PLAN_ADD" != "0" ] || [ "$PLAN_CHANGE" != "1" ] || [ "$PLAN_DESTROY" != "0" ]; then
|
|
echo "HCP plan counts are add=${PLAN_ADD} change=${PLAN_CHANGE} destroy=${PLAN_DESTROY}; expected 0/1/0." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Guard version-only Terraform plan
|
|
run: |
|
|
set -euo pipefail
|
|
python scripts/check-terraform-release-plan.py \
|
|
--plan-id "${{ steps.release-run.outputs.plan_id }}" \
|
|
--expected-version-label "${{ steps.release.outputs.version_label }}"
|
|
|
|
- name: Discard release run when the guard fails
|
|
if: failure() && steps.release-run.outcome == 'success'
|
|
uses: hashicorp/tfc-workflows-github/actions/discard-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
|
|
with:
|
|
run: ${{ steps.release-run.outputs.run_id }}
|
|
comment: Rejected by the version-only plan guard from GitHub Actions
|
|
|
|
- name: Apply Terraform release run
|
|
id: release-apply
|
|
uses: hashicorp/tfc-workflows-github/actions/apply-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
|
|
with:
|
|
run: ${{ steps.release-run.outputs.run_id }}
|
|
comment: Apply version-only release from GitHub Actions ${{ github.sha }}
|
|
|
|
- name: Verify exact application version is active
|
|
run: |
|
|
set -euo pipefail
|
|
expected="${{ steps.release.outputs.version_label }}"
|
|
status="Unknown"
|
|
current="Unknown"
|
|
health="Unknown"
|
|
|
|
for _ in $(seq 1 80); do
|
|
read -r status current health < <(
|
|
aws elasticbeanstalk describe-environments \
|
|
--environment-names "${EB_ENVIRONMENT_NAME}" \
|
|
--region us-east-1 \
|
|
--query 'Environments[0].[Status,VersionLabel,Health]' \
|
|
--output text
|
|
)
|
|
echo "environment status: $status; version: $current; health: $health"
|
|
|
|
if [ "$status" = "Ready" ]; then
|
|
if [ "$current" = "$expected" ] && { [ "$health" = "Green" ] || [ "$health" = "Yellow" ]; }; then
|
|
echo "Expected application version is Ready and healthy."
|
|
exit 0
|
|
fi
|
|
echo "Environment became Ready without activating expected version $expected." >&2
|
|
exit 1
|
|
fi
|
|
sleep 15
|
|
done
|
|
|
|
echo "Expected application version did not become Ready within the deployment window." >&2
|
|
exit 1
|
|
|
|
- name: Post-deploy smoke
|
|
run: bash scripts/smoke-elastic-beanstalk.sh "${SMOKE_URL}"
|
|
|
|
- name: Verify webhook secret source is operational
|
|
run: |
|
|
set -euo pipefail
|
|
response_file="$(mktemp)"
|
|
trap 'rm -f "$response_file"' EXIT
|
|
status="$(curl --silent --show-error \
|
|
--output "$response_file" \
|
|
--write-out '%{http_code}' \
|
|
--request POST \
|
|
--header 'Content-Type: application/json' \
|
|
--header "X-SH-Timestamp: $(date +%s)" \
|
|
--header 'X-SH-Key-Id: deployment-smoke-invalid-key' \
|
|
--header "X-SH-Signature: v1=$(printf '0%.0s' {1..64})" \
|
|
--data '{}' \
|
|
"${SMOKE_URL}/api/webhooks/work-orders")"
|
|
if [ "$status" != "401" ]; then
|
|
echo "Expected enabled webhook with an operational secret source to reject the invalid probe with 401; received $status." >&2
|
|
sed -n '1,20p' "$response_file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Restore previous application version on failure (schema is not reverted)
|
|
if: failure()
|
|
run: |
|
|
set -euo pipefail
|
|
prev_file=".artifacts/elastic-beanstalk/previous-version.txt"
|
|
if [ ! -f "$prev_file" ]; then
|
|
echo "No previous version captured; nothing to roll back." >&2
|
|
exit 0
|
|
fi
|
|
prev="$(cat "$prev_file")"
|
|
if [ -z "$prev" ] || [ "$prev" = "null" ] || [ "$prev" = "None" ] || [ "$prev" = "N/A" ]; then
|
|
echo "No previous version recorded; nothing to roll back." >&2
|
|
exit 0
|
|
fi
|
|
|
|
echo "Waiting for any in-flight environment update to settle..."
|
|
status="Unknown"
|
|
current="Unknown"
|
|
health="Unknown"
|
|
for _ in $(seq 1 80); do
|
|
read -r status current health < <(
|
|
aws elasticbeanstalk describe-environments \
|
|
--environment-names "${EB_ENVIRONMENT_NAME}" \
|
|
--region us-east-1 \
|
|
--query 'Environments[0].[Status,VersionLabel,Health]' \
|
|
--output text
|
|
)
|
|
echo "environment status: $status; version: $current; health: $health"
|
|
if [ "$status" = "Ready" ]; then
|
|
break
|
|
fi
|
|
sleep 15
|
|
done
|
|
|
|
if [ "$status" != "Ready" ]; then
|
|
echo "Environment did not settle before rollback." >&2
|
|
exit 1
|
|
fi
|
|
if [ "$current" = "$prev" ]; then
|
|
echo "Environment is already on previous version $prev."
|
|
exit 0
|
|
fi
|
|
if [[ ! "$prev" =~ ^[0-9a-f]{40}-[0-9]+-[0-9]+$ ]]; then
|
|
echo "Previous version $prev is not a Terraform-managed release label; cannot roll back through HCP." >&2
|
|
exit 1
|
|
fi
|
|
echo "rollback_label=$prev" >> "${GITHUB_OUTPUT}"
|
|
id: rollback-prepare
|
|
|
|
- name: Create Terraform rollback run
|
|
id: rollback-run
|
|
if: failure() && steps.rollback-prepare.outputs.rollback_label != ''
|
|
uses: hashicorp/tfc-workflows-github/actions/create-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
|
|
env:
|
|
TF_VAR_release_version_label: '"${{ steps.rollback-prepare.outputs.rollback_label }}"'
|
|
with:
|
|
workspace: shoc-backend-dev
|
|
message: "Rollback to ${{ steps.rollback-prepare.outputs.rollback_label }} from GitHub Actions"
|
|
|
|
- name: Read Terraform rollback plan counts
|
|
id: rollback-plan
|
|
if: failure() && steps.rollback-run.outcome == 'success'
|
|
uses: hashicorp/tfc-workflows-github/actions/plan-output@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
|
|
with:
|
|
plan: ${{ steps.rollback-run.outputs.plan_id }}
|
|
|
|
- name: Reject non-version-only rollback counts
|
|
id: rollback-count-guard
|
|
if: failure() && steps.rollback-plan.outcome == 'success'
|
|
env:
|
|
PLAN_ADD: ${{ steps.rollback-plan.outputs.add }}
|
|
PLAN_CHANGE: ${{ steps.rollback-plan.outputs.change }}
|
|
PLAN_DESTROY: ${{ steps.rollback-plan.outputs.destroy }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "$PLAN_ADD" != "0" ] || [ "$PLAN_CHANGE" != "1" ] || [ "$PLAN_DESTROY" != "0" ]; then
|
|
echo "Rollback HCP plan counts are add=${PLAN_ADD} change=${PLAN_CHANGE} destroy=${PLAN_DESTROY}; expected 0/1/0." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Guard version-only Terraform rollback plan
|
|
id: rollback-json-guard
|
|
if: failure() && steps.rollback-count-guard.outcome == 'success'
|
|
run: |
|
|
set -euo pipefail
|
|
python scripts/check-terraform-release-plan.py \
|
|
--plan-id "${{ steps.rollback-run.outputs.plan_id }}" \
|
|
--expected-version-label "${{ steps.rollback-prepare.outputs.rollback_label }}"
|
|
|
|
- name: Discard rollback run when the guard fails
|
|
if: failure() && steps.rollback-run.outcome == 'success' && steps.rollback-json-guard.outcome != 'success'
|
|
uses: hashicorp/tfc-workflows-github/actions/discard-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
|
|
with:
|
|
run: ${{ steps.rollback-run.outputs.run_id }}
|
|
comment: Rejected by the version-only rollback plan guard from GitHub Actions
|
|
|
|
- name: Apply Terraform rollback run
|
|
id: rollback-apply
|
|
if: failure() && steps.rollback-json-guard.outcome == 'success'
|
|
uses: hashicorp/tfc-workflows-github/actions/apply-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2
|
|
with:
|
|
run: ${{ steps.rollback-run.outputs.run_id }}
|
|
comment: Apply version-only rollback from GitHub Actions ${{ github.sha }}
|
|
|
|
- name: Verify previous application version is active
|
|
if: failure() && steps.rollback-apply.outcome == 'success'
|
|
run: |
|
|
set -euo pipefail
|
|
prev="${{ steps.rollback-prepare.outputs.rollback_label }}"
|
|
echo "Database migrations are not reverted; deployable migrations must follow the expand/contract policy."
|
|
status="Unknown"
|
|
current="Unknown"
|
|
health="Unknown"
|
|
for _ in $(seq 1 80); do
|
|
read -r status current health < <(
|
|
aws elasticbeanstalk describe-environments \
|
|
--environment-names "${EB_ENVIRONMENT_NAME}" \
|
|
--region us-east-1 \
|
|
--query 'Environments[0].[Status,VersionLabel,Health]' \
|
|
--output text
|
|
)
|
|
echo "environment status: $status; version: $current; health: $health"
|
|
if [ "$status" = "Ready" ]; then
|
|
if [ "$current" = "$prev" ] && { [ "$health" = "Green" ] || [ "$health" = "Yellow" ]; }; then
|
|
echo "Application version restore complete; previous code is Ready and healthy."
|
|
exit 0
|
|
fi
|
|
echo "Rollback reached Ready in an unexpected version/health state." >&2
|
|
exit 1
|
|
fi
|
|
sleep 15
|
|
done
|
|
echo "Environment did not return to Ready within rollback window." >&2
|
|
exit 1
|
|
|
|
deploy-staging:
|
|
name: Deploy shoc-backend-staging to Elastic Beanstalk
|
|
if: >
|
|
github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/staging'
|
|
needs: validate
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
environment:
|
|
name: staging
|
|
concurrency:
|
|
group: deploy-staging
|
|
cancel-in-progress: false
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Resolve deploy target
|
|
id: target
|
|
run: |
|
|
set -euo pipefail
|
|
application=shoc-backend
|
|
environment=shoc-backend-staging
|
|
smoke_url=https://api.staging.seahaven.com
|
|
{
|
|
echo "application=${application}"
|
|
echo "environment=${environment}"
|
|
echo "smoke_url=${smoke_url}"
|
|
} >> "${GITHUB_OUTPUT}"
|
|
{
|
|
echo "EB_APPLICATION_NAME=${application}"
|
|
echo "EB_ENVIRONMENT_NAME=${environment}"
|
|
echo "SMOKE_URL=${smoke_url}"
|
|
} >> "${GITHUB_ENV}"
|
|
|
|
- name: Set up .NET
|
|
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
|
|
with:
|
|
dotnet-version: "8.0.x"
|
|
|
|
- name: Build Elastic Beanstalk source bundle
|
|
run: bash scripts/package-elastic-beanstalk.sh
|
|
|
|
- name: Validate exact release bundle
|
|
run: bash scripts/validate-elastic-beanstalk-bundle.sh
|
|
|
|
- name: Configure AWS credentials (OIDC)
|
|
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
|
|
with:
|
|
role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
aws-region: us-east-1
|
|
audience: sts.amazonaws.com
|
|
|
|
- name: Capture current environment version
|
|
run: |
|
|
set -euo pipefail
|
|
prev="$(aws elasticbeanstalk describe-environments \
|
|
--environment-names "${EB_ENVIRONMENT_NAME}" \
|
|
--region us-east-1 \
|
|
--query 'Environments[0].VersionLabel' \
|
|
--output text)"
|
|
echo "$prev" > .artifacts/elastic-beanstalk/previous-version.txt
|
|
echo "Previous version label: $prev"
|
|
|
|
- name: Deploy prebuilt bundle to existing environment
|
|
uses: aws-actions/aws-elasticbeanstalk-deploy@7883cdd454c162051bf6fc13389536b045149b4c # v1.0.8
|
|
with:
|
|
aws-region: us-east-1
|
|
application-name: ${{ steps.target.outputs.application }}
|
|
environment-name: ${{ steps.target.outputs.environment }}
|
|
version-label: ${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
|
deployment-package-path: .artifacts/elastic-beanstalk/site.zip
|
|
s3-bucket-name: elasticbeanstalk-us-east-1-396287094661
|
|
create-application-if-not-exists: "false"
|
|
create-environment-if-not-exists: "false"
|
|
create-s3-bucket-if-not-exists: "false"
|
|
use-existing-application-version-if-available: "false"
|
|
wait-for-deployment: "true"
|
|
wait-for-environment-recovery: "true"
|
|
|
|
- name: Verify exact application version is active
|
|
run: |
|
|
set -euo pipefail
|
|
expected="${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}"
|
|
status="Unknown"
|
|
current="Unknown"
|
|
health="Unknown"
|
|
|
|
for _ in $(seq 1 80); do
|
|
read -r status current health < <(
|
|
aws elasticbeanstalk describe-environments \
|
|
--environment-names "${EB_ENVIRONMENT_NAME}" \
|
|
--region us-east-1 \
|
|
--query 'Environments[0].[Status,VersionLabel,Health]' \
|
|
--output text
|
|
)
|
|
echo "environment status: $status; version: $current; health: $health"
|
|
|
|
if [ "$status" = "Ready" ]; then
|
|
if [ "$current" = "$expected" ] && { [ "$health" = "Green" ] || [ "$health" = "Yellow" ]; }; then
|
|
echo "Expected application version is Ready and healthy."
|
|
exit 0
|
|
fi
|
|
echo "Environment became Ready without activating expected version $expected." >&2
|
|
exit 1
|
|
fi
|
|
sleep 15
|
|
done
|
|
|
|
echo "Expected application version did not become Ready within the deployment window." >&2
|
|
exit 1
|
|
|
|
- name: Post-deploy smoke
|
|
run: bash scripts/smoke-elastic-beanstalk.sh "${SMOKE_URL}"
|
|
|
|
- name: Verify webhook secret source is operational
|
|
run: |
|
|
set -euo pipefail
|
|
response_file="$(mktemp)"
|
|
trap 'rm -f "$response_file"' EXIT
|
|
status="$(curl --silent --show-error \
|
|
--output "$response_file" \
|
|
--write-out '%{http_code}' \
|
|
--request POST \
|
|
--header 'Content-Type: application/json' \
|
|
--header "X-SH-Timestamp: $(date +%s)" \
|
|
--header 'X-SH-Key-Id: deployment-smoke-invalid-key' \
|
|
--header "X-SH-Signature: v1=$(printf '0%.0s' {1..64})" \
|
|
--data '{}' \
|
|
"${SMOKE_URL}/api/webhooks/work-orders")"
|
|
if [ "$status" != "401" ]; then
|
|
echo "Expected enabled webhook with an operational secret source to reject the invalid probe with 401; received $status." >&2
|
|
sed -n '1,20p' "$response_file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Restore previous application version on failure (schema is not reverted)
|
|
if: failure()
|
|
run: |
|
|
set -euo pipefail
|
|
prev_file=".artifacts/elastic-beanstalk/previous-version.txt"
|
|
if [ ! -f "$prev_file" ]; then
|
|
echo "No previous version captured; nothing to roll back." >&2
|
|
exit 0
|
|
fi
|
|
prev="$(cat "$prev_file")"
|
|
if [ -z "$prev" ] || [ "$prev" = "null" ] || [ "$prev" = "None" ] || [ "$prev" = "N/A" ]; then
|
|
echo "No previous version recorded; nothing to roll back." >&2
|
|
exit 0
|
|
fi
|
|
|
|
echo "Waiting for any in-flight environment update to settle..."
|
|
status="Unknown"
|
|
current="Unknown"
|
|
health="Unknown"
|
|
for _ in $(seq 1 80); do
|
|
read -r status current health < <(
|
|
aws elasticbeanstalk describe-environments \
|
|
--environment-names "${EB_ENVIRONMENT_NAME}" \
|
|
--region us-east-1 \
|
|
--query 'Environments[0].[Status,VersionLabel,Health]' \
|
|
--output text
|
|
)
|
|
echo "environment status: $status; version: $current; health: $health"
|
|
if [ "$status" = "Ready" ]; then
|
|
break
|
|
fi
|
|
sleep 15
|
|
done
|
|
|
|
if [ "$status" != "Ready" ]; then
|
|
echo "Environment did not settle before rollback." >&2
|
|
exit 1
|
|
fi
|
|
if [ "$current" = "$prev" ]; then
|
|
echo "Environment is already on previous version $prev."
|
|
exit 0
|
|
fi
|
|
|
|
echo "Restoring ${EB_ENVIRONMENT_NAME} application code to version label: $prev"
|
|
echo "Database migrations are not reverted; deployable migrations must follow the expand/contract policy."
|
|
aws elasticbeanstalk update-environment \
|
|
--environment-name "${EB_ENVIRONMENT_NAME}" \
|
|
--version-label "$prev" \
|
|
--region us-east-1
|
|
|
|
echo "Waiting for previous version to become healthy..."
|
|
for _ in $(seq 1 80); do
|
|
read -r status current health < <(
|
|
aws elasticbeanstalk describe-environments \
|
|
--environment-names "${EB_ENVIRONMENT_NAME}" \
|
|
--region us-east-1 \
|
|
--query 'Environments[0].[Status,VersionLabel,Health]' \
|
|
--output text
|
|
)
|
|
echo "environment status: $status; version: $current; health: $health"
|
|
if [ "$status" = "Ready" ]; then
|
|
if [ "$current" = "$prev" ] && { [ "$health" = "Green" ] || [ "$health" = "Yellow" ]; }; then
|
|
echo "Application version restore complete; previous code is Ready and healthy."
|
|
exit 0
|
|
fi
|
|
echo "Rollback reached Ready in an unexpected version/health state." >&2
|
|
exit 1
|
|
fi
|
|
sleep 15
|
|
done
|
|
|
|
echo "Environment did not return to Ready within rollback window." >&2
|
|
exit 1
|