CI para Python usando GitHub Actions

CI para Python usando GitHub Actions
Photo by Roman Synkevych / Unsplash

Tener herramientas que nos ayuden a automatizar nuestro flujo de trabajo es extremadamente útil, y que además nos facilite incorporar otras herramientas para optimizar nuestros procesos de calidad y seguridad (entre otros) es impresionante, pero que adicional a ello sea gratis y muy fácil de hacer ya es otro nivel.

GitHub realmente ha cambiado la forma de automatizar el ciclo de vida de desarrollo de software en los últimos años, principalmente porque ha integrado un montón de herramientas que realmente ayudan a los desarrolladores, solo por mencionar algunas como VSCode, Code Server, Workspaces, ghcr, Copilot y por supuesto Actions con la que estaremos jugando un poco el día de hoy.

Qué viene a reemplazar GitHub Actions?

Pues depende mucho de como lo uses, pero principalmente viene a dejar un poco atrás a herramientas como Jenkins o CircleCI, principalmente porque ya esta aprovisionado, solo debes usarlo y además incluye un montón de integraciones que puedes ejecutar directamente en la infraestructura de GitHub sin tener que preocuparte casi que por nada.

Marketplace

En el marketplace vamos a encontrar un sin número de extensiones que permiten integrar con soluciones de terceros, pero también muchas de ellas que puedes ejecutar de forma contenerizada dentro de los recursos de GitHub sin pagar un solo dólar, y eso exactamente vamos a ver aquí sobre como usar herramientas gratuitas para construir un servicio desarrollado en Python 3.

Qué se hace generalmente en un proceso de CI?

Partamos que CI viene del Inglés Continuous Integration (o Integración continua en Español), y se refiere a una práctica DevOps que se ejecuta cada vez que un desarrollador integra código nuevo en el repositorio, ejecutando tareas como: construcción / compilación, pruebas, controles de calidad y seguridad, entre otros.

.github > workflows > ci-action.yml

Para iniciar usando GitHub Actions debemos crear una ruta similar a esta en la raíz de nuestro repositorio de código en GitHub, dónde usando el formato YAML vamos a definir un conjunto de pasos a realizar.

Ruta de ejemplo
name: Python application CI

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

env:
  REGISTRY: "ghcr.io"
  IMAGE_NAME: ${{ github.repository }}
  IMAGE_TAG: ${{ github.sha }}

permissions:
  packages: write
  contents: read
  security-events: write

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - name: Set up Python 3.${{ vars.PYTHON_MINOR_VERSION }}
        uses: actions/setup-python@v3
        with:
          python-version: "3.${{ vars.PYTHON_MINOR_VERSION }}"
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pip-tools
          python -m piptools compile -o src/requirements.txt src/pyproject.toml
          pip install flake8 pytest coverage 
          if [ -f src/requirements.txt ]; then pip install -r src/requirements.txt; fi
      - name: Lint with flake8
        run: |
          # stop the build if there are Python syntax errors or undefined names
          flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
          # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
          flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
      - name: Test with pytest
        run: |
          coverage run -m pytest
          coverage report -m

      - name: Build and push Docker images
        uses: docker/build-push-action@v6.18.0
        with:
          context: ./
          file: Dockerfile.actions
          pull: true
          build-args: |
            PYTHON_VERSION=3.${{ vars.PYTHON_MINOR_VERSION }}
          tags: |
            "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
            "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}"

      - name: Run 2ms Scan
        run: docker run -v $(pwd):/repo checkmarx/2ms:2.8.1 git /repo

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@0.32.0
        with:
          image-ref: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}"
          format: "sarif"
          output: "trivy-results.sarif"
          ignore-unfixed: true
          severity: "CRITICAL,HIGH"

      - name: Upload Trivy scan results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: "trivy-results.sarif"

      - name: Get changed files
        id: changed-files
        uses: tj-actions/changed-files@2f7c5bfce28377bc069a65ba478de0a74aa0ca32 #v46.0.1
        with:
          separator: ','
        
      - name: Vorpal with reviewdog
        if: ${{ steps.changed-files.outputs.all_changed_files != '' }}
        uses: checkmarx/vorpal-reviewdog-github-action@v1.0.0
        with:
          source_path: "${{ steps.changed-files.outputs.all_changed_files }}"
          filter_mode: file
          github_token: ${{ secrets.github_token }}
          reporter: github-pr-check
          level: error
          fail_on_error: false

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }} # Or your PAT secret

      - name: push image to ghcr.io
        run: docker push "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" --all-tags

En este ejemplo tenemos algunas secciones que me gustaría destacar:

  • Nombre del Workflow
  • Disparadores: Ejemplo push o pull-request
  • Variables de entorno: Variables de configuración que podemos usar en los pasos
  • Permisos: Definir a que recursos tiene acceso el workflow
  • Jobs: Acciones que queremos automatizar por ejemplo el build
  • Ejecutar en: La infraestructura de computo que vamos a usar para realizar la tarea podemos usar gratis contenedores
  • Pasos: Tareas que definimos para completar la acción

Cómo se ve este ejemplo en la práctica?

Vista Actions en nuestro Repo
Checks en cada Pull Request
Reportes de Seguridad
Imagen Docker en ghcr.io

Éste es solo un ejemplo sencillo que espero te ayude a dar el paso de iniciar en el uso de GitHub Actions en tu flujo de trabajo, además invitarte a adoptar practicas DevOps y DevSecOps con el fin de no perder del radar los procesos de calidad y seguridad y ejecutarlos de forma temprana cómo buena práctica.

Gracias por llegar hasta este punto, nos vemos en el próximo post.

Read more