DEV Community

Cover image for Crear una canalización de CI/CD con GitHub Actions
Daniel J. Saldaña
Daniel J. Saldaña

Posted on • Originally published at danieljsaldana.dev on

Crear una canalización de CI/CD con GitHub Actions

Vamos a desarrollar una solución para una canalización de CI/CD con GitHub Actions.

Para ello vamos a abordar distintos aspectos como pueden ser:

  • Pasar varios code linters en busca de errores de sintaxis
  • Generar un diagrama del contendido del repositorio
  • Autoincrementar la versión del package
  • Automatizar la creación de la release en GitHub
  • Automatizar la actualización del changelog conforme a los cambios en la release
  • Eliminar las últimas 10 release y tags
  • Generar reporte de PageSpeed Insights y adjuntar en el pipeline
  • Generar la imagen Docker y subirla al Registry de GitHub
  • Buscar información sensible “contraseñas o token” en los commit
  • Automatizar la creación de la pull request conforme a una plantilla
  • Crear distintos disparadores en el pipeline según el contenido del commit

Ahora que tenemos los puntos definidos de lo que queremos realizar en nuestra automatización, vamos a comenzar.

GitHub - danieljesussp/danieljsaldana-terminal

Para empezar, vamos a crear nuestro fichero con todos los jobs con los que vamos a trabajar.

📋 En relación con los tutoriales anteriores, en este introduciremos los eventos que disparan nuestro worflow, condicionales y la necesidad de que se complete un job anterior.

Tenemos dos disparados distintos. Por un lado, tenemos el disparador task completed e issue resolved. Cada uno de ellos, nos permitirá controlar que jobs se van a ejecutar según el contenido commit.

name: CI + CD

on:
  push:
    branches:
      - "**"
      - "!production"
  pull_request:
    branches:
      - "**"
    types:
      - synchronize
      - closed

concurrency:
  group: ci-tests-${{ github.ref }}-1
  cancel-in-progress: true

jobs:
  gitguardian:
    name: GitGuardian scan
    if: github.event_name == 'pull_request' && github.event.action == 'synchronize' || contains(github.event.head_commit.message, 'task completed') || contains(github.event.head_commit.message, 'issue resolved')
    uses: ./.github/workflows/gitguardian.yml
    secrets:
      GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}

  super_linter:
    name: Super-Linter scan
    if: github.event_name == 'pull_request' && github.event.action == 'synchronize' || contains(github.event.head_commit.message, 'task completed') || contains(github.event.head_commit.message, 'issue resolved')
    uses: ./.github/workflows/super-linter.yml

  create_pull_request:
    name: Create pull request
    if: contains(github.event.head_commit.message, 'issue resolved')
    needs: [super_linter, gitguardian]
    uses: ./.github/workflows/create-pull-request.yml

  repo_visualizer:
    name: Update diagram
    if: github.event.pull_request.merged == true
    uses: ./.github/workflows/repo_visualizer.yml

  release:
    name: Create release
    if: github.event.pull_request.merged == true
    needs: [repo_visualizer]
    uses: ./.github/workflows/release.yml

  delete_older_releases:
    name: Delete older releases
    if: github.event.pull_request.merged == true
    needs: [release]
    uses: ./.github/workflows/delete-tag-and-release.yml

  lighthouse:
    name: Lighthouse check action
    if: github.event.pull_request.merged == true
    uses: ./.github/workflows/lighthouse.yml

  build_and_push_to_registry:
    name: Build and push Docker image to GitHub Packages
    if: github.event.pull_request.merged == true
    needs: [release]
    uses: ./.github/workflows/build.yml

  trivy_scan:
    name: Trivy image scan
    if: github.event.pull_request.merged == true
    needs: [build_and_push_to_registry]
    uses: ./.github/workflows/trivy-image.yml

Enter fullscreen mode Exit fullscreen mode

En este job vamos a revisar que nuestros commit no contengan ningún token o contraseña.

💡 Aquí podríamos agregar otros jobs, como por ejemplo test funcionales, pero esto lo dejaremos para más adelante.

name: GitGuardian scan

on:
  workflow_call:
    secrets:
      GITGUARDIAN_API_KEY:
        required: false

jobs:
  security:
    name: GitGuardian scan
    runs-on: ubuntu-latest
    steps:
      - name: "☁️ checkout repository"
        uses: actions/checkout@v3
        with:
          fetch-depth: 0 # fetch all history so multiple commits can be scanned

      - name: GitGuardian scan
        uses: GitGuardian/gg-shield-action@master
        env:
          GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }}
          GITHUB_PUSH_BASE_SHA: ${{ github.event.base }}
          GITHUB_PULL_BASE_SHA: ${{ github.event.pull_request.base.sha }}
          GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
          GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}

Enter fullscreen mode Exit fullscreen mode

Esta es una solución de las muchas que podemos encontrar, ya que verifica que nuestros ficheros no tengan errores de sintaxis. Esto puede ser interesante si tenemos manifiestos Kubernetes o los propios pipeline.

name: Lint Code scan

on:
  workflow_call:

jobs:
  super-linter:
    runs-on: ubuntu-latest
    steps:
      - name: "☁️ checkout repository"
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Lint Code Base
        uses: github/super-linter@v4
        env:
          DEFAULT_WORKSPACE: .github
          VALIDATE_ALL_CODEBASE: false
          DEFAULT_BRANCH: production
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Archive super-linter artifacts
        uses: actions/upload-artifact@v2
        with:
          name: MegaLinter reports
          path: |
            super-linter.log

Enter fullscreen mode Exit fullscreen mode

Ahora vamos a automatizar la apertura de la pull request.

name: Create pull request

on:
  workflow_call:

jobs:
  create-pull-request:
    runs-on: ubuntu-latest
    steps:
      - name: "☁️ checkout repository"
        uses: actions/checkout@v3

      - name: Version Increment
        id: version
        run: |
          echo " **********************"
          git config user.name "$GITHUB_ACTOR"
          git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
          npm version minor -m "v%s"
          version=$(node -p "require('./package.json').version")
          echo "::set-output name=version::${version}"
          echo " **********************"

      - name: Create Pull Request
        id: cpr
        uses: peter-evans/create-pull-request@v4
        with:
          commit-message: Create pull request
          committer: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
          author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
          signoff: false
          branch: ${{ github.ref }}
          delete-branch: true
          base: production
          title: "v${{ steps.version.outputs.version }} release"
          body: |
            # Cambios
            <!-- Incluya un resumen del cambio y qué problema se solucionó. -->
            <!-- Incluya también la motivación y el contexto pertinentes. -->
            <!-- Enumere las dependencias necesarias para este cambio. -->

            Fixes # (issue)

            ## De qué se trata este PR

            - Ingrese una breve descripción para este PR

            ### Ejecuciones de prueba

            - [Run actions](<>)

            ## Tipo de cambio
            <!-- Elimine las opciones que no sean relevantes. -->
            - [] 📚 Actualización de documentación
            - [] 🧪 Casos de prueba
            - [] 🐞 Corrección de errores (cambio continuo que soluciona un problema)
            - [] 🔬 Nueva característica (cambio continuo que agrega funcionalidad)
            - [] 🚨 Cambio importante (corrección o característica que haría que la funcionalidad existente no funcionara como se esperaba)
            - [] 📝 Este cambio requiere una actualización de documentación

            ## Checklist

            - [] Mi código sigue las pautas de estilo de este proyecto
            - [] He realizado una auto-revisión de mi propio código
            - [] He comentado mi código, particularmente en áreas difíciles de entender
            - [] He realizado los cambios correspondientes a la documentación.
            - [] Mis cambios no generan nuevas advertencias
            - [] ¿Actualizó CHANGELOG en caso de un cambio importante?
          labels: |
            automated pr
          assignees: ${{ github.actor }}
          reviewers: ${{ github.actor }}
          draft: false

Enter fullscreen mode Exit fullscreen mode

Ahora vamos a una imagen svg con el que podamos tener un gráfico de burbujas del contenido de nuestro repositorio.

name: Repo Visualizer

on:
  workflow_call:

jobs:
  repo-visualizer:
    name: Repo Visualizer
    runs-on: ubuntu-latest
    steps:
      - name: "☁️ checkout repository"
        uses: actions/checkout@v3

      - name: Update diagram
        uses: githubocto/repo-visualizer@0.7.1
        with:
          output_file: "diagram.svg"
          excluded_paths: "dist,node_modules"

Enter fullscreen mode Exit fullscreen mode

En el siguiente job, lo más relevante bajo mi punto de vista, sería el hecho de usar una variable en otro step.

name: Create release

on:
  workflow_call:

jobs:
  version:
    permissions:
      contents: write
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
      - name: "☁️ checkout repository"
        uses: actions/checkout@v3

      - name: Version Increment
        id: version
        run: |
          echo " **********************"
          git config user.name "$GITHUB_ACTOR"
          git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
          npm version minor -m "v%s"
          version=$(node -p "require('./package.json').version")
          git tag ${VERSION}
          git push --force
          echo "::set-output name=version::${version}"
          echo " **********************"

      - name: "Create release"
        uses: release-drafter/release-drafter@v5
        id: release
        with:
          version: ${{ steps.version.outputs.version }}
          publish: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Update Changelog
        uses: stefanzweifel/changelog-updater-action@v1
        with:
          latest-version: ${{ steps.release.outputs.tag_name }}
          release-notes: ${{ steps.release.outputs.body }}

      - name: Commit updated CHANGELOG
        uses: stefanzweifel/git-auto-commit-action@v4
        with:
          branch: ${{ github.event.release.target_commitish }}
          commit_message: Update CHANGELOG
          file_pattern: CHANGELOG.md

Enter fullscreen mode Exit fullscreen mode

Este paso nos evitará tener que ir borrando manualmente antiguas release y tags.

💡 Si queremos tener todas las release y tags, solo tendremos que eliminar este fichero y también de la parte de ci.yml

name: Delete tag and release

on:
  workflow_call:

jobs:
  delete-tag-and-release:
    runs-on: ubuntu-latest
    steps:
      - uses: dev-drprasad/delete-older-releases@v0.2.0
        with:
          keep_latest: 10
          delete_tags: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Enter fullscreen mode Exit fullscreen mode

Este job ya nos suena, ya que lo hemos empleado anteriormente.

name: Lighthouse check

on:
  workflow_call:

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - name: "☁️ checkout repository"
        uses: actions/checkout@v3

      - name: "Create temporary directory"
        run: mkdir -p ${{ github.workspace }}/lighthouse/artifacts

      - name: Lighthouse
        uses: foo-software/lighthouse-check-action@master
        with:
          outputDirectory: ${{ github.workspace }}/lighthouse/artifacts
          urls: "https://terminal.danieljsaldaña.com"

      - name: Upload artifacts
        uses: actions/upload-artifact@master
        with:
          name: Lighthouse reports
          path: ${{ github.workspace }}/lighthouse/artifacts

Enter fullscreen mode Exit fullscreen mode

Ahora sí, llego el momento de crear nuestra imagen y subirla a nuestro Registry.

name: Build and push Docker image to GitHub Packages

on:
  workflow_call:

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

jobs:
  build_and_push_to_registry:
    name: Build and push Docker image to GitHub Packages
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Log into registry ${{ env.REGISTRY }}
        uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=raw,value=1.0.${{ github.run_number }},priority=1000
            type=ref,event=branch
            type=sha
            type=raw,value=latest

      - name: Build image
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          context: .
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

      - name: Push Docker image
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          push: true

Enter fullscreen mode Exit fullscreen mode

En este caso vamos a utilizar Trivy, pero podemos utilizar la herramienta que queramos para analizar nuestra imagen.

💡 Este job podríamos agregarlo a la parte de build y analizar nuestra imagen antes de publicarla. De esta forma, si tuviera vulnerabilidades críticas, evitaríamos su publicación.

name: Trivy scan image

on:
  workflow_call:

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

jobs:
  build_and_push_to_registry:
    name: Build and push Docker image to GitHub Packages
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: "Create temporary directory"
        run: mkdir -p ${{ github.workspace }}/trivy-image/artifacts

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
          format: "table"
          exit-code: "0"
          ignore-unfixed: true
          vuln-type: "os,library"
          output: trivy-image/artifacts/trivy-image.log
          severity: "CRITICAL,HIGH"
        env:
          TRIVY_USERNAME: ${{ github.repository_owner }}
          TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

      - name: Upload artifacts
        uses: actions/upload-artifact@master
        with:
          name: Trivy image reports
          path: ${{ github.workspace }}/trivy-image/artifacts

Enter fullscreen mode Exit fullscreen mode

Ahora sí, hemos terminado nuestra automatización. Espero que os haya parecido interesante este lavatorio.

Top comments (0)