Nel frenetico mondo dello sviluppo web, integrazione continua e distribuzione continua (CI/CD) sono diventate prassi indispensabili per fornire software di alta qualità in modo efficiente. Il CI/CD permette agli sviluppatori di automatizzare il processo di creazione, test e distribuzione delle modifiche al codice, riducendo il rischio di errori umani e favorendo iterazioni più rapide.

Questo articolo spiega l’importanza del CI/CD, come creare una pipeline CI e come impostare in modo programmatico la distribuzione continua nella propria pipeline CI con l’API di Kinsta, il tutto usando GitHub Actions nel proprio repository GitHub.

Perché usare il CI/CD?

La piattaforma di Hosting di Applicazioni di Kinsta ha sempre reso disponibile un’opzione per il deployment automatico che si attiva ogni volta che viene apportata una modifica a un branch specifico del repository Git ospitato. Tuttavia, questo potrebbe non essere l’ideale per progetti di grandi dimensioni con più membri all’interno del team. Molti sviluppatori tendono a non abilitare la distribuzione automatica per diversi motivi.

Uno di questi è che, in un ambiente collaborativo con più sviluppatori sullo stesso progetto, le distribuzioni automatiche attivate dalla modifica di uno sviluppatore al repository possono generare instabilità e altri problemi non sempre prevedibili. Senza una verifica e una convalida adeguate, anche una piccola modifica al codice potrebbe disturbare il sito live, generando possibili interruzioni e portando ad esperienze negative per gli utenti.

È qui che entra in gioco una pipeline CI/CD. Creando un workflow CI/CD accuratamente orchestrato, gli sviluppatori possono assicurarsi che le modifiche al codice vengano sottoposte a test e convalida prima di essere distribuite al sito live. Esistono molti strumenti per implementare il CI/CD nello sviluppo del software; in questo tutorial utilizzeremo GitHub Actions.

Cos’è GitHub Actions?

GitHub Actions è un potente strumento di automazione fornito da GitHub. Offre agli sviluppatori la possibilità di automatizzare attività, processi e flussi di lavoro nei progetti di sviluppo software. Si integra con i repository di GitHub, cosa che lo rende facile da usare.

Con GitHub Actions e l’API di Kinsta, è possibile definire flussi di lavoro personalizzati che si adattano alle esigenze di ogni progetto. Si può impostare una pipeline CI che testa l’applicazione e attiva il deployment su Kinsta.

Primi passi con GitHub Actions

GitHub Actions si basa sul concetto di workflow, ovvero un insieme di attività automatizzate che vengono attivate da eventi specifici o programmate a intervalli regolari. Questi eventi possono includere push di codice, richieste di pull, creazione di issue e altro ancora. Quando si verifica uno di questi eventi, GitHub Actions esegue automaticamente un workflow associato, eseguendo una serie di passaggi predefiniti.

Ogni fase del workflow rappresenta un’azione particolare, come la creazione del codice, l’esecuzione di test, il deploy o l’invio di notifiche. Adesso creeremo un workflow con tre attività:

  1. Controllare la sintassi con ESLint
  2. Eseguire i test
  3. Re-distribuire l’applicazione

Fase 1: Configurare il repository GitHub

Per iniziare, abbiamo bisogno di un repository GitHub.

Qui utilizziamo questo repository GitHub, sviluppato per il tutorial Come costruire e distribuire un’applicazione clone di ChatGPT con React e l’API di OpenAI.

Potete utilizzare lo stesso repository andando su GitHub e selezionando: Use this template > Create new repository.

In questa applicazione React, sono stati creati dei test unitari per verificare ogni componente. Viene utilizzato anche ESLint per avere sintassi e formattazione del codice perfette. Se una richiesta di pull o il codice aggiunto tramite merge inviato al repository non supera i test del workflow, la pipeline CI bloccherà il deployment.

Fase 2: Creare un file del workflow

Definiamo il workflow creando un file YAML nella directory .github/workflows del repository. Questa directory dovrebbe trovarsi al livello principale del repository. La convenzione di denominazione dei file del workflow è nome-del-workflow.yml.

  1. Nel repository, creiamo una directory .github.
  2. All’interno della directory .github, creiamo una nuova directory chiamata workflows.
  3. All’interno della directory workflows, creiamo un nuovo file con un nome come build-test-deploy.yml.

Fase 3: Scrivere il workflow CI/CD

Ora che abbiamo creato il file, definiamo un workflow con i passaggi necessari per verificare la sintassi con ESLint, eseguire i test e distribuire l’applicazione.

Creare un evento CI

Quando si crea una pipeline CI, il primo passo è dare un nome al workflow e poi impostare l’evento che lo innesca. In questo esempio, i due eventi sono una richiesta di pull e un push al branch principale.

name: Build, Test, and Deploy

on:
  push:
    branches: "main"
  pull_request:
    branches: "main"

Se si desidera programmare job periodici (CRON job) per attività specifiche, è sufficiente aggiungerli al workflow. Ad esempio, si potrebbero eseguire alcune attività come il backup del database, la pulizia dei dati o altre attività di manutenzione periodica.

Ecco un esempio di come aggiungere un CRON job al workflow:

on:
  # Existing event triggers for push and pull_request

  # Add a schedule for CRON jobs
  schedule:
    - cron: "0 0 * * *"

L’esempio precedente attiverà il workflow ogni giorno a mezzanotte (ora UTC) perché la pianificazione cron è impostata su 0 0 * * *. Si può personalizzare la pianificazione del cron per esigenze specifiche.

Per fare un altro esempio, supponiamo di voler programmare il workflow CI/CD in modo che venga eseguito ogni lunedì alle 8. Possiamo impostare un CRON job utilizzando l’evento schedule:

name: Build, Test, and Deploy

on:
  push:
    branches: "main"
  pull_request:
    branches: "main"

  # Schedule the workflow to run every Monday at 8 a.m. (UTC time)
  schedule:
    - cron: "0 8 * * 1"

jobs:
 # Add jobs

La sintassi utilizzata nell’evento schedule per pianificare i flussi di lavoro di GitHub Actions si basa sulla sintassi cron di UNIX. Permette di definire tempi o intervalli specifici per l’esecuzione automatica del workflow. La sintassi è composta da cinque campi che rappresentano diversi aspetti della pianificazione. Ogni campo è separato da uno spazio. Il formato generale è il seguente:

* * * * *
┬ ┬ ┬ ┬ ┬
│ │ │ │ │
│ │ │ │ └─ Day of the week (0 - 7) (Sunday to Saturday, where both 0 and 7 represent Sunday)
│ │ │ └─── Month (1 - 12)
│ │ └───── Day of the month (1 - 31)
│ └─────── Hour (0 - 23)
└───────── Minute (0 - 59)

Analizziamo i singoli campi:

  • Minute (0 – 59): Il minuto in cui si attiverà il cron job. Ad esempio, 15 significa che il workflow si attiverà al 15° minuto dell’ora.
  • Hour (0 – 23): L’ora in cui si attiverà il cron job. Ad esempio, 8 significa che il workflow si attiverà alle 8 del mattino.
  • Day of the month (1 – 31): Il giorno del mese in cui si attiverà il cron job. Ad esempio, 1 significa che il workflow si attiverà il primo giorno del mese.
  • Month (1 – 12): Il mese in cui si attiverà il cron job. Ad esempio, 6 significa che il workflow si attiverà a giugno.
  • Day of the week (0 – 7): Il giorno della settimana in cui si attiverà il cron job. In questo caso, 0 e 7 rappresentano entrambi la domenica, mentre 1 rappresenta il lunedì e così via. Ad esempio, 4 significa che il workflow si attiverà il giovedì.

Caratteri speciali:

  • * (asterisco): Corrisponde a qualsiasi valore per quel campo. Ad esempio, * nel campo minuti significa che il workflow si attiverà ogni minuto.
  • */n (slash): Specifica un intervallo. Ad esempio, */5 nel campo dei minuti significa che il workflow si attiverà ogni 5 minuti.
  • , (virgola): Specifica più valori. Ad esempio, 1,15,30 nel campo dei minuti significa che il workflow si attiverà al 1°, 15° e 30° minuto dell’ora.
  • - (trattino): Specifica un intervallo di valori. Ad esempio, 1-5 nel campo giorno della settimana significa che il workflow si attiverà dal lunedì al venerdì (da 1 a 5).
  • ? (punto interrogativo): Utilizzato per non specificare alcun valore. Viene comunemente utilizzato nel campo del giorno della settimana quando viene specificato il giorno del mese. Ad esempio, ? nel campo giorno della settimana e 15 nel campo giorno del mese significa che il workflow si attiverà il 15° giorno del mese, indipendentemente dal giorno della settimana.

Creare un job CI per verificare la sintassi con ESLint

Per impostare il processo di CI, creeremo job o task necessari. Ogni job deve avere un nome chiaro e comprensibile. Chiameremo il primo eslint, dato che verificherà la sintassi del codice con ESLint.

Possiamo fornire una descrizione leggibile, anche se questa parte è facoltativa. Poi specifichiamo che il job deve essere eseguito su un ambiente Ubuntu e utilizziamo una strategia a matrice per testare il codice con due versioni di Node.js: 18.x e 20.x.

jobs:
  eslint:
    name: Check Syntax with ESLint
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

Quindi, definiamo i passaggi che dovranno essere eseguiti da “ESLint”. Si tratta di verifica del codice, impostazione della versione di Node.js per l’esecuzione di ESLint, memorizzazione nella cache dei pacchetti npm, installazione delle dipendenze del progetto e infine esecuzione di ESLint per la verifica della sintassi del codice.

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Use Node.js ${{ matrix.node-version }} to Check Lint
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install Dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint

Nel workflow qui sopra, ogni fase è descritta con un nome per facilitare l’individuazione della fonte di errori o bug quando si esamina il workflow da GitHub Actions. In particolare, nel terzo passaggio utilizziamo il comando npm ci per installare le dipendenze, che è preferibile a npm install perché esegue un’installazione pulita. Inoltre, l’ultimo passaggio, l’esecuzione di ESLint con npm run lint, presuppone l’aver configurato questo comando nel file package.json.

Di seguito è riportato il job completo per la verifica della sintassi del codice con ESLint:

jobs:
  eslint:
    name: Check Syntax with ESLint
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Use Node.js ${{ matrix.node-version }} to Check Lint
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install Dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint

Creare un job CI per eseguire i test

Per aggiungere il job CI per eseguire i test, iniziamo definendo il job e assegnandogli un nome descrittivo, ad esempio tests. Specificheremo anche che questo job dipende dal job eslint. Ciò significa che il job eslint verrà eseguito per primo e prima del job tests. Questa dipendenza fa sì che il codice venga controllato per verificare la presenza di errori di sintassi prima di eseguire i test.

  tests:
    name: Run Tests
    needs: eslint
    runs-on: ubuntu-latest

Quindi, definiamo i passaggi per il job tests. Come per il job precedente, controlleremo il codice, imposteremo la versione di Node.js 18.x per eseguire i test, installeremo le dipendenze del progetto con npm ci e infine eseguiremo i test con il comando npm run test.

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Use Node.js 18.x to run Test
        uses: actions/setup-node@v3
        with:
          node-version: 18.x
          cache: 'npm'

      - name: Install Dependencies
        run: npm ci

      - name: Run Tests
        run: npm run test

Creare un job CI per distribuire con la Kinsta API

Ora creeremo il job CI per il deploy su Kinsta utilizzando l’API di Kinsta. Definiremo il job e lo chiameremo deploy. Questo job avrà delle dipendenze dai job eslint e tests, in modo da eseguire la distribuzione solo dopo che il codice sia stato controllato per individuare eventuali errori di sintassi e verificare il superamento dei test. Imposteremo il job in modo che venga eseguito in un ambiente Ubuntu utilizzando l’ultima versione disponibile.

  deploy:
    name: Re-Deploy Application
    needs: [eslint, tests]
    runs-on: ubuntu-latest

Definiamo i passaggi. In questo caso, eseguiremo un comando cURL per interagire con l’API di Kinsta in modo programmatico e attivare un re-deployment. Vediamo innanzitutto di comprendere l’API di Kinsta, quali sono le informazioni necessarie per interagire con l’API e come ottenere/memorizzare le informazioni importanti associate all’API – come la chiave API – in modo sicuro su GitHub.

L’API di Kinsta

L’API di Kinsta permette di interagire con i servizi di Kinsta in modo programmatico. Per utilizzare l’API, è necessario avere un account con almeno un sito WordPress, un’applicazione o un database in MyKinsta. Sarà anche necessario generare una chiave API per autenticarsi e accedere all’account.

Per generare una chiave API:

  1. Aprite la dashboard di MyKinsta.
  2. Andate alla pagina delle chiavi API (Nome > Impostazioni dell’azienda > Chiavi API).
  3. Fate clic su Crea chiave API.
  4. Scegliete una scadenza o impostiamo una data di inizio personalizzata e un numero di ore di scadenza della chiave.
  5. Assegnate alla chiave un nome univoco.
  6. Fate clic su Genera.
Creare una chiave API su MyKinsta
Creare una chiave API su MyKinsta.

Dopo aver creato una chiave API, copiatela e conservatela in un luogo sicuro (consigliamo di utilizzare un gestore di password), perché è l’unica volta che verrà rivelata all’interno di MyKinsta.

Come attivare la distribuzione con l’API di Kinsta

Per distribuire un’applicazione su Kinsta utilizzando l’API, sono necessari due parametri: l’ID dell’applicazione e il branch. È possibile recuperare programmaticamente l’ID dell’applicazione recuperando prima l’elenco delle applicazioni, che fornirà i dettagli di ogni applicazione, compreso il suo ID.

Dopo aver ottenuto le informazioni necessarie, potremo fare una richiesta POST all’endpoint /applications/deployments dell’API. Per la pipeline CI, utilizzeremo cURL, uno strumento a riga di comando utile per interagire con gli URL.

curl -i -X POST 
  https://api.kinsta.com/v2/applications/deployments 
  -H 'Authorization: Bearer <YOUR_TOKEN_HERE>' 
  -H 'Content-Type: application/json' 
  -d '{
    "app_id": "<YOUR_APP_ID>",
    "branch": "main"
  }'

Attivare la distribuzione con cURL nella pipeline CI/CD

Per attivare la distribuzione con l’API di Kinsta, aggiungiamo il comando cURL al comando run della pipeline CI. Tuttavia, è importante memorizzare la chiave API e l’ID dell’applicazione in modo sicuro.

Per memorizzare i segreti su GitHub e utilizzarli nelle GitHub Actions, seguite questi passaggi:

  1. Accedete al repository in cui si vuole impostare il segreto.
  2. Fate clic sulla scheda Settings nel menu del repository.
  3. Nella barra laterale di sinistra, selezionate Secrets nella categoria Options.
  4. Fate clic su New repository secret.
  5. Assegnate un nome al segreto (come KINSTA_API_KEY) e inserite la chiave API Kinsta nel campo Value.
  6. Dopo aver inserito il nome e il valore, cliccate sul pulsante Add secret per salvarlo.
  7. Ripetete la procedura per gli altri segreti.

Memorizzare i segreti in GitHub
Memorizzare i segreti in GitHub.

Una volta aggiunti i segreti, possiamo farvi riferimento nel workflow di GitHub Actions utilizzando la sintassi ${{ secrets.SECRET_NAME }}.

Ora completiamo il job deploy per la pipeline CI/CD di GitHub Actions. Definiamo i passaggi come in precedenza, con un unico passaggio per il deploy su Kinsta. Per prima cosa, definiamo i segreti nel comando env e poi aggiungiamo il comando cURL per eseguire il deploy.

    steps:
      - name: Deploy to Kinsta
        env:
          KINSTA_API_KEY: ${{ secrets.KINSTA_API_KEY }}
          APP_ID: ${{ secrets.APP_ID }}
        run: |
          curl -i -X POST 
            https://api.kinsta.com/v2/applications/deployments 
            -H "Authorization: Bearer $KINSTA_API_KEY" 
            -H "Content-Type: application/json" 
            -d '{
              "app_id": "'"$APP_ID"'",
              "branch": "main"
            }'

Nel comando cURL, si noterà che al comando sono state aggiunte le variabili d’ambiente per consentire l’accesso sicuro ai segreti durante la distribuzione.

Ecco come apparirà il workflow CI/CD finale:

name: Build, Test, and Deploy

on:
  push:
    branches: "main"
  pull_request:
    branches: "main"

jobs:
  eslint:
    name: Check Syntax with ESLint
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        
      - name: Use Node.js ${{ matrix.node-version }} to Check Lint
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
          
      - name: Install Dependencies
        run: npm ci
        
      - name: Run ESLint
        run: npm run lint

  tests:
    name: Run Tests
    needs: eslint
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        
      - name: Use Node.js 18.x to run Test
        uses: actions/setup-node@v3
        with:
          node-version: 18.x
          cache: 'npm'
          
      - name: Install Dependencies
        run: npm ci
        
      - name: Run Tests
        run: npm run test

  deploy:
    name: Re-Deploy Application
    needs: [eslint, tests]
    runs-on: ubuntu-latest

    steps:
      - name: Deploy to Kinsta
        env:
          KINSTA_API_KEY: ${{ secrets.KINSTA_API_KEY }}
          APP_ID: ${{ secrets.APP_ID }}
        run: |
          curl -i -X POST 
            https://api.kinsta.com/v2/applications/deployments 
            -H "Authorization: Bearer $KINSTA_API_KEY" 
            -H "Content-Type: application/json" 
            -d '{
              "app_id": "'"$APP_ID"'",
              "branch": "main"
            }'

Copiamo il workflow e incolliamolo nel file build-test-deploy.yml. Quindi avviamo una richiesta di pull per aggiungere questo file al branch principale del repository. Ricordate che questa richiesta di pull attiverà automaticamente il workflow.

Ciò permette di rivedere le modifiche apportate al repository e di essere sicuri che ogni nuova modifica contenuta nella richiesta di pull soddisfi i controlli specificati prima di decidere se effettuarne il merge con la codebase.

Memorizzare i segreti in GitHub
Memorizzare i segreti in GitHub.

Mentre eseguite il merge della richiesta di pull, andate alla scheda Actions del repository GitHub per vedere il workflow CI/CD in esecuzione.

Riepilogo di GitHub Actions
Riepilogo di GitHub Actions.

Possiamo fare clic su ogni job per vederne i dettagli (ecco perché è consigliabile dare una descrizione significativa a ogni fase del job).

Dettagli delle fasi CI
Dettagli delle fasi CI.

Applicare il workflow delle richieste di pull su GitHub

Per garantire una gestione ottimale del codice e favorire la collaborazione nei repository di GitHub, è utile implementare un flusso di richieste di pull e bloccare i commit diretti al branch principale. Questo approccio permette di creare un processo di sviluppo controllato e organizzato, dato che tutte le modifiche vengono sottoposte a richieste di pull e revisioni prima di essere unite al branch principale.

In questo modo, i team di sviluppo possono migliorare la qualità del codice, ridurre al minimo il rischio di introdurre bug e mantenere una cronologia trasparente delle modifiche.

Ecco come impostare l’applicazione del workflow delle richieste di pull:

  1. Fate clic sulla scheda Settings del repository GitHub.
  2. Sotto Code and Automation, selezionate Branches dalla barra laterale delle opzioni.
  3. Se non esistono regole, fate clic su Add branch protection rule.
  4. Date un nome alla regola, quindi selezionate la casella di controllo Require a pull request before merging. In questo modo verranno visualizzate altre opzioni di configurazione.
  5. Poi selezionate la casella Require status checks to pass before merging.
  6. Personalizzate altre opzioni in base alle vostre preferenze.
  7. Cliccate sul pulsante Create per salvare la regola.

Applicazione del workflow delle richieste di pull su GitHub
Applicazione del workflow delle richieste di pull su GitHub.

Seguendo questi passaggi, abbiamo impostato una regola per applicare il workflow delle richieste di pull nel nostro repository GitHub. Questo fa sì che tutte le modifiche siano sottoposte a revisione e a controlli automatici prima di essere unite al branch principale, in modo da avere un ambiente di sviluppo più affidabile e collaborativo.

Riepilogo

Combinando la potenza di GitHub Actions e dell’API di Kinsta, è possibile ottimizzare il workflow di sviluppo e offrire al proprio team di sviluppo un ambiente collaborativo ed efficiente.

Gli sviluppatori possono contribuire con sicurezza al codice, sapendo che sarà testato a fondo prima di raggiungere la produzione, e gli stakeholder possono stare tranquilli sapendo che il processo di distribuzione è ben controllato e a prova di errore.

Come utilizzate l’API di Kinsta? Quali endpoint vorreste che aggiungessimo all’API? Quale tutorial vorreste leggere sull’API di Kinsta?

Joel Olawanle Kinsta

Joel is a Frontend developer working at Kinsta as a Technical Editor. He is a passionate teacher with love for open source and has written over 200 technical articles majorly around JavaScript and it's frameworks.