Cómo liberar/distribuir versiones de proyectos Maven+Java con submódulos Git en un entorno CI

El objetivo de este tutorial es explicar cómo hemos resuelto una serie de dificultades técnicas en el proceso de liberación y distribución de un proyecto Java cuya arquitectura está basada en submódulos GIT en un entorno genérico de integración continua.

Índice de contenidos

1. Introducción

Siempre hablando desde un punto de vista genérico, uno de los aspectos que no puede faltar en cualquier proyecto, normalmente Java en nuestro caso, es un entorno de integración continua (CI) que nos permita orquestar una serie de tareas/pasos sobre nuestro código fuente para conseguir un producto final.

De entre estos pasos, cabe destacar la importancia que tiene la liberación y distribución de nuevas versiones a medida que se va cerrando la funcionalidad desarrollada. Este paso, como la mayoría de vosotros sabéis, es el responsable de construir y empaquetar nuestra aplicación, la almacena y la etiqueta en nuestro repositorio de código y además la distribuye a través del repositorio de librerías que tengamos configurado para tal efecto.

Una manera muy habitual y sencilla de resolver está situación es hacer uso de Jenkins y la multitud de plugins a nuestra disposición, acompañado de la configuración de Maven necesaria a nivel de proyecto si fuese necesario. En este contexto, es también muy habitual que todo nuestro código fuente esté alojado en un sólo repositorio, independientemente de los módulos que lo conformen, pero ¿qué ocurre si cada uno de los módulos de nuestro proyecto es un repositorio Git independientemente?, ¿se comportarán igual nuestras herramientas en el entorno de CI a la hora de liberar nuevas versiones?, ¿será igual la configuración de nuestro proyecto y nuestro entorno de CI al trabajar con repositorios Git independientes en el proceso de liberación y distribución de nuevas versiones de nuestra aplicación?

La respuesta es “NO”, y por ese motivo os propongo una alternativa sencilla pero para la que habrá que tener en cuenta algunos detalles, si no queréis dedicar más tiempo de lo normal a preparar vuestro entorno de CI para liberar versiones de vuestros proyectos Maven+Java+Submódulos Git.

2. Entorno

El tutorial está escrito usando el siguiente entorno:
  • Hardware: Portátil MacBook Pro Retina 15′ (2.3 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS Sierra 10.12.5
  • Entorno de desarrollo: Eclipse Java EE IDE for Web Developers.Version: Neon.2 Release (4.6.2)
  • Maven 3.5.0
  • Wget 1.19.1
  • Docker for Mac
    • Docker 17.03.1-ce
    • Docker Machine 0.10.0
    • Docker Compose 1.11.2
  • Dockerización de herramientas
    • Gitlab CE
    • Jenkins 2.46.3
    • SonarQube 6.3
    • Nexus 3

3. El problema

El problema, siempre hablando en términos generales, es que la conjunción de plugins a nivel de Maven+Jenkins más utilizados para generar y distribuir las nuevas versiones de aplicaciones Java/Maven como Maven Release Plugin y M2 Release Plugin no saben gestionar, o al menos no saben manejar del todo, los distintos repositorios que conforman un proyecto en cuya arquitectura se esta trabajando con sub-módulos de GIT pero, ¿por qué?, ¿existe alguna diferencia entre en el flujo de liberación de versiones entre proyectos con un solo repositorio y aquellos que trabajan con múltiples repositorios?

En realidad, la diferencia no está en el flujo de liberación de versiones, sino en cómo gestionan este flujo las herramientas/plugins que utilizamos en el proceso. Es importante tener en cuenta que, cuando trabajamos con sub-módulos de GIT, cualquier cambio en cualquiera de los sub-módulos que conforman el proyecto, que a su vez son sub-módulos de GIT y que disponen de su repositorio independiente, genera cambios por defecto en “unstage” en el proyecto “padre/parent”, que también dispone de su propio repositorio de código y que también es necesario versionar.

En este contexto, el problema básicamente es que Maven Release Plugin, a pesar de ejecutar de manera completa el ciclo de vida de release para los sub-módulos, no interactúa con el repositorio de código del proyecto “padre/parent”, por lo que no se hacen los commit & push de los cambios realizados y se impide que la build de release finalice satisfactoriamente, lo que provoca que no se realice la distribución de la nueva versión de la aplicación.

Lamentablemente no podemos cambiar el comportamiento de ninguno de los actores involucrados (Git Submodule, Maven Release Plugin, M2 Release Plugin), al menos de manera sencilla, por lo que hemos tenido que buscar una solución alternativa que paso a explicar a continuación.

4. La solución

Bueno, sin duda la primera opción fue buscar algún plugin que realizase la gestión de los cambios del proyecto “padre/parent” por nosotros. Después de una primera investigación, lo cierto es que no encontramos ningún plugin que realmente se ajustase a las necesidades de una arquitectura donde se están gestionando múltiples repositorios de GIT para el mismo proyecto, aunque se trate de módulos distintos. Esto no significa que no existan, así que, si conocéis algún plugin que permita versionar una única aplicación que aloja el código fuente en distintos repositorios de código, por favor no dudéis en poner un comentario.

Mientras tanto, y dado que conocemos a la perfección cuál es el ciclo de vida de Maven a la hora de liberar una versión lo más rápido y sencillo es:

  • Escribir un shell script.

    Script que se encargará de ir ejecutando las distintas acciones necesarias para liberar y distribuir las nuevas versiones.

  • Cambio de Maven Release Plugin por Maven Versions Plugin más Maven Help Plugin en Maven.

    Plugins sobre los que se apoyará el proceso de liberación de versión para realizar los cambios de las versiones de desarrollo por la releases y de los cambios de la release por la nueva versión de desarrollo.

  • Cambio de M2 Release Plugin por Release Plugin en Jenkins.

    Mientras M2 Release Plugin nos permite únicamente ejecutar comandos de Maven, Release plugin es más completo, permite realizar una mayor configuración. Por ejemplo, nos permitirá ejecutar el shell script encargado del proceso de liberación y distribución de versiones.

4.1 Shell Script

Por favor, tened en cuenta que esto es solo un ejemplo basado en un proyecto con la siguiente estructura:



Podemos observar que el proyecto está conformado por 3 proyectos Maven, un parent con dos módulos que se gestionarán con esta jerarquía en el repositorio Maven. Sin embargo, cada uno de estos proyectos Maven se gestionan a nivel de repositorio de código de manera independiente, haciendo uso de los submódulos de GIT.

Como muchos de vosotros ya sabréis, el ciclo de vida del plugin Maven Release Plugin es:

  • Se modifican los pom.xml de cada proyecto con la versión a liberar.
  • Se hace commit & push con los cambios de la nueva versión.
  • Se etiqueta esta versión en el repositorio.
  • Se hace un deploy de la versión liberada que distribuirá la releases en el almacén de librerías configurado.
  • Se modifican los pom.xml de cada proyecto con la versión SNAPSHOT que corresponda para seguir desarrollando.
  • Se hace commit & push con los cambios de la nueva versión de desarrollo.

Por tanto, el camino a seguir es claro, necesitamos un shell script que realice todo el proceso teniendo en cuenta que cada módulo esta alojado en su repositorio de manera independiente.

Una buena aproximación podría ser:

Si realizáis una primera lectura creo que no tendréis ningún problema en ver que se está haciendo:

  • Cambio de todos los pom.xml del proyecto.
  • Nos apoyamos en Maven Version Plugin para realizar está acción. Destaca que en el comando de Maven utilizado para tal efecto se hace uso del parámetro -DnewVersion=$1. El valor del parámetro será sustituido por el valor que incluyamos para la nueva versión desde Jenkins a través de Releases Plugin.

    Adicionalmente, en este primer paso del proceso nos apoyamos en Maven Help Plugin para recuperar la versión que acaba de ser incluida en los pom.xml, ya que nos servirá posteriormente para etiquetar nuestra releases.

  • Se hace commit & push & tag de los cambios para cada sub-módulo del proyecto.
  • En este paso del proceso hay dos acciones muy importantes que os pueden dar un quebradero de cabeza.

    • git branch -f master HEAD
    • git checkout master

    La primera acción fuerza a que el HEAD del repositorio local de GIT apunte contra la rama master. Por norma general, trabajando con un único repositorio, la referencia del puntero HEAD siempre es a la rama master. Sin embargo, al trabajar con submódulos de GIT esto no siempre ocurre y si se pierde la referencia estaríamos realizando las posteriores acciones sobre el HEAD y no sobre la rama master.

  • Se hace commit & push & tag de los cambios para proyecto padre.

  • Se hace deploy para todos los proyectos.

    Se encarga de distribuir la nueva versión de cada artefacto en el repositorio de librerías configurado para tal efecto. Por ejemplo, Nexus es una muy buena opción para distribuir las nuevas versiones de nuestras aplicaciones.

A partir de aquí, el proceso es muy similar solo que el resultado será que todos los módulos de nuestro proyecto incluirán la nueva versión SnapShot de desarrollo después de liberar la release.

4.2 Cambio Plugin en Maven

Este apartado, la verdad, no tiene mucho misterio. Anteriormente, y casi de manera tradicional casi siempre he utilizado Maven Release Plugin. Sin embargo y dado que ahora será el shell script el encargado de gestionar el proceso de liberación y distribución de versión no es necesario este plugin y sí hacer uso de Maven Version Plugin que, como hemos podido comprobar, es sobre el que se apoya el script para realizar todos los cambios de versión en todos los proyectos/módulos de la aplicación.

4.3 Cambio Plugin en Jenkins

Del mismo modo que el apartado anterior, la sustitución en Jenkins de un plugin por otro no tiene mucho misterio. Por tanto, una vez instalado el Release Plugin solo será necesario ir a la configuración de nuestro job, al apartado “Entorno de ejecución”, click sobre el check “Configure release build”. En este punto incluimos los parámetros necesarios que viajarán desde el inicio de liberación de versión hasta el shell script encargado del proceso.

Adicionalmente, añadimos un paso posterior a la ejecución de la build en el apartado “After successful release build” que se encargará de lanzar el script con las parámetros que correspondan, como se puede apreciar en la siguiente imagen.

Esta acción, que lanzará la ejecución del shell script, se iniciará cuando desde el backend del plugin se lance un nuevo proceso de liberación de versión. En este punto se solicita la nueva versión y la siguiente versión de desarrollo y el valor que se introduzca será utilizado por el shell script encargado del proceso.

5. Conclusiones

Bueno, mi conclusión respecto a este tema es bastante clara, si no es absolutamente imprescindible o no es una imposición a nivel de proyecto, no dispongáis vuestra aplicaciones de modo que cada módulo este alojado en un repositorio de código distinto. La verdad es que no he encontrado ninguna ventaja trabajando bajo este contexto, al revés, todo han sido inconvenientes, y no solo a la hora de liberar y distribuir versiones sino también en la gestión de cambios en el día a día, que se hace mucho menos sencilla.

Al final, la independencia de repositorios modifica el proceso habitual de algunos procesos habituales en el desarrollo y, la verdad, es que esto es algo con lo que no estoy muy de acuerdo si no es para agilizar estos procesos.

Espero que os sirva de utilidad.

Un cordial saludo