#概要
Firebaseにおいて、Cloud StorageへZIPファイルがアップロードされたとき、それを自動展開するための方法です。
複数のファイルをまとめてアップロードしたいけど参照は個別にできたほうがうれしい…といった場合などに。
ちょっとした処理なのですが意外とスニペットが見付からなかったので記事にしました。
#手順
Cloud FunctionsのCloud Storageトリガーを利用します。
Cloud Functionsの設定と初期化、デプロイなどの基本的な手順についてはスタートガイドを参照してください。
https://firebase.google.com/docs/functions/get-started?hl=ja
プロジェクトの初期化後、 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の例です。
...
"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サービスアカウントをそのまま用いる場合、管理コンソールの 設定 > サービス アカウント > 新しい秘密鍵の生成 より認証情報ファイルをダウンロードできます。
ガイドに記載されている通り、Cloud Functions for Firebaseは2018年7月現在ベータ版での提供のため、SLAの対象になりません。ご利用の際は十分ご注意ください。