HTTPトリガーのCloud FunctionsをTypeScriptで開発してローカルでデバッグしてみる
HTTPトリガーで動くCloud Functionsを, TypeScriptからトランスパイルしたコードを使って開発してみる.
更に, コードをローカル環境でデバッグしてGCPにデプロイしてみる.
今回は題材として, 下の記事で書いた機能をTypeScriptで書き直してみる.
kourin.hatenadiary.com
ローカルのOSはmacOS High Sierra10.13.4で, ターミナルはzshです.
ngrokのインストール
ngrokはlocalhostで動いているサーバに外部からアクセスするためにURLを発行してくれるツール.
今回はSlackからCloud Functionsの方にアクセスするため, このツールを使用してlocalhostにアクセスできるようにする.
ngrokを使うと外部からアクセスできるようになるので, 使う時だけ起動するように....
以下でインストールできる.
$ brew install homebrew/binary/ngrok2
Nodeのインストール
Cloud Functionsが使えるnodeのバージョンはv6.11.5らしいので, ローカル環境もそれに合わせる.
nodeのバージョン管理には nodebrew を使う.
$ brew install nodebrew $ nodebrew setup
.bash_profile(zshは.zshrc)に以下を追加する.
$ export PATH=/usr/local/var/nodebrew/current/bin:$PATH $ export PATH=$HOME/.nodebrew/current/bin:$PATH
source ~/.bash_profile
や source ~/.zshrc
をして追加した部分を反映する.
nodebrewを使って, v6.11.5をインストールする.
# インストールできるバージョンを確認 $ nodebrew ls-all # node v6.11.5をインストール $ nodebrew install-binary v6.11.5 # node v6.11.5を使う $ nodebrew use v5.7.1
TypeScript開発環境構築
プロジェクトのディレクトリを作り, 以下を実行する.
$ npm install typescript tslint --save-dev
tsconfig.json と package.json を追加する.
tsconfig.json
{ "compilerOptions": { "target": "es6", "module": "commonjs", "outDir": "./", "noImplicitAny": true, "strictNullChecks": true, "sourceMap": true }, "include": [ "src/**/*.ts" ], "exclude": [ "node_modeles" ] }
package.json
{ "name": "slack_oauth", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "tsc", "watch": "tsc -w" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "typescript": "^2.8.3" } }
srcディレクトリを作り, src内にtypescriptのコードを書く.
index.ts
export const onReceiveSlackOAuth = (req, res) => { console.log(req); res.end(); };
npm run build
を実行すると, index.js が生成される.
Google Cloud Functions Emulatorの導入
Google Cloud Functions Emulatorをインストールする
$ npm install -g @google-cloud/functions-emulator
functions で使用できるが, zsh の場合は他で使われてるので, 代わりに functions-emulator を使う.
# 一般的なシェルの場合 $ functions start $ functions deploy onReceiveSlackOAuth --trigger-http # zshの場合(zshだとfunctionsが既に使われている) $ functions-emulator start $ functions-emulator deploy onReceiveSlackOAuth --trigger-http
deployすると以下のような情報が表示される.
その中からResourceの値をメモしておく.
Resourceに書かれてるURLからポート番号をみて, ngrokを起動する.
(今回は8010番)
$ ngrok http 8010
http://(hogehoge).ngrok.io
と書かれたURLが外部から見たURLである.
このURLに外部からアクセスすると, localhost:8010に繋がる.
cloud functions emulatorのResourceに書かれてるURLから http://localhost:8010
の部分をngrokのURLに変更する.
つまり, http://(hogehoge).ngrok.io/poised-ceiling-202712/us-central1/onReceiveSlackOAuth
となる.
このURLに外部からアクセスすると, functions emulatorが関数を実行する.
このURLをSlack Appで設定してみる.
OAuth & Permissions の Redirect URL にURLを設定する.
そして, https://slack.com/oauth/authorize?client_id=(クライアントID)&scope=(設定したいスコープ)
にブラウザからアクセスする.
ngrokの画面にリクエストログが追加されたら, ローカル環境にアクセスできている.
関数のログを見るには以下のコマンドを実行する.
$ functions-emulator logs read
とりあえず, リクエストの中身をlogに表示するだけのコードなので, リクエストの中身がログに表示されていればOK.
コードの記述
実際にコードをOAuth認証のためのコードを書いていく.
GCP上で環境変数の値を取得するための @google-cloud/rcloadenv とHTTPリクエストを投げるための request モジュールを使う.
npm install -D @types/(モジュール名)
でTypeScriptに必要な型定義ファイルを持ってこれる.
(残念ながらrcloadenvの型定義ファイルは見つからなかった)
$npm install request --save $npm install @types/request $npm install @google-cloud/rcloadenv --save
コードを書く, 基本的には前回の記事と変わらない.
index.ts
import * as rcloadenv from '@google-cloud/rcloadenv' import * as request from 'request' export const onReceiveSlackOAuth = (req: any, res: any) => { if(req.method !== 'GET' || req.query.code === undefined) { const error_message = "Illegal Request, wrong method: " + req.method; console.error(error_message); res.status(405).send(error_message); return; } if (req.query.code === undefined) { const error_message = "Illegal Request, no required query: " + req.query; console.error(new Error(error_message)); res.status(400).send(error_message); return; } const code = req.query.code; console.log("Correct Request: code=", code); rcloadenv.getAndApply('test-slackbot', {}).then((env:any) => { if ( env.CLIENT_ID !== undefined && env.CLIENT_SECRET != undefined ) { const CLIENT_ID = env.CLIENT_ID; const CLIENT_SECRET = env.CLIENT_SECRET; const url = 'https://slack.com/api/oauth.access'; request.get({ uri: url, headers: { 'Content-type': 'application/json' }, qs: { client_id: CLIENT_ID, client_secret: CLIENT_SECRET, code: code }, json: true }, (err, req, data) => { if (data) { if (data.ok) { const access_token = data.access_token; const team_name = data.team_name; const team_id = data.team_id; const message = "OAuth Successful" res.status(200).send(message); } else { const error_message = 'OAuth Failure'; console.error(error_message, data.error); res.status(500).send(error_message); } } if (err) { const error_message = 'OAuth Failure'; console.error(error_message, err); res.status(500).send(error_message); } }) } else { const error_message = "Failure: TOKEN_ID and TOKEN_SECRET is not set"; console.error(new Error(error_message)); res.status(500).send(error_message); } }).catch(console.error); return; };
ローカル環境でGCP上と変わらずrcloadenvなどのAPIを使うために, CREDENTIALSを設定する.
gloud auth
でCREDENTIALSファイルを生成する.
GOOGLE_APPLICATION_CREDENTIALSに生成されたjsonファイルのパスを指定する.
$ gcloud auth application-default login $ export GOOGLE_APPLICATION_CREDENTIALS="(上のコマンドの結果に出てきたjsonファイルのパス)" $ functions-emulator restart
ビルドしてデプロイする.
$ npm run build $ functions-emulator deploy onReceiveSlackOAuth --trigger-http
再度ブラウザから https://slack.com/oauth/authorize?client_id=(クライアントID)&scope=(設定したいスコープ)
にアクセスする.
GCPへデプロイ
せっかくなので, ローカルのターミナルからGCPにデプロイしてみる.
$ gcloud beta functions deploy onReceiveSlackOAuth --trigger-http
GCPのコンソールからCloud Functionsを選択し, 関数が追加されていたらデプロイされている.
関数の設定画面からURLを取得し, Slack Appの設定でURLをセットしたら終わり.
参考
Cloud Functions Execution Environment | Cloud Functions Documentation | Google Cloud