Qué vas a conseguir
Que ningún script del equipo llegue a la rama principal sin haber pasado un análisis estático y sus pruebas, y que cuando alguien rompa uno, el canal de Teams lo diga con el nombre y el commit. El repositorio de la guía de Git del Nivel 2 deja de ser una carpeta compartida y pasa a ser código con integración continua.
Antes de empezar
- El servidor de herramientas con Docker (guía de Docker) y 4 GB de RAM libres.
- El repositorio de scripts en Git (GitHub, GitLab o Azure DevOps) con una cuenta de servicio de solo lectura o un token de acceso para Jenkins.
- Al menos un script de PowerShell con un test de Pester (
tests/*.Tests.ps1) y uno de Bash. - El webhook de Teams de la guía de Grafana (vale el mismo canal).
1. Jenkins en Docker
services: jenkins: image: jenkins/jenkins:2.479.1-lts-jdk17 container_name: jenkins restart: unless-stopped ports: ["8080:8080", "50000:50000"] volumes: - jenkins-home:/var/jenkins_home - /var/run/docker.sock:/var/run/docker.sock environment: JAVA_OPTS: -Djenkins.install.runSetupWizard=truevolumes: jenkins-home:docker compose up -d && sleep 45docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword3f9c1a2b7d4e4c88a9e0b1f2c3d4e5f6docker compose exec -u root jenkins sh -c "apt-get update -qq && apt-get install -y -qq docker.io >/dev/null && usermod -aG docker jenkins"Abre http://srv-tools-01:8080, pega la contraseña inicial, elige Install suggested plugins y crea el usuario administrador (contraseña en el gestor del equipo, no en la wiki). Añade después los plugins Docker Pipeline, Office 365 Connector (Teams) y Warnings Next Generation. Montar el socket de Docker permite que cada etapa corra en un contenedor limpio con las herramientas que necesite, sin instalarlas en Jenkins.
2. Conectar el repositorio
Multibranch crea un job por rama y por pull request automáticamente: la rama feature/inventario-v2 de la guía de Git se prueba sola en cuanto se sube. El webhook del repositorio hacia http://srv-tools-01:8080/github-webhook/ evita esperar los 15 minutos; si el servidor no es accesible desde internet, el sondeo periódico basta.
3. El Jenkinsfile
pipeline { agent none options { timestamps(); disableConcurrentBuilds() } stages { stage('Lint Bash') { agent { docker { image 'koalaman/shellcheck-alpine:v0.10.0' } } steps { sh 'find bash -name "*.sh" -print0 | xargs -0 shellcheck -f checkstyle > shellcheck.xml || true' recordIssues tool: checkStyle(pattern: 'shellcheck.xml', name: 'shellcheck'), qualityGates: [[threshold: 1, type: 'TOTAL_ERROR', unstable: false]] } } stage('Lint PowerShell') { agent { docker { image 'mcr.microsoft.com/powershell:7.4-ubuntu-22.04' } } steps { sh ''' pwsh -NoProfile -Command " Install-Module PSScriptAnalyzer -Force -Scope CurrentUser \$r = Invoke-ScriptAnalyzer -Path ./powershell -Recurse -Severity Error,Warning \$r | Format-Table -AutoSize if (\$r | Where-Object Severity -eq 'Error') { exit 1 }" ''' } } stage('Test PowerShell') { agent { docker { image 'mcr.microsoft.com/powershell:7.4-ubuntu-22.04' } } steps { sh ''' pwsh -NoProfile -Command " Install-Module Pester -Force -Scope CurrentUser -MinimumVersion 5.6 \$c = New-PesterConfiguration \$c.Run.Path = './tests'; \$c.Run.Exit = \$true \$c.TestResult.Enabled = \$true; \$c.TestResult.OutputPath = 'pester.xml'; \$c.TestResult.OutputFormat = 'JUnitXml' Invoke-Pester -Configuration \$c" ''' } post { always { junit 'pester.xml' } } } } post { failure { office365ConnectorSend webhookUrl: credentials('teams-it-ops-webhook'), message: "❌ ${env.JOB_NAME} #${env.BUILD_NUMBER} falló (${env.GIT_COMMIT.take(7)})", status: 'Failed' } fixed { office365ConnectorSend webhookUrl: credentials('teams-it-ops-webhook'), message: "✅ ${env.JOB_NAME} vuelve a estar en verde", status: 'Fixed' } }}Cada etapa corre en su imagen: shellcheck, PowerShell 7 con PSScriptAnalyzer, PowerShell 7 con Pester. recordIssues pinta las advertencias en Jenkins; el exit 1 en errores de PSScriptAnalyzer y Run.Exit de Pester son lo que hace que el pipeline falle de verdad. El webhook de Teams se guarda en Manage Jenkins → Credentials como texto secreto con el id teams-it-ops-webhook; nunca en el Jenkinsfile.
4. La primera ejecución y el primer fallo
[Pipeline] stage (Lint Bash)+ find bash -name *.sh -print0+ xargs -0 shellcheck -f checkstyle[shellcheck] Found 2 issues (1 error, 1 warning)[Pipeline] stage (Lint PowerShell)RuleName Severity ScriptName Line Message-------- -------- ---------- ---- -------PSAvoidUsingPlainTextForPassword Warning Get-Inventory.ps1 14 Parameter '$Password' should use SecureString[Pipeline] stage (Test PowerShell)Starting discovery in 1 files.Describing Get-Inventory [+] devuelve una fila por equipo 112ms [-] escribe el CSV con las columnas esperadas 38ms Expected 'LastBoot' to be found in collection @('Name','OS','FreeGB'), but it was not found.Tests completed in 1.21sTests Passed: 1, Failed: 1, Skipped: 0[Pipeline] junitRecording test resultsERROR: script returned exit code 1Finished: FAILUREEl pipeline hizo su trabajo: alguien cambió las columnas del CSV y el test lo cazó antes de que el script de inventario de la guía de PowerShell dejara de servir al informe mensual. En Teams aparece el mensaje con el commit. El arreglo es un commit en la rama, y Jenkins vuelve a probar solo.
Consejo: configura la rama main como protegida en el repositorio, con "require status checks" apuntando al job de Jenkins. Así un pull request no se puede fusionar en rojo, y la regla la pone el repositorio, no la buena voluntad.
5. Lo que revisas cada mes
- Jobs en rojo más de una semana: o se arreglan o se borran los scripts.
- Manage Jenkins → Plugins: actualizaciones de seguridad pendientes; Jenkins avisa con un banner.
- Espacio de
jenkins-homey la retención de builds (options { buildDiscarder(logRotator(numToKeepStr: '30')) }en el Jenkinsfile). - Credenciales que caducan (el token de Git, el webhook) y quién las conoce.
Siguientes pasos
- Una etapa de despliegue: cuando
mainesté en verde, copiar los scripts al recurso compartido desde el que los lanza la tarea programada, o lanzar el playbook de Ansible conansible-playbooken un contenedor. - Lo mismo con GitHub Actions o Azure Pipelines si el repositorio ya está allí: el Jenkinsfile se traduce casi línea a línea y te ahorras el servidor.
- Añade
Pestertambién a los scripts de Bash con bats-core: el patrón es el mismo.
¿Falta algo o en tu entorno es distinto? Escríbeme y lo corrijo.
Tech Career Lab · newsletter
¿Te ha servido? Cada dos semanas envío una idea práctica para perfiles IT: LinkedIn, proyectos, networking y procesos de selección.
Apúntate y llévate las 33 ideas →