11
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

#概要

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の対象になりません。ご利用の際は十分ご注意ください。

11
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?