CDD-2442 Set Up CI/CD Workflow for auth-dev and auth-test Environments #2811
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Pull Request Workflow | |
on: | |
pull_request: | |
branches: | |
- "*" | |
env: | |
AWS_REGION: "eu-west-2" | |
permissions: | |
id-token: write | |
contents: read | |
jobs: | |
build_base: | |
name: Build base env | |
runs-on: ubuntu-latest | |
outputs: | |
public_env: ${{ steps.set_env.outputs.PUBLIC_ENV }} | |
auth_env: ${{ steps.set_env.outputs.AUTH_ENV }} | |
steps: | |
- name: Checkout Repository (Main) | |
uses: actions/checkout@v4 | |
with: | |
ref: main | |
fetch-depth: 0 | |
persist-credentials: true | |
- uses: ./.github/actions/setup-terraform | |
- uses: ./.github/actions/setup-zsh | |
- uses: ./.github/actions/short-sha | |
- name: Export SHORT_SHA Globally | |
run: | | |
echo "SHORT_SHA=${{ env.SHORT_SHA }}" >> $GITHUB_ENV | |
echo "SHORT_SHA=${{ env.SHORT_SHA }}" >> $GITHUB_OUTPUT | |
- name: Verify Short SHA | |
run: | | |
if [[ -z "${{ env.SHORT_SHA }}" ]]; then | |
echo "Error: SHORT_SHA is not set!" | |
exit 1 | |
fi | |
echo "SHORT_SHA=${{ env.SHORT_SHA }}" | |
- name: Apply Terraform for Main Branch | |
run: | | |
source uhd.sh || { echo "ERROR: uhd.sh not found!"; exit 1; } | |
uhd terraform init:layer "20-app" | |
uhd terraform apply:layer "20-app" "ci-${{ github.event.pull_request.number }}-$SHORT_SHA" | |
shell: zsh {0} | |
- name: Checkout Feature Branch | |
run: | | |
git fetch origin ${{ github.head_ref }} --depth=1 || echo "No remote branch found" | |
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }} || echo "Branch exists only locally" | |
- name: Debug Repository Files | |
run: | | |
echo "Current Directory: $(pwd)" | |
ls -la .github/actions | |
ls -la .github/actions/mask-secrets || echo "mask-secrets directory is missing!" | |
- name: Mask Secrets | |
uses: ./.github/actions/mask-secrets | |
with: | |
github_token: ${{ secrets.GITHUB_TOKEN }} | |
terraform_iam_role: ${{ secrets.UHD_TERRAFORM_IAM_ROLE }} | |
terraform_role_public: ${{ secrets.UHD_TERRAFORM_ROLE_PUBLIC }} | |
terraform_role_auth: ${{ secrets.UHD_TERRAFORM_ROLE_AUTH }} | |
terraform_role_test: ${{ secrets.UHD_TERRAFORM_ROLE_TEST }} | |
terraform_role_auth_test: ${{ secrets.UHD_TERRAFORM_ROLE_AUTH_TEST }} | |
- name: Ensure UHD Script Exists | |
run: | | |
if [[ ! -f uhd.sh ]]; then | |
echo "uhd.sh script missing. Exiting." | |
exit 1 | |
fi | |
chmod +x uhd.sh | |
- name: Configure AWS credentials | |
uses: aws-actions/configure-aws-credentials@v4 | |
with: | |
role-to-assume: ${{ secrets.UHD_TERRAFORM_IAM_ROLE }} | |
aws-region: ${{ env.AWS_REGION }} | |
- name: Validate AWS Assume Role Permissions | |
run: | | |
aws sts get-caller-identity || { echo "ERROR: AWS credentials invalid!"; exit 1; } | |
- name: Apply Terraform for Feature Branch | |
run: | | |
source uhd.sh | |
uhd terraform apply:layer 20-app ci-${{ github.event.pull_request.number }}-$SHORT_SHA | |
shell: zsh {0} | |
- name: Determine Environment | |
id: set_env | |
run: | | |
echo "PUBLIC_ENV=ci-${{ github.event.pull_request.number }}-$SHORT_SHA" >> $GITHUB_ENV | |
echo "AUTH_ENV=ci-a-${{ github.event.pull_request.number }}-$SHORT_SHA" >> $GITHUB_ENV | |
echo "PUBLIC_ENV=ci-${{ github.event.pull_request.number }}-$SHORT_SHA" >> $GITHUB_OUTPUT | |
echo "AUTH_ENV=ci-a-${{ github.event.pull_request.number }}-$SHORT_SHA" >> $GITHUB_OUTPUT | |
fast_forward_env_branches: | |
name: Fast forward env branches | |
runs-on: ubuntu-latest | |
permissions: | |
contents: write | |
outputs: | |
deploy_auth_dev: ${{ steps.fast_forward_merge.outputs.deploy_auth_dev }} | |
deploy_auth_test: ${{ steps.fast_forward_merge.outputs.deploy_auth_test }} | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.base_ref }} | |
fetch-depth: 1 | |
sparse-checkout-cone-mode: true | |
fetch-tags: false | |
show-progress: true | |
lfs: false | |
submodules: false | |
set-safe-directory: true | |
- uses: ./.github/actions/setup-zsh | |
- name: Fast forward env branches | |
id: fast_forward_merge | |
run: | | |
echo "Starting fast-forward merge..." | |
if scripts/fast-forward-env-branches.sh; then | |
echo "Fast forward merge completed successfully." | |
else | |
echo "Fast forward merge failed!" >&2 | |
exit 1 | |
fi | |
shell: zsh {0} | |
env: | |
CI: "true" | |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
deploy_auth_dev: | |
needs: fast_forward_env_branches | |
if: ${{ needs.fast_forward_env_branches.outputs.deploy_auth_dev == 'true' }} | |
uses: ./.github/workflows/well-known-environment.yml | |
with: | |
branch: env/auth-dev/auth-dev | |
secrets: inherit | |
deploy_auth_test: | |
needs: fast_forward_env_branches | |
if: ${{ needs.fast_forward_env_branches.outputs.deploy_auth_test == 'true' }} | |
uses: ./.github/workflows/well-known-environment.yml | |
with: | |
branch: env/auth-test/auth-test | |
secrets: inherit | |
unit_test_functions: | |
needs: [ "build_base" ] | |
strategy: | |
fail-fast: false | |
matrix: | |
function: [ lambda-producer-handler, lambda-db-password-rotation, lambda-alarm-notification, legacy-dashboard-redirect-viewer-request, public-api-cloud-front-viewer-request ] | |
name: Unit test functions | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
id-token: write | |
pull-requests: write | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.base_ref }} | |
fetch-depth: 1 | |
sparse-checkout-cone-mode: true | |
fetch-tags: false | |
show-progress: true | |
lfs: false | |
submodules: false | |
set-safe-directory: true | |
- name: Test ${{ matrix.function }} | |
uses: ./.github/actions/npm-test | |
with: | |
function-name: ${{ matrix.function }} | |
unit_test_report: | |
name: Unit test coverage report | |
runs-on: ubuntu-latest | |
needs: [ "unit_test_functions" ] | |
permissions: | |
contents: read | |
id-token: write | |
pull-requests: write | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.base_ref }} | |
fetch-depth: 1 | |
sparse-checkout-cone-mode: true | |
fetch-tags: false | |
show-progress: true | |
lfs: false | |
submodules: false | |
set-safe-directory: true | |
- name: Download test coverage | |
uses: actions/download-artifact@v4.1.8 | |
with: | |
path: ./reports | |
- name: Unit test report | |
uses: ukhsa-internal/jest-coverage-comment-action@v1 | |
with: | |
multiple-files: | | |
lambda-producer-handler, ./reports/lambda-producer-handler-coverage-summary/coverage-summary.json | |
lambda-db-password-rotation, ./reports/lambda-db-password-rotation-coverage-summary/coverage-summary.json | |
lambda-alarm-notification, ./reports/lambda-alarm-notification-coverage-summary/coverage-summary.json | |
legacy-dashboard-redirect-viewer-request, ./reports/legacy-dashboard-redirect-viewer-request-coverage-summary/coverage-summary.json | |
public-api-cloud-front-viewer-request, ./reports/public-api-cloud-front-viewer-request-coverage-summary/coverage-summary.json | |
multiple-junitxml-files: | | |
lambda-producer-handler, ./reports/lambda-producer-handler-coverage-report/junit.xml | |
lambda-db-password-rotation, ./reports/lambda-db-password-rotation-coverage-report/junit.xml | |
lambda-alarm-notification, ./reports/lambda-alarm-notification-coverage-report/junit.xml | |
legacy-dashboard-redirect-viewer-request, ./reports/legacy-dashboard-redirect-viewer-request-coverage-report/junit.xml | |
public-api-cloud-front-viewer-request, ./reports/public-api-cloud-front-viewer-request-coverage-report/junit.xml | |
title: unit test coverage report | |
terraform_plan: | |
name: Terraform plan (${{ matrix.environment }}) | |
runs-on: ubuntu-latest | |
needs: [ "build_base" ] | |
strategy: | |
fail-fast: false | |
matrix: | |
environment: [ public, auth ] | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.base_ref }} | |
fetch-depth: 1 | |
sparse-checkout-cone-mode: true | |
fetch-tags: false | |
show-progress: true | |
lfs: false | |
submodules: false | |
set-safe-directory: true | |
- name: Set TARGET_ENV | |
run: | | |
if [[ "${{ matrix.environment }}" == "public" ]]; then | |
echo "TARGET_ENV=${{ needs.build_base.outputs.public_env }}" >> $GITHUB_ENV | |
echo "TERRAFORM_ROLE=${{ secrets.UHD_TERRAFORM_ROLE_PUBLIC }}" >> $GITHUB_ENV | |
else | |
echo "TARGET_ENV=${{ needs.build_base.outputs.auth_env }}" >> $GITHUB_ENV | |
echo "TERRAFORM_ROLE=${{ secrets.UHD_TERRAFORM_ROLE_AUTH }}" >> $GITHUB_ENV | |
fi | |
- name: Terraform plan (${{ matrix.environment }}) | |
run: | | |
source uhd.sh | |
uhd terraform init | |
uhd terraform plan:layer 10-account $TARGET_ENV -refresh=true | |
uhd terraform plan:layer 20-app $TARGET_ENV -refresh=true | |
shell: zsh {0} | |
- name: Terraform Plan Summary | |
run: | | |
echo "### Terraform Plan Summary ###" | |
PLAN_FILE="terraform-plan-${{ matrix.environment }}.json" | |
terraform show -json tfplan-${{ matrix.environment }}.out | jq '.' > "$PLAN_FILE" || { echo "⚠ No Terraform output found!"; exit 0; } | |
if [[ ! -s "$PLAN_FILE" ]]; then | |
echo "⚠ No changes detected in Terraform plan. Skipping summary." | |
exit 0 | |
fi | |
ADDED_RESOURCES=$(jq '[.resource_changes[] | select(.change.actions | contains(["create"]))] | length' "$PLAN_FILE") | |
CHANGED_RESOURCES=$(jq '[.resource_changes[] | select(.change.actions | contains(["update"]))] | length' "$PLAN_FILE") | |
DELETED_RESOURCES=$(jq '[.resource_changes[] | select(.change.actions | contains(["delete"]))] | length' "$PLAN_FILE") | |
echo "Resources to be added: $ADDED_RESOURCES" | |
echo "Resources to be changed: $CHANGED_RESOURCES" | |
if [[ "$DELETED_RESOURCES" -gt 0 ]]; then | |
echo "WARNING: Terraform plan contains deletions! ($DELETED_RESOURCES)" | |
echo "Proceeding with Terraform apply as deletions may be intentional." | |
else | |
echo "No deletions detected. Safe to proceed!" | |
fi | |
- name: Upload Terraform Plan Artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: terraform-plan-${{ matrix.environment }} | |
path: terraform-plan-${{ matrix.environment }}.json | |
retention-days: 7 | |
- name: Validate Terraform Plan Output | |
run: | | |
echo "Checking Terraform Plan..." | |
PLAN_FILE=tfplan-${{ matrix.environment }}.out | |
terraform show -json $PLAN_FILE | jq '.' > terraform-plan-${{ matrix.environment }}.json || { echo "⚠ No Terraform plan output found!"; exit 0; } | |
DELETED_RESOURCES=$(jq -r '.resource_changes[] | select(.change.actions | contains(["delete"]))' terraform-plan-${{ matrix.environment }}.json 2>/dev/null || echo "") | |
if [[ -n "$DELETED_RESOURCES" ]]; then | |
echo "ERROR: Terraform plan contains deletions!" | |
echo "Resources marked for deletion:" | |
echo "$DELETED_RESOURCES" | |
exit 1 | |
else | |
echo "Terraform plan is safe to apply!" | |
fi | |
- name: Debug Terraform Setup | |
if: ${{ env.DESTROY_SKIPPED != 'true' }} | |
run: | | |
terraform version | |
[[ -n "$TARGET_ENV" ]] && terraform workspace show | |
- name: Select Terraform Workspace | |
uses: ./.github/actions/select-terraform-workspace | |
with: | |
target_env: $TARGET_ENV | |
- name: Debug Terraform State | |
run: | | |
echo "Current Terraform State in $TARGET_ENV:" | |
terraform state list || { | |
echo "No Terraform state found! Retrying with refresh..."; | |
terraform refresh || echo "Terraform refresh failed!" | |
terraform state list || { echo "Still no state! Check backend permissions."; exit 1; } | |
} | |
- name: Configure AWS credentials (${{ matrix.environment }}) | |
uses: ./.github/actions/configure-aws-credentials | |
with: | |
aws-region: ${{ env.AWS_REGION }} | |
test-account-role: ${{ env.TERRAFORM_ROLE }} | |
- name: Debug AWS Role & Permissions | |
run: | | |
echo "Verifying AWS Role Identity..." | |
aws sts get-caller-identity || { echo "ERROR: Failed to fetch AWS identity"; exit 1; } | |
echo "Validating AWS Permissions..." | |
aws s3 ls s3://nonexistent-bucket >/dev/null 2>&1 && echo "S3 Access OK" || echo "Limited S3 Access (Expected for some roles)" | |
- uses: ./.github/actions/setup-terraform | |
- uses: ./.github/actions/setup-zsh | |
- uses: ./.github/actions/short-sha | |
- name: Validate Terraform Configuration | |
run: | | |
terraform validate || { echo "Terraform configuration is invalid!"; exit 1; } | |
- name: Validate AWS Credentials Before Terraform | |
run: | | |
echo "Checking AWS credentials before Terraform Apply..." | |
if ! aws sts get-caller-identity; then | |
echo "ERROR: AWS credentials are invalid! Terraform cannot proceed." | |
exit 1 | |
fi | |
- name: Auto-Fix Terraform Formatting | |
run: | | |
terraform fmt -recursive -diff | |
git diff --exit-code || (echo "ERROR: Terraform formatting issues detected! Run 'terraform fmt' locally to fix." && exit 1) | |
terraform_apply: | |
name: Terraform apply (${{ matrix.environment }}) | |
runs-on: ubuntu-latest | |
needs: [ "terraform_plan" ] | |
strategy: | |
fail-fast: false | |
matrix: | |
environment: [ public, auth ] | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.base_ref }} | |
fetch-depth: 1 | |
sparse-checkout-cone-mode: true | |
fetch-tags: false | |
show-progress: true | |
lfs: false | |
submodules: false | |
set-safe-directory: true | |
- name: Set TARGET_ENV | |
run: | | |
if [[ "${{ matrix.environment }}" == "public" ]]; then | |
echo "TARGET_ENV=${{ needs.build_base.outputs.public_env }}" >> $GITHUB_ENV | |
echo "TERRAFORM_ROLE=${{ secrets.UHD_TERRAFORM_ROLE_PUBLIC }}" >> $GITHUB_ENV | |
else | |
echo "TARGET_ENV=${{ needs.build_base.outputs.auth_env }}" >> $GITHUB_ENV | |
echo "TERRAFORM_ROLE=${{ secrets.UHD_TERRAFORM_ROLE_AUTH }}" >> $GITHUB_ENV | |
fi | |
- uses: actions/setup-python@v5 | |
- name: Debug Terraform Setup | |
if: ${{ env.DESTROY_SKIPPED != 'true' }} | |
run: | | |
terraform version | |
[[ -n "$TARGET_ENV" ]] && terraform workspace show | |
- name: Select Terraform Workspace | |
uses: ./.github/actions/select-terraform-workspace | |
with: | |
target_env: $TARGET_ENV | |
- name: Configure AWS credentials (${{ matrix.environment }}) | |
uses: ./.github/actions/configure-aws-credentials | |
with: | |
aws-region: ${{ env.AWS_REGION }} | |
test-account-role: ${{ secrets[format('UHD_TERRAFORM_ROLE_{0}', matrix.environment == 'public' && 'PUBLIC' || 'AUTH')] }} | |
- name: Retry Terraform Apply (${{ matrix.environment }}) | |
run: | | |
for attempt in {1..3}; do | |
set -e | |
source uhd.sh | |
uhd terraform init | |
layers=("10-account" "20-app") | |
declare -A pids errors logs start_times end_times durations | |
for layer in "${layers[@]}"; do | |
logs[$layer]="terraform-apply-$layer.log" | |
start_times[$layer]=$(date +%s) | |
echo "Applying Terraform for $layer (log: ${logs[$layer]})..." | |
(uhd terraform apply:layer $layer $TARGET_ENV | tee "${logs[$layer]}") & | |
pids[$layer]=$! | |
done | |
for layer in "${!pids[@]}"; do | |
if ! wait "${pids[$layer]}"; then | |
errors[$layer]=1 | |
echo "Terraform Apply Failed for $layer! Attempting to unlock..." | |
terraform force-unlock -force || echo "Warning: Could not force-unlock Terraform state." | |
fi | |
end_times[$layer]=$(date +%s) | |
durations[$layer]=$((end_times[$layer] - start_times[$layer])) | |
done | |
if [[ ${#errors[@]} -gt 0 ]]; then | |
echo "Terraform apply failed for layers: ${!errors[@]}" | |
if [[ $attempt -lt 3 ]]; then | |
echo "Retrying Terraform Apply (Attempt $((attempt+1))) in 15s..." | |
sleep 15 | |
else | |
echo "Final Attempt Failed. Exiting..." | |
exit 1 | |
fi | |
else | |
echo "Terraform Apply Succeeded!" | |
break | |
fi | |
done | |
shell: zsh {0} | |
- name: Upload Terraform Apply Logs | |
uses: actions/upload-artifact@v4 | |
with: | |
name: terraform-apply-${{ matrix.environment }} | |
path: terraform-apply-*.log | |
retention-days: 7 | |
- name: Terraform Apply Summary | |
run: | | |
echo "### Terraform Apply Summary ###" | |
for layer in "10-account" "20-app"; do | |
if [[ -f "terraform-apply-$layer.log" ]]; then | |
echo "----- Terraform Apply Log for $layer (Last 50 lines) -----" | |
tail -n 50 "terraform-apply-$layer.log" || echo "No log file found." | |
echo "--------------------------------------------------------" | |
echo "Changes Applied:" | |
CREATED=$(grep "created" "terraform-apply-$layer.log" || echo "No new resources created.") | |
UPDATED=$(grep "updated" "terraform-apply-$layer.log" || echo "No resources updated.") | |
DESTROYED=$(grep "destroyed" "terraform-apply-$layer.log" || echo "No resources deleted.") | |
echo "Terraform Apply Summary for $layer" | |
echo "----------------------------------------" | |
echo "Created Resources:" | |
echo "$CREATED" | |
echo "" | |
echo "Updated Resources:" | |
echo "$UPDATED" | |
echo "" | |
echo "Deleted Resources:" | |
echo "$DESTROYED" | |
echo "----------------------------------------" | |
echo "Checking Terraform Apply status for $layer..." | |
if grep -q "Apply complete!" "terraform-apply-$layer.log" || ! grep -iq "error" "terraform-apply-$layer.log"; then | |
echo "Terraform Apply Completed Successfully for $layer" | |
else | |
echo "ERROR: Terraform Apply failed for $layer!" | |
ERROR_LINES=$(grep -i "error" "terraform-apply-$layer.log" | tail -n 10) | |
if [[ -z "$ERROR_LINES" ]]; then | |
echo "No specific errors found, check full logs." | |
else | |
echo "Last 10 error messages:" | |
echo "$ERROR_LINES" | |
fi | |
exit 1 | |
fi | |
else | |
echo "Warning: Log file for $layer not found!" | |
fi | |
done | |
shell: zsh {0} | |
push_docker_images: | |
name: Push docker images (${{ matrix.environment }}) | |
runs-on: ubuntu-latest | |
needs: [ "terraform_apply" ] | |
strategy: | |
fail-fast: false | |
matrix: | |
environment: [ public, auth ] | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.base_ref }} | |
fetch-depth: 1 | |
- name: Set TARGET_ENV | |
run: | | |
if [[ "${{ matrix.environment }}" == "public" ]]; then | |
echo "TARGET_ENV=${{ needs.build_base.outputs.public_env }}" >> $GITHUB_ENV | |
else | |
echo "TARGET_ENV=${{ needs.build_base.outputs.auth_env }}" >> $GITHUB_ENV | |
fi | |
- name: Configure AWS credentials (${{ matrix.environment }}) | |
uses: ./.github/actions/configure-aws-credentials | |
with: | |
aws-region: ${{ env.AWS_REGION }} | |
test-account-role: ${{ secrets[format('UHD_TERRAFORM_ROLE_{0}', matrix.environment == 'public' && 'PUBLIC' || 'AUTH')] }} | |
- uses: ./.github/actions/setup-zsh | |
- uses: ./.github/actions/short-sha | |
- name: Set SHORT_SHA Environment Variable | |
run: | | |
echo "SHORT_SHA=${{ env.SHORT_SHA }}" >> $GITHUB_ENV | |
echo "SHORT_SHA=${{ env.SHORT_SHA }}" | |
- name: Retrieve ECR Repository Name | |
id: get_ecr_repo | |
run: | | |
REPO_NAME="ukhsa-data-dashboard/back-end" | |
echo "Using ECR Repository: $REPO_NAME" | |
echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV | |
- name: Login to AWS ECR | |
run: | | |
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) | |
aws ecr get-login-password --region ${{ env.AWS_REGION }} | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com | |
- name: Check if Docker image exists in ECR | |
id: check_docker_image | |
run: | | |
IMAGE_TAG="ci-${{ github.event.pull_request.number }}-${{ env.SHORT_SHA }}" | |
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) | |
echo "Checking if image $IMAGE_TAG exists in AWS ECR ($REPO_NAME)..." | |
if aws ecr describe-images --repository-name "$REPO_NAME" \ | |
--image-ids imageTag="$IMAGE_TAG" \ | |
--registry-id "$AWS_ACCOUNT_ID" &> /dev/null; then | |
echo "Docker image $IMAGE_TAG already exists. Skipping push." | |
echo "SKIP_PUSH=true" >> $GITHUB_ENV | |
else | |
echo "Docker image $IMAGE_TAG not found. Proceeding with push." | |
echo "SKIP_PUSH=false" >> $GITHUB_ENV | |
fi | |
- name: Pull / Push Docker image | |
if: ${{ env.SKIP_PUSH != 'true' }} | |
run: | | |
source uhd.sh | |
uhd docker update ${{ matrix.environment }} ci-${{ github.event.pull_request.number }}-${{ env.SHORT_SHA }} | |
restart_services: | |
name: Restart services (${{ matrix.environment }}) | |
runs-on: ubuntu-latest | |
needs: [ "push_docker_images" ] | |
strategy: | |
fail-fast: false | |
matrix: | |
environment: [ public, auth ] | |
env: | |
TARGET_ENV: | |
${{ needs.build_base.outputs[matrix.environment == 'public' && 'public_env' || 'auth_env'] }} | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.base_ref }} | |
fetch-depth: 1 | |
sparse-checkout-cone-mode: true | |
fetch-tags: false | |
show-progress: true | |
lfs: false | |
submodules: false | |
set-safe-directory: true | |
- name: Configure AWS credentials (${{ matrix.environment }}) | |
uses: ./.github/actions/configure-aws-credentials | |
with: | |
aws-region: ${{ env.AWS_REGION }} | |
test-account-role: ${{ secrets[format('UHD_TERRAFORM_ROLE_{0}', matrix.environment == 'public' && 'PUBLIC' || 'AUTH')] }} | |
- uses: ./.github/actions/setup-terraform | |
- uses: ./.github/actions/setup-zsh | |
- uses: ./.github/actions/short-sha | |
- name: Terraform output | |
run: | | |
source uhd.sh | |
uhd terraform init:layer 20-app | |
uhd terraform output:layer 20-app $PUBLIC_ENV | |
uhd terraform output:layer 20-app $AUTH_ENV | |
shell: zsh {0} | |
- name: Restart ECS services (${{ matrix.environment }}) | |
run: | | |
source uhd.sh | |
uhd ecs restart-services | |
shell: zsh {0} | |
- name: Redeploy lambda functions | |
run: | | |
source uhd.sh | |
uhd lambda restart-functions | |
shell: zsh {0} | |
terraform_destroy: | |
name: Terraform destroy (${{ matrix.environment }}) | |
runs-on: ubuntu-latest | |
timeout-minutes: 30 | |
if: ${{ success() }} | |
needs: [ "terraform_plan" ] | |
strategy: | |
fail-fast: false | |
matrix: | |
environment: [ public, auth ] | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.base_ref }} | |
fetch-depth: 1 | |
sparse-checkout-cone-mode: true | |
fetch-tags: false | |
show-progress: true | |
lfs: false | |
submodules: false | |
set-safe-directory: true | |
- name: Set TARGET_ENV | |
run: | | |
if [[ "${{ matrix.environment }}" == "public" ]]; then | |
echo "TARGET_ENV=${{ needs.build_base.outputs.public_env }}" >> $GITHUB_ENV | |
echo "TERRAFORM_ROLE=${{ secrets.UHD_TERRAFORM_ROLE_PUBLIC }}" >> $GITHUB_ENV | |
else | |
echo "TARGET_ENV=${{ needs.build_base.outputs.auth_env }}" >> $GITHUB_ENV | |
echo "TERRAFORM_ROLE=${{ secrets.UHD_TERRAFORM_ROLE_AUTH }}" >> $GITHUB_ENV | |
fi | |
- uses: actions/setup-python@v5 | |
- name: Debug Terraform Setup | |
if: ${{ env.DESTROY_SKIPPED != 'true' }} | |
run: | | |
terraform version | |
[[ -n "$TARGET_ENV" ]] && terraform workspace show | |
- name: Select Terraform Workspace | |
uses: ./.github/actions/select-terraform-workspace | |
with: | |
target_env: $TARGET_ENV | |
- name: Configure AWS credentials (${{ matrix.environment }}) | |
uses: ./.github/actions/configure-aws-credentials | |
with: | |
aws-region: ${{ env.AWS_REGION }} | |
test-account-role: ${{ secrets[format('UHD_TERRAFORM_ROLE_{0}', matrix.environment == 'public' && 'PUBLIC' || 'AUTH')] }} | |
- name: Cleanup Terraform Workspace on Failure | |
if: failure() | |
run: | | |
echo "Terraform Destroy failed. Cleaning up workspace..." | |
terraform workspace select default | |
terraform workspace delete $TARGET_ENV || echo "Warning: Failed to delete workspace" | |
- name: Retry Terraform Destroy (${{ matrix.environment }}) | |
run: | | |
for attempt in {1..3}; do | |
set -x | |
source uhd.sh | |
uhd terraform init:layer 20-app | |
layers=("20-app" "10-account") | |
declare -A pids errors logs start_times end_times durations | |
for layer in "${layers[@]}"; do | |
logs[$layer]="terraform-destroy-$layer.log" | |
start_times[$layer]=$(date +%s) | |
echo "Destroying Terraform for $layer (log: ${logs[$layer]})..." | |
(uhd terraform destroy:layer $layer $TARGET_ENV > >(tee "${logs[$layer]}") 2> >(tee -a "${logs[$layer]}" >&2)) & | |
pids[$layer]=$! | |
done | |
for layer in "${!pids[@]}"; do | |
if ! wait "${pids[$layer]}"; then | |
errors[$layer]=1 | |
echo "Terraform Destroy Failed for $layer! Unlocking state..." | |
terraform force-unlock -force || echo "Warning: Could not force-unlock Terraform state." | |
fi | |
end_times[$layer]=$(date +%s) | |
durations[$layer]=$((end_times[$layer] - start_times[$layer])) | |
done | |
if [[ ${#errors[@]} -gt 0 ]]; then | |
echo "Terraform destroy failed for layers: ${!errors[@]}" | |
if [[ $attempt -lt 3 ]]; then | |
echo "Retrying Terraform Destroy (Attempt $((attempt+1)))..." | |
sleep 10 | |
else | |
echo "Final attempt failed. Exiting..." | |
exit 1 | |
fi | |
else | |
echo "Terraform Destroy Succeeded!" | |
break | |
fi | |
done | |
shell: zsh {0} | |
- name: Upload Terraform Destroy Logs | |
uses: actions/upload-artifact@v4 | |
with: | |
name: terraform-destroy-${{ matrix.environment }} | |
path: terraform-destroy-${{ matrix.environment }}.log | |
clean_up_remaining_resources: | |
name: Clean up remaining resources | |
runs-on: ubuntu-latest | |
needs: [ "terraform_destroy" ] | |
if: always() | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.base_ref }} | |
fetch-depth: 1 | |
sparse-checkout-cone-mode: true | |
fetch-tags: false | |
show-progress: true | |
lfs: false | |
submodules: false | |
set-safe-directory: true | |
- name: Configure AWS credentials for tools account | |
uses: ./.github/actions/configure-aws-credentials | |
with: | |
aws-region: ${{ env.AWS_REGION }} | |
tools-account-role: ${{ secrets.UHD_TERRAFORM_IAM_ROLE }} | |
- uses: ./.github/actions/setup-zsh | |
- uses: ./.github/actions/short-sha | |
- name: Cleanup Terraform Workspaces | |
continue-on-error: true | |
run: | | |
terraform init -upgrade | |
terraform workspace list || { echo "Warning: Failed to list workspaces"; exit 1; } | |
if ! terraform workspace list | grep -q "default"; then | |
terraform workspace new default | |
fi | |
WORKSPACES=$(terraform workspace list | grep -E '^ci-|^auth-ci-' | tr -d '* ' || echo "") | |
if [[ -z "$WORKSPACES" ]]; then | |
echo "No CI workspaces found. Skipping cleanup." | |
exit 0 | |
fi | |
echo "Deleting Terraform workspaces in parallel..." | |
for workspace in $WORKSPACES; do | |
( | |
echo "Attempting to delete workspace: $workspace" | |
terraform workspace select default | |
terraform workspace delete "$workspace" || (echo "Workspace $workspace still in use. Retrying in 10s..." && sleep 10 && terraform workspace delete "$workspace" || echo "Final attempt failed for $workspace.") | |
) & | |
done | |
wait # Ensure all background jobs finish | |
- name: Delete secrets safely | |
continue-on-error: true | |
run: | | |
source uhd.sh | |
uhd secrets delete-all-secrets ci-$SHORT_SHA || echo "⚠ Warning: Secret deletion failed!" | |
shell: zsh {0} |