LoginSignup
20
15

More than 5 years have passed since last update.

Google Cloud Functionsを使ってGCSに新規アップロードされたファイルを即座に取得する

Last updated at Posted at 2017-12-19

Google Cloud Functionsとは

  • HTTPリクエスト(POST,PUT, GET, DELETE, OPTIONS)
  • GCS の状態変更
  • Cloud Pub / Sub

をトリガーにして、事前に作成しておいたスクリプトを実行できます。
(ちなみにFirebaseにも対応したCloud Functions for Firebaseでは、上記に加え以下のものもトリガー可能です。データベースへのwrite、所持ドメイン内の任意パスへのアクセス、ユーザの作成・削除、アナリティクスのイベント、モバイルアプリのクラッシュ)

環境構築

  • Cloud Functionsのインストール
    • ここを参考に。
    • gcloudからbetaのcomponents をinstall
      gcloud components update beta
  • Emulatorインストール
    • ここを参考に。
    • nodeはnodebrewとかで入れたほうがいいかも。
    • Emulatorのインストールが完了したら、consoleでwhich functionsできることを確認。
  • 注意

簡単なサンプルをローカルで動かす

  • HTTP Trriger
    • 以下のようなjsを用意し、コマンドを実行
helloWorld.js
exports.helloWorld = (req, res) => res.send("Hello, World!");
functions start
functions deploy helloWorld --trigger-http
functions call helloWorld
  • EmulatorでホストされているFunctionsを確認
functions list
  • GCS Trriger
    • 以下のようなjsを用意し、コマンドを実行
index.js
exports.index = function (event, callback) {
    const file = event.data;
    if (file.resourceState === 'not_exists') {
        console.log('File ' + file.name + ' not_exists.');
    } else {
        if (file.metageneration === '1') {
            // この判定を入れることで、ファイルの新規作成なのかそれ以外(変名、削除)なのかの
            // 判別が可能です。
            console.log('File + ' + file.name + ' created.');
            // ファイルが作成されたらやりたい処理 //
        } else {
            console.log('File + ' + file.name + ' metadata updated.');
        }
    }
    callback();
};
functions start
functions deploy index --trigger-bucket=my-bucket-project.appspot.com
functions call index --data='{"name":"upload.zip", "bucket":"my-bucket-project.appspot.com", "metageneration": "1"}'

ちなみに、GCSのBucket内にディレクトリ構造を持たせている場合、
以下のようにして指定すれば再現可能です。

functions call index --data='{"name":"functions_files/upload.zip", "bucket":"my-bucket-project.appspot.com", "metageneration": "1"}'

なお、Deployのtargetはindex.js内でexportsしているmodule名称で指定します。
index.js内に複数のexportしても、それぞれDeployすれば問題ありません。
逆にindex.js以外の名称のjsを読み込むことはできなさそうです。

実行結果のログは functions logs read で確認できます。

GoogleCloudStrage, CloudFunctions, サーバーサイドAPIの連携

まずはStorageの準備

GCSに適当なバケットを作成し、そこに適当なファイルを配置。
以下はサンプル。

  • GCP Project
    • sample-project
  • バケット
    • artifacts.sample-project.appspot.com
  • 階層
    • functions_files /
      • test.zip

Cloud Functionsでhookされるjs

  • このjsでは、バックエンドサーバーへPOSTリクエストを行います。
  • パラメータとして、ファイル名とバケット名を渡します。
index.js
var request = require('request');

exports.index = function (event, callback) {
    const file = event.data;

    if (file.resourceState === 'not_exists') {
        console.log('File ' + file.name + ' not_exists.');
        callback();
        return
    } else {
        if (file.metageneration === '1') {
            var data = {
                'file_name': file.name,
                'bucket_name': file.bucket
            };

            var options = {
                url: 'http://localhost:8000',
                method: 'POST',
                headers: {'Content-Type':'application/json'},
                json: data
             };

            request(options, function(err, response, body) {
                if (response && response.statusCode === 200 && body) {
                    console.log('backend request success.');
                } else {
                    console.log('backend error.');
                }
                callback();
            });
        } else {
            console.log('File + ' + file.name + ' metadata updated.');
            callback();
            return
        }
    }
};

バックエンドAPI

  • バックエンドAPIでは、localhost:8000 にてPythonサーバーを起動して、パラメータで指定されたファイルをGCSから取得してローカルサーバーに保存します。
sample.py
# -*- coding: utf-8 -*-
import json
from bottle import request, HTTPResponse
from google.cloud import storage

file_types = {
    'zip' : 'application/zip'
}

class Sample():
    @classmethod
    def main(cls):
        try:
            file_name = request.json.get('file_name')
            bucket_name = request.json.get('bucket_name')

            if '.' in file_name:
                file_ext = file_name.split('.')[len(file_name.split('.')) - 1]
            else:
                file_ext = ''

            result, validated_result = get_validated_file(file_name, bucket_name, file_ext)

            if result:
                body = { 'result': 'ok' }
            else:
                body = { 'result': validated_result }

            # レスポンスを返却する
            response = HTTPResponse(status=200, body=json.dumps(body))
            response.set_header('Content-Type', 'application/json')
            return response



def get_file(file_name, bucket_name):
    """GCSからファイルを取得
    """
    storage_client = storage.Client(project='sample-project')
    bucket = storage_client.get_bucket(bucket_name)
    return bucket.get_blob(file_name)


def is_accept_type(file, file_ext):
    """想定ファイルかどうかチェック
    """
    if file_ext not in file_types:
        return False
    else:
        return file.content_type == file_types[file_ext]


def get_validated_file(file_name, bucket_name, file_ext):
    """対象ファイルのバリデーション
    :return: バリデーションの成否, バリデーション結果(成功ならファイルパス)
    """
    file = get_file(file_name, bucket_name)

    if not file or not file.exists():
        return False, 'is not exist'

    if not is_accept_type(file, file_ext):
        return False, 'content_type'

    outfile_name = '/hoge/hoge/text.zip'
    # GCSのBlobオブジェクトをローカルのファイルオブジェクトとして保存
    file.download_to_filename(outfile_name)

    return True, outfile_name

CloudFunctions Emulatorで試す

functions deploy index --trigger-bucket=artifacts.sample-project.appspot.com
functions call index --data='{"name":"functions_files/test.zip", "bucket":"artifacts.sample-project.appspot.com", "metageneration": "1"}'

あらかじめ localhost:8000 でPythonサーバーを起動しておき、上記コマンドでfunctionsをローカルから叩いてPythonへリクエストが通れば成功。

Python側で指定ファイルがGCSに存在するかなどのチェックを行っているため、無い場合はちゃんとエラーが返ります。

これで動作確認が出来れば、GCSにファイル配置をトリガーにしてマスタインポートなどの処理が可能となります。

GCP上にCloudFunctionsをデプロイする

  • index.js があるディレクトリで以下のコマンドを実行すればDeploy可能です。
gcloud beta functions deploy index --stage-bucket test-stage-bucket --trigger-bucket=artifacts.sample-project.appspot.com

※GCFはバケットに展開されるため、stage-bucket はあらかじめ作成しておくこと。

上記にてDeploy完了したら、以後GCSをトリガーにしてFunctionsが実行されます。

20
15
4

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
20
15