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:
name: CI Example
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Run a script
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:
- GitHub avvia un runner Ubuntu per eseguire il workflow.
- 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.
- 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:
npm create vite@latest mia-app --template vanilla cd mia-app npm 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
import { defineConfig } from 'vite';
export default defineConfig({
base: '/<nome-repo>/',
build: {
outDir: 'dist'
}
});
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:
name: Deploy to GitHub Pages
on:
push:
branches:
- main
permissions:
contents: read # Permette al workflow di leggere i contenuti del repository
pages: write # Concede i permessi per scrivere sulle GitHub Pages
id-token: write # Abilita lâautenticazione per il deploy sicuro
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '22'
- name: Install dependencies
run: npm install
- name: Build project
run: npm run build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: dist
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
uses: actions/deploy-pages@v4
Cosa fa questo workflow:
- Trigger (<code>on: push</code>): Il workflow si avvia ogni volta che si esegue un push sul branch
main. - Setup Node.js (<code>setup-node@v3</code>): Installa Node.js nella versione specificata.
- Installazione delle dipendenze (<code>npm install</code>): Installa i pacchetti dal
package.json. - Build del progetto (<code>npm run build</code>): Genera i file statici nella cartella
dist. - Upload dellâartefatto (<code>upload-pages-artifact@v3</code>): Carica i file della build per il deploy.
- Deploy finale (<code>deploy-pages@v4</code>): Pubblica il sito su GitHub Pages.
Configurare GitHub Pages nel repository
Prima di effettuare il primo deploy, abilita GitHub Pages:
- Vai su Settings > Pages.
- Seleziona "GitHub Actions" come Build and deployment source.
- 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:
npm install -D vitest @vitest/ui jsdom
Aggiorniamo package.json per aggiungere uno script dedicato all'esecuzione dei test:
"scripts": {
âŚ
"test": "vitest"
}
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:
import { defineConfig } from 'vite';
export default defineConfig({
test: {
environment: 'jsdom'
}
âŚ
});
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
import { beforeEach, describe, expect, it } from 'vitest';
import { setupCounter } from '../src/counter';
describe('setupCounter', () => {
let button;
beforeEach(() => {
// Creiamo un elemento fittizio (mock) per simulare il pulsante
button = document.createElement('button');
document.body.appendChild(button);
// Inizializziamo il contatore sulla nostra simulazione di elemento
setupCounter(button);
});
it('Dovrebbe inizializzare il contatore a 0', () => {
expect(button.innerHTML).toBe('count is 0');
});
it('Dovrebbe incrementare il contatore quando viene cliccato', () => {
button.click(); // Simuliamo un click
expect(button.innerHTML).toBe('count is 1');
button.click(); // Un altro click
expect(button.innerHTML).toBe('count is 2');
});
});
Questi test verificano che:
- Il contatore venga inizializzato a 0.
- Si incrementi correttamente ogni volta che il pulsante viene cliccato.
Eseguiamo i test in locale con:
npm 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:
name: CI/CD con GitHub Pages
on:
push:
branches:
- main
pull_request: # Esegue il workflow anche sulle pull request
branches:
- main
permissions:
contents: read
pages: write
id-token: write
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '22'
- name: Install dependencies
run: npm install
- name: Run tests
run: npm run test
build:
needs: test # Esegue la build solo se i test sono passati con successo
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '22'
- name: Install dependencies
run: npm install
- name: Build project
run: npm run build
- name: Debug build output
run: ls -la dist/assets/
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: dist
deploy:
needs: build # Il deploy viene eseguito solo se la build è andata a buon fine
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
uses: actions/deploy-pages@v4
Cosa cambia?
- Aggiunto un job <code>test</code> che verifica il codice prima di ogni build.
- La build (<code>build</code>) ora dipende dal job <code>test</code> (
needs: test), quindi se i test falliscono, la build non viene eseguita. - Il deploy (<code>deploy</code>) 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?
- Apri il tuo repository su GitHub.
- Clicca sulla scheda "Actions".
- Seleziona il workflow che vuoi esaminare.
- Guarda la lista dei job e clicca su quello che è fallito.
- 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:
- name: Debugging step run: echo "Current directory:" && pwd && ls -la
Usa il comando <code>set -e</code> 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:
- Aprire il repository su GitHub.
- Accedere alla sezione Settings e selezionare Secrets and variables > Actions.
- Cliccare su New repository secret e inserire il nome e il valore del secret.
- Salvare il secret.
Una volta creato, il secret può essere utilizzato nei workflow facendo riferimento a secrets.<NOME_SECRET>.
Utilizzo di un Secret in un Workflow
Esempio di workflow che utilizza un token API per il deploy sicuro su Netlify:
name: Deploy to Netlify
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '22'
- name: Install dependencies
run: npm install
- name: Build project
run: npm run build
- name: Deploy to Netlify
run: netlify deploy --prod --dir=dist
env:
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 buildgenera i file statici nella cartelladist. - Il comando
netlify deployutilizza 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:
- name: Cache Node.js modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ 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:
on:
push:
branches:
- main
- develop
pull_request:
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.
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: npm test
lint:
runs-on: ubuntu-latest
steps:
- 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.
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm run build
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- 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:
permissions: contents: read 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:
if: 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.