Full Stack

Cosa sono le Github Actions

Autore

Manuel Ricci

Nel mondo dello sviluppo moderno, il tempo è una risorsa preziosa. Scrivere codice è solo una parte del lavoro: bisogna testarlo, rilasciarlo, distribuire aggiornamenti e assicurarsi che tutto funzioni correttamente. Tutte queste operazioni possono diventare ripetitive e soggette a errori se eseguite manualmente. È qui che entra in gioco l'automazione.

L'automazione consente di eseguire processi in modo sistematico e senza intervento manuale, garantendo efficienza, affidabilità e rapidità nello sviluppo software. Ad esempio, con l'automazione possiamo:

  • Eseguire test automaticamente ogni volta che viene modificato il codice.
  • Distribuire nuove versioni di un'applicazione senza dover caricare manualmente i file su un server.
  • Mantenere la qualità del codice con strumenti di linting e analisi statica.

Tra i vari strumenti di automazione disponibili, GitHub Actions si distingue perché è perfettamente integrato in GitHub. Questo significa che puoi configurare processi automatici direttamente nei tuoi repository, senza bisogno di strumenti esterni.

Nel corso di questa lezione vedremo cos'è GitHub Actions, come funziona e come puoi sfruttarlo per migliorare il tuo flusso di sviluppo, riducendo il lavoro manuale e aumentando la produttività.

Cos'è GitHub Actions?

GitHub Actions è un sistema di CI/CD (Continuous Integration e Continuous Deployment) integrato direttamente in GitHub. Permette di automatizzare operazioni come test, build e deploy senza bisogno di configurare servizi esterni. Con GitHub Actions, puoi definire flussi di lavoro che si attivano in base a eventi specifici nel repository, migliorando la produttività e riducendo gli errori manuali.

Concetti chiave di GitHub Actions

Per capire come funziona GitHub Actions, è importante conoscere alcuni concetti fondamentali:

  • Workflow: Un workflow è un insieme di job che vengono eseguiti automaticamente in risposta a un evento (ad esempio, un push o una pull request). I workflow vengono definiti all’interno della directory .github/workflows/ del repository, utilizzando file YAML.
  • Job: Un job è una singola unità di lavoro all’interno di un workflow. Ogni job può essere indipendente o dipendere dall'esecuzione di altri job. Ad esempio, un workflow potrebbe avere un job per eseguire i test e un altro per il deploy, con il secondo che parte solo se il primo ha avuto successo.
  • Step: Ogni job è composto da una serie di step, ovvero le singole operazioni che devono essere eseguite. Un step può eseguire comandi shell o utilizzare azioni predefinite disponibili nel GitHub Marketplace.
  • Runner: Un runner è l’ambiente in cui viene eseguito un workflow. GitHub fornisce runner preconfigurati (GitHub-hosted) basati su sistemi operativi come Ubuntu, Windows e macOS, ma è anche possibile utilizzare runner self-hosted per avere maggiore controllo sull’infrastruttura.
  • Trigger: I workflow vengono attivati da trigger, cioè eventi che avviano l’esecuzione. Alcuni esempi di trigger comuni includono:
    • Push: quando viene eseguito un push su un branch specifico.
    • Pull request: quando viene aperta o aggiornata una PR.
    • Schedule: esecuzione periodica tramite cron job.
    • Manuale: attivazione manuale tramite interfaccia o API.

Grazie a questi elementi, GitHub Actions offre una soluzione potente per automatizzare le operazioni nei progetti software, garantendo un'integrazione continua ed efficiente.

Struttura di un workflow in GitHub Actions

I workflow di GitHub Actions sono definiti utilizzando file YAML all'interno della cartella .github/workflows/ del repository. Un workflow è un insieme di job e step che vengono eseguiti automaticamente in base a eventi specifici.

Esempio di un workflow base

Vediamo un esempio semplice di workflow, salvato nel file .github/workflows/main.yml:

1
2name: CI Example
3
4on: [push]
5
6jobs:
7  build:
8    runs-on: ubuntu-latest
9
10    steps:
11      - name: Checkout repository
12        uses: actions/checkout@v3
13      - name: Run a script
14        run: echo "Hello, GitHub Actions!"

Spiegazione della sintassi YAML

Questo file definisce un workflow chiamato "CI Example". Analizziamo le sue parti:

  • name: CI Example: Il nome del workflow, utile per identificarlo all’interno di GitHub Actions.
  • on: [push]: Definisce il trigger del workflow. In questo caso, il workflow verrà eseguito automaticamente ogni volta che viene effettuato un push su qualsiasi branch del repository.
  • jobs:: Contiene l'elenco dei job che devono essere eseguiti.
  • build:È il nome del job (può essere qualsiasi nome descrittivo).
  • runs-on: ubuntu-latestSpecifica il tipo di runner, ovvero l’ambiente in cui verrà eseguito il job. Qui viene utilizzato un server con Ubuntu fornito da GitHub.
  • steps: Contiene l'elenco di step che compongono il job.
  • name: Checkout repository: Ogni step ha un nome descrittivo. Questo step utilizza un'azione predefinita (actions/checkout@v3) che clona il repository nel runner, rendendo i file disponibili per i successivi comandi.
  • name: Run a script: Questo step esegue un comando shell (echo "Hello, GitHub Actions!"). Il comando viene eseguito nel runner specificato (ubuntu-latest).

Cosa Succede quando il workflow viene Eseguito?

Quando viene eseguito un push nel repository:

  1. GitHub avvia un runner Ubuntu per eseguire il workflow.
  2. Il runner esegue il job build, che è composto da due step:
    • Checkout del repository: clona il codice per poterlo usare nei passaggi successivi.
    • Esecuzione di un comando: stampa "Hello, GitHub Actions!" nella console.
  3. Al termine dell'esecuzione, GitHub mostra lo stato del workflow (successo o errore) nella sezione "Actions" del repository.

Questo è un esempio basilare, ma già utile per comprendere il funzionamento di GitHub Actions. Nei prossimi step vedremo come personalizzare i workflow per casi d’uso più complessi!

Creazione e debug di un workflow personalizzato

In questa sezione vedremo come configurare GitHub Actions per il deploy automatico di una web app su GitHub Pages. Useremo Vite.js, un moderno tool per lo sviluppo frontend, per creare la web app e generare i file di build da pubblicare.

Creazione di una web app con Vite

Come prima cosa inizializziamo il progetto con la CLI di Vite eseguendo:

1npm create vite@latest mia-app --template vanilla
2cd mia-app
3npm install

Questo comando crea un progetto molto basilare, perfetto per il prossimo passaggio. L’unica modifica che dobbiamo fare è creare un file vite.config.js

1import { defineConfig } from 'vite';
2
3export default defineConfig({
4  base: '/<nome-repo>/',
5  build: {
6    outDir: 'dist'
7  }
8});

Dove <nome-repo>è il nome della repository che avete creato su GitHub.

Configurazione del workflow di deploy su GitHub Pages

Ora creiamo il file .github/workflows/deploy.yml per automatizzare il deploy:

1name: Deploy to GitHub Pages
2
3on:
4  push:
5    branches:
6      - main
7
8permissions:
9  contents: read # Permette al workflow di leggere i contenuti del repository
10  pages: write # Concede i permessi per scrivere sulle GitHub Pages
11  id-token: write # Abilita l’autenticazione per il deploy sicuro
12
13jobs:
14  build:
15    runs-on: ubuntu-latest
16
17    steps:
18      - name: Checkout repository
19        uses: actions/checkout@v3
20
21      - name: Setup Node.js
22        uses: actions/setup-node@v3
23        with:
24          node-version: '22'
25
26      - name: Install dependencies
27        run: npm install
28
29      - name: Build project
30        run: npm run build
31
32      - name: Upload artifact
33        uses: actions/upload-pages-artifact@v3
34        with:
35          path: dist
36  deploy:
37    needs: build
38    runs-on: ubuntu-latest
39    steps:
40      - name: Deploy to GitHub Pages
41        uses: actions/deploy-pages@v4

Cosa fa questo workflow:

  • Trigger (on: push): Il workflow si avvia ogni volta che si esegue un push sul branch main.
  • Setup Node.js (setup-node@v3): Installa Node.js nella versione specificata.
  • Installazione delle dipendenze (npm install): Installa i pacchetti dal package.json.
  • Build del progetto (npm run build): Genera i file statici nella cartella dist.
  • Upload dell’artefatto (upload-pages-artifact@v3): Carica i file della build per il deploy.
  • Deploy finale (deploy-pages@v4): Pubblica il sito su GitHub Pages.

Configurare GitHub Pages nel repository

Prima di effettuare il primo deploy, abilita GitHub Pages:

  1. Vai su Settings > Pages.
  2. Seleziona "GitHub Actions" come Build and deployment source.
  3. Dopo il primo deploy, il sito sarà disponibile all’URL: https://<tuo-username>.github.io/<repository-name>/

Attenzione! Le GitHub Pages sono previste nel piano GitHub Pro che costa 4$ al mese. Gratuitamente avete la possibilità di creare una repo con GitHub Pages creando una repository speciale chiamata <tuo-username>.github.io.

E se volessimo aggiungere anche i test per completare la nostra pipeline CI/CD? Vediamo.

Aggiungere i test alla pipeline CI/CD con GitHub Actions

Un'automazione efficace non si limita solo al deploy, ma include anche una fase di test per garantire che il codice funzioni correttamente prima di essere distribuito. Aggiungiamo quindi una fase di test alla nostra pipeline CI/CD utilizzando Vitest per eseguire test unitari sul nostro progetto.

Per iniziare, installiamo Vitest, il suo UI runner e jsdom, che ci permette di simulare il DOM in ambiente Node.js:

1npm install -D vitest @vitest/ui jsdom

Aggiorniamo package.json per aggiungere uno script dedicato all'esecuzione dei test:

1
2"scripts": {
34  "test": "vitest"
5}

Configuriamo l'ambiente di test nel file vite.config.js, specificando jsdom come ambiente per garantire la compatibilità con i test che coinvolgono il DOM:

1
2import { defineConfig } from 'vite';
3
4export default defineConfig({
5
6  test: {
7
8    environment: 'jsdom'
9
10  }
11
1213
14});

Questa configurazione assicura che i test abbiano accesso a un DOM virtuale, necessario per verificare il comportamento di componenti che interagiscono con il documento HTML.

Creiamo una cartella tests/ nella root del progetto e all'interno un file counter.test.js. Nel file tests/counter.test.js scriviamo i test per verificare il comportamento della funzione setupCounter

1
2import { beforeEach, describe, expect, it } from 'vitest';
3
4import { setupCounter } from '../src/counter';
5
6describe('setupCounter', () => {
7
8  let button;
9
10  beforeEach(() => {
11
12    // Creiamo un elemento fittizio (mock) per simulare il pulsante
13
14    button = document.createElement('button');
15
16    document.body.appendChild(button);
17
18    // Inizializziamo il contatore sulla nostra simulazione di elemento
19
20    setupCounter(button);
21
22  });
23
24  it('Dovrebbe inizializzare il contatore a 0', () => {
25
26    expect(button.innerHTML).toBe('count is 0');
27
28  });
29
30  it('Dovrebbe incrementare il contatore quando viene cliccato', () => {
31
32    button.click(); // Simuliamo un click
33
34    expect(button.innerHTML).toBe('count is 1');
35
36    button.click(); // Un altro click
37
38    expect(button.innerHTML).toBe('count is 2');
39
40  });
41
42});

Questi test verificano che:

  1. Il contatore venga inizializzato a 0.
  2. Si incrementi correttamente ogni volta che il pulsante viene cliccato.

Eseguiamo i test in locale con:

1npm run test

Ora che i test sono configurati e funzionano localmente, li integriamo nella pipeline CI/CD per verificare il codice automaticamente prima di ogni build e deploy.

Modifichiamo il file .github/workflows/deploy.yml per aggiungere un job di test:

1
2name: CI/CD con GitHub Pages
3
4on:
5
6  push:
7
8    branches:
9
10      - main
11
12  pull_request: # Esegue il workflow anche sulle pull request
13
14    branches:
15
16      - main
17
18permissions:
19
20  contents: read
21
22  pages: write
23
24  id-token: write
25
26jobs:
27
28  test:
29
30    runs-on: ubuntu-latest
31
32    steps:
33
34      - name: Checkout repository
35
36        uses: actions/checkout@v3
37
38      - name: Setup Node.js
39
40        uses: actions/setup-node@v3
41
42        with:
43
44          node-version: '22'
45
46      - name: Install dependencies
47
48        run: npm install
49
50      - name: Run tests
51
52        run: npm run test
53
54  build:
55
56    needs: test # Esegue la build solo se i test sono passati con successo
57
58    runs-on: ubuntu-latest
59
60    steps:
61
62      - name: Checkout repository
63
64        uses: actions/checkout@v3
65
66      - name: Setup Node.js
67
68        uses: actions/setup-node@v3
69
70        with:
71
72          node-version: '22'
73
74      - name: Install dependencies
75
76        run: npm install
77
78      - name: Build project
79
80        run: npm run build
81
82      - name: Debug build output
83
84        run: ls -la dist/assets/
85
86      - name: Upload artifact
87
88        uses: actions/upload-pages-artifact@v3
89
90        with:
91
92          path: dist
93
94  deploy:
95
96    needs: build # Il deploy viene eseguito solo se la build è andata a buon fine
97
98    runs-on: ubuntu-latest
99
100    steps:
101
102      - name: Deploy to GitHub Pages
103
104        uses: actions/deploy-pages@v4

Cosa cambia?

  • Aggiunto un job test che verifica il codice prima di ogni build.
  • La build (build) ora dipende dal job test (needs: test), quindi se i test falliscono, la build non viene eseguita.
  • Il deploy (deploy) dipende dalla build (needs: build), garantendo che venga eseguito solo se tutto è andato a buon fine.
  • I test vengono eseguiti automaticamente sia su push che su pull request, migliorando la qualità del codice prima che venga unito nel branch main.

Facciamo la commit e diamo un’occhiata ai nostri log per verificare che tutto funzioni come dovrebbe. Se tutto è ok. Possiamo andare oltre.

Debug e lettura dei log di un workflow

Anche se GitHub Actions è molto potente, può capitare che un workflow fallisca. Per individuare i problemi, GitHub fornisce log dettagliati che possiamo analizzare direttamente dall'interfaccia web.

Come accedere ai log di esecuzione?

  1. Apri il tuo repository su GitHub.
  2. Clicca sulla scheda "Actions".
  3. Seleziona il workflow che vuoi esaminare.
  4. Guarda la lista dei job e clicca su quello che è fallito.
  5. Scorri i log per trovare eventuali errori nei vari step.

Strategie di debug

Controlla la sintassi YAML: Un errore di indentazione può rompere tutto.

Aggiungi step di debug: Puoi stampare variabili e informazioni utili con echo:

1- name: Debugging step
2  run: echo "Current directory:" && pwd && ls -la
`

Usa il comando set -e nei tuoi script per interrompere l'esecuzione in caso di errori.

Verifica i permessi: Alcune azioni necessitano di token di autenticazione per funzionare.

Variabili, Secrets e personalizzazione in GitHub Actions

Quando si lavora con GitHub Actions, può essere necessario gestire dati sensibili come API Key, credenziali di accesso o token di autenticazione. Per proteggere queste informazioni ed evitare di inserirle direttamente nel codice sorgente, si utilizzano variabili d'ambiente e secrets.

Le variabili d'ambiente vengono utilizzate per memorizzare informazioni di configurazione non sensibili, come il nome del progetto o l'ambiente di deploy. I secrets, invece, sono utilizzati per archiviare dati sensibili come chiavi API e credenziali di autenticazione. A differenza delle variabili d'ambiente, i secrets sono nascosti nei log e possono essere accessibili solo dai workflow autorizzati.

Creare e usare secrets in GitHub Actions

Per aggiungere un secret in un repository su GitHub:

  1. Aprire il repository su GitHub.
  2. Accedere alla sezione Settings e selezionare Secrets and variables > Actions.
  3. Cliccare su New repository secret e inserire il nome e il valore del secret.
  4. Salvare il secret.

Una volta creato, il secret può essere utilizzato nei workflow facendo riferimento a secrets.&lt;NOME_SECRET>.

Utilizzo di un Secret in un Workflow

Esempio di workflow che utilizza un token API per il deploy sicuro su Netlify:

1name: Deploy to Netlify
2
3on:
4  push:
5    branches:
6      - main
7
8jobs:
9  deploy:
10    runs-on: ubuntu-latest
11    steps:
12      - name: Checkout repository
13        uses: actions/checkout@v3
14
15      - name: Setup Node.js
16        uses: actions/setup-node@v3
17        with:
18          node-version: '22'
19
20      - name: Install dependencies
21        run: npm install
22
23      - name: Build project
24        run: npm run build
25
26      - name: Deploy to Netlify
27        run: netlify deploy --prod --dir=dist
28        env:
29          NETLIFY_AUTH_TOKEN: ${{ secrets.DEPLOY_API_KEY }}

Questo workflow prevede diversi passaggi:

  • Il repository viene clonato nel runner con checkout@v3.
  • Node.js viene installato nella versione specificata.
  • Le dipendenze del progetto vengono installate con npm install.
  • Il comando npm run build genera i file statici nella cartella dist.
  • Il comando netlify deploy utilizza il token memorizzato nel secret per autenticarsi senza renderlo visibile nei log.

Debug e sicurezza

Se un workflow fallisce per problemi di autenticazione, è necessario verificare che il secret sia stato creato correttamente e che l’azione di GitHub abbia accesso ai secrets.

Per migliorare la sicurezza, è consigliabile evitare di stampare direttamente i secrets nei log e revocare i token API se si sospetta una compromissione. È inoltre opportuno differenziare i secrets in base agli ambienti di sviluppo, test e produzione per evitare che credenziali sensibili vengano esposte o utilizzate involontariamente in contesti non appropriati.

Best practices e ottimizzazione in GitHub Actions

Quando si utilizzano GitHub Actions, è importante ottimizzare i workflow per ridurre i tempi di esecuzione, migliorare l'efficienza e garantire un livello adeguato di sicurezza. Un workflow ben strutturato permette di evitare sprechi di risorse, ridurre i tempi di attesa nelle pipeline di CI/CD e proteggere i dati sensibili.

Ridurre i tempi di esecuzione con la cache

L’uso della cache consente di ridurre significativamente i tempi di esecuzione dei workflow, evitando di dover ricostruire o reinstallare dipendenze già utilizzate in esecuzioni precedenti. GitHub Actions fornisce un'azione predefinita, actions/cache, per memorizzare e riutilizzare file tra le esecuzioni.

Esempio di utilizzo della cache per le dipendenze di Node.js:

1- name: Cache Node.js modules
2  uses: actions/cache@v3
3
4  with:
5    path: ~/.npm
6    key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
7    restore-keys: |
8      ${{ runner.os }}-node-

In questo caso:

  • La cache viene salvata nella cartella ~/.npm, che contiene i moduli installati con npm.
  • Il valore della cache è determinato dal contenuto del file package-lock.json.
  • Se non viene trovata una cache corrispondente, viene usata una versione precedente basata sul prefisso runner.os-node-.

Questo approccio consente di evitare il download ripetitivo delle dipendenze, accelerando l’esecuzione dei job.

Strategie per workflow efficienti

Un workflow ben progettato deve ridurre il numero di esecuzioni inutili e ottimizzare il parallelismo tra job. Alcune strategie utili includono:

Utilizzare trigger mirati: non tutti i workflow devono essere eseguiti su ogni push. È possibile specificare trigger più selettivi:

1on:
2  push:
3    branches:
4      - main
5      - develop
6  pull_request:
7    types: [opened, synchronize]

In questo esempio, il workflow si attiva solo su push nei branch main e develop, o quando viene aperta o aggiornata una pull request.

Eseguire job in parallelo: Job indipendenti possono essere eseguiti in parallelo per ridurre i tempi di esecuzione.

1jobs:
2  test:
3    runs-on: ubuntu-latest
4    steps:
5      - run: npm test
6
7  lint:
8    runs-on: ubuntu-latest
9    steps:
10      - run: npm run lint

Condizioni per l'esecuzione dei job: È possibile impostare condizioni per eseguire determinati job solo se i precedenti hanno avuto esito positivo.

1jobs:
2  build:
3    runs-on: ubuntu-latest
4    steps:
5      - run: npm run build
6
7  deploy:
8    needs: build
9    runs-on: ubuntu-latest
10    steps:
11      - run: npm run deploy

In questo caso, il job deploy viene eseguito solo se il job build ha avuto successo.

Sicurezza e gestione dei permessi

Un workflow deve essere configurato per minimizzare i rischi di sicurezza, specialmente quando manipola secrets, esegue codice esterno o interagisce con ambienti di produzione.

Limitare i permessi nei workflow

GitHub Actions consente di definire i permessi con granularità per ridurre i privilegi concessi ai job:

1permissions:
2  contents: read
3  id-token: write

Questo impedisce ai workflow di modificare i contenuti del repository, riducendo il rischio di azioni non autorizzate.

Evitare l’esposizione di secrets nei log

Se un job stampa variabili d'ambiente, potrebbe accidentalmente esporre secrets nei log. Per evitarlo, non usare echo $MY_SECRET, ma affidarsi ai sistemi di logging di GitHub Actions.

Bloccare l’esecuzione su fork non autorizzati

Se un repository permette i fork, chiunque può creare una copia del repository e avviare workflow. Per evitare che i secrets vengano esposti, è possibile limitare l’accesso ai workflow in base all’origine della richiesta:

1if: github.event.pull_request.head.repo.fork == false

Utilizzare token temporanei

Per ridurre il rischio di compromissione, i workflow dovrebbero utilizzare token a scadenza limitata invece di chiavi API statiche. GitHub fornisce il token GITHUB_TOKEN, che viene generato automaticamente e scade al termine dell’esecuzione del workflow.

Questo è tutto amici

GitHub Actions è un potente strumento di automazione che permette di migliorare l'efficienza nello sviluppo software attraverso workflow personalizzati. Nel corso di questa lezione, abbiamo visto come creare e ottimizzare workflow, utilizzando concetti fondamentali come job, step, runner e trigger. Abbiamo approfondito l'uso delle variabili e dei secrets per proteggere dati sensibili, oltre a strategie per ridurre i tempi di esecuzione e garantire la sicurezza dei workflow.

Per applicare quanto appreso, il passo successivo è provare GitHub Actions in un progetto reale. Puoi iniziare automatizzando il deploy di una web app su GitHub Pages, eseguendo test automatici su ogni push o ottimizzando il tuo flusso di lavoro con job paralleli e caching.

Esplorare le potenzialità di GitHub Actions ti permetterà di integrare l'automazione nei tuoi progetti, migliorando la produttività e la qualità del codice.

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.