Skip to content
Merged
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
205 changes: 150 additions & 55 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -1,55 +1,150 @@
name: CI

on:
push:
branches: [master, develop]
pull_request:
branches: [master, develop]

env:
NODE_VERSION: '18'

jobs:
server:
name: Server - Install, Test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: server/package-lock.json

- name: Install dependencies
working-directory: server
run: npm ci

- name: Run tests
working-directory: server
run: npm test

client:
name: Client - Install, Build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: client/package-lock.json

- name: Install dependencies
working-directory: client
run: npm ci

- name: Build
working-directory: client
run: npm run build
name: CI

on:
push:
branches: [master, develop]
pull_request:
branches: [master, develop]

env:
NODE_VERSION: '18'

jobs:
server:
name: Server - Install, Test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: server/package-lock.json

- name: Install dependencies
working-directory: server
run: npm ci

- name: Run tests
working-directory: server
run: npm test

client:
name: Client - Install, Build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: client/package-lock.json

- name: Install dependencies
working-directory: client
run: npm ci

- name: Build
working-directory: client
run: npm run build

acr-push:
name: Docker - Build & Push to ACR
runs-on: ubuntu-latest
needs: [server, client]
if: github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Azure Container Registry
uses: docker/login-action@v3
with:
registry: ${{ secrets.ACR_NAME }}.azurecr.io
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}

- name: Build and Push Server Image
uses: docker/build-push-action@v5
with:
context: server
file: server/Dockerfile
push: true
tags: |
${{ secrets.ACR_NAME }}.azurecr.io/server:${{ github.sha }}
${{ secrets.ACR_NAME }}.azurecr.io/server:latest

- name: Build and Push Client Image
uses: docker/build-push-action@v5
with:
context: client
file: client/Dockerfile
push: true
tags: |
${{ secrets.ACR_NAME }}.azurecr.io/client:${{ github.sha }}
${{ secrets.ACR_NAME }}.azurecr.io/client:latest

- name: Scan Server Image with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ secrets.ACR_NAME }}.azurecr.io/server:${{ github.sha }}
format: 'table'
exit-code: '0'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'

- name: Scan Client Image with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ secrets.ACR_NAME }}.azurecr.io/client:${{ github.sha }}
format: 'table'
exit-code: '0'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'

deploy-to-aks:
name: Kubernetes - Deploy to AKS
runs-on: ubuntu-latest
needs: [acr-push]
if: github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up kubectl
uses: azure/setup-kubectl@v4

- name: Set up Kubernetes Context
uses: azure/k8s-set-context@v4
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBE_CONFIG }}

- name: Create or Update Secrets
run: |
kubectl create secret generic server-secrets \
--from-literal=JWT_SECRET=${{ secrets.JWT_SECRET }} \
--dry-run=client -o yaml | kubectl apply -f -

- name: Deploy with Kustomize (Git SHA Tagging)
run: |
cd kubernetes
kustomize edit set image devopscommerce.azurecr.io/server=${{ secrets.ACR_NAME }}.azurecr.io/server:${{ github.sha }}
kustomize edit set image devopscommerce.azurecr.io/client=${{ secrets.ACR_NAME }}.azurecr.io/client:${{ github.sha }}
kubectl apply -k .

- name: Verify Rollout Status
run: |
kubectl rollout status deployment/server --timeout=180s
kubectl rollout status deployment/client --timeout=180s
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,17 @@ yarn-error.log*
DOC.md
ROADMAP.md
ecommerce_cicd_capstone_project.md

# terraform
**/.terraform/*
*.tfstate
*.tfstate.*
crash.log
crash.*.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json
.terraformrc
terraform.rc

22 changes: 22 additions & 0 deletions infra/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

80 changes: 80 additions & 0 deletions infra/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
terraform {
required_version = ">= 1.3.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.70"
}
}
}

provider "azurerm" {
features {}
}

# 1. Reference existing Azure Container Registry from Phase 4
data "azurerm_container_registry" "acr" {
name = var.acr_name
resource_group_name = var.resource_group_name
}

# 2. Virtual Network for AKS Cluster
resource "azurerm_virtual_network" "vnet" {
name = "vnet-ecommerce-aks"
location = var.location
resource_group_name = var.resource_group_name
address_space = ["10.0.0.0/16"]

tags = {
Environment = "DevOps-Capstone"
ManagedBy = "Terraform"
}
}

# 3. Subnet for AKS Node Pool
resource "azurerm_subnet" "aks_subnet" {
name = "subnet-aks"
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
}

# 4. Azure Kubernetes Service (AKS) Cluster
resource "azurerm_kubernetes_cluster" "aks" {
name = var.cluster_name
location = var.location
resource_group_name = var.resource_group_name
dns_prefix = var.dns_prefix

default_node_pool {
name = "default"
node_count = var.node_count
vm_size = var.vm_size
vnet_subnet_id = azurerm_subnet.aks_subnet.id
}

identity {
type = "SystemAssigned"
}

network_profile {
network_plugin = "azure"
load_balancer_sku = "standard"
service_cidr = var.service_cidr
dns_service_ip = var.dns_service_ip
}

tags = {
Environment = "DevOps-Capstone"
Project = "MERN-Ecommerce"
ManagedBy = "Terraform"
}
}

# 5. Role Assignment: Grant AKS permission to pull images from ACR (AcrPull)
resource "azurerm_role_assignment" "aks_acr_pull" {
principal_id = azurerm_kubernetes_cluster.aks.kubelet_identity[0].object_id
role_definition_name = "AcrPull"
scope = data.azurerm_container_registry.acr.id
skip_service_principal_aad_check = true
}
19 changes: 19 additions & 0 deletions infra/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
output "resource_group_name" {
description = "The Resource Group containing all resources"
value = var.resource_group_name
}

output "kubernetes_cluster_name" {
description = "The name of the AKS cluster"
value = azurerm_kubernetes_cluster.aks.name
}

output "acr_login_server" {
description = "The login server for the Azure Container Registry"
value = data.azurerm_container_registry.acr.login_server
}

output "connect_command" {
description = "Command to configure kubectl to connect to the new cluster"
value = "az aks get-credentials --resource-group ${var.resource_group_name} --name ${azurerm_kubernetes_cluster.aks.name}"
}
Loading
Loading