Rilascio automatico di stage multipli React su Firebase

Introduzione

Uno degli argomenti più importanti che si deve affrontare quando si deve sviluppare un software è quello del rilascio del codice nell’ambiente di sviluppo/test/produzione. In questo articolo trattiamo l’automazione del processo di rilascio di un’applicazione React che utilizza Firebase sia come BaaS (Backend As A Service) sia come hosting. Utilizzeremo GitHub come hosting del codice e le Actions di GitHub per automatizzare il processo di compilazione e di upload della web app sull’hosting di Firebase.

Quando si sviluppa un software innanzitutto è buona pratica utilizzare almeno 3 (o più) ambienti per dividere i codici delle diverse fasi intermedie (fase di sviluppo, fase di testing e fase di produzione). Nel nostro caso creeremo più versioni (stage) della app React ognuna delle quali ha un ramo git collegato ed un ambiente (progetto) su firebase su cui verrà rilasciato.

Come detto in precedenza la componente che ci permette di automatizzare il processo di rilascio della webapp su Firebase è GitHub Action

GitHub Actions ti consente di creare flussi di lavoro personalizzati per il ciclo di vita dello sviluppo del software (SDLC) direttamente nel tuo repository GitHub.

Fonte: GitHub

Nel nostro caso specifico la GitHub Action, nel momento in cui viene effettuato il push di uno dei rami Git, deve creare una macchina virtuale Ubuntu con il runtime nodejs installato, installare le dipendenza del progetto (dato che per semplicità la cartella node_modules non viene caricata su GitHub), compilarlo utilizzando le configurazioni di firebase relative allo stage del progetto ed infine caricarlo nel giusto hosting firebase. 

Essendo però 3 (o più) stage da gestire, dovremo creare 3 (o più) GitHub Action: una per ogni stage.

Rappresentazione grafica della soluzione

Prerequisiti:

  • Conoscere React
  • Conoscere Git
  • Saper implementare Firebase con React

Creazione progetti multipli Firebase

Per gestire multipli ambienti della stessa app su firebase, l’unico modo è creare diversi progetti. Il primo passaggio è quindi quello creare i diversi progetti su firebase, uno per ogni stage, ed inserire tutte le configurazioni sdk (Impostazioni progetto -> Generali -> Le tue app) all’interno di un file /src/config.js .

Script di configurazione di un singolo progetto Firebase

Per ogni progetto Firebase creato quindi bisogna memorizzare il suo script di configurazione SDK nel file config.js .

const dev = {
 firebaseConfig: {
   "apiKey": "FIREBASE_SDK_API_KEY",
   "authDomain": "FIREBASE_AUTH_DOMAIN",
   "databaseURL": "FIREBASE_DATABASE_URL",
   "projectId": "FIREBASE_PROJECT_ID",
   "storageBucket": "FIREBASE_STORAGE_BUCKET",
   "messagingSenderId": "FIREBASE_MESSAGING_SENDER_ID",
   "appId": "FIREBASE_APP_ID"
 }
};
const test = {
 firebaseConfig: {
   "apiKey": "FIREBASE_SDK_API_KEY",
   "authDomain": "FIREBASE_AUTH_DOMAIN",
   "databaseURL": "FIREBASE_DATABASE_URL",
   "projectId": "FIREBASE_PROJECT_ID",
   "storageBucket": "FIREBASE_STORAGE_BUCKET",
   "messagingSenderId": "FIREBASE_MESSAGING_SENDER_ID",
   "appId": "FIREBASE_APP_ID"
 }
};
const prod = {
 firebaseConfig: {
   "apiKey": "FIREBASE_SDK_API_KEY",
   "authDomain": "FIREBASE_AUTH_DOMAIN",
   "databaseURL": "FIREBASE_DATABASE_URL",
   "projectId": "FIREBASE_PROJECT_ID",
   "storageBucket": "FIREBASE_STORAGE_BUCKET",
   "messagingSenderId": "FIREBASE_MESSAGING_SENDER_ID",
   "appId": "FIREBASE_APP_ID"
 }
};
const config =
 process.env.REACT_APP_STAGE === 'dev' ? dev :
 process.env.REACT_APP_STAGE === 'test' ? test :
 prod;

export default {
 ...config
};

In questo modo, in base al valore della variabile d’ambiente process.env.REACT_APP_STAGE (che imposteremo in fase di start/build del progetto), viene utilizzato il relativo oggetto json con all’interno la configurazione API di firebase.

Da notare come gli oggetti “dev” “test” e “prod” contengano in questo momento solamente le api di firebase ma all’interno possono contenere tutte le variabili d’ambiente che devono cambiare in base alla versione in cui compiliamo il progetto.

Per utilizzare questa configurazione basta importare la variabile config ed utilizzarla come un normale oggetto json.

import firebase from 'firebase/app';
import config from '../src/config';
firebase.initializeApp(config.firebaseConfig);

Collegamento React e Firebase

Ora dobbiamo collegare i progetti firebase creati in precedenza al progetto React tramite questa istruzione su riga di comando:

 firebase use --add 

e selezionare il progetto dall’elenco. Ripetere questo step per ogni progetto creato precedentemente.

Aggiunta Script nel package.json

Come detto in precedenza per avviare/compilare il progetto React dobbiamo innanzitutto impostare la variabile d’ambiente process.env.REACT_APP_STAGE con lo stage di cui abbiamo bisogno (“dev”, “test”, “prod”) tramite il comando:

 env REACT_APP_STAGE=NOME_STAGE

Una volta impostato il valore della variabile d’ambiente con il nome dello stage su cui stiamo lavorando ed avviare/compilare il progetto tramite il comando

 react-scripts start/react-scripts build

Per velocizzare questo processo possiamo utilizzare gli scripts messi a disposizione da npm (o yarn), aggiungendo al nodo “scripts” del file package.json questi valori:

"start:dev": "env REACT_APP_STAGE=dev react-scripts start",
"start:test": "env REACT_APP_STAGE=test react-scripts start",
"start:prod": "env REACT_APP_STAGE=prod react-scripts start",
"build:dev": "REACT_APP_STAGE=dev react-scripts build",
"build:test": "REACT_APP_STAGE=test react-scripts build",
"build:prod": "REACT_APP_STAGE=prod react-scripts build",

Esempio di un package.json

 {
 "name": "progetto",
 "version": "1.0.0",
 "scripts": {
   "start:dev": "env REACT_APP_STAGE=dev react-scripts start",
   "start:prod": "env REACT_APP_STAGE=prod react-scripts start",
   "build:dev": "REACT_APP_STAGE=dev react-scripts build",
   "build:prod": "REACT_APP_STAGE=prod react-scripts build",
   "test": "react-scripts test",
   "eject": "react-scripts eject"
 },
 ...
}

In questo modo avviando il progetto con start:NOME_STAGE o compilandolo con build:NOME_STAGE, in automatico viene impostata la variabile d’ambiente e avviato/compilato il progetto

Creazione Repository Git

Dopo aver configurato il collegamento del progetto con i vari ambienti/progetti di firebase, possiamo creare un nuovo repository Git, su cui possiamo hostare il progetto React. 

Per creare quindi un nuovo repository Git da un progetto locale esistente bisogna posizionarsi con il terminale nella root del progetto e digitare rispettivamente i comandi:

git init
git add
git commit
git remote add origin https://github.com/username/nome_repository
git push -u origin master

Questi comandi rispettivamente inizializzano il repository git in locale, aggiunge tutti i file presenti nel progetto e crea la prima commissione. Dopodichè si connette con il repository creato su GitHub tramite link

Dopo aver caricato il progetto su GitHub bisogna creare i rami relativi ai vari stage della app. In questo esempio quindi verranno creati 3 rami: dev, test e master (quello di default che useremo per la produzione). Questa operazione può essere effettuata direttamente dal sito GitHub.

GitHub Actions

Tramite le GitHub Actions si possono automatizzare tutti i processi di rilascio di un software. Nel nostro caso imposteremo 3 GitHub Actions, ognuna delle quali automatizza il processo di rilascio di ogni versione di staging sul relativo ambiente Firebase.

E’ possibile creare le azioni direttamente dalla piattaforma di GitHub selezionando la repository del progetto -> Actions -> Set up a workflow yourself

Azione 1: Rilascio della versione dev

Quando viene effettuato un push del progetto sul ramo “dev” di GitHub, allora l’azione deve:

  • Installare le dipendenze del progetto tramite npm install. La cartella npm_modules infatti non viene caricata su GitHub per motivi di velocità, quindi deve essere generata.
  • Compilare il progetto React impostando prima la variabile d’ambiente REACT_APP_STAGE = “dev”, per utilizzare la corretta configurazione sdk di firebase relativa al progetto utilizzato per lo sviluppo. Nel package.json avevamo impostato queste due istruzioni nello script “build:dev”. Di conseguenza l’azione GitHub deve solamente eseguire l’istruzione “npm run build:dev”.
  • Caricare progetto compilato sull’hosting di firebase tramite firebase deploy –only hosting -P dev

Configurazione deploy-dev.yml

name: Build and Deploy Development
on:
  push:
    branches:
      - dev
jobs:
  build:
    name: Build and Deploy Development
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@master
      - name: Install Dependencies
        run: npm install
      - name: Build
        run: npm run build:dev
      - name: Deploy to Firebase
        uses: w9jds/firebase-action@master
        with:
          args: deploy --only hosting -P dev
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}

Azione 2: Rilascio della versione test

Quando viene effettuato un push del progetto sul ramo “test” di GitHub, allora l’azione deve:

  • Installare le dipendenze del progetto tramite npm install. La cartella npm_modules infatti non viene caricata su GitHub per motivi di velocità, quindi deve essere generata.
  • Compilare il progetto React impostando prima la variabile d’ambiente REACT_APP_STAGE = “test”, per utilizzare la corretta configurazione sdk di firebase relativa al progetto utilizzato per lo sviluppo. Nel package.json avevamo impostato queste due istruzioni nello script “build:test”. Di conseguenza l’azione GitHub deve solamente eseguire l’istruzione “npm run build:test”.
  • Caricare progetto compilato sull’hosting di firebase tramite firebase deploy –only hosting -P test

Esempio deploy-test.yml

name: Build and Deploy Test
on:
  push:
    branches:
      - test
jobs:
  build:
    name: Build and Deploy Test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@master
      - name: Install Dependencies
        run: npm install
      - name: Build
        run: npm run build:test
      - name: Deploy to Firebase
        uses: w9jds/firebase-action@master
        with:
          args: deploy --only hosting -P test
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}

Azione 3: Rilascio della versione prod

Quando viene effettuato un push del progetto sul ramo “master” di GitHub, allora l’azione deve:

  • Installare le dipendenze del progetto tramite npm install. La cartella npm_modules infatti non viene caricata su GitHub per motivi di velocità, quindi deve essere generata.
  • Compilare il progetto React impostando prima la variabile d’ambiente REACT_APP_STAGE = “prod”, per utilizzare la corretta configurazione sdk di firebase relativa al progetto utilizzato per lo sviluppo. Nel package.json avevamo impostato queste due istruzioni nello script “build:prod”. Di conseguenza l’azione GitHub deve solamente eseguire l’istruzione “npm run build:prod”.
  • Caricare progetto compilato sull’hosting di firebase tramite firebase deploy –only hosting -P prod

Esempio deploy-dev.yml

name: Build and Deploy Production
on:
  push:
    branches:
      - master
jobs:
  build:
    name: Build and Deploy Production
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@master
      - name: Install Dependencies
        run: npm install
      - name: Build
        run: npm run build:prod
      - name: Deploy to Firebase
        uses: w9jds/firebase-action@master
        with:
          args: deploy --only hosting -P prod
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}

GitHub Secret

Nelle azioni GitHub configurate precedentemente è possibile vedere che nella fase “Deploy to Firebase”, viene impostata una variabile d’ambiente denominata “FIREBASE_TOKEN” a cui viene impostato il valore ${{ secrets.FIREBASE_TOKEN }}.

 - name: Deploy to Firebase
   uses: w9jds/firebase-action@master
   with:
     args: deploy --only hosting -P prod
   env:
     FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}

${{ secrets.FIREBASE_TOKEN }} viene definita “GitHub Secret”, ovvero una variabile d’ambiente segreta memorizzata all’interno dell’ambiente GitHub (non all’interno del repository). Questo permette di nascondere dati sensibili dal codice presente nel repository nel caso in cui sia pubblico.

Nel nostro caso quindi dovremo impostare nella variabile segreta chiamata “FIREBASE_TOKEN”, il token di autenticazione rilasciato da firebase in fase di login da terminale. 

Effettuiamo quindi il processo di login in firebase da terminale tramite il comando

firebase login:ci

Che ci restituirà

Waiting for authentication...
✔ Success! Use this token to login on a CI server:
1/A29..............y

Memorizziamo il token 1/A29….y restituito dal comando ed impostiamolo come GitHub Secret.

Per impostare la GitHub Secret è necessario andare nelle impostazioni del repository, scegliere la voce Secrets dal menu verticale di sinistra ed infine aggiungere una variabile.

Conclusione

Ora l’applicazione in React si troverà in 3 differenti rami Git ognuno dei quali identifica un ambiente preciso (sviluppo, test e produzione).
Tramite le Actions di GitHub automatizziamo il processo di deployment dei singoli ambienti sui relativi progetti Firebase ogni volta che viene effettuato un push sul relativo ramo Git.

Problemi che si possono rilevare