ベスパリブ

プログラミングを主とした日記・備忘録です。ベスパ持ってないです。

Access to bucket "staging.PROJECT_ID.appspot.com" denied. You must grant Storage Object Viewer permission to ... エラーの解消

Google App EngineHello Worldチュートリアルを進めていたらデプロイ中にエラーが発生しました。

(python_docs_samples) C:\Users\XXXX\workspace\python-docs-samples\appengine\standard_python37\hello_world>gcloud app deploy
Services to deploy:

descriptor:      [C:\Users\XXXX\workspace\python-docs-samples\appengine\standard_python37\hello_world\app.yaml]
source:          [C:\Users\XXXX\workspace\python-docs-samples\appengine\standard_python37\hello_world]
target project:  [PROJECT_ID]
target service:  [default]
target version:  [20190126t190818]
target url:      [https:/PROJECT_ID.appspot.com]


Do you want to continue (Y/n)?  Y

Beginning deployment of service [default]...
#============================================================#
#= Uploading 5 files to Google Cloud Storage                =#
#============================================================#
File upload done.
Updating service [default]...failed.
ERROR: (gcloud.app.deploy) Error Response: [9] Cloud build AA-BB-CC-DD-EEEEE status: FAILURE.
Build error details: Access to bucket "staging.PROJECT_ID.appspot.com" denied. You must grant Storage Object Viewer permission to 9XXXXXXXX8@cloudbuild.gserviceaccount.com.
.
Check the build log for errors: https://console.cloud.google.com/gcr/builds/AA-BB-CC-DD-EEEEE?project=9XXXXXXXX8

注目すべきエラーメッセージは Build error details: Access to bucket "staging.PROJECT_ID.appspot.com" denied. You must grant Storage Object Viewer permission to 9XXXXXXXX8@cloudbuild.gserviceaccount.com. . の部分で、「バケットへのアクセスが失敗」と言われています。

さらに「9XXXXXXXX8@cloudbuild.gserviceaccount.com」に「Storage Object Viewer権限を与えてくれ」と言われています。

権限のエラーはIAMをいじれば解消できるはず。IAMの設定をろくにしたことがないので以下のリンクを読みます(バケットに対するIAMについても書いてあります)。

Using (Cloud IAM) Permissions  |  Cloud Storage  |  Google Cloud

なんとなくわかったところで、GCPから「IAMと管理」の画面を開き、9XXXXXXXX8@cloudbuild.gserviceaccount.comの編集アイコンをクリックします。

f:id:takeg:20190128160804p:plain
9XXXXXXXX8@cloudbuild.gserviceaccount.comの編集アイコンをクリック

「別の役割を追加」で「ストレージオブジェクトの閲覧者」を追加します。

f:id:takeg:20190128160910p:plain
「ストレージオブジェクトの閲覧者」を追加

そして再度デプロイするといけるはず……たぶん。

(python_docs_samples) C:\Users\XXXX\workspace\python-docs-samples\appengine\standard_python37\hello_world>gcloud app deploy
Services to deploy:

descriptor:      [C:\Users\XXXX\workspace\python-docs-samples\appengine\standard_python37\hello_world\app.yaml]
source:          [C:\Users\XXXX\workspace\python-docs-samples\appengine\standard_python37\hello_world]
target project:  [PROJECT_ID]
target service:  [default]
target version:  [20190126t193649]
target url:      [https://PROJECT_ID.appspot.com]


Do you want to continue (Y/n)?  Y

Beginning deployment of service [default]...
#============================================================#
#= Uploading 0 files to Google Cloud Storage                =#
#============================================================#
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://PROJECT_ID.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse

デプロイできました!

gcloud app browseを実行するとWEBアプリケーションにアクセスできます。

chcpコマンドは何の略なのか

"CHange CodePage" の略らしいです。

ターミナルでchcp /?と打つと、

>chcp /?
Displays or sets the active code page number.

CHCP [nnn]

  nnn   Specifies a code page number.

Type CHCP without a parameter to display the active code page number.

Specifies a code page numberと書いてあるので、たしかにcode pageの略で間違いなさそうです。ところでcode pageってなんでしょう。

Wikipediaによれば

コードページとは、特定の符号化文字集合を指定するための数字、またはその数字で指定された符号化文字集合、あるいはそのような方法で符号化文字集合を指定するためのシステムのことである。cpと表示されることもある。それぞれの符号化文字集合は「コードページ○○(○○は2桁から5桁の数字)」という形で管理される。

「符号化文字集合」とあるので、文字コード(charset)のイメージでよいのでしょうか。

そして、

コードページという用語は、システムベンダ各社が管理している符号化文字集合を指す時にしか用いられず、ISO等の公的な規格の文字集合を「コードページ○○」などということはない。IBMおよび、マイクロソフトは各自、コードページを定めて管理している。マイクロソフトのコードページ群はMS-DOSWindowsなどで利用されている。IBMのコードページはSystem iやDB2等の文字データ表現体系(CDRA: "Character Data Representation Architecture")をサポートするIBMシステムで利用されている。

とあるので、文字コードをコードページと言い換えてベンダ各社が勝手に定義している…という理解でいいのかな。

最後によく使うchcpコマンド。

# 現在のcode pageを確認する
> chcp
Active code page: 932
# UTF-8に変更する
> chcp 65001
# SJISに変更する
> chcp 932

2019 Japan IT Week 関西 1/23 レポート

インテックス大阪で企業向けイベント2019 Japan IT Week 関西 がありました。それに一般参加し申した。出展はしていません。

朝10:00に入場。

3つのセミナーに参加しました。7000円するセミナーは早割で6000円であった。どの内容も面白く、参考になる部分もあり、大変良かったのだが、6000円は個人的な感覚で高杉晋作なのですけれど、企業向けでは相場価格なのだろうか。まあ会社のお金なんですけど。

名古屋大学の方のお話を聞いたのですけど、自動運転の技術者を増やすためにもAutowareっていうソフトウェアをOSS化していたらしいです。初めて知りました。自動運転OSSデファクトスタンダードみたいな立ち位置になってきているらしいです。

セミナーが終わったら自由にブースを回りました。メインはIoT展で、今後の製造業をIoTでビッグデータしてディープラーニングするための情報収集であった。

スーパーミーティングメモという音声認識議事録はちょうど社内で「こういうの欲し~誰か作って~」みたいに言ってたものなので非常にタイムリーでした。

個人的に驚いたのがSkyの情報資産管理システム(いわゆる社内のPC管理・監視とかのソフト)で、どのPCがなんのファイルをいつ作成したとか、ファイル名を変更してもハッシュ値を保持してるから追跡できたりとか、誰がどのWEBページを見たかとかが履歴でわかりやすく表示されるとこで、情シスの話は噂レベルでしか聞いたことなかったですけど、こんなん私が朝はてブ見てることとか(テクノロジーカテゴリで情報収集のためですよ、先輩)、誤クリックで増田の記事読んでしまったこととかバレバレじゃん!!って思いました。他にもUSBメモリを読み取りのみ・使用不可の制御を端末ごとはもちろんネットワーク、グループごとに出来たり、色々痒いところに手が届いてるな〜〜と言った感じでした。多分大手インフラ企業とか導入してそう…してるに違いないと思うなどしました。

印象に残っているのが日本マイクロソフトのデモプレゼンで、Surfaceのプレゼンのはずなのだけれど、最初の導入が日本の長時間労働の話から始まり、働き方改革の話に続いた。日本マイクロソフト社内の課題を見える化などし、どう働き方改革を進めたかみたいな話が中心で、「行きたくなるオフィス」「使いたくなるデバイス」という話が出て、その「使いたくなるデバイス」の文脈で最後の最後にSurfaceをちょろっと紹介してデモは終了した。 私だったら「今からSurfaceの新機能を紹介します。」とか言ってSurfaceのスペックや機能の話でプレゼンをはじめてしまうと思ったのだが、プレゼン力の違いを見せられた。おそらく百聞は一見に如かず実機を触ってくださいとのことなのでしょう。あとブースのバランスチェアがめっちゃよかったので会社で使いたいなど思った。

個人的に印象が悪いと思ったのが、恐らく営業だと思われる方が説明して、「ほら、あとはお前の番だぞ」と開発者っぽい人に話を振り「え、俺っすかw」みたいな感じで詳しい説明に入るのはやめたほうが良いと思った。自分も出展側に回ることもあり、そういう風に営業の方から話を振られることがあったのだが、内輪ノリとまではいかないまでもそういうのを社外の人に見せるのは諸刃の剣だと感じた。気をつけたい。

あと組み込みシステム・開発技術ブースにGitHubジャパンが出展していた。プライベートリポジトリの宣伝だろうか。でもちょっと謎感が否めなかったが、たくさんステッカーをもらって帰った。

さくらインターネットさんがLoRa製品を出してた。

その他色んな出展企業をまわった。

ところでこういう展示会って本当にその場で商談成立したり企業側にメリットあるんでしょうか。せいぜい企業の存在感をアピールするくらいが関の山で、あとは一般参加者が楽しんで観覧したり、コンパニオンを雇ってる企業を見てその企業のカラーを知って楽しむと思っているのは私だけ……ですね。これ以上はやめましょう。

帰りにタイ料理を食らいました。バナナマンの日村氏がおいしいとかラジオで言ってたような気がするプーパッポンカリーを食した。味は美味しかったけど食感がひどすぎる。

パクチーの酎ハイ、パクチュウはパクチー好きにはたまらない、そうでもない人には地獄のような飲み物だった。見えてる地雷を踏んどいて文句言ってごめんなさい。

余談。

JR大阪駅は乗降人数が多いくせに2列分のエスカレーターかエレベーターしかないし、ホームには時刻表も、どこの駅に行くのかの看板や情報がマジで皆無だし、どうなってるんだ?

WindowsのSSHの設定、備忘録

SSH鍵の保管場所は?

C:/Users/XXXXX/.ssh/

SSHコマンド

SSH-KEYGEN (1)

RSA鍵の生成

# RSA1024ビット
> ssh-keygen -t rsa -b 1024
# RSA2048ビット
> ssh-keygen -t rsa -b 2048
# RSA4096ビット
> ssh-keygen -t rsa -b 4096

ED25519鍵の生成

> ssh-keygen -t ed25519

GitHubでEd25519鍵をつかう - 尋常でないもふもふ

コメント付きの鍵生成

> ssh-keygen -t ed25519 -C "コメント。メールアドレスを入れることが多い"

バージョン確認

> ssh -V

SSH鍵をどう管理する?

サーバが多くSSH鍵の管理が大変です。どんな工夫をしていますか? - Qiita

SSH鍵は使いまわすこともできるし、サービスごとにSSH鍵を分けてもよい。

f:id:takeg:20190122174843p:plain
.sshフォルダ

たとえば.ssh/configファイルは次のようにして、サービスごとのSSH鍵はそのフォルダのconfigに従うようにする。

Host *
  IgnoreUnknown UseKeychain
  UseKeychain yes

Include */config

IgnoreUnknown UseKeychainは、Bitbucketに接続しようとしたらBad configuration option: usekeychainというエラーが出たのでそのエラーを解消をするために追加している。詳しくは以下のStack Overflowを参照。

macos - .ssh/config: "Bad configuration option: UseKeychain" on Mac OS Sierra 10.12.6 - Stack Overflow

そして、.ssh/githubフォルダを以下のような構成にする。

f:id:takeg:20190122175848p:plain
.ssh/githubフォルダ

たとえば.ssh/github/configファイルは次のようにして、専用のSSH鍵を使うように指示する。

Host github github.com
  HostName github.com
  User tanaka_taro
  IdentityFile ~/.ssh/github/id_ed25519

同様に、たとえば.ssh/bitbucket/configファイルは次のようにして、専用のSSH鍵を使うように指示する。

Host bitbucket bitbucket.org
  HostName bitbucket.org
  User tanaka_taro
  IdentityFile ~/.ssh/bitbucket/id_ed25519
  Port 22

設定できる項目は以下の通り。

.ssh/configファイルでSSH接続を管理する - Qiita

設定項目 説明
Host ホスト名
HostName ホストのアドレスかIPアドレス
User ログインユーザ名
IdentiyFile ログインするための秘密鍵パス
Port ポート番号(デフォルト22)
TCPKeepAlive 接続状態を継続したい場合:yes / 継続しない場合:no
IdentitiesOnly IdentityFileが必要な場合:yes / 必要ない場合:no
ServerAliveInterval 一定期間サーバからデータが送られてこないとき、タイムアウトする秒数

SendGridのメールで改行コードが反映されない問題

SendGridでメールを送信するとき、本文に改行コードを入れても、メール受信時には本文が改行がされなくて困りました。

ダメ元でググったらまんまのトラブルシューティングが公式から出てました。

sendgrid.kke.co.jp

PlainContentをActiveにすると症状は直りました。やったね。

Cloud Functionsのダイレクトトリガーでbase64エンコードしたデータを送信して実行する

GCPのCloud Functionsには動作テストをするためにダイレクトトリガーというものがあります。このダイレクトトリガーをする方法にGUIのテストを実行するか、CUIでcallコマンドを実行する方法があります。

詳しくは以下の2つのリンクに書いてあります。

Direct Triggers  |  Cloud Functions Documentation  |  Google Cloud

https://cloud.google.com/sdk/gcloud/reference/beta/functions/call

1個目のリンクにhelloPubSubのサンプルコードがあるのですが、これに送るデータはbase64エンコードする必要があります。

以下はサンプルコードそのままのコピペです。

/**
 * Background Cloud Function to be triggered by Pub/Sub.
 * This function is exported by index.js, and executed when
 * the trigger topic receives a message.
 *
 * @param {object} event The Cloud Functions event.
 * @param {function} callback The callback function.
 */
exports.helloPubSub = (event, callback) => {
  const pubsubMessage = event.data;
  const name = pubsubMessage.data
    ? Buffer.from(pubsubMessage.data, 'base64').toString()
    : 'World';

  console.log(`Hello, ${name}!`);

  callback();
};

上記のhelloPubSubに'PubSub!'という文字列を送信して実行するコマンドは以下になります(サンプルそのまま)。

DATA=$(printf 'PubSub!'|base64) && gcloud functions call helloPubSub --data '{"data":"'$DATA'"}'

上記のような'PubSub!'のような短い文字列はこのままでいけるのですが、base64コマンドは長い文字列をエンコードしようとする場合、なぜか半角スペースが入るようです。

例えば以下のようなデータをbase64エンコードすると、

DATA=$(printf '{"a" : "1547604173", "b" : "-0.91", "c" : "0.40", "d" : "10.45", "e" : "80.58", "f" : "1013.67", "g" : "36.98", "h" : "", "i" : "", "j" : "0.00"}'|base64)
echo -n $DATA

実行結果は以下。

eyJhIiA6ICIxNTQ3NjA0MTczIiwgImIiIDogIi0wLjkxIiwgImMiIDogIjAuNDAiLCAiZCIgOiAi MTAuNDUiLCAiZSIgOiAiODAuNTgiLCAiZiIgOiAiMTAxMy42NyIsICJnIiA6ICIzNi45OCIsICJo IiA6ICIiLCAiaSIgOiAiIiwgImoiIDogIjAuMDAifQ==

このようにbase64コマンドは見栄えを整えて半角スペースを入れてくれます。余計なことを。

この場合はしょうがないのでtrコマンドで半角スペースとついでに改行コードを除去します。

DATA=$(printf '{"a" : "1547604173", "b" : "-0.91", "c" : "0.40", "d" : "10.45", "e" : "80.58", "f" : "1013.67", "g" : "36.98", "h" : "", "i" : "", "j" : "0.00"}'|base64|tr -d " \n")
echo $DATA

実行結果は以下。

eyJhIiA6ICIxNTQ3NjA0MTczIiwgImIiIDogIi0wLjkxIiwgImMiIDogIjAuNDAiLCAiZCIgOiAiMTAuNDUiLCAiZSIgOiAiODAuNTgiLCAiZiIgOiAiMTAxMy42NyIsICJnIiA6ICIzNi45OCIsICJoIiA6ICIiLCAiaSIgOiAiIiwgImoiIDogIjAuMDAifQ==

これでよし。

例えばJSONデータを送信したい場合、以下のようなシェルスクリプトを用意しコンソールで実行してやれば、helloPubSubを実行することができます。

DATA=$(printf '{"a" : "1547604173", "b" : "-0.91", "c" : "0.40", "d" : "10.45", "e" : "80.58", "f" : "1013.67", "g" : "36.98", "h" : "", "i" : "", "j" : "0.00"}'|base64|tr -d " \n")
gcloud functions call helloPubSub --data '{"data":"'$DATA'"}'

Cloud FunctionsのSendGridのチュートリアルの備忘録

GCP(Google Cloud Platform)のCloud FunctionsのSendGridのチュートリアルをしました。

cloud.google.com

ちなみに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」で良い。

f:id:takeg:20190111111250p:plain
SELECT ACTIONSの有効化も忘れずにする

こっちの日本語ドキュメントのほうが正確に書いてあります。

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)、「トリガー」メニューの画面で確認することができます。

f:id:takeg:20190110190658p:plain
sendgridEmail関数のURLの確認

さて、タイポを修正して再度実行すると、今度は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ファイルが保存されます。

f:id:takeg:20190111112837p:plain
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にデータが格納されていきます。

f:id:takeg:20190115144940p:plain

ということで、とりあえずチュートリアルは終了です。