Help us understand the problem. What is going on with this article?

Cloud Storage にアップロードされたZIPファイルを自動展開する for Firebase

More than 1 year has passed since last update.

概要

Firebaseにおいて、Cloud StorageへZIPファイルがアップロードされたとき、それを自動展開するための方法です。

複数のファイルをまとめてアップロードしたいけど参照は個別にできたほうがうれしい…といった場合などに。
ちょっとした処理なのですが意外とスニペットが見付からなかったので記事にしました。

手順

Cloud FunctionsのCloud Storageトリガーを利用します。
Cloud Functionsの設定と初期化、デプロイなどの基本的な手順についてはスタートガイドを参照してください。
https://firebase.google.com/docs/functions/get-started?hl=ja

プロジェクトの初期化後、 index.js を次のように書き換えます。

index.js
'use strict'

const functions = require('firebase-functions')
const mkdirp = require('mkdirp-promise')
const gcs = require('@google-cloud/storage')({keyFilename: 'service-account-credentials.json'})
const unzipper = require('unzipper')

const path = require('path')
const os = require('os')
const fs = require('fs')

/**
 * バケットで新しいオブジェクト(または既存オブジェクトの新しい世代)が正常に作成された場合に送信されます。
 * 既存のオブジェクトをコピーまたは再作成した場合にも送信されます。
 * アップロードが失敗した場合、このイベントはトリガーされません。
 */
exports.extractZip = functions.storage.object().onFinalize((object) => {

  // ZIPファイル以外は対象外
  if (!object.name.endsWith('.zip') || !object.contentType.includes('zip')) {
    console.log(`処理対象外のファイルです。name=${object.name},contentType=${object.contentType}`)
    return null
  }

  const baseName = path.basename(object.name, path.extname(object.name))
  const srcFile = gcs.bucket(object.bucket).file(object.name)
  const tmpDir = path.join(os.tmpdir(), baseName)
  const tmpFile = path.join(os.tmpdir(), path.basename(object.name))
  const dstBucket = gcs.bucket('gs://example') // 展開先のバケット名
  const dstDir = path.join('extract', baseName) // 展開先のディレクトリパス

  return mkdirp(path.dirname(tmpFile))
  .then(() => {
    return srcFile.download({destination: tmpFile})

  }).then(() => {
    console.log('Cloud StorageからローカルへZIPファイルをダウンロードしました。', tmpFile)

    return fs.createReadStream(tmpFile)
      .pipe(unzipper.Extract({path: tmpDir}))
      .on('close', () => {console.log('ZIPファイルを次のディレクトリへ展開しました。', tmpDir)})
      .promise()

  }).then(() => {
    let uploads = []
    fs.readdirSync(tmpDir).forEach(file => {
      uploads.push(
        dstBucket.upload(path.join(tmpDir, file), {destination: path.join(dstDir, file)})
      )
    })
    return Promise.all(uploads)

  }).then(() => {
    console.log('ローカルからCloud Storageへ展開後ファイル群をアップロードしました。', dstBucket.name)

    fs.unlinkSync(tmpFile)
    fs.readdirSync(tmpDir).forEach(file => {
      fs.unlinkSync(path.join(tmpDir, file))
    })
    fs.rmdirSync(tmpDir)
    console.log('ローカルの一時ファイルを削除しました。')
    return null

  }).then(() => {
    // TODO: 後続処理のキックなど
    return null
  })
})

必要となるパッケージを npm で追加します。次がdependenciesの例です。

package.json
...
"dependencies": {
  "@google-cloud/storage": "^1.7.0",
  "firebase-admin": "~5.12.1",
  "firebase-functions": "^1.0.3",
  "mkdirp": "^0.5.1",
  "mkdirp-promise": "^5.0.1",
  "unzipper": "^0.9.2"
},
...

また、デプロイの前に、Cloud Storageへのアクセス権限を持つ認証情報ファイルを index.js と同じ functions ディレクトリへ格納してください。
上の例ではファイル名を service-account-credentials.json としています。

Firebaseサービスアカウントをそのまま用いる場合、管理コンソールの 設定 > サービス アカウント > 新しい秘密鍵の生成 より認証情報ファイルをダウンロードできます。

:warning: ガイドに記載されている通り、Cloud Functions for Firebaseは2018年7月現在ベータ版での提供のため、SLAの対象になりません。ご利用の際は十分ご注意ください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした