Índice de contenidos
- 1. Introducción
- 2. Entorno
- 3. Crear una shared library en Jenkins
- 4. Creando nuestro Jenkinsfile
- 5. Conclusiones
- 6. Referencias
1. Introducción
DevOps ha venido para quedarse. Y con ello todas las tareas de administración asociadas. Cada día aparecen nuevas herramientas o se potencian las existentes, pero un fiel compañero siempre ha sido Jenkins. Hoy vamos a comprobar cómo podemos aplicar el principio DRY (don’t repeat yourself) a nuestros jobs de Jenkins.
Digamos que tenemos nuestras tareas de administración automatizadas y nos damos cuenta de que estamos empezando a repetir un patrón de actuación: preparar entorno, ejecutar acción, validar estado.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 17′ (2’5 Ghz i7, 16GB DDR3).
- Sistema Operativo: Mac OS High Sierra 10.13
- Entorno de desarrollo: JetBrains IntelliJ
- Jenkins 2.60.3 con los plugins de pipeline instalados
3. Crear una shared library en Jenkins
3.1. Definiendo el pipeline común
Lo primero que necesitamos a la hora de definir una librería es conocer qué queremos hacer. En nuestro caso vamos a definir un pipeline muy sencillo que únicamente haga un echo de los parámetros recibidos.
TestPipeline.goovy
import org.jenkinsci.plugins.* String parm1 Boolean parm2 String parm3 TestPipeline withParm1(String parm1) { this.parm1 = parm1 return this } TestPipeline withParm2(Boolean parm2) { this.parm2 = parm2 return this } TestPipeline withParm3(String parm3) { this.parm3 = parm3 return this } def execute() { pipeline { stage("Setup environment") { echo "Parameter: ${parm1}" } stage("Should execute action: ${parm2}") { if (parm2) { echo "Executing action as stated by parm2: ${parm2}" } } stage("Verify state") { echo "Executing ${parm3}" } } } return this
Es muy importante que la última sentencia del fichero sea return this. Si no, Jenkins no será capaz de cargar correctamente el pipeline y nos fallará.
Para hacer uso de las librerías, estas se deben importar desde un sistema de control de versiones (GitLab, GitHub, etc). Además, el repositorio debe seguir una estructura determinada. Queda bastante bien explicado en la página oficial de documentación:
(root) +- src # Groovy source files | +- org | +- foo | +- Bar.groovy # for org.foo.Bar class +- vars | +- foo.groovy # for global 'foo' variable | +- foo.txt # help for 'foo' variable +- resources # resource files (external libraries only) | +- org | +- foo | +- bar.json # static helper data for org.foo.Bar
Esta estructura nos da la potencia suficiente como para organizar nuestras librerías siguiendo una jerarquía que permita reutilización. Por ejemplo, podemos definir una serie de métodos dentro de una clase Maven.groovy que nos faciliten las tareas de compilación, generación de release, análisis con SonarQube, etc. De esta forma, cualquier pipeline que quiera utilizarlas podrá hacerlo sin tener que definir todas las opciones necesarias y sólo las imprescindibles.
3.2. Definir la shared library en Jenkins
Para definirla de forma efectiva en Jenkins, nos dirigiremos a la sección de administración, configurar el sistema y a la sección Global Pipeline Libraries. Añadiremos nuestra ruta al repositorio Git y le daremos un nombre por el que la identificaremos. Es importante definir como versión por defecto master, ya que como veremos después se puede sobreescribir, pero lo lógico es que sea master la que nos interese siempre.
4. Creando nuestro Jenkinsfile
Tomemos como ejemplo un Jenkinsfile mínimo:
Jenkinsfile
#!/usr/bin/env groovy @Library("Test") _ import com.autentia.test.TestPipeline new TestPipeline() .withParm1(params.environment) .withParm2(params.dryRun) .withParm3(params.verifier) .execute()
Con la directiva @Library estamos importando la librería definida con ese nombre. El guión bajo (_) hace referencia a la versión, en este caso a la por defecto, ¿recordáis haberla indicado como master?, pues era para simplificar este punto. Además, si tenemos en cuenta que cada vez que hacemos referencia a params son parámetros del Job de Jenkins, acabamos de construir un Jenkinsfile genérico para todos nuestros Jobs. Lo único que tendrán que hacer es fijar los parámetros necesarios, pero el flujo de ejecución será común para todos. ¿No es más sencillo de mantener? ¿Y de entender? ¿Y de mantener ;)?
5. Conclusiones
Con un poco de cariño podemos empezar a construir una serie de librerías con funcionalidad común que nos faciliten la vida, todo manteniendo una coherencia entre los jobs que lo utilicen. El mantenimiento de las librerías está centralizado, por lo que será más sencillo y tendremos menos errores. Una de las grandes ventajas que tienen es que al actualizar el código de la librería, todo job que la utilice refrescará inmediatamente los cambios.
Estamos acostumbrados a aplicar patrones y principios de diseño a nuestro código, y cuando nos enfrentamos a un pipeline descuidamos lo aprendido. No nos olvidemos que tenemos que aplicar el mismo mimo al ciclo de vida.
6. Referencias
- Extending Jenkins with shared libraries. Jenkins official documentation.
- StackOverflow: define Jenkins pipeline on an external file. Contestación parcial y origen de este tutorial.