Cloud Storage トリガー


Cloud Storage 内のファイルやフォルダのアップロード、更新、削除に応じて、関数をトリガーできます。

このページの例は、画像ファイルが Cloud Storage にアップロードされたときにトリガーされるサンプル関数に基づいています。このサンプル関数は、イベント属性にアクセスする方法、ファイルを Cloud Functions インスタンスにダウンロードする方法、Cloud Storage イベントの処理に関するその他の基礎知識を示しています。

必要なモジュールをインポートする

まず、次の Cloud Storage イベントの処理に必要なモジュールをインポートします。

Node.js

 const {onObjectFinalized} = require("firebase-functions/v2/storage");

Python

 from firebase_functions import storage_fn

完全なサンプルを構築するには、Firebase Admin SDK と画像処理ツールの依存関係も追加します。

Node.js

 const {initializeApp} = require("firebase-admin/app");
const {getStorage} = require("firebase-admin/storage");
const logger = require("firebase-functions/logger");
const path = require("path");

// library for image resizing
const sharp = require("sharp");

initializeApp();

Python

 import io
import pathlib

from PIL import Image

from firebase_admin import initialize_app

initialize_app()
from firebase_admin import storage

Cloud Storage 関数のスコープを設定する

次のパターンを使用して、関数のスコープを特定の Cloud Storage バケットに設定し、必要なオプションを設定します。

Node.js

// scope handler to a specific bucket, using storage options parameter
export archivedopts = onObjectArchived({ bucket: "myBucket" }, (event) => {
  //…
});

Python

# Scope handler to a specific bucket using storage options parameter
@storage_fn.on_object_archived(bucket="myBucket")
def archived_bucket(event: storage_fn.CloudEvent[storage_fn.StorageObjectData]):
    # ...

一方、次に示すサムネイル生成関数の例では、プロジェクトのデフォルト バケットをスコープとしています。

Node.js

exports.generateThumbnail = onObjectFinalized({cpu: 2}, async (event) => {
// ...
});

Python

@storage_fn.on_object_archived()
def generatethumbnail(event: storage_fn.CloudEvent[storage_fn.StorageObjectData]):
    # ...

関数のロケーションを設定する

ロケーションが一致しないとデプロイが失敗することもあります。また、Cloud Storage バケットのロケーションと関数のロケーションとの距離によっては、ネットワーク レイテンシが大幅に増加することがあります。このような状況を回避するには、次のいずれかの方法でバケットやトリガーのロケーションと一致するように関数のロケーションを指定します。

  • 関数のロケーションがトリガーのロケーションと同じである
  • 関数のロケーションがトリガーのロケーション内にある(トリガーのリージョンがデュアル / マルチ リージョンの場合)
  • トリガーのリージョンが us-central1 に設定されている場合、関数はどのロケーションにも配置できる

Cloud Storage イベントを処理する

Cloud Storage イベントに応答するハンドラは次のとおりです。

Node.js

  • onObjectArchived。バケットでオブジェクトのバージョニングが有効になっている場合にのみ送信されます。このイベントは、アーカイブ化の操作または同名オブジェクトのアップロードによる上書きが原因で、オブジェクトのライブ バージョンがアーカイブ バージョンになったことを表します。
  • onObjectDeleted。オブジェクトが完全に削除された場合に送信されます。バケットのライフサイクル構成で上書きまたは削除されたオブジェクトも対象になります。オブジェクトのバージョニングが有効になっているバケットでは、storage.objects.delete メソッドによりオブジェクトがアーカイブされた場合も含めて、アーカイブの発生時にはこのイベントを送信しません(onArchive 参照)。
  • onObjectFinalized。バケットで新しいオブジェクト(または既存オブジェクトの新しい世代)が正常に作成された場合に送信されます。既存のオブジェクトのコピーまたは書き換えを行った場合にも送信されます。アップロードが失敗した場合、このイベントはトリガーされません。
  • onMetadataUpdated。既存オブジェクトのメタデータが変更された場合に送信されます。

Python

  • on_object_archived。バケットでオブジェクトのバージョニングが有効になっている場合にのみ送信されます。このイベントは、アーカイブ化の操作または同名オブジェクトのアップロードによる上書きが原因で、オブジェクトのライブ バージョンがアーカイブ バージョンになったことを表します。
  • on_object_deleted。オブジェクトが完全に削除された場合に送信されます。バケットのライフサイクル構成で上書きまたは削除されたオブジェクトも対象になります。オブジェクトのバージョニングが有効になっているバケットでは、storage.objects.delete メソッドによりオブジェクトがアーカイブされた場合も含めて、アーカイブの発生時にはこのイベントを送信しません(onArchive 参照)。
  • on_object_finalized。バケットで新しいオブジェクト(または既存オブジェクトの新しい世代)が正常に作成された場合に送信されます。既存のオブジェクトのコピーまたは書き換えを行った場合にも送信されます。アップロードが失敗した場合、このイベントはトリガーされません。
  • on_metadata_updated。既存オブジェクトのメタデータが変更された場合に送信されます。

Cloud Storage オブジェクトの属性にアクセスする

Cloud Functions は、更新されたファイルのサイズやコンテンツ タイプなど、Cloud Storage オブジェクトのいくつかの属性を公開します。metageneration 属性は、オブジェクトのメタデータが変更されるたびに増分されます。新しいオブジェクトの場合、metageneration の値は 1 です。

Node.js

const fileBucket = event.data.bucket; // Storage bucket containing the file.
const filePath = event.data.name; // File path in the bucket.
const contentType = event.data.contentType; // File content type.

Python

bucket_name = event.data.bucket
file_path = pathlib.PurePath(event.data.name)
content_type = event.data.content_type

サムネイル生成サンプルでは、これらの属性のいくつかを使用して、関数が返される終了ケースを検出します。

Node.js

// Exit if this is triggered on a file that is not an image.
if (!contentType.startsWith("image/")) {
  return logger.log("This is not an image.");
}
// Exit if the image is already a thumbnail.
const fileName = path.basename(filePath);
if (fileName.startsWith("thumb_")) {
  return logger.log("Already a Thumbnail.");
}

Python

# Exit if this is triggered on a file that is not an image.
if not content_type or not content_type.startswith("image/"):
    print(f"This is not an image. ({content_type})")
    return

# Exit if the image is already a thumbnail.
if file_path.name.startswith("thumb_"):
    print("Already a thumbnail.")
    return

ファイルをダウンロード、変換、アップロードする

Cloud Storage からファイルをダウンロードする必要がない場合もありますが、Cloud Storage に保存されているファイルからサムネイル画像を生成するなどの込み入った作業を行う場合は、関数インスタンス(つまり、コードを実行する仮想マシン)にファイルをダウンロードする必要があります。

Cloud Functions を、Node.js 用の sharp や Python 用の Pillow などの画像処理プログラムと組み合わせて使用すると、グラフィカルな画像ファイルに対して操作を行うことができます。次に、アップロードした画像ファイルのサムネイル画像を作成する方法の例を示します。

Node.js

/**
 * When an image is uploaded in the Storage bucket,
 * generate a thumbnail automatically using sharp.
 */
exports.generateThumbnail = onObjectFinalized({cpu: 2}, async (event) => {

  const fileBucket = event.data.bucket; // Storage bucket containing the file.
  const filePath = event.data.name; // File path in the bucket.
  const contentType = event.data.contentType; // File content type.

  // Exit if this is triggered on a file that is not an image.
  if (!contentType.startsWith("image/")) {
    return logger.log("This is not an image.");
  }
  // Exit if the image is already a thumbnail.
  const fileName = path.basename(filePath);
  if (fileName.startsWith("thumb_")) {
    return logger.log("Already a Thumbnail.");
  }

  // Download file into memory from bucket.
  const bucket = getStorage().bucket(fileBucket);
  const downloadResponse = await bucket.file(filePath).download();
  const imageBuffer = downloadResponse[0];
  logger.log("Image downloaded!");

  // Generate a thumbnail using sharp.
  const thumbnailBuffer = await sharp(imageBuffer).resize({
    width: 200,
    height: 200,
    withoutEnlargement: true,
  }).toBuffer();
  logger.log("Thumbnail created");

  // Prefix 'thumb_' to file name.
  const thumbFileName = `thumb_${fileName}`;
  const thumbFilePath = path.join(path.dirname(filePath), thumbFileName);

  // Upload the thumbnail.
  const metadata = {contentType: contentType};
  await bucket.file(thumbFilePath).save(thumbnailBuffer, {
    metadata: metadata,
  });
  return logger.log("Thumbnail uploaded!");
});

ファイルを Cloud Functions インスタンスの一時ディレクトリにダウンロードします。この場所で、必要に応じてファイルを処理してから、Cloud Storage にアップロードできます。非同期タスクを実行するときは、コールバックで JavaScript Promise を返すようにしてください。

Python

@storage_fn.on_object_finalized()
def generatethumbnail(event: storage_fn.CloudEvent[storage_fn.StorageObjectData]):
    """When an image is uploaded in the Storage bucket, generate a thumbnail
    automatically using Pillow."""

    bucket_name = event.data.bucket
    file_path = pathlib.PurePath(event.data.name)
    content_type = event.data.content_type

    # Exit if this is triggered on a file that is not an image.
    if not content_type or not content_type.startswith("image/"):
        print(f"This is not an image. ({content_type})")
        return

    # Exit if the image is already a thumbnail.
    if file_path.name.startswith("thumb_"):
        print("Already a thumbnail.")
        return

    bucket = storage.bucket(bucket_name)

    image_blob = bucket.blob(str(file_path))
    image_bytes = image_blob.download_as_bytes()
    image = Image.open(io.BytesIO(image_bytes))

    image.thumbnail((200, 200))
    thumbnail_io = io.BytesIO()
    image.save(thumbnail_io, format="png")
    thumbnail_path = file_path.parent / pathlib.PurePath(f"thumb_{file_path.stem}.png")
    thumbnail_blob = bucket.blob(str(thumbnail_path))
    thumbnail_blob.upload_from_string(thumbnail_io.getvalue(), content_type="image/png")

このコードは、一時ディレクトリに保存した画像のサムネイル(200x200)を作成し、それを Cloud Storage に再アップロードします。