# CI/CD PIPELINE - ERP Generic **Última actualización:** 2025-11-24 **Responsable:** DevOps Team / Tech Lead **Estado:** ✅ Production-Ready --- ## TABLE OF CONTENTS 1. [Overview](#1-overview) 2. [CI/CD Architecture](#2-cicd-architecture) 3. [GitHub Actions Workflows](#3-github-actions-workflows) 4. [Automated Testing Integration](#4-automated-testing-integration) 5. [Code Quality Gates](#5-code-quality-gates) 6. [Security Scanning](#6-security-scanning) 7. [Docker Build & Push](#7-docker-build--push) 8. [Deployment Automation](#8-deployment-automation) 9. [Rollback Automation](#9-rollback-automation) 10. [Notifications](#10-notifications) --- ## 1. OVERVIEW ### 1.1 CI/CD Pipeline Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ Developer Workflow │ │ 1. git push origin feature/new-feature │ │ 2. Create Pull Request │ └────────────┬────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ CI Pipeline (GitHub Actions) │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ Lint │→ │ Build │→ │ Test │→ │Security│ │ │ │ │ │ │ │ (1.5k) │ │ Scan │ │ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │ ↓ ↓ ↓ ↓ │ │ ┌─────────────────────────────────────────────┐ │ │ │ Quality Gate (SonarQube) │ │ │ │ - Coverage >80% │ │ │ │ - No critical bugs │ │ │ └─────────────────────────────────────────────┘ │ └────────────┬────────────────────────────────────────────────┘ │ (If all checks pass) ▼ ┌─────────────────────────────────────────────────────────────┐ │ CD Pipeline (Deployment) │ │ │ │ develop branch → QA (Auto) │ │ main branch → Staging (Manual approval) │ │ release tags → Production (Manual approval) │ │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ Build │→ │ Push │→ │ Deploy │→ │ Verify │ │ │ │ Docker │ │ to │ │ to │ │ Health │ │ │ │ Image │ │Registry│ │ Server │ │ Checks │ │ │ └────────┘ └────────┘ └────────┘ └────────┘ │ └────────────┬────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Post-Deployment │ │ - Health checks │ │ - Smoke tests │ │ - Notifications (Slack/Discord) │ │ - Rollback if failure │ └─────────────────────────────────────────────────────────────┘ ``` ### 1.2 Pipeline Stages | Stage | Duration | Trigger | Blocking | |-------|----------|---------|----------| | **Lint** | 30s | Every push | Yes | | **Build** | 2-3 min | Every push | Yes | | **Unit Tests** | 1-2 min | Every push | Yes | | **Integration Tests** | 5-10 min | Every push | Yes | | **E2E Tests** | 15-30 min | Push to main/develop | No (parallel) | | **Security Scan** | 2-5 min | Every push | Yes (critical only) | | **Quality Gate** | 1 min | Every push | Yes | | **Docker Build** | 5-10 min | Merge to main/develop | No | | **Deploy QA** | 3-5 min | Merge to develop | No | | **Deploy Production** | 10-15 min | Release tag + approval | No | **Total CI time:** ~15-25 minutes (blocking) **Total CD time:** 3-15 minutes (per environment) --- ## 2. CI/CD ARCHITECTURE ### 2.1 Branching Strategy (GitFlow) ``` main (production) │ ├── release/v1.0.0 (staging) │ │ │ └── hotfix/critical-bug │ └── develop (QA) │ ├── feature/user-authentication ├── feature/sales-orders └── bugfix/fix-inventory-calc ``` **Branch Protection Rules:** ```yaml # main branch required_reviews: 2 require_code_owner_review: true dismiss_stale_reviews: true require_linear_history: true required_status_checks: - ci/lint - ci/build - ci/test - ci/security - ci/quality-gate # develop branch required_reviews: 1 require_code_owner_review: false required_status_checks: - ci/lint - ci/test ``` ### 2.2 Deployment Environments | Environment | Branch | Trigger | Approval Required | |-------------|--------|---------|-------------------| | **Development** | feature/* | Manual | No | | **QA** | develop | Automatic | No | | **Staging** | main | Manual | Yes (Tech Lead) | | **Production** | tags/v* | Manual | Yes (CTO + PO) | --- ## 3. GITHUB ACTIONS WORKFLOWS ### 3.1 CI Workflow (Main Pipeline) **File:** `.github/workflows/ci.yml` ```yaml name: CI Pipeline on: push: branches: [main, develop] pull_request: branches: [main, develop] env: NODE_VERSION: '20' PNPM_VERSION: '8' jobs: lint: name: Lint Code runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run ESLint run: npm run lint - name: Run Prettier check run: npm run format:check build: name: Build Application runs-on: ubuntu-latest timeout-minutes: 10 needs: lint steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Build backend run: npm run build --workspace=backend - name: Build frontend run: npm run build --workspace=frontend - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: build-artifacts path: | backend/dist frontend/dist retention-days: 7 test-unit: name: Unit Tests runs-on: ubuntu-latest timeout-minutes: 10 needs: build steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run unit tests (Backend) run: npm run test:unit --workspace=backend -- --coverage - name: Run unit tests (Frontend) run: npm run test:unit --workspace=frontend -- --coverage - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: files: ./backend/coverage/coverage-final.json,./frontend/coverage/coverage-final.json flags: unit-tests name: unit-tests-coverage test-integration: name: Integration Tests runs-on: ubuntu-latest timeout-minutes: 20 needs: build services: postgres: image: postgres:16-alpine env: POSTGRES_DB: test_db POSTGRES_USER: test_user POSTGRES_PASSWORD: test_password options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 redis: image: redis:7-alpine options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 6379:6379 steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run Prisma migrations run: npx prisma migrate deploy --workspace=backend env: DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db - name: Seed test data run: npm run seed:test --workspace=backend env: DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db - name: Run integration tests run: npm run test:integration --workspace=backend env: DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db REDIS_URL: redis://localhost:6379 - name: Upload test results if: always() uses: actions/upload-artifact@v3 with: name: integration-test-results path: backend/test-results/ retention-days: 7 test-e2e: name: E2E Tests runs-on: ubuntu-latest timeout-minutes: 30 needs: build steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Install Playwright run: npx playwright install --with-deps - name: Start services (Docker Compose) run: docker-compose -f docker-compose.test.yml up -d - name: Wait for services to be ready run: | timeout 60 bash -c 'until curl -f http://localhost:3000/health; do sleep 2; done' - name: Run E2E tests run: npm run test:e2e env: BASE_URL: http://localhost:5173 - name: Upload Playwright report if: always() uses: actions/upload-artifact@v3 with: name: playwright-report path: playwright-report/ retention-days: 30 - name: Stop services if: always() run: docker-compose -f docker-compose.test.yml down security-scan: name: Security Scanning runs-on: ubuntu-latest timeout-minutes: 10 needs: build steps: - uses: actions/checkout@v4 - name: Run Snyk security scan uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --severity-threshold=high --fail-on=all - name: Run OWASP Dependency Check uses: dependency-check/Dependency-Check_Action@main with: project: 'erp-generic' path: '.' format: 'HTML' args: > --failOnCVSS 7 --enableRetired - name: Upload dependency check report if: always() uses: actions/upload-artifact@v3 with: name: dependency-check-report path: reports/ retention-days: 30 quality-gate: name: Quality Gate (SonarQube) runs-on: ubuntu-latest timeout-minutes: 10 needs: [test-unit, test-integration] steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Full history for SonarQube - name: SonarQube Scan uses: sonarsource/sonarqube-scan-action@master env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} with: args: > -Dsonar.projectKey=erp-generic -Dsonar.qualitygate.wait=true - name: Check Quality Gate status run: | STATUS=$(curl -s -u ${{ secrets.SONAR_TOKEN }}: \ "${{ secrets.SONAR_HOST_URL }}/api/qualitygates/project_status?projectKey=erp-generic" \ | jq -r '.projectStatus.status') if [ "$STATUS" != "OK" ]; then echo "Quality Gate failed: $STATUS" exit 1 fi notify-success: name: Notify Success runs-on: ubuntu-latest needs: [lint, build, test-unit, test-integration, test-e2e, security-scan, quality-gate] if: success() steps: - name: Send Slack notification uses: slackapi/slack-github-action@v1 with: webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} payload: | { "text": "✅ CI Pipeline passed for ${{ github.repository }}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "*CI Pipeline Success* ✅\n*Repository:* ${{ github.repository }}\n*Branch:* ${{ github.ref_name }}\n*Commit:* ${{ github.sha }}\n*Author:* ${{ github.actor }}" } } ] } notify-failure: name: Notify Failure runs-on: ubuntu-latest needs: [lint, build, test-unit, test-integration, test-e2e, security-scan, quality-gate] if: failure() steps: - name: Send Slack notification uses: slackapi/slack-github-action@v1 with: webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} payload: | { "text": "❌ CI Pipeline failed for ${{ github.repository }}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "*CI Pipeline Failed* ❌\n*Repository:* ${{ github.repository }}\n*Branch:* ${{ github.ref_name }}\n*Commit:* ${{ github.sha }}\n*Author:* ${{ github.actor }}\n*Logs:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Logs>" } } ] } ``` ### 3.2 CD Workflow (Deployment to QA) **File:** `.github/workflows/cd-qa.yml` ```yaml name: CD - Deploy to QA on: push: branches: [develop] workflow_dispatch: # Manual trigger env: DOCKER_REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: deploy-qa: name: Deploy to QA Environment runs-on: ubuntu-latest timeout-minutes: 15 environment: name: qa url: https://qa.erp-generic.local steps: - uses: actions/checkout@v4 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ${{ env.DOCKER_REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Backend Docker image uses: docker/build-push-action@v5 with: context: ./backend file: ./backend/Dockerfile push: true tags: | ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-backend:qa ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-backend:qa-${{ github.sha }} cache-from: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-backend:qa cache-to: type=inline - name: Build and push Frontend Docker image uses: docker/build-push-action@v5 with: context: ./frontend file: ./frontend/Dockerfile push: true tags: | ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:qa ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:qa-${{ github.sha }} cache-from: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:qa cache-to: type=inline - name: Deploy to QA server uses: appleboy/ssh-action@v1.0.0 with: host: ${{ secrets.QA_SERVER_HOST }} username: ${{ secrets.QA_SERVER_USER }} key: ${{ secrets.QA_SERVER_SSH_KEY }} script: | cd /opt/erp-generic docker-compose pull docker-compose up -d --no-deps backend frontend docker-compose exec -T backend npx prisma migrate deploy docker image prune -f - name: Wait for services to be ready run: | timeout 120 bash -c 'until curl -f https://qa.erp-generic.local/health; do sleep 5; done' - name: Run smoke tests run: | curl -f https://qa.erp-generic.local/api/health || exit 1 # Add more smoke tests here - name: Notify deployment success uses: slackapi/slack-github-action@v1 with: webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} payload: | { "text": "✅ Deployed to QA: ${{ github.sha }}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "*Deployment to QA* ✅\n*Environment:* QA\n*Version:* ${{ github.sha }}\n*URL:* https://qa.erp-generic.local\n*Deployed by:* ${{ github.actor }}" } } ] } ``` ### 3.3 CD Workflow (Deployment to Production) **File:** `.github/workflows/cd-production.yml` ```yaml name: CD - Deploy to Production on: push: tags: - 'v*.*.*' workflow_dispatch: inputs: version: description: 'Version to deploy (e.g., v1.0.0)' required: true env: DOCKER_REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: deploy-production: name: Deploy to Production runs-on: ubuntu-latest timeout-minutes: 30 environment: name: production url: https://erp-generic.com steps: - uses: actions/checkout@v4 - name: Extract version id: version run: | if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT else echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT fi - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ${{ env.DOCKER_REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Backend Docker image uses: docker/build-push-action@v5 with: context: ./backend file: ./backend/Dockerfile push: true tags: | ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-backend:${{ steps.version.outputs.VERSION }} ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-backend:latest cache-from: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-backend:latest cache-to: type=inline - name: Build and push Frontend Docker image uses: docker/build-push-action@v5 with: context: ./frontend file: ./frontend/Dockerfile push: true tags: | ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:${{ steps.version.outputs.VERSION }} ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:latest cache-from: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:latest cache-to: type=inline - name: Create database backup uses: appleboy/ssh-action@v1.0.0 with: host: ${{ secrets.PROD_SERVER_HOST }} username: ${{ secrets.PROD_SERVER_USER }} key: ${{ secrets.PROD_SERVER_SSH_KEY }} script: | /opt/erp-generic/scripts/backup-postgres.sh - name: Deploy to Production server (Blue-Green) uses: appleboy/ssh-action@v1.0.0 with: host: ${{ secrets.PROD_SERVER_HOST }} username: ${{ secrets.PROD_SERVER_USER }} key: ${{ secrets.PROD_SERVER_SSH_KEY }} script: | cd /opt/erp-generic ./scripts/deploy-blue-green.sh production ${{ steps.version.outputs.VERSION }} - name: Verify deployment run: | timeout 300 bash -c 'until curl -f https://erp-generic.com/health; do sleep 10; done' - name: Run post-deployment tests run: | npm run test:smoke:production - name: Notify deployment success uses: slackapi/slack-github-action@v1 with: webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} payload: | { "text": "✅ Deployed to Production: ${{ steps.version.outputs.VERSION }}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "*Deployment to Production* ✅\n*Environment:* Production\n*Version:* ${{ steps.version.outputs.VERSION }}\n*URL:* https://erp-generic.com\n*Deployed by:* ${{ github.actor }}" } }, { "type": "section", "text": { "type": "mrkdwn", "text": "*Rollback command:*\n```./scripts/rollback.sh ${{ steps.version.outputs.VERSION }}```" } } ] } - name: Create GitHub Release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ steps.version.outputs.VERSION }} release_name: Release ${{ steps.version.outputs.VERSION }} body: | ## Changes in this release - Deployed to production on $(date) - Docker images: ${{ steps.version.outputs.VERSION }} ## Rollback If issues arise, run: `./scripts/rollback.sh ${{ steps.version.outputs.VERSION }}` draft: false prerelease: false ``` --- ## 4. AUTOMATED TESTING INTEGRATION **Test Execution Summary:** - **Unit Tests:** 876 tests (backend: 526, frontend: 350) - ~2 minutes - **Integration Tests:** 438 tests (API + DB + Components) - ~10 minutes - **E2E Tests:** 147 tests (Critical flows) - ~25 minutes - **Total:** 1,461 automated tests **Coverage Requirements:** - Line coverage: >80% - Branch coverage: >75% - Function coverage: >85% --- ## 5. CODE QUALITY GATES **SonarQube Quality Gate:** - **Coverage:** >80% - **Duplicated Lines:** <3% - **Code Smells:** <10 per 1000 LOC - **Bugs:** 0 critical, 0 major - **Vulnerabilities:** 0 critical, 0 major - **Security Hotspots:** Reviewed - **Maintainability Rating:** A or B - **Reliability Rating:** A - **Security Rating:** A --- ## 6. SECURITY SCANNING **Tools:** - **Snyk:** Dependency vulnerability scanning - **OWASP Dependency Check:** Known CVE scanning - **GitGuardian:** Secret scanning - **Trivy:** Container image scanning **Fail Criteria:** - Critical vulnerabilities: Block merge - High vulnerabilities: Warning (fail after 7 days) - Medium/Low vulnerabilities: Warning only --- ## 7. DOCKER BUILD & PUSH **Image Naming Convention:** ``` ghcr.io/your-org/erp-generic-backend:latest ghcr.io/your-org/erp-generic-backend:v1.0.0 ghcr.io/your-org/erp-generic-backend:qa ghcr.io/your-org/erp-generic-backend:qa-abc123 ``` **Image Scanning:** ```yaml - name: Scan Docker image uses: aquasecurity/trivy-action@master with: image-ref: ghcr.io/${{ github.repository }}-backend:latest format: 'sarif' output: 'trivy-results.sarif' ``` --- ## 8. DEPLOYMENT AUTOMATION **Deployment Strategies:** 1. **QA:** Rolling update (zero-downtime) 2. **Staging:** Blue-green deployment 3. **Production:** Blue-green with canary (10% → 50% → 100%) **Pre-Deployment Checklist:** - [ ] All tests passing - [ ] Security scans clean - [ ] Database backup created - [ ] Migration scripts reviewed - [ ] Rollback plan documented - [ ] On-call team notified --- ## 9. ROLLBACK AUTOMATION **Automated Rollback Triggers:** - Health check failures (>5 consecutive) - Error rate >5% for >5 minutes - p95 latency >2s for >5 minutes - Manual trigger via GitHub Actions --- ## 10. NOTIFICATIONS **Notification Channels:** - **Slack:** #erp-deployments (all deployments), #erp-alerts (failures) - **Email:** devops@erp-generic.com (production deployments) - **PagerDuty:** On-call engineer (production failures) --- ## REFERENCES - [GitHub Actions Documentation](https://docs.github.com/en/actions) - [Docker Build Best Practices](https://docs.docker.com/develop/dev-best-practices/) - [Deployment Guide](./DEPLOYMENT-GUIDE.md) --- **Documento:** CI-CD-PIPELINE.md **Versión:** 1.0 **Total Páginas:** ~16 **Última Actualización:** 2025-11-24