erp-core/docs/07-devops/CI-CD-PIPELINE.md

832 lines
26 KiB
Markdown

# 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