Mayo, 2019
La Integración continua se ha vuelto una pieza imprescindible en el desarrollo de software y la metodología ágil. Nosotros lo vemos como un camino o proceso en constante desarrollo. En este artículo se pretende mostrar el proceso CI actual de ZSS, para una Tecnologia dada. Este no es un articulo introductorio ni un tutorial.
Los beneficios que nos provee la integración continua desde un punto de vista práctico son los siguientes:
El proceso de Integración Continua que utilizamos no difiere de los modelos comunes. En el caso presentado en este artículo el despliegue corresponde a subir el artefacto construido a un repositorio de artefactos binarios.
Para explicar nuestro proceso vamos a usar una aplicación de muestra expuesta a continuación. Esta se compone de una interfaz WEB para la capa Front, y de un conjunto de API en la capa Back que son consumidos desde el Front que corre en el navegador del usuario. La aplicación que vamos a examinar esta compuesta por dos componentes:
Dado estos dos proyectos, se espera poder realizar el siguiente proceso para cada uno de ellos:
El objetivo es poder crear un pipeline utilzando Jenkins, Sonarqube y Sonatype Nexus. Todos los test ejecutados, y los análisis asociados a Sonarqube y los Linter deben estar disponibles para el desarrollador, en las plataformas y en su ambiente local, en todo momento.
Lo primero que se puede notar es que utilizamos Bitbucket para almacenar nuestro código y que las plataformas instaladas se encuentran en AWS. Como se mecionó el repositorio recibe un push y emite un webhook a la plataforma de integración, en este caso Jenkins. Utilizamos Google conectado con Jenkins y Sonarqube, de forma que usemos nuestros usuarios de Google para acceder a las plataformas. Sin el uso de este SSO, se reduce la usabilidad de la solución.
Lo segundo que salta a la vista es que los IDE están conectados a Sonarqube. Utilizando SonarLint podemos obtener el análisis estático de código, en tiempo real conectado a nuestro servidor central Sonarqube en los IDE de los desarrolladores examinando su código local. Con esto se reduce la iteración del desarrollador para corregir los code smells detectados.
Ahora vamos a revisar que se configuró en cada uno de los servidores de nuestro ambiente. Posteriormente revisaremos el pipeline para Java y luego para Angular así como la configuración de los IDE.
Es el motor de Integración Continua. Encargado de ejecutar los pasos descritos. Un punto esencial, sobre todo en el caso de Angular, es que pueda ejecutar contenedores Docker. Los plugin adicionalmente instalados y su uso son los siguientes:
Dado que el pipeline (JenkinsFile) se encuentra alojado dentro de nuestro controlador de versiones junto con nuestro código, se deben realizar varias configuraciones: configuración del servidor Sonarque, credenciales varias, de modo que queden ofuscadas en los pipeline y los token de SonarQube. También no olvidar configurar la dirección de nuestro server, paso vital para utilizar el Login via Google.
Es una plataforma para evaluar código fuente. Realiza análisis estático de código y además va a incorporar los análisis de otras herramientas, en este caso CheckStyle y TSLINT. Está encargada de evaluar los Quality Gates. SonarQube viene con la mayoria de los plugin necesarios instalados, salvo por:
Se puede observar como los informes de SonarQube, contienen sensores propios y externos, como TSLINT en este caso. Un paso importante para lograr la integración entre SonarQube y Jenkins es habilitar el webhook para informar sobre el estado del Quality Gates hacia Jenkins.
Es una plataforma que utlizada como repositorio de artefactos. En la fase final de nuestros pipeline, el último paso es desplegarlos en un repositorio de atefactos, con esto, aseguramos la trazabilidad de los binarios entregados al cliente y/o despleguados.
A la plataforma Nexus, no se le instala ningún plugin adicional. Principalmente se configuran los repositorios específicos, los usuarios y token respectivos.
La aplicación Back se encuentra escrita en Java haciendo uso intensivo de Spring y Spring Boot. La herramienta de Build a utilizar es Maven. El pipeline que hemos diseñado es el siguiente:
Se puede observar que los pasos de pipeline no difieren mucho de los habituales. Un punto interesante es que incorporamos el análisis con checkstyle, que a su vez es subido al reporte en SonarQube. Checkstyle, es un herramienta OpenSource distribuida bajo licencia GNU LGPL. Nosotros utilizamos la plantilla de estilos de Google. Para correr el análisis utilizamos Maven.
El despliegue sobre Nexus se realiza utilizando el plugin Nexus Artifact Uploader. Se puede apreciar en el código del pipeline la sección enviroment donde se definen coordenadas para realizar el upload. Como ya se mecionó una de las características principales de los pipeline en Jenkins, es poder ofuscar todo tipo de credenciales. El plugin Pipieline Utility Steps es utilizado para parsear el pom.xml del proyecto Maven y obetener la metadata necesaria para el upload.
pipeline {
agent any
environment {
scannerHome = tool 'Sonar 3.3'
jdkHome = tool 'Java Local 8'
mavenHome = tool 'Maven Local'
// This can be nexus3 or nexus2
NEXUS_VERSION = "nexus3"
// This can be http or https
NEXUS_PROTOCOL = "xxxx"
// Where your Nexus is running
NEXUS_URL = "xxxxxxxxxxx"
// Repository where we will upload the artifact
NEXUS_REPOSITORY = "zss_repo"
// Jenkins credential id to authenticate to Nexus OSS
NEXUS_CREDENTIAL_ID = "ZSS Nexus"
}
tools {
maven 'Maven Local'
jdk 'Java Local 8'
}
stages {
stage('Build') {
steps {
sh 'mvn -B -DskipTests clean install'
}
}
stage('Test') {
steps {
sh 'mvn -B test'
}
}
stage('Checkstyle') {
steps {
sh 'mvn checkstyle:checkstyle'
}
}
stage('sonar') {
steps {
withSonarQubeEnv('Sonar ZSS') {
sh "${scannerHome}/bin/sonar-scanner"
}
}
}
stage('Quality Gate') {
steps {
waitForQualityGate abortPipeline: true
}
}
stage("publish to nexus") {
steps {
sh "mvn package -DskipTests=true"
script {Permite utilizar las credenciales de Google para autentificarse
// Read POM xml file using 'readMavenPom' step , this step 'readMavenPom' is included in: https://plugins.jenkins.io/pipeline-utility-steps
pom = readMavenPom file: "pom.xml";
// Find built artifact under target folder
filesByGlob = findFiles(glob: "target/*.${pom.packaging}");
// Print some info from the artifact found
echo "${filesByGlob[0].name} ${filesByGlob[0].path} ${filesByGlob[0].directory} ${filesByGlob[0].length} ${filesByGlob[0].lastModified}"
// Extract the path from the File found
artifactPath = filesByGlob[0].path;
// Assign to a boolean response verifying If the artifact name exists
artifactExists = fileExists artifactPath;
if(artifactExists) {
echo "*** File: ${artifactPath}, group: ${pom.groupId}, packaging: ${pom.packaging}, version ${pom.version}";
nexusArtifactUploader(
nexusVersion: NEXUS_VERSION,
protocol: NEXUS_PROTOCOL,
nexusUrl: NEXUS_URL,
groupId: pom.groupId,
version: pom.version,
repository: NEXUS_REPOSITORY,
credentialsId: NEXUS_CREDENTIAL_ID,
artifacts: [
// Artifact generated such as .jar, .ear and .war files.
[artifactId: pom.artifactId,
classifier: '',
file: artifactPath,
type: pom.packaging],
// Lets upload the pom.xml file for additional information for Transitive dependencies
[artifactId: pom.artifactId,
classifier: '',
file: "pom.xml",
type: "pom"]
]
);
} else {
error "*** File: ${artifactPath}, could not be found";
}
}
}
}
}
}
El ambiente de desarrollo utilizado para construir la aplicación Back sobre Java, es el clásico Eclipse IDE. Uno de nuestros objetivos principales es que el desarrollador tenga retroalimentación de la calidad del código en todo momento. Para lograr esto incorporamos una serie plugins y configuraciones en Eclipse:
La aplicación Front se encuentra escrita en Typescript en base al Framework Angular 7. Hacemos uso extensivo de Angular CLI. El pipeline que hemos diseñado es el siguiente:
Algunos pasos del pipeline están decorados con el simbolo Docker. La razón de esto es que para realizar las pruebas unitarias via Karma, se encesita un navegador. Existe opciones headless, como Headless Chrome, PhantomJS, pero configurarlas en los distintos ambientes (Jenkins en AWS, máquinas de los desarrolladores) es un trabajo extra en sí. Además, a lo largo del tiempo, nos percatamos, que dependiendo de la versión de NodeJS y otros detalles los resultados de las pruebas y linter, eran distintos entre los diferentes ambientes. Como en la mayoría de los problemas en informática, ya fue resuelto por alguien mas hábil. Cual es la solución? utilizar contenedores en base a Docker. Existe la imagen trion/ng-cli-karma y varios artículos sobre como usarla. Esta imagen nos provee un contenedor con NodeJS, Angular CLI y Karma listos para usar.
pipeline {
agent any
environment {
scannerHome = tool 'Sonar 3.3'
jdkHome = tool 'Java Local 8'
mavenHome = tool 'Maven Local'
// This can be nexus3 or nexus2
NEXUS_VERSION = "nexus3"
// This can be http or https
NEXUS_PROTOCOL = "xxxx"
// Where your Nexus is running
NEXUS_URL = "xxxxxxxxxxxx"
// Repository where we will upload the artifact
NEXUS_REPOSITORY = "zss_repo"
// Jenkins credential id to authenticate to Nexus OSS
NEXUS_CREDENTIAL_ID = "ZSS Nexus"
}
tools {nodejs 'NodeJS 10.15.3'}
stages {
stage('install') {
agent {
docker {
image 'trion/ng-cli-karma'
registryCredentialsId 'dockercell'
reuseNode true
}
}
steps {
sh 'npm install'
}
}
stage('test') {
agent {
docker {
image 'trion/ng-cli-karma'
registryCredentialsId 'dockercell'
reuseNode true
}
}
steps {
sh 'node_modules/@angular/cli/bin/ng test --watch=false'
}
}
stage('lint') {
agent {
docker {
image 'trion/ng-cli-karma'
registryCredentialsId 'dockercell'
reuseNode true
}
}
steps {
sh 'node_modules/@angular/cli/bin/ng lint --format=json --force=true > report.json'
}
}
stage('sonar') {
steps {
sh 'truncate -s-3 report.json'
withSonarQubeEnv('Sonar ZSS') {
sh "node_modules/sonar-scanner/bin/sonar-scanner"
}
}
}
stage("Quality Gate") {
steps {
waitForQualityGate abortPipeline: true
}
}
stage('build') {
agent {
docker {
image 'trion/ng-cli-karma'
registryCredentialsId 'dockercell'
reuseNode true
}
}
steps {
sh 'node_modules/@angular/cli/bin/ng build --prod'
}
}
stage('nexus') {
steps {
script {
def project = readJSON file: "${env.WORKSPACE}/package.json"
def fileExists = new File( "${env.WORKSPACE}/${project.name}.zip" )
if( fileExists.exists() ) {
sh "rm ${project.name}.zip"
} else {
println "File doesn't exist"
}
zip zipFile: "${project.name}.zip", archive: false, dir: "dist/${project.name}"
filesByGlob = findFiles(glob: "${project.name}.zip");
artifactPath = filesByGlob[0].path;
nexusArtifactUploader(
nexusVersion: NEXUS_VERSION,
protocol: NEXUS_PROTOCOL,
nexusUrl: NEXUS_URL,
groupId: project.name,
version: project.version,
repository: NEXUS_REPOSITORY,
credentialsId: NEXUS_CREDENTIAL_ID,
artifacts: [
// Artifact generated such as .jar, .ear and .war files.
[artifactId: project.name,
classifier: '',
file: artifactPath,
type: 'zip']
]
);
}
}
}
}
}
El utilizar contenedores Docker nos permite realizar ciertas tareas pero nos limita en otras. Por tanto combinamos ciertos pasos de pipeline entre Docker y el host de Jenkins. Los únicos pasos que podemos invocar dentro de los contenedores Docker, son instrucciones 'sh'. Además si se utiliza la imagen en las fases, nos encontramos con el problema que crea dos workspace distintos, uno para lo que corre en el host y otro para los contenedores Docker. El plugin de Jenkins nos soluciona esto a través de la opción 'reuseNode true'. Con esto logramos un único workspace entre los distintos mundos. Las fases asociadas a SonarQube se deben realizar a nivel de host Jenkins. Al momento de realizar el upload a Nexus, utilizamos el plugin Pipieline Utility Steps el cual nos permite parsear un archivo JSON (package.json) a través de 'readJSON'. Este plugin nos soluciona obtener la metadata al momento de hacer push a Nexus. Se debe tener atención en el código dentro de los script, por ejemplo, en este caso utilizamos funciones de manipulación de archivos; estas, deben ser autorizadas explícitamente en la configuración de Jenkins.
Desde hace algún tiempo Microsoft ha comenzado a hacer guiños al mundo Open Source. De esta manera nos encontramos con Visual Studio Code, el cual se ha convertido en nuestro IDE de cabecera para proyectos Angular y React. Cuenta con un variado ecosistema de plugin y marketplace, y salvo algunos detalles de latencia y cantidad de archivos abiertos funciona de manera excelente en Linux. Para complementar nuestro pipeline y que los desarrolladores cuenten con las reglas en linea, instalamos los siguientes plugin en Visual Studio Code:
kisspng-jenkins-docker-continuous-delivery-continuous-inte-5b1eb567f01075.492816241528739175983
Finalmente podemos ver combinados los exámenes de las herramientas locales, con los exámenes de las herramientas en linea.
Al inicio del artículo se plantea la integración continua como un proceso en constante desarrollo, las conclusiones que se presentan tienen que ver con la experiencia ganada en este proceso:
Los nuevos pasos que queremos implementar son los siguientes: