26 KiB
26 KiB
CI/CD PIPELINE - ERP Generic
Última actualización: 2025-11-24 Responsable: DevOps Team / Tech Lead Estado: ✅ Production-Ready
TABLE OF CONTENTS
- Overview
- CI/CD Architecture
- GitHub Actions Workflows
- Automated Testing Integration
- Code Quality Gates
- Security Scanning
- Docker Build & Push
- Deployment Automation
- Rollback Automation
- 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:
# 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
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
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
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:
- 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:
- QA: Rolling update (zero-downtime)
- Staging: Blue-green deployment
- 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
Documento: CI-CD-PIPELINE.md Versión: 1.0 Total Páginas: ~16 Última Actualización: 2025-11-24