Salta el contingut
 

Exemples de fluxos de treball

Joan Puigcerver Ibáñez

j.puigcerveribanez@edu.gva.es

Llicència: CC BY-NC-SA 4.0

(Reconeixement - NoComercial - CompartirIgual) 🅭

Exemples de fluxos de treball

Per veure com combinar totes aquestes opcions, anem a veure diferents exemples d'automatitzacions en projectes de naturalesa distinta.

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

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

La següent automatització permet generar aquest lloc web amb MkDocs i publicar-lo a GitHub Pages.

Aquesta acció s'executa sempre que es publiquen nous canvis sobre la branca main. També es pot executar manualment.

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

  1. Compila el lloc web estàtic amb MkDocs.
    • Còpia 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 executar MkDocs.
    • Compila el lloc web amb l'ordre mkdocs build.
    • Emmagatzema el directori amb la documentació generada (site/) com a artefacte per a la següent tasca amb l'acció predefinida actions/upload-pages-artifact.
  2. Publica el lloc web a GitHub Pages.
    • Sols s'executa si la tasca anterior s'ha executat correctament.
    • Publica l'artefacte generat en la tasca anterior 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@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 -r requirements.txt
      - name: Build # Acció per compilar la documentació
        run: mkdocs build --clean
      - name: Upload static files as artifact # Acció per pujar els fitxers estàtics com a artefacte
        uses: actions/upload-pages-artifact@v3
        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@v4

Prova de correcció ortogràfica

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

Aquest flux de treball comprova la correcció ortogràfica de la documentació del repositori utilitzant el programa pyspelling.

S'executa quan es crea Pull Request o es marca com a que està llesta per a revisió sobre la branca main.

El flux de treball es compon de dues tasques. S'ha configurat d'aquesta manera perquè la tasca de correcció ortogràfica només s'executa quan s'han modificat fitxers de documentació, evitant així executar-la innecessàriament quan es modifiquen altres fitxers.

  1. changed-files: Comprova si cal realitzar la correcció ortogràfica.

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

    • S'executa només si needs.changed-files.outputs.EXIST_CHANGED_FILES == 1.
    • Instal·la les dependències.
    • Es 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 mkdocs 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 mkdocs $SOURCES"
          pyspelling $SPELL_ARGS

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

Repositori d'exemple: tasklist-api

Podeu observar el seu 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 a haver marcat 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 a la branca main o quan es crea o 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.

    • Sols s'executa si la tasca unit-test s'ha executat 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 nova etiqueta (tag) al repositori.

Els passos que realitza són:

  • Configura l'entorn per poder utilitzar Python 3.8.
  • Instal·la el paquet build per compilar les distribucions de Python.
  • Compila les distribucions del paquet.
  • Publica les distribucions a PyPI utilitzant 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/pygments-shell-console
    permissions:
      id-token: write  # IMPORTANT: this permission is mandatory for trusted publishing
    steps:
      - name: Set up Python 3.8
        uses: actions/checkout@master
      - name: Set up Python 3.8
        uses: actions/setup-python@v1
        with:
          python-version: 3.8
      - 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

Podeu seguir aquest tutorial per crear una imatge de Docker a partir d'un repositori de GitHub i publicar-la a Docker Hub.

Repositori d'exemple: tasklist-api

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

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

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

  1. Còpia el repositori a 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 com 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.