Skip to content

feat: AWS EKS cloud deployment specification (Phase VI) #9

feat: AWS EKS cloud deployment specification (Phase VI)

feat: AWS EKS cloud deployment specification (Phase VI) #9

Workflow file for this run

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