LoginSignup
5
6

More than 5 years have passed since last update.

Cloud Storageにアップロードされたファイルをslackのボットユーザーで通知するデモ関数

Last updated at Posted at 2018-05-26

概要

GCPのCloud Storageにファイルがアップロードされたことをslackのボットユーザーで通知するCloud Functionsのデモ関数です。

環境

  • Windows 10 Professional
  • Node.js 6.11.5
  • Google Cloud SDK 202.0.0

参考

事前準備

slack ボットユーザーの作成

slackのApp管理画面のカスタムインテグレーションよりBotsを設定します。
設定するとAPI TOKENが発行されるので控えておきます。

s3.png

ボットユーザーを招待

ワークスペースにボットユーザーを招待します。

/invite @rubybot

Google Cloud SDKのインストール

関数のデプロイにGoogle Cloud SDKのgcloudというコマンドラインツールを使用するのでインストールします。
インストールおよび初期設定については省略します。

依存モジュールのインストール

ストレージのオブジェクトを操作するために@google-cloud/storageを利用します。

> npm install @google-cloud/storage --save

Google Cloud RuntimeConfig APIを利用するために@google-cloud/rcloadenvを利用します。
このAPIを使うことでボットユーザーのAPI TOKENをプログラム外部で管理できるようになります。

> npm install @google-cloud/rcloadenv --save

slack APIのchat.postMessageを呼び出すためにRequest-Promiseを利用します。

> npm install request request-promise --save

バケットの作成

動作確認用にCloud Storageに"my_bucket_dpqyvp50"というバケットを作成します。

b1.png

API TOKENの登録

メッセージの送信にボットユーザーのAPI TOKENを使いますが、下記に引用した通り取扱いには注意が必要です。

ボットユーザートークンをアプリケーションと共有する際は十分注意してください。パブリックコードレポジトリでは、ボットユーザートークンを公開しないでください。トークンの安全面に関するヒントを確認する。

ソースコード中にAPI TOKENを書き込まなくても済むように、runtime-configという機能を利用します。

変数を管理するリソースを作成します。この例では"dev-config"という名前にしています。
”runtimeconfig.googleapis.com”APIが必要なので有効にします。

> gcloud beta runtime-config configs create dev-config
API [runtimeconfig.googleapis.com] not enabled on project
[************]. Would you like to enable and retry?  (y/N)?  y

Enabling service runtimeconfig.googleapis.com on project ************...

リソースにAPI TOKENを登録します。

> gcloud beta runtime-config configs variables set slack-bot-api-token {BOT_API_TOKEN} --config-name dev-config --is-text

登録されている変数の一覧を確認するには

> gcloud beta runtime-config configs variables list --config-name dev-config

変数の値を確認するには

> gcloud beta runtime-config configs variables get-value slack-bot-api-token --config-name dev-config

変数を削除するには

> gcloud beta runtime-config configs variables unset slack-bot-api-token --config-name dev-config

トリガー関数の実装

config.json
{
  "PROJECT_ID": "************",
  "CONFIG_NAME": "dev-config",
  "SLACK_BOT_API_TOKEN": "slack-bot-api-token",
  "SLACK_API": {
    "CHAT_POST_MESSAGE": "https://slack.com/api/chat.postMessage"
  }
}

Cloud Storageにファイルがアップロードされたイベントで実行される関数の本体です。
関数名を”noticeUploadfile”としました。

index.js
'use strict';
const config = require('./config.json');
const rp = require('request-promise');
const storage = require('@google-cloud/storage')();
const rcloadenv = require('@google-cloud/rcloadenv');

const STORAGE_BASE_URL = 'https://storage.googleapis.com';
const ALLOWED_CONTENT_TYPES = ['image/jpeg', 'image/png', 'image/gif'];

exports.noticeUploadfile = (event) => {
  const data = event.data;
  const context = event.context;

  if (context.eventType !== 'google.storage.object.finalize') {
    return Promise.resolve();
  }
  if (data.resourceState === 'not_exists') {
    return Promise.reject(new Error('This is a deletion event.'));
  } else if (!data.name) {
    return Promise.reject(new Error('This is a deploy event.'));
  }

  return storage.bucket(data.bucket).file(data.name)
    // https://cloud.google.com/nodejs/docs/reference/storage/1.6.x/File#makePublic
    // makePublic(callback) returns Promise containing MakeFilePublicResponse
    .makePublic()
    .then(res => {
      return rcloadenv.getVariables(config.CONFIG_NAME);
    })
    .then(variables => {
      const botApiToken = getApiToken(variables);
      const attachments = [createAttachment(data)];
      console.log(attachments);
      return sendMessage(botApiToken, attachments);
    })
    .catch(err => {
      console.error(err);
      return new Error(err);
    });
};

function getApiToken(variables) {
  const varName = `projects/${config.PROJECT_ID}/configs/${config.CONFIG_NAME}/variables/${config.SLACK_BOT_API_TOKEN}`;
  for (let variable of variables) {
    if (variable.name === varName && variable.text) {
      return variable.text;
    }
  }
  throw new Error('missing slack bot api token');
}

function createAttachment(data) {
  const bucketName = data.bucket;
  const fileName = data.name;
  const contentType = data.contentType;
  const size = Math.floor(data.size / 1024);
  const storageClass = data.storageClass;
  const attachment = {
    fallback: `"${fileName}"が${bucketName}にアップロードされました - URL: ${STORAGE_BASE_URL}/${bucketName}/${fileName}`,
    title: `${bucketName}/${fileName}`,
    title_link: `${STORAGE_BASE_URL}/${bucketName}/${fileName}`,
    pretext: `"${fileName}"が${bucketName}にアップロードされました`,
    text: `*Content-Type*: ${contentType}\n *File-Size*: ${size} KB\n *Storage-Class*: ${storageClass}`,
    color: '#F35A00'
  };
  // The thumbnail's longest dimension will be scaled down to 75px while maintaining the aspect ratio of the image.
  // The filesize of the image must also be less than 500 KB.
  if (ALLOWED_CONTENT_TYPES.includes(contentType) && size < 500) {
    attachment['thumb_url'] = `${STORAGE_BASE_URL}/${bucketName}/${fileName}`;
  }
  return attachment;
}

function sendMessage(botApiToken, attachments) {
  const headers = {
    'Content-type': 'application/json'
  };
  const params = {
    token: botApiToken,
    channel: '#general',
    attachments: JSON.stringify(attachments),
    as_user: true
  };
  const options = {
    method: 'POST',
    uri: config.SLACK_API.CHAT_POST_MESSAGE,
    headers: headers,
    qs: params,
    json: true
  }
  return rp(options);
}

注意点

slackのメッセージにアップロードした画像ファイルへのリンクを記載するために、makePublicメソッドを実行して公開リンクを有効にしています。

storage.bucket(data.bucket).file(data.name).makePublic()

管理画面で確認すると"公開リンク"にチェックが付いていることがわかります。

b3.png

公開リンクが有効なファイルは誰からでもアクセスが可能になるため注意が必要です。

デプロイ

> gcloud beta functions deploy noticeUploadfile --trigger-resource my_bucket_dpqyvp50 --trigger-event google.storage.object.finalize --memory=128MB

動作確認

Cloud Storageの管理画面から画像ファイルをアップロードしてslackにメッセージがポストされることを確認します。

この例では、foster.jpgという50KBの画像ファイルをバケットにアップロードして

b2.png

slackにメッセージが送られてくることを確認します。
また、リンクをクリックすると画像ファイルがブラウザで表示されます。

s.png

このメッセージのフォーマットは下記のattachmentで決まります。

attachment
const attachment = {
  fallback: `"${fileName}"が${bucketName}にアップロードされました - URL: ${storageBaseUrl}/${bucketName}/${fileName}`,
  title: `${bucketName}/${fileName}`,
  title_link: `${storageBaseUrl}/${bucketName}/${fileName}`,
  pretext: `"${fileName}"が${bucketName}にアップロードされました`,
  text: `*Content-Type*: ${contentType}\n *File-Size*: ${size} KB\n *Storage-Class*: ${storageClass}`,
  color: '#F35A00',
  thumb_url: `${storageBaseUrl}/${bucketName}/${fileName}`
};

ボットのユーザー名やアイコンを変更するには、as_userをfalseにしてusernameicon_emojiを任意の値を設定します。

params
const params = {
  token: config.SLACK_TOKEN,
  channel: '#general',
  attachments: JSON.stringify(attachments),
  as_user: false,
  username: 'osenbei',
  icon_emoji: ':rice_cracker:'
};

s2.png

Cloud Functions for Firebaseについて

当初はCloud Functions for Firebaseで実装を進めていたのですが、FirebaseのSparkプラン(無料プラン)では、Cloud FunctionsのネットワークはGoogle専用(外部ネットワークとの通信ができない)という制限(2018/05現在)のため断念しました。

firebase2.png

5
6
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
5
6