開発用GolangアプリケーションDockerfile

## 開発用ベースイメージ

開発環境共通で使えるベースイメージを作成して、Docker Hubとかにアップロードする。

[roothy/golang-1.6-development](https://hub.docker.com/r/roothy/golang-1.6-development/)

* FROM: Golang公式イメージをベースイメージにする
* 管理ツールをインストール
	* [go generate](https://blog.golang.org/generate)で使う go tool
		* stringer: enumの文字列表現コードを自動生成
		* jsonenums: enumのjson表現コードを自動生成
	* パッケージバージョン管理: [gom](https://github.com/mattn/gom)
	* Task runner: [godo](https://github.com/go-godo/godo)


```Dockerfile
FROM golang:1.6
MAINTAINER roothybrid7@gmail.com

# Install tools
RUN go get golang.org/x/tools/cmd/stringer \
        github.com/mattn/gom \
        gopkg.in/godo.v2/cmd/godo \
        github.com/campoy/jsonenums \
        github.com/pquerna/ffjson \
        github.com/k0kubun/pp
```

## アプリ用にカスタマイズしたDockerfile

開発用ベースイメージを基にカスタマイズして、アプリで使えるようにする。

※ローカルPCのプロジェクトディレクトリをVolumeとして接続している場合は、gomコマンドの実行は不要。


```Dockerfile
FROM roothy/golang-1.6-development
MAINTAINER roothybrid7@gmail.com

# Setup project

ENV HOSTING=bitbucket.org \
    VCS_USER=team1 \
    PROJECT=go_app

ENV WORK_DIR=/go/src/${HOSTING}/${VCS_USER}/${PROJECT}

WORKDIR $WORK_DIR

# Workaround: cached go packages not affected by the file changes.
ADD Gomfile ${WORK_DIR}/Gomfile
RUN gom update || echo "gom install error: $?"
RUN gom build

ADD entrypoint.sh ${WORK_DIR}/entrypoint.sh
ADD . $WORK_DIR
```

## Dockerプロセスを起動するためのシェルスクリプト

* ON_WATCHという環境変数がセットされていれば、Task runnerを使って起動

Goアプリケーション

```entrypoint.sh
#!/bin/bash - 
set -o nounset                              # Treat unset variables as an error
set -x

env | grep ON_WATCH >/dev/null
if [ $? -eq 0 ]; then
  godo server --watch
else
  go install
  go_app
fi
```

Go Consumerアプリケーション(Job-Worker)

```consumer_entrypoint.sh
#!/bin/bash - 
set -o nounset                              # Treat unset variables as an error
set -x

env | grep ON_WATCH >/dev/null
if [ $? -eq 0 ]; then
  godo server --watch
else
  go install
  go_app runConsumer
fi
```

## Task runner

```main.go
package main

import (
	do "gopkg.in/godo.v2"
)

func generateTasks(p *do.Project) {
	p.Task("default", do.P{"scope", "runas", "errors", "uploadtype", "distrib", "cjob", "topic"}, nil)

	// API Scope types
	p.Task("scope", nil, func(c *do.Context) {
		c.Run(`go generate`, do.M{"$in": "app/scope"})
	}).Src("app/scope/*.go", "!app/scope/*_string.go")

	// Data fetching/manipulating authority types
	p.Task("runas", nil, func(c *do.Context) {
		c.Run(`go generate`, do.M{"$in": "app/runas"})
	}).Src("app/runas/*.go", "!app/runas/*_string.go")

	// Error codes
	p.Task("errors", nil, func(c *do.Context) {
		c.Run(`go generate`, do.M{"$in": "app/errors"})
	}).Src("app/errors/*.go", "!app/errors/*_string.go", "!app/errors/*_jsonenums.go")

	// File upload type
	p.Task("uploadtype", nil, func(c *do.Context) {
		c.Run(`go generate`, do.M{"$in": "app/uploadtype"})
	}).Src("app/uploadtype/*.go", "!app/uploadtype/*_string.go")

	// Distribution file storage type
	p.Task("distrib", nil, func(c *do.Context) {
		c.Run(`go generate`, do.M{"$in": "app/distrib"})
	}).Src("app/distrib/*.go", "!app/distrib/*_string.go")

	// Job type
	p.Task("cjob", nil, func(c *do.Context) {
		c.Run(`go generate`, do.M{"$in": "module/cjob/ctype"})
	}).Src("module/cjob/ctype/ctype.go")

	// Message queue topic names(NSQ)
	p.Task("topic", nil, func(c *do.Context) {
		c.Run(`go generate`, do.M{"$in": "app/topic"})
	}).Src("app/topic/*.go", "!app/topic/*_string.go")
}

func tasks(p *do.Project) {
	p.Use("generate", generateTasks)

	p.Task("generate", do.S{"generate:default"}, nil)
	p.Task("default", do.S{"server"}, nil)

	p.Task("server", do.S{"generate"}, func(c *do.Context) {
		c.Start("cli.go app.go consumer.go", do.M{"$in": "./"})
	}).Src("*.go", "**/*.go", "conf/*.toml").Debounce(3000)

	p.Task("dbInit", nil, func(c *do.Context) {
		c.Run(`go run cli.go app.go dbInit`, do.M{"$in": "./"})
	})
}

func main() {
	do.Godo(tasks)
}
```