Awk深いトレジャーデータのAPI


awk深いよTreasure!

こんにちは、トレジャーデータでギャグオープンソース事業を担当しています、@kiyototamuraです。ふだんはもっぱらFluentdの仕事をしております。

RTableauなど、様々なツールとサクッと連携できるトレジャーデータですが、なかなか知られていない機能として、awkとのインテグレーションがありますあるわけないでしょ!

てか、若いみなさんはawkが何かすら知らないと思いますので、簡単に説明しておくと、ファイルや標準入力に対して手軽にストリーム処理ができるスクリプト言語です。さらにワンライナーに特化したperlだと思ってください。え、perlも使ったことない?

awkは非常に便利でして、統計的処理にも非常に向いています。今回はawkのGNU版であるgawkから頑張ってトレジャーデータを使ってみたいと思います。

トレジャーデータのREST API

最近はwebコンソールが充実しているため、あまり知られていないかもしれませんが、トレジャーデータにはRESTfulなAPIがあります。なので、HTTP(S)での通信をサポートしている言語からはさくっとクライアントライブラリが作れたりします。というかHTTPはしょせんアプリケーションレイヤーなので、要はTCPでの通信ができればいいわけです。

(たぶん)あまり知られていないgawkの機能

意外と知られていないのですが、gawkはTCPおよびUDPでの通信をサポートしています。そのインターフェースがなかなかイカしており、特殊ファイルというものに|&という演算子を使ってデータを書き込んだり、そっからデータを読みだしたりします。特殊ファイルは文字列で定義され、

"/net-type/protocol/localport/hostname/remoteport"

という感じになります。例えばapi.treasuredata.comのポート80番にTCPでデータを送る場合、

print hello |& "/inet/tcp/0/api.treasuredata.com/80"

という風になります。詳しくは公式ドキュメントをご参照ください。

ということで、gawkでTCP通信ができることがわかりました。後はおわかりですよね(^ω^)

ということでtd.awkしてみた

BEGIN {
  http_service="/inet/tcp/0/api.treasuredata.com/80"
  printf("GET /v3/database/list HTTP/1.1\r\nHost: http://api.treasuredata.com/\r\nAuthorization: TD1 %s\r\n\r\n\n", ARGV[1]) |& http_service
  RS= "\r\n"
  FS=": "

  while ((http_service |& getline) > 0)
    if ($1=="Content-Length") content_length=int($2)
    else if ($1=="transfer-encoding") chunked=1
    else if ($0~/^$/) break
  end

  if (chunked) { content_length=0 }

  while ((http_service |& getline) > 0)
    if (chunked && /^[0-9a-f]+$/)
      content_length += strtonum("0x"$0)
    else if (!/^[0-9a-f]+$/)
      buf=buf $0
    else if (/^$/)
      break
  end
  close(http_service)
  print substr(buf, 0, content_length)
}

このスクリプトは、/v3/database/listエンドポイントを叩き、所望のAPIキーに紐づいているデータベースを取ってきます。いろいろなことをやっていますが、本質的にはHTTPのリクエストを送って、レスポンスをパースするだけです。HTTPでは各ヘッダがCRLFで分けられており、ヘッダ名と値が": "で区切られているので、うまくawkのRS/FS変数を変更して使いキレイにパースしているところがワンポイントです。しかしchunked responseのパースでちょっと嵌った。

APIキーには、CLIの最初の引数が使われる設定となっています。ですので、仮にこのスクリプトがtd.awkというファイル名で保存されていたとし、

gawk -f td.awk `td apikey:show`

と実行すると...

{"databases":[{"name":"testdb","count":5000,"created_at":"2012-10-22 08:17:18 UTC","updated_at":"2012-10-22 08:17:18 UTC","organization":null,"permission":"administrator"},(以下略)

とちゃんとレスポンスが返ってきました!

ここまでやって気づいたこと

そうです、テキストデータのストリーム処理のための言語であるawkなんですが、JSONはパースできないんですね。なのでここでjqにアウトプットをパイプして…と夢が広がりますね。

教訓: awkからトレジャーデータを使うのは、まだまだ敷居が高そうです>_<