Análisis del Jenkinsfile - Eziuz/Proyecto-POLI-Generador-de-Claves GitHub Wiki

📋 Análisis Sección por Sección

1. 🔧 Declaración del Pipeline

pipeline {
    agent any
Elemento Descripción Propósito
pipeline Bloque principal del pipeline declarativo Define la estructura completa
agent any Ejecuta en cualquier agente disponible Flexibilidad de ejecución

¿Por qué agent any?

  • Simplicidad: No requiere agentes específicos
  • Flexibilidad: Funciona en cualquier nodo Jenkins
  • Escalabilidad: Permite distribución automática

2. 🌍 Variables de Entorno

environment {
    // Configuración de DockerHub
    DOCKERHUB_CREDENTIALS = credentials('dockerhub-credentials')
    DOCKERHUB_USERNAME = "${DOCKERHUB_CREDENTIALS_USR}"
    DOCKERHUB_REPO = 'generador-claves'

    // Etiquetas de la imagen con versionado semántico
    IMAGE_NAME = "${DOCKERHUB_USERNAME}/${DOCKERHUB_REPO}"
    GIT_COMMIT_SHORT = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
}

Análisis de Variables

Variable Valor Propósito
DOCKERHUB_CREDENTIALS credentials('dockerhub-credentials') Acceso seguro a credenciales
DOCKERHUB_USERNAME ${DOCKERHUB_CREDENTIALS_USR} Usuario extraído de credenciales
DOCKERHUB_REPO 'generador-claves' Nombre del repositorio en Docker Hub
IMAGE_NAME "${DOCKERHUB_USERNAME}/${DOCKERHUB_REPO}" Nombre completo de la imagen
GIT_COMMIT_SHORT sh(script: 'git rev-parse --short HEAD', ...) Hash corto del commit

🔒 Seguridad de Credenciales

DOCKERHUB_CREDENTIALS = credentials('dockerhub-credentials')

¿Cómo funciona?

  1. Jenkins busca credenciales con ID dockerhub-credentials

  2. Automáticamente crea variables:

  3. DOCKERHUB_CREDENTIALS_USR → Usuario

  4. DOCKERHUB_CREDENTIALS_PSW → Contraseña

  5. Las credenciales se enmascaran en los logs

Ventajas:

  • 🔐 Seguridad: Credenciales nunca expuestas en código
  • 🎭 Enmascaramiento: Automático en logs
  • 🔄 Reutilización: Disponible en todo el pipeline

3. 📥 Stage: Checkout

stage('Checkout') {
    steps {
        checkout scm
    }
}

¿Qué hace este stage?

Acción Descripción Resultado
checkout scm Clona el repositorio configurado Código disponible en workspace

Para repositorios privados:

  • ✅ Utiliza las credenciales de GitHub configuradas
  • ✅ Clona la rama especificada (*/main)
  • ✅ Descarga todo el historial necesario

Archivos Descargados

workspace/
├── Dockerfile          ← Para construcción de imagen
├── Jenkinsfile         ← Este mismo archivo
├── package.json        ← Información de versión
├── app/               ← Código de la aplicación
├── components/        ← Componentes React
├── public/            ← Archivos estáticos
└── ...

4. 🏷️ Stage: Determine Version

stage('Determine Version') {
    steps {
        script {
            // Intentar obtener la versión desde Git tags
            def gitTag = sh(
                script: "git describe --tags --exact-match HEAD 2>/dev/null || echo ''",
                returnStdout: true
            ).trim()

            if (gitTag && gitTag.startsWith('v')) {
                // Si hay un tag que empiece con 'v', usarlo como versión
                env.VERSION = gitTag.substring(1) // Remover la 'v' del inicio
                env.IS_RELEASE = 'true'
            } else {
                // Si no hay tag, usar versión desde package.json + build number
                def packageVersion = sh(
                    script: "node -p \"require('./package.json').version\"",
                    returnStdout: true
                ).trim()
                env.VERSION = "${packageVersion}-build.${BUILD_NUMBER}"
                env.IS_RELEASE = 'false'
            }

            echo "🏷️ Version determined: ${env.VERSION}"
            echo "📦 Is release: ${env.IS_RELEASE}"
        }
    }
}

🧠 Lógica de Versionado

Flujo de Decisión:

flowchart TD
    A[Check Git Tags] --> B{Tag en HEAD?}
    B -->|Si| C{Empieza con v?}
    B -->|No| D[Read package.json]
    C -->|Si| E[Use Tag Version]
    C -->|No| D
    D --> F[Add Build Number]
    E --> G[IS_RELEASE = true]
    F --> H[IS_RELEASE = false]

Ejemplos de Versionado

Escenario Git Tag package.json BUILD_NUMBER Resultado IS_RELEASE
Release oficial v1.2.3 1.2.3 42 1.2.3 true
Development build (ninguno) 1.2.3 42 1.2.3-build.42 false
Tag sin 'v' release-1.2.3 1.2.3 42 1.2.3-build.42 false

🔍 Comandos Utilizados

# Obtener tag exacto en HEAD (si existe)
git describe --tags --exact-match HEAD 2>/dev/null || echo ''

# Leer versión desde package.json
node -p "require('./package.json').version"

¿Por qué este enfoque?

  • 🎯 Flexibilidad: Soporta releases y development builds
  • 📋 Trazabilidad: Cada build tiene versión única
  • 🏷️ Semántico: Sigue convenciones de versionado semántico

5. 🏗️ Stage: Build Docker Image

stage('Build Docker Image') {
    steps {
        script {
            // Construir la imagen Docker con versionado semántico
            sh """
                docker build -t ${IMAGE_NAME}:${VERSION} \
                             -t ${IMAGE_NAME}:latest \
                             -t ${IMAGE_NAME}:${GIT_COMMIT_SHORT} .
            """
        }
    }
}

🏷️ Estrategia de Etiquetado

Cada build crea 3 tags:

Tag Ejemplo Propósito
Versión específica usuario/generador-claves:1.2.3 Versión exacta
Latest usuario/generador-claves:latest Última versión
Commit hash usuario/generador-claves:a1b2c3d Trazabilidad por commit

🔧 Comando Docker Build

docker build -t usuario/generador-claves:1.2.3 \
             -t usuario/generador-claves:latest \
             -t usuario/generador-claves:a1b2c3d .

Parámetros explicados:

  • -t: Asigna un tag a la imagen
  • .: Contexto de build (directorio actual)
  • \: Continuación de línea para legibilidad

📁 Contexto de Build

El comando utiliza el Dockerfile en la raíz del proyecto:

# Usar Node.js 20 LTS con Alpine como base
FROM node:20-alpine AS base

# Instalar dependencias solo cuando sea necesario
FROM base AS deps
# ... configuración de dependencias

# Construir la aplicación
FROM base AS builder
# ... proceso de build

# Imagen de producción
FROM node:20-alpine AS runner
# ... configuración final

6. ✅ Stage: Verify Image

stage('Verify Image') {
    steps {
        script {
            sh """
                echo "Verificando que la imagen existe..."
                docker images ${IMAGE_NAME}:${VERSION}

                echo "Verificando estructura de la imagen..."
                docker inspect ${IMAGE_NAME}:${VERSION} > /dev/null

                echo "Verificando configuración de la imagen..."
                docker inspect ${IMAGE_NAME}:${VERSION} | grep -E '"User"|"Entrypoint"|"Cmd"|"WorkingDir"' || true

                echo "Imagen verificada exitosamente"
            """
        }
    }
}

🔍 Verificaciones Realizadas

Verificación Comando Propósito
Existencia docker images ${IMAGE_NAME}:${VERSION} Confirma que la imagen se creó
Estructura docker inspect ${IMAGE_NAME}:${VERSION} Valida metadatos de la imagen
Configuración `grep -E '"User" "Entrypoint"...'`

📊 Salida Esperada

# Verificación de existencia
REPOSITORY                    TAG       IMAGE ID       CREATED         SIZE
usuario/generador-claves     1.2.3     a1b2c3d4e5f6   2 minutes ago   150MB

# Verificación de configuración
"User": "nextjs",
"Entrypoint": null,
"Cmd": ["node", "server.js"],
"WorkingDir": "/app"

🛡️ Verificaciones de Seguridad

¿Qué busca el grep?

  • "User": Confirma que no ejecuta como root
  • "Entrypoint": Verifica punto de entrada
  • "Cmd": Confirma comando de inicio
  • "WorkingDir": Valida directorio de trabajo

7. 📦 Stage: Push to DockerHub

stage('Push to DockerHub') {
    steps {
        script {
            // Iniciar sesión en DockerHub
            sh "echo ${DOCKERHUB_CREDENTIALS_PSW} | docker login -u ${DOCKERHUB_CREDENTIALS_USR} --password-stdin"

            // Publicar las imágenes en DockerHub
            sh """
                docker push ${IMAGE_NAME}:${VERSION}
                docker push ${IMAGE_NAME}:${GIT_COMMIT_SHORT}
            """

            // Solo pushear 'latest' si es un release oficial
            if (env.IS_RELEASE == 'true') {
                sh "docker push ${IMAGE_NAME}:latest"
                echo "✅ Released version ${VERSION} as latest"
            } else {
                echo "⚠️ Development build - not updating 'latest' tag"
            }

            // Cerrar sesión de DockerHub
            sh 'docker logout'
        }
    }
}

🔐 Proceso de Autenticación

# Login seguro usando stdin
echo $PASSWORD | docker login -u $USERNAME --password-stdin

Ventajas de --password-stdin:

  • 🔒 Seguridad: Password no aparece en historial de comandos
  • 🎭 Enmascaramiento: Jenkins oculta automáticamente la contraseña
  • 🛡️ Mejores prácticas: Recomendado por Docker

📤 Estrategia de Push

Siempre se publican:

  • ✅ Tag de versión específica (1.2.3)
  • ✅ Tag de commit hash (a1b2c3d)

Condicionalmente se publica:

  • 🔄 Tag latest (solo para releases oficiales)

🎯 Lógica de Latest Tag

flowchart TD
    A[Ready to Push] --> B{IS_RELEASE == true?}
    B -->|Si| C[Push latest tag]
    B -->|No| D[Skip latest tag]
    C --> E[Official Release]
    D --> F[Development Build]

¿Por qué esta estrategia?

  • 🎯 Control de calidad: Solo releases verificados como latest
  • 🔄 Estabilidad: Evita que builds de desarrollo sobrescriban latest
  • 📋 Trazabilidad: Siempre hay versiones específicas disponibles

8. 🧹 Post Actions

post {
    always {
        // Limpiar workspace y eliminar imágenes locales
        cleanWs()
        script {
            sh """
                # Obtener contenedores que usan nuestras imágenes
                CONTAINERS=\$(docker ps -aq --filter ancestor=${IMAGE_NAME} 2>/dev/null || echo "")
                if [ ! -z "\$CONTAINERS" ]; then
                    echo "Limpiando contenedores: \$CONTAINERS"
                    docker stop \$CONTAINERS || true
                    docker rm \$CONTAINERS || true
                else
                    echo "No hay contenedores que limpiar"
                fi

                # Eliminar las imágenes
                docker rmi ${IMAGE_NAME}:${VERSION} || true
                docker rmi ${IMAGE_NAME}:latest || true
                docker rmi ${IMAGE_NAME}:${GIT_COMMIT_SHORT} || true
                docker image prune -f || true
            """
        }
    }
    success {
        script {
            if (env.IS_RELEASE == 'true') {
                echo """
                🎉 ¡Release ${VERSION} publicado exitosamente!

                Para usar esta versión:
                docker pull ${IMAGE_NAME}:${VERSION}
                docker pull ${IMAGE_NAME}:latest
                docker run -d -p 3000:3000 ${IMAGE_NAME}:${VERSION}
                """
            } else {
                echo """
                ✅ ¡Build de desarrollo completado!

                Para usar esta versión:
                docker pull ${IMAGE_NAME}:${VERSION}
                docker run -d -p 3000:3000 ${IMAGE_NAME}:${VERSION}

                También disponible por commit:
                docker pull ${IMAGE_NAME}:${GIT_COMMIT_SHORT}
                """
            }
        }
    }
    failure {
        echo '❌ Error al construir o publicar la imagen'
    }
}

🧹 Limpieza Automática

Sección always (se ejecuta siempre):

Acción Comando Propósito
Limpiar workspace cleanWs() Libera espacio en disco
Parar contenedores docker stop $CONTAINERS Detiene contenedores activos
Eliminar contenedores docker rm $CONTAINERS Remueve contenedores
Eliminar imágenes docker rmi ${IMAGE_NAME}:* Libera espacio de imágenes
Limpiar imágenes huérfanas docker image prune -f Remueve imágenes sin usar

🎉 Notificaciones de Éxito

Para releases oficiales:

🎉 ¡Release 1.2.3 publicado exitosamente!

Para usar esta versión:
docker pull usuario/generador-claves:1.2.3
docker pull usuario/generador-claves:latest
docker run -d -p 3000:3000 usuario/generador-claves:1.2.3

Para builds de desarrollo:

✅ ¡Build de desarrollo completado!

Para usar esta versión:
docker pull usuario/generador-claves:1.2.3-build.42
docker run -d -p 3000:3000 usuario/generador-claves:1.2.3-build.42

También disponible por commit:
docker pull usuario/generador-claves:a1b2c3d

❌ Manejo de Errores

En caso de fallo:

  • 🚨 Mensaje de error claro
  • 🧹 Limpieza automática ejecutada
  • 📋 Logs disponibles para debugging

🔧 Características Avanzadas

1. 🛡️ Manejo de Errores

# Comandos con manejo de errores
docker stop \$CONTAINERS || true
docker rm \$CONTAINERS || true
docker rmi ${IMAGE_NAME}:${VERSION} || true

|| true significa:

  • ✅ Si el comando falla, continúa sin error
  • 🔄 Útil para limpieza (puede que no haya nada que limpiar)
  • 🛡️ Evita que el pipeline falle por limpieza

2. 🎭 Enmascaramiento de Credenciales

DOCKERHUB_CREDENTIALS = credentials('dockerhub-credentials')

Jenkins automáticamente:

  • 🎭 Enmascara DOCKERHUB_CREDENTIALS_PSW en logs
  • 🔒 Protege credenciales en salida de consola
  • 🛡️ Evita exposición accidental

3. 📊 Logging Estructurado

echo "🏷️ Version determined: ${env.VERSION}"
echo "📦 Is release: ${env.IS_RELEASE}"

Ventajas:

  • 🎯 Claridad: Emojis y formato consistente
  • 📋 Trazabilidad: Información clave visible
  • 🔍 Debugging: Fácil identificación de problemas

4. 🔄 Versionado Inteligente

Combina múltiples fuentes:

  • 🏷️ Git tags para releases oficiales
  • 📦 package.json para versión base
  • 🔢 BUILD_NUMBER para uniqueness

📊 Métricas y Monitoreo

Tiempos Esperados por Stage

Stage Tiempo Típico Factores que Afectan
Checkout 10-30s Tamaño del repositorio, velocidad de red
Determine Version 5-10s Complejidad de Git history
Build Docker Image 2-5 min Tamaño de dependencias, cache de Docker
Verify Image 10-30s Complejidad de verificaciones
Push to DockerHub 30s-2min Tamaño de imagen, velocidad de upload
Post Actions 10-30s Cantidad de recursos a limpiar

📈 Optimizaciones Implementadas

Optimización Beneficio Implementación
Multi-stage Dockerfile Imágenes más pequeñas FROM node:20-alpine AS base
Cache de dependencias Builds más rápidos Separación de layers en Docker
Limpieza automática Ahorro de espacio Post actions comprensivas
Versionado inteligente Mejor organización Lógica de tags condicional

🚨 Troubleshooting del Jenkinsfile

Error: "Version determination failed"

Síntomas:

ERROR: script returned exit code 1

Posibles causas:

  1. package.json no existe o está malformado
  2. ❌ Node.js no está disponible en el agente
  3. ❌ Permisos de Git insuficientes

Soluciones:

// Agregar validación
script {
    if (!fileExists('package.json')) {
        error "package.json not found"
    }
    
    def packageVersion = sh(
        script: "node -p \"require('./package.json').version\" 2>/dev/null || echo 'unknown'",
        returnStdout: true
    ).trim()
    
    if (packageVersion == 'unknown') {
        error "Could not read version from package.json"
    }
}

Error: "Docker build failed"

Síntomas:

ERROR: The command '/bin/sh -c yarn install --immutable' returned a non-zero code: 1

Posibles causas:

  1. ❌ Dockerfile tiene errores de sintaxis
  2. ❌ Dependencias no disponibles
  3. ❌ Problemas de red durante build

Soluciones:

// Agregar retry y mejor logging
script {
    retry(3) {
        sh """
            echo "Building Docker image (attempt \${env.BUILD_NUMBER})..."
            docker build --no-cache -t ${IMAGE_NAME}:${VERSION} .
        """
    }
}

Error: "Push to DockerHub failed"

Síntomas:

denied: requested access to the resource is denied

Posibles causas:

  1. ❌ Credenciales incorrectas o expiradas
  2. ❌ Repositorio no existe en Docker Hub
  3. ❌ Permisos insuficientes

Soluciones:

// Verificar login antes de push
script {
    sh """
        echo "Testing Docker Hub connection..."
        docker login -u ${DOCKERHUB_CREDENTIALS_USR} --password-stdin <<< ${DOCKERHUB_CREDENTIALS_PSW}
        
        echo "Verifying repository access..."
        docker pull hello-world
        
        echo "Pushing images..."
        docker push ${IMAGE_NAME}:${VERSION}
    """
}

🔧 Personalizaciones Comunes

1. Agregar Tests Automatizados

stage('Run Tests') {
    steps {
        script {
            sh """
                echo "Running unit tests..."
                docker run --rm ${IMAGE_NAME}:${VERSION} npm test
                
                echo "Running integration tests..."
                docker run --rm -p 3000:3000 -d --name test-container ${IMAGE_NAME}:${VERSION}
                sleep 10
                curl -f http://localhost:3000 || exit 1
                docker stop test-container
            """
        }
    }
}

2. Notificaciones Slack

post {
    success {
        slackSend(
            channel: '#deployments',
            color: 'good',
            message: "✅ Build ${env.BUILD_NUMBER} successful! Version: ${env.VERSION}"
        )
    }
    failure {
        slackSend(
            channel: '#deployments',
            color: 'danger',
            message: "❌ Build ${env.BUILD_NUMBER} failed! Check: ${env.BUILD_URL}"
        )
    }
}

3. Análisis de Seguridad

stage('Security Scan') {
    steps {
        script {
            sh """
                echo "Scanning image for vulnerabilities..."
                docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
                    aquasec/trivy image ${IMAGE_NAME}:${VERSION}
            """
        }
    }
}

📋 Checklist de Validación

Antes de usar el Jenkinsfile:

  • Credenciales configuradas (dockerhub-credentials)
  • Dockerfile presente en la raíz del proyecto
  • package.json válido con campo version
  • Docker disponible en el agente Jenkins
  • Permisos de Git configurados para repositorio privado

Durante la ejecución:

  • Checkout exitoso del repositorio privado
  • Versión determinada correctamente
  • Imagen construida sin errores
  • Verificaciones pasadas todas las validaciones
  • Push exitoso a Docker Hub

Después de la ejecución:

  • Imagen disponible en Docker Hub
  • Tags correctos aplicados
  • Limpieza completada en Jenkins
  • Logs claros y sin errores

🎯 Mejores Prácticas Implementadas

1. 🔒 Seguridad

  • Credenciales seguras: Uso de Jenkins credentials store
  • No hardcoding: Variables de entorno para configuración
  • Limpieza automática: Remoción de credenciales y recursos

2. 🔄 Mantenibilidad

  • Código declarativo: Pipeline fácil de leer y mantener
  • Logging estructurado: Mensajes claros con emojis
  • Manejo de errores: Graceful degradation con || true

3. 📊 Observabilidad

  • Versionado claro: Estrategia de tags comprensible
  • Métricas de tiempo: Stages bien definidos
  • Notificaciones informativas: Mensajes de éxito detallados

4. 🚀 Performance

  • Builds paralelos: Uso eficiente de recursos
  • Cache de Docker: Optimización de build times
  • Limpieza automática: Prevención de acumulación de recursos

📚 Referencias y Documentación

Jenkins Pipeline

Docker

Git