В этом руководстве показано, как перенести ваши расширения из устаревшей среды Firebase Extensions в функцию, которую ваши пользователи будут устанавливать и развертывать в своей собственной кодовой базе Cloud Functions for Firebase (2-го поколения).
Это рекомендуемый путь миграции. Firebase будет поддерживать список расширений с официальными аналогами в npm; это руководство поможет вам создать свой собственный список.
В данном руководстве в качестве примера используется расширение Stream Firestore to BigQuery (firestore-bigquery-export). Каждый раздел заканчивается примером , демонстрирующим, как выглядело это расширение до миграции и как оно выглядит после миграции в виде пакета @firebase/firestore-bigquery-export.
Зарегистрируйтесь, чтобы получить дополнительную информацию и помощь по миграции расширений.
Если у вас есть вопросы по миграции с Firebase Extensions вы можете связаться с нами по адресу firebase-extensions-migrator-support-external@google.com . Мы также будем отправлять электронные письма этой группе по мере обновления руководства с дополнительной информацией о сборке, тестировании и распространении ваших функций второго поколения.
Чтобы присоединиться к этой группе, отправьте сообщение на адрес firebase-extensions-migrator-support-external+subscribe@google.com , в ответ вы получите электронное письмо с запросом на вступление. Вам необходимо ответить на это письмо, а не нажимать кнопку «Присоединиться к этой группе».
Прежде чем начать
Для завершения миграции вам понадобятся следующие функции Cloud Functions :
Параметризованная конфигурация . Каждый параметр, объявленный в
extension.yamlстановится определенным параметром в коде вашего пакета.Декларативные роли IAM и необходимые API. Каждая роль, объявленная в
extension.yamlстановится вызовомrequiresRole(...), а каждый API — вызовомrequiresAPI(...)в коде вашей функции. Во время развертывания Firebase CLI предоставляет объявленные роли управляемой учетной записи службы среды выполнения и включает объявленные API от вашего имени.События жизненного цикла для кодовых баз Cloud Functions . Кодовые базы Cloud Functions теперь поддерживают события жизненного цикла, аналогичные тем, что используются в Firebase Extensions . Объявляйте события установки и обновления с помощью хуков жизненного цикла
afterFirstDeploy(...)иafterRedeploy(...). Они заменяютlifecycleEventsкоторые вы объявляете вextension.yaml.
Проведите инвентаризацию пристройки.
Начните с инвентаризации вашего расширения: составьте полный список всего, что расширение объявляет, включает в себя и документирует, чтобы каждое поведение имело определенное место назначения во второй функции и ничего не было потеряно при миграции.
Проанализируйте каждый из следующих пунктов и отметьте, что вы обнаружили:
extension.yaml, в котором объявляются ваши параметры, функции, события, роли IAM, необходимые API, секреты и хуки жизненного цикла.functions/, где находится код ваших функций, зависимости, конфигурация сборки, триггеры и функции очереди задач.README.md,PREINSTALL.mdиPOSTINSTALL.mdсодержат шаги установки, предупреждения и примечания по выставлению счетов.scripts/, который содержит любые утилиты для импорта, заполнения, управления идентификацией и доступом (IAM), восстановления или миграции, а также любые другие инструменты, которые вы поставляете вместе с расширением.
Затем для каждого элемента в extension.yaml определите, куда он будет помещен в npm-пакете:
Преобразуйте пользовательскую конфигурацию в параметры Cloud Functions (раздел 5).
Преобразование секретов в секреты Cloud Functions (раздел 6).
Преобразуйте роли IAM в объявления
requiresRole(...)(раздел 8).При необходимости преобразуйте необходимые API Google в объявления
requiresAPI(...)(раздел 8).Преобразуйте обработчики установки и обновления в объявления
afterFirstDeploy(...)иafterRedeploy(...)(раздел 8).
Пример работы: передача данных из Firestore в BigQuery
При чтении файлов firestore-bigquery-export/extension.yaml и functions/ выводится следующий список файлов:
| В файле extension.yaml | Количество / значение | Куда это денется |
|---|---|---|
| параметры | 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) | Параметры Cloud Functions (раздел 5) |
| апи | bigquery.googleapis.com | требует API(...) (раздел 7) |
| роли | bigquery.dataEditor, datastore.user, bigquery.user | requiresRole(...) (раздел 7) |
| ресурсы | 1 триггер события (fsexportbigquery) + функции очереди задач (initBigQuerySync, setupBigQuerySync) | Функции экспортируемого пакета (раздел 3) |
| события жизненного цикла | onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync | afterFirstDeploy / afterRedeploy (раздел 9) |
| скрипты/ | импорт/ (заполнение), gen-schema-view/ | Сохранено в виде скриптов (здесь это выходит за рамки данной статьи). |
Расширение не объявляет тип: секретные параметры, поэтому в разделе 6 этого руководства ничего переносить не нужно. Триггер событий уже относится ко второму поколению; только функции очереди задач по-прежнему относятся к первому поколению (это актуально в разделе 3).
Обновите файл package.json.
Обновите файл package.json вашего расширения. Если вы переносите одно расширение, это может быть корневой файл package.json . Если вы переносите много расширений в одном репозитории, присвойте каждому расширению свой собственный файл package.json.
Минимальные версии SDK. Укажите firebase-functions >= 7.3 и firebase-admin >= 14.2.0 в качестве зависимостей. Также укажите вашу версию firebase-functions в качестве зависимой пары.
{
"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"
}
}
В дополнение к вашей обычной зависимости, укажите firebase-functions в качестве зависимой библиотеки, чтобы в проекте Cloud Functions ваших пользователей использовалась та же версия SDK, что и при написании вашей библиотеки.
Пример работы: передача данных из Firestore в BigQuery
Раньше. Файл functions/package.json расширения является приватным, в нём указывается идентификатор расширения, и firebase-functions объявляется прямой зависимостью:
{
"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"
}
}
После этого пакет, готовый к публикации: имя с указанием области видимости, карта экспорта и firebase-functions перемещены в 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"
}
}
Обновление функций с 1-го поколения до 2-го поколения.
Если ваше расширение по-прежнему экспортирует функции первого поколения, преобразуйте каждую функцию в её эквивалент второго поколения. Импортируйте функции из модулей firebase-functions/... и передайте параметры времени выполнения в параметрах функции.
Вы можете свести к минимуму усилия по переписыванию кода, используя деструктуризацию событий второго поколения, и избежать переписывания логики функций, поскольку SDK второго поколения теперь предоставляет параметры версии 1 в качестве полей в объекте события, что позволяет использовать деструктурированные/именованные параметры и сохранять бизнес-логику без изменений.
Раньше. 1-е поколение:
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);
});
После. 2-е поколение:
import { onDocumentWritten } from "firebase-functions/firestore";
export const syncV2 = onDocumentWritten(
{ document: "{collectionId}/{documentId}" },
async ({change,context}) =>
await handleWrite(change.before, change.after, context.params);
);
Полный список различий между функциями первого и второго поколений можно найти в сравнении версий Cloud Functions .
Преобразование параметров и секретов расширения.
Преобразовать параметры
Каждый параметр, который вы указываете в extension.yaml становится параметром Cloud Functions .
Преобразование прямых чтений из среды:
const collectionPath = process.env.COLLECTION_PATH;
в параметры 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);
}
);
Используйте collectionPath.value() для чтения строки внутри обработчика; используйте collectionPath напрямую там, где ожидается заполнитель, например, путь к триггеру функции.
Интерфейс командной строки Firebase обнаруживает ваши параметры и считывает их значения из файлов .env, projectId или запрашивает их у пользователей во время развертывания. Сохраняйте те же имена параметров, чтобы значения из существующей установки перенеслись.
Важно, чтобы вы вообще не меняли имена параметров, объявленных в вашем коде. Миграция расширений автоматически сохранит существующие значения параметров конечного пользователя, но только если имена останутся неизменными.
Рабочий пример: потоковая передача данных из Firestore в BigQuery . Параметр, объявленный в extension.yaml , считывается как необработанная переменная окружения в config.ts :
# extension.yaml
- param: COLLECTION_PATH
label: Collection path
type: string
required: true
// functions/src/config.ts
collectionPath: process.env.COLLECTION_PATH,
После этого команда defineString обнаруживает CLI и считывает данные из файла .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} }
}),
Имя параметра остается неизменным, поэтому существующий файл .env продолжает работать.
Преобразовать секреты
В файле extension.yaml вы объявляете секреты с типом: secret. Среда выполнения расширений хранит и связывает их, поэтому ваш код расширения может напрямую считывать process.env.PARAM_NAME. В типичном коде Cloud Functions вы объявляете и связываете каждый секрет явно:
import { defineSecret } from "firebase-functions/params";
import { onRequest } from "firebase-functions/https";
const apiKey = defineSecret("API_KEY");
export const fn = onRequest({ secrets: [apiKey] }, handler);
После переноса вашего расширения в npm-пакет/комплект, управление секретными ссылками будет осуществляться в файле .env конечного пользователя. Важно не изменять имена секретов, объявленные в вашем коде . В процессе миграции секреты конечного пользователя будут перенесены соответствующим образом.
Пример работы: Отправка электронного письма из Cloud Firestore
Ранее переменные MAIL_COLLECTION и SMTP_PASSWORD считывались как необработанные переменные окружения в файле 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,
После этого. Один defineString и один defineSecret ; CLI обнаруживает оба и считывает их из файла .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();
// ...
}
);
Перенос вызовов внутренней очереди задач
Некоторые расширения добавляют задачи в свои собственные очереди задач из кода своих функций, используя Firebase Admin SDK . Это отличается от получения отправленной задачи (рассмотрено в разделах «Функции обновления» и «Круты жизненного цикла преобразования »). В данном случае ваш код выступает в роли производителя , который вызывает queue.enqueue(...).
В предыдущих версиях Admin SDK расширениям требовалось передавать свой собственный идентификатор экземпляра расширения в качестве второго параметра для обращения к функции очереди задач в том же расширении. Начиная с версии `firebase-admin` 14.2.0, это не требуется и не рекомендуется. API очереди задач теперь по умолчанию будет обращаться к очередям задач в том же контексте (например, в расширении). Безопасно и даже рекомендуется удалить этот параметр в вашем коде как для расширений, так и для отдельных функций. Удаление этого параметра обеспечивает переносимость и обратную совместимость.
Все остальное, касающееся вызова enqueue — путь к ресурсу locations/ region /functions/ name , полезная нагрузка задачи и ваша логика повторных попыток — остается неизменным.
Дополнительную информацию о добавлении функций в очередь с помощью Cloud Tasks см. в файле /docs/functions/task-functions .
Ранее. Расширение первого поколения.
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);
После. Расширение 2-го поколения.
import { getFunctions } from "firebase-admin/functions";
import { region } from "firebase-functions/params";
const queue = getFunctions().taskQueue(
`locations/${region.value()}/functions/syncBigQuery`);
await queue.enqueue(taskData);
Если ваш вызов функции добавления в очередь нацелен на кодовую базу с префиксом, то и имя обнаруженной функции будет иметь префикс (например, orders-syncBigQuery ).
Укажите необходимые API и роли IAM.
Перенесите требования IAM и API вашего расширения из файла extension.yaml в код:
import { requiresAPI, requiresRole } from "firebase-functions"
requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");
При использовании декларативной безопасности Firebase CLI создает или обновляет управляемую учетную запись службы среды выполнения для кодовой базы и предоставляет ей объединение всех объявленных ролей. Задокументируйте для пользователей, что все функции в кодовой базе выполняются с этими ролями, если только окончательный API не поддерживает более узкую модель.
Пример работы: передача данных из Firestore в BigQuery
Ранее. Объявлялось в файле extension.yaml; среда выполнения расширений включала API и предоставляла роли управляемой учетной записи:
apis:
- apiName: bigquery.googleapis.com
roles:
- role: bigquery.dataEditor
- role: datastore.user
- role: bigquery.user
После этого. Объявлено в коде с помощью requiresAPI и 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");
Преобразовать хуки жизненного цикла
Если ваше расширение вызывает getExtensions().runtime() , например, setProcessingState или setFatalError , удалите эти вызовы, так как они вызовут ошибку, если будут вызваны из обычно развернутой функции второго поколения. Состояние жизненного цикла теперь управляется функциями afterFirstDeploy и afterRedeploy , где отслеживание состояния не используется.
Firebase Extensions могут запускать настройку при установке, обновлении или перенастройке расширения пользователем. В вашем npm-пакете объявите эквивалентные действия жизненного цикла в коде.
Для однократной настройки:
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: {}
}
});
Для обновления конфигурации или кода:
import { afterRedeploy } from "firebase-functions/lifecycle";
afterRedeploy({
task: {
function: "runInitialSetup",
body: { reconcile: true }
}
});
Сделайте действия жизненного цикла идемпотентными. Вашим пользователям может потребоваться повторно запустить их вручную, если отправка или выполнение завершится неудачей:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME
Пример работы: передача данных из Firestore в BigQuery
Ранее использовались lifecycleEvents в extension.yaml , управляемые средой выполнения расширений:
lifecycleEvents:
onInstall:
function: initBigQuerySync
processingMessage: Configuring BigQuery Sync.
onUpdate:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
onConfigure:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
После этого. Объявлено в коде; задача инициализирует BigQuery при первом развертывании:
import { afterFirstDeploy, afterRedeploy } from "firebase-functions/lifecycle";
afterFirstDeploy({ task: { function: "initBigQuerySync" } });
afterRedeploy({ task: { function: "setupBigQuerySync" } });
Процесс подготовки ресурсов является идемпотентным, поэтому повторный запуск согласует набор данных, таблицу и представления. Пользователи могут повторно запустить процесс вручную с помощью команды firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME.
Настройка документов для ваших пользователей
Напишите файл
READMEдля пакета, в котором, как минимум, будет объяснено следующее:Значения
.env, необходимые для работы пакета.Секреты, необходимые для работы пакета, и как перенести существующие секретные значения.
Роли IAM, которые объявляются в пакете с помощью
requiresRole(...).API-интерфейсы Google, которые включает или требует данный пакет.
Объявленные в пакете механизмы жизненного цикла и способы их ручного повторного запуска.
Примечания к выставлению счетов.
Что изменилось по сравнению с первоначальным расширением.
Пример работы: передача данных из Firestore в BigQuery
В файле README пакета содержится подробная таблица с описанием изменений:
| Беспокойство | В качестве расширения | Как @firebase/firestore-bigquery-export |
|---|---|---|
| Конфигурация | Параметры расширения | Параметры функций передаются через файл .env. |
| Я | Предоставлено в рамках продления срока действия. | requiresRole(...), применяется при развертывании |
| Предоставление ресурсов | Задача жизненного цикла, созданная расширениями. | задача afterFirstDeploy / afterRedeploy |
| Названия функций | ext- instanceId -fsexportbigquery | fsexportbigquery (с возможностью добавления префикса) |
Проверьте работу функций вашего устройства второго поколения.
Теперь у вас должна быть функция второго поколения, которая после развертывания будет вести себя идентично новой установке вашего расширения. Последний шаг — проверить и исправить любые проблемы, случайно возникшие в процессе.
Убедитесь, что вы используете firebase-tools >= 15.24.0, и разверните преобразованную функцию второго поколения в тестовый проект с соответствующими ресурсами для проверки её поведения. Если у вас уже есть тестовый проект, созданный после тестирования вашего расширения, используйте команду:
firebase deploy --only functions
После ввода этой команды заполните появившийся мастер, запрашивающий значения параметров, точно так же, как вы заполняли бы форму установки в консоли Firebase для расширения.
Пример работы: передача данных из Firestore в BigQuery
Мы проверяем сквозную синхронизацию Cloud Firestore и BigQuery :
- В консоли Cloud Firestore создайте коллекцию, указанную в качестве COLLECTION_PATH (users), если она еще не существует.
- Создайте документ с именем bigquery-mirror-test, содержащий любые поля с любыми значениями.
- В консоли BigQuery выполните запрос к исходной таблице changelog. Она должна содержать одну строку, регистрирующую создание документа:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
- Запросите последнюю версию представления , и вы получите последнее событие изменения для единственного присутствующего документа:
bigquery-mirror-test
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
- Удалите документ
bigquery-mirror-testв Cloud Firestore . Он исчезнет из списка последних изменений, и в таблицу исходного журнала изменений будет добавлено событиеDELETE.
Вы можете просмотреть полную историю отдельного документа с помощью:
SELECT *
FROM `PROJECT_ID.analytics.users_raw_changelog`
WHERE document_name = "bigquery-mirror-test"
ORDER BY timestamp ASC
Отличия от результатов тестирования расширения:
- Триггер развертывается как
fsexportbigquery(опционально с префиксом codebase), а неext-<instanceId>-fsexportbigquery. Найдите это имя на панели мониторинга и в журналах Cloud Functions . - Теперь ваш код будет выполняться в Firebase Local Emulator Suite как обычные функции. Вы можете задать значения параметров для использования в эмуляторе с помощью
.env.local. Вы также можете протестировать свой код с помощью SDK `firebase-functions-test`, как описано в разделе «Модульное тестирование Cloud Functions - Процесс подготовки ресурсов больше не управляется средой выполнения расширений. Если после развертывания отсутствует таблица журнала изменений, повторно запустите задачу настройки вручную:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. Задача является идемпотентной, поэтому ее повторный запуск согласует набор данных, таблицу и представления. - Значения параметров берутся из
.env, а не из формы установки, поэтому повторный запуск командыfirebase deployстановится неинтерактивным после завершения обработки файла.env.