Integra Bash en tus procesos de integración y despliegue continuo para automatización robusta y escalable
Los scripts Bash son fundamentales en los pipelines CI/CD modernos, proporcionando flexibilidad, portabilidad y control granular sobre los procesos de construcción, pruebas y despliegue. En este módulo aprenderás a crear pipelines robustos y escalables.
Los scripts Bash son fundamentales en los pipelines CI/CD modernos, proporcionando flexibilidad, portabilidad y control granular sobre los procesos de construcción, pruebas y despliegue.
#!/bin/bash
# build.sh - Script de construcción inteligente
set -euo pipefail
# Configuración
PROJECT_NAME="${PROJECT_NAME:-$(basename "$(pwd)")}"
BUILD_ID="${BUILD_ID:-$(date +%Y%m%d_%H%M%S)}"
NODE_VERSION="${NODE_VERSION:-18}"
DOCKER_REGISTRY="${DOCKER_REGISTRY:-registry.company.com}"
SKIP_TESTS="${SKIP_TESTS:-false}"
PARALLEL_JOBS="${PARALLEL_JOBS:-$(nproc)}"
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging mejorado
log() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $*"
}
log_success() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] ✓${NC} $*"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ✗${NC} $*" >&2
}
log_warning() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] ⚠${NC} $*"
}
# Detección automática del tipo de proyecto
detect_project_type() {
if [[ -f "package.json" ]]; then
echo "node"
elif [[ -f "pom.xml" ]]; then
echo "maven"
elif [[ -f "build.gradle" ]]; then
echo "gradle"
elif [[ -f "Cargo.toml" ]]; then
echo "rust"
elif [[ -f "go.mod" ]]; then
echo "go"
elif [[ -f "requirements.txt" ]] || [[ -f "pyproject.toml" ]]; then
echo "python"
elif [[ -f "Dockerfile" ]]; then
echo "docker"
else
echo "unknown"
fi
}
# Preparación del entorno
prepare_environment() {
local project_type="$1"
log "Preparando entorno para proyecto $project_type"
case "$project_type" in
"node")
# Verificar versión de Node
if ! node --version | grep -q "v${NODE_VERSION}"; then
log_warning "Versión de Node diferente a la esperada (${NODE_VERSION})"
fi
# Cache de node_modules
if [[ -f "package-lock.json" ]]; then
npm ci --prefer-offline --no-audit
else
npm install
fi
;;
"python")
# Crear entorno virtual si no existe
if [[ ! -d "venv" ]]; then
python3 -m venv venv
fi
source venv/bin/activate
pip install -r requirements.txt
;;
"go")
go mod download
;;
"rust")
cargo fetch
;;
*)
log_warning "Tipo de proyecto no reconocido: $project_type"
;;
esac
}
# Construcción por tipo de proyecto
build_project() {
local project_type="$1"
log "Iniciando construcción para $project_type"
case "$project_type" in
"node")
# Linting
if npm run lint --if-present; then
log_success "Linting pasado"
else
log_error "Linting falló"
return 1
fi
# Build
if npm run build --if-present; then
log_success "Build completado"
else
log_error "Build falló"
return 1
fi
;;
"python")
source venv/bin/activate
# Linting con flake8
if command -v flake8 >/dev/null 2>&1; then
flake8 src/ --count --select=E9,F63,F7,F82 --show-source --statistics
fi
# Build del paquete
python setup.py build
;;
"go")
# Linting
if command -v golangci-lint >/dev/null 2>&1; then
golangci-lint run
fi
# Build con optimizaciones
CGO_ENABLED=0 go build -ldflags="-w -s" -o bin/ ./...
;;
"rust")
cargo build --release
;;
"docker")
# Build multi-stage optimizado
docker build \
--build-arg BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--build-arg VCS_REF="$(git rev-parse --short HEAD)" \
--build-arg VERSION="${BUILD_ID}" \
--tag "${DOCKER_REGISTRY}/${PROJECT_NAME}:${BUILD_ID}" \
--tag "${DOCKER_REGISTRY}/${PROJECT_NAME}:latest" \
.
;;
*)
log_error "No se sabe cómo construir proyecto tipo: $project_type"
return 1
;;
esac
}
# Función principal
main() {
local start_time=$(date +%s)
log "Iniciando build de ${PROJECT_NAME} (${BUILD_ID})"
# Detección automática del tipo de proyecto
local project_type=$(detect_project_type)
log "Tipo de proyecto detectado: $project_type"
# Pipeline de construcción
prepare_environment "$project_type" || { log_error "Falló preparación del entorno"; exit 1; }
build_project "$project_type" || { log_error "Falló la construcción"; exit 1; }
local end_time=$(date +%s)
local duration=$((end_time - start_time))
log_success "Build completado exitosamente en ${duration} segundos"
}
# Ejecutar si es llamado directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
#!/bin/bash
# deploy-pipeline.sh - Pipeline de despliegue multi-entorno
set -euo pipefail
# Configuración global
ENVIRONMENTS=("dev" "staging" "prod")
PROJECT_NAME="${PROJECT_NAME:-$(basename "$(pwd)")}"
DEPLOYMENT_STRATEGY="${DEPLOYMENT_STRATEGY:-rolling}"
SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"
# Configuración por entorno
declare -A ENV_CONFIG
ENV_CONFIG[dev]="namespace=development replicas=1 resources=small"
ENV_CONFIG[staging]="namespace=staging replicas=2 resources=medium"
ENV_CONFIG[prod]="namespace=production replicas=5 resources=large"
# Funciones de utilidad
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
send_notification() {
local message="$1"
local color="${2:-good}"
if [[ -n "$SLACK_WEBHOOK" ]]; then
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"$message\", \"color\":\"$color\"}" \
"$SLACK_WEBHOOK" || true
fi
log "$message"
}
# Validación de pre-despliegue
validate_deployment() {
local environment="$1"
local image_tag="$2"
log "Validando despliegue para $environment"
# Verificar que la imagen existe
if ! docker manifest inspect "$image_tag" >/dev/null 2>&1; then
log "ERROR: Imagen $image_tag no encontrada"
return 1
fi
# Verificar conectividad al cluster
if ! kubectl cluster-info --context="$environment" >/dev/null 2>&1; then
log "ERROR: No se puede conectar al cluster $environment"
return 1
fi
log "Validación exitosa para $environment"
return 0
}
# Estrategia de despliegue Rolling
deploy_rolling() {
local environment="$1"
local image_tag="$2"
local config="${ENV_CONFIG[$environment]}"
log "Iniciando despliegue rolling en $environment"
# Parsear configuración
local namespace=$(echo "$config" | grep -o 'namespace=[^ ]*' | cut -d'=' -f2)
local replicas=$(echo "$config" | grep -o 'replicas=[^ ]*' | cut -d'=' -f2)
# Crear namespace si no existe
kubectl create namespace "$namespace" --dry-run=client -o yaml | kubectl apply -f - --context="$environment"
# Aplicar deployment
cat << EOF | kubectl apply -f - --context="$environment"
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${PROJECT_NAME}
namespace: ${namespace}
labels:
app: ${PROJECT_NAME}
version: $(echo "$image_tag" | cut -d':' -f2)
spec:
replicas: ${replicas}
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 50%
maxUnavailable: 0
selector:
matchLabels:
app: ${PROJECT_NAME}
template:
metadata:
labels:
app: ${PROJECT_NAME}
version: $(echo "$image_tag" | cut -d':' -f2)
spec:
containers:
- name: ${PROJECT_NAME}
image: ${image_tag}
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
EOF
# Esperar que el despliegue se complete
log "Esperando completar despliegue..."
kubectl rollout status deployment/"$PROJECT_NAME" -n "$namespace" --context="$environment" --timeout=600s
log "Despliegue rolling completado exitosamente en $environment"
return 0
}
# Pipeline principal
main() {
local environment="$1"
local image_tag="$2"
local strategy="${DEPLOYMENT_STRATEGY}"
log "Iniciando pipeline de despliegue"
log "Entorno: $environment"
log "Imagen: $image_tag"
log "Estrategia: $strategy"
# Validar parámetros
if [[ ! " ${ENVIRONMENTS[*]} " =~ " ${environment} " ]]; then
log "ERROR: Entorno inválido: $environment"
log "Entornos disponibles: ${ENVIRONMENTS[*]}"
exit 1
fi
# Validación previa
if ! validate_deployment "$environment" "$image_tag"; then
send_notification "❌ Validación falló para $environment" "danger"
exit 1
fi
# Notificar inicio
send_notification "🚀 Iniciando despliegue en $environment con imagen $image_tag"
# Ejecutar estrategia de despliegue
if deploy_rolling "$environment" "$image_tag"; then
send_notification "✅ Despliegue exitoso en $environment" "good"
else
send_notification "❌ Despliegue falló en $environment" "danger"
exit 1
fi
}
# Ejecutar si es llamado directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
if [[ $# -lt 2 ]]; then
echo "Uso: $0 [strategy]"
echo "Environments: ${ENVIRONMENTS[*]}"
echo "Strategies: rolling, blue-green"
exit 1
fi
DEPLOYMENT_STRATEGY="${3:-$DEPLOYMENT_STRATEGY}"
main "$1" "$2"
fi
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Run build script
run: |
chmod +x ./scripts/build.sh
./scripts/build.sh
env:
PROJECT_NAME: ${{ github.repository }}
BUILD_ID: ${{ github.sha }}
NODE_VERSION: 18
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: artifacts/
deploy-staging:
needs: build
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment: staging
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure kubectl
run: |
echo "${{ secrets.KUBECONFIG_STAGING }}" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
- name: Deploy to staging
run: |
chmod +x ./scripts/deploy-pipeline.sh
./scripts/deploy-pipeline.sh staging ${{ needs.build.outputs.image-tag }}
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
deploy-production:
needs: [build, deploy-staging]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- name: Manual approval
uses: trstringer/manual-approval@v1
with:
secret: ${{ github.TOKEN }}
approvers: devops-team,tech-leads
- name: Deploy to production
run: |
chmod +x ./scripts/deploy-pipeline.sh
./scripts/deploy-pipeline.sh prod ${{ needs.build.outputs.image-tag }} blue-green
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
set -euo pipefail
para fallar rápidamente#!/bin/bash
# template-cicd-script.sh - Template seguro para scripts CI/CD
set -euo pipefail
IFS=$'\n\t'
# Configuración
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
readonly LOG_FILE="${LOG_FILE:-/dev/stdout}"
readonly DEBUG="${DEBUG:-false}"
# Variables requeridas
required_vars=(
"PROJECT_NAME"
"BUILD_ID"
"ENVIRONMENT"
)
# Logging
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
debug() {
if [[ "$DEBUG" == "true" ]]; then
log "DEBUG: $*"
fi
}
error() {
log "ERROR: $*" >&2
}
# Cleanup function
cleanup() {
local exit_code=$?
log "Cleaning up..."
# Cleanup temp files
rm -rf "${TMPDIR:-/tmp}/cicd-$$"
# Kill background processes
jobs -p | xargs -r kill 2>/dev/null || true
exit $exit_code
}
trap cleanup EXIT
# Validar variables requeridas
validate_environment() {
for var in "${required_vars[@]}"; do
if [[ -z "${!var:-}" ]]; then
error "Variable requerida no definida: $var"
exit 1
fi
done
}
# Función principal
main() {
log "Iniciando script CI/CD"
validate_environment
# Tu lógica aquí
log "Ejecutando lógica principal..."
log "Script completado exitosamente"
}
# Ejecutar solo si es llamado directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Crea un pipeline CI/CD completo que incluya:
Implementa un sistema de monitoreo que:
Desarrolla scripts para desplegar en múltiples clouds: