GCP(Google Cloud Platform)のCloud FunctionsのSendGridのチュートリアルをしました。
ちなみにCloud Functionsのチュートリアルのリンク↓
チュートリアル | Cloud Functions | Google Cloud
チュートリアルになかなか手こずったので備忘録です。
以下の章はチュートリアルの章のタイトルと同じです。
始める前に
書いてあるとおりにします。
データの流れを可視化
読みます。なるほど。
アプリケーションの準備
書いてあるとおりに進めます。 SendGridのAPIキーは一度しか表示されないので、どこかにコピペしておきます。
https://[YOUR_USERNAME]:[YOUR_PASSWORD]@[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/sendgridWebhook
[YOUR_USERNAME]と[YOUR_PASSWORD]は任意のユーザ名とパスワードです。適当にわかりやすいのにしてよいです。
[YOUR_PROJECT_ID]はCloud プロジェクトIDで、GCPのホームの「ダッシュボード」メニューの「プロジェクト情報」に記載されている「プロジェクトID」です。
[YOUR_REGION]は関数がデプロイされる領域らしいです。どう選べば良いのかわかりませんが、GCEのVMインスタンスと同じリージョンにしました。リージョンの確認は、GCEのページに行ってVMインスタンスの詳細に「ゾーン」の項目があるのですが、それを見ればわかります。ゾーンが「us-central1-c」なら、リージョンは「us-central1」です。
注意としては、「SELECT ACTIONS」の項目にチェックを入れないと、SendGridがメールを送信するときにWebhookイベントが発生しません。これチュートリアルに記述がまんま抜けているので注意(2019/01/11現在)。選択するACTIONはおそらく「Processed」と「Delivered」だけで良いが、チュートリアルなので「ALL」で良い。
こっちの日本語ドキュメントのほうが正確に書いてあります。
Event Webhookでイベントを受信する - ドキュメント | SendGrid
そのほかは書いてあるとおりに進めます。
コードを理解する
コードの説明がざっくり書いてあります。
関数のデプロイ
ここも書いてあるとおりに進めます。
$ gcloud beta functions deploy sendgridEmail --trigger-http $ gcloud beta functions deploy sendgridWebhook --trigger-http $ gcloud beta functions deploy sendgridLoad --trigger-bucket [YOUR_EVENT_BUCKET_NAME]
[YOUR_STAGING_BUCKET_NAME]とか書いてありますが、別に使いません。何かの間違いでしょう。
メールを送信
$ curl -X POST "https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/sendgridEmail?sg_key=[YOUR_SENDGRID_KEY]" --data '{"to":"[YOUR_SENDER_ADDR]","from":"[YOUR_RECIPIENT_ADDR]","subject":"Hello from Sendgrid!","body":"Hello World!"}' --header "Content-Type: application/json"
[YOUR_SENDER_ADDR]は送信者のアドレスです。よって"from"の方です(チュートリアルが間違っている)。こっちにSendGridアカウントのメールアドレスを書きます。
[YOUR_RECIPIENT_ADDR]は受信者のアドレスです。メールの受信を確認できるアドレスを入れます。
たとえば次のような感じになります。
$ curl -X POST "https://us-central1.my_project/sendgridEmail?sg_key=hogehogehogehogehoge" --data '{"to":"to@example.com","from":"from@example.com","subject":"Hello from Sendgrid!","body":"Hello World!"}' --header "Content-Type: application/json"
これを実行すると、以下のエラーが発生しました。
curl: (51) SSL: no alternative certificate subject name matches target host name 'us-central1.my_project.cloudfunctions.net'
このエラーメッセージでググると、Stack Overflowがヒットします。
api - Fix CURL (51) SSL error: no alternative certificate subject name matches - Stack Overflow
-kオプションでSSL証明書の検証をオフにできるそうです。とりあえずそうしてみます。
$ curl -kX POST "https://us-central1.my_project/sendgridEmail?sg_key=hogehogehogehogehoge" --data '{"to":"to@example.com","from":"from@example.com","subject":"Hello from Sendgrid!","body":"Hello World!"}' --header "Content-Type: application/json"
これを実行すると、今度は以下の結果が返ってきました。
<!DOCTYPE html> <html lang=en> <meta charset=utf-8> <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width"> <title>Error 404 (Page not found)!!1</title> ...
Error 404!! ページが存在しないと言われました。なんで?
結果から言うと、URLのタイポでした。タイポには気をつけましょう。
Cloud FunctionsのHTTPトリガーのURLの確認は、Cloud Functionsのページに行けばデプロイした関数がリストされているので該当の関数をクリックし(今回はsendgridEmail)、「トリガー」メニューの画面で確認することができます。
さて、タイポを修正して再度実行すると、今度はSendGridからのメールを受信することができました。やったね。
Cloud Functionsの実行ログを見てみます。
$ gcloud beta functions logs read --limit 100 .... D sendgridEmail vra---gkc 2019-01-10 18:09:22.766 Function execution started I sendgridEmail vra---gkc 2019-01-10 18:09:22.773 Sending email to: to@example.com I sendgridEmail vra---gkc 2019-01-10 18:09:23.120 Email sent to: to@example.com D sendgridEmail vra---gkc 2019-01-10 18:09:23.122 Function execution took 356 ms, finished with status code: 202
sendgridEmailが実行されたあと、SendGridがメールを送信してくれます。そのときにSendGrid側のWebhookイベントが同時に発生し、 https://[YOUR_USERNAME]:[YOUR_PASSWORD]@[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/sendgridWebhook宛にPOSTを送信します。POSTを受信するとGCP側のCloud FunctionsのsendgridWebhookが実行されます。するとGoogle Cloud StrageにJSONファイルが保存されます。
sendgridWebhookの実行ログは以下のようになるはずです。
D sendgridWebhook n64---gwu 2019-01-11 02:05:42.699 Function execution started I sendgridWebhook n64---gwu 2019-01-11 02:05:42.707 Saving events to 1547172342706000-6630d5c0-d6f8-40d6-a630-9a844e4239b3.json in bucket gcf_sendgrid_tutorial_bucket I sendgridWebhook n64---gwu 2019-01-11 02:05:42.999 JSON written to 1547172342706000-6630d5c0-d6f8-40d6-a630-9a844e4239b3.json D sendgridWebhook n64---gwu 2019-01-11 02:05:43.001 Function execution took 302 ms, finished with status code: 200
もしログがいつまで経っても現れない場合、SendGrid側のWebhookが発生していない可能性があります。「アプリケーションの準備」の章を見直してみてください。
sendgridLoadのエラー
ここまでやったら、sendgridEmailからsendgridWebhookまでは実行が完了するのですが、sendgridLoadで以下のエラーが出ました。
D sendgridLoad 353717043065300 2019-01-11 11:29:34.810 Function execution started I sendgridLoad 353717043065300 2019-01-11 11:29:35.217 Starting job for 1547206173966000-13e6adf5-271c-42c5-bac3-116d89ef3ea6.json I sendgridLoad 353717043065300 2019-01-11 11:29:35.218 Job failed for 1547206173966000-13e6adf5-271c-42c5-bac3-116d89ef3ea6.json E sendgridLoad 353717043065300 2019-01-11 11:29:35.241 TypeError: table.import is not a function at Promise.resolve.then.then (/user_code/index.js:340:26) at process._tickDomainCallback (internal/process/next_tick.js:135:7) D sendgridLoad 353717043065300 2019-01-11 11:29:35.251 Function execution took 442 ms, finished with status: 'error'
TypeError: table.import is not a function
tableにはimportという関数がないと言われました。
あまり良くわかっていないのですが、 @google-cloud/bigquery 2.0.5 » Class: Table | Node.js | Google Cloud を見ると、importという関数はないようです。loadの間違い?
なんか色々こねこねして最終的にできたsendgridLoadのコードが以下。自分でも何をやっているのかよくわかっていない。
// [START functions_sendgrid_load] /** * Cloud Function triggered by Cloud Storage when a file is uploaded. * * @param {object} event The Cloud Functions event. * @param {object} event.data A Cloud Storage file object. * @param {string} event.data.bucket Name of the Cloud Storage bucket. * @param {string} event.data.name Name of the file. * @param {string} [event.data.timeDeleted] Time the file was deleted if this is a deletion event. * @see https://cloud.google.com/storage/docs/json_api/v1/objects#resource */ exports.sendgridLoad = event => { const file = event.data; if (file.resourceState === 'not_exists') { // This was a deletion event, we don't want to process this return; } return Promise.resolve() .then(() => { if (!file.bucket) { throw new Error( 'Bucket not provided. Make sure you have a "bucket" property in your request' ); } else if (!file.name) { throw new Error( 'Filename not provided. Make sure you have a "name" property in your request' ); } return getTable(); }) .then(([table]) => { const fileObj = storage.bucket(file.bucket).file(file.name); console.log(`Starting job for ${file.name}`); const metadata = { autodetect: true, sourceFormat: 'NEWLINE_DELIMITED_JSON', }; // tableのプロパティを全て出力させる for(var n in table){ console.log("table: " + n); } // Error: table.import is not function ... why? console.log("table has import: " + ("import" in table)); //return table.import(fileObj, metadata); return table.load(fileObj, metadata); }) //.then(([job]) => job.promise()) .then(([job]) => { for(var n in job){ // jobのプロパティを全て出力させる console.log("job: " + n); } }) .then(() => console.log(`Job complete for ${file.name}`)) .catch(err => { console.log(`Job failed for ${file.name}`); return Promise.reject(err); }); }; // [END functions_sendgrid_load]
以下はその実行結果
D sendgridLoad 357398789515366 2019-01-15 04:02:31.061 Function execution started I sendgridLoad 357398789515366 2019-01-15 04:02:32.245 Starting job for 1547524950091000-396529d3-8fad-4bcb-b527-f1c24a3bb25b.json I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: domain I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: _events I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: _eventsCount I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: _maxListeners I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: metadata I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: baseUrl I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: parent I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: id I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: createMethod I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: methods I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: interceptors I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: Promise I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: requestModule I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: bigQuery I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: dataset I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: createReadStream I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: getRows_ I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: setMaxListeners I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: getMaxListeners I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: emit I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: addListener I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: on I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: prependListener I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: once I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: prependOnceListener I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: removeListener I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: removeAllListeners I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: listeners I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: listenerCount I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table: eventNames I sendgridLoad 357398789515366 2019-01-15 04:02:32.247 table has import: false I sendgridLoad 357398789515366 2019-01-15 04:02:34.446 job: kind I sendgridLoad 357398789515366 2019-01-15 04:02:34.447 job: etag I sendgridLoad 357398789515366 2019-01-15 04:02:34.447 job: id I sendgridLoad 357398789515366 2019-01-15 04:02:34.447 job: selfLink I sendgridLoad 357398789515366 2019-01-15 04:02:34.447 job: jobReference I sendgridLoad 357398789515366 2019-01-15 04:02:34.447 job: configuration I sendgridLoad 357398789515366 2019-01-15 04:02:34.447 job: status I sendgridLoad 357398789515366 2019-01-15 04:02:34.447 job: statistics I sendgridLoad 357398789515366 2019-01-15 04:02:34.447 job: user_email I sendgridLoad 357398789515366 2019-01-15 04:02:34.447 Job complete for 1547524950091000-396529d3-8fad-4bcb-b527-f1c24a3bb25b.json D sendgridLoad 357398789515366 2019-01-15 04:02:34.455 Function execution took 3395 ms, finished with status: 'ok'
よくわかってないですけど、実行が正常終了したっぽいです。jobの正体がイマイチ謎ですが、
Jobs | BigQuery | Google Cloud
ということでいいのかな?
sendgridLoadが動作するとBigQueryにデータが格納されていきます。
ということで、とりあえずチュートリアルは終了です。