Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
88 changes: 88 additions & 0 deletions .github/workflows/00-terraform.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: 00 - Terraform Infrastructure

on:
workflow_dispatch:
inputs:
action:
description: "Terraform action to execute"
required: true
default: "plan"
type: choice
options:
- plan
- apply
- destroy

pull_request:
paths:
- "terraform/**"

push:
branches:
- main
paths:
- "terraform/**"

jobs:
terraform:
name: Terraform ${{ github.event.inputs.action || 'Validate & Plan' }}
runs-on: ubuntu-latest

defaults:
run:
working-directory: ./terraform

env:
ARM_CLIENT_ID: ${{ fromJSON(secrets.AZURE_CREDENTIALS).clientId }}
ARM_CLIENT_SECRET: ${{ fromJSON(secrets.AZURE_CREDENTIALS).clientSecret }}
ARM_SUBSCRIPTION_ID: ${{ fromJSON(secrets.AZURE_CREDENTIALS).subscriptionId }}
ARM_TENANT_ID: ${{ fromJSON(secrets.AZURE_CREDENTIALS).tenantId }}

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

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.7.5

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

- name: Ensure Remote State Backend Exists
run: |
echo "Ensuring remote state backend resource group and storage account exist..."
az group create --name tfstate-rg --location "Australia East" || true
az storage account create \
--name tfstate20245 \
--resource-group tfstate-rg \
--sku Standard_LRS \
--encryption-services blob || true
az storage container create \
--name tfstate \
--account-name tfstate20245 \
--auth-mode login || true

- name: Terraform Format Check
run: terraform fmt -check

- name: Terraform Init
run: terraform init

- name: Terraform Validate
run: terraform validate

- name: Terraform Plan
id: plan
run: terraform plan -no-color -out=tfplan

- name: Terraform Apply
if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'apply'
run: terraform apply -auto-approve tfplan

- name: Terraform Destroy
if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'destroy'
run: terraform destroy -auto-approve
39 changes: 36 additions & 3 deletions .github/workflows/01-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ on:
branches:
- main

# Trigger the workflow on pull requests targeting main branch
pull_request:
branches:
- main

# Manual trigger for the workflow
workflow_dispatch:

Expand Down Expand Up @@ -83,10 +88,10 @@ jobs:
steps:

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

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v7
with:
python-version: "3.12"

Expand Down Expand Up @@ -142,7 +147,7 @@ jobs:
steps:

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

- name: Login to Azure
uses: azure/login@v2
Expand All @@ -160,6 +165,34 @@ jobs:
-t ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} \
./${{ matrix.service }}

# =========================================================
# Docker Scout Security Scanning (DevSecOps)
# =========================================================
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Docker Scout Quickview
uses: docker/scout-action@v1
with:
command: quickview
image: ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }}

- name: Docker Scout CVE Analysis
uses: docker/scout-action@v1
with:
command: cves
image: ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }}
only-severities: critical,high

- name: Docker Scout Recommendations
uses: docker/scout-action@v1
with:
command: recommendations
image: ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }}

- name: Push Docker image with commit SHA
run: |
docker push \
Expand Down
56 changes: 47 additions & 9 deletions .github/workflows/04-deploy-production.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,51 @@
name: 04 - Deploy to Production

on:
# Automated trigger: runs automatically when Staging tests pass
workflow_run:
workflows:
- "03 - Test Staging"
types:
- completed
branches:
- main

# Manual trigger fallback
workflow_dispatch:
inputs:
image_tag:
description: "Tested image SHA to deploy"
required: true
required: false
type: string

jobs:
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest

# Only run if manual trigger OR if staging tests succeeded
if: >
${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}

environment:
name: production

env:
IMAGE_TAG: ${{ inputs.image_tag || github.event.workflow_run.head_sha || github.sha }}

steps:
- name: Checkout repository
- name: Checkout tested commit
uses: actions/checkout@v4
with:
ref: ${{ env.IMAGE_TAG }}

- name: Display deployment metadata
run: |
echo "=========================================="
echo "Starting Continuous Deployment to Production"
echo "Trigger Event: ${{ github.event_name }}"
echo "Deploying Image Tag / Commit SHA: ${{ env.IMAGE_TAG }}"
echo "=========================================="

- name: Login to Azure
uses: azure/login@v3
Expand Down Expand Up @@ -69,37 +96,37 @@ jobs:
- name: Update frontend image
run: |
kubectl set image deployment/frontend \
frontend=${{ vars.ACR_LOGIN_SERVER }}/koalatech-frontend:${{ inputs.image_tag }} \
frontend=${{ vars.ACR_LOGIN_SERVER }}/koalatech-frontend:${{ env.IMAGE_TAG }} \
-n production

- name: Update user-service image
run: |
kubectl set image deployment/user-service \
user-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-user-service:${{ inputs.image_tag }} \
user-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-user-service:${{ env.IMAGE_TAG }} \
-n production

- name: Update student-service image
run: |
kubectl set image deployment/student-service \
student-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-student-service:${{ inputs.image_tag }} \
student-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-student-service:${{ env.IMAGE_TAG }} \
-n production

- name: Update lecturer-service image
run: |
kubectl set image deployment/lecturer-service \
lecturer-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-lecturer-service:${{ inputs.image_tag }} \
lecturer-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-lecturer-service:${{ env.IMAGE_TAG }} \
-n production

- name: Update course-service image
run: |
kubectl set image deployment/course-service \
course-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-course-service:${{ inputs.image_tag }} \
course-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-course-service:${{ env.IMAGE_TAG }} \
-n production

- name: Update enrollment-service image
run: |
kubectl set image deployment/enrollment-service \
enrollment-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-enrollment-service:${{ inputs.image_tag }} \
enrollment-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-enrollment-service:${{ env.IMAGE_TAG }} \
-n production

- name: Wait for frontend rollout
Expand Down Expand Up @@ -142,4 +169,15 @@ jobs:
run: |
kubectl get pods -n production
kubectl get services -n production
kubectl get pvc -n production
kubectl get pvc -n production

- name: Show production frontend URL
run: |
echo "=========================================="
echo "Production Application Deployment Complete"
echo "=========================================="
kubectl get service frontend -n production
PROD_IP=$(kubectl get service frontend -n production -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "Production Application External IP: $PROD_IP"
echo "Production Application URL: http://${PROD_IP}"
echo "Deployed Image Tag: ${{ env.IMAGE_TAG }}"
79 changes: 79 additions & 0 deletions .github/workflows/05-monitoring.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: 05 - Deploy Monitoring (Prometheus & Grafana)

on:
workflow_run:
workflows:
- "04 - Deploy to Production"
types:
- completed
branches:
- main

workflow_dispatch:

jobs:
deploy-monitoring:
name: Deploy & Validate Prometheus and Grafana on AKS
runs-on: ubuntu-latest

if: >
github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Login to Azure
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: Set up Helm
uses: azure/setup-helm@v4

- name: Deploy kube-prometheus-stack via Helm
run: |
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm upgrade --install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--values kubernetes/monitoring/values.yaml \
--wait \
--timeout 10m
- name: Validate Monitoring Pods and Services
run: |
echo "=========================================="
echo "Monitoring Namespace Pods:"
kubectl get pods -n monitoring
echo "=========================================="
echo "Monitoring Namespace Services:"
kubectl get svc -n monitoring
echo "=========================================="
- name: Output Grafana Dashboard Access URL
run: |
echo "Waiting for Grafana LoadBalancer External IP..."
for i in {1..30}; do
GRAFANA_IP=$(kubectl get svc kube-prometheus-stack-grafana -n monitoring -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
if [ -n "$GRAFANA_IP" ]; then
echo "===================================================="
echo "Grafana Dashboard is accessible at:"
echo "URL: http://${GRAFANA_IP}"
echo "Username: admin"
echo "Password: AdminPassword123!"
echo "===================================================="
exit 0
fi
echo "Waiting for external IP assignment ($i/30)..."
sleep 10
done
echo "Note: If external IP is still pending, use 'kubectl port-forward svc/kube-prometheus-stack-grafana 3000:80 -n monitoring'"
15 changes: 14 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,17 @@ cython_debug/
.cursorignore
.cursorindexingignore

node_modules/
node_modules/

# Terraform
.terraform/
*.tfstate
*.tfstate.*
*.tfvars.json
crash.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json
.terraformrc
terraform.rc
3 changes: 2 additions & 1 deletion frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ ENV VITE_ENROLLMENT_SERVICE_URL=${VITE_ENROLLMENT_SERVICE_URL}

RUN npm run build

FROM nginx:1.27-alpine
#FROM nginx:1.27-alpine
FROM nginx:1-alpine-slim

COPY nginx.conf /etc/nginx/conf.d/default.conf

Expand Down
Loading