dockerによるgolang開発にトライ w/ AWS SDK

AWS上で高速に動くETLアプリケーションを、さっくり書く必要があったので調査結果をまとめつつ。
（goは触ったばかりなので非効率なところがあるかも）

## 前提
### 利用シーン
S3から複数のオブジェクトをダウンロード、加工、格納するアプリケーションを想定。
環境調査が目的なので、まずはlistobjectが出来る状態を目指す。

検証データには、s3に格納されているオープンデータ、[Global Surface Summary of Day](https://registry.opendata.aws/noaa-gsod/)を利用。

### 環境類
- go v1.12
- [aws sdk for go v2](https://github.com/aws/aws-sdk-go-v2)  
  現在Developer previewだが調査も兼ね（S3周りはそこまでIF変わっていなそうな印象を受けた）

dockerやaws cli、クレデンシャル類も設定しておく。


## 準備
### 1. ディレクトリ構成
https://github.com/golang-standards/project-layout を参考。
小さなプロジェクトにはOverkillだぜ！と書いてあるけど、変な並びになるのも気持ち悪いので、必要最低限だけ参考に。

```
.
├── README.md
├── build  // CI/CDなどに利用するスクリプト類
│   └── docker  
│       ├── Dev.Dockerfile
│       └── Prod.Dockerfile
├── cmd  // アプリケーションのメイン
│   └── simple-etl-go-aws
│       └── main.go
└── internal  // プロジェクト内部でしか使わないコード類
    └── s3
        └── listobjects.go

```

外部から利用されるコード類は `/pkg` を作ってその配下に置くとのこと。



### 2. 開発用docker環境
https://hub.docker.com/_/golang よりバージョンタグのみのものを利用。
単にバージョン指定のみの場合、 `x.x.x-streach` という名称のdebianベースのものが選択される。

```dockerfile:Dev.dockerfile
FROM golang:1.12

RUN apt-get update && apt-get install -y \
  python3-dev \
  python3-pip

RUN pip3 install \
  awscli  # デバッグ用

WORKDIR /simple-etl-go-aw
```

ルートディレクトリから下記コマンドで開発用イメージビルド。

```sh
docker build -t simple-etl-go-aws:dev -f build/docker/Dev.Dockerfile .
```

さらにルートディレクトリから、下記コマンドでコンテナ内へ。
（`~/.aws` 以下にクレデンシャルが設定されていることを前提。環境変数として注入してローカルで開発をする）

```sh
docker run -it --rm \
  -e AWS_DEFAULT_REGION=ap-northeast-1 \
  -e AWS_ACCESS_KEY_ID=$(aws --profile default configure get aws_access_key_id) \
  -e AWS_SECRET_ACCESS_KEY=$(aws --profile default configure get aws_secret_access_key) \
  -v $(pwd):/simple-etl-go-aws \
  simple-etl-go-aws:dev \
  /bin/bash
```

### 3. go modules初期化

https://github.com/golang/go/wiki/Modules

v1.13からはコレが標準の外部モジュール管理のツールになるとのこと。v1.12でも使えるので利用する。

```sh
# 初期化
go mod init github.com/leo-mon/simple-etl-go-aws
```

これにより`go.mod`が生成される。GitHubなどのユーザー名+プロジェクト名を使うと、後ほど必要なモジュールを入れ込んだ際にいい感じに名前が揃うが、公開するプロジェクトでなければ階層は深くしなくてもいい気がする。

## 開発
### コード類
https://github.com/aws/aws-sdk-go-v2/tree/master/example/service/s3/listObjects
を元に、2ファイルへ分割（配置場所は上記ディレクトリ）


```main.go
package main

import (
	"fmt"
	"github.com/leo-mon/simple-etl-go-aws/internal/s3"
	"os"
)

func exitErrorf(msg string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, msg+"\n", args...)
	os.Exit(1)
}

func main() {
	if len(os.Args) < 2 {
		exitErrorf("you must specify a bucket")
	}
	s3.ListObjects(os.Args[1])
}
```

```listobject.go
package s3

import (
	"fmt"
	"os"

	"github.com/aws/aws-sdk-go-v2/aws/endpoints"
	"github.com/aws/aws-sdk-go-v2/aws/external"
	"github.com/aws/aws-sdk-go-v2/service/s3"
)

func exitErrorf(msg string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, msg+"\n", args...)
	os.Exit(1)
}

// Lists all objects in a bucket using pagination
//
// Usage:
// listObjects <bucket>
func ListObjects(bucketname string) {

	cfg, err := external.LoadDefaultAWSConfig()
	if err != nil {
		exitErrorf("failed to load config, %v", err)
	}

	cfg.Region = endpoints.UsEast1RegionID

	svc := s3.New(cfg)

	req := svc.ListObjectsRequest(&s3.ListObjectsInput{Bucket: &bucketname})
	p := req.Paginate()
	for p.Next() {
		page := p.CurrentPage()
		for _, obj := range page.Contents {
			fmt.Println("Object: ", *obj.Key)
		}
	}

	if err := p.Err(); err != nil {
		exitErrorf("failed to list objects, %v", err)
	}
}
```

`main.go`のimport文で分かるように、`go mod init` で設定したパスを起点にimportを行なっている。

### 走行

```sh
go run cmd/simple-etl-go-aws/main.go aws-gsod
```
と打つと、

```shell-session
go: finding github.com/aws/aws-sdk-go-v2 v0.7.0
...
go: downloading github.com/aws/aws-sdk-go-v2 v0.7.0
go: extracting github.com/aws/aws-sdk-go-v2 v0.7.0
...
Object:  1929/030050-99999.csv
Object:  1929/030750-99999.csv
Object:  1929/030910-99999.csv
...
```

のように、import文に含めたモジュールを自動で検知、ダウンロードしてくれる。
（この際、このコンテナ内では/go/pkg以下に入る）

##ビルド
```shell
go build cmd/simple-etl-go-aws/main.go
```

とすると`main`がビルドされる。

---

コンテナを破棄するたびに再度パッケージをダウンロードする必要があるのがネックではあるが、コンテナ内`/go/pkg`を適当なディレクトリにマウントしてあげれば良い、はず。