diff --git a/.github/workflows/CD_linked.yml b/.github/workflows/CD_linked.yml new file mode 100644 index 00000000..06af9fc2 --- /dev/null +++ b/.github/workflows/CD_linked.yml @@ -0,0 +1,117 @@ +## .github/workflows/cd_linked.yml +name: CD - Deploy Backend then Frontend to AKS + +on: + push: + branches: [ main ] + paths: + - 'k8s/**' + - 'backend/**' + - 'frontend/**' + - '.github/workflows/CD_linked.yml' + +permissions: + id-token: write + contents: read + +env: + RG: SIT722-RG + AKS: sit722-aks + NS: sit722 + PRODUCT_PORT: "8000" + ORDER_PORT: "8001" + +jobs: + backend_cd: + name: Deploy Backend (product + order) + runs-on: ubuntu-latest + outputs: + product_ip: ${{ steps.capture.outputs.product_ip }} + order_ip: ${{ steps.capture.outputs.order_ip }} + steps: + - uses: actions/checkout@v4 + + - name: Azure Login + uses: azure/login@v2 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Set AKS context + uses: azure/aks-set-context@v4 + with: + resource-group: ${{ env.RG }} # = SIT722-RG + cluster-name: ${{ env.AKS }} # = sit722-aks + + - name: Deploy product + order services + run: | + kubectl apply -n $NS -f k8s/product-service.yaml + kubectl apply -n $NS -f k8s/order-service.yaml + + - name: Wait for External IPs & capture + id: capture + shell: bash + run: | + for i in {1..60}; do + PIP=$(kubectl get svc product-service-w08e1 -n $NS -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + OIP=$(kubectl get svc order-service-w08e1 -n $NS -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + if [[ -n "$PIP" && -n "$OIP" ]]; then + echo "Product IP: $PIP" + echo "Order IP: $OIP" + break + fi + sleep 5 + done + [[ -n "$PIP" && -n "$OIP" ]] || { echo "IPs not assigned"; exit 1; } + echo "product_ip=$PIP" >> $GITHUB_OUTPUT + echo "order_ip=$OIP" >> $GITHUB_OUTPUT + + frontend_cd: + name: Deploy Frontend (uses backend IPs) + runs-on: ubuntu-latest + needs: backend_cd + steps: + - uses: actions/checkout@v4 + + - name: Azure Login + uses: azure/login@v2 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Login to ACR + run: az acr login --name ${{ secrets.AZURE_CONTAINER_REGISTRY_NAME }} # einweek9acr + + - name: Inject Backend IPs into frontend/main.js + shell: bash + env: + PRODUCT_IP: ${{ needs.backend_cd.outputs.product_ip }} + ORDER_IP: ${{ needs.backend_cd.outputs.order_ip }} + PRODUCT_PORT: ${{ env.PRODUCT_PORT }} + ORDER_PORT: ${{ env.ORDER_PORT }} + run: | + FILE="frontend/main.js" + test -f "$FILE" || { echo "Missing $FILE"; exit 1; } + grep -q "_PRODUCT_API_URL_" "$FILE" || { echo "PRODUCT placeholder missing"; exit 1; } + grep -q "_ORDER_API_URL_" "$FILE" || { echo "ORDER placeholder missing"; exit 1; } + cp "$FILE" "${FILE}.before" + + sed -i "s|_PRODUCT_API_URL_|http://${PRODUCT_IP}:${PRODUCT_PORT}|g" "$FILE" + sed -i "s|_ORDER_API_URL_|http://${ORDER_IP}:${ORDER_PORT}|g" "$FILE" + + echo "Diff (context=0, only changed lines):" + diff -u --strip-trailing-cr --label before "${FILE}.before" --label after "$FILE" | sed -n '1,120p' || true + + - name: Build & push frontend + run: | + docker build -t ${{ secrets.AZURE_CONTAINER_REGISTRY }}/frontend:latest ./frontend + docker push ${{ secrets.AZURE_CONTAINER_REGISTRY }}/frontend:latest + + - name: Set AKS context + uses: azure/aks-set-context@v4 + with: + resource-group: ${{ env.RG }} # = SIT722-RG + cluster-name: ${{ env.AKS }} # = sit722-aks + + - name: Deploy frontend & show service + run: | + kubectl apply -n $NS -f k8s/frontend.yaml + kubectl get svc -n $NS -o wide diff --git a/.github/workflows/backend-cd.yml b/.github/workflows/backend-cd.yml index 6035ed15..c3420791 100644 --- a/.github/workflows/backend-cd.yml +++ b/.github/workflows/backend-cd.yml @@ -6,15 +6,15 @@ on: aks_cluster_name: description: 'Name of the AKS Cluster to deploy to' required: true - default: '' + default: 'sit722-aks' aks_resource_group: description: 'Resource Group of the AKS Cluster' required: true - default: '' + default: 'SIT722-RG' aks_acr_name: - description: 'Name of ACR' + description: 'Azure Container Registry name (not FQDN)' required: true - default: '' + default: '223313284acr' jobs: deploy_backend: @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@v4 - name: Log in to Azure - uses: azure/login@v1 + uses: azure/login@v2 with: creds: ${{ secrets.AZURE_CREDENTIALS }} enable-AzPSSession: true @@ -38,10 +38,28 @@ jobs: - name: Set Kubernetes context (get AKS credentials) run: | az aks get-credentials --resource-group ${{ github.event.inputs.aks_resource_group }} --name ${{ github.event.inputs.aks_cluster_name }} --overwrite-existing + + - name: Setup kubectl + uses: azure/setup-kubectl@v4 + + - name: Ensure namespace exists + run: | + kubectl create namespace sit722 --dry-run=client -o yaml | kubectl apply -f - - - name: Attach ACR + - name: Create/refresh imagePullSecret (acr-auth) run: | - az aks update --name ${{ github.event.inputs.aks_cluster_name }} --resource-group ${{ github.event.inputs.aks_resource_group }} --attach-acr ${{ github.event.inputs.aks_acr_name }} + kubectl delete secret acr-auth -n sit722 --ignore-not-found + kubectl create secret docker-registry acr-auth \ + --namespace sit722 \ + --docker-server=${{ secrets.AZURE_CONTAINER_REGISTRY }} \ + --docker-username=${{ secrets.AZURE_CONTAINER_REGISTRY_NAME }} \ + --docker-password='${{ secrets.ACR_PASSWORD }}' \ + --docker-email="devnull@example.local" + kubectl get secret acr-auth -n sit722 + +# - name: Attach ACR +# run: | +# az aks update --name ${{ github.event.inputs.aks_cluster_name }} --resource-group ${{ github.event.inputs.aks_resource_group }} --attach-acr ${{ github.event.inputs.aks_acr_name }} - name: Deploy Backend Infrastructure (Namespace, ConfigMaps, Secrets, Databases) run: | @@ -59,16 +77,19 @@ jobs: kubectl apply -f product-service.yaml kubectl apply -f order-service.yaml - - name: Wait for Backend LoadBalancer IPs + - name: Wait for Backend LoadBalancer IPs run: | + echo "Sanity check: services across namespaces" + kubectl get svc -A + echo "Waiting for Product, Order LoadBalancer IPs to be assigned (up to 5 minutes)..." PRODUCT_IP="" ORDER_IP="" for i in $(seq 1 60); do echo "Attempt $i/60 to get IPs..." - PRODUCT_IP=$(kubectl get service product-service-w08e1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - ORDER_IP=$(kubectl get service order-service-w08e1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + PRODUCT_IP=$(kubectl get service product-service-w08e1 -n sit722 -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + ORDER_IP=$(kubectl get service order-service-w08e1 -n sit722 -o jsonpath='{.status.loadBalancer.ingress[0].ip}') if [[ -n "$PRODUCT_IP" && -n "$ORDER_IP" ]]; then echo "All backend LoadBalancer IPs assigned!" @@ -80,10 +101,13 @@ jobs: done if [[ -z "$PRODUCT_IP" || -z "$ORDER_IP" ]]; then + echo "DEBUG: describe services in sit722" + kubectl describe svc product-service-w08e1 -n sit722 || true + kubectl describe svc order-service-w08e1 -n sit722 || true echo "Error: One or more LoadBalancer IPs not assigned after timeout." - exit 1 # Fail the job if IPs are not obtained + exit 1 fi - + # These are environment variables for subsequent steps in the *same job* # And used to set the job outputs echo "PRODUCT_IP=$PRODUCT_IP" >> $GITHUB_ENV diff --git a/.github/workflows/backend_ci.yml b/.github/workflows/backend_ci.yml index d69725aa..5d59b5fd 100644 --- a/.github/workflows/backend_ci.yml +++ b/.github/workflows/backend_ci.yml @@ -5,8 +5,11 @@ name: Backend CI - Test, Build and Push Images to ACR # Trigger the workflow on pushes to the 'main' branch # You can also add 'pull_request:' to run on PRs on: - # Manual trigger - workflow_dispatch: + pull_request: + branches: [ main ] # or: [ development ] + paths: + - 'backend/**' + - '.github/workflows/backend_ci.yml' # Automatically on pushes to main branch push: @@ -24,6 +27,7 @@ env: # Dynamically generate image tags based on Git SHA and GitHub Run ID # This provides unique, traceable tags for each image build IMAGE_TAG: ${{ github.sha }}-${{ github.run_id }} + ACR_NAME: ${{ secrets.AZURE_CONTAINER_REGISTRY_NAME }} jobs: # Job 1: Run tests and linting for all backend services @@ -109,8 +113,9 @@ jobs: run: | pytest tests --maxfail=1 --disable-warnings -q - # Job 2: Build and Push Docker Images (runs only if tests pass) + ## Job 2: Build and Push Docker Images (runs only if tests pass) build_and_push_images: + if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/main') runs-on: ubuntu-latest needs: test_and_lint_backends @@ -125,8 +130,10 @@ jobs: creds: ${{ secrets.AZURE_CREDENTIALS }} # Needs to be set as a GitHub Secret (Service Principal JSON) # Login to Azure Container Registry (ACR) - - name: Login to Azure Container Registry - run: az acr login --name ${{ env.ACR_LOGIN_SERVER }} + - name: ACR login (AAD, short name) + run: | + echo "az acr login --name $ACR_NAME" + az acr login --name "$ACR_NAME" # Build and Push Docker image for Product Service - name: Build and Push Product Service Image diff --git a/.github/workflows/frontend-cd.yml b/.github/workflows/frontend-cd.yml index 0a0879c8..edbea9ca 100644 --- a/.github/workflows/frontend-cd.yml +++ b/.github/workflows/frontend-cd.yml @@ -10,19 +10,19 @@ on: product_api_ip: description: 'External IP of Product Service' required: true - default: 'http://:8000' + default: 'http://172.188.196.47:8000' order_api_ip: description: 'External IP of Order Service (e.g., http://Y.Y.Y.Y:8001)' required: true - default: 'http://:8001' + default: 'http://4.144.233.236:8001' aks_cluster_name: - description: 'Name of the AKS Cluster to deploy to' + description: 'AKS cluster name' required: true - default: '' + default: 'sit722-aks' aks_resource_group: - description: 'Resource Group of the AKS Cluster' + description: 'AKS resource group' required: true - default: '<' + default: 'SIT722-RG' workflow_call: inputs: diff --git a/.github/workflows/frontend_ci.yml b/.github/workflows/frontend_ci.yml index 9f9e76d9..dec267e0 100644 --- a/.github/workflows/frontend_ci.yml +++ b/.github/workflows/frontend_ci.yml @@ -1,10 +1,13 @@ -# week08/.github/workflows/frontend_ci.yml +## week08/.github/workflows/frontend_ci.yml name: Frontend CI - Build & Push Image on: - # Manual trigger - workflow_dispatch: + pull_request: + branches: [ main ] + paths: + - 'frontend/**' + - '.github/workflows/frontend_ci.yml' # Automatically on pushes to main branch push: @@ -39,13 +42,22 @@ jobs: # Login to Azure Container Registry (ACR) - name: Login to Azure Container Registry - run: az acr login --name ${{ env.ACR_LOGIN_SERVER }} + run: az acr login --name ${{ secrets.AZURE_CONTAINER_REGISTRY_NAME }} # Build and Push Docker image for Frontend - name: Build and Push Frontend Image run: | + # Build as :latest docker build -t ${{ env.ACR_LOGIN_SERVER }}/frontend:latest ./frontend/ + + # Tag the same image with a unique IMAGE_TAG + docker tag ${{ env.ACR_LOGIN_SERVER }}/frontend:latest ${{ env.ACR_LOGIN_SERVER }}/frontend:${{ env.IMAGE_TAG }} + + # Push both tags docker push ${{ env.ACR_LOGIN_SERVER }}/frontend:latest + docker push ${{ env.ACR_LOGIN_SERVER }}/frontend:${{ env.IMAGE_TAG }} + + # Logout from Azure for security (runs even if image push fails) - name: Logout from Azure diff --git a/.gitignore b/.gitignore index cfa76a7f..8776beb4 100644 Binary files a/.gitignore and b/.gitignore differ diff --git a/frontend/main.js b/frontend/main.js index f321fd91..4bd6d2da 100644 --- a/frontend/main.js +++ b/frontend/main.js @@ -4,8 +4,9 @@ document.addEventListener('DOMContentLoaded', () => { // API endpoints for the Product and Order services. // These ports (30000 for Product, 30001 for Order) are mapped // from the Docker containers to the host machine in docker-compose.yml for Example 2. - const PRODUCT_API_BASE_URL = '_PRODUCT_API_URL_'; - const ORDER_API_BASE_URL = '_ORDER_API_URL_'; + const PRODUCT_API_BASE_URL = "_PRODUCT_API_URL_"; + const ORDER_API_BASE_URL = "_ORDER_API_URL_"; + // Product Service is named 'product-service-w04e2' and exposes port 8000 internally. //const PRODUCT_API_BASE_URL = 'http://product-service-w04e2:8000'; diff --git a/k8s/frontend.yaml b/k8s/frontend.yaml index 1948536d..a66b1cfb 100644 --- a/k8s/frontend.yaml +++ b/k8s/frontend.yaml @@ -18,7 +18,7 @@ spec: spec: containers: - name: frontend-container - image: durgeshsamariya.azurecr.io/frontend:latest + image: 223313284acr.azurecr.io/frontend:latest imagePullPolicy: Always ports: - containerPort: 80 diff --git a/k8s/order-service.yaml b/k8s/order-service.yaml index c9d92e4d..58bd9423 100644 --- a/k8s/order-service.yaml +++ b/k8s/order-service.yaml @@ -16,9 +16,11 @@ spec: labels: app: order-service spec: + imagePullSecrets: + - name: acr-auth containers: - name: order-service-container - image: durgeshsamariya.azurecr.io/order_service:latest + image: 223313284acr.azurecr.io/order_service:latest imagePullPolicy: Always ports: - containerPort: 8000 diff --git a/k8s/product-service.yaml b/k8s/product-service.yaml index 0cbbd505..91c18d30 100644 --- a/k8s/product-service.yaml +++ b/k8s/product-service.yaml @@ -16,9 +16,11 @@ spec: labels: app: product-service spec: + imagePullSecrets: + - name: acr-auth containers: - name: product-service-container - image: durgeshsamariya.azurecr.io/product_service:latest + image: 223313284acr.azurecr.io/product_service:latest imagePullPolicy: Always ports: - containerPort: 8000