diff --git a/.github/workflows/backend-cd.yml b/.github/workflows/backend-cd.yml index 6f5f1f67..0f09b2ba 100644 --- a/.github/workflows/backend-cd.yml +++ b/.github/workflows/backend-cd.yml @@ -3,17 +3,14 @@ name: CD - Deploy Backend (then Frontend) on: workflow_dispatch: inputs: - aks_cluster_name: { description: 'AKS name', required: true } - aks_resource_group: { description: 'RG name', required: true } + aks_cluster_name: { description: 'AKS name', required: false, default: '' } + aks_resource_group: { description: 'RG name', required: false, default: '' } + image_tag: { description: 'Image tag to deploy (optional)', required: false, default: '' } workflow_run: workflows: ["CI - Test, Build & Push (Backend + Frontend)"] types: [completed] branches: [main] -env: - REGISTRY_LOGIN_SERVER: ${{ secrets.AZURE_ACR_LOGIN_SERVER }} - IMAGE_TAG: ${{ github.event.workflow_run?.outputs.image_tag || github.sha }} - permissions: id-token: write contents: read @@ -24,36 +21,85 @@ concurrency: jobs: deploy_backend: + # Run if manual OR CI completed successfully if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest environment: Production + outputs: PRODUCT_API_IP: ${{ steps.capture.outputs.product_ip }} - ORDER_API_IP: ${{ steps.capture.outputs.order_ip }} + ORDER_API_IP: ${{ steps.capture.outputs.order_ip }} + IMAGE_TAG: ${{ steps.compute_tag.outputs.val }} + AKS_NAME: ${{ steps.compute_aks.outputs.name }} + AKS_RG: ${{ steps.compute_aks.outputs.rg }} steps: - uses: actions/checkout@v4 - - name: Azure Login (OIDC) - uses: azure/login@v2 + # Decide which image tag to deploy + - name: Compute IMAGE_TAG + id: compute_tag + run: | + if [ "${{ github.event_name }}" = "workflow_run" ]; then + echo "val=${{ github.event.workflow_run.head_sha }}" >> $GITHUB_OUTPUT + elif [ -n "${{ github.event.inputs.image_tag }}" ]; then + echo "val=${{ github.event.inputs.image_tag }}" >> $GITHUB_OUTPUT + else + echo "val=${{ github.sha }}" >> $GITHUB_OUTPUT + fi + + - name: Ensure image tag exists (fallback to latest) + id: tag_check + env: + REGISTRY_NAME: ${{ secrets.AZURE_ACR_NAME }} # e.g. cyweek09acr + IMAGE_TAG: ${{ steps.compute_tag.outputs.val }} + run: | + set -e + echo "Checking if tag exists in ACR: $IMAGE_TAG" + if az acr repository show-tags --name "$REGISTRY_NAME" --repository product_service --output tsv | grep -q "^${IMAGE_TAG}$"; then + echo "resolved=${IMAGE_TAG}" >> $GITHUB_OUTPUT + else + echo "Tag not found in ACR, falling back to: latest" + echo "resolved=latest" >> $GITHUB_OUTPUT + fi + + # use tag_check.outputs.resolved everywhere instead of compute_tag.outputs.val + + + + + # Compute AKS name/RG (inputs override secrets) + - name: Compute AKS values + id: compute_aks + run: | + NAME="${{ github.event.inputs.aks_cluster_name }}" + RG="${{ github.event.inputs.aks_resource_group }}" + if [ -z "$NAME" ]; then NAME="${{ secrets.AKS_NAME }}"; fi + if [ -z "$RG" ]; then RG="${{ secrets.AKS_RG }}"; fi + echo "name=$NAME" >> $GITHUB_OUTPUT + echo "rg=$RG" >> $GITHUB_OUTPUT + echo "AKS_NAME=$NAME" >> $GITHUB_ENV + echo "AKS_RG=$RG" >> $GITHUB_ENV + + # Azure login + - name: Azure Login + uses: azure/login@v1 with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + creds: ${{ secrets.AZURE_CREDENTIALS }} enable-AzPSSession: true - name: Set AKS context run: | az aks get-credentials \ - --resource-group "${{ github.event.inputs.aks_resource_group || secrets.AKS_RG }}" \ - --name "${{ github.event.inputs.aks_cluster_name || secrets.AKS_NAME }}" \ + --resource-group "$AKS_RG" \ + --name "$AKS_NAME" \ --overwrite-existing - name: Attach ACR run: | az aks update \ - --resource-group "${{ github.event.inputs.aks_resource_group || secrets.AKS_RG }}" \ - --name "${{ github.event.inputs.aks_cluster_name || secrets.AKS_NAME }}" \ + --resource-group "$AKS_RG" \ + --name "$AKS_NAME" \ --attach-acr "${{ secrets.AZURE_ACR_NAME }}" - name: Deploy Config & Databases @@ -64,25 +110,50 @@ jobs: kubectl apply -f product-db.yaml kubectl apply -f order-db.yaml - - name: Deploy Services with pinned images - working-directory: k8s + - name: Deploy Services (apply manifests and pin images) + env: + REGISTRY_LOGIN_SERVER: ${{ secrets.AZURE_ACR_LOGIN_SERVER }} + IMAGE_TAG: ${{ steps.tag_check.outputs.resolved }} + run: | + kubectl apply -f k8s/product-service.yaml + kubectl apply -f k8s/order-service.yaml + + kubectl set image deploy/product-service-w08e1 product-service-container="${REGISTRY_LOGIN_SERVER}/product_service:${IMAGE_TAG}" --record=true || true + kubectl set image deploy/order-service-w08e1 order-service-container="${REGISTRY_LOGIN_SERVER}/order_service:${IMAGE_TAG}" --record=true || true + + kubectl rollout status deploy/product-service-w08e1 --timeout=600s + kubectl rollout status deploy/order-service-w08e1 --timeout=600s + + + - name: Debug (product-service) on failure + if: failure() run: | - # Patch images to the exact CI-built tag - kubectl set image deploy/product-service-w08e1 product-service-container="${{ env.REGISTRY_LOGIN_SERVER }}/product_service:${{ env.IMAGE_TAG }}" --record=true || true - kubectl set image deploy/order-service-w08e1 order-service-container="${{ env.REGISTRY_LOGIN_SERVER }}/order_service:${{ env.IMAGE_TAG }}" --record=true || true - # If first time apply: - kubectl apply -f product-service.yaml - kubectl apply -f order-service.yaml - - - name: Wait for LoadBalancer IPs + echo "=== Deployments ===" + kubectl get deploy -o wide + echo "=== ReplicaSets (product) ===" + kubectl get rs -l app=product-service -o wide || true + echo "=== Pods (product) ===" + kubectl get pods -l app=product-service -o wide || true + echo "=== Describe deployment ===" + kubectl describe deploy/product-service-w08e1 || true + echo "=== Describe pods ===" + for p in $(kubectl get pods -l app=product-service -o name); do + kubectl describe "$p" || true + echo "--- Logs $p ---" + kubectl logs "$p" --all-containers --tail=200 || true + done + echo "=== Recent events ===" + kubectl get events --sort-by=.lastTimestamp | tail -n 200 || true + + - name: Capture LoadBalancer IPs id: capture run: | for i in {1..60}; do PRODUCT_IP=$(kubectl get svc product-service-w08e1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - ORDER_IP=$(kubectl get svc order-service-w08e1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + ORDER_IP=$(kubectl get svc order-service-w08e1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}') if [[ -n "$PRODUCT_IP" && -n "$ORDER_IP" ]]; then echo "product_ip=$PRODUCT_IP" >> $GITHUB_OUTPUT - echo "order_ip=$ORDER_IP" >> $GITHUB_OUTPUT + echo "order_ip=$ORDER_IP" >> $GITHUB_OUTPUT exit 0 fi sleep 5 @@ -93,7 +164,11 @@ jobs: needs: deploy_backend uses: ./.github/workflows/frontend-cd.yml with: - product_api_ip: "http://${{ needs.deploy_backend.outputs.PRODUCT_API_IP }}:8000" - order_api_ip: "http://${{ needs.deploy_backend.outputs.ORDER_API_IP }}:8001" - aks_cluster_name: ${{ github.event.inputs.aks_cluster_name || secrets.AKS_NAME }} - aks_resource_group: ${{ github.event.inputs.aks_resource_group || secrets.AKS_RG }} + product_api_ip: "http://${{ needs.deploy_backend.outputs.PRODUCT_API_IP }}:8000" + order_api_ip: "http://${{ needs.deploy_backend.outputs.ORDER_API_IP }}:8001" + aks_cluster_name: ${{ needs.deploy_backend.outputs.AKS_NAME }} + aks_resource_group: ${{ needs.deploy_backend.outputs.AKS_RG }} + image_tag: ${{ needs.deploy_backend.outputs.IMAGE_TAG }} + secrets: inherit + +# update \ No newline at end of file diff --git a/.github/workflows/frontend-cd.yml b/.github/workflows/frontend-cd.yml index 16a54401..c7c3f394 100644 --- a/.github/workflows/frontend-cd.yml +++ b/.github/workflows/frontend-cd.yml @@ -51,7 +51,6 @@ on: permissions: id-token: write contents: read - packages: write env: REGISTRY_NAME: ${{ secrets.AZURE_ACR_NAME }} diff --git a/k8s/frontend.yaml b/k8s/frontend.yaml index 983d9d3a..9dbbfa4e 100644 --- a/k8s/frontend.yaml +++ b/k8s/frontend.yaml @@ -18,7 +18,7 @@ spec: spec: containers: - name: frontend-container - image: cyweek09acr.azurecr.io/frontend:351d67963dc6fa999d76cd122dac19bb0220b0b9 + image: cyweek09acr.azurecr.io/frontend:latest imagePullPolicy: Always ports: - containerPort: 80 diff --git a/k8s/order-service.yaml b/k8s/order-service.yaml index 04441e06..da06c57b 100644 --- a/k8s/order-service.yaml +++ b/k8s/order-service.yaml @@ -18,7 +18,7 @@ spec: spec: containers: - name: order-service-container - image: cyweek09acr.azurecr.io/order_service:351d67963dc6fa999d76cd122dac19bb0220b0b9 + image: cyweek09acr.azurecr.io/order_service:latest imagePullPolicy: Always ports: - containerPort: 8000 diff --git a/k8s/product-service.yaml b/k8s/product-service.yaml index 0a938a1c..d7996976 100644 --- a/k8s/product-service.yaml +++ b/k8s/product-service.yaml @@ -18,7 +18,7 @@ spec: spec: containers: - name: product-service-container - image: cyweek09acr.azurecr.io/product_service:351d67963dc6fa999d76cd122dac19bb0220b0b9 + image: cyweek09acr.azurecr.io/product_service:latest imagePullPolicy: Always ports: - containerPort: 8000