Contract Testing en CI: Uniendo PACT y Jenkins

Contract Testing en CI: Uniendo PACT y Jenkins

24 de noviembre de 2020

¿Qué vamos a ver?

Debido al auge de los sistemas basados en arquitecturas distribuidas, ya sea utilizando microservicios o no, el reto de conseguir asegurar la calidad en los despliegues y puestas en producción aumenta considerablemente. Como si se tratase de un puzzle, pasamos a tener muchas piezas que deben encajar entre sí para que todo funcione correctamente.

Es aquí donde técnicas como contract testing cobran una relevancia importante, ya que nos permitirá detectar estos problemas de comunicación, es decir piezas que no encajan, antes de llegar a poner los sistemas en funcionamiento. Este trabajo de prevención de errores hará que ganemos mucho tiempo en resolución y depuración de bugs.

En este artículo, veremos cómo integrar de manera adecuada contract testing en un workflow de integración continua. Concretamente, veremos el caso de integrar PACT en un pipeline de Jenkins, apoyándonos en la herramienta Pact Broker.

¿Por qué es interesante?

Una vez que entendemos el concepto de contract testing y tenemos claras sus ventajas, casi de manera inmediata, se nos plantea la duda de cómo incluir estas prácticas en nuestros proyectos del día a día de manera efectiva.

Es en este punto donde debemos tener claro, que contract testing se basa en comprobar que la manera en la que se comunican los sistemas es la adecuada. Por ello, obviamente, son un tipo de pruebas que no dependen únicamente de nuestra programación tal y como podrían ser las pruebas unitarias.

Es decir, dado que para la generación y verificación de contratos deberán actuar tanto la parte consumidora como productora de servicios, resulta especialmente conveniente disponer de un sistema externo que coordine este proceso y nos ofrezca información al respecto de manera rápida y clara.

Por ello, herramientas como Jenkins y el propio Pact Broker resultan muy útiles en este tipo de situaciones en las que queremos asegurar que los sistemas evolucionan de manera correcta, y que no estamos rompiendo la comunicación entre ellos durante el desarrollo de nuevas funcionalidades.

Funcionalidades principales

En este caso, de manera resumida, podemos ver las características principales de cada uno de los tres sistemas que intervienen: Pact, Pact Broker y Jenkins.

  • Pact
    • Generación y verificación de contratos.
    • Publicación de contratos en el Pact Broker.
  • Pact Broker
    • Albergar los contratos entre consumidor y proveedor.
    • Mostrar información sobre el estado de verificación de los contratos y versionado.
    • Lanzar webhooks ante eventos concretos previamente definidos.
  • Jenkins
    • Responsable de orquestar y coordinar las verificaciones tanto en consumidor como proveedor.
    • Publicar y avisar sobre el resultado de las builds.

¿Dónde lo puedes aplicar?

Casos de uso habituales

Como hemos comentado anteriormente, añadir la validación de contratos a un pipeline de Jenkins es recomendable en la mayoría de los casos, aunque particularmente adecuado cuando el desarrollo de proveedor y consumidor lo realizan equipos de desarrollo distintos ya que en esos casos, las posibilidades de introducir errores de compatibilidad de sistemas aumentan.

Dónde no es recomendable

En escenarios donde las APIs sufren muy pocas modificaciones y/o en sistemas con un número muy reducido de endpoints expuestos, quizá no sea algo que aporte demasiado valor y el coste de mantenimiento de la infraestructura necesaria supere las ventajas.

¿Cómo funciona?

A continuación veremos los pasos para validar pactos entre consumidor y proveedor en un workflow de integración continua con Jenkins y haciendo uso de Pact Broker.

Para ello vamos a implementar el workflow propuesto por pact.io

Paso previo

Disponer de un API-Token en Jenkins para un usuario.

Pasos para crear un API-TOKEN.

  1. Hacer click en el nombre de usuario de Jenkins (arriba a la derecha)
  2. En el menú izquierdo ir a "Configurar"
  3. Añadir un nuevo API TOKEN

ContractTesting_2

  1. Copiar el valor
  2. Para poder realizar llamadas POST desde fuera, lo más cómodo es codificar la clave en Base64 y pasarla como cabecera de autenticación:
  • Ir a <https://www.base64encode.org/> o utilizar cualquier otra herramienta de codificación.
  • Introducir: nombreUsuarioJenkins:API-TOKEN.
  • Por ejemplo: fran:11505d62fcdd42d3d7d645ee3f1414297c.
  • Guardar el código generado. Por ejemplo: ZnJhbjoxMWRlZWE5ODBiNmMxZmJkZWYxMjRlZGQ0ZWY3NjhkZWMx.
  1. Con esto, las llamadas que realicemos a nuestro Jenkins para lanzar los jobs deberán tener la cabecera:
  • Authorizartion: Basic ZnJhbjoxMWRlZWE5ODBiNmMxZmJkZWYxMjRlZGQ0ZWY3NjhkZWMx`.
Configurar Workflow

Los siguientes pasos, podrían tener sentido realizarlos en dos instancias de Jenkins independientes, nosotros lo haremos sobre la misma para simplificar el proceso.

Todo el código de ejemplo puede encontrarse en este repositorio.

  1. Configuración previa Pact Broker con docker
  • Para permitir la conexión entre el contenedor de docker y nuestro Jenkins local debemos añadir las siguientes líneas al archivo docker-compose.
  • PACT\_BROKER\_WEBHOOK\_SCHEME\_WHITELIST: http.
  • PACT\_BROKER\_WEBHOOK\_HOST\_WHITELIST: 192.168.0.12.
  1. Iniciar Pact Broker: docker-compose up

Fichero completo docker-compose para Pact Broker

version: "3"
services:
  postgres:
    image: postgres
    healthcheck:
      test: psql postgres --command "select 1" -U postgres
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: postgres
  broker_app:
    image: dius/pact-broker
    links:
      - postgres
    ports:
      - 8000:80
    environment:
      PACT_BROKER_BASIC_AUTH_USERNAME: pact_workshop
      PACT_BROKER_BASIC_AUTH_PASSWORD: pact_workshop
      PACT_BROKER_DATABASE_USERNAME: postgres
      PACT_BROKER_DATABASE_PASSWORD: password
      PACT_BROKER_DATABASE_HOST: postgres
      PACT_BROKER_DATABASE_NAME: postgres
      PACT_BROKER_WEBHOOK_SCHEME_WHITELIST: http
      PACT_BROKER_WEBHOOK_HOST_WHITELIST: 192.168.0.12 host.docker.internal
JOB - CONSUMER: Publicación de pactos
  1. Ir a Jenkins
  2. Crear una tarea de tipo "Pipeline"
  • En este caso la llamaremos: "PublishPacts"
  1. Este paso es opcional. Podríamos configurar esta tarea para que se ejecute cuando detecte cambios en el repositorio
  • Indicar el “GitHub Project” correspondiente
  • Configurar la periodicidad de consulta del repositorio
  • Por ejemplo, cada 5 minutos -> \*/5 \* \* \* \*
  1. Crear el pipeline para creación y publicación de pactos
  pipeline {
        agent any
        stages {
            stage('Get project'){
                steps{
                   git branch: '6-PactBroker', url: 'https://github.com/morvader/Pact-Workshop'
                   bat "npm install"
                }
            }
            stage('Create Pacts') {
                steps {
                    bat "npm run generate-pact-client"
                }
            }
            stage('Publish Pacts') {
                steps {
                    bat "npm run publish-pacts-Broker"
                }
            }
        }
    }
  1. Con esto, ya deberíamos visualizar los pactos en PactBroker

- <http://localhost:8000>

Contract Testing

JOB - PROVIDER: Verificar pactos
  1. Ir a Jenkins
  2. Crear una tarea de tipo "Pipeline"
  • En este caso la llamaremos: "VerifyPacts".
  1. Este pipeline realizará varias tareas:
  • Verificar que el pactos se cumplen.
  • Publicar los resultados de la verificación en el Pact Broker.
  • Llamar al comando de Pact "can i deploy" para saber si es seguro desplegar el servidor.
  1. Pipeline:
pipeline {
    agent any
    stages {
        stage('Get project') {
            steps {
                git branch: '6-PactBroker', url: 'https://github.com/morvader/Pact-Workshop'
                script {
                    if (isUnix()) {
                        sh 'npm install'
                    }
                    else {
                        bat 'npm install'
                    }
                }
            }
        }
        stage('Create Pacts') {
            steps {
                script {
                    if (isUnix()) {
                        sh 'npm run pact-server'
                    }
                    else {
                        bat 'npm run pact-server'
                    }
                }
            }
        }
        stage('Can-I-Deploy Server') {
            steps {
                script {
                    if (isUnix()) {
                        sh 'npx pact-broker can-i-deploy --pacticipant FilmsProvider --broker-base-url http://localhost:8000 --broker-username pact_workshop --broker-password pact_workshop --latest'
                    }
                    else {
                        bat 'npx pact-broker can-i-deploy --pacticipant FilmsProvider --broker-base-url http://localhost:8000 --broker-username pact_workshop --broker-password pact_workshop --latest'
                    }
                }
            }
        }
    }
}
  • Lo deseable es que este paso se ejecute automáticamente cada vez que los pactos se modifiquen. En el paso siguiente veremos cómo crear un Webhook desde Pact Broker.
  • Para ello será necesario que en la configuración de esta tarea marquemos el check de que se permita lanzar ejecuciones remotas y guardemos la URL sobre la que haremos las peticiones.

Contract Testing Sngular

JOB - CONSUMER: Publicar consumidor

Una vez que tanto proveedor como consumidor hayan publicado y verificado los pactos, como último paso del proceso, debemos verificar que es seguro desplegar el consumidor mediante la herramienta "can-i-deploy".

Para ello, en Jenkins crearemos otro proceso que sea llamado desde el Pact Broker cada vez que el proveedor verifique los pactos.

Paso a seguir:

  1. Crear tarea en Jenkins de tipo "Pipeline"
  • En este caso la llamaremos: "Deploy Consumer".
  1. Permitir que la tarea pueda ser ejecutada remotamente marcando el check correspondiente.
  2. Pipeline:
pipeline {
     agent any
     stages {
         stage('Can-I-Deploy Consumer') {
             steps {
                 dir('../PublishPacts'){
                 script {
                     if (isUnix()) {
                         sh 'npx pact-broker can-i-deploy --pacticipant FilmsClient --broker-base-url http://localhost:8000 --broker-username pact_workshop --broker-password pact_workshop --latest'
                     }
                     else {
                        bat 'npx pact-broker can-i-deploy --pacticipant FilmsClient --broker-base-url http://localhost:8000 --broker-username pact_workshop --broker-password pact_workshop --latest'
                     }
                 }
                 }
             }
         }
     }
}

Estado final

Al finalizar esta configuración tendremos en Jenkins tres jobs distintos para las tareas de:

  • Generación de contratos en cliente y publicación en Pact Broker.
  • Verificación de contratos en proveedor, publicación de resultados en Pact Broker y comprobar si es seguro desplegar el proveedor.
  • Comprobar si es es seguro desplegar el cliente.

Contract Testing Sngular

Crear Webhooks

Una vez que tenemos los pactos subidos y los pipelines creados en Jenkins, vamos a configurar dos webhooks en Pact Broker:

- Uno de ellos para que el proveedor pueda verificar la validez de los contratos cada vez que haya algún cambio en los mismos

- Otro para que el cliente sepa si puede desplegarse una vez que el proveedor publique los resultados.

Verificación del proveedor

Desde el Pact Broker

  1. Acceder a <http://localhost:8000>
  2. Si el paso anterior de la publicación ha funcionado correctamente, deberían mostrarse los pactos publicados
  3. En la columna de "Webhook status" pulsar en "Create"
  4. En la siguiente vista, en la fila de "pb:create", en la columna "NON-GET" pulsar en el símbolo "**!**"
  5. Se mostrará una ventana para introducir los valores de una petición POST para crear el webhook. En ella, introducir el siguiente BODY.
{
  //Eventos que harán que el webhook se ejecute
  //Para la demo, se ejecutará cada vez que se publique el contrato. En un entorno real, lo habitual sería lanzarlo cada vez que cambien los contratos
  "events": [
    {
       "name": "contract_content_changed"
    }
  ],
  "request": {
    "method": "POST",
    //URL del job del jenkins
    "url": "http://192.168.0.12:8080/job/VerifyPacts/build",
    "headers": {
      // Autorización anteriormente creada y codificada en base64
      "authorization": "Basic ZnJhbjoxMWRlZWE5ODBiNmMxZmJkZWYxMjRlZGQ0ZWY3NjhkZWMx"
    }
  }
}
¿Consumidor desplegable?

Creamos otro webhook para que el consumidor sepa si puede desplegarse una vez que el proveedor publique los resultados.

Para ello, creamos un webhook que estará ligado a la publicación de resultados por parte del proveedor, por lo que tendrá el siguiente BODY:

{
 "events": [
    {
      "name": "provider_verification_published"
    }
  ],
  "request": {
    "method": "POST",
    "url": "http://192.168.0.12:8080/job/DeployConsumer/build",
    "headers": {
      "authorization": "Basic ZnJhbjoxMWRlZWE5ODBiNmMxZmJkZWYxMjRlZGQ0ZWY3NjhkZWMx"
    }
  }
}

Este WebHook llamará al job previamente creado para comprobar si es seguro desplegar el cliente.

Herramienta “CAN I DEPLOY”

Esta utilidad, incluida en la instalación de básica de Pact, indicará cuándo es seguro desplegar alguna de las partes que formen parte de los pactos albergados en Pact Broker.

Se podrían dar las siguientes casuísticas:

  • Pactos publicados por el consumidor pero el consumidor aún no los ha verificado: FALSO. Motivo: No se puede determinar.
  • Pactos publicados por el consumidor pero fallan al ser verificados por el proveedor: FALSO. Motivo: Fallo en verificación.
  • Pactos publicados por el consumidor y el proveedor verifica que son correctos: OK. Motivo: Es seguro desplegar

Estos comandos podrían lanzarse desde línea de comandos en cualquier momento.

CHECK CLIENT

npx pact-broker can-i-deploy --pacticipant FilmsClient --broker-base-url http://localhost:8000 --broker-username pact\_workshop --broker-password pact\_workshop --latest

CHECK PROVIDER

npx pact-broker can-i-deploy --pacticipant FilmsProvider --broker-base-url http://localhost:8000 --broker-username pact\_workshop --broker-password pact\_workshop --latest

El resultado será una salida por consola de “SUCCESS” o “FAILURE” dependiendo del estado de los contratos.

Qué nos ha parecido

Ventajas

Llegar a integrar contract testing con en un workflow de integración continua resulta la manera más efectiva de aplicar esta estrategia de testing. Como hemos comentado anteriormente, al involucrar a varios sistemas distintos resulta esencial contar con mecanismos de sincronización que conozcan el estado de cada parte en todo momento.

Por otro lado, estos trabajos sólo serán necesarios realizarlos una vez al inicio de la configuración por lo que no será algo que requiera demasiado mantenimiento futuro. En principio, parece que el esfuerzo inicial compensa sobradamente.

Inconvenientes

Configurar adecuadamente el workflow completo requiere de muchas partes distintas, por lo que habrá tener conocimientos, al menos básicos, de varias herramientas y tecnologías. En algunos equipos esto puede suponer una curva de aprendizaje bastante pronunciada si se desconocen conceptos esenciales para este escenario como: pipelines, webhooks, docker y conceptos sobre consumer-driven contract testing.

En definitiva, es conveniente tener claros los conceptos básicos sobre los que se sustenta esta configuración.

Conclusiones

La propia naturaleza de la estrategia de contract testing en proyectos reales, se queda “coja” sin un workflow de integración continuo adecuadamente configurado.

Para aprovechar de manera completa todas las ventajas que aporta esta estrategia de pruebas es fundamental invertir tiempo y esfuerzo en esta parte.

Todo el código de ejemplo puede encontrarse en este repositorio.

Referencias

<https://github.com/morvader/Pact-Workshop/tree/6-PactBroker>

<https://docs.pact.io/pact_broker/webhooks>

<https://blog.testproject.io/2020/06/09/integrating-consumer-contract-testing-in-build-pipelines/>

<https://kreuzwerker.de/post/integrating-contract-tests-into-build-pipelines-with-pact-broker-and>