feat: AWS EKS cloud deployment specification (Phase VI) #9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build, Test, and Deploy | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| branches: [main] | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }} | |
| jobs: | |
| # Build multi-arch images for all 6 services | |
| build-images: | |
| name: Build Multi-Arch Images | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| matrix: | |
| service: | |
| - name: backend | |
| context: ./backend | |
| dockerfile: ./backend/Dockerfile | |
| - name: frontend | |
| context: ./frontend | |
| dockerfile: ./frontend/Dockerfile | |
| - name: audit-service | |
| context: ./services/audit-service | |
| dockerfile: ./services/audit-service/Dockerfile | |
| - name: recurring-task-service | |
| context: ./services/recurring-task-service | |
| dockerfile: ./services/recurring-task-service/Dockerfile | |
| - name: notification-service | |
| context: ./services/notification-service | |
| dockerfile: ./services/notification-service/Dockerfile | |
| - name: websocket-service | |
| context: ./services/websocket-service | |
| dockerfile: ./services/websocket-service/Dockerfile | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.IMAGE_PREFIX }}/lifestepsai-${{ matrix.service.name }} | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=pr | |
| type=sha,prefix={{branch}}- | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: ${{ matrix.service.context }} | |
| file: ${{ matrix.service.dockerfile }} | |
| platforms: linux/amd64,linux/arm64 | |
| push: ${{ github.event_name != 'pull_request' }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # Run backend tests | |
| test-backend: | |
| name: Test Backend (Python) | |
| runs-on: ubuntu-latest | |
| needs: build-images | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| cache: 'pip' | |
| - name: Install dependencies | |
| run: | | |
| cd backend | |
| pip install -r requirements.txt | |
| pip install pytest pytest-cov pytest-asyncio httpx | |
| - name: Run unit tests | |
| run: | | |
| cd backend | |
| python -m pytest tests/unit/ -v --cov=src --cov-report=xml | |
| - name: Upload coverage | |
| uses: codecov/codecov-action@v4 | |
| if: always() | |
| with: | |
| file: ./backend/coverage.xml | |
| flags: backend | |
| name: backend-coverage | |
| # Run frontend tests (if configured) | |
| test-frontend: | |
| name: Test Frontend (TypeScript) | |
| runs-on: ubuntu-latest | |
| needs: build-images | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install dependencies | |
| run: | | |
| cd frontend | |
| npm ci | |
| - name: Run linter | |
| run: | | |
| cd frontend | |
| npm run lint | |
| # Uncomment when frontend tests are configured | |
| # - name: Run tests | |
| # run: | | |
| # cd frontend | |
| # npm run test | |
| # Deploy to staging environment (auto) | |
| deploy-staging: | |
| name: Deploy to Staging | |
| runs-on: ubuntu-latest | |
| needs: [build-images, test-backend, test-frontend] | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| environment: | |
| name: staging | |
| url: ${{ steps.deploy.outputs.url }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up kubectl | |
| uses: azure/k8s-set-context@v3 | |
| with: | |
| method: kubeconfig | |
| kubeconfig: ${{ secrets.KUBE_CONFIG_STAGING }} | |
| - name: Set up Helm | |
| uses: azure/setup-helm@v4 | |
| with: | |
| version: '3.13.0' | |
| - name: Deploy with Helm | |
| id: deploy | |
| run: | | |
| helm upgrade --install lifestepsai-staging ./helm/lifestepsai \ | |
| -f ./helm/lifestepsai/values-staging.yaml \ | |
| --set global.imageTag=${{ github.sha }} \ | |
| --set backend.image.repository=${{ env.IMAGE_PREFIX }}/lifestepsai-backend \ | |
| --set frontend.image.repository=${{ env.IMAGE_PREFIX }}/lifestepsai-frontend \ | |
| --set auditService.image.repository=${{ env.IMAGE_PREFIX }}/lifestepsai-audit-service \ | |
| --set recurringTaskService.image.repository=${{ env.IMAGE_PREFIX }}/lifestepsai-recurring-task-service \ | |
| --set notificationService.image.repository=${{ env.IMAGE_PREFIX }}/lifestepsai-notification-service \ | |
| --set websocketService.image.repository=${{ env.IMAGE_PREFIX }}/lifestepsai-websocket-service \ | |
| --atomic \ | |
| --timeout 10m \ | |
| --namespace staging \ | |
| --create-namespace | |
| # Get LoadBalancer IP | |
| LB_IP=$(kubectl get service lifestepsai-staging-frontend -n staging -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "pending") | |
| echo "url=http://${LB_IP}" >> $GITHUB_OUTPUT | |
| - name: Verify deployment | |
| run: | | |
| kubectl get pods -n staging | |
| kubectl rollout status deployment/lifestepsai-staging-backend -n staging --timeout=5m | |
| # Deploy to production environment (manual approval) | |
| deploy-production: | |
| name: Deploy to Production | |
| runs-on: ubuntu-latest | |
| needs: [build-images, test-backend, test-frontend] | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| environment: | |
| name: production | |
| url: ${{ steps.deploy.outputs.url }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up kubectl | |
| uses: azure/k8s-set-context@v3 | |
| with: | |
| method: kubeconfig | |
| kubeconfig: ${{ secrets.KUBE_CONFIG_PROD }} | |
| - name: Set up Helm | |
| uses: azure/setup-helm@v4 | |
| with: | |
| version: '3.13.0' | |
| - name: Deploy with Helm | |
| id: deploy | |
| run: | | |
| helm upgrade --install lifestepsai ./helm/lifestepsai \ | |
| -f ./helm/lifestepsai/values-prod.yaml \ | |
| --set global.imageTag=${{ github.sha }} \ | |
| --set backend.image.repository=${{ env.IMAGE_PREFIX }}/lifestepsai-backend \ | |
| --set frontend.image.repository=${{ env.IMAGE_PREFIX }}/lifestepsai-frontend \ | |
| --set auditService.image.repository=${{ env.IMAGE_PREFIX }}/lifestepsai-audit-service \ | |
| --set recurringTaskService.image.repository=${{ env.IMAGE_PREFIX }}/lifestepsai-recurring-task-service \ | |
| --set notificationService.image.repository=${{ env.IMAGE_PREFIX }}/lifestepsai-notification-service \ | |
| --set websocketService.image.repository=${{ env.IMAGE_PREFIX }}/lifestepsai-websocket-service \ | |
| --atomic \ | |
| --timeout 15m \ | |
| --namespace production \ | |
| --create-namespace | |
| # Get LoadBalancer IP | |
| LB_IP=$(kubectl get service lifestepsai-frontend -n production -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "pending") | |
| echo "url=http://${LB_IP}" >> $GITHUB_OUTPUT | |
| - name: Verify deployment | |
| run: | | |
| kubectl get pods -n production | |
| kubectl rollout status deployment/lifestepsai-backend -n production --timeout=10m | |
| - name: Run smoke tests | |
| run: | | |
| LB_IP=$(kubectl get service lifestepsai-frontend -n production -o jsonpath='{.status.loadBalancer.ingress[0].ip}') | |
| curl -f http://${LB_IP}/health || exit 1 |