前提
- Cloud Functions(Node.js) + Express.js + TypeScript
- 異なるプロジェクトのFirestoreにアクセスする必要がある
- Service Account Keyのclient_email, private_keyを用いてinitializeApp()する
- Secret Managerにclient_email, private_keyを保存
- Cloud FunctionsにSecretを環境変数として渡している
最初作った構成
下記のソースだと、firebase deploy --only functions
時にエラーを吐いた
client_emailをハードコードすると、private_keyで同様のエラー。
private_keyもハードコードするとうまく動いた。
Error: Error occurred while parsing your function triggers.
Error: Service account object must contain a string "client_email" property.
index.ts
Express.jsを用いてAPIを作成
ルーティングを行う
import * as functions from "firebase-functions";
import express from "express";
import create from "./create";
const app = express();
app.post("/create", create);
export const api = functions
.region("asia-northeast1")
.https
.onRequest(app);
create.ts
import {primaryFirebase, secondaryFirebase} from "./firebaseInitialize";
const primaryFirestore = primaryFirebase.firestore();
const secondaryFirestore = secondaryFirebase.firestore();
const create = async (req, res) => {
const doc = await primaryFirestore.
.collection("collectionName")
.add({
key: "value",
});
await secondaryFirestore
.collection("collectionName")
.doc(doc.id) // primary and secondary are same id
.set({
key: "value",
});
res.send("OK");
};
export default create;
firebaseInitialize.ts
process.env.CLIENT_EMAIL
が、直前のlogには出力されているのに、certにわたすService Account ObjectのclientEmailは文字列になっていないらしい。なぜ…?
import admin from "firebase-admin";
import * as functions from "firebase-functions";
functions.logger.log("env: ", process.env); // ちゃんとログに出力されているのに……
export const primaryFirebase = admin.initializeApp();
export const secondaryFirebase = admin.initializeApp(
{
credential: admin.credential.cert({
projectId: process.env.PROJECT_ID,
clientEmail: process.env.CLIENT_EMAIL,
privateKey: process.env.PRIVATE_KEY,
}),
},
"secondary",
);
対策
ハードコードすると動くが、環境変数だと動かない。
Secret Manager経由で環境変数を渡す場合は、デプロイ時には環境変数は空のまま解釈されるとか…?
なぜだかわからないが、process.env.CLIENT_EMAIL
, process.env.PRIVATE_KEY
の読み込みを遅らせてみた。
create.ts
dynamic importを使って解釈のタイミングをズラしてみたら、うまくデプロイされた。
APIもうまく動いている。
const create = async (req, res) => {
const {primaryFirebase, secondaryFirebase} = await import("./firebaseInitialize");
const doc = await primaryFirebase.
.firestore()
.collection("collectionName")
.add({
key: "value",
});
await secondaryFirebase
.firestore()
.collection("collectionName")
.doc(doc.id) // primary and secondary are same id
.set({
key: "value",
});
res.send("OK");
};
export default create;
なぜ最初のソースだとうまくいかなかったのか、、、分かる方ぜひご教授ください