Recientemente logré la fusión de un pull request en el popular framework Phoenix, y lo hice sin escribir ningún código Elixir. Tampoco escribí ninguna documentación. Lo que hice fue ayudar a mejorar el proceso de construcción.

En este post, me gustaría compartir las mejoras que hice a su proceso de construcción. Estas mejoras no son específicamente al framework Phoenix y quizás cambien la forma en que te aproximas a la integración continua.

Pero primero, algo de contexto.

¿Qué es el Framework Phoenix?

Phoenix es un framework web con algunas propiedades muy interesantes. Con Phoenix, puedes crear aplicaciones web interactivas enriquecidas sin escribir código del lado del cliente.

Puede hacer esto usando una función llamada LiveView que envía actualizaciones en tiempo real desde el servidor para actualizar el HTML del navegador del cliente.

Podemos crear una página que muestre los últimos tweets sobre un tema, en tiempo real, con bastante facilidad.

Aquí hay un ejemplo:

defmodule TimelineLive do
  use Phoenix.LiveView

  def render(assigns) do
    render("timeline.html", assigns)
  end

  def mount(_, socket) do
    Twitter.subscribe("elixirphoenix")
    {:ok, assign(socket, :tweets, [])}
  end

  def handle_info({:new, tweet}, socket) do
    {:noreply,
     update(socket, :tweets, fn tweets ->
       Enum.take([tweet | tweets], 10)
     end)}
  end
end
Controlador Phoenix LiveView para una aplicación de Twitter
imgs%2Fapp%2FCorecursive%2FDUy3Kzmdsn
Resultados de Twitter en tiempo real sin JavaScript escrito

El framework está escrito en el lenguaje de programación Elixir.

Fue creado por José Valim. Se parece mucho a Ruby, pero tiene una semántica muy diferente. Elixir se ejecuta en una máquina virtual (VM) Erlang, e impulsa proyectos como Discord y se utiliza en empresas como Heroku.

¿Cómo reproducir las compilaciones?

El Framework Phoenix usa Acciones de GitHub en su proceso de compilación. Como muchos grandes proyectos, tienen un conjunto de pruebas unitarias que necesitan ejecutar en cada contribución de los usuarios.

Sin embargo, aquí no es donde se detienen sus esfuerzos de prueba. También tienen un conjunto de pruebas de integración. Phoenix usa un ORM para comunicarse con varias bases de datos y las pruebas de integración garantizan que ningún cambio interrumpa la integración con cualquiera de las 3 bases de datos compatibles.

Este es un patrón común. Tener una gran cantidad de pruebas unitarias que sean fáciles de ejecutar y un puñado de pruebas de integración más lentas, pero más completas, es una excelente manera de evitar que se introduzcan errores en el proyecto.

Sin embargo, Phoenix Framework lleva esto aún más lejos, ya que también necesitan admitir varias versiones del lenguaje Elixir y un puñado de versiones de Open Telecom Platform (OTP).

Esto empieza a parecer complejo. Tenemos que probar cada cambio con todas las combinaciones de lo siguiente:
Bases de datos (Postgres, MySQL MSSQL)
Elixir (versión actual y anterior)
OTP (versión actual y anterior)

Esto empieza a parecer complejo. Tenemos que probar cada cambio con todas las combinaciones de lo siguiente:

  • Bases de datos (Postgres, MySQL MSSQL)
  • Elixir (versión actual y anterior)
  • OTP (versión actual y anterior)

Es relativamente fácil configurar esto en Acciones de GitHub, pero ¿cómo ejecutarías estas pruebas localmente?

Instalar todo esto sería mucho pedir, por lo que los contribuyentes tienden a confiar en las Acciones de GitHub para probar estas combinaciones. Sin embargo, si todos tienen que confiar en enviar cosas a GitHub, y ver si las pruebas pasan, entonces el desarrollo se ralentiza.

¿Cómo arreglamos esto?

¿Cómo unificar las ejecuciones de pruebas?

Aquí es donde me involucré. Trabajo en Earthly Technologies como defensor de los desarrolladores de código abierto. Tenemos una herramienta de compilación de código abierto bastante interesante, y aunque ocasionalmente contribuyo directamente al proyecto, mi trabajo es ser el punto de contacto entre la comunidad que usa la herramienta y el equipo que trabaja en ella.

Había oído hablar de este problema de reproducibilidad que estaba teniendo el equipo de Phoenix. Pensé que podría ayudar a escribir un script de compilación que podría usarse tanto en Acciones de GitHub como para un flujo de trabajo de desarrollo local. Así que me puse a trabajar en un PR (Pull Request).

Ejecutando las pruebas localmente

Lo que terminé creando, ligeramente simplificado, es esto:

setup:
   ARG ELIXIR=1.10.4
   ARG OTP=23.0.3
   # Pull a Docker Image to Run Build Inside Of
   FROM hexpm/elixir:$ELIXIR-erlang-$OTP-alpine-3.12.0
   ...
 
integration-test:
    FROM +setup
    COPY . .
    # Pull In Dependencies
    RUN mix deps.get 
    # Start Up Service Dependencies
    WITH DOCKER --compose docker-compose.yml 
        # Run Tests
        RUN mix test --include database 
    # Stop Service Dependencies
    END

Este es un Earthfile. Está compuesto por varios objetivos, como setup y integration-test. Los objetivos pueden tener dependencias entre ellos. Puede usar la herramienta de línea de comandos earthly para ejecutar cualquier objetivo y cada uno se ejecuta en un contenedor Docker. La contenerización nos permitirá ejecutar la compilación donde elijamos.

Este ejemplo ejecuta la prueba de integración integration-test dentro de un contenedor Docker hexpm/elixir con la versión especificada de Elixir y OTP instaladas.

Antes de ejecutar las pruebas con mix test --include database, usamos Docker compose para iniciar todas las dependencias necesarias:

 WITH DOCKER --compose docker-compose.yml
        RUN mix test --include database
 END 

El archivo de Docker compose luce así:

version: '3'
services:
  postgres:
    image: postgres
    ports:
      - "5432:5432"
    environment:
      POSTGRES_PASSWORD: postgres
  mysql:
    image: mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
  mssql:
    image: mcr.microsoft.com/mssql/server:2019-latest
    environment:
      ACCEPT_EULA: Y
      SA_PASSWORD: some!Password
    ports:
      - "1433:1433"	

Estas son las bases de datos que necesitamos para probar Phoenix.

Ahora podemos ejecutar las pruebas de integración en la línea de comando así:

>  earthly -P +integration-test

Y si queremos probar una versión diferente de Elixir, podemos especificar la versión como argumentos de compilación:

 > earthly -P --build-arg ELIXIR=1.11.0 --build-arg OTP=23.1.1 +integration-test

Hay otras formas de lograr esto. Una combinación de makefile y dockerfile también habría funcionado. La clave es sacar la lógica de compilación de un formato específico de GHA y convertirla en algo que se pueda ejecutar en cualquier lugar.

Cómo ejecutarlo en Acciones de GitHub

Para usar este mismo proceso dentro de Acciones de GitHub, lo único que tenemos que hacer es ajustar nuestro yaml de Acciones de GitHub para usar Earthly para la canalización de compilación y estamos listos.

  integration-test-elixir:
    runs-on: ubuntu-latest
    env:
      FORCE_COLOR: 1
    
    strategy:
      fail-fast: false
      matrix:
        include:
          - elixir: 1.11.1
            otp: 21.3.8.18
          - elixir: 1.11.1
            otp: 23.1.1
    steps:
      - uses: actions/checkout@v2
      - name: Download released earth
        run: "sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/download/v0.4.1/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly'"
      - name: Execute tests
        run: earthly -P --build-arg ELIXIR=${{ matrix.elixir }}  --build-arg OTP=${{ matrix.otp }} +integration-test

Ahí vamos, ahora podemos ejecutar nuestro proceso de compilación localmente, sin ninguna configuración de entorno compleja. También podemos ejecutar el mismo proceso de compilación en nuestra máquina de desarrollo sin necesidad de instalar nada excepto Earthly. Esto facilita el acercamiento de nuevos contribuyentes al proyecto.

Resultado final

Finalmente, con la ayuda del equipo Phoenix, obtuve la aprobación de este cambio y el proyecto Phoenix ahora tiene una manera fácil de probar e iterar en su canal de compilación localmente. ¡Y ni siquiera escribí ningún código de Elixir! Puede encontrar más detalles en el PR.

Gracias por leer este artículo. Si deseas obtener más información sobre Earthly, puedes encontrar mucha aquí. Y si deseas mi ayuda en la construcción de tu proyecto de código abierto, házmelo saber.

Traducido del artículo de Adam Gordon Bell - How I Contributed to a Major Open Source Project Without Writing Any Code