Cloud Functions for Firebase で環境変数を扱うための方法


はじめに

Functionsにcrediancalな情報をもたせるにあたって、
可能な限り環境変数をそのまま利用した形式にしたかったのでその作業録となります。

外部APIのトークン情報などを環境変数で管理しており、CIや手元では環境変数の値で向き先を切り替えるといったことをしているので、可能な限りそこに合わせたかった都合もあります。

今回使用した言語はJavascriptで、webpack + Jestを利用している構成になります。

JavaScriptでWebpackを利用したFunctionsに関してはCloud Functions for Firebase入門 (簡単なテストまで)で記載したので、もしよければご確認ください。

公式が推奨する方法について

設定値をデプロイする方法

まず最初に公式で紹介されている設定値を与える方法を説明します。
これは環境変数をそのまま扱うわけではなく、予め設定値をデプロイしそれを利用する形式となります。
公式のリファレンスをみるとfirebaseコマンドで設定や取得もできます。コマンドで設定ファイルを登録後なら、SDKからも設定値の取得もできます。

  • 値の設定
$ firebase functions:config:set someservice.key="THE API KEY" someservice.id="THE CLIENT ID"
  • 値の取得(コマンドの場合)
$ firebase functions:config:get
# -> 結果
{
  "someservice": {
    "key":"THE API KEY",
    "id":"THE CLIENT ID"
  }
}

  • 値の取得(JavaScript SDKの場合)
const functions = require('firebase-functions');
const someservice = functions.config().someservice;

また、デフォルトでFunctionsのRuntime上のprocess.envに展開される環境変数もあるようなので、興味のある方は調べてみても面白いかもです。(参考: Firebase Cloud Functionsの環境変数はRuntimeによって変わるので注意 )

この方法について

公式が紹介している方法だけあって、こだわりがなく、複雑な設定項目が無いのならこれでもいいのかなと思います。

一方で自分は以下の観点で公式の方法は採用しない方針にしました。

  • 設定値の制限の厳しさ

    • キーにUpperCamelケースの値がなぜかだめだった。
    • いわゆる.envの内容を流用するスクリプトを書いていたら怒られました。
  • firebaseに依存する箇所の隠蔽化

    • 設定値がfirebase依存だと、全体的にfirebaseに依存する形になるので避けたかった。
    • テストを簡単にするため。
    • 将来的なfirebaseからの移行も検討の余地を残したかった。
  • 構成の一貫性

    • 環境の差異はほぼ.envで記述できる向き先が異なる程度にとどめたかった。
    • 環境の差異からくる事故や作業漏れのリスク軽減のため。

環境変数をそのまま渡す方法

色々方法が考えられると思いますが、簡単なものはbundle時に環境変数そのものを埋め込む方法かなと思います。それにはwebpackのval-loadlerが簡単に利用できてオススメです。

val-loaderを使った環境変数の渡し方

まずval-loaderをインストールします。


$ npm install val-loader --save-dev

次に環境変数(prcess.env)をbundleするためのファイル(env.js)を用意してwebpackにloaderの設定を追加すればOKです。

env.js(環境変数をbundleに組み込むためのスクリプト)
codeには実際にbundle時に評価されたい値を記載します。ほかにもキャッシュ用の項目などあったりするので、詳しくはval-loadlerを参照してください。
環境変数を利用する場合はこれをコピペすればOkです。


module.exports = (options, loaderContext) => {
    return { code: `module.exports = ${JSON.stringify(process.env)};` };
};

webpack.config.js


module.exports = {
  module: {
    rules: [
      {
        test: /env.js$/,
        use: [
          {
            loader: `val-loader`,
          },
        ],
      },
    ],
  },
};

利用する側は下記のようにenv.jsをimportをすれば変数envはビルド時のprocess.envと同じように環境変数にアクセスすることができます。

// 環境変数に FOO=bar がセットされているとする

import env from "path/to/env.js";
// env["FOO"]
// -> bar となります。

jestでの対応について

ちなみにここまでの内容だけだとwebpackの機能を使うことになっているので、import部分がjestだと動かずに失敗してしまいます。
なので少し追加作業が必要です。本当はこの作業も割愛したいのですが、方法を知っている方いれば教えてください。

jest.config.js
jestでimportを動くするためにはtrasnform設定を追加してあげます。キーには変換するファイルパターン、変換先ファイルを指定します。
今回はimport env from "path/to/env.js";に対応する変換をjest用にしますので、それにマッチするパターンで記載します。これに限ったことではないですが、webpack側でloaderを追加した場合は何かしら対応するtransformを用意する必要があるみたいです。


module.exports = {
//…
    transform: {
        'env\\.js': '<rootDir>/tests/module/env.js',
         //…
    },
//…
};

tests/module/env.js
実質val-loader用ファイルと同じことをしています。webpackとjestでコンパイルに微妙に差があるっぽいので別のファイルが必要な感じです。


module.exports = {
    process(src, filename) {
        return `module.exports = ${JSON.stringify(process.env)}`;
    },
};

これでテストも無事に通るはずです。

おわりに

環境変数を可能な限りそのまま活用する方法について説明しました。
Functionsで環境変数のまま利用しようとしている記事が見当たらずって感じだったので、自分なりにまとめてみましたがどうだったしょうか?

credencialな設定値を環境変数で管理はどこもやっている気はするので、それをFunctionsで活用できたら結構便利なんじゃないでしょうか?おとなしくCloud Runを使う手も
同じような悩みを抱えている方の助けになれば幸いです。

感想や意見あればコメントあると嬉しいです。まさかりも歓迎です。