Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
27b88df
Add Week 08 Terraform infrastructure
rohanrao-git Sep 10, 2026
cb71336
feat: automate production deployment for 9.3C
rohanrao-git Sep 14, 2026
84d9e11
Merge pull request #1 from rohanrao-git/feature/sit722-9.3c-continuou…
rohanrao-git Sep 14, 2026
212d3ce
Add Terraform validation and plan to CI
rohanrao-git Sep 21, 2026
a3487dc
Merge pull request #2 from rohanrao-git/feature/sit722-9.3c-continuou…
rohanrao-git Sep 21, 2026
2bef43e
Use ACR pull secrets without role assignment
rohanrao-git Sep 21, 2026
65a5cc2
Merge pull request #3 from rohanrao-git/feature/sit722-9.3c-continuou…
rohanrao-git Sep 21, 2026
1644759
Add Docker Scout image scanning
rohanrao-git Sep 21, 2026
2a86a99
Deploy Prometheus and Grafana in pipeline
rohanrao-git Sep 21, 2026
d194398
Merge pull request #4 from rohanrao-git/feature/sit722-9.3c-continuou…
rohanrao-git Sep 21, 2026
4af4bcc
Authenticate Docker Scout in CI
rohanrao-git Sep 21, 2026
b1ab1a6
Merge pull request #5 from rohanrao-git/feature/sit722-9.3c-continuou…
rohanrao-git Sep 21, 2026
93e6d94
Discover AKS nodes for Prometheus cAdvisor
rohanrao-git Sep 21, 2026
88ce8fb
Merge pull request #6 from rohanrao-git/feature/sit722-9.3c-continuou…
rohanrao-git Sep 21, 2026
256e4e9
Reload Prometheus monitoring configuration
rohanrao-git Sep 21, 2026
ede21d2
Merge pull request #7 from rohanrao-git/feature/sit722-9.3c-continuou…
rohanrao-git Sep 21, 2026
7989ffc
Remediate PyJWT vulnerability
rohanrao-git Sep 21, 2026
c163dbc
Merge pull request #8 from rohanrao-git/feature/sit722-9.3c-continuou…
rohanrao-git Sep 21, 2026
86f6b7f
Demonstrate production rollback with temporary fault
rohanrao-git Sep 23, 2026
f3dd860
Demonstrate production rollback with temporary fault
rohanrao-git Sep 23, 2026
aa26503
Deploy frontend with App Service slot promotion
rohanrao-git Sep 23, 2026
940f1c6
Deploy frontend with App Service slot promotion
rohanrao-git Sep 23, 2026
ad3a03d
Allow frontend container to start on App Service
rohanrao-git Sep 23, 2026
07a6a83
Allow frontend container to start on App Service
rohanrao-git Sep 23, 2026
50d7f17
Harden App Service slot deployment commands
rohanrao-git Sep 23, 2026
4c7ba2a
Harden App Service slot deployment commands
rohanrao-git Sep 23, 2026
12b3aaf
Configure staging image through App Service ARM
rohanrao-git Sep 23, 2026
d8884d2
Configure staging image through App Service ARM
rohanrao-git Sep 23, 2026
b529632
Do not block CI on Scout download failures
rohanrao-git Sep 23, 2026
11f4ab4
Do not block CI on Scout download failures
rohanrao-git Sep 23, 2026
9109712
Wait for App Service swaps before verification
rohanrao-git Sep 23, 2026
e7b7224
Wait for App Service swaps before verification
rohanrao-git Sep 23, 2026
1a3d9ee
Restore healthy frontend deployment
rohanrao-git Sep 23, 2026
a9e5f67
Restore healthy frontend deployment
rohanrao-git Sep 23, 2026
be2b3ea
ALL DONE
rohanrao-git Sep 24, 2026
eb2c634
Prepare clean blue green deployment submission
rohanrao-git Sep 24, 2026
a7dcc18
Merge complete blue green deployment submission
rohanrao-git Sep 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 61 additions & 6 deletions .github/workflows/01-ci.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
name: 01 - CI

on:
# Trigger the workflow on push to main branch

push:
branches:
- main


pull_request:
branches:
- main

# Manual trigger for the workflow

workflow_dispatch:


jobs:

# =========================================================

# Backend Tests
# =========================================================

backend-test:
name: Test ${{ matrix.service }}
runs-on: ubuntu-latest
Expand Down Expand Up @@ -102,16 +107,55 @@ jobs:
pytest -v


# =========================================================

# Terraform Infrastructure Validation and Plan

terraform:
name: Terraform Validate and Plan
runs-on: ubuntu-latest

needs:
- backend-test

steps:

- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Terraform
uses: hashicorp/setup-terraform@v3

- name: Initialize Terraform
working-directory: terraform
run: terraform init -input=false

- name: Validate Terraform
working-directory: terraform
run: terraform validate

- name: Login to Azure for Terraform plan
if: github.event_name != 'pull_request'
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: Plan Terraform changes
if: github.event_name != 'pull_request'
working-directory: terraform
run: terraform plan -input=false -var-file=terraform.tfvars



# Build and Push Docker Images
# =========================================================

build-and-push:
name: Build and Push ${{ matrix.image }}
runs-on: ubuntu-latest

# All backend tests must pass before this job starts
needs:
- backend-test
- terraform

# Build and push when code is pushed to main or manually triggered
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
Expand Down Expand Up @@ -160,6 +204,17 @@ jobs:
-t ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} \
./${{ matrix.service }}

- name: Scan image with Docker Scout
continue-on-error: true
uses: docker/scout-action@v1
with:
command: cves
image: ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }}
only-severities: critical,high
dockerhub-user: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-password: ${{ secrets.DOCKERHUB_TOKEN }}
exit-code: false

- name: Push Docker image with commit SHA
run: |
docker push \
Expand Down
145 changes: 32 additions & 113 deletions .github/workflows/02-deploy-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:

jobs:
deploy-staging:
name: Deploy to Staging
name: Deploy frontend to App Service staging slot
runs-on: ubuntu-latest

if: >
Expand All @@ -20,132 +20,51 @@ jobs:
environment:
name: staging

env:
APP_SERVICE_NAME: koalatech-week08-webapp-226035073
RESOURCE_GROUP: koalatech-week08-rg
SLOT_NAME: staging
ACR_NAME: koalatech8acr226035073
ACR_LOGIN_SERVER: koalatech8acr226035073.azurecr.io
IMAGE_TAG: ${{ github.event.workflow_run.head_sha }}
IMAGE_NAME: koalatech-frontend

steps:
- name: Checkout tested commit
uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_sha }}

- name: Login to Azure
uses: azure/login@v3
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: Get AKS credentials
run: |
az aks get-credentials \
--resource-group ${{ vars.AKS_RESOURCE_GROUP }} \
--name ${{ vars.AKS_CLUSTER_NAME }} \
--overwrite-existing

- name: Create staging namespace
run: |
kubectl create namespace staging \
--dry-run=client \
-o yaml | kubectl apply -f -

- name: Create PostgreSQL secret
run: |
kubectl create secret generic postgres-secret \
--namespace staging \
--from-literal=POSTGRES_USER="${{ secrets.POSTGRES_USER }}" \
--from-literal=POSTGRES_PASSWORD="${{ secrets.POSTGRES_PASSWORD }}" \
--dry-run=client \
-o yaml | kubectl apply -f -

- name: Create application secret
- name: Configure App Service staging image
run: |
kubectl create secret generic application-secret \
--namespace staging \
--from-literal=POSTGRES_USER="${{ secrets.POSTGRES_USER }}" \
--from-literal=POSTGRES_PASSWORD="${{ secrets.POSTGRES_PASSWORD }}" \
--from-literal=JWT_SECRET_KEY="${{ secrets.JWT_SECRET_KEY }}" \
--from-literal=DEFAULT_ADMIN_USERNAME="${{ secrets.DEFAULT_ADMIN_USERNAME }}" \
--from-literal=DEFAULT_ADMIN_EMAIL="${{ secrets.DEFAULT_ADMIN_EMAIL }}" \
--from-literal=DEFAULT_ADMIN_PASSWORD="${{ secrets.DEFAULT_ADMIN_PASSWORD }}" \
--from-literal=AZURE_STORAGE_CONNECTION_STRING="${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}" \
--dry-run=client \
-o yaml | kubectl apply -f -
SUBSCRIPTION_ID=$(az account show --query id --output tsv)
IMAGE_REF="${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG}"
SITE_CONFIG_URL="https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.Web/sites/${APP_SERVICE_NAME}/slots/${SLOT_NAME}/config/web?api-version=2022-03-01"

- name: Apply Kubernetes manifests
run: |
kubectl apply \
-f kubernetes/staging/
echo "Deploying ${IMAGE_REF} to slot ${SLOT_NAME}"

- name: Update frontend image
run: |
kubectl set image deployment/frontend \
frontend=${{ vars.ACR_LOGIN_SERVER }}/koalatech-frontend:${{ github.event.workflow_run.head_sha }} \
-n staging

- name: Update user-service image
run: |
kubectl set image deployment/user-service \
user-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-user-service:${{ github.event.workflow_run.head_sha }} \
-n staging

- name: Update student-service image
run: |
kubectl set image deployment/student-service \
student-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-student-service:${{ github.event.workflow_run.head_sha }} \
-n staging

- name: Update lecturer-service image
run: |
kubectl set image deployment/lecturer-service \
lecturer-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-lecturer-service:${{ github.event.workflow_run.head_sha }} \
-n staging

- name: Update course-service image
run: |
kubectl set image deployment/course-service \
course-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-course-service:${{ github.event.workflow_run.head_sha }} \
-n staging

- name: Update enrollment-service image
run: |
kubectl set image deployment/enrollment-service \
enrollment-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-enrollment-service:${{ github.event.workflow_run.head_sha }} \
-n staging

- name: Wait for frontend rollout
run: |
kubectl rollout status deployment/frontend \
-n staging \
--timeout=300s

- name: Wait for user-service rollout
run: |
kubectl rollout status deployment/user-service \
-n staging \
--timeout=300s

- name: Wait for student-service rollout
run: |
kubectl rollout status deployment/student-service \
-n staging \
--timeout=300s

- name: Wait for lecturer-service rollout
run: |
kubectl rollout status deployment/lecturer-service \
-n staging \
--timeout=300s

- name: Wait for course-service rollout
run: |
kubectl rollout status deployment/course-service \
-n staging \
--timeout=300s
az rest \
--method put \
--url "$SITE_CONFIG_URL" \
--body "{\"properties\":{\"linuxFxVersion\":\"DOCKER|${IMAGE_REF}\"}}"

- name: Wait for enrollment-service rollout
- name: Restart staging slot
run: |
kubectl rollout status deployment/enrollment-service \
-n staging \
--timeout=300s
az webapp restart \
--name "$APP_SERVICE_NAME" \
--resource-group "$RESOURCE_GROUP" \
--slot "$SLOT_NAME"

- name: Show staging resources
- name: Show App Service staging status
run: |
kubectl get pods -n staging
kubectl get services -n staging
kubectl get pvc -n staging
az webapp show \
--name "$APP_SERVICE_NAME" \
--resource-group "$RESOURCE_GROUP" \
--slot "$SLOT_NAME" \
--query '{name:name, state:state, defaultHostName:defaultHostName, slot:slotSwapStatus}' \
-o table
64 changes: 38 additions & 26 deletions .github/workflows/03-staging-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,44 +20,56 @@ jobs:
environment:
name: staging

env:
APP_SERVICE_NAME: koalatech-week08-webapp-226035073
RESOURCE_GROUP: koalatech-week08-rg
SLOT_NAME: staging

steps:
- name: Login to Azure
uses: azure/login@v3
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: Get AKS credentials
- name: Get staging hostname
id: staging
run: |
az aks get-credentials \
--resource-group ${{ vars.AKS_RESOURCE_GROUP }} \
--name ${{ vars.AKS_CLUSTER_NAME }} \
--overwrite-existing
STAGING_HOSTNAME=$(az webapp show \
--name "$APP_SERVICE_NAME" \
--resource-group "$RESOURCE_GROUP" \
--slot "$SLOT_NAME" \
--query defaultHostName \
--output tsv)

- name: Wait for staging frontend IP
run: |
for i in {1..30}; do
if [ -z "$STAGING_HOSTNAME" ]; then
echo "Staging hostname not available."
exit 1
fi

FRONTEND_IP=$(kubectl get service frontend \
-n staging \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "STAGING_HOSTNAME=$STAGING_HOSTNAME" >> "$GITHUB_ENV"

- name: Wait for staging app to respond
run: |
echo "Checking https://${STAGING_HOSTNAME}"

if [ -n "$FRONTEND_IP" ]; then
echo "FRONTEND_IP=$FRONTEND_IP" >> "$GITHUB_ENV"
for i in {1..30}; do
if curl \
--fail \
--silent \
--show-error \
--location \
--connect-timeout 10 \
--max-time 30 \
"https://${STAGING_HOSTNAME}" \
> /tmp/staging-response.html 2>/dev/null; then
echo "Staging app responded successfully."
grep -Eiq "<html|<!doctype html|KoalaTech|Login" /tmp/staging-response.html || true
exit 0
fi

echo "Waiting for staging frontend IP..."

echo "Waiting for staging app to become ready... attempt ${i}/30"
sleep 10
done

echo "Unable to find staging frontend IP."
exit 1

- name: Test frontend
run: |
curl \
--fail \
--retry 10 \
--retry-delay 5 \
http://${{ env.FRONTEND_IP }}
echo "Staging app did not respond successfully within the timeout."
exit 1
Loading