ex_awsを利用して、S3の擬似サービス、s3rverにアクセスしてみよう

## 1. やりたいこと

Elixirを利用して、kinesis, s3を操作したいです。
とはいえ、テスト環境はローカルで行いたいので、[s3rver](https://github.com/jamhall/s3rver)だったり、そのdocker版の[docker-s3rver](https://github.com/tripleW/docker-s3rver)を利用してみることにしました。

## 2. 利用してみたリポジトリ

* https://github.com/letusfly85/generate_test_data4s3

## 3. 実施手順

### 3-0. s3rverをローカルホストで起動する

```bash
npm install s3rver
export PATH=$PATH:node_modules/.bin

mkdir -p tmp
s3rver -d tmp
```

上記のコマンドを実行することで、`localhost:4568`で、s3rverが立ち上がります。

### 3-1. mix.exsに依存を追加して、deps.getを実行

```mix.exs
  defp deps do
    [
     {:mix_test_watch, "~> 0.2", only: [:dev, :test]},
     {:ex_aws,    "~> 0.4.10"},
     {:poison,    "~> 1.2"},
     {:httpoison, "~> 0.7"} 
    ]
  end
```

```bash
mix deps.get
```

### 3-2. config.exsに、aws、s3の設定を記載する

```config/config.exs
config :my_app_conf, :ex_aws,
  s3: [
    scheme: "http://",
    region: "us-east-0",
    access_key_id: "xxxxxxxxxxxxxxxxxxxxx",
    secret_access_key: "xxxxxxxxxxxxxxxxxxxxx",
    host: %{"us-east-0" => "localhost:4568"}
 ],
 debug_requests: true
```

上記のように、s3rverを利用する場合は、`access_key_id`や`secret_access_key`にはダミーの値を設定して問題ないです。

また、少しややこしかったのですが、下記を見ると、`host`は`Map`形式で設定してあげる必要があるようでした。

* https://github.com/CargoSense/ex_aws/blob/master/lib/ex_aws/config/defaults.ex#L26:L40

s3rverへのリクエスト実行時に、どのようなパラメータが渡っていくのかをconsole出力したい場合は、`debug_requests`を`true`に設定する必要があるようでした。

### 3-3. mix testを実行することで、接続テストを実施


以下のような、実行ファイルを用意しました。

```ex
defmodule MyModule do
  @moduledoc"""
  """
  
  require Logger
  use ExAws.S3.Client, otp_app: :my_app_conf

  @doc"""
  """
  def create_a_bucket_and_put_data do
    put_bucket("fuga", "us-east-0")
    put_object("fuga", "name", "aaa")
  end
end
```

### 3-4. 実行したけれども動かず。。

logには、`HTTP ERROR :nxdomain`のようなエラーが出ました。
`put_bucket`からして失敗しているようでした。

## 4. 今回の暫定対応

### 4-1. 原因について

ちょっとソースを眺めてみると、endpointの解決に失敗しているようでした。

https://github.com/CargoSense/ex_aws/blob/master/lib/ex_aws/s3/request.ex#L39:L45

```lib/ex_aws/s3/request.ex
  defp host_and_bucket(host, ""), do: host
  defp host_and_bucket(host, bucket) do
    case bucket |> String.contains?(".") do
      true  -> [host, "/", bucket]
      false -> [bucket, ".", host]
    end
  end
```

本物のs3であれば、問題なく動くのですが、s3rverの場合は、バケットの名前が、ドメインの先頭ではなく、relative_pathとして、ドメインの後ろに付くようです。

### 4-2. 暫定対応

1. テスト実施時にホスト名を変更する
2. ex_awsを改造しちゃう

`2.`のやりかたについてはテストのためにライブラリをいじる、という本末転倒気味な形になりますので却下としました。
でも一応[こちら](https://github.com/letusfly85/ex_aws/blob/dev_s3rver/lib/ex_aws/s3/request.ex#L39:L49)のように実装はしてみました。

ということで、`1.`で試してみました。

### 4-3. ホスト名の変更

テストコマンドを叩くターミナルで、以下のように名称変更しました。

```bash
export bucket_name=テスト用のバケット名
sudo hostname ${bucket_name}.localhost
```

### 4-4. mix testの実行

`4-3.`と同様のターミナルにて`mix test`を実行

`mix deps.get`を気を取り直して実施して、再度実行したところ、うまいこと、bucket作成とデータを作成したbucketに登録することができました＾＾

## 5. 所感

* ex_aws使いやすいですね＾＾　kinesis連携とかは、elixirで記述したほうがscalaとかよりもすっきりかけるかも。
* s3rverの思わぬ落とし穴、というか仕様でした。。しかし止むを得ずか。。動的にホスト名変更できるサービスを間にかましてまでテストもしたくないし。。ちょっと微妙かも。オールインワンなサービスが欲しいところです。
* bucket作成のテストはs3rverを使うと、現状だと難しいです。
* riak-s2とかにも時間があったら触ってみよう。

## 6. おまけ

下記のように`instance_role`を見て、認証してくれるようです。
実運用時には、keyを配布する必要もなさそうなので、セキュリティ設計をしっかりと組むこともできそうですね＾＾

```config/config.exs
config :ex_aws,
  access_key_id: [{:system, "AWS_ACCESS_KEY_ID"}, :instance_role],
  secret_access_key: [{:system, "AWS_SECRET_ACCESS_KEY"}, :instance_role]
```

---

本日は以上となります。