AWS SDK for Go の基本とRoute53へのHostedZone作成とレコード作成

# はじめに
[Go Advent Calendar 2015 その3](http://qiita.com/advent-calendar/2015/go3)の5日目の記事です。

[AWS SDK for Go](https://github.com/aws/aws-sdk-go)(以下SDK) は ver 1.0.0 が11月にリリースされ12月4日時点でver 1.0.3がリリースされています。
この記事ではSDKの基本的な使い方と私が実際に行ったRoute53を対象にしたSDKの利用方法について書きます[^1]。

[^1]: SDKのバージョンは1.0.3で実施した内容です。

# 基本的な使い方
### パッケージの取得
まずはパッケージを取得する必要があるので`go get`コマンドで取得します。
`go get github.com/aws/aws-sdk-go`
またcredentialsファイルの読み込みなどで使用するためgo-iniパッケージも入れます。
`go get github.com/vaughan0/go-ini`
あとは通常通り`import`して利用します。基本的には以下の3つと必要なサービス(`github.com/aws/aws-sdk-go/service/route53`など)を`import`すると良いかと思います。

```go:importの例
import (
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/credentials"
    "github.com/aws/aws-sdk-go/aws/session"
)
```

### AccessKeyIDとSecretAccessKeyの設定と読み込み
SDKを利用し自身のAWS環境にアクセスするためにはAccessKeyIDとSecretAccessKeyが最低限必要です。他にSessionTokenが必要な場合もありますが今回は省略します。また取得の方法などは各自で調べていただくとして、ここではどのように設定するのかを書きます。
今回はコードにAccessKeyIDとSecretAccessKeyをべた書きしたり環境変数として埋め込むことはせずに、ファイルを事前に作成してそこから読み込むようにする場合を想定しています。

まずは`.aws`ディレクトリをホームディレクトリに作成します。
次に作成した`.aws`ディレクトリ内に`credentials`というファイルを作成します[^2] [^3]。
`credentials`には以下のような内容でAccessKeyIDとSecretAccessKeyを記述します。

[^2]: 作成したディレクトリやファイルのパーミッションは700や600など状況に合わせて設定します。
[^3]: 今回の読み込み方法の場合、実際にはファイルの作成場所は.awsディレクトリ内である必要やcredentialsというファイル名である必要はないです。


```text:credentialsの例
[default]
aws_access_key_id = BBBBBBYYYYYYYY11111
aws_secret_access_key = 987ZXY654FEDcba321
[develop]
aws_access_key_id = AAAAAAZZZZZ2222333
aws_secret_access_key = 123abcDEF456XYZ789
```

`[default]`や`[develop]`はプロファイルであり、SDKでは指定がなければ`[default]`のプロファイルを読み込むようになっています。
もちろん上記のように`[develop]`や任意の名前を付けて複数管理することができます。
これで設定が完了しました。

最後に実際にコード上でどのように読み込むか記載します。

```go:credentialsの読み込み例
// $HOME/.aws/credentialsのdefaultプロファイルを読み込む場合
var creds *credentials.Credentials = credentials.NewSharedCredentials("", "default")

// $HOME/.aws/credentialsのdevelopプロファイルを読み込む場合
var creds *credentials.Credentials = credentials.NewSharedCredentials("", "develop")
```

今回はAccessKeyIDとSecretAccessKeyの読み込みに`NewSharedCredentials`を使用しています。
`NewSharedCredentials`の第一引数はファイルのフルパスで第二引数にはプロファイル名を与えます。
第一引数が空の場合は`$HOME/.aws/credentials`を読み込みます。
第二引数が空の場合はプロファイル名`default`の情報を読み込みます。

他にも先述したとおり直接AccessKeyIDとSecretAccessKeyを書いて読み込ませることができる`NewStaticCredentials`や、環境変数から読み込ませることができる`NewEnvCredentials`[^4]などもあります。
[^4]: 環境変数でのアクセスキーはAWS_ACCESS_KEY_IDまたはAWS_ACCESS_KEY、 シークレットキーはAWS_SECRET_ACCESS_KEYまたはAWS_SECRET_KEYから読み込みます。

# Route53へHostedZone作成とレコード登録
今回はRoute53へのHostedZoneを作成し、レコードを登録するというものを例にどのようにSDKを使ったのかを説明していきます。

## 事前準備

始めにサービスに接続するために以下のようにclientを準備します。
credentialsは`$HOME/.aws/credentials`から`develop`プロファイルを読み込むようにしています。
他のサービスを利用する場合も基本的にはこの方法でcredentialsを作成し、clientはサービス毎に変更していく必要があります。
また`&aws.Config`では今回はグローバルサービスなのでCredentialsだけを指定していますが、EC2などを対象にする場合はリージョンなどをこの段階で指定する必要があります。
Configの詳細については[こちら](https://github.com/aws/aws-sdk-go/blob/master/aws/config.go)にあります。

```go:sample1
import (
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/credentials"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/route53"
)

func main() {
    var (
        creds *credentials.Credentials = credentials.NewSharedCredentials("", "develop")
        client *route53.Route53 = route53.New(session.New(&aws.Config{Credentials: creds}))
    )
}
```

## HostedZoneの作成

HostedZoneを作成するにはパラメータを作成する必要があります。今回はpublicなHostedZoneを作成する場合を例にあげます。パラメータは以下のようになります。

```go:sample2
    var HostedZoneParams *route53.CreateHostedZoneInput
    HostedZoneParams = &route53.CreateHostedZoneInput{
      CallerReference: aws.String("createhogefuga.com"),
      Name:            aws.String("hogefuga.com"),
      HostedZoneConfig: &route53.HostedZoneConfig{
        Comment: aws.String("createhogefuga.com"),
      },
    }
```

それぞれの要素について説明します。

 - CallerReference：作成操作の重複を防ぐために任意の文字列
 - Name：ドメイン名
 - HostedZoneConfig：HosetdZoneを作成する際のコンフィグ
    - Comment：任意のコメント

パラメータを作成後、以下のようにしてHostedZoneを作成します。

```go:sample3
var (
    CreateHostedZoneResp *route53.CreateHostedZoneOutput
    err                  error
)
CreateHostedZoneResp, err = client.CreateHostedZone(HostedZoneParams)
```

ここで使用している`client`はsample1で示したもの、`HostedZoneParams`はsample2で示したものです。
作成が正常に完了するとレスポンスが返ってきます。
`CreateHostedZoneResp`で受け取っているのがそのレスポンスです。
以下はレスポンス内容を構造体として表現した場合のものです。

```go:sample4
// HostedZone作成後レスポンス
type HostedZone struct {
    CallerReference        string
    Config                 Config
    Id                     string
    Name                   string
    ResourceRecordSetCount int
}
// HostedZone作成後レスポンス内のコンフィグ構造体
type Config struct {
    Comment     string
    PrivateZone bool
}
```

レスポンスの要素については`sample2`の時に紹介したものがほとんどですが、一部は出ていないものもあります。以下にその内容をまとめます。

 - Id：作成したHostedZoneのId
 - ResourceRecordSetCount：HostedZoneに作成させれているレコード数
 - PrivateZone：HostedZoneがprivateかpublicかのbool値

これでHostedZoneは作成できました。
次に作成したHostedZonenに対してのレコード作成方法を説明します。

## レコード作成

レコード作成時もHostedZone作成時と同様にパラメータを作成する必要があります。
レコード作成のパラメータは以下のようになります。

```go:sample5
var ResourceRecordParams *route53.ChangeResourceRecordSetsInput
ResourceRecordParams = &route53.ChangeResourceRecordSetsInput {
    ChangeBatch: &route53.ChangeBatch {
        Changes: []*route53.Change {
            {
                Action: aws.String("CREATE"),
                ResourceRecordSet: &route53.ResourceRecordSet {
                    Name: aws.String("sub.hogefuga.com."),
                    Type: aws.String("A"),
                    ResourceRecords: []*route53.ResourceRecord {
                        {
                            Value: aws.String("192.168.128.1"),
                        },
                    },
                    TTL: aws.Int64(300),
                },
            },
        },
    },
    HostedZoneId: aws.String("/hostedzone/1234ABCD5678EF"),
}
```

それぞれの要素について説明します。

- ChangeBatch：レコード編集内容の大本
    - Changes：レコード編集内容についての配列
        - Action：作成、更新、削除などの実施内容
        - ResourceRecordSet：ACTION内容の詳細の大本
            - Name：対象のドメイン、もしくはサブドメイン
            - Type：レコードの種類
            - ResourceRecords：値の配列
                - Value：IPなどの値
            - TTL：レコードのTTL
- HostedZoneId：レコード編集を行うHostedZoneのId

レコード編集のパラメータについては要素説明にあるように配列が存在しています。
そのため一度に複数のレコードの設定ができます。
ただし注意してほしいのは複数のレコード設定がまとめて書けるのは1つのHostedZoneに対してということです。

複数のレコード設定をまとめて書いた場合のパラメータは以下のようになります。

```go:sample6
var ResourceRecordParams *route53.ChangeResourceRecordSetsInput
ResourceRecordParams = &route53.ChangeResourceRecordSetsInput {
    ChangeBatch: &route53.ChangeBatch {
        Changes: []*route53.Change {
            {
                Action: aws.String("CREATE"),
                ResourceRecordSet: &route53.ResourceRecordSet {
                    Name: aws.String("sub.hogefuga.com."),
                    Type: aws.String("A"),
                    ResourceRecords: []*route53.ResourceRecord {
                        {
                            Value: aws.String("192.168.128.1"),
                        },
                    },
                    TTL: aws.Int64(300),
                },
            },
            {
                Action: aws.String("CREATE"),
                ResourceRecordSet: &route53.ResourceRecordSet {
                    Name: aws.String("mail.hogefuga.com."),
                    Type: aws.String("MX"),
                    ResourceRecords: []*route53.ResourceRecord{
                        {
                            Value: aws.String("1 ASPMX.L.GOOGLE.COM."),
                        },
                        {
                            Value: aws.String("5 ALT1.ASPMX.L.GOOGLE.COM."),
                        },
                        {
                            Value: aws.String("5 ALT2.ASPMX.L.GOOGLE.COM."),
                        },
                    },
                    TTL: aws.Int64(300),
                },
            },
        },
    },
    HostedZoneId: aws.String("/hostedzone/1234ABCD5678EF"),
}
```

パラメータ作成後は以下のようにしてレコードを作成します。

```go:sample7
var (
    ResourceRecordResp   *route53.ChangeResourceRecordSetsOutput
    err                  error
)
ResourceRecordResp, err = client.ChangeResourceRecordSets(ResourceRecordParams)
```
ここで使用している`client`はsample1で示したもの`ResourceRecordParams`はsample7で示したものです。
作成が正常に完了するとレスポンスが返ってきます。
`ResourceRecordResp`で受け取っているのがそのレスポンスです。
以下はその内容例です。

```text:sample8
{
  ChangeInfo: {
    Id: "/change/4321ABCD8765EF",
    Status: "PENDING",
    SubmittedAt: 2015-12-05 00:00:20.555 +0000 UTC
  }
}
```

レコード作成時のレスポンスの要素についてはHostedZone作成時とは違い3つの要素のみです。

 - Id：HostedZoneのIdとは別の操作識別のためのもの
 - Status: ACTIONのステータス
 - SubmittedAt：追加したときの時間(UTC)

レコード作成はこれで完了です。
レコードについてはパラメータ作成時に少し書きましたが、作成以外にも更新、削除ができます。これはACTIONをCREATEからUPSERTやDELETEにすることで可能です。

# おわりに
ここまで読んでいただき、ありがとうございました！
SDKのバージョンも1系になったので今後はいろいろなGo実装のツールが登場してより便利にAWS環境の構築やリソースの管理などができるのではないかなと思います。

明日は@minodiskさんです。

---