diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 8c12d6a..9e3126c 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -31,9 +31,10 @@ jobs: git fetch origin main git reset --hard origin/main - echo "=== Running tests ===" + echo "=== Running Backend Tests ===" dotnet test tests/Domain.Tests --verbosity minimal dotnet test tests/Application.Tests --verbosity minimal + dotnet test tests/Integration.Tests --verbosity minimal echo "=== Building Docker images ===" sudo docker build -f deploy/docker/Dockerfile.api -t student-api:latest . & @@ -62,9 +63,11 @@ jobs: runs-on: ubuntu-latest needs: deploy steps: - - name: Wait and Verify + - name: Wait for services + run: sleep 15 + + - name: Basic Health Checks run: | - sleep 15 echo "Checking health..." curl -sf https://${{ env.DOMAIN }}/health | grep -q '"status":"Healthy"' echo "Checking frontend..." @@ -73,11 +76,48 @@ jobs: curl -sf -X POST https://${{ env.DOMAIN }}/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{ subjects { id } }"}' | grep -q '"subjects"' - echo "All checks passed!" + echo "Basic checks passed!" + + e2e-tests: + runs-on: ubuntu-latest + needs: smoke-tests + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + cache-dependency-path: src/frontend/package-lock.json + + - name: Install dependencies + working-directory: src/frontend + run: npm ci --legacy-peer-deps + + - name: Install Playwright browsers + working-directory: src/frontend + run: npx playwright install chromium --with-deps + + - name: Run E2E Smoke Tests + working-directory: src/frontend + env: + CI: true + BASE_URL: https://${{ env.DOMAIN }} + run: npx playwright test smoke.spec.ts --reporter=list + + - name: Upload test artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: src/frontend/playwright-report/ + retention-days: 7 rollback: runs-on: ubuntu-latest - needs: smoke-tests + needs: e2e-tests if: failure() steps: - name: Setup SSH and Rollback @@ -86,5 +126,7 @@ jobs: echo "${{ secrets.K3S_SSH_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan -H ${{ env.K3S_HOST }} >> ~/.ssh/known_hosts 2>/dev/null + echo "Rolling back deployments..." ssh ${{ env.K3S_USER }}@${{ env.K3S_HOST }} \ 'sudo kubectl rollout undo deployment/student-api deployment/student-frontend -n academia' + echo "Rollback complete" diff --git a/deploy/k3s/deploy.sh b/deploy/k3s/deploy.sh index b99c299..56c42ec 100755 --- a/deploy/k3s/deploy.sh +++ b/deploy/k3s/deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash -# K3s Deployment Script - Optimizado para tiempo mínimo -# Uso: ./deploy.sh [build|deploy|all|status|clean] +# K3s Deployment Script con Tests y Smoke Tests +# Uso: ./deploy.sh [all|test|build|deploy|smoke|status|clean|rollback] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -12,120 +12,165 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' -# Cargar credenciales si existen -CREDS_FILE="$HOME/.secrets/credentials.env" -[[ -f "$CREDS_FILE" ]] && source "$CREDS_FILE" - # Configuración -K3S_HOST="${K8S_MASTER_HOST:-hp62a}" -NAMESPACE="student-enrollment" -API_IMAGE="academia-api:latest" -FRONTEND_IMAGE="academia-frontend:latest" +NAMESPACE="academia" +DOMAIN="academia.ingeniumcodex.com" +API_IMAGE="student-api:latest" +FRONTEND_IMAGE="student-frontend:latest" log() { echo -e "${GREEN}[$(date +%T)]${NC} $1"; } warn() { echo -e "${YELLOW}[$(date +%T)]${NC} $1"; } -err() { echo -e "${RED}[$(date +%T)]${NC} $1" >&2; } +err() { echo -e "${RED}[$(date +%T)]${NC} $1" >&2; exit 1; } + +pull_code() { + log "=== Actualizando código ===" + cd "$PROJECT_ROOT" + git fetch origin main + git reset --hard origin/main + log "✓ Código actualizado" +} + +run_tests() { + log "=== Ejecutando tests ===" + cd "$PROJECT_ROOT" + dotnet test tests/Domain.Tests --verbosity minimal || err "Domain tests fallaron" + dotnet test tests/Application.Tests --verbosity minimal || err "Application tests fallaron" + log "✓ Todos los tests pasaron" +} build_images() { log "=== Construyendo imágenes Docker (paralelo) ===" cd "$PROJECT_ROOT" - # Build en paralelo - docker build -t "$API_IMAGE" -f deploy/docker/Dockerfile.api . & + sudo docker build -t "$API_IMAGE" -f deploy/docker/Dockerfile.api . & PID_API=$! - docker build -t "$FRONTEND_IMAGE" -f deploy/docker/Dockerfile.frontend . & - PID_FRONTEND=$! + sudo docker build -t "$FRONTEND_IMAGE" -f deploy/docker/Dockerfile.frontend . & + PID_FE=$! - # Esperar ambos - wait $PID_API && log "✓ API imagen construida" || { err "✗ Error construyendo API"; exit 1; } - wait $PID_FRONTEND && log "✓ Frontend imagen construida" || { err "✗ Error construyendo Frontend"; exit 1; } + wait $PID_API && log "✓ API imagen construida" || err "Error construyendo API" + wait $PID_FE && log "✓ Frontend imagen construida" || err "Error construyendo Frontend" } -transfer_images() { - log "=== Transfiriendo imágenes a k3s ===" - - # Exportar y transferir en paralelo - docker save "$API_IMAGE" | ssh "$K3S_HOST" "sudo k3s ctr images import -" & - PID_API=$! - docker save "$FRONTEND_IMAGE" | ssh "$K3S_HOST" "sudo k3s ctr images import -" & - PID_FRONTEND=$! - - wait $PID_API && log "✓ API transferida" || { err "✗ Error transfiriendo API"; exit 1; } - wait $PID_FRONTEND && log "✓ Frontend transferida" || { err "✗ Error transfiriendo Frontend"; exit 1; } +import_images() { + log "=== Importando imágenes a K3s ===" + sudo sh -c "docker save $API_IMAGE | k3s ctr images import -" + sudo sh -c "docker save $FRONTEND_IMAGE | k3s ctr images import -" + log "✓ Imágenes importadas" } -deploy_k3s() { - log "=== Desplegando en k3s con Kustomize ===" +deploy_k8s() { + log "=== Desplegando en K3s ===" + cd "$SCRIPT_DIR" + sudo kubectl apply -k . + sudo kubectl rollout restart deployment/student-api deployment/student-frontend -n $NAMESPACE - # Aplicar con kustomize - ssh "$K3S_HOST" "cd /tmp && sudo kubectl apply -k -" < <(kubectl kustomize "$SCRIPT_DIR") + log "=== Esperando rollout ===" + sudo kubectl rollout status deployment/student-api -n $NAMESPACE --timeout=180s + sudo kubectl rollout status deployment/student-frontend -n $NAMESPACE --timeout=60s + log "✓ Deploy completado" +} - log "=== Esperando despliegues ===" - # Esperar SQL Server primero (es dependencia) - ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE rollout status deployment/sqlserver --timeout=180s" || warn "SQL Server timeout" +smoke_tests() { + log "=== Ejecutando smoke tests ===" + sleep 10 - # Esperar API y Frontend en paralelo - ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE rollout status deployment/student-api --timeout=120s" & - PID_API=$! - ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE rollout status deployment/student-frontend --timeout=60s" & - PID_FRONTEND=$! + # Health check + if curl -sf https://$DOMAIN/health | grep -q '"status":"Healthy"'; then + log "✓ Health check API" + else + err "Health check falló" + fi - wait $PID_API && log "✓ API desplegada" || warn "API timeout" - wait $PID_FRONTEND && log "✓ Frontend desplegado" || warn "Frontend timeout" + # Frontend + if curl -sf https://$DOMAIN/ | grep -q 'Sistema de Estudiantes'; then + log "✓ Frontend check" + else + err "Frontend check falló" + fi + + # GraphQL + if curl -sf -X POST https://$DOMAIN/graphql \ + -H "Content-Type: application/json" \ + -d '{"query":"{ subjects { id name } }"}' | grep -q '"subjects"'; then + log "✓ GraphQL check" + else + err "GraphQL check falló" + fi + + # Database + if curl -sf https://$DOMAIN/health | grep -q '"name":"database","status":"Healthy"'; then + log "✓ Database check" + else + err "Database check falló" + fi + + log "✓ Todos los smoke tests pasaron" } show_status() { - log "=== Estado del despliegue ===" - ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE get pods -o wide" + log "=== Estado del cluster ===" + sudo kubectl get pods -n $NAMESPACE -o wide echo "" - ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE get svc" - echo "" - ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE get ingress 2>/dev/null || true" + sudo kubectl get svc -n $NAMESPACE +} + +rollback() { + warn "=== Rollback ===" + sudo kubectl rollout undo deployment/student-api deployment/student-frontend -n $NAMESPACE + log "✓ Rollback completado" } clean() { warn "=== Limpiando namespace $NAMESPACE ===" - ssh "$K3S_HOST" "sudo kubectl delete namespace $NAMESPACE --ignore-not-found" + sudo kubectl delete namespace $NAMESPACE --ignore-not-found log "✓ Namespace eliminado" } -restart_pods() { - log "=== Reiniciando pods (rolling restart) ===" - ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE rollout restart deployment/student-api" - ssh "$K3S_HOST" "sudo kubectl -n $NAMESPACE rollout restart deployment/student-frontend" - log "✓ Pods reiniciados" -} - # Main -case "${1:-all}" in +case "${1:-help}" in + all) + pull_code + run_tests + build_images + import_images + deploy_k8s + smoke_tests + show_status + ;; + test) + run_tests + ;; build) build_images - ;; - transfer) - transfer_images + import_images ;; deploy) - deploy_k3s - show_status + deploy_k8s ;; - all) - build_images - transfer_images - deploy_k3s - show_status + smoke) + smoke_tests ;; status) show_status ;; + rollback) + rollback + ;; clean) clean ;; - restart) - restart_pods - ;; *) - echo "Uso: $0 [build|transfer|deploy|all|status|clean|restart]" - exit 1 + echo "Uso: $0 [all|test|build|deploy|smoke|status|rollback|clean]" + echo "" + echo " all - Pull, test, build, deploy y smoke tests" + echo " test - Solo ejecutar tests" + echo " build - Construir e importar imágenes" + echo " deploy - Solo desplegar a K8s" + echo " smoke - Solo ejecutar smoke tests" + echo " status - Ver estado del cluster" + echo " rollback - Revertir último deploy" + echo " clean - Eliminar namespace" + exit 0 ;; esac