**Integración continua Front
**
**1) Preparar ambientes en front. **
Se crean 4 ambientes (environment) dentro de la MFSicof (Local, Dev, QA, Prod). Dentro de ellos se ejecutan los comandos correspondientes de ambiente:
Dev: npm run build:dev QA: npm run build:qa Prod: npm run build:prod Local es el definido por defecto si no se le coloca un ambiente y se deja tal cual:
Local: npm run start o npm start
**2) Preparar Docker. Construir la imagen Docker:
**
* docker build –build-arg TYPE=qa -t mfsicof .
Nota: TYPE cambia en los pipelines de despliegue en Jenkins dependiendo del ambiente que apunte. Las opciones son dev, qa y prod.
Ejecutar el contenedor Docker:
* docker run -d -p 80:80 mfsicof
Nota: Se define el puerto, donde el primero es el externo y el segundo es el interno del contenedor.
**Al hacer Docker Build se ejecuta la siguiente receta:
**
# Usa una imagen base de Node.js
FROM node:20-alpine AS build-step
# Establecer el límite de memoria para Node.js
ENV NODE_OPTIONS=--max_old_space_size=4096
# Variable de entorno que define el ambiente
ARG TYPE
# Crea un directorio /app en la imagen
RUN mkdir -p /app
# Establece el directorio de trabajo
WORKDIR /app
# Copia los archivos de tu proyecto al directorio de trabajo
COPY package.json /app
# Instala las dependencias del proyecto
RUN npm install
# Copia el resto de los archivos de tu proyecto al directorio de trabajo
COPY . /app
# Construye la aplicación Angular
RUN npm run build:${TYPE}
# Configura la imagen de producción de Nginx
FROM nginx:alpine
# Copia los archivos generados de la compilación de Angular a la carpeta de Nginx
COPY --from=build-step /app/dist/mfsicof /usr/share/nginx/html
# Expone el puerto 80 para que se pueda acceder a la aplicación desde el navegador
EXPOSE 80
# Comando para iniciar Nginx cuando se ejecute el contenedor
CMD ["nginx", "-g", "daemon off;"]
**3) Crear proyecto en GitLab.
**
Se crea un proyecto en GitLab con el siguiente repositorio: http://10.1.140.120/ada-microservices-ecosystem/frontends/mfsicof.git
Se crea un webhook para el frontend con la siguiente URL:
Para clonar el proyecto una vez tengas permisos de Git:
code git clone http://10.1.140.120/ada-microservices-ecosystem/frontends/mfsicof.git Para instalar el ambiente se recomienda consultar la guía de primeros pasos.
Se recomienda consultar la guía de flujo Git.
**4) Preparar Jenkins.
**
Adicional a la configuración de contenedores para Jenkins es necesario instalar jq.
jq es una herramienta de línea de comandos para procesar JSON. Asegúrate de que esté instalada en el sistema donde Jenkins está ejecutando el script.
En sistemas basados en Debian/Ubuntu, puedes instalar jq con:
- sudo apt-get install jq
- Configurar el Script en Jenkins:
- Accede a la configuración del proyecto en Jenkins.
- En la sección “Build”, añade o edita el paso “Execute shell”.
- Copia y pega el script anterior en el campo de texto del shell.
- Guardar y Probar:
- Guarda la configuración del proyecto.
**5.1 Acceder a Jenkins:
**
Inicia sesión en tu instancia de Jenkins.
**5.2 Crear un nuevo proyecto:
**
Ve a "New Item".
Selecciona "Pipeline" y da un nombre a tu proyecto.
Haz clic en "OK".
**5.3 Configurar el repositorio Git:
**En la sección "Source Code Management", selecciona "Git".
Ingresa la URL de tu repositorio GitLab y las credenciales necesarias.
**5.4 Configurar el webhook de GitLab:**
* En GitLab, ve a tu proyecto.
* Navega a "Settings" > "Webhooks".
* Añade una nueva URL de webhook apuntando a tu Jenkins.
* Selecciona los eventos que deseas que disparen el webhook, como "Push events".
**5.5 Añadir pasos de ejecución shell del pipeline**
pipeline {
agent any
environment {
DOCKER_HOST_URI = 'tcp://172.17.0.1:2375'
GIT_REPO = 'http://10.1.140.120/ada-microservices-ecosystem/frontends/mfsicof.git'
GIT_BRANCH = 'master'
CREDENTIALS_ID = 'f0a2166c-1e70-47b4-9205-5284d47227f1'
SLACK_CREDENTIALS = 'slack_secret_2'
SLACK_CHANNEL = 'ada-ecosystem-deploy'
SLACK_BASE_URL = 'https://slack.com/api'
SLACK_USERNAME = 'jenkins'
}
stages {
stage('Send Initial Notification') {
steps {
script {
def fullJobName = env.JOB_NAME.replaceAll('/', ' » ')
def buildCauseDescription = currentBuild.getBuildCauses().first().shortDescription
def initialMessage = "${fullJobName} - #${env.BUILD_NUMBER} Started by ${buildCauseDescription} (${env.BUILD_URL})"
slackSend(channel: env.SLACK_CHANNEL, message: initialMessage, color: 'good', tokenCredentialId: env.SLACK_CREDENTIALS)
}
}
}
stage('Checkout') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: GIT_BRANCH]],
doGenerateSubmoduleConfigurations: false,
extensions: [],
submoduleCfg: [],
userRemoteConfigs: [[
url: GIT_REPO,
credentialsId: CREDENTIALS_ID
]]
])
}
}
}
stage('Clean Workspace') {
steps {
script {
echo "*********************************SE LIMPIA EL ESPACIO DE TRABAJO**********************"
}
}
}
stage('Set Environment Variables') {
steps {
script {
echo "*********************************VARIABLES DE ENTORNO*********************************"
def tagName = sh(script: "jq -r '.version' package.json", returnStdout: true).trim()
def name = sh(script: "jq -r '.name' package.json", returnStdout: true).trim()
def containerName = name.replaceFirst(/^mf/, 'mf-')
def repositoryName = "ecosystemuser/${name}"
env.TAG_NAME = tagName
env.NAME = name
env.CONTAINER_NAME = containerName
env.REPOSITORY_NAME = repositoryName
echo " TAG_NAME=${env.TAG_NAME}"
echo " NAME=${env.NAME}"
echo " CONTAINER_NAME=${env.CONTAINER_NAME}"
echo " REPOSITORY_NAME=${env.REPOSITORY_NAME}"
}
}
}
stage('Docker Build & Push') {
steps {
script {
echo "************************EJECUCION DE COMANDOS DOCKER***************************"
sh """
export DOCKER_HOST=${env.DOCKER_HOST_URI}
docker build --no-cache --build-arg TYPE=prod -t ${env.REPOSITORY_NAME}:${env.TAG_NAME} .
docker tag ${env.REPOSITORY_NAME}:${env.TAG_NAME} ${env.REPOSITORY_NAME}:latest
docker push ${env.REPOSITORY_NAME}:${env.TAG_NAME}
docker push ${env.REPOSITORY_NAME}:latest
"""
echo "*******************TERMINA EJECUCION DE COMANDOS DOCKER***********************"
}
}
}
stage('Deploy Docker Container') {
steps {
script {
echo "*******************INICIA EJECUCION Deploy Docker Container***********************"
sh """
export DOCKER_HOST=${env.DOCKER_HOST_URI}
if [ \$(docker ps -aq -f name=${env.CONTAINER_NAME}) ]; then
docker stop ${env.CONTAINER_NAME} || true
docker rm -fv ${env.CONTAINER_NAME} || true
fi
docker run -d -p 8095:80 --name ${env.CONTAINER_NAME} ${env.REPOSITORY_NAME}:latest
"""
echo "*******************TERMINA EJECUCION Deploy Docker Container***********************"
}
}
}
stage('Merge Branches') {
steps {
script {
def warningMessage = ''
sh '''
# Configuración de usuario git
git config --global user.email "simon.gil@ada.co"
git config --global user.name "simon.gil"
GIT_USER='simon.gil'
GIT_TOKEN='jwPyfnRVxbD2ihgVPByN'
git config credential.helper "store --file=.git-credentials"
echo "http://${GIT_USER}:${GIT_TOKEN}@10.1.140.120" > .git-credentials
# Borrar las ramas locales develop, qa y pre-production si existen
delete_local_branches() {
for BRANCH in "$@"; do
if git show-ref --quiet refs/heads/\$BRANCH; then
echo "Borrando la rama local \$BRANCH..."
git branch -D \$BRANCH
fi
done
}
delete_local_branches develop qa pre-production
# Re-crear la rama master desde el remoto
git fetch origin master:master
git checkout master
'''
def branches = ['develop', 'qa', 'pre-production']
branches.each { branch ->
def branchWarningMessage = sh(script: """
# Función para fusionar ramas
merge_branch() {
BRANCH=\$1
if git ls-remote --exit-code --heads origin \$BRANCH >/dev/null 2>&1; then
echo "La rama \$BRANCH existe en el remoto. Haciendo pull y checkout..."
git fetch origin \$BRANCH:\$BRANCH
git checkout \$BRANCH
else
echo "La rama \$BRANCH no existe en el remoto. Creando una nueva rama basada en master..."
git checkout origin/master -b \$BRANCH
git push origin \$BRANCH
fi
CURRENT_VERSION=\$(jq -r '.version' package.json)
echo "La versión de \$BRANCH : \$CURRENT_VERSION"
git checkout master
MASTER_VERSION=\$(jq -r '.version' package.json)
echo "La versión de Master : \$MASTER_VERSION"
if dpkg --compare-versions "\$CURRENT_VERSION" "le" "\$MASTER_VERSION"; then
echo "La versión en la rama \$BRANCH es igual o menor que la versión en master. Realizando merge..."
git checkout \$BRANCH
git merge origin/master
git push origin \$BRANCH
else
echo "Advertencia: La versión en la rama \$BRANCH es superior a la versión en master. No se realizará el merge."
return 1
fi
git checkout \$BRANCH
}
merge_branch ${branch}
""", returnStatus: true)
if (branchWarningMessage != 0) {
echo "Agrego mensaje advertencia."
warningMessage += "Advertencia: La versión de la rama ${branch} es superior a la versión de master. No se realizará el merge en dicha rama.\n"
echo "Mostrar mensaje : (${warningMessage})"
}
}
sh '''
# Limpiar credenciales
rm .git-credentials
git config --unset credential.helper
'''
if (warningMessage) {
echo "Warning : (${warningMessage})"
echo "Agrego mensaje advertencia 2."
def WAR_NAME = warningMessage
env.WARNING_MESSAGE = WAR_NAME
echo "Mostrar mensaje : ${env.WARNING_MESSAGE}"
}
}
}
}
}
post {
success {
script {
echo "Sending Slack notification for SUCCESS..."
def fullJobName = env.JOB_NAME.replaceAll('/', ' » ')
def buildStatus = currentBuild.result ?: 'UNKNOWN'
def buildDuration = currentBuild.durationString
def customMessage = "${fullJobName} - #${env.BUILD_NUMBER} ${buildStatus} after ${buildDuration} (${env.BUILD_URL})"
slackSend(channel: env.SLACK_CHANNEL, message: customMessage, color: 'good', tokenCredentialId: env.SLACK_CREDENTIALS)
if (env.WARNING_MESSAGE) {
slackSend(channel: env.SLACK_CHANNEL, message: env.WARNING_MESSAGE, color: 'warning', tokenCredentialId: env.SLACK_CREDENTIALS)
}
}
}
failure {
script {
echo "Sending Slack notification for FAILURE..."
def fullJobName = env.JOB_NAME.replaceAll('/', ' » ')
def buildStatus = currentBuild.result ?: 'UNKNOWN'
def buildDuration = currentBuild.durationString
def customMessage = "${fullJobName} - #${env.BUILD_NUMBER} ${buildStatus} after ${buildDuration} (${env.BUILD_URL})"
slackSend(channel: env.SLACK_CHANNEL, message: customMessage, color: 'danger', tokenCredentialId: env.SLACK_CREDENTIALS)
if (env.WARNING_MESSAGE) {
slackSend(channel: env.SLACK_CHANNEL, message: env.WARNING_MESSAGE, color: 'warning', tokenCredentialId: env.SLACK_CREDENTIALS)
}
}
}
unstable {
script {
echo "Sending Slack notification for UNSTABLE..."
def fullJobName = env.JOB_NAME.replaceAll('/', ' » ')
def buildStatus = currentBuild.result ?: 'UNKNOWN'
def buildDuration = currentBuild.durationString
def customMessage = "${fullJobName} - #${env.BUILD_NUMBER} ${buildStatus} after ${buildDuration} (${env.BUILD_URL})"
slackSend(channel: env.SLACK_CHANNEL, message: customMessage, color: 'warning', tokenCredentialId: env.SLACK_CREDENTIALS)
if (env.WARNING_MESSAGE) {
slackSend(channel: env.SLACK_CHANNEL, message: env.WARNING_MESSAGE, color: 'warning', tokenCredentialId: env.SLACK_CREDENTIALS)
}
}
}
aborted {
script {
echo "Sending Slack notification for ABORTED..."
def fullJobName = env.JOB_NAME.replaceAll('/', ' » ')
def buildStatus = currentBuild.result ?: 'UNKNOWN'
def buildDuration = currentBuild.durationString
def customMessage = "${fullJobName} - #${env.BUILD_NUMBER} ${buildStatus} after ${buildDuration} (${env.BUILD_URL})"
slackSend(channel: env.SLACK_CHANNEL, message: customMessage, color: '#439FE0', tokenCredentialId: env.SLACK_CREDENTIALS)
if (env.WARNING_MESSAGE) {
slackSend(channel: env.SLACK_CHANNEL, message: env.WARNING_MESSAGE, color: 'warning', tokenCredentialId: env.SLACK_CREDENTIALS)
}
}
}
}
}
Activar esta configuración en el pipeline
{{:ada:howto:sicoferp:factory:new-migracion-sicoferp:front:opcion.png?800|}}
**5.6 Una vez configurado**
Probar el pipeline si funciona correctamente, replicar los webhook para cada ambiente.
[[ada:howto:sicoferp:factory:new-migracion-sicoferp:front|←Regresar]]