【Firebase】Cloud Functionsからstorageへのアクセスについて理解する

公開:2021/02/13
更新:2021/02/14
7 min読了の目安(約6400字TECH技術記事

Firebase と Google Cloud は少し違うものだ!!!正確に言うとFirebaseはGoogle Cloud プロジェクトに独自のサービスや機能を付与したもので、Firebaseプロジェクトの実態はGoogle Cloud プロジェクトだけども、使うAPI、SDK、メソッドが異なるので、これらをごっちゃにしてる状態でググると訳わからなくなった。ので書いた!

Firebase プロジェクトと Google Cloud の関係

Firebase コンソールで新しい Firebase プロジェクトを作成した場合、内部で実際に作成されるのは Google Cloud プロジェクトです。Google Cloud プロジェクトは、データ、コード、構成、サービスのための仮想的なコンテナと考えることができます。Firebase プロジェクトは、Google Cloud プロジェクトに Firebase 固有の構成とサービスを加えたものです。はじめに Google Cloud プロジェクトを作成し、その後に Firebase を追加することもできます。
https://firebase.google.com/docs/projects/learn-more?hl=ja

名称

Firebase

  • Cloud Firestore
  • Cloud Functions for Firebase
  • Cloud Storage (for Firebase )

Google Cloud

  • Firestore
  • Google Cloud Functions
  • Google Cloud Storage

繰り返しになるがFirebaseの各プロダクトも中身はGoogle Cloud プロジェクトであり、Firebaseコンソールでアクセス可能なプロジェクトはGCPコンソール画面からもアクセスが可能である。

初期化の方法

ローカルでテストで実行する場合を除いて、基本的にFirebaseに上げてるCloud FunctionsはinitializeApp()の引数の指定は不要で初期化が可能。

//index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
//index.ts
import * as functions from 'firebase-functions';
import * as admin from "firebase-admin";
const firebase = admin.initializeApp();

基本的に(Firebaseの)Firestore × Cloud Functions ではfunctionsadminのモジュールを読み込めばOKみたい。別途サービスアカウントを設定したり、admin.configを設定したりするケースもあるようだが、上記がまず基本形でゆるがない!

Cloud Functions から Cloud Storage にアクセスする方法

あくまでFirebaseでの話。

const functions = require("firebase-functions");
const admin = require('firebase-admin');
admin.initializeApp();

const db = admin.firestore();
const storage = admin.storage();
// db.settings({ ignoreUndefinedProperties: true }) // undifinedでエラーにならないための設定

// 例)Firestoreのデータが削除されたら Cloud Storageのデータも消す
exports.onDeleteMyPost = functions.firestore
  .document('/users/{userId}/posts/{postId}')
  .onDelete((snapshot, context) => {
    const userId = context.params.userId;
    const fileName = snapshot.data().postImageFileName; // 画像アップロード時にFirestoreに書き込んでおいたファイル名
    const path = `post/${userId}/images/${fileName}`; // Cloud Storage のパス
    return storage.bucket().file(path).delete(); // ←こんな感じでbucketを取得し操作する
  })

ここでいうstorage.bucket()は中身はGoogle Cloud で定義されているBucketと同じものとドキュメントで言われている。なのでdelete()以外にも、持ってるプロパティや使えるメソッドを知りたいという方は Google Cloud のドキュメントのBucketの項目を見てということらしい。

https://googleapis.dev/nodejs/storage/latest/

Cloud Functions ではサービスアカウントの設定は不要

ちなみにFirebaseのCloud Storageのドキュメント「Admin Cloud Storage API の概要」をみると以下のようにサービスアカウントやバケット名を設定している。しかしFirebaseのCloud Functionsのindex.jsもしくはindex.tsにおいては明示的に指定しなくても自分が属するプロジェクトのバケット名などを知っているようだ。

var admin = require("firebase-admin");
var serviceAccount = require("path/to/serviceAccountKey.json");
admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    storageBucket: "<BUCKET_NAME>.appspot.com"
});
var bucket = admin.storage().bucket();
// 'bucket' is an object defined in the @google-cloud/storage library.

https://firebase.google.com/docs/storage/admin/start?hl=ja

admin.storage と firebase.storage は違う

もともとはstorage.refFromURL()を使いたかった

そもそもやりたかったのは、Cloud Storage のダウンロードURLを Cloud Firestore に保存しているので、これをもとにファイルの保存場所を取得したかった。しかしstorage.ref()storage.refFromURL()と書けるのはFirebase Javascript SDKのstorageだけなので、Web上の文献で非常に混乱する

JavaScriptで Cloud Storage にアクセスする方法は3つある

  • Firebase API

    • Firebase Admin SDK
    • Firebase JavaScript SDK
  • Google Cloud Storage API

    • Google-Cloud Server SDK

このうち Firebase JavaScript SDK はクライアント側(フロントエンド?)で使うものらしく、Cloud Functions のNode.jsで使えるのは Firebase Admin SDK と Google-Cloud ServeSDK の2つのようだ。

※SDK=Software Development Kit。APIとかを簡単に使えるようにしてくれる奴

Admin SDK からアクセスできるstorage

先の初期化方法を用いてアクセスできるstorageはFirebase Admin SDKを使って取得したものである。このSDKは主にCloud Functionsなどのサーバーサイドで使うものらしい。

スクリーンショット 2021-02-13 19.32.42.png

https://firebase.google.com/docs/reference/admin/node/admin.storage.Storage-1

Firebaseのドキュメントにおけるadmin.storegeはプロパティappとメソッドbukectしか持ってない。

Javascript SDK からアクセスできるstorage

これはJavascriptを使うクライアント=主にWebのフロントエンド?(React, Vue, Angularとか?)が使うSDKらしい。

スクリーンショット 2021-02-13 20.11.17.png

https://firebase.google.com/docs/reference/js/firebase.storage.Storage?hl=ja

firebase.storageは.ref().refFromURL()メソッドを持っている。

Google Cloud Storage API からアクセスできるstorage?

firebase-adminモジュールやfirebaseモジュールではなく、@google-cloudのモジュールを読み込んで、Google Cloud Storage としてstoregaを取得する方法もあるらしい‥。もう訳がわからん‥。

//index.js
    // Require gcloud
    var gcloud = require('google-cloud');

    // Enable Storage
    var gcs = gcloud.storage({
      projectId: 'grape-spaceship-123',
      keyFilename: '/path/to/keyfile.json'
    });

    // Reference an existing bucket.
    var bucket = gcs.bucket('my-existing-bucket');

    // Upload a local file to a new file to be created in your bucket.
    bucket.upload('/photos/zoo/zebra.jpg', function(err, file) {
      if (!err) {
        // "zebra.jpg" is now in your bucket.
      }
    });

    // Download a file from your bucket.
    bucket.file('giraffe.jpg').download({
      destination: '/photos/zoo/giraffe.jpg'
    }, function(err) {});

https://firebase.google.com/docs/storage/gcp-integration?hl=ja

その他の Google Cloud モジュールを使うケース

他にもググっていると以下のようにfirestoreを定義しているケースがある。これはFirestoreのデータを定期的に自動バックアップするFunctionだが、この場合はadmin.firestore()ではなくて、@google-cloud/firestoreで読み込んだものを使わないと動かなかった。おそらくデータベースのexportやbucketという概念がFirebaseの中に正式に準備されてないので、(中身はGoogle Cloud プロジェクトなので)Google Cloud Firestore として処理を実行しないといけないためだろう。

//index.js
const firestore = require("@google-cloud/firestore");
const client = new firestore.v1.FirestoreAdminClient();
const bucketForExport = "gs://myproject-export";

exports.scheduledFirestoreExport = functions.pubsub
  .schedule("45 19 * * *")
  .onRun((context) => {
  // 省略

参考

https://qiita.com/HorikawaTokiya/items/59d9da0c02a4bcfe1e8d

https://qiita.com/kira_puka/items/8743c2b1a012a934ef92

・記述が正解かは不明だが参考になりそうなStackoverflow

https://stackoverflow.com/questions/53143965/uploading-files-from-firebase-cloud-functions-to-cloud-storage

・Google Cloud の Cloud Storage でのオブジェクトの削除方法

https://cloud.google.com/storage/docs/deleting-objects?hl=ja#storage-delete-object-nodejs