Introducción
Creo que es una realidad imperativa la relevancia de mantener actualizadas las versiones que utilizamos de librerías/dependencias externas en nuestros proyectos. Ya sea porque evitamos brechas de seguridad que se propagan desde ese factor externo hasta nuestro código o también incluso porque las pequeñas actualizaciones son más fáciles de incorporar. Cambiar algunas grandes versiones más adelante, junto con las complicaciones que esto conlleva, puede hacer que sea un auténtico sufrimiento tener que adaptar nuestro código a los nuevos mandatos de la dependencia externa, ya que han ocurrido cambios que rompen con lo que actualmente usábamos.
Partiendo de esta premisa, en la que no le estoy abriendo los ojos a nadie, me gustaría compartir una herramienta en la que he trabajado para solventar una necesidad que tenía en el día a día con mi equipo de trabajo y que puede ser útil para cualquier proyecto que busque una solución similar.
Todos nuestros proyectos están escritos en Java o Kotlin usando como framework Spring Boot y Gradle como herramientas de compilación. En Gradle las dependencias se definen con un archivo build.gradle, que puede tener una estructura como la siguiente:
plugins {
id 'org.springframework.boot' version '2.7.4'
id 'io.spring.dependency-management' version '1.0.14.RELEASE'
id 'java'
id "com.github.ben-manes.versions" version "0.43.0"
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2'
testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.mockito:mockito-core:4.5.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
}
tasks.named('test') {
useJUnitPlatform()
}
Problemática
Para mantener nuestras dependencias actualizadas recurrimos a un plugin que tanto por consola como en un fichero de
texto plano nos muestra las nuevas versiones a las que podemos actualizar las nuestras. Dicho plugin lo hemos visto en
el anterior bloque de código id "com.github.ben-manes.versions" version "0.43.0"
y este nos permite ejecutar el
comando desde la consola ./gradlew dependencyUpdates
.
Es aquí donde surge nuestro «problema», y es que como hemos visto, debemos puntualizar o precisar el lanzamiento de dicho comando en local para saber si tenemos que actualizar algo y a qué versión en concreto, por lo que depende de la buena memoria de los developers a la hora de lanzar esto periódicamente y realizar los cambios pertinentes. Es entonces cuando se me ocurre que quizá esto se pueda automatizar aprovechando el CI/CD, por lo que me puse a buscar soluciones ya existentes. Para mi asombro, fue realmente simple encontrarlas en el marketplace de Github Actions para este propósito, pero no terminaban de convencerme. Resulta que todo lo que encontré eran soluciones en las que automáticamente se abría una Pull Request en el proyecto con las dependencias actualizadas para que los developers puedan mergearla, e incluso otras soluciones eran auto-mergeable ¿Suena bien verdad? Ahora bien, planteándome la situación en profundidad descubrí que esto no terminaba de ajustarse a nuestra forma de trabajo, ya que implicaría una revisión constante, estar pendiente de la sección de PR en los proyectos, por lo que si contamos con un sistema de microservicios este razonamiento nos suponía que era inviable. En ocasiones, los grandes cambios entre versiones de las dependencias puede conllevar que nuestros test no pasen en verde, lo que requiere de un trabajo extra, así que tuve que descartar las soluciones auto-mergeable. Mi impresión era que nos sería más sencillo y flexible a todos decidir en las Pull Request que haríamos con las actualizaciones pendientes que teníamos, teniendo la posibilidad de no actualizar algunas porque harían que algunos test no pasaran en verde o por cualquier otro factor que nos pudiese suponer un bloqueo para sacar la Pull Request adelante en un momento en el que quizá no podíamos permitirnos dedicarle demasiado tiempo.
Solución
Una vez identificada la problemática, me gustaría contaros la resolución a la que llegué. Básicamente, he desarrollado una Github Action que he publicado en el Marketplace de Github, donde te comenta las PR que abras con las dependencias que necesitas actualizar, o en su defecto con un comentario de que todo está actualizado. El código fuente de la action lo podéis encontrar aquí. Os pongo también dos GIF sobre como funciona:
- Cuando tienes dependencias que actualizar:
- Cuando todas tus dependencias están actualizadas:
Un poco más de Github Actions
Además de compartir esta herramienta, me gustaría haceros una breve introducción para animaros a desarrollar vuestra propia Github Action alguna vez. Lo primero de todo es consultar la información más extendida en la documentación oficial de Github, sin embargo, trataré de resumir lo básico que necesité para crear la mía.
Existen tres formas de crear GitHub Actions:
- Usando Javascript
- Usando Docker
- Compuestas
En mi caso he optado por crear una acción que funciona dentro de un contenedor de Docker. El proyecto se compone de:
- Un archivo Dockerfile en el cual se define la imagen sobre la que se ejecutará el script bash que declaramos en el entrypoint.
- Un archivo de metadatos en el que se definen las entradas y salidas de la action así como otra información relevante para el marketplace de Github.
- El script de bash en el que se encuentra el core de nuestra action.
Conclusión
En un mundo ideal no debería ser necesario esta «manualidad» de tener que actualizar a mano las dependencias, pero hasta que conozca una solución cómoda y simple creo que esta forma de recordar que existen actualizaciones pendientes en las PR es una buena forma de no descuidar las dependencias de un proyecto Gradle.
Espero que este post haya sido claro y preciso, a la vez que útil e informativo. ¡Gracias por dedicar una parte de vuestro tiempo a la lectura de este artículo!