🙃

Cloud Functions for Firebase への Credential の渡し方決定版

2 min read

Cloud Functions for FirebaseでFirebase Admin SDKを使いたいとき、サービスアカウントファイル (長いので「あのJSON」と呼びます) の置き場所に困りませんか?

既出 or 常識 だったらクソ記事です。すみません。

TL; DR

  • あのJSON をBase64エンコードする
  • firebase functions:config:set する
  • 👍

今まで

公式のインストラクションでは あのJSON をホームディレクトリかどこかに置いていたと思いますが、ゴッチャゴチャになるのも嫌なので私はプロジェクトの中に credentials というディレクトリを作って中身をignoreするようにしていました。

公式のやり方でも私のやり方でも、ローカルのFirebase CLIを使ってデプロイする分にはちゃんと動きます。
短期間しか使わないプロジェクトならこれでもいいと思いますが、それなりにイジるプロジェクトになると自動でデプロイしたいですよね。

問題点

w9jds/firebase-action を使ってGitHub Actionsからデプロイしたいと思ったのですが、当然ながらGitHubのリポジトリに あのJSON は上がっていないので、ビルドが通りません。

解決

あのJSON をまるごとBase64エンコードしたら環境変数で渡せるのでは!?

Cloud Functionsには 環境 を設定する機能があります。
ローカルのCLIで以下のように実行すると、FunctionsのSDK経由でアクセスできる環境変数にセットされます。

firebase functions:config:set service_account.project_name='BASE64_ENCODED_SERVICE_ACCOUNT_KEY'

エミュレータで同じ値を使いたければ、さらに以下のように実行するといい感じになります。

firebase functions:config:get > .runtimeconfig.json

生成した .runtimeconfig.json は忘れずにignoreしましょう!
せっかく回りくどいことをしたのに台無しになってしまいます。

環境変数を使うときはBase64のデコードも必要ですしセットされていなかった場合のハンドリングもあった方が親切なので、こんなモジュールを用意すると便利かと思います。

import * as functions from 'firebase-functions'

const decode = (data? : string) => {
  if (data) {
    return JSON.parse(Buffer.from(data, 'base64').toString())
  } else {
    throw new Error('certificate is not set')
  }
}

export const SERVICE_ACCOUNT = decode(functions.config().service_account.project_name)

既に decode でやることは済んでいるので、Admin SDKの初期化は上のモジュールをインポートして渡すだけです。

import { SERVICE_ACCOUNT } from '@/credential'

admin.initializeApp({
  credential: admin.credential.cert(SERVICE_ACCOUNT)
})

w9jds/firebase-action は賢くて、ちゃんとFunctionsにセットした環境変数を取ってきてビルドしてくれるので、GitHub Actionsのworkflowに追加すべきものはありません。

ちなみに

最初はブラウザから追加更新できて楽なリポジトリシークレットに入れようとしましたが、w9jds/firebase-action まで届かなくて悩みました。
でもそもそもバンドル時に環境変数が展開されないかもしれないし、ここを通過してもデプロイ先までは届かないかもしれないな~と思い、途中でやめました。

Discussion

ログインするとコメントできます