けいまさんですけど

死ぬ気でやってりゃ、そりゃ死ぬわ

はじめに

DroidKaigiお疲れ様でした(雑感ブログはまた今度書くかもしれないし、書かないかも知れません)

DroidKaigiの準備が忙しくなってきた年明けすぐ頃にぼんやりと考えていたことを、DroidKaigiが無事に終わったのを契機に本格的に作り始め、なんとか動くようになったのでご紹介したいと思います。

GAE/Goと僕

僕は趣味で作るWebアプリはだいたいGAE/Goで制作をしています。
GAE/Goを選ぶ理由としては、個人用途ではほぼ無課金で遊べること、そしてGo言語を動かせるプラットフォームとしてはかなりお手軽な部類であることが挙げられます。
(go1.5対応はスルーされたけど)go1.6対応はされるということで、まだアクティブにプラットフォーム開発が続けられていることも好印象です。

Bitbucketとwercker

werckerというCIサービスがあります。
パブリックリポジトリが無料な(だいたいGitHub前提なのですが)CIサービスは比較的たくさん存在しますが、werckerはなんといっても無料でもプライベートリポジトリからのCI実行が可能であり、庶民の強い味方だなぁという感想です。

サーバーサイドのコードは、個人プロジェクトであってもソースコードを公にしづらいことが多いように思います。
(もちろんサービスの内容やソースコードの構成次第でどうとでもなる場合もありますが)
こちらも無料でプライベートリポジトリを利用できるBitbucketを使うことが多いのかな、と思います。

そういう意味では、「Bitbucketのプライベートリポジトリ」+「wercker」の組み合わせというのはなかなかにしてオトクだと言えます。

デプロイ作業とGAE/Go

GAE/Goに限らず、デプロイ作業は自動化したいものです。
GAE/Goのデプロイ作業を自動化の視点をまず置いといて見てみると割とシンプルで、きちんとSDKがセットアップできていればgoapp deployで楽々とデプロイ可能です。
しかし、すでに諸々の諸手続を完了しているから手軽に出来るのだということです。goapp deployするためにはソースコードの依存関係(ライブラリの入手)をクリアしている必要がある為、goapp get[^1]を前もって行う必要はありますし、そもそもそのプロジェクトが正しくビルドできるのか確認した方がよいのでgoapp testgoapp buildをdeploy前にやっておきたいところです。
これをCIサーバーを用いて自動化するということは、セットアップ作業を列挙し整理し毎回実行できなければなりません。

このプロセスを手軽にできるようにすれば、もっと開発に集中できる気がします。

[^1]: GAE/GoのSDKにはgoappというバイナリが含まれており、これはほぼgolangのgoコマンドと同様であるが、GAE用のPythonスクリプトを呼び出せるように 引数追加がされている。例えばgoapp deploygoapp serveがそれである。

werckerとGAE/Go

そんなわけで早速werckerを使いGAE/Goのデプロイ作業を自動化したいわけですが、問題があります。

上記リンク先はwerckerの公式ブログのため信頼度は高そうですが、既にdeprecatedのようです。また、pjvds/step-go-appengine-deployについてはGoogleアカウントのID/Passを指定する必要があり、いくらwerckerが隠蔽してくれるといえど、第三者にGoogleアカウント情報を与えるのは抵抗があります。

ところで、werckerのstepはユーザーが自由に作成することが出来ます。ということは、同じように不満を持った人が何か作っている可能性はあります。
そこで、werckerのsteps registryを調べてみると、やはりいくつかヒットしました。

いくつかのプラグインにおいては、GAE/GoのSDKをセットアップすると生成される.appcfg_oauth2_tokensからrefresh tokenを取り出して利用する方法を採用しています。
(この方法はかつて僕がTravis CIからGAE/Goへデプロイをする際に利用した方法のため、馴染みがあります)

しかし、個人的にそれ以外の点がどうも気に入りません。
というのも、多くのプラグインは、フルセットのSDKを取得しているにもかかわらず、デプロイしか担ってくれないのです。
GAE/GoのSDKに含まれるgoappは、それ自体がgoのバイナリも含むため、go testgo getもそれぞれgoapp testgoapp getとして使用可能です。
一方、werckerの多くのプラグインでは、goapp deploy相当のコマンドを実行することしかできないようでした。
ビルド作業が終わると、werckerで取得したSDKは原則として破棄されます。とても勿体ないことです。

MiCHiLUさんの作成されたstepおよびboxは、今回の開発において大いに参考になりました。
デプロイだけでなく依存関係の解決なども行ってくれるようです。
そして、自分がやりたいことの半分は実現されていました。
もう半分のやりたいことと言うのは、nodeを使ってフロントエンド側の依存関係やビルドを行いたいというものです。
僕が制作するGAEアプリでは、サーバーサイドはjsonだけを返し、フロントエンドはAngularJSを使用することが多いため、bowerやgulpを使いたいのですが、そうなるとbox内にnodeが入っていることが強く求められます。

つまるところ、現状のwerckerにおいては、僕がやりたい開発スタイルを満たすようなツールキットは提供されていないということが分かりました。

僕が望む条件

  • tokenを用いた認証を行いデプロイを行う
    • tokenはwerckerのWebUIから設定することで(明示的にechoなどしないかぎりは)非公開の環境変数として利用できます
  • deploy以外のことも行えるようにする
    • つまりはSDKをキャッシュしておく必要があるということでもあります
  • box(Dockerコンテナ)に依存しない
    • このboxじゃないと動かないということは避けたい(ただし、Debian系とRHEL系の両方対応とかは厳しそうな気はしているので、微妙に妥協していきたい)

この条件を設定し、いろいろ思案していたのですが、DroidKaigiの支度や仕事で案件に関わるようになって作業が停滞しておりました。ただ最近少し時間が出来たので作業開始し、なんとか良い感じになったので公開しました!

step:keima/go-appengine-utilを作りました

使用例がまだ用意できてないのが申し訳ないです。。そのうち用意したいです。

出来ること

まぁ条件のセクションに書いてあることと被りますが。。

  • 最新SDKの取得
    • SDKはたまに更新されるのですが、更新されるたびにstepを更新するのは面倒なので、自動で最新のSDKを取得します
  • refresh tokenを用いた認証を行いデプロイを行う
  • goappの機能の呼び出し
    • deploy : GAE本番環境へのデプロイ
    • get : ライブラリ・依存関係の入手
    • test : go test
    • build : ビルド(deployコマンドがbuildも兼ねているので、無事にコンパイルできるかを試すのに使うのが良さそう)
  • 依存関係の自動解決
    • 諸般の事情で7zを用いてSDKのzipファイルを展開するのですが、おおよそ標準で7zを持っているコンテナはない気がするので、取得するようにしています。もし7zがあれば取得処理はスキップされるので、その分だけタスクが早く終わるようになります。

実装にあたって困ったこと

シェルスクリプトの知識

werckerのstepを書くに当たってはシェルスクリプトをガッツリ書く必要に迫られました(しかしrun.shは必須であるものの、実行された後の言語は何でも良いので、書きたければnodeで書いても良いらしい)。
特にtestコマンドあたりは、今回とても良く使ったので、良い経験になりました。ただ、理解できるまでにかなりの回数のトライアンドエラーを繰り返しました。

「werckerのcache領域はタイムスタンプを保持しない」問題

werckerには、ライブラリやSDKを保管しておく用途でcache領域が用意されています。
この領域はとても便利なのですが、アンドキュメントな仕様として「cache領域から復元されたファイルはタイムスタンプを喪っている」という問題があります。
単にファイルの有無を見るだけであれば問題になりませんが、golangでは*.a(パッケージファイルと言うらしい)のタイムスタンプがその基となるソースコードよりも 古い とソースコードからパッケージファイルを生成しようとする( http://stackoverflow.com/a/26524210 )為、GAEのSDKの為に調整されたタイムスタンプが影響を受けてしまい、ビルド時にtest関連のエラーが発生し、タスクが止まってしまいます。

タイムスタンプを無理矢理書き換えるなど対応をしてみたのですが、どうもうまくいかなかったため、結局ビルドの度に展開済みSDKを削除した後、SDKのzipファイルを展開するようにしました。

ところで、先ほど"諸般の事情で7zを用いて"と説明しましたが、zipファイルの展開でunzipを使わなかったのは、あるバージョンのunzipではタイムスタンプを保持せず、SDKが上記理由で壊れてしまう為です( https://groups.google.com/d/msg/google-appengine-go/rWc4TkhSECk/Dh7k4k3bDxUJ )。

今後の展望

書き直したい・・・。
というのも、step側で毎回7zを取得するのは無駄だなぁ・・・と。
なので、ある程度boxも使用する形にするのが良いのではないか?と思っています。

しかし、現状でだいたい動いており満足しているので、書き直すモチベーションは低いです。。

まとめ

あまりお困りの方がいるようにも思えないのですが、宜しければご利用頂ければと思います!