GitHub Actions permite crear flujos de trabajo (workflows) que se pueden utilizar para compilar, probar y desplegar código, dando la posibilidad de crear flujos de integración y despliegue continuo dentro del propio repositorio de git.

Los flujos de trabajo tienen que contener al menos un job. Estos incluyen una serie de pasos que ejecutan tareas individuales que pueden ser acciones o comandos. Un flujo de trabajo puede comenzar por distintos eventos que suceden dentro de GitHub, como un push al repositorio o un pull request.

En este tutorial vamos a crear un workflow para ejecutar las pruebas unitarias de un proyecto Laravel y realizar el despliegue por ssh a un servidor.

Puedes ver el workflow funcionando en este repositorio de demo.

Creando el archivo del workflow

Los archivos de workflow deben ser almacenados en la carpeta .github/workflows en formato yaml en la raíz del repositorio.

Crea el archivo .github/workflows/ci-cd.yml con el siguiente contenido:

name: ci/cd workflow
 
on:
 push:
   branches:
     - master
     - features/*
 pull_request:
 
jobs:
 tests:
   runs-on: ubuntu-latest
   strategy:
     matrix:
       php: [7.3, 7.4]
 
   name: Test - PHP ${{ matrix.php }}
 
   steps:
     - name: Checkout code
       uses: actions/[email protected]
 
     - name: Cache PHP dependencies
       uses: actions/[email protected]
       with:
         path: vendor
         key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
 
     - name: Setup PHP
       uses: shivammathur/[email protected]
       with:
         php-version: ${{ matrix.php }}
         extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd
         coverage: none
 
     - name: Copy ENV Laravel Configuration for CI
       run: php -r "file_exists('.env') || copy('.env.example', '.env');"
 
     - name: Install dependencies
       run: composer install --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist
 
     - name: Generate key
       run: php artisan key:generate
 
     - name: Execute tests
       run: vendor/bin/phpunit --verbose
 
 deploy:
   runs-on: ubuntu-latest
   needs: tests
   if: github.ref == 'refs/heads/master'
   steps:
     - name: Checkout code
       uses: actions/[email protected]
 
     - name: Cache PHP dependencies
       uses: actions/[email protected]
       with:
         path: vendor
         key: dependencies-php-composer-${{ hashFiles('**/composer.lock') }}
 
     - name: Setup PHP
       uses: shivammathur/[email protected]
       with:
         php-version: 7.3
         extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd
         coverage: none
 
     - name: Install dependencies
       run: composer install --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist
 
     - name: Copy dotenv file
       run: php -r "file_put_contents(__DIR__ . '/.env', '${{ secrets.DOT_ENV }}');"
 
     - name: Deploy
       uses: AEnterprise/[email protected]
       env:
         DEPLOY_KEY: ${{ secrets.KEY }}
         ARGS: '-avzh --exclude=".*"'
         SERVER_PORT: ${{ secrets.PORT }}
         FOLDER: "./"
         SERVER_IP: ${{ secrets.HOST }}
         USERNAME: ${{ secrets.USERNAME }}
         SERVER_DESTINATION: "/var/www/html"

Configurando los secretos

Con Github Actions podemos manejar secretos, que son información sensible (como contraseñas, api keys, etc) que no debería estar versionada en nuestro repositorio.

Debemos seguir los siguientes pasos para configurar los secretos que necesitaremos en nuestro workflow:

  1. Dentro de nuestro repositorio hacer clic en Settings y luego en Secrets.
  2. Hacer clic en Add a new secret para añadir un nuevo secreto al repositorio.
  3. En Name escribir DOT_ENV y en Value copiar el contenido del archivo .env que queremos desplegar en nuestro servidor.
  4. Hacer clic en Add secret para guardar el secreto.
  5. Repetir los pasos 2 a 4 para crear secretos que se llamen HOST, PORT, USERNAME y KEY. Deben tener los valores para poder conectarse por ssh al servidor al que queramos desplegar.

Entendiendo el Workflow

Vamos a analizar cada sección del workflow para entender su funcionamiento.

Definir eventos

on:
 push:
   branches:
     - master
     - features/*
 pull_request:

En esta sección definimos que el workflow se ejecute cuando se haga un push en la rama master o cualquier rama que empiece con feature; o cuando se realice un pull request.

Definir el Job para las pruebas

jobs:
 tests:
   runs-on: ubuntu-latest
   strategy:
     matrix:
       php: [7.3, 7.4]
 
   name: Test - PHP ${{ matrix.php }}

Estamos definiendo el job «tests» que tendrá los pasos para ejecutar nuestras pruebas unitarias. Además, le indicamos que se ejecute en una máquina con la última versión de Ubuntu (ésta será provisionada de forma automática por GitHub).

En la sección de strategy estamos utilizando la función de matrix. Esto nos va a permitir ejecutar el job pasando como variable dos versiones de PHP.

El valor de name se utilizará para poder identificarlo dentro de GitHub.

Definir Steps para el Job de pruebas

Los steps son los pasos que va a ejecutar nuestro job.

Descargar el código

     - name: Checkout code
       uses: actions/[email protected]

Esta acción descarga el código del repositorio dentro de la máquina virtual.

Realizar un caché de dependencias

     - name: Cache PHP dependencies
       uses: actions/[email protected]
       with:
         path: vendor
         key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}

Esta acción nos permite hacer un cache de las librerías de Composer que se mantendrá entre cada ejecución del workflow. Esto nos ayuda a acelerar el workflow en caso que no se haya modificado el archivo composer.json.

Instalar PHP

     - name: Setup PHP
       uses: shivammathur/[email protected]
       with:
         php-version: ${{ matrix.php }}
         extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd
         coverage: none

Vamos a utilizar la acción shivammathur/setup-php para instalar php y las dependencias que necesitamos. Esta instalación de PHP incluye Composer.

Estamos utilizando la sintaxis ${{ matrix.php }} para inyectar el valor del matrix que definimos al inicio del job. Este valor será inyectado automáticamente por GitHub Actions.

Creando el archivo .env para las pruebas

     - name: Copy ENV Laravel Configuration for CI
       run: php -r "file_exists('.env') || copy('.env.example', '.env');"

Estamos ejecutando un comando de php para renombrar el archivo .env.example a .env que es necesario para poder ejecutar las pruebas. También podrías crear un archivo .env.ci con las configuraciones que necesite tu proyecto para realizar el flujo de Integración Continua.

Instalar dependencias de Composer

     - name: Install dependencies
       run: composer install --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist

Realizamos un composer install para instalar las dependencias de nuestro proyecto. Los parámetros extra nos permiten realizar la instalación sin que dependa de la interacción con una persona.

Generar key

     - name: Generate key
       run: php artisan key:generate

El archivo .env.example no cuenta con un API_KEY, por lo que tenemos que ejecutar el comando de Artisan correspondiente para generarla.

Ejecutar pruebas

     - name: Execute tests
       run: vendor/bin/phpunit --verbose

Utilizamos el comando de phpunit para ejecutar las pruebas unitarias.

Definir el Job para el despliegue

 deploy:
   runs-on: ubuntu-latest
   needs: tests
   if: github.ref == 'refs/heads/master'

En el job de despliegue estamos utilizando dos parámetros nuevos:

  • El parámetro needs indica que este job tiene que esperar que el job de tests se complete antes de ejecutarse. De esta manera nos aseguramos de no desplegar nuestro código si las pruebas unitarias están fallando.
  • El parámetro if nos permite decir que este job solo debe ser ejecutado si estamos en la rama master.

Definiendo los Steps para el Job de despliegue

Los primeros cuatro pasos para configurar el ambiente son los mismos que en el job de tests.

Crear el archivo .env para producción

    - name: Create dotenv file
      run: php -r "file_put_contents(__DIR__ . '/.env', '${{ secrets.DOT_ENV }}');"

Creamos el archivo .env utilizando el contenido que guardamos en el secreto con el nombre DOT_ENV.

Desplegar el proyecto

     - name: Deploy
       uses: AEnterprise/[email protected]
       env:
         DEPLOY_KEY: ${{ secrets.KEY }}
         ARGS: '-avzh --exclude=".*"'
         SERVER_PORT: ${{ secrets.PORT }}
         FOLDER: "./"
         SERVER_IP: ${{ secrets.HOST }}
         USERNAME: ${{ secrets.USERNAME }}
         SERVER_DESTINATION: "/var/www/html"

Estamos desplegando a una instancia Linux utilizando una acción de Github que utiliza rsync. En los parámetros de ambiente utilizamos la información guardada en los secretos para hacer la conexión por ssh utilizando una llave .pem. Especificamos que queremos copiar todo el contenido utilizando los caracteres ./ en el parámetro FOLDER.

El Workflow en acción

Cuando realizamos un nuevo push a la rama master con el archivo, vamos a poder ver cómo se ejecuta el workflow dentro de la pestaña Actions de nuestro repositorio.

Al hacer clic en cualquier workflow vamos a ver en la barra lateral todos los jobs que se han ejecutado. Al hacer clic en un job en la parte derecha aparecerán todos los pasos con el estado de su ejecución.

Agregando un Badge para visualizar el estado del workflow en el Readme.md

Una funcionalidad común es mostrar el resultado del flujo de CI/CD mediante badges dentro del Readme.md del proyecto:

Para obtener la imagen con el estado del workflow, se puede utilizar una url con la siguiente estructura:

https://github.com/<OWNER>/<REPOSITORY>/workflows/<WORKFLOW_NAME>/badge.svg

Para agregarla dentro del README.md tenemos que utilizar la sintaxis de markdown para agregar la imagen:

![](https://github.com/actions/hello-world/workflows/Greet%20Everyone/badge.svg)

 

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.