Salta el contingut
 

Exemples de fluxos de treball

Joan Puigcerver Ibáñez

Llicència: CC BY-NC-SA 4.0

(Reconeixement - NoComercial - CompartirIgual) 🅭

Exemples de fluxos de treball

Aquests apunts recullen diferents exemples d'automatitzacions en projectes de naturalesa diversa, per a mostrar com es combinen les opcions de GitHub Actions en casos reals.

Publicació d'un lloc web estàtic generat amb ProperDocs a GitHub Pages

Exemple en el repositori d'aquesta documentació: curs-git

L'automatització següent permet generar aquest lloc web amb ProperDocs i publicar-lo a GitHub Pages. S'executa sempre que es publiquen canvis nous en la branca main, i també es pot executar manualment.

Els passos que la componen són els següents:

  1. Compila el lloc web estàtic amb ProperDocs.
    • Copia els fitxers del repositori amb l'acció predefinida actions/checkout.
    • Configura Python amb l'acció predefinida actions/setup-python.
    • Instal·la les dependències necessàries per a executar ProperDocs.
    • Compila el lloc web amb l'ordre properdocs build.
    • Emmagatzema el directori amb la documentació generada (site/) com a artefacte per a la tasca següent, amb l'acció predefinida actions/upload-pages-artifact.
  2. Publica el lloc web a GitHub Pages.
    • Només s'executa si la tasca anterior ha acabat correctament.
    • Publica l'artefacte generat en la tasca anterior amb l'acció predefinida actions/deploy-pages.
.github/workflows/deploy.yml
name: deploy # Nom de l'automatització
on: # Events que disparen l'automatització
  push:
    branches:
      - main # Quan es fa un push a la branca main
  workflow_dispatch:

permissions: # Permisos que es concedeixen a l'automatització
  contents: read
  pages: write
  id-token: write

concurrency:
  group: github-pages
  cancel-in-progress: true

jobs: # Llista de tasques a executar
  build: # Nom de la tasca
    runs-on: ubuntu-latest # Sistema operatiu on s'executarà la tasca
    steps: # Passos a executar
      - uses: actions/checkout@v6 # Acció per clonar i còpiar el repositori a l'entorn d'execució
        with:
          fetch-depth: 0
      - uses: actions/setup-python@v6 # Acció per configurar Python
        with:
          python-version: 3.x
          cache: 'pip'
      - name: Install dependencies # Acció per instal·lar les dependències
        run: pip install -r requirements.txt
      - name: Build # Acció per compilar la documentació
        run: properdocs build --clean
      - name: Upload static files as artifact # Acció per pujar els fitxers estàtics com a artefacte
        uses: actions/upload-pages-artifact@v5
        with:
          path: ./site/ # Ruta dels fitxers estàtics generats per MkDocs

  deploy: # Nom de la tasca
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest # Sistema operatiu on s'executarà la tasca
    needs: build # Aquesta tasca depèn de la tasca "build"
    steps: # Passos a executar
      - name: Deploy to GitHub Pages # Acció per desplegar a GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v5

Prova de correcció ortogràfica

Exemple en el repositori d'aquesta documentació: curs-git

Aquest flux de treball comprova l'ortografia de la documentació del repositori amb el programa pyspelling. S'executa quan es crea una Pull Request sobre la branca main o quan es marca com a llesta per a revisió.

El flux de treball es compon de dues tasques. S'ha dividit d'aquesta manera perquè la correcció ortogràfica només s'execute quan s'han modificat fitxers de documentació i no es faça innecessàriament quan es modifiquen altres fitxers.

  1. changed-files: comprova si cal fer la correcció ortogràfica.

    • Comprova si s'han modificat fitxers de documentació (*.md) respecte de la branca main.
    • Emmagatzema el resultat en la variable EXIST_CHANGED_FILES.
  2. spellcheck: en cas afirmatiu, comprova l'ortografia dels fitxers modificats.

    • Només s'executa si needs.changed-files.outputs.EXIST_CHANGED_FILES == 1.
    • Instal·la les dependències.
    • Descarrega els diccionaris necessaris.
    • Executa la correcció ortogràfica amb pyspelling.
.github/workflows/spellcheck.yml
name: spellcheck # Nom de l'automatització
on: # Events que disparen l'automatització
  pull_request:
    # Tipus d'events sobre la pull request
    types: ['opened', 'edited', 'reopened', 'synchronize', 'ready_for_review']
    branches:
      - 'main' # Quan s'obri un pull request a la branca 'main'

jobs: # Llista de tasques a executar
  changed-files: # Nom de la tasca
    if: github.event.pull_request.draft == false # Condició per executar la tasca

    runs-on: ubuntu-latest # Sistema operatiu on s'executarà la tasca

    outputs:
      EXIST_CHANGED_FILES: ${{ steps.get-changed-files.outputs.EXIST_CHANGED_FILES }}

    steps: # Passos a executar
      - uses: actions/checkout@v4 # Acció per clonar i còpiar el repositori a l'entorn d'execució
        with:
          fetch-depth: 0
      - name: Get changed files
        id: get-changed-files
        run: |
          CHANGED_FILES=$(git diff --name-only origin/main HEAD | grep 'docs/.*\.md$' || true)
          EXIST_CHANGED_FILES=0
          if [ -n "$CHANGED_FILES" ]; then
              EXIST_CHANGED_FILES=1
          fi
          echo "EXIST_CHANGED_FILES=$EXIST_CHANGED_FILES" >> $GITHUB_OUTPUT


  spellcheck: # Nom de la tasca
    needs: changed-files # Tasca que s'executarà abans
    if: github.event.pull_request.draft == false && needs.changed-files.outputs.EXIST_CHANGED_FILES == '1' # Condició per executar la tasca

    runs-on: ubuntu-latest # Sistema operatiu on s'executarà la tasca

    env: # Variables d'entorn
      DICPATH: .hunspell/
      CHANGED_FILES: ${{ needs.changed-files.outputs.CHANGED_FILES }}

    steps: # Passos a executar
      - uses: actions/checkout@v4 # Acció per clonar i còpiar el repositori a l'entorn d'execució
        with:
          fetch-depth: 0
      - uses: actions/setup-python@v5 # Acció per configurar Python
        with:
          python-version: 3.x
          cache: 'pip'
      - name: Install dependencies # Acció per instal·lar les dependències
        run: pip install -U -r requirements.txt
      - name: Install pyspelling # Acció per instal·lar el paquet pyspelling
        run: pip install pyspelling
      - name: Install hunspell # Acció per instal·lar el corrector ortogràfic hunspell
        run: |
          sudo apt update
          sudo apt install hunspell -y
      - name: Build documents # Acció per compilar la documentació
        run: CI=false properdocs build --clean

      - name: Download dictionary # Acció per descarregar el diccionari que s'utilitzarà en el corrector ortogràfic
        run: |
          mkdir -p .hunspell
          curl -L https://raw.githubusercontent.com/Softcatala/catalan-dict-tools/refs/heads/master/resultats/hunspell/catalan-valencia.dic -o .hunspell/ca_ES_valencia.dic
          curl -L https://raw.githubusercontent.com/Softcatala/catalan-dict-tools/refs/heads/master/resultats/hunspell/catalan-valencia.aff -o .hunspell/ca_ES_valencia.aff
          hunspell -D
      - name: Spell check # Acció per comprovar l'ortografia dels documents
        run: |
          CHANGED_FILES=$(git diff --name-only origin/main HEAD | grep 'docs/.*\.md$' | sed 's/\/index//' | sed 's/docs/site/' | sed 's/.md$/\/index.html/')
          echo "CHANGED_FILES=$CHANGED_FILES"
          for FILE in $(echo "$CHANGED_FILES"); do
              if [ -f $FILE ]; then
                  SOURCES="$SOURCES -S $FILE"
              fi
          done
          echo "SOURCES=$SOURCES"
          if [ -z "$SOURCES" ]; then
              echo "No files to check"
              exit 0
          fi
          SPELL_ARGS="$SPELL_ARGS --name docs $SOURCES"
          pyspelling $SPELL_ARGS

Execució de proves unitàries i d'integració en un projecte Java amb Maven

Repositori d'exemple: tasklist-api

Pots observar-ne el comportament en la Pull Request feature: tasks can be marked as favorites (#1):

  1. En el commit 2ca7024 – feature: tasks can be marked as favorites no s'han executat les proves, perquè la Pull Request encara estava marcada com a esborrany.
  2. En els commits posteriors, una vegada marcada la Pull Request com a llesta per a revisió, sí que s'han executat les proves.

Aquest flux de treball s'executa cada vegada que es publiquen canvis en la branca main o quan es crea o s'actualitza una pull request cap a les branques main o dev. Es basa en dues tasques:

  1. unit-tests: executa les proves unitàries del projecte.

    • Configura l'entorn amb JDK 21.
    • Executa les proves unitàries amb l'ordre mvn test.
  2. integration-tests: executa les proves d'integració del projecte.

    • Només s'executa si la tasca unit-tests ha acabat correctament.
    • Configura l'entorn amb JDK 21.
    • Executa les proves d'integració amb l'ordre mvn verify, sense tornar a executar les proves unitàries.
.github/workflows/tests.yml
name: Tests

on:
  pull_request:
    branches:
      - dev
      - main
    types: [opened, synchronize, reopened, ready_for_review]

jobs:
  unit-tests:
    name: Tests Unitaris
    runs-on: ubuntu-latest

    # Evitar executar en PR en draft
    if: github.event.pull_request.draft == false

    steps:
      - name: Checkout del codi
        uses: actions/checkout@v4

      - name: Configurar Java 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
          cache: 'maven'

      - name: Executar tests unitaris
        run: mvn clean test

  integration-tests:
    name: Tests d'Integració
    runs-on: ubuntu-latest
    needs: unit-tests

    if: github.event.pull_request.draft == false

    steps:
      - name: Checkout del codi
        uses: actions/checkout@v4

      - name: Configurar Java 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
          cache: 'maven'

      - name: Executar tests d'integració
        run: mvn clean verify -Dskip.unit.tests=true
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.2</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>task-management-api</artifactId>
    <version>1.0.0</version>
    <name>Task Management API</name>
    <description>Spring Boot REST API for Task Management</description>

    <properties>
        <java.version>21</java.version>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <skip.unit.tests>false</skip.unit.tests>
    </properties>

    <dependencies>
        <!-- Spring Boot Web Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot Data JPA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- H2 Database -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Spring Boot Validation -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!-- Spring Boot DevTools (optional) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- Spring Boot Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <!-- Surefire Plugin for Unit Tests -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>${skip.unit.tests}</skip>
                    <argLine>-XX:+EnableDynamicAgentLoading</argLine>
                    <includes>
                        <include>**/*Test.java</include>
                    </includes>
                    <excludes>
                        <exclude>**/*IT.java</exclude>
                    </excludes>
                </configuration>
            </plugin>

            <!-- Failsafe Plugin for Integration Tests -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <configuration>
                    <argLine>-XX:+EnableDynamicAgentLoading</argLine>
                    <includes>
                        <include>**/*IT.java</include>
                    </includes>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Publicació d'un paquet de Python a PyPI

Exemple en el repositori mkdocs-data-plugin

Aquest flux de treball compila i publica les distribucions d'un paquet de Python a PyPI cada vegada que es publica una etiqueta (tag) nova en el repositori.

Els passos que fa són:

  1. Configura l'entorn per a utilitzar Python 3.8.
  2. Instal·la el paquet build per a compilar les distribucions de Python.
  3. Compila les distribucions del paquet.
  4. Publica les distribucions a PyPI amb l'acció predefinida pypa/gh-action-pypi-publish.
.github/workflows/publish-to-pypi.yml
name: Publish Python 🐍 distributions 📦 to PyPI
on:
  push:
    tags:
      - "*"
jobs:
  build-n-publish:
    runs-on: ubuntu-latest
    name: Build and publish Python 🐍 distributions 📦 to PyPI
    environment:
      name: pypi
      url: https://pypi.org/p/mkdocs-data-plugin
    permissions:
      id-token: write  # IMPORTANT: this permission is mandatory for trusted publishing
    steps:
      - name: Set up Python 3
        uses: actions/checkout@v6
      - name: Set up Python 3
        uses: actions/setup-python@v6
        with:
          python-version: 3.x
      - name: Install pypa/build
        run: >-
          python -m
          pip install
          build
          --user
      - name: Build a binary wheel and a source tarball
        run: >-
          python -m
          build
          --sdist
          --wheel
          --outdir dist/
      - name: Publish package distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

Creació d'una imatge de Docker

Aquest exemple crea una imatge de Docker a partir d'un repositori de GitHub i la publica a Docker Hub, seguint el tutorial següent:

Repositori d'exemple: tasklist-api

Quan s'ha publicat l'etiqueta v0.1.0 en el commit d42ed1e – Primera versió del projecte, el flux de treball s'ha executat correctament i ha publicat la imatge de Docker en el repositori joapuiib/tasklist-api de Docker Hub.

Aquest flux de treball s'executa cada vegada que es publica en el repositori una etiqueta nova amb el format v*.*.*. També es pot executar manualment, ja que està configurat amb workflow_dispatch.

Consisteix en una única tasca que fa els passos següents:

  1. Copia el repositori en l'entorn d'execució amb l'acció predefinida actions/checkout.
  2. Inicia sessió a Docker Hub amb l'acció predefinida docker/login-action.
    • Utilitza les credencials DOCKER_USERNAME i DOCKER_PASSWORD emmagatzemades com a secrets del repositori.
  3. Configura les metadades de la imatge de Docker amb l'acció predefinida docker/metadata-action.
    • Especifica el nom de la imatge: joapuiib/tasklist-api.
    • Configura les etiquetes de la imatge:
      • latest per a la branca main.
      • El número de versió extret de l'etiqueta publicada amb els patrons *.*.*, *.* i *.
  4. Construeix i publica la imatge de Docker amb l'acció predefinida docker/build-push-action.
.github/workflows/docker-build.yml
name: Construcció Imatge Docker

on:
  push:
    tags:
      - 'v*.*.*'
  workflow_dispatch:

env:
  REGISTRY: docker.io
  IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME || github.repository_owner }}/tasklist-api

jobs:
  build-and-push:
    name: Construir i Publicar Imatge
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout out the repo
        uses: actions/checkout@v4

      - name: Login a Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Extreure metadades
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=raw,value=latest,enable={{is_default_branch}}
          flavor: |
            latest=auto

      - name: Construir i publicar imatge Docker
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache,mode=max
📌 Aquest document pot quedar desactualitzat després d’imprimir-lo. Pots consultar la versió més recent a la pàgina web.
🌿 Abans d’imprimir aquest document, considera si és realment necessari. Redueix el consum de paper i ajuda a protegir el nostre entorn.