GoでJSON文字列を整形し直す
Goで、JSONを返すWebAPIを呼んだ時、普通は結果をプログラムで使うので、対応する構造体を用意して、json.Unmarshal()などで変換します。
しかし、JSONをそのまま整形だけして出力したい場合があります。例えば、WebAPIを呼んで、そのレスポンスを見せるだけとか。curl | jq .みたいな感じ。
JSON自体の文字列がある時は、Unmarshal() -> 構造体 -> Marshal()ということまではする必要はなく、json.Indent()を使います。
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error
prefixは空文字で、indentの方にインデントに使う文字列を渡します。ここでは半角スペース2個指定。
j := `{"key":"value","obj":{"obj_key":"obj_value"}}` var buf bytes.Buffer err := json.Indent(&buf, []byte(j), "", " ") if err != nil { panic(err) } indentJson := buf.String()
これを使うと、以下のように変換できます。
{"key":"value","obj":{"obj_key":"obj_value"}}
↓
{ "key": "value", "obj": { "obj_key": "obj_value" } }
ちなみに改行インデントなしからだけでなく、2スペースからタブへの変換といった整形し直しも可能です。
逆に、インデントを全てなくす場合はjson.Compact()を使います。
func Compact(dst *bytes.Buffer, src []byte) error
使い方はIndent()のprefix, indent引数がないだけです。
全体のgo playgroundはこちら
Apexでlambda slack POSTにGoを使う
以前のエントリで、Lambda Node.js 4.3を使ってSlackにPostしていました。
普段Javascriptは書かないので、Goで書いてApexでデプロイする版を試しました。
ApexはLambdaのコードを管理(?)、デプロイするツールで、PythonやNode.jsなどのLambda対応言語はもちろん、GoをLambdaで実行(Node.js経由)することができます。
Apex自体は参考にしたページの方が詳しいのでそちらを。
Apexを使ってGoでslackにpostするLambda functionを作る
今回のApexプロジェクトディレクトリを作って、初期設定します。
mkdir post-slack cd post-slack apex init
Project nameとdescriptionを聞かれるので、project name: post-slack
とdescriptionは空白のままEnter。
_ ____ _______ __ / \ | _ \| ____\ \/ / / _ \ | |_) | _| \ / / ___ \| __/| |___ / \ /_/ \_\_| |_____/_/\_\ Enter the name of your project. It should be machine-friendly, as this is used to prefix your functions in Lambda. Project name: post-slack Enter an optional description of your project. Project description: [+] creating IAM post-slack_lambda_function role [+] creating IAM post-slack_lambda_logs policy [+] attaching policy to lambda_function role. [+] creating ./project.json [+] creating ./functions Setup complete, deploy those functions! $ apex deploy
ディレクトリ、ファイルが作成され、Lambda function用のRoleが自動作成されます。
slackにPOSTするfunctionの作成
新しいfunction用のディレクトリを作ります。go-general
は、general channelにPOSTしていたののGo版という意味でつけてます。
mkdir functions/go-general
次に、ここにあるGoのファイルを持ってきます。
curl -o functions/go-general/main.go https://gist.githubusercontent.com/reiki4040/afe3bd4317992b4748b0983f51058fae/raw/078b143186e03b45322d8373d5747afa78e801b8/main.go
最後に、環境変数用のfunction.jsonを作成します。(自分の環境のslack URLに置き換えてください)
echo '{ "environment":{ "SLACK_WEBHOOK_URL": "REPLACE YOUR SLACK WEBHOOK URL" } }' > functions/go-general/function.json
ディレクトリ/ファイル構成は、こんな感じになります。
post-slack/ ├── functions/ │ ├── go-general/ │ │ ├── function.json │ │ └── main.go │ └── hello/ │ └── index.js └── project.json
deploy (Lambda functionの作成)
apex deploy
だけだと、サンプルのhello functionもアップされるので、function名を指定します。
apex deploy go-general
以下のように作成されます。
• creating function env= function=go-general • created alias current env= function=go-general version=1 • function created env= function=go-general name=post-slack_go-general version=1
あとは元のエントリにあるように、functionの詳細から[Test]で、同じようにJSONを与えてあげると、slackにPOSTされます。 同様に、中編と後編をやると、API gatewayでWebAPIにできます。
所感
Apex便利ですね。Goだとサードパーティライブラリを使ってもバイナリにまとまるので、Lambdaの実行環境の影響を受けません*1 その代わり、このapexのディレクトリ構成上、Goのvendoringは一工夫要りそうな感じです。
参考など
- ApexでAWS Lambdaファンクションを管理する | Developers.IO
- Apexを使ってGoでlambdaを動かす - Qiita
- Apex – Serverless Infrastructure
- AWS API Gateway+LambdaでSlackにメッセージをPOSTする(前編) - 年中アイス
- AWS API Gateway+LambdaでSlackにメッセージをPOSTする(中編) - 年中アイス
- AWS API Gateway+LambdaでSlackにメッセージをPOSTする(後編) - 年中アイス
*1:cgo関係はダメかもですが、pure goは性質上いけるはず
AWS CodeBuildでGoプロジェクトをビルドする
AWS TokyoリージョンにもCodeBuild等のCodeシリーズ(?)一式揃い、使ってみようということで、まずはCodeBuildを試してみました。
公式にGoのサンプルはあるんですが、他のライブラリ使ってないhello worldなので、CodeBuildの説明だけという感じでした。vendoringが試したいので、今回は昔コンセプト実装したrnbin*1のビルドをCodeBuildに任せる形で試してみました。
つまづきポイントとしては、glideでvendoringしても、cannot find package
になってしまうという点。回避策を書いてくれている方がいましたが、それでも解決せず、詳しく見たらそうかーという感じでした。
全体的なCodeBuild設定の流れ
- ビルドソースを選ぶ(S3, CodeCommit, github) *2
- ビルド方法を指定する(buildspec.ymlを書く or 直接ビルドコードをアップロード)
- ビルド生成物の保存先を指定(S3のバケット+Path)
- ビルド実行!
今回はgithubのpublic repository (rnbin)に、CodeBuild用のbuildspec.ymlを置いています。buildspec.ymlは、まだサンプルなので、featureブランチにpushして、master(default)のブランチにはマージしてない状態です。 このプルリク(色々試したので、コミットがめちゃくちゃ)が対象ブランチ。buildspec.ymlについては、後で説明します。
CodeBuildでBuild Projectを作成する
AWSコンソールから、CodeBuildを開いて、[Get Start] or [Create Project]とするとBuild Projectの作成画面になります。
Project nameは任意のものを。今回はsample-build-rnbin
にしました。Descriptionは特にないので省略。
次にSourceを選択します。今回はSource Providerにgithubを使います。githubを選択すると、[Connect to Github]と出て、GithubへのアクセストークンをOAuthで求められます。認証し、public repositoryを選んで、https://github.com/reiki4040/rnbin
を入れます。
次に、ビルドに使うイメージを選択します。
今回は、Codebuildに用意されているGolangの1.7.3を使いました。Use an image managed by AWS CodeBuild
を選び、Operating systemはUbuntu
(しかない)にします。Go最新のバージョンだと、docker hubなどから持ってきて使う必要がありますね。
build specificationはリポジトリのbuildspec.ymlを使うので、そのままUse the buildspec.yml in the source code root directory
を。
最後に、ビルド後のファイルを置くS3バケットとパスを指定します。
Amazon S3
、artifacts nameは、S3のディレクトリになるので、今回はrnbin
にします。Bucket nameは任意のS3バケットを選択します。
Service Roleはデフォルトで勝手に作ってくれるので、それに任せます。*3
[continue]を押すと、確認プレビューが出ます。Advancedの所は、ビルドマシンのスペックなど選択できますが、今回は省略したのでデフォルトのスペックで動くようになっています。
ビルドする
[Save and Build]を押すと、Build Projectが保存され、Buildが始まり、対象のハッシュを聞かれます。
省略するとデフォルトブランチの最新状態で実行されますが、今回はfeatureブランチの最新のコミットハッシュをコピーしてきて入れます。(画像のハッシュは途中のものなので、最後のものではないです)そして、[Start Build]を押すと開始されます。
あとは、ビルドのフェーズが着々と進み、In ProgressからSucceededやFailedなどやビルドのログが表示されます。ビルドのログは CloudWatch logsに記録されます。何か問題がある場合はそこを見て判断します。
最後まで成功すると、指定したS3のバケット/rnbin/にrnbinというバイナリが置かれています。*4
buildspec.yml概要と、vendoring回避策
リポジトリのbuildspec.ymlは以下。debug用のpwd
とecho $GOPATH
も入ってます。
version: 0.1 phases: install: commands: - go get github.com/Masterminds/glide pre_build: commands: # debug - pwd - echo $GOPATH # install libraries - glide up # workaround for vendoring - mkdir -p /go/src/github.com/reiki4040/rnbin - mv * /go/src/github.com/reiki4040/rnbin/ - mv .git /go/src/github.com/reiki4040/rnbin/ build: commands: # workaround for vendoring - cd /go/src/github.com/reiki4040/rnbin && ./build.sh post_build: commands: # workaround for vendoring - cp /go/src/github.com/reiki4040/rnbin/rnbin . artifacts: files: - rnbin
buildspec.yml概要
buildspec.ymlに書く内容は、主に4つのビルドフェーズと、ビルド生成物の出力定義(artifacts)です。 ビルドフェーズは以下4つです。
- install
- pre_build
- build
- post_build
必要なツールを入れる、依存解決など、ビルド自体、ビルド後の後始末など、といった感じで、それぞれ複数のコマンドを書けます。
rnbinには、build.shというスクリプトを使って、go buildの流れを実行できるようにしてあります。主にはgitからハッシュやversion、加えてgo versionなどを持ってきてgo buildするという処理が入っています。これをCodeBuildで実行させます。
今回は、glideをインストールして、ライブラリインストール、後述のvendoring回避策をやって、build.shを呼び出し、バイナリを移動させて、S3に保存する。となっています。
vendoringでハマる (回避はしているが、きれいでない)
glideでインストールしたライブラリですが、./vendor/
ができているにもかかわらず、cannot find package
と言われてハマりました。使っている環境はgo1.7.3で、go1.5からvendoring ができますが、この機能は$GOPATH以下の場合のみ動作し、それ以外では動作しないとのこと。
試しにpwd
とecho $GOPATH
で、パスを出してみると、GOPATHには含まれている。
コマンド | 結果 ※Xの部分はビルドごとに数字変わります。 |
---|---|
pwd |
/codebuild/output/srcXXXXXXXX/src |
echo $GOPATH |
/go:/codebuild/output/srcXXXXXXXX/src |
エラーを見ると、GOPATH以下のsrcを見ているようで、そもそもrnbin自体に含まれているs3backend packageを読み込めていない。
/usr/local/go/src/github.com/reiki4040/rnbin/s3backend (from $GOROOT) /go/src/github.com/reiki4040/rnbin/s3backend (from $GOPATH) /codebuild/output/srcXXXXXXXX/src/src/github.com/reiki4040/rnbin/s3backend
つまり、currentのパッケージも読めないと。vendorが読めないのは、GOPATH以下ですが、$GOPATH/src/github.com/reiki4040/rnbin
という形になっていないのが原因っぽい。そういうことか。
参考にしたページでは、$GOPATH
以下に全部コピーして、ビルドする回避策が提示されていましたが、これだと完全には解決しませんでした。
前述の通り、$GOPATH
には:
区切りで2つ入っているので、そのまま使うとうまく動作していない様子。それなら、/go
はまぁ入ってるでしょうと割り切り、$GOPATH
を使わず、直接/go/src/github.com/reiki4040/rnbin
といったようにコピーして実行していったらやっと通りました。
vendoring回避後のartifactsのファイル指定
生成したものをS3に保存するため、buildspec.ymlのartifactsの所にファイル名を書くんですが、goプロジェクト一式を/go
以下にコピーして、ビルドした関係上、そっちに生成されています。
ただ、以下のように指定すると、S3にもそのディレクトリ構造がそのまま残ってしまいました。
artifacts: files: - /go/src/github.com/reiki4040/rnbin/rnbin
これはpost_buildのcommandにcp /go/src/github.com/reiki4040/rnbin/rnbin .
とcurrentにバイナリをコピーしてくるコマンドを追加しました。commandは1行ずつ独立して実行されるので、.
で持って来れます。
これでartifactsの所にはrnbin (生成バイナリのファイル名)のみを指定して、S3にもそのバイナリファイルだけが保存されます。
別ブランチの指定
サンプルだったのでfeatureブランチを使いました。最初はpre_buildにgit checkout
を入れていましたが、start buildでfeatureブランチのハッシュを指定したらfeatureブランチ(というかハッシュのやつ)で落とされたので、不要でした。
まとめ
EC2立てずに、マネージドなサーバでビルドできるところがいいですね。githubじゃなくても、S3にファイルあげてもいいし、CodeCommitにしてもいいので、AWS使ってる人は手をつけやすいかも。vendoringハマりましたが、Jenkins用のインスタンス作って運用するのは手間なので、その点はマネージドの強み。前後の作業も、AWSマネージドでやれるなら便利そうですね。他のCIサービスと比べてどうかは、他やってないのでまだわからず。
glideなどでのvendoringやサブパッケージは、普通に使うと思うので、CodeBuild側でやらなくていいようにしてくれると嬉しいですね。