Skip to content

Deploy to Production #26

Deploy to Production

Deploy to Production #26

name: Deploy to Production
on:
workflow_dispatch:
inputs:
invoke_staging:
description: "Invoke staging Lambda after deploy"
type: boolean
default: true
permissions:
id-token: write
contents: read
concurrency:
group: deploy-production
cancel-in-progress: false
env:
AWS_REGION: us-west-2
STAGING_FUNCTION: HNDigest-staging
PROD_FUNCTION: HNDigest
jobs:
echo-inputs:
runs-on: ubuntu-latest
steps:
- name: Echo Inputs
run: |
echo "invoke_staging: ${{ github.event.inputs.invoke_staging }}"
build:
name: Build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/build-lambda
# --- Staging Infrastructure ---
staging-infra-plan:
name: "Infra Plan (staging)"
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
defaults:
run:
working-directory: infrastructure/environments/staging
outputs:
has_changes: ${{ steps.plan.outputs.has_changes }}
steps:
- uses: actions/checkout@v6
- uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- uses: opentofu/setup-opentofu@v1
with:
tofu_wrapper: false
- name: Initialize
run: tofu init
- name: Plan
id: plan
run: |
set +e
tofu plan -detailed-exitcode -out=tfplan > plan_output.txt 2>&1
EXIT_CODE=$?
set -e
cat plan_output.txt
if [ $EXIT_CODE -eq 0 ]; then
echo "has_changes=false" >> $GITHUB_OUTPUT
elif [ $EXIT_CODE -eq 2 ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "Plan failed with exit code $EXIT_CODE"
exit 1
fi
- name: Upload plan artifact
if: steps.plan.outputs.has_changes == 'true'
uses: actions/upload-artifact@v6
with:
name: tfplan-staging
path: infrastructure/environments/staging/tfplan
retention-days: 1
staging-infra-apply:
name: "Infra Apply (staging)"
needs: staging-infra-plan
if: needs.staging-infra-plan.outputs.has_changes == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: infrastructure/environments/staging
steps:
- uses: actions/checkout@v6
- uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- uses: opentofu/setup-opentofu@v1
- name: Initialize
run: tofu init
- name: Download plan artifact
uses: actions/download-artifact@v7
with:
name: tfplan-staging
path: infrastructure/environments/staging
- name: Apply
run: tofu apply -auto-approve tfplan
- name: Invalidate CloudFront cache
run: |
DIST_ID=$(tofu output -raw cloudfront_distribution_id)
aws cloudfront create-invalidation \
--distribution-id "$DIST_ID" \
--paths "/*"
# --- Staging Code Deploy ---
deploy-staging:
name: Deploy to Staging
needs: [staging-infra-apply, build]
if: ${{ !cancelled() && !failure() }}
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Download artifact
uses: actions/download-artifact@v7
with:
name: lambda-zips
- name: Deploy to Digest Lambda
run: |
aws lambda update-function-code \
--function-name ${{ env.STAGING_FUNCTION }} \
--zip-file fileb://lambda.zip
- name: Deploy to API Lambda
run: |
aws lambda update-function-code \
--function-name ${{ env.STAGING_FUNCTION }}-api \
--zip-file fileb://api-lambda.zip
- name: Deploy to Bounce Handler Lambda
run: |
aws lambda update-function-code \
--function-name ${{ env.STAGING_FUNCTION }}-bounce-handler \
--zip-file fileb://bounce-handler-lambda.zip
- name: Wait for function update
run: |
aws lambda wait function-updated \
--function-name ${{ env.STAGING_FUNCTION }}
aws lambda wait function-updated \
--function-name ${{ env.STAGING_FUNCTION }}-api
aws lambda wait function-updated \
--function-name ${{ env.STAGING_FUNCTION }}-bounce-handler
invoke-staging:
name: Invoke Staging
needs: deploy-staging
if: ${{ !cancelled() && !failure() && inputs.invoke_staging }}
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Invoke Lambda
run: |
echo "Invoking ${{ env.STAGING_FUNCTION }}..."
aws lambda invoke \
--function-name ${{ env.STAGING_FUNCTION }} \
--log-type Tail \
--query 'LogResult' \
--output text \
response.json | base64 --decode
echo ""
echo "Response:"
cat response.json
# --- Production Infrastructure ---
prod-infra-plan:
name: "Infra Plan (production)"
runs-on: ubuntu-latest
defaults:
run:
working-directory: infrastructure/environments/production
outputs:
has_changes: ${{ steps.plan.outputs.has_changes }}
steps:
- uses: actions/checkout@v6
- uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- uses: opentofu/setup-opentofu@v1
with:
tofu_wrapper: false
- name: Initialize
run: tofu init
- name: Plan
id: plan
run: |
set +e
tofu plan -detailed-exitcode -out=tfplan > plan_output.txt 2>&1
EXIT_CODE=$?
set -e
cat plan_output.txt
if [ $EXIT_CODE -eq 0 ]; then
echo "has_changes=false" >> $GITHUB_OUTPUT
elif [ $EXIT_CODE -eq 2 ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "Plan failed with exit code $EXIT_CODE"
exit 1
fi
- name: Upload plan artifact
if: steps.plan.outputs.has_changes == 'true'
uses: actions/upload-artifact@v6
with:
name: tfplan-production
path: infrastructure/environments/production/tfplan
retention-days: 1
# --- Production Approval + Deploy ---
approve-prod:
name: Approve Production
needs: [prod-infra-plan, deploy-staging, invoke-staging]
if: ${{ !cancelled() && !failure() }}
runs-on: ubuntu-latest
environment: production
steps:
- run: echo "Production deployment approved"
prod-infra-apply:
name: "Infra Apply (production)"
needs: [approve-prod, prod-infra-plan]
if: ${{ !cancelled() && !failure() && needs.prod-infra-plan.outputs.has_changes == 'true' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: infrastructure/environments/production
steps:
- uses: actions/checkout@v6
- uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- uses: opentofu/setup-opentofu@v1
- name: Initialize
run: tofu init
- name: Download plan artifact
uses: actions/download-artifact@v7
with:
name: tfplan-production
path: infrastructure/environments/production
- name: Apply
run: tofu apply -auto-approve tfplan
- name: Invalidate CloudFront cache
run: |
DIST_ID=$(tofu output -raw cloudfront_distribution_id)
aws cloudfront create-invalidation \
--distribution-id "$DIST_ID" \
--paths "/*"
deploy-prod:
name: Deploy to Production
needs: [approve-prod, prod-infra-apply]
if: ${{ !cancelled() && !failure() }}
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Download artifact
uses: actions/download-artifact@v7
with:
name: lambda-zips
- name: Deploy to Digest Lambda
run: |
echo "Deploying to ${{ env.PROD_FUNCTION }}..."
aws lambda update-function-code \
--function-name ${{ env.PROD_FUNCTION }} \
--zip-file fileb://lambda.zip
- name: Deploy to API Lambda
run: |
echo "Deploying to ${{ env.PROD_FUNCTION }}-api..."
aws lambda update-function-code \
--function-name ${{ env.PROD_FUNCTION }}-api \
--zip-file fileb://api-lambda.zip
- name: Deploy to Bounce Handler Lambda
run: |
echo "Deploying to ${{ env.PROD_FUNCTION }}-bounce-handler..."
aws lambda update-function-code \
--function-name ${{ env.PROD_FUNCTION }}-bounce-handler \
--zip-file fileb://bounce-handler-lambda.zip
- name: Wait for function update
run: |
aws lambda wait function-updated \
--function-name ${{ env.PROD_FUNCTION }}
aws lambda wait function-updated \
--function-name ${{ env.PROD_FUNCTION }}-api
aws lambda wait function-updated \
--function-name ${{ env.PROD_FUNCTION }}-bounce-handler
echo "Production Lambda updated successfully"