From ca4d17d3be8fdb1ca039525856226bcbc21fdaaa Mon Sep 17 00:00:00 2001 From: betty cheng Date: Wed, 16 Sep 2026 21:10:46 +1000 Subject: [PATCH 1/6] Automate production deployment with updated header text --- .github/workflows/04-deploy-production.yml | 18 ++++++++++++------ frontend/index.html | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/04-deploy-production.yml b/.github/workflows/04-deploy-production.yml index 969d646b..7e057382 100644 --- a/.github/workflows/04-deploy-production.yml +++ b/.github/workflows/04-deploy-production.yml @@ -1,24 +1,30 @@ name: 04 - Deploy to Production on: - workflow_dispatch: - inputs: - image_tag: - description: "Tested image SHA to deploy" - required: true - type: string + workflow_run: + workflows: + - "03 - Test Staging" + types: + - completed + branches: + - main jobs: deploy-production: name: Deploy to Production runs-on: ubuntu-latest + if: > + ${{ github.event.workflow_run.conclusion == 'success' }} + environment: name: production steps: - name: Checkout repository uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} - name: Login to Azure uses: azure/login@v3 diff --git a/frontend/index.html b/frontend/index.html index 3176fb0b..da1126ad 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - KoalaTech University + KoalaTech University - Live From 19ec4ed79a06f5fe11f448d808414f7551dcd2e4 Mon Sep 17 00:00:00 2001 From: betty cheng Date: Wed, 16 Sep 2026 21:33:24 +1000 Subject: [PATCH 2/6] fix the image ref from workflow --- .github/workflows/04-deploy-production.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/04-deploy-production.yml b/.github/workflows/04-deploy-production.yml index 7e057382..5d1ac3de 100644 --- a/.github/workflows/04-deploy-production.yml +++ b/.github/workflows/04-deploy-production.yml @@ -75,37 +75,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:${{ github.event.workflow_run.head_sha }} \ -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:${{ github.event.workflow_run.head_sha }} \ -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:${{ github.event.workflow_run.head_sha }} \ -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:${{ github.event.workflow_run.head_sha }} \ -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:${{ github.event.workflow_run.head_sha }} \ -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:${{ github.event.workflow_run.head_sha }} \ -n production - name: Wait for frontend rollout From a13d223548d50ded2b7c1699d5edfef63c1482e1 Mon Sep 17 00:00:00 2001 From: betty cheng Date: Thu, 24 Sep 2026 20:51:01 +1000 Subject: [PATCH 3/6] add terraform and .gitignore --- .gitignore | 8 +++- terraform/.terraform.lock.hcl | 43 +++++++++++++++++ terraform/container_registry.tf | 15 ++++++ terraform/kubernetes_serivce.tf | 34 ++++++++++++++ terraform/output.tf | 57 ++++++++++++++++++++++ terraform/resource_group.tf | 11 +++++ terraform/storage_account.tf | 30 ++++++++++++ terraform/terraform.tfvars | 24 ++++++++++ terraform/variables.tf | 83 +++++++++++++++++++++++++++++++++ terraform/versions.tf | 18 +++++++ 10 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 terraform/.terraform.lock.hcl create mode 100644 terraform/container_registry.tf create mode 100644 terraform/kubernetes_serivce.tf create mode 100644 terraform/output.tf create mode 100644 terraform/resource_group.tf create mode 100644 terraform/storage_account.tf create mode 100644 terraform/terraform.tfvars create mode 100644 terraform/variables.tf create mode 100644 terraform/versions.tf diff --git a/.gitignore b/.gitignore index 4299cd1a..d51bfe2b 100644 --- a/.gitignore +++ b/.gitignore @@ -196,4 +196,10 @@ cython_debug/ .cursorignore .cursorindexingignore -node_modules/ \ No newline at end of file +node_modules/ + +# terraform +terraform/.terraform/ +terraform/*.tfstate +terraform/*.tfstate.* +terraform/tfplan \ No newline at end of file diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 00000000..ec22119d --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,43 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "4.81.0" + constraints = "~> 4.0" + hashes = [ + "h1:XhToZua4gtih1Kv8RdStcfND83G4Tmb6GZFT4jEUhDU=", + "zh:0732e7b74264ddfa2b90ba69d01c283d3cbae9f72ed3e506c6ac92529fed7fd3", + "zh:12afb524e232fe4e3d6161927724af5dfa4831d71edd9c174917ca9b7377bfae", + "zh:169d619ae202c4145e02fb706fb7c3679445ab3e3ff722edbf89597517a8c92e", + "zh:6beb95a3ef2f2d9c76abaa48e5450e90686a3fb6a47f1cb0ff7c5e94b6960151", + "zh:705e075fb5ffc4bf66fd7cbabf1a65007a41621e80030a2c158a4c83b6046216", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:79a8d17fefe647040fcb9ee8821a4f09f395427c4fd49493489b9a93a9a1038e", + "zh:8cc3f900b3774c0ae37ae42365c4579a199cf9e5edf88e476fdf5ab1048f84ea", + "zh:dec373b9390fa95e257291acd018ed65a7d512b428645d35e22cdbe8b245a08b", + "zh:e60f1e9fb45df6defade2855ed6e68547409ea75d30655c556adb0c08579749b", + "zh:f901d12ec82f3f8b5880a27b5cbcd7bd0d97e60c9367a2d7ed82fdd1157b39ff", + "zh:facf68ea5bf0f2b8ba720e7fba5f86492e1d4c591100460bb91c3f79f391f4b6", + ] +} + +provider "registry.terraform.io/hashicorp/local" { + version = "2.9.1" + constraints = "~> 2.0" + hashes = [ + "h1:OZGJN0LSSat5QIxZPxDXtVr4XpHb2oG7cVKJhSqBFIE=", + "zh:25606c7a5e308144fb627f6e31611bb52ff72bb9ae2d27af39673ab1a6b3c1bf", + "zh:2568c4ef4dab31821f6f7040af0d1a2aa2b9455b8d9cc546598b791b8ada34cd", + "zh:25ac210f136042047975896e5e11fe6012425301c826d6b7ce22a49f96a1e0e8", + "zh:55d3a7bf01eced8e1f259b548020ef1221c5687e0fe6b5b30f81ad0b4121ff12", + "zh:6168d6934777b853c57815baa18a54101c604a6f1124ca47cd23d016c6f2f1e5", + "zh:63c0aa17c8373f761123376226debffd1f2259901ff259d4547c520def9a6787", + "zh:6d1f1572db6c85bd6d6b1b1deef247084e189816875f67e6bcc5d4736794e0a9", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a8ea1c155bd5e0d695a28d3c0e2f542de8844e1791ea021f015a3f08e1c059e1", + "zh:ccd790f97269509ade0574ef63c955d3943cb1d812c59177bd85ce4c1d17ca0f", + "zh:dcfc17bb666e659a9daa9ff52fd8f6ec9ea519868726015e645955c372296332", + "zh:eb83abbfa7f52cba609b48cbb9a6a5cab07780a672b2020fc2ba190e699a2516", + "zh:eedffca70074ae153790fb23c4ace8d1637a60ceba4e3c42c8eaeda7c1f9d10f", + ] +} diff --git a/terraform/container_registry.tf b/terraform/container_registry.tf new file mode 100644 index 00000000..dc32f38a --- /dev/null +++ b/terraform/container_registry.tf @@ -0,0 +1,15 @@ +resource "azurerm_container_registry" "acr" { + name = var.acr_name + resource_group_name = azurerm_resource_group.rg.name + location = azurerm_resource_group.rg.location + + sku = "Basic" + admin_enabled = true + + tags = merge( + var.tags, + { + Environment = var.environment + } + ) +} \ No newline at end of file diff --git a/terraform/kubernetes_serivce.tf b/terraform/kubernetes_serivce.tf new file mode 100644 index 00000000..bee30484 --- /dev/null +++ b/terraform/kubernetes_serivce.tf @@ -0,0 +1,34 @@ +resource "azurerm_kubernetes_cluster" "aks" { + name = var.aks_cluster_name + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + dns_prefix = var.aks_dns_prefix + kubernetes_version = var.kubernetes_version + + default_node_pool { + name = "default" + node_count = var.aks_node_count + vm_size = var.aks_node_vm_size + } + + identity { + type = "SystemAssigned" + } + + tags = merge( + var.tags, + { + Environment = var.environment + } + ) +} + +# +# Grant AKS permission to pull images from your ACR +# +resource "azurerm_role_assignment" "acr_pull" { + principal_id = azurerm_kubernetes_cluster.aks.kubelet_identity[0].object_id + role_definition_name = "AcrPull" + scope = azurerm_container_registry.acr.id + skip_service_principal_aad_check = true +} \ No newline at end of file diff --git a/terraform/output.tf b/terraform/output.tf new file mode 100644 index 00000000..36e88828 --- /dev/null +++ b/terraform/output.tf @@ -0,0 +1,57 @@ +output "resource_group_name" { + description = "Name of the resource group" + value = azurerm_resource_group.rg.name +} + +output "acr_name" { + description = "Name of the Azure Container Registry" + value = azurerm_container_registry.acr.name +} + +output "acr_login_server" { + description = "Login server of the Azure Container Registry" + value = azurerm_container_registry.acr.login_server +} + +output "storage_account_name" { + description = "Name of the Azure Storage Account" + value = azurerm_storage_account.storage_account.name +} + +output "storage_connection_string" { + description = "Connection string used by the application to access Blob Storage" + value = azurerm_storage_account.storage_account.primary_connection_string + sensitive = true +} + +output "student_profile_container" { + description = "Student profile photo Blob container" + value = azurerm_storage_container.student_profile_photo.name +} + +output "lecturer_profile_container" { + description = "Lecturer profile photo Blob container" + value = azurerm_storage_container.lecturer_profile_photo.name +} + +output "aks_cluster_name" { + description = "Name of the AKS cluster" + value = azurerm_kubernetes_cluster.aks.name +} + +output "aks_get_credentials_command" { + description = "Azure CLI command used to configure kubectl" + value = join(" ", [ + "az aks get-credentials", + "--resource-group", + azurerm_resource_group.rg.name, + "--name", + azurerm_kubernetes_cluster.aks.name, + "--overwrite-existing" + ]) +} + +output "acr_login_command" { + description = "Azure CLI command used to log in to ACR" + value = "az acr login --name ${azurerm_container_registry.acr.name}" +} \ No newline at end of file diff --git a/terraform/resource_group.tf b/terraform/resource_group.tf new file mode 100644 index 00000000..ff3255ec --- /dev/null +++ b/terraform/resource_group.tf @@ -0,0 +1,11 @@ +resource "azurerm_resource_group" "rg" { + name = var.resource_group_name + location = var.location + + tags = merge( + var.tags, + { + Environment = var.environment + } + ) +} \ No newline at end of file diff --git a/terraform/storage_account.tf b/terraform/storage_account.tf new file mode 100644 index 00000000..ce33235f --- /dev/null +++ b/terraform/storage_account.tf @@ -0,0 +1,30 @@ +resource "azurerm_storage_account" "storage_account" { + name = var.storage_account_name + resource_group_name = azurerm_resource_group.rg.name + location = azurerm_resource_group.rg.location + + account_tier = "Standard" + account_replication_type = "LRS" + + min_tls_version = "TLS1_2" + allow_nested_items_to_be_public = false + + tags = merge( + var.tags, + { + Environment = var.environment + } + ) +} + +resource "azurerm_storage_container" "student_profile_photo" { + name = "student-profile-photo" + storage_account_id = azurerm_storage_account.storage_account.id + container_access_type = "private" +} + +resource "azurerm_storage_container" "lecturer_profile_photo" { + name = "lecturer-profile-photo" + storage_account_id = azurerm_storage_account.storage_account.id + container_access_type = "private" +} \ No newline at end of file diff --git a/terraform/terraform.tfvars b/terraform/terraform.tfvars new file mode 100644 index 00000000..d2d141ff --- /dev/null +++ b/terraform/terraform.tfvars @@ -0,0 +1,24 @@ +location = "Australia East" +resource_group_name = "225294805-koalatech-week09-rg" + +# Replace with a unique name for your Azure Container Registry +acr_name = "225294805acrweek09" + +# Replace with a unique name for your Azure Storage Account +storage_account_name = "225294805week09" + +# Replace with a unique name for your Azure Kubernetes Service cluster +aks_cluster_name = "225294805week09" +aks_dns_prefix = "koalatech" + +aks_node_count = 3 +aks_node_vm_size = "Standard_D2s_v3" + +environment = "development" + +tags = { + Project = "KoalaTech Course Platform" + ManagedBy = "Terraform" + Practical = "Week10" + Environment = "Development" +} \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 00000000..c6e53382 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,83 @@ +variable "location" { + description = "Azure region where the resources will be created" + type = string + default = "Australia East" +} + +variable "resource_group_name" { + description = "Name of the Azure Resource Group" + type = string +} + +variable "acr_name" { + description = "Globally unique name of the Azure Container Registry" + type = string + + validation { + condition = can(regex("^[a-zA-Z0-9]+$", var.acr_name)) + error_message = "The ACR name must contain only alphanumeric characters." + } +} + +variable "storage_account_name" { + description = "Globally unique name of the Azure Storage Account" + type = string + + validation { + condition = ( + length(var.storage_account_name) >= 3 && + length(var.storage_account_name) <= 24 && + can(regex("^[a-z0-9]+$", var.storage_account_name)) + ) + + error_message = "The storage account name must contain 3–24 lowercase letters and numbers." + } +} + +variable "aks_cluster_name" { + description = "Name of the Azure Kubernetes Service cluster" + type = string +} + +variable "aks_dns_prefix" { + description = "DNS prefix used by the AKS cluster" + type = string +} + +variable "aks_node_count" { + description = "Number of nodes in the default AKS node pool" + type = number + default = 2 + + validation { + condition = var.aks_node_count >= 1 + error_message = "The AKS node count must be at least 1." + } +} + +variable "aks_node_vm_size" { + description = "Virtual machine size used by the AKS nodes" + type = string + default = "Standard_D2s_v3" +} + +variable "environment" { + description = "Environment name applied to resource tags" + type = string + default = "development" +} + +variable "kubernetes_version" { + default = "1.36.1" +} + +variable "tags" { + description = "Tags applied to Azure resources" + type = map(string) + + default = { + Project = "KoalaTech Course Platform" + ManagedBy = "Terraform" + Practical = "Week06" + } +} \ No newline at end of file diff --git a/terraform/versions.tf b/terraform/versions.tf new file mode 100644 index 00000000..6f0bbb0e --- /dev/null +++ b/terraform/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.7.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 4.0" + } + local = { + source = "hashicorp/local" + version = "~> 2.0" + } + } +} + +provider "azurerm" { + features {} +} \ No newline at end of file From bbe01cc2c9bec27e459a19ef70b9823413e68826 Mon Sep 17 00:00:00 2001 From: betty cheng Date: Thu, 24 Sep 2026 20:51:17 +1000 Subject: [PATCH 4/6] add terraform into workflow --- .github/workflows/00-terraform.yml | 76 ++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 .github/workflows/00-terraform.yml diff --git a/.github/workflows/00-terraform.yml b/.github/workflows/00-terraform.yml new file mode 100644 index 00000000..d04dc9ba --- /dev/null +++ b/.github/workflows/00-terraform.yml @@ -0,0 +1,76 @@ +name: 00 - Terraform + +on: + pull_request: + paths: + - "terraform/**" + push: + branches: + - main + paths: + - "terraform/**" + workflow_dispatch: + +permissions: + contents: read + +env: + TF_IN_AUTOMATION: "true" + TF_WORKING_DIR: terraform + +jobs: + terraform: + name: Terraform validate, plan and apply + runs-on: ubuntu-latest + + defaults: + run: + working-directory: terraform + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Login to Azure + uses: azure/login@v3 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + # The azurerm provider reads ARM_* variables; azure/login does not + # export them, so pull them out of the same service principal JSON. + - name: Export ARM credentials for Terraform + env: + CREDS: ${{ secrets.AZURE_CREDENTIALS }} + run: | + for key in clientId clientSecret tenantId subscriptionId; do + echo "::add-mask::$(echo "$CREDS" | jq -r .$key)" + done + echo "ARM_CLIENT_ID=$(echo "$CREDS" | jq -r .clientId)" >> "$GITHUB_ENV" + echo "ARM_CLIENT_SECRET=$(echo "$CREDS" | jq -r .clientSecret)" >> "$GITHUB_ENV" + echo "ARM_TENANT_ID=$(echo "$CREDS" | jq -r .tenantId)" >> "$GITHUB_ENV" + echo "ARM_SUBSCRIPTION_ID=$(echo "$CREDS" | jq -r .subscriptionId)" >> "$GITHUB_ENV" + + - name: Set up Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: "~1.9" + + - name: Terraform format check + run: terraform fmt -check -diff + + - name: Terraform init + run: | + terraform init \ + -backend-config="resource_group_name=${{ vars.TFSTATE_RESOURCE_GROUP }}" \ + -backend-config="storage_account_name=${{ vars.TFSTATE_STORAGE_ACCOUNT }}" + + - name: Terraform validate + run: terraform validate + + - name: Terraform plan + run: terraform plan -input=false -out=tfplan + + # Only changes merged to main are applied. Pull requests stop at plan. + - name: Terraform apply + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + run: terraform apply -input=false -auto-approve tfplan From 23aa0d9bb21563ce17d479686b0e52c3596ffd0f Mon Sep 17 00:00:00 2001 From: betty cheng Date: Thu, 24 Sep 2026 20:55:19 +1000 Subject: [PATCH 5/6] Format terraform files --- terraform/container_registry.tf | 22 ++++---- terraform/kubernetes_serivce.tf | 46 ++++++++--------- terraform/resource_group.tf | 16 +++--- terraform/storage_account.tf | 38 +++++++------- terraform/terraform.tfvars | 10 ++-- terraform/variables.tf | 92 ++++++++++++++++----------------- terraform/versions.tf | 22 ++++---- 7 files changed, 123 insertions(+), 123 deletions(-) diff --git a/terraform/container_registry.tf b/terraform/container_registry.tf index dc32f38a..1a5f350f 100644 --- a/terraform/container_registry.tf +++ b/terraform/container_registry.tf @@ -1,15 +1,15 @@ resource "azurerm_container_registry" "acr" { - name = var.acr_name - resource_group_name = azurerm_resource_group.rg.name - location = azurerm_resource_group.rg.location + name = var.acr_name + resource_group_name = azurerm_resource_group.rg.name + location = azurerm_resource_group.rg.location - sku = "Basic" - admin_enabled = true + sku = "Basic" + admin_enabled = true - tags = merge( - var.tags, - { - Environment = var.environment - } - ) + tags = merge( + var.tags, + { + Environment = var.environment + } + ) } \ No newline at end of file diff --git a/terraform/kubernetes_serivce.tf b/terraform/kubernetes_serivce.tf index bee30484..ebfd7692 100644 --- a/terraform/kubernetes_serivce.tf +++ b/terraform/kubernetes_serivce.tf @@ -1,34 +1,34 @@ resource "azurerm_kubernetes_cluster" "aks" { - name = var.aks_cluster_name - location = azurerm_resource_group.rg.location - resource_group_name = azurerm_resource_group.rg.name - dns_prefix = var.aks_dns_prefix - kubernetes_version = var.kubernetes_version + name = var.aks_cluster_name + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + dns_prefix = var.aks_dns_prefix + kubernetes_version = var.kubernetes_version - default_node_pool { - name = "default" - node_count = var.aks_node_count - vm_size = var.aks_node_vm_size - } + default_node_pool { + name = "default" + node_count = var.aks_node_count + vm_size = var.aks_node_vm_size + } - identity { - type = "SystemAssigned" - } + identity { + type = "SystemAssigned" + } - tags = merge( - var.tags, - { - Environment = var.environment - } - ) + tags = merge( + var.tags, + { + Environment = var.environment + } + ) } # # Grant AKS permission to pull images from your ACR # resource "azurerm_role_assignment" "acr_pull" { - principal_id = azurerm_kubernetes_cluster.aks.kubelet_identity[0].object_id - role_definition_name = "AcrPull" - scope = azurerm_container_registry.acr.id - skip_service_principal_aad_check = true + principal_id = azurerm_kubernetes_cluster.aks.kubelet_identity[0].object_id + role_definition_name = "AcrPull" + scope = azurerm_container_registry.acr.id + skip_service_principal_aad_check = true } \ No newline at end of file diff --git a/terraform/resource_group.tf b/terraform/resource_group.tf index ff3255ec..0fbe75d3 100644 --- a/terraform/resource_group.tf +++ b/terraform/resource_group.tf @@ -1,11 +1,11 @@ resource "azurerm_resource_group" "rg" { - name = var.resource_group_name - location = var.location + name = var.resource_group_name + location = var.location - tags = merge( - var.tags, - { - Environment = var.environment - } - ) + tags = merge( + var.tags, + { + Environment = var.environment + } + ) } \ No newline at end of file diff --git a/terraform/storage_account.tf b/terraform/storage_account.tf index ce33235f..8b3a6b6f 100644 --- a/terraform/storage_account.tf +++ b/terraform/storage_account.tf @@ -1,30 +1,30 @@ resource "azurerm_storage_account" "storage_account" { - name = var.storage_account_name - resource_group_name = azurerm_resource_group.rg.name - location = azurerm_resource_group.rg.location + name = var.storage_account_name + resource_group_name = azurerm_resource_group.rg.name + location = azurerm_resource_group.rg.location - account_tier = "Standard" - account_replication_type = "LRS" + account_tier = "Standard" + account_replication_type = "LRS" - min_tls_version = "TLS1_2" - allow_nested_items_to_be_public = false + min_tls_version = "TLS1_2" + allow_nested_items_to_be_public = false - tags = merge( - var.tags, - { - Environment = var.environment - } - ) + tags = merge( + var.tags, + { + Environment = var.environment + } + ) } resource "azurerm_storage_container" "student_profile_photo" { - name = "student-profile-photo" - storage_account_id = azurerm_storage_account.storage_account.id - container_access_type = "private" + name = "student-profile-photo" + storage_account_id = azurerm_storage_account.storage_account.id + container_access_type = "private" } resource "azurerm_storage_container" "lecturer_profile_photo" { - name = "lecturer-profile-photo" - storage_account_id = azurerm_storage_account.storage_account.id - container_access_type = "private" + name = "lecturer-profile-photo" + storage_account_id = azurerm_storage_account.storage_account.id + container_access_type = "private" } \ No newline at end of file diff --git a/terraform/terraform.tfvars b/terraform/terraform.tfvars index d2d141ff..a2724352 100644 --- a/terraform/terraform.tfvars +++ b/terraform/terraform.tfvars @@ -2,7 +2,7 @@ location = "Australia East" resource_group_name = "225294805-koalatech-week09-rg" # Replace with a unique name for your Azure Container Registry -acr_name = "225294805acrweek09" +acr_name = "225294805acrweek09" # Replace with a unique name for your Azure Storage Account storage_account_name = "225294805week09" @@ -17,8 +17,8 @@ aks_node_vm_size = "Standard_D2s_v3" environment = "development" tags = { - Project = "KoalaTech Course Platform" - ManagedBy = "Terraform" - Practical = "Week10" - Environment = "Development" + Project = "KoalaTech Course Platform" + ManagedBy = "Terraform" + Practical = "Week10" + Environment = "Development" } \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf index c6e53382..d9c95973 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -1,83 +1,83 @@ variable "location" { - description = "Azure region where the resources will be created" - type = string - default = "Australia East" + description = "Azure region where the resources will be created" + type = string + default = "Australia East" } variable "resource_group_name" { - description = "Name of the Azure Resource Group" - type = string + description = "Name of the Azure Resource Group" + type = string } variable "acr_name" { - description = "Globally unique name of the Azure Container Registry" - type = string + description = "Globally unique name of the Azure Container Registry" + type = string - validation { - condition = can(regex("^[a-zA-Z0-9]+$", var.acr_name)) - error_message = "The ACR name must contain only alphanumeric characters." - } + validation { + condition = can(regex("^[a-zA-Z0-9]+$", var.acr_name)) + error_message = "The ACR name must contain only alphanumeric characters." + } } variable "storage_account_name" { - description = "Globally unique name of the Azure Storage Account" - type = string + description = "Globally unique name of the Azure Storage Account" + type = string - validation { - condition = ( - length(var.storage_account_name) >= 3 && - length(var.storage_account_name) <= 24 && - can(regex("^[a-z0-9]+$", var.storage_account_name)) - ) + validation { + condition = ( + length(var.storage_account_name) >= 3 && + length(var.storage_account_name) <= 24 && + can(regex("^[a-z0-9]+$", var.storage_account_name)) + ) - error_message = "The storage account name must contain 3–24 lowercase letters and numbers." - } + error_message = "The storage account name must contain 3–24 lowercase letters and numbers." + } } variable "aks_cluster_name" { - description = "Name of the Azure Kubernetes Service cluster" - type = string + description = "Name of the Azure Kubernetes Service cluster" + type = string } variable "aks_dns_prefix" { - description = "DNS prefix used by the AKS cluster" - type = string + description = "DNS prefix used by the AKS cluster" + type = string } variable "aks_node_count" { - description = "Number of nodes in the default AKS node pool" - type = number - default = 2 + description = "Number of nodes in the default AKS node pool" + type = number + default = 2 - validation { - condition = var.aks_node_count >= 1 - error_message = "The AKS node count must be at least 1." - } + validation { + condition = var.aks_node_count >= 1 + error_message = "The AKS node count must be at least 1." + } } variable "aks_node_vm_size" { - description = "Virtual machine size used by the AKS nodes" - type = string - default = "Standard_D2s_v3" + description = "Virtual machine size used by the AKS nodes" + type = string + default = "Standard_D2s_v3" } variable "environment" { - description = "Environment name applied to resource tags" - type = string - default = "development" + description = "Environment name applied to resource tags" + type = string + default = "development" } variable "kubernetes_version" { - default = "1.36.1" + default = "1.36.1" } variable "tags" { - description = "Tags applied to Azure resources" - type = map(string) + description = "Tags applied to Azure resources" + type = map(string) - default = { - Project = "KoalaTech Course Platform" - ManagedBy = "Terraform" - Practical = "Week06" - } + default = { + Project = "KoalaTech Course Platform" + ManagedBy = "Terraform" + Practical = "Week06" + } } \ No newline at end of file diff --git a/terraform/versions.tf b/terraform/versions.tf index 6f0bbb0e..c1a6ec16 100644 --- a/terraform/versions.tf +++ b/terraform/versions.tf @@ -1,18 +1,18 @@ terraform { - required_version = ">= 1.7.0" + required_version = ">= 1.7.0" - required_providers { - azurerm = { - source = "hashicorp/azurerm" - version = "~> 4.0" - } - local = { - source = "hashicorp/local" - version = "~> 2.0" - } + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 4.0" } + local = { + source = "hashicorp/local" + version = "~> 2.0" + } + } } provider "azurerm" { - features {} + features {} } \ No newline at end of file From ce44d619019026baf83f98a6e8ef0d3761ebcf95 Mon Sep 17 00:00:00 2001 From: betty cheng Date: Thu, 24 Sep 2026 22:13:05 +1000 Subject: [PATCH 6/6] add docker scout to scan the images --- .github/workflows/01-ci.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.github/workflows/01-ci.yml b/.github/workflows/01-ci.yml index 8206db43..52a9faf9 100644 --- a/.github/workflows/01-ci.yml +++ b/.github/workflows/01-ci.yml @@ -160,6 +160,36 @@ jobs: -t ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} \ ./${{ matrix.service }} + - name: Build Docker image + run: | + docker build \ + --platform linux/amd64 \ + -t ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} \ + ./${{ matrix.service }} + + - name: Login to Docker Hub (for Docker Scout) + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_PAT }} + + - name: Docker Scout scan + uses: docker/scout-action@v1 + with: + command: quickview,cves,recommendations + image: local://${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} + only-severities: critical,high + sarif-file: scout-${{ matrix.service }}.sarif + # Report-only until the first findings are fixed, then flip to true + exit-code: false + + - name: Upload Scout report + uses: actions/upload-artifact@v4 + if: always() + with: + name: scout-${{ matrix.service }} + path: scout-${{ matrix.service }}.sarif + - name: Push Docker image with commit SHA run: | docker push \