Libertya CI/CD

Last modified by admin on 2026/07/18 17:44

Pestaña 1

Libertya CI/CD

Análisis e implementación de mejoras en la metodología de desarrollo Libertya mediante CI/CD y testing automation.

image1.png
image2.png

Índice

Objetivo

El objetivo de este documento es detallar el análisis, tareas realizadas y proporcionar información útil acerca de la implementación de automatización en la metodología de desarrollo de Libertya. La idea es apuntar a un pipeline CI/CD que ayude a automatizar diferentes etapas del proceso, así como también incorporar el uso de tests. Para lograr llevar adelante estas mejoras, se propone la utilización de Jenkins https://www.jenkins.io/

Acceso a Jenkins

Se configuró Jenkins para acceder por medio de tunel ssh en el puerto 9090.

Conectarse de esta forma:

ssh -p 4565 usuario@138.219.43.229 -L 9090:localhost:9090

Luego acceder desde el navegador con localhost:9090

  • Username: jenkins
  • Password: Bangla…

image3.png

Acceso a SonarQube (deshabilitado)

Se configuró SonarQube para acceder por medio de tunel ssh en el puerto 9000.

Acceder desde el navegador con localhost:9000

  • Username: admin
  • Password: Putra… 424 (con espacio)

token (utilizado por jenkins): squ_6999e9c29d865ed3233d47cb2ec31fb9904f06f2

image4.png

Información de la instancia

Nociones generales

Todo lo relacionado a Jenkins se encuentra ubicado en el servidor de desarrollo en el directorio /home/jenkins.

Se encuentra archivo el docker-compose.yml

version: '3.8' services:   jenkins:     image~: jenkins/jenkins-custom:latest     container_name: jenkins     restart: unless-stopped     user: root     privileged: true     network_mode: host     volumes:       - jenkins_home:/var/jenkins_home       - /var/run/docker.sock:/var/run/docker.sock       - /home/jenkins/reportes:/var/reportes     environment:       - JAVA_OPTS=-Djenkins.install.runSetupWizard=true       - JENKINS_OPTS=--httpPort=9090   postgres:     image~: postgres:10.10     container_name: libertya-postgres     restart: unless-stopped     network_mode: host     environment:       POSTGRES_DB: libertya_test       POSTGRES_USER: libertya       POSTGRES_PASSWORD: libertya       PGPORT: 5434     volumes:       -
./init-db:/docker-entrypoint-initdb.d       -
./pgdata:/var/lib/postgresql/data
#  sonarqube:
#    image~: sonarqube:latest
#    container_name: sonarqube
#    restart: unless-stopped
#    network_mode: host
#    ports:
#      - "9000:9000"
#    volumes:
#      - sonarqube_data:/opt/sonarqube/data
#      - sonarqube_extensions:/opt/sonarqube/extensions
#      - sonarqube_logs:/opt/sonarqube/logs volumes:   jenkins_home:
#  sonarqube_data:
#  sonarqube_extensions:
#  sonarqube_logs:

Existen 2 contenedores principales, el de jenkins y el de postgres utilizado para almacenar las bases de test de los pipelines.

Notar que se creó una imagen custom de jenkins como solución, ya que fue necesario instalar ciertas herramientas y al reiniciar el contenedor esos cambios se perderían. Actualmente esas herramientas son las siguientes:

Instalar jdk11 y zip

echo “deb http://deb.debian.org/debian bullseye main” >> /etc/apt/sources.list apt update apt install -y openjdk-11-jdk apt install -y zip

Instalar jdk8 (desde servidor)

cd /home/jenkins docker
cp jdk1.8.0_121.zip jenkins:/opt docker exec -it jenkins bash
cd /opt
unzip jdk1.8.0_121.zip

Cómo reiniciar el servicio?

Para reiniciar tanto Jenkins como el Postgres asociado, acceder como root y luego ejecutar lo siguiente:

#Acceder a la ubicación
cd /home/jenkins #Detener los contenedores docker compose down  #Levantar los contenedores en modo detach docker compose up -d

Pipelines

Actualmente existen 2 pipelines principales.

Libertya-Core es el pipeline productivo para Libertya Core. Clona los repositorios de Libertya y lyrestapi, compila, configura y ejecuta el test-set. Finalmente envía un mail con los reportes generados con los tests automáticos.

Libertya-Core-Sandbox es un pipeline copia de Libertya-Core pensado para implementar nuevas funcionalidades y hacer pruebas en el pipeline antes de pisar el pipeline productivo. Es un pipeline de “juguete” que en principio se comporta igual que el productivo pero no envía mails, no pisa los reportes ni ninguna otra acción que pueda repercutir.

image5.png

Diseño de pipelines

Pipeline exploratorio para Libertya-Core

🚀 Desencadenante

  • Se realiza un push a master del repo de Libertya Core en GitHub.
  • Jenkins detecta el cambio y dispara el pipeline automáticamente.

🏗️ Flujo completo

      1. Preparación del entorno
  • Se configura el entorno con JAVA 11, paths internos (OXP_HOME, WORKDIR, REPORTS_DIR, etc.), y credenciales de DB.
  • Se definen variables de entorno necesarias para compilar Libertya y ejecutar los tests.

2. Clonar y compilar Libertya Core

  • Se clona el repo de Libertya desde GitHub (branch: master).
  • Se obtiene el commit hash actual (LIBERTYA_COMMIT).
  • Se le dan permisos de ejecución a los scripts .sh.
  • Se ejecuta el script utils_dev/Compilar.sh para compilar el sistema.

3. Generación de Keystore

  • Se genera un keystore Java (myKeystore) si no existe, necesario para la ejecución de ConfigurarAuto.sh

4. Configuración de Libertya

  • Se copia el archivo LibertyaEnv.properties desde el Config File Provider de Jenkins.
  • Se le da permiso de ejecución a todos los scripts.
  • Se ejecuta ConfigurarAuto.sh para inicializar la instalación.

5. Limpieza del jar OXPXLib

  • Se eliminan clases conflictivas (org.slf4j.impl.*) y metainformación innecesaria del .jar (META-INF/INDEX.LIST).

6. Clonar LYRestAPI

  • Se clona el repo de lyrestapi desde GitHub (branch: main).
  • Se guarda el commit hash (LYRESTAPI_COMMIT).

7. Ejecución de Tests

  • Se usa Java 8 (temporal downgrade) para ejecutar los tests.
  • Se corre ./gradlew clean test --info
    desde el directorio lyrestapi.

🧾 Post build (siempre se ejecuta)

Si los tests pasan o fallan:

📧 Se envía un email HTML con:

  • Estado del build (SUCCESS o FAILURE)
  • Fecha y hora
  • Commit de Libertya
  • Commit de LYRestAPI
  • Link al reporte de tests

Pipelines para ramas dev y master

✅ 2 Pipelines separados en Jenkins: uno para dev y otro para master

  1. 🛠️ Pipeline dev (Integración continua)
  • Trigger: push en dev
  • Qué hace:
    • Compila
    • Ejecuta tests automáticos
    • despliega en entorno de testing manual
  • Resultado esperado: Confirmar que lo nuevo no rompe nada. Si falla, no se hace el PR a master.

🎯 Este es el filtro de calidad previo.


  1. 🚀 Pipeline master (Despliegue continuo)
  • Trigger: merge a master (después del PR aprobado desde dev)
  • Qué hace:
    • Repite la build
    • Corre los tests automáticos nuevamente
    • Hace deploy del release (release server, prod, etc.)
    • (Opcional) versiona, taguea, sube binarios, etc.

🎯 Este pipeline representa la versión oficial y estable.


🧠 ¿Por qué dos pipelines?

Porque la intención y los riesgos de cada rama son distintos:

Rama Pipeline Objetivo Despliegue
dev CI Validar cambios antes del merge Staging (opcional)
master CD Versión estable para producción Producción

Automatización de otros proyectos y consideración de metadata

Otros proyectos van a requerir sus propios pipelines, como LYEI o LocaleAR. Estos a su vez deberían ser tenidos en cuenta para integrarse en el deploy automático a la instancia de testing manual. En caso de exportar un release de estos proyectos, se incluiría tanto binarios como metadata completa, pero para exportar a la instancia de testing manual, se tienen que ajustar algunos parámetros ya que la metadata se instala de forma incremental. Esto impacta directamente en los ID de changelogs a considerar en cada incremento.

Opcion 1: parametrizar el archivo devinfo.properties dentro del pipeline.

Se podría utilizar sed, awk, envsubst o alguna herramienta similar que pise valores específicos, por ejemplo, el ExportChangelogFromID Para ajustar la versión a instalar en el entorno de pruebas. Al ser incremental, la base de datos de test ya tendría la metadata previa, entonces habría que parametrizar los changelogs a incluir.

IMPORTANTE: Para realizar esto, es necesario sanitizar el preinstall de LYEI. Actualmente tiene instrucciones del tipo “CREATE TABLE” que dan error en la instalación si esas tablas ya fueron creadas.

Opcion 2: Modificar clases de Libertya para lograr cierta flexibilidad a la hora de realizar un export o install. Por ejemplo, modificar la clase ExportPlugin para que permita tomar variables de entorno con prioridad en lugar de tomar únicamente los valores por defecto de devinfo.properties. También se podría investigar si es posible modificar el PluginInstaller.sh (y clases java asociadas) para instalar un componente de forma más flexible. Por ejemplo instalar los changelogs faltantes de la versión actual e ignorar los que ya fueron instalados, en lugar de tener que exportar el rango exacto a instalar.

Cambios en metodología de Desarrollo – Flujo de ramas y automatización

Ramas

dev

  • Rama libre de trabajo diario
  • Todos los desarrolladores pueden hacer push directo a esta rama
  • Cada push dispara el pipeline CI/CD (build + tests automáticos).

master

  • Rama estable y protegida.
  • No se puede pushear directamente.
  • Solo se actualiza vía Pull Request (PR) desde dev.
  • Representa siempre el estado listo para producción/release.

Flujo de trabajo

      1. Trabajo diario
  • Cada dev hace sus cambios y pushea a la rama dev (directamente o mediante branches personales, si lo prefiere).
  • El pipeline de Jenkins corre automáticamente con cada cambio en dev:
    • Compila el proyecto.
    • Ejecuta todos los tests automáticos.
    • Al finalizar, envía un email a todos los desarrolladores con los resultados (éxito o errores de los tests).

Ejemplo de notificación implementada:

image6.png

2. Preparación para nuevo release

Cuando dev está estable y todos los tests pasan, cualquier integrante puede proponer un Pull Request (PR) de dev hacia master desde la web de GitHub.

El pipeline podría volverse a ejecutar sobre master después del merge para asegurar estabilidad final.

3. Generación y distribución del release

  • Una vez que el PR se aprueba y mergea en master, Jenkins dispara automáticamente el pipeline de release:
    • Se asigna un nuevo número de versión (versionado automático).
    • Se genera el ejecutable/distribuible.
    • Se publica o distribuye en el servidor de releases.

Bitácora de implementación

Se detallan a continuación las tareas realizadas en función de avanzar con la implementación del entorno y pipelines.

Se instaló Jenkins mediante docker en el servidor de desarrollo. Se creó el directorio /home/jenkins para centralizar todo lo relacionado a la herramienta.

Se configuró la red en modo host y el puerto de Jenkins 9090.

El archivo docker-compose.yml utilizado al momento de redactar este documento es el siguiente:

version: '3.8' services:   jenkins:     image~: jenkins/jenkins:lts     container_name: jenkins     restart: unless-stopped     user: root     privileged: true     network_mode: host     volumes:       - jenkins_home:/var/jenkins_home       - /var/run/docker.sock:/var/run/docker.sock     environment:       - JAVA_OPTS=-Djenkins.install.runSetupWizard=true       - JENKINS_OPTS=--httpPort=9090   postgres:     image~: postgres:10.10     container_name: libertya-postgres     restart: unless-stopped     network_mode: host     environment:       POSTGRES_DB: libertya_test       POSTGRES_USER: libertya       POSTGRES_PASSWORD: libertya       PGPORT: 5434     volumes:       -
./init-db:/docker-entrypoint-initdb.d volumes:   jenkins_home:

Se configuraron las credenciales para Github y SVN. Estas son necesarias para interactuar con los repositorios y poder clonar los proyectos.

Se creó un pipeline inicial para probar las funcionalidades básicas como clonación de repositorios, ejecución de scripts, etc… Se probaron también diferentes formas de ejecutar el pipeline, mediante el uso de Jenkinsfile en el repositorio y mediante un script dentro del propio Jenkins.

Se configuró un contenedor para postgres (ya especificado en el docker-compose.yml) que se levanta junto a Jenkins para instanciar la base de datos de test. Al crear el contenedor se toma el dump ubicado en el directorio init-db que es una base de datos preparada para testing actualmente basada en Libertya 22.

Se mejoró iterativamente el pipeline, probando diferentes ejecuciones y actualmente se pueden clonar los repositorios de Libertya y lyrestapi, disparar la compilación de Libertya, etc…

La compilación de Libertya falla con el script ./Compilar.sh debido a que intenta utilizar el jdk17 que está por defecto en la instancia. Se configuró el openjdk-11 desde el contenedor para utilizar en el pipeline.

Configuración de JDK para pipeline

Por defecto, el contenedor de Jenkins tenía configurado Java 17. Para instalar Java 11, se siguieron los siguientes pasos:

      1. Actualizar las fuentes del sistema operativo (Debian):
        El paquete openjdk-11-jdk no estaba disponible inicialmente. Para solucionarlo se agregó el repositorio adecuado:
echo “deb http://deb.debian.org/debian bullseye main” >> /etc/apt/sources.list apt update apt install -y openjdk-11-jdk

2. Configuración de la instalación del JDK en Jenkins

Una vez instalado el JDK, se registró manualmente en Jenkins:

  1. Ir a: Jenkins > Administrar Jenkins > Global Tool Configuration
  2. Buscar la sección JDK
  3. Añadir una nueva entrada con los siguientes valores:
    • Nombre: java-11-openjdk-amd64
    • JAVA_HOME: /usr/lib/jvm/java-11-openjdk-amd64
    • Desmarcar la opción “Instalar automáticamente” (ya que ya está instalado en el sistema).

3. Uso del JDK desde un Jenkinsfile

Para utilizar este JDK en un pipeline declarativo, se incluyó la configuración del JDK en la sección tools, y luego se actualizó el PATH:

tools {         jdk 'java-11-openjdk-amd64' } environment {         JAVA_HOME = "${tool 'java-11-openjdk-amd64'}"         PATH = "${env.JAVA_HOME}/bin:${env.PATH}" }

Nota: Para la ejecución de lyrestapi se configuró java-8-temurin de manera similar en /opt/jdk8u452-b09

image7.png

El error persiste:

[mkdir] Created dir: /var/jenkins_home/workspace/Libertya-Core/libertya/install/compilacion/ServidorOXP/jboss/server
[copy] Copying 284 files to /var/jenkins_home/workspace/Libertya-Core/libertya/install/compilacion/ServidorOXP/jboss/server
[zip] Building zip: /var/jenkins_home/workspace/Libertya-Core/install/ServidorOXP_V22.0.zip

BUILD FAILED
/var/jenkins_home/workspace/Libertya-Core/libertya/utils_dev/build.xml:32: The following error occurred while executing this line:
/var/jenkins_home/workspace/Libertya-Core/libertya/install/build.xml:218: Problem creating zip: /var/jenkins_home/workspace/Libertya-Core/install/ServidorOXP_V22.0.zip (No such file or directory) (and the archive is probably corrupt but I could not delete it)Total time: 1 minute 31 seconds
Se intentó modificar los scripts y/o pipeline para poder alterar los directorios donde se exporta Libertya (Compilar.sh, build.xml, VariablesCompilacion.sh, etc..)

Se logró realizar la compilación. Da errores al descomprimir el zip pero se probó realizar la descompresión manualmente dentro del pipeline:

Env.

environment {         WORKDIR = '/var/jenkins_home/workspace/Libertya-Core'         JAVA_HOME = "${tool 'java-11-openjdk-amd64'}"         PATH = "${env.JAVA_HOME}/bin:${env.PATH}"         OXP_ROOT = '$(pwd)'         DB_NAME = 'libertya_test'         DB_USER = 'libertya'         DB_PASS = 'libertya'         DB_PORT = '5434'     }

Stage “Clonar y Compilar Libertya”

stage('Clonar y Compilar Libertya') {             steps {                 dir('libertya'){                     git branch: 'master', url: 'https://github.com/Disytel-Consulting-SA/libertya.git'                                  sh '''                     echo "==> Listando directorios y archivos"                     ls -lhst                                      INSTALL_DIR="${WORKDIR}/install"
mkdir -p ${INSTALL_DIR}
chmod -R u+w ${INSTALL_DIR}                              echo "==> Habilitando permisos de ejecución..."                     find . -type f -name "*.sh" -exec
chmod \+x {} \\;                              echo "==> Ejecutando script de compilación..."
cd utils_dev &&
./Compilar.sh                              #echo "==> Descomprimiendo..."                     OXP_HOME="${WORKDIR}/ServidorOXP"
unzip ${INSTALL_DIR}/ServidorOXP_V22.0.zip -d ${WORKDIR}                     '''
                }             }         }

Consiguiendo que Libertya quede en el workspace dentro de ServidorOXP.
image8.png
Lógicamente esto se buscó para no utilizar las rutas por defecto como /ServidorOXP o /install ya que se escapan del contexto del pipeline (workdir). La idea es que todo quede contenido en el propio workdir del pipeline para no correr el riesgo de mezclar datos con otros pipelines.

Para tener una versión preparada de LibertyaEnv.properties se configuró el plugin Config File Provider que permite administrar archivos de configuración (como .properties, .xml, etc.) directamente desde Jenkins y usarlos en los pipelines o jobs.

Se puede acceder desde Administrar Jenkins > Managed files

image9.png

Ejecución de ConfigurarAuto.sh

+ ./ConfigurarAuto.sh
=======================================
Comenzando Configuraci�n …
=======================================
Validando existencia de clases duplicadas en ./lib/plugins
Sin archivos para procesar.



[delete] Deleting directory /var/jenkins_home/workspace/Libertya-Core/ServidorOXP/nuevosComponents
[delete] Deleting directory /var/jenkins_home/workspace/Libertya-Core/ServidorOXP/backupOXP
[echo] KeyStore=/var/jenkins_home/workspace/Libertya-Core/ServidorOXP/keystore/myKeystore - Alias=libertya
[signjar] Signing JAR: /var/jenkins_home/workspace/Libertya-Core/ServidorOXP/lib/OXPXLib.jar
[signjar] jarsigner error: java.lang.RuntimeException: keystore load: /var/jenkins_home/workspace/Libertya-Core/ServidorOXP/keystore/myKeystore (No such file or directory)

BUILD FAILED
/var/jenkins_home/workspace/Libertya-Core/ServidorOXP/build.xml:414: exec returned: 1

Total time: 26 seconds

Luego de revisar la ejecución y hacer pruebas locales, se observó que no fue creado ServidorOXP/keystore/myKeystore en jenkins. Probablemente por algo ocurrido en el proceso de compilación.

Se modificó VariablesCompilacion.sh desde Github para hacer mas flexible la toma de variables para compilacion, ahora seteando un OXP_HOME adaptado a jenkins se debería poder compilar correctamente.

Se realizaron modificaciones en Libertya CORE pero aun así se tiene el problema de la keystore. Se va a configurar manualmente para que permita firmar los Jars a la hora de ejecutar el ConfigurarAuto.sh

Se optó por incluir la configuración de keystore desde el propio pipeline:

stage('Preparar keystore') {            steps {                sh '''
mkdir -p ${OXP_HOME}/keystore                    if [ ! -f ${OXP_HOME}/keystore/myKeystore ]; then                        keytool -genkey -keyalg RSA \                            -alias libertya \                            -dname "CN=Jenkins, OU=CI, O=TuEmpresa, L=Ciudad, ST=Provincia, C=AR" \                            -keypass libertya \                            -storepass libertya \                            -validity 365 \                            -keystore ${OXP_HOME}/keystore/myKeystore                    fi                '''
           } }

La ejecución final funcionó correctamente.

BUILD SUCCESSFUL
Total time: 1 minute 8 seconds

*** 2025-06-12 02:01:46.38 OpenXpertya Log (CLogConsole) ***

Si se examina el workspace se puede observar como quedó compilado Libertya:
image10.png

Se configuró el pipeline para tomar lyrestapi y ejecutar los tests. Actualmente falla porque lyrestapi aun no es compatible con jdk11. Se va a configurar un jdk8 para poder hacer esta ejecución.

 Task :compileJava FAILED
/var/jenkins_home/workspace/Libertya-Core/lyrestapi/src/main/java/org/libertya/api/controller/AbstractController.java:16: error: package javax.rmi.CORBA does not exist
import javax.rmi.CORBA.Util;
^
Note: /var/jenkins_home/workspace/Libertya-Core/lyrestapi/src/main/java/org/libertya/api/security/WebSecurityConfig.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
1 error

FAILURE: Build failed with an exception.

Se configuró jdk-8-temurin y se volvió a ejecutar:

java -version
openjdk version “1.8.0_452”
OpenJDK Runtime Environment (Temurin)(build 1.8.0_452-b09)
OpenJDK 64-Bit Server VM (Temurin)(build 25.452-b09, mixed mode)
[Pipeline] sh
+ chmod +x ./gradlew
[Pipeline] sh

+ ./gradlew clean test
Starting a Gradle Daemon (subsequent builds will be faster)

 Task :clean UP-TO-DATE

 Task :compileJava
Note: /var/jenkins_home/workspace/Libertya-Core/lyrestapi/src/main/java/org/libertya/api/security/WebSecurityConfig.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

 Task :processResources
> Task :classes

 Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses

 Task :test

AllocationIntegrationTests > initializationError FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:98
Caused by: java.lang.IllegalArgumentException at Assert.java:702

BPartnerIntegrationTests > initializationError FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:98
Caused by: java.lang.IllegalArgumentException at Assert.java:702

BPartnerLocationIntegrationTests > initializationError FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:98
Caused by: java.lang.IllegalArgumentException at Assert.java:702

BanksAccountsIntegrationTests > initializationError FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:98
Caused by: java.lang.IllegalArgumentException at Assert.java:702

BanksIntegrationTests > initializationError FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:98
Caused by: java.lang.IllegalArgumentException at Assert.java:702

CashBooksIntegrationTests > initializationError FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:98
Caused by: java.lang.IllegalArgumentException at Assert.java:702

Se obtuvieron varios errores que en local no ocurren. Se procede a cambiar el jdk por una version de oracle.

AllocationIntegrationTests > initializationError FAILED java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:98 Caused by: java.lang.IllegalArgumentException at Assert.java:702

Se sigue obteniendo el mismo error. Se ejecutó gradlew clean test –info

Successfully started process ‘Gradle Test Executor 1’

Gradle Test Executor 1 started executing tests.

 Task :test

AllocationIntegrationTests STANDARD_OUT
- Neither @ContextConfiguration nor @ContextHierarchy found for test class [org.libertya.api.AllocationIntegrationTests], using SpringBootContextLoader
- Could not detect default resource locations for test class [org.libertya.api.AllocationIntegrationTests]: no resource found for suffixes {-context.xml, Context.groovy}.
- Could not detect default configuration classes for test class [org.libertya.api.AllocationIntegrationTests]: AllocationIntegrationTests does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
- Found @SpringBootConfiguration org.libertya.api.LYRestAPI for test class org.libertya.api.AllocationIntegrationTests
- Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.event.ApplicationEventsTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
- Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@75390459, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@7756c3cd, org.springframework.test.context.event.ApplicationEventsTestExecutionListener@2313052e, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@2bd2b28e, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@16746061, org.springframework.test.context.support.DirtiesContextTestExecutionListener@57fd91c9, org.springframework.test.context.event.EventPublishingTestExecutionListener@6cfcd46d, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@52045dbe, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@674658f7, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@5c8eee0f, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@565b064f, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener@26425897, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@73163d48]

  • Caught exception while allowing TestExecutionListener [org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@16746061] to prepare test instance [org.libertya.api.AllocationIntegrationTests@419a20a6]
    java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:98)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
    at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:43)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248)
    at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$8(ClassBasedTestDescriptor.java:363)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:368)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$9(ClassBasedTestDescriptor.java:363)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:312)
    at java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:743)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:362)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$6(ClassBasedTestDescriptor.java:283)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:282)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:272)
    at java.util.Optional.orElseGet(Optional.java:267)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:271)
    at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$before$2(ClassBasedTestDescriptor.java:197)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:196)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:80)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:148)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.util.ArrayList.forEach(ArrayList.java:1249)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
    at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
    at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    at com.sun.proxy.$Proxy2.stop(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
    at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
    Caused by: java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.slf4j.impl.JDK14LoggerFactory loaded from file:/var/jenkins_home/workspace/Libertya-Core/ServidorOXP/lib/OXPXLib.jar). If you are using WebLogic you will need to add ‘org.slf4j’ to prefer-application-packages in WEB-INF/weblogic.xml: org.slf4j.impl.JDK14LoggerFactory
    at org.springframework.util.Assert.instanceCheckFailed(Assert.java:702)
    at org.springframework.util.Assert.isInstanceOf(Assert.java:621)
    at org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext(LogbackLoggingSystem.java:294)
    at org.springframework.boot.logging.logback.LogbackLoggingSystem.beforeInitialize(LogbackLoggingSystem.java:118)
    at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationStartingEvent(LoggingApplicationListener.java:238)
    at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:220)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:131)
    at org.springframework.boot.context.event.EventPublishingRunListener.starting(EventPublishingRunListener.java:79)
    at org.springframework.boot.SpringApplicationRunListeners.lambda$starting$0(SpringApplicationRunListeners.java:56)
    at java.util.ArrayList.forEach(ArrayList.java:1249)
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:120)
    at org.springframework.boot.SpringApplicationRunListeners.starting(SpringApplicationRunListeners.java:56)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:298)
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:136)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:141)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:90)
    … 81 more

Luego de análisis y consultas, se modificó el pipeline para eliminar org/slf4j/impl y el index

zip -d OXPXLib.jar “org/slf4j/impl/*”
zip -d OXPXLib.jar “META-INF/INDEX.LIST”

Para esto fue necesario instalar ZIP en el contenedor de jenkins.

Se configuraron las notificaciones por email mediante Extended email plugin.

image11.png

Node 22 y pnpm

Se instaló Node 22 y pnpm 9.6.0 para el deploy del pos-web y productos afines

curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y nodejs
npm install -g pnpm@9.6.0

SonarQube

Se realizaron pruebas a nivel local con SonarQube para el análisis de código, con la posibilidad de integrarlo en el pipeline junto con los tests automáticos.

services: sonarqube: image: sonarqube:latest ports: - “9000:9000” volumes: - sonarqube_data:/opt/sonarqube/data - sonarqube_extensions:/opt/sonarqube/extensions - sonarqube_logs:/opt/sonarqube/logs volumes: sonarqube_data: sonarqube_extensions: sonarqube_logs:

image12.pngimage13.png

Se ejecutó sonar-scanner desde CLI (docker). Para eso fue necesario generar un token desde el servidor de SonarQube para el usuario admin. Después es necesario especificar el directorio donde se encuentra Libertya compilado. En este caso se configuró /libertya_export

docker run \ –rm \ -e SONAR_HOST_URL=“http://172.17.0.1:9000” \ -e SONAR_TOKEN=“squ_69d3653bc7494955e0187e4dee003fef14231bd0” \ -v “/libertya_export:/usr/src” \ sonarsource/sonar-scanner-cli

Para ejecutar el CLI se tiene que configurar un archivo sonar-project.properties en el directorio raiz del proyecto, en este caso directamente en /libertya_export

sonar.projectKey=libertya sonar.projectName=Libertya ERP sonar.projectVersion=1.0 sonar.sources=base/src,client/Src sonar.java.binaries=base/compilacion,client/compilacion sonar.sourceEncoding=UTF-8

Uno de los errores que surgieron en la ejecución fue:
22:39:23.573 INFO Sensor JavaSensor [java]
22:39:23.593 ERROR Error during SonarScanner Engine execution
org.sonar.java.AnalysisException: Your project contains .java files, please provide compiled classes with sonar.java.binaries property, or exclude them from the analysis with sonar.exclusions property.
at org.sonar.java.classpath.ClasspathForMain.init(ClasspathForMain.java:70)
at org.sonar.java.classpath.AbstractClasspath.getElements(AbstractClasspath.java:316)
at org.sonar.java.SonarComponents.getJavaClasspath(SonarComponents.java:250)
at org.sonar.java.JavaFrontend.<init>(JavaFrontend.java:92)

Esto se debe a que la CLI no encuentra los .class del proyecto. Se debe configurar explicitamente donde encontrarlos en el archivo sonar-project.properties. Para eso se utiliza la línea sonar.java.binaries=base/compilacion,client/compilacion. En este caso se incluyeron solamente los .class de esos directorios. Lógicamente no hay que tener en cuenta directorios relacionados a librerías como tools/ etc..

Una vez ejecutado, va a aparecer el reporte en el servidor de sonarqube:
image14.png

image15.png

Dentro de Jenkins se instaló el plugin SonarQube Scanner, luego en Tools se configuró:
image16.png

y luego en administrar jenkins -> system se agrega el server

image17.png

Se configuró un pipeline de pruebas para la ejecución de SonarQube, los stages relevantes son los siguientes:

stage('Analizar con SonarQube') {            tools {                jdk 'jdk-17'            }            steps {                dir('libertya') {                    script {                        def scannerHome = tool 'SonarQube-Scanner'                        withSonarQubeEnv("${SONARQUBE_SERVER}") {                            sh """                                ${scannerHome}/bin/sonar-scanner \                                    -Dsonar.projectKey=libertya \                                    -Dsonar.projectName="Libertya ERP" \                                    -Dsonar.projectVersion=1.0 \                                    -Dsonar.sources=base/src,client/Src \                                    -Dsonar.java.binaries=base/compilacion,client/compilacion \                                    -Dsonar.sourceEncoding=UTF-8 \                                    -Dsonar.token=${SONARQUBE_TOKEN}                            """                        }                    }                }            }        }        stage('Esperar Quality Gate') {            steps {                timeout(time: 10, unit: 'MINUTES') {                    waitForQualityGate abortPipeline: true                }            }        }

Aún así, se presentaron errores en la ejecución:

02:29:41.092 WARN Invalid character encountered in file /var/jenkins_home/workspace/test/libertya/base/src/org/openXpertya/model/MRole.java at line 2 for encoding UTF-8. Please fix file content or configure the encoding to be used using property ‘sonar.sourceEncoding’.
02:29:46.206 WARN Invalid character encountered in file /var/jenkins_home/workspace/test/libertya/base/src/org/openXpertya/model/MAttributeSet.java at line 173 for encoding UTF-8. Please fix file content or configure the encoding to be used using property ‘sonar.sourceEncoding’.
02:29:49.441 WARN Invalid character encountered in file /var/jenkins_home/workspace/test/libertya/base/src/org/openXpertya/model/MOrder.java at line 3550 for encoding UTF-8. Please fix file content or configure the encoding to be used using property ‘sonar.sourceEncoding’.
02:30:10.808 WARN Invalid character encountered in file /var/jenkins_home/workspace/test/libertya/base/src/org/openXpertya/print/ArchiveEngine.java at line 125 for encoding UTF-8. Please fix file content or configure the encoding to be used using property ‘sonar.sourceEncoding’.
02:30:21.575 INFO 42% analyzed
02:30:37.897 INFO 43% analyzed
02:30:42.794 WARN Invalid character encountered in file /var/jenkins_home/workspace/test/libertya/client/Src/org/openXpertya/grid/ed/VPAttributeDialog.java at line 1134 for encoding UTF-8. Please fix file content or configure the encoding to be used using property ‘sonar.sourceEncoding’.
02:30:42.847 WARN Invalid character encountered in file /var/jenkins_home/workspace/test/libertya/base/src/org/openXpertya/model/MQuarter.java at line 251 for encoding UTF-8. Please fix file content or configure the encoding to be used using property ‘sonar.sourceEncoding’.
02:30:56.626 ERROR [stderr] OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000c5200000, 378535936, 0) failed; error=‘Not enough space’ (errno=12)
02:30:56.681 INFO [stdout] #
02:30:56.681 INFO [stdout] # There is insufficient memory for the Java Runtime Environment to continue.
02:30:56.681 INFO [stdout] # Native memory allocation (mmap) failed to map 378535936 bytes. Error detail: committing reserved memory.
02:30:56.682 INFO [stdout] # An error report file with more information is saved as:
02:30:56.682 INFO [stdout] # /var/jenkins_home/workspace/test/libertya/hs_err_pid10107.log
02:30:57.682 INFO EXECUTION FAILURE
02:30:58.539 INFO Total time: 5:32.863s

El servidor llegó al límite de memoria, usar Jenkins, SonarQube, Postgres, etc.. es demasiada carga para la memoria actual.

Se deshabilitó temporalmente la utilización de SonarQUbe por sobrecarga en el servidor.

Se creó el pipeline Libertya-Core-Sandbox para pruebas que no afecten el pipeline “productivo”.

Se configuraron credenciales y secrets para transferir builds al server de desarrollo

Referencias

https://www.jenkins.io/doc/book/installing/