Questa guida mostra come eseguire la migrazione delle estensioni dall'ambiente Firebase Extensions ritirato a una funzione che gli utenti installano ed eseguono il deployment nel proprio Cloud Functions per la base di codice Firebase (2ª gen.).
Questo è il percorso di migrazione consigliato. Firebase manterrà un elenco di estensioni con equivalenti npm ufficiali. Questa guida ti mostrerà come creare la tua.
In questa guida, l'estensione Stream Firestore to BigQuery (firestore-bigquery-export) viene utilizzata come esempio. Ogni sezione termina con un esempio pratico che mostra l'aspetto dell'estensione prima della migrazione e dopo, come pacchetto @firebase/firestore-bigquery-export.
Registrati per ricevere maggiori informazioni e assistenza sulla migrazione delle estensioni
Se hai domande su come eseguire la migrazione da Firebase Extensions, puoi contattarci all'indirizzo firebase-extensions-migrator-support-external@google.com. Invieremo un'email a questo gruppo anche quando aggiorneremo la guida con ulteriori informazioni su pacchettizzazione, test e distribuzione delle funzioni di 2ª gen.
Per unirti a questo gruppo, invia un messaggio all'indirizzo firebase-extensions-migrator-support-external+subscribe@google.com, che risponderà con un'email di richiesta di iscrizione. Devi rispondere a questa email, non fare clic sul pulsante "Iscriviti a questo gruppo".
Prima di iniziare
Per completare questa migrazione, utilizzerai le seguenti funzionalità di Cloud Functions:
Configurazione parametrizzata. Ogni parametro dichiarato in
extension.yamldiventa un parametro definito nel codice del pacchetto.Ruoli IAM dichiarativi e API richieste. Ogni ruolo dichiarato in
extension.yamldiventa una chiamatarequiresRole(...)e ogni API diventa una chiamatarequiresAPI(...)nel codice della funzione. Al momento del deployment, la CLI Firebase concede i ruoli dichiarati a un service account di runtime gestito e abilita le API dichiarate per tuo conto.Eventi del ciclo di vita per i codebase Cloud Functions. Le codebase Cloud Functions ora supportano gli eventi del ciclo di vita analoghi a Firebase Extensions. Dichiara la configurazione al momento dell'installazione e dell'aggiornamento con gli hook del ciclo di vita
afterFirstDeploy(...)eafterRedeploy(...). Questi sostituisconolifecycleEventsche dichiari inextension.yaml.
Inventario dell'estensione
Inizia con un inventario della tua estensione: un elenco completo di tutto ciò che l'estensione dichiara, spedisce e documenta, in modo che ogni comportamento abbia una destinazione definita nella funzione di 2ª gen. e nulla vada perso durante la migrazione.
Esamina ciascuno dei seguenti elementi e annota ciò che trovi:
extension.yaml, che dichiara parametri, funzioni, eventi, ruoli IAM, API richieste, secret e hook del ciclo di vita.functions/, che contiene il codice della funzione, le dipendenze, la configurazione della build, i trigger e le funzioni della coda di attività.README.md,PREINSTALL.mdePOSTINSTALL.md, che contengono passaggi di configurazione, avvisi e note di fatturazione.scripts/, che contiene eventuali utilità di importazione, backfill, IAM, riparazione o migrazione e qualsiasi altro strumento fornito insieme all'estensione.
Poi, per ogni elemento in
extension.yaml, decidi dove
inserirlo nel pacchetto npm:
Converti la configurazione utente in parametri Cloud Functions (sezione 5).
Converti i secret in secret Cloud Functions (sezione 6).
Converti i ruoli IAM in dichiarazioni
requiresRole(...)(sezione 8).Converti le API di Google richieste in dichiarazioni
requiresAPI(...), se opportuno (sezione 8).Converti gli hook di installazione e aggiornamento in dichiarazioni
afterFirstDeploy(...)eafterRedeploy(...)(sezione 8).
Esempio elaborato: trasmetti in streaming Firestore a BigQuery
La lettura di firestore-bigquery-export/extension.yaml e functions/ produce questo inventario:
| In extension.yaml | Conteggio / valore | Dove vengono inviati |
|---|---|---|
| params | 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) | Parametri Cloud Functions (sezione 5) |
| API | bigquery.googleapis.com | requiresAPI(...) (sezione 7) |
| ruoli | bigquery.dataEditor, datastore.user, bigquery.user | requiresRole(...) (sezione 7) |
| risorse | 1 trigger evento (fsexportbigquery) + funzioni di code di attività (initBigQuerySync, setupBigQuerySync) | Funzioni del pacchetto esportato (sezione 3) |
| lifecycleEvents | onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync | afterFirstDeploy / afterRedeploy (sezione 9) |
| scripts/ | import/ (backfill), gen-schema-view/ | Mantenuti come script (non rientrano nell'ambito di questo articolo) |
L'estensione non dichiara alcun tipo: parametri secret, quindi non c'è nulla da migrare nella sezione 6 di questa guida. Il trigger di eventi è già di 2ª gen.; solo le funzioni della coda delle attività sono ancora di 1ª gen. (pertinente nella sezione 3).
Aggiorna package.json
Aggiorna il file package.json dell'estensione. Se esegui la migrazione di un'estensione,
puoi utilizzare la package.json principale. Se esegui la migrazione di molte estensioni in un unico
repository, assegna a ogni estensione il proprio pacchetto.
Versioni minime dell'SDK. Dichiara firebase-functions >= 7.3 e firebase-admin >= 14.2.0 come dipendenze. Dichiara anche la tua versione di firebase-functions come dipendenza peer
{
"name": "<package-name>",
"version": "1.0.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
}
},
"engines": {
"node": ">=22"
},
"peerDependencies": {
"firebase-functions": "^7.3.0"
},
"dependencies": {
"firebase-functions": "^7.3.0",
"firebase-admin": "^14.2.0"
}
}
Dichiara firebase-functions come dipendenza peer oltre alla normale dipendenza, in modo che il progetto Cloud Functions dei tuoi utenti abbia la stessa versione dell'SDK con cui è stata scritta la tua libreria.
Esempio elaborato: trasmetti in streaming Firestore a BigQuery
Prima. Il file functions/package.json dell'estensione è privato, indica l'ID estensione e dichiara firebase-functions come dipendenza diretta:
{
"name": "firestore-bigquery-export",
"main": "lib/index.js",
"private": true,
"dependencies": {
"@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
"firebase-admin": "^14.2.0",
"firebase-functions": "^6.3.2"
}
}
Dopo. Un pacchetto pubblicabile: nome con ambito, una mappa delle esportazioni e
firebase-functions spostato in peerDependencies:
{
"name": "@firebase/firestore-bigquery-export",
"version": "0.1.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"exports": {
".": { "types": "./lib/index.d.ts", "default": "./lib/index.js" }, },
"engines": { "node": ">=22" },
"peerDependencies": { "firebase-functions": "^7.3.0" },
"dependencies": {
"@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
"firebase-admin": "^14.2.0",
"firebase-functions": "^7.3.0"
}
}
Esegui l'upgrade delle funzioni dalla 1ª alla 2ª gen.
Se l'estensione esporta ancora funzioni di 1ª gen., converti ogni funzione nel suo
equivalente di 2ª gen. Importa dai moduli firebase-functions/... e passa
le impostazioni di runtime nelle opzioni della funzione.
Puoi ridurre al minimo gli sforzi di riscrittura con la destrutturazione degli eventi patch di 2ª gen. ed evitare di riscrivere la logica della funzione perché l'SDK di 2ª gen. ora espone i parametri V1 come campi nell'oggetto evento, consentendoti di utilizzare parametri destrutturati/denominati e mantenere invariata la logica di business.
Prima. 1ª gen.:
import * as functions from "firebase-functions/v1";
export const sync = functions.firestore
.document("{collectionId}/{documentId}")
.onWrite(async (change, context) => {
await handleWrite(change.before, change.after, context.params);
});
Dopo. 2ª gen.:
import { onDocumentWritten } from "firebase-functions/firestore";
export const syncV2 = onDocumentWritten(
{ document: "{collectionId}/{documentId}" },
async ({change,context}) =>
await handleWrite(change.before, change.after, context.params);
);
Consulta il confronto tra le versioni Cloud Functions per un elenco completo delle differenze tra le funzioni di 1ª gen. e 2ª gen.
Convertire i parametri e i secret dell'estensione
Converti parametri
Ogni parametro che dichiari in extension.yaml diventa un parametro Cloud Functions.
Converti le letture dirette dell'ambiente:
const collectionPath = process.env.COLLECTION_PATH;
nei parametri Cloud Functions:
import { defineString } from "firebase-functions/params";
import { onDocumentWritten} from "firebase-functions/firestore";
const collectionPath = defineString("COLLECTION_PATH");
// Pass the param directly when used as a placeholder (e.g. trigger path)
export const sync = onDocumentWritten(
{ document: collectionPath },
async (event) => {
// Call .value() to read the string inside a handler
const path = collectionPath.value();
await handleWrite(path, event);
}
);
Utilizza collectionPath.value() per leggere la stringa all'interno di un gestore; utilizza
collectionPath direttamente dove è previsto un segnaposto, ad esempio un percorso di trigger
di funzione.
La CLI Firebase rileva i parametri e legge i relativi valori da .env, .env.projectId o chiede agli utenti durante la distribuzione. Mantieni gli stessi nomi dei parametri, in modo che i valori di un'installazione esistente vengano trasferiti.
È importante non modificare in alcun modo i nomi dei parametri dichiarati nel codice. La migrazione delle estensioni manterrà automaticamente il valore del parametro utente finale esistente, ma solo se i nomi rimangono invariati.
Esempio elaborato: trasmetti in streaming Firestore a BigQuery
Prima. Un parametro dichiarato in extension.yaml, letto come variabile di ambiente
non elaborata in config.ts:
# extension.yaml
- param: COLLECTION_PATH
label: Collection path
type: string
required: true
// functions/src/config.ts
collectionPath: process.env.COLLECTION_PATH,
Dopo. Uno defineString; la CLI lo rileva e legge da .env:
// src/config.ts
import { defineString } from "firebase-functions/params";
collectionPath: defineString("COLLECTION_PATH", {
label: "Collection path",
// We now support "nonEmpty: true" to ensure a value other than the empty string
// is entered, analagous to "required: true" in extensions.yaml
input: { text: { nonEmpty: true} }
}),
Il nome del parametro è invariato, quindi un file .env esistente continua a funzionare.
Convertire i secret
In extension.yaml, dichiari i secret con type: secret. L'ambiente di runtime delle estensioni le memorizza e le associa, in modo che il codice dell'estensione possa leggere direttamente process.env.PARAM_NAME. In un codebase Cloud Functions tipico, dichiari e associ ogni secret in modo esplicito:
import { defineSecret } from "firebase-functions/params";
import { onRequest } from "firebase-functions/https";
const apiKey = defineSecret("API_KEY");
export const fn = onRequest({ secrets: [apiKey] }, handler);
Una volta eseguita la migrazione dell'estensione a un pacchetto/kit npm, i riferimenti ai secret verranno gestiti nel file .env dell'utente finale. È importante non
modificare in alcun modo i nomi dei secret dichiarati nel codice. Durante la migrazione,
i secret degli utenti finali verranno migrati di conseguenza.
Esempio elaborato: Trigger Email From Cloud Firestore
Prima. MAIL_COLLECTION e SMTP_PASSWORD vengono letti come variabili di ambiente non elaborate in config.ts:
# extension.yaml
- param: MAIL_COLLECTION
label: Email documents collection
type: string
default: mail
required: true
- param: SMTP_PASSWORD
label: SMTP password
type: secret
// functions/src/config.ts
mailCollection: process.env.MAIL_COLLECTION,
smtpPassword: process.env.SMTP_PASSWORD,
Dopo. Un defineString e un defineSecret; la CLI rileva entrambi
e legge da .env
import { defineString, defineSecret } from "firebase-functions/params";
import { onDocumentWritten } from "firebase-functions/firestore";
const mailCollection = defineString("MAIL_COLLECTION",{ label: "Email documents collection",
default: "mail"
});
const smtpPassword = defineSecret("SMTP_PASSWORD", { label: "SMTP password" });
export const processQueue = onDocumentWritten(
{ document: `${mailCollection}/{documentId}`, secrets: [smtpPassword] },
async (event) => {
const collection = mailCollection.value();
const password = smtpPassword.value();
// ...
}
);
Migrazione delle chiamate alla coda di attività interna
Alcune estensioni mettono in coda il lavoro nelle proprie code di attività dall'interno del codice della funzione, utilizzando Firebase Admin SDK. Questa operazione è diversa dalla ricezione di un'attività inviata (descritta nelle sezioni Funzioni di upgrade e Hook del ciclo di vita di conversione). Qui il codice è il producer che chiama queue.enqueue(...).
Le versioni precedenti di Admin SDK richiedevano che le estensioni trasmettessero il proprio ID istanza estensione come secondo parametro per indirizzare una funzione di coda di attività nella stessa estensione. A partire da `firebase-admin` 14.2.0, questo non è né richiesto né consigliato. L'API Task Queue ora ha come target le code di attività nello stesso contesto (ad es. estensione) per impostazione predefinita. È sicuro e consigliabile rimuovere questo parametro nel codice sia come estensione che come funzioni autonome. La rimozione di questo parametro garantisce la portabilità e la compatibilità futura.
Tutto il resto della chiamata enqueue, ovvero il percorso della risorsa locations/region/functions/name, il payload dell'attività e la logica di ripetizione, rimane invariato.
Per maggiori dettagli sull'accodamento delle funzioni con Cloud Tasks, consulta la pagina /docs/functions/task-functions.
Prima. Estensione di 1ª gen.
import { getFunctions } from "firebase-admin/functions";
const queue = getFunctions().taskQueue(
`locations/${config.location}/functions/syncBigQuery`,
process.env.EXT_INSTANCE_ID, // extension instance ID, injected by the runtime
);
await queue.enqueue(taskData);
Dopo. Estensione di 2ª gen.
import { getFunctions } from "firebase-admin/functions";
const queue = getFunctions().taskQueue(
`locations/${process.env.FUNCTION_REGION}/functions/syncBigQuery`);
await queue.enqueue(taskData);
Se la chiamata di accodamento ha come target un codebase con prefisso, anche il nome della funzione rilevato
ha un prefisso (ad esempio, orders-syncBigQuery).
Dichiarare le API e i ruoli IAM richiesti
Sposta i requisiti IAM e API della tua estensione da extension.yaml nel codice:
import { requiresAPI, requiresRole } from "firebase-functions"
requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");
Con la sicurezza dichiarativa, la CLI Firebase crea o aggiorna un service account di runtime gestito per la base di codice e gli concede l'unione di tutti i ruoli dichiarati. Documenta per i tuoi utenti che tutte le funzioni nel codebase vengono eseguite con questi ruoli, a meno che l'API finale non supporti un modello più ristretto.
Esempio elaborato: trasmetti in streaming Firestore a BigQuery
Prima. Dichiarata in extension.yaml; il runtime delle estensioni ha attivato l'API e ha concesso i ruoli a un account gestito:
apis:
- apiName: bigquery.googleapis.com
roles:
- role: bigquery.dataEditor
- role: datastore.user
- role: bigquery.user
Dopo. Dichiarato nel codice con requiresAPI e requiresRole:
import { requiresAPI, requiresRole } from "firebase-functions/";
requiresAPI("bigquery.googleapis.com",
"Needed to write changelog rows and views");
requiresRole("roles/biguqery.dataEditor");
requiresRole("roles/datastore.user");
requiresRole("roles/bigquery.user");
Convertire gli hook del ciclo di vita
Se la tua estensione chiama getExtensions().runtime(), ad esempio
setProcessingState o setFatalError, elimina queste chiamate, in quanto genereranno
un errore se chiamate da una funzione di 2ª gen. normalmente implementata. Lo stato del ciclo di vita è
ora determinato da afterFirstDeploy e afterRedeploy, dove il monitoraggio dello stato
non viene utilizzato.
Firebase Extensions può eseguire la configurazione quando un utente installa, aggiorna o riconfigura un'estensione. Nel pacchetto npm, dichiara azioni del ciclo di vita equivalenti nel codice.
Per la configurazione una tantum:
import { afterFirstDeploy } from "firebase-functions/lifecycle";
import { onTaskDispatched } from "firebase-functions/tasks";
export const runInitialSetup = onTaskDispatched(async (request) => {
await initializeResources(request.data);
});
afterFirstDeploy({
task: {
function: "runInitialSetup",
body: {}
}
});
Per gli aggiornamenti di configurazione o del codice:
import { afterRedeploy } from "firebase-functions/lifecycle";
afterRedeploy({
task: {
function: "runInitialSetup",
body: { reconcile: true }
}
});
Rendi idempotenti le azioni del ciclo di vita. Se l'invio o l'esecuzione non va a buon fine, gli utenti potrebbero doverli eseguire di nuovo manualmente:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME
Esempio elaborato: trasmetti in streaming Firestore a BigQuery
Prima. lifecycleEvents in extension.yaml, generato dal runtime
delle estensioni:
lifecycleEvents:
onInstall:
function: initBigQuerySync
processingMessage: Configuring BigQuery Sync.
onUpdate:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
onConfigure:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
Dopo. Dichiarato nel codice; il provisioning dell'attività BigQuery al primo deployment:
import { afterFirstDeploy, afterRedeploy } from "firebase-functions/lifecycle";
afterFirstDeploy({ task: { function: "initBigQuerySync" } });
afterRedeploy({ task: { function: "setupBigQuerySync" } });
Il provisioning è idempotente, quindi una nuova esecuzione riconcilia il set di dati, la tabella e le viste. Gli utenti possono eseguire nuovamente l'operazione manualmente con firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME.
Configurazione dei documenti per gli utenti
Scrivi un
READMEdel pacchetto che spieghi, come minimo:I valori di
.envrichiesti dal pacchetto.I secret richiesti dal pacchetto e come eseguire la migrazione dei valori dei secret esistenti.
I ruoli IAM dichiarati dal pacchetto con
requiresRole(...).Le API di Google che il pacchetto abilita o richiede.
Gli hook del ciclo di vita dichiarati dal pacchetto e come eseguirli di nuovo manualmente.
Note di fatturazione.
Cosa è cambiato rispetto all'estensione originale.
Esempio elaborato: trasmetti in streaming Firestore a BigQuery
Il file README del pacchetto include una tabella concreta "Che cosa è cambiato":
| Problema | Come estensione | As @firebase/firestore-bigquery-export |
|---|---|---|
| Configurazione | Parametri di estensione | Parametri delle funzioni tramite .env |
| IAM | Concessa dalle estensioni | requiresRole(...), applicato al deployment |
| In fase di provisioning | Attività del ciclo di vita per estensioni | afterFirstDeploy / afterRedeploy task |
| Nomi delle funzioni | ext-instanceId-fsexportbigquery | fsexportbigquery (con prefisso facoltativo) |
Testare la funzione di 2ª gen.
Ora dovresti avere una funzione di 2ª gen. che, una volta eseguito il deployment, si comporterà in modo identico a una nuova installazione della tua estensione. L'ultimo passaggio consiste nel verificare e risolvere eventuali problemi introdotti accidentalmente durante la procedura.
Assicurati di utilizzare
firebase-tools
>= 15.25.1 e di eseguire il deployment della funzione di 2ª gen. convertita in un progetto di test
con le risorse appropriate per testarne il comportamento. Se hai già configurato un progetto di test per testare l'estensione, utilizza il comando:
firebase deploy --only functions
Dopo aver inserito questo comando, compila la procedura guidata risultante che ti chiede i valori dei parametri nello stesso modo in cui avresti compilato il modulo di installazione nella console Firebase per l'estensione.
Esempio elaborato: trasmetti in streaming Firestore a BigQuery
Verifichiamo la sincronizzazione end-to-end da Cloud Firestore a BigQuery:
- Nella console Cloud Firestore, crea la raccolta che hai impostato come COLLECTION_PATH (users) se non esiste già.
- Crea un documento denominato bigquery-mirror-test contenente campi con valori qualsiasi.
- Nella console BigQuery, esegui una query della tabella del log delle modifiche non elaborato. Deve contenere una sola riga che registra la creazione del documento:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
- Esegui una query sulla visualizzazione più recente, che dovrebbe restituire l'ultimo evento di modifica per
l'unico documento presente:
bigquery-mirror-test
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
- Elimina il documento
bigquery-mirror-testin Cloud Firestore. Scompare dalla visualizzazione più recente e alla tabella del log delle modifiche non elaborato viene aggiunto un eventoDELETE.
Puoi esaminare la cronologia completa di un singolo documento con:
SELECT *
FROM `PROJECT_ID.analytics.users_raw_changelog`
WHERE document_name = "bigquery-mirror-test"
ORDER BY timestamp ASC
Differenze rispetto al test dell'estensione:
- Il trigger viene eseguito il deployment come
fsexportbigquery(con prefisso codebase facoltativo), non comeext-<instanceId>-fsexportbigquery. Cerca questo nome nella dashboard e nei log di Cloud Functions. - Il codice verrà ora eseguito in Firebase Local Emulator Suite come funzioni normali. Puoi impostare il valore dei parametri da utilizzare nell'emulatore con
.env.local. Puoi anche testare le unità del tuo codice utilizzando l'SDK firebase-functions-test come descritto in Test delle unità di Cloud Functions - Il provisioning non è più gestito dal runtime delle estensioni. Se la tabella
del log delle modifiche non è presente dopo l'implementazione, esegui di nuovo manualmente l'attività di configurazione:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. L'attività è idempotente, quindi la sua esecuzione riconcilia il set di dati, la tabella e le viste. - I valori dei parametri provengono da
.envanziché dal modulo di installazione, pertanto le ripetizioni difirebase deploynon sono interattive una volta completato.env.