diff --git a/.github/workflows/backend-cd.yml b/.github/workflows/backend-cd.yml index 6035ed15..db9442f6 100644 --- a/.github/workflows/backend-cd.yml +++ b/.github/workflows/backend-cd.yml @@ -2,19 +2,6 @@ name: CD - Deploy Backend Services to AKS on: workflow_dispatch: - inputs: - aks_cluster_name: - description: 'Name of the AKS Cluster to deploy to' - required: true - default: '' - aks_resource_group: - description: 'Resource Group of the AKS Cluster' - required: true - default: '' - aks_acr_name: - description: 'Name of ACR' - required: true - default: '' jobs: deploy_backend: @@ -29,19 +16,14 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Log in to Azure - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - enable-AzPSSession: true - - - name: Set Kubernetes context (get AKS credentials) + # 🗝️ Configure kubeconfig using GitHub Secret + - name: Configure kubeconfig run: | - az aks get-credentials --resource-group ${{ github.event.inputs.aks_resource_group }} --name ${{ github.event.inputs.aks_cluster_name }} --overwrite-existing + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_B64 }}" | base64 --decode > ~/.kube/config - - 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: Verify cluster connection + run: kubectl get nodes - name: Deploy Backend Infrastructure (Namespace, ConfigMaps, Secrets, Databases) run: | @@ -76,16 +58,14 @@ jobs: echo "Order Service IP: $ORDER_IP" break fi - sleep 5 # Wait 5 seconds before next attempt + sleep 5 done if [[ -z "$PRODUCT_IP" || -z "$ORDER_IP" ]]; then 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 echo "ORDER_IP=$ORDER_IP" >> $GITHUB_ENV @@ -96,6 +76,3 @@ jobs: - name: Capture Order Service IP for Workflow Output id: get_order_ip run: echo "external_ip=${{ env.ORDER_IP }}" >> $GITHUB_OUTPUT - - - name: Logout from Azure - run: az logout diff --git a/.github/workflows/backend_ci.yml b/.github/workflows/backend_ci.yml index d69725aa..d2a2453b 100644 --- a/.github/workflows/backend_ci.yml +++ b/.github/workflows/backend_ci.yml @@ -1,44 +1,45 @@ -# week08/.github/workflows/backend_ci.yml - 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: - - # Automatically on pushes to main branch + pull_request: + branches: [ main ] + paths: + - 'backend/**' + - '.github/workflows/backend_ci.yml' push: - branches: - - main - paths: # Only trigger if changes are in backend directories + branches: [ main ] + paths: - 'backend/**' - - '.github/workflows/backend_ci.yml' # Trigger if this workflow file changes + - '.github/workflows/backend_ci.yml' + workflow_dispatch: + +# minimal, explicit permissions (prep for later OIDC work) +permissions: + contents: read + id-token: write + packages: write + +# cancel stale runs on same ref +concurrency: + group: backend-ci-${{ github.ref }} + cancel-in-progress: true -# Define global environment variables that can be used across jobs env: - # ACR Login Server (e.g., myregistry.azurecr.io) - # This needs to be set as a GitHub Repository Secret + # e.g. myregistry.azurecr.io ACR_LOGIN_SERVER: ${{ secrets.AZURE_CONTAINER_REGISTRY }} - # 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 }} jobs: - # Job 1: Run tests and linting for all backend services test_and_lint_backends: - runs-on: ubuntu-latest # Use a GitHub-hosted runner + runs-on: ubuntu-latest services: - # Product DB container product_db: image: postgres:15 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: products - # Make pg_isready available so the service is healthy before tests run options: >- --health-cmd "pg_isready -U postgres" --health-interval 10s @@ -46,46 +47,43 @@ jobs: --health-retries 5 ports: - 5432:5432 - - # Order DB order_db: image: postgres:15 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: orders - ports: - - 5433:5432 options: >- --health-cmd "pg_isready -U postgres" --health-interval 10s --health-timeout 5s --health-retries 5 + ports: + - 5433:5432 steps: - # 1. Checkout the repository code to the runner - name: Checkout repository - uses: actions/checkout@v4 # Action to check out your repository code + uses: actions/checkout@v4 - # 2. Set up Python environment - - name: Set up Python 3.10 - uses: actions/setup-python@v5 # Action to set up Python environment + - uses: actions/setup-python@v5 with: python-version: '3.10' + cache: 'pip' + + - name: Install tooling & deps + run: | + python -m pip install --upgrade pip + pip install ruff mypy pytest httpx + # Single-service or multi-service deps: + if [ -f backend/requirements.txt ]; then pip install -r backend/requirements.txt; fi + for req in backend/*/requirements.txt; do [ -f "$req" ] && pip install -r "$req"; done + + - name: Lint (ruff) + run: ruff check backend/ || true + + - name: Type-check (mypy) + run: mypy backend/ || true - # 3. Install dependencies and run code quality checks - - name: Install dependencies - run: | # Use a multi-line script to install pip dependencies - pip install --upgrade pip - # Loop through each backend service folder - for req in backend/*/requirements.txt; do - echo "Installing $req" - pip install -r "$req" - done - # Install CI tools - pip install pytest httpx - - # 5. Run tests for product service - name: Run product_service tests working-directory: backend/product_service env: @@ -94,10 +92,8 @@ jobs: POSTGRES_DB: products POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - run: | - pytest tests --maxfail=1 --disable-warnings -q - - # 6. Run tests for order service + run: pytest tests --maxfail=1 --disable-warnings -q + - name: Run order_service tests working-directory: backend/order_service env: @@ -106,41 +102,49 @@ jobs: POSTGRES_DB: orders POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - run: | - pytest tests --maxfail=1 --disable-warnings -q + run: pytest tests --maxfail=1 --disable-warnings -q - # Job 2: Build and Push Docker Images (runs only if tests pass) build_and_push_images: + # do not build/push images on PR validation + if: ${{ github.event_name != 'pull_request' }} runs-on: ubuntu-latest needs: test_and_lint_backends steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Azure login using a Service Principal secret - - name: Azure Login - uses: azure/login@v1 - with: - 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 }} - - # Build and Push Docker image for Product Service - - name: Build and Push Product Service Image - run: | - docker build -t ${{ env.ACR_LOGIN_SERVER }}/product_service:latest ./backend/product_service/ - docker push ${{ env.ACR_LOGIN_SERVER }}/product_service:latest - - # Build and Push Docker image for Order Service - - name: Build and Push Order Service Image - run: | - docker build -t ${{ env.ACR_LOGIN_SERVER }}/order_service:latest ./backend/order_service/ - docker push ${{ env.ACR_LOGIN_SERVER }}/order_service:latest - - # Logout from Azure for security (runs even if image push fails) - - name: Logout from Azure - run: az logout - if: always() + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Debug ACR login server + run: echo "Using ACR login server:${{ env.ACR_LOGIN_SERVER }}" + + - name: Docker login to ACR + shell: bash + run: | + if [ -z "${{ env.ACR_LOGIN_SERVER }}" ]; then + echo "ACR_LOGIN_SERVER is empty. Check secret AZURE_CONTAINER_REGISTRY." + exit 1 + fi + echo "${{ secrets.ACR_PASSWORD }}" | docker login "${{ env.ACR_LOGIN_SERVER }}" \ + -u "${{ secrets.ACR_USERNAME }}" --password-stdin + + - name: Build and Push Product Service Image + shell: bash + run: | + docker build -t "${{ env.ACR_LOGIN_SERVER }}/product_service:latest" \ + -t "${{ env.ACR_LOGIN_SERVER }}/product_service:${{ env.IMAGE_TAG }}" \ + ./backend/product_service/ + docker push "${{ env.ACR_LOGIN_SERVER }}/product_service:latest" + docker push "${{ env.ACR_LOGIN_SERVER }}/product_service:${{ env.IMAGE_TAG }}" + + - name: Build and Push Order Service Image + shell: bash + run: | + docker build -t "${{ env.ACR_LOGIN_SERVER }}/order_service:latest" \ + -t "${{ env.ACR_LOGIN_SERVER }}/order_service:${{ env.IMAGE_TAG }}" \ + ./backend/order_service/ + docker push "${{ env.ACR_LOGIN_SERVER }}/order_service:latest" + docker push "${{ env.ACR_LOGIN_SERVER }}/order_service:${{ env.IMAGE_TAG }}" + + - name: Docker logout + if: always() + run: docker logout "${{ env.ACR_LOGIN_SERVER }}" diff --git a/.github/workflows/frontend-cd.yml b/.github/workflows/frontend-cd.yml index 0a0879c8..ceb61170 100644 --- a/.github/workflows/frontend-cd.yml +++ b/.github/workflows/frontend-cd.yml @@ -1,43 +1,7 @@ -# week08/.github/workflows/frontend-cd.yml - name: CD - Deploy Frontend to AKS -# This workflow can be called by other workflows and takes inputs. -# Or it can be run manually if you provide the IPs. on: workflow_dispatch: - inputs: - product_api_ip: - description: 'External IP of Product Service' - required: true - default: 'http://:8000' - order_api_ip: - description: 'External IP of Order Service (e.g., http://Y.Y.Y.Y:8001)' - required: true - default: 'http://:8001' - aks_cluster_name: - description: 'Name of the AKS Cluster to deploy to' - required: true - default: '' - aks_resource_group: - description: 'Resource Group of the AKS Cluster' - required: true - default: '<' - - workflow_call: - inputs: - product_api_ip: - required: true - type: string - order_api_ip: - required: true - type: string - aks_cluster_name: - required: true - type: string - aks_resource_group: - required: true - type: string jobs: deploy_frontend: @@ -48,46 +12,25 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - # Azure login using a Service Principal secret - - name: Azure Login - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - # Login to Azure Container Registry (ACR) - - name: Login to Azure Container Registry - run: az acr login --name ${{ secrets.AZURE_CONTAINER_REGISTRY }} - - - name: Inject Backend IPs into Frontend main.js + - name: Configure kubeconfig run: | - echo "Injecting IPs into frontend/static/js/main.js" - # Ensure frontend/main.js is directly in the path for sed - sed -i "s|_PRODUCT_API_URL_|${{ inputs.product_api_ip }}|g" frontend/main.js - sed -i "s|_ORDER_API_URL_|${{ inputs.order_api_ip }}|g" frontend/main.js - - # Display the modified file content for debugging - echo "--- Modified main.js content ---" - cat frontend/main.js - echo "---------------------------------" + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_B64 }}" | base64 --decode > ~/.kube/config - # Build and Push Docker image for Frontend - - name: Build and Push Frontend Image - run: | - docker build -t ${{ secrets.AZURE_CONTAINER_REGISTRY }}/frontend:latest ./frontend/ - docker push ${{ secrets.AZURE_CONTAINER_REGISTRY }}/frontend:latest - - - name: Set Kubernetes context (get AKS credentials) - uses: azure/aks-set-context@v3 - with: - resource-group: ${{ inputs.aks_resource_group }} - cluster-name: ${{ inputs.aks_cluster_name }} + - name: Verify cluster connection + run: kubectl get nodes - - name: Deploy Frontend to AKS + # If your repo has ONE file (frontend.yaml), keep this block and remove the next one. + - name: Deploy Frontend (single manifest) + if: ${{ hashFiles('k8s/frontend.yaml') != '' }} run: | - echo "Deploying frontend with latest tag to AKS cluster: ${{ inputs.aks_cluster_name }}" - cd k8s/ - # Ensure frontend-service.yaml is configured with your ACR - kubectl apply -f frontend.yaml + echo "Applying k8s/frontend.yaml" + kubectl apply -f k8s/frontend.yaml - - name: Logout from Azure (AKS deployment) - run: az logout + # If your repo has split files, this block will run; the single-file block above will be skipped. + - name: Deploy Frontend (split manifests) + if: ${{ hashFiles('k8s/frontend-deployment.yaml') != '' || hashFiles('k8s/frontend-service.yaml') != '' }} + run: | + echo "Applying split frontend manifests" + if [ -f k8s/frontend-deployment.yaml ]; then kubectl apply -f k8s/frontend-deployment.yaml; fi + if [ -f k8s/frontend-service.yaml ]; then kubectl apply -f k8s/frontend-service.yaml; fi diff --git a/.github/workflows/frontend_ci.yml b/.github/workflows/frontend_ci.yml index 9f9e76d9..afd76010 100644 --- a/.github/workflows/frontend_ci.yml +++ b/.github/workflows/frontend_ci.yml @@ -1,53 +1,84 @@ -# week08/.github/workflows/frontend_ci.yml - name: Frontend CI - Build & Push Image on: - # Manual trigger workflow_dispatch: - - # Automatically on pushes to main branch + inputs: + PRODUCT_API_URL: + description: "Public URL for Product API (e.g., http://4.237.166.140:8000)" + required: true + ORDER_API_URL: + description: "Public URL for Order API (e.g., http://4.254.100.97:8001)" + required: true push: - branches: - - main - paths: # Only trigger if changes are in the frontend directory + branches: [ main ] + paths: - 'frontend/**' - - '.github/workflows/frontend_ci.yml' # Trigger if this workflow file changes + - '.github/workflows/frontend_ci.yml' -# Define global environment variables that can be used across jobs env: - # ACR Login Server (e.g., myregistry.azurecr.io) - # This needs to be set as a GitHub Repository Secret ACR_LOGIN_SERVER: ${{ secrets.AZURE_CONTAINER_REGISTRY }} - # 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 }} jobs: build_and_push_frontend: runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Azure login using a Service Principal secret - - name: Azure Login - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - # Login to Azure Container Registry (ACR) - - name: Login to Azure Container Registry - run: az acr login --name ${{ env.ACR_LOGIN_SERVER }} - - # Build and Push Docker image for Frontend - - name: Build and Push Frontend Image - run: | - docker build -t ${{ env.ACR_LOGIN_SERVER }}/frontend:latest ./frontend/ - docker push ${{ env.ACR_LOGIN_SERVER }}/frontend:latest - - # Logout from Azure for security (runs even if image push fails) - - name: Logout from Azure - run: az logout - if: always() + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Debug ACR login server + run: echo "Using ACR login server:${{ env.ACR_LOGIN_SERVER }}" + + - name: (Optional) Inject API URLs into main.js for manual runs + if: ${{ github.event_name == 'workflow_dispatch' }} + shell: bash + run: | + set -euo pipefail + # Show the first lines for sanity + head -n 5 frontend/main.js || true + + # Ensure placeholders exist + if ! grep -q "_PRODUCT_API_URL_" frontend/main.js; then + echo "ERROR: Placeholder _PRODUCT_API_URL_ not found in frontend/main.js" + exit 1 + fi + if ! grep -q "_ORDER_API_URL_" frontend/main.js; then + echo "ERROR: Placeholder _ORDER_API_URL_ not found in frontend/main.js" + exit 1 + fi + + # Do the replacements + sed -i "s|_PRODUCT_API_URL_|${{ github.event.inputs.PRODUCT_API_URL }}|g" frontend/main.js + sed -i "s|_ORDER_API_URL_|${{ github.event.inputs.ORDER_API_URL }}|g" frontend/main.js + + echo "Injected:" + echo " PRODUCT_API_URL=${{ github.event.inputs.PRODUCT_API_URL }}" + echo " ORDER_API_URL=${{ github.event.inputs.ORDER_API_URL }}" + + # Show a diff summary + echo "--- diff preview ---" + grep -n "const .*_BASE_URL" frontend/main.js || true + echo "--------------------" + + - name: Docker login to ACR + shell: bash + run: | + if [ -z "${{ env.ACR_LOGIN_SERVER }}" ]; then + echo "ACR_LOGIN_SERVER is empty. Check secret AZURE_CONTAINER_REGISTRY." + exit 1 + fi + echo "${{ secrets.ACR_PASSWORD }}" | docker login "${{ env.ACR_LOGIN_SERVER }}" \ + -u "${{ secrets.ACR_USERNAME }}" --password-stdin + + - name: Build & Push Frontend Image + shell: bash + run: | + docker build -t "${{ env.ACR_LOGIN_SERVER }}/frontend:latest" \ + -t "${{ env.ACR_LOGIN_SERVER }}/frontend:${{ env.IMAGE_TAG }}" \ + ./frontend/ + docker push "${{ env.ACR_LOGIN_SERVER }}/frontend:latest" + docker push "${{ env.ACR_LOGIN_SERVER }}/frontend:${{ env.IMAGE_TAG }}" + + - name: Docker logout + if: always() + run: docker logout "${{ env.ACR_LOGIN_SERVER }}" diff --git a/frontend/main.js b/frontend/main.js index f321fd91..eb98eae4 100644 --- a/frontend/main.js +++ b/frontend/main.js @@ -1,11 +1,13 @@ // week08/frontend/main.js +const PRODUCT_API_BASE_URL = "http://4.237.166.140:8000"; +const ORDER_API_BASE_URL = "http://4.254.100.97:8001"; + + 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_'; // Product Service is named 'product-service-w04e2' and exposes port 8000 internally. //const PRODUCT_API_BASE_URL = 'http://product-service-w04e2:8000';