Skip to content

Automate Deployment to AWS Cloud #58

Automate Deployment to AWS Cloud

Automate Deployment to AWS Cloud #58

Workflow file for this run

# Workflow references https://stackoverflow.com/questions/59166099/github-action-aws-cli,
# https://stackoverflow.com/questions/51028677/create-aws-ecr-repository-if-it-doesnt-exist,
# https://github.com/aws-actions/amazon-ecr-login and
# https://medium.com/@octavio/ecs-deployments-with-github-actions-dd34beed6528
# https://stackoverflow.com/questions/75546117/github-action-how-to-edit-a-json-objects-with-github-repository-secrets
name: Run Tests and Deploy to AWS
on:
pull_request:
# pull_request_review:
# types:
# - submitted
push:
env:
PYTHON_VERSION: "3.12"
AWS_REGION: "ap-southeast-1"
TF_CLOUD_ORGANIZATION: "ssg-wsg"
TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }}
TF_WORKSPACE: "Sample-Codes"
TF_BUCKET_NAME: "ssg-tf-bucket"
TF_VAR_SSL_PRIVATE_KEY: ${{ secrets.SSL_PRIVATE_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
jobs:
test:
strategy:
matrix:
os: [ ubuntu-latest, windows-latest, macOS-latest ]
name: Test on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: "pip"
- name: Install dependencies
working-directory: ./SSG-API-Testing-Application-v2/app
run: pip install -r requirements.txt
- name: Run tests
working-directory: ./SSG-API-Testing-Application-v2/app
run: python test_runner.py
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4.0.1
with:
files: ./SSG-API-Testing-Application-v2/app/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
setup:
needs:
- test
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
name: Setup Terraform Backend
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Terraform
uses: hashicorp/setup-terraform@v3
- name: Verify Terraform Script
id: create-backend-verify
working-directory: ./SSG-API-Testing-Application-v2/deploy/create-backend
run: |
terraform fmt
terraform fmt -check
- name: Initialise Backend
id: init-backend
working-directory: ./SSG-API-Testing-Application-v2/deploy/create-backend
run: terraform init
- name: Validate Terraform Script
id: create-backend-validate
working-directory: ./SSG-API-Testing-Application-v2/deploy/create-backend
run: terraform validate
# sync with TF cloud
# https://developer.hashicorp.com/terraform/tutorials/automation/github-actions
- name: Upload Terraform Configuration
uses: hashicorp/tfc-workflows-github/actions/upload-configuration@v1.3.0
id: plan-upload
continue-on-error: true # this is optional
with:
token: ${{ secrets.TF_API_TOKEN }}
organization: ${{ env.TF_CLOUD_ORGANIZATION }}
workspace: ${{ env.TF_WORKSPACE }}
configuration_path: ./SSG-API-Testing-Application-v2/deploy/create-backend
speculative: true
- name: Create Plan Run
uses: hashicorp/tfc-workflows-github/actions/create-run@v1.3.0
id: plan-run
continue-on-error: true # this is optional
with:
token: ${{ secrets.TF_API_TOKEN }}
organization: ${{ env.TF_CLOUD_ORGANIZATION }}
workspace: ${{ env.TF_WORKSPACE }}
configuration_version: ${{ steps.plan-upload.outputs.configuration_version_id }}
plan_only: true
- name: Get Plan Output
uses: hashicorp/tfc-workflows-github/actions/plan-output@v1.3.0
id: plan-output
continue-on-error: true # this is optional
with:
plan: ${{ fromJSON(steps.plan-run.outputs.payload).data.relationships.plan.data.id }}
- name: Update PR
uses: actions/github-script@v6
id: plan-comment
continue-on-error: true # this is optional
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// 1. Retrieve existing bot comments for the PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(comment => {
return comment.user.type === 'Bot' && comment.body.includes('HCP Terraform Plan Output')
});
const output = `#### HCP Terraform Plan Output
\`\`\`
Plan: ${{ steps.plan-output.outputs.add }} to add, ${{ steps.plan-output.outputs.change }} to change, ${{ steps.plan-output.outputs.destroy }} to destroy.
\`\`\`
[HCP Terraform Plan](${{ steps.plan-run.outputs.run_link }})
`;
// 3. Delete previous comment so PR timeline makes sense
if (botComment) {
github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
});
}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
});
- name: View Backend Plan
id: plan-backend
working-directory: ./SSG-API-Testing-Application-v2/deploy/create-backend
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: terraform plan
- name: Apply Terraform Plan
id: apply-backend
working-directory: ./SSG-API-Testing-Application-v2/deploy/create-backend
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: terraform apply -auto-approve
continue-on-error: true # possible errors are to do with the presence of the bucket
main-infra:
needs:
- setup
runs-on: ubuntu-latest
name: Create/Maintain Main Infrastructure
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Terraform
uses: hashicorp/setup-terraform@v3
- name: Verify Terraform Script
id: create-main-verify
working-directory: ./SSG-API-Testing-Application-v2/deploy/main-infrastructure
run: |
terraform fmt
terraform fmt -check
- name: Initialise Backend
id: init-main
working-directory: ./SSG-API-Testing-Application-v2/deploy/main-infrastructure
run: |
terraform init -backend-config="access_key=$AWS_ACCESS_KEY_ID" -backend-config="secret_key=$AWS_SECRET_ACCESS_KEY"
- name: Validate Terraform Script
id: create-main-validate
working-directory: ./SSG-API-Testing-Application-v2/deploy/main-infrastructure
run: terraform validate
# sync with TF cloud
# https://developer.hashicorp.com/terraform/tutorials/automation/github-actions
- name: Upload Terraform Configuration
uses: hashicorp/tfc-workflows-github/actions/upload-configuration@v1.3.0
id: plan-upload
continue-on-error: true # this is optional
with:
token: ${{ secrets.TF_API_TOKEN }}
organization: ${{ env.TF_CLOUD_ORGANIZATION }}
workspace: ${{ env.TF_WORKSPACE }}
configuration_path: ./SSG-API-Testing-Application-v2/deploy/main-infrastructure
speculative: true
- name: Create Plan Run
uses: hashicorp/tfc-workflows-github/actions/create-run@v1.3.0
id: plan-run
continue-on-error: true # this is optional
with:
token: ${{ secrets.TF_API_TOKEN }}
organization: ${{ env.TF_CLOUD_ORGANIZATION }}
workspace: ${{ env.TF_WORKSPACE }}
configuration_version: ${{ steps.plan-upload.outputs.configuration_version_id }}
plan_only: true
- name: Get Plan Output
uses: hashicorp/tfc-workflows-github/actions/plan-output@v1.3.0
id: plan-output
continue-on-error: true # this is optional
with:
plan: ${{ fromJSON(steps.plan-run.outputs.payload).data.relationships.plan.data.id }}
- name: Update PR
uses: actions/github-script@v6
id: plan-comment
continue-on-error: true # this is optional
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// 1. Retrieve existing bot comments for the PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(comment => {
return comment.user.type === 'Bot' && comment.body.includes('HCP Terraform Plan Output')
});
const output = `#### HCP Terraform Plan Output
\`\`\`
Plan: ${{ steps.plan-output.outputs.add }} to add, ${{ steps.plan-output.outputs.change }} to change, ${{ steps.plan-output.outputs.destroy }} to destroy.
\`\`\`
[HCP Terraform Plan](${{ steps.plan-run.outputs.run_link }})
`;
// 3. Delete previous comment so PR timeline makes sense
if (botComment) {
github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
});
}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
});
- name: View Main Infrastructure Plan
id: plan-main
working-directory: ./SSG-API-Testing-Application-v2/deploy/main-infrastructure
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ env.AWS_REGION }}
TF_VAR_SSL_PRIVATE_KEY: ${{ secrets.SSL_PRIVATE_KEY }}
run: terraform plan
- name: Apply Terraform Plan
id: apply-main
working-directory: ./SSG-API-Testing-Application-v2/deploy/main-infrastructure
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ env.AWS_REGION }}
TF_VAR_SSL_PRIVATE_KEY: ${{ secrets.SSL_PRIVATE_KEY }}
run: terraform apply -auto-approve
# if rollback fails, you may need to manually delete the infrastructure using the AWS console
# Inspired by: https://github.com/orgs/community/discussions/26097
- name: Rollback and Destroy Infrastructure
if: failure() && steps.apply-main.outcome == 'failure'
continue-on-error: true
working-directory: ./SSG-API-Testing-Application-v2/deploy/main-infrastructure
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ env.AWS_REGION }}
run: terraform destroy -auto-approve