GoからAWSの各種操作をしようとする際、対応する公式のSDKがないので自前でAPIを叩いて行う必要があります。
Goの勉強がてら東京リージョンのec2インスタンスの一覧を取得するコードを書いてみました。
基本的にAPIの公式ドキュメントを参照していますが、
それ以外にvagrantなどの作者としてもおなじみのmitchelhさん作成のawsライブラリの実装を参考にしました。
https://github.com/mitchellh/goamz
APIのリクエスト構造
エンドポイント
使用するリージョンにあわせて選択します。
今回はec2の東京リージョンなので、https://ec2.ap-northeast-1.amazonaws.com/
を使用します。
エンドポイント一覧:http://docs.aws.amazon.com/general/latest/gr/rande.html#emr_region
クエリパラメータ
クエリパラメータには実行するすべてのリクエストで共通の必須パラメータと実施する操作固有のパラメータを指定します。
共通パラメータ
すべてのリクエストで共通のパラメータです。アクションの指定とクエリ認証が主です。
認証方式にはSignatureVersion2とSignatureVersion4があるらしいのですが、以降はクエリ認証にSignatureVersion2を使用する前提で進みます。
(SignatureVersion4はあまり調べてない。。)
以下、指定必須のパラメータです。
-
Action: AWSに対する操作を指定。今回は
DescribeInstance
を使用。
アクション一覧:http://docs.aws.amazon.com/AWSEC2/latest/APIReference/query-apis.html -
Version: APIバージョン。
-
AWSAccessKeyId: AccessKeyIDを指定。
-
Expires: シグネチャの期限(たぶん・・)。ExpiresかTimestampのいずれかは必須。
-
Timestamp: シグネチャ作成日時(たぶん・・)。ExpiresかTimestampのいずれかは必須。
-
Signature: シグネチャ。作成方法は後述。
-
SignatureMethod: シグネチャ作成に使用するアルゴリズム。HmacSHA256かHmacSHA1を指定。
-
SignatureVersion: 認証に使うバージョン。2を指定。
参考:http://docs.aws.amazon.com/AWSEC2/latest/APIReference/Query-Common-Parameters.html
固有パラメータ
各操作ごとに必要なものがあれば指定します。
シグネチャの生成方法
HmacSHA256の場合、Canonical文字列を生成しシークレットキーと一緒にハッシュ値を計算し、base64エンコードします。
Canonical文字列は以下のルールで生成します。
リクエストメソッド + \n + ホスト名 + \n + パス + \n + クエリパラメータ
クエリパラメータはキーの辞書順で&で結合されている必要があり、かつ各パラメータがURLエンコードされていなければなりません。
GET\n
ec2.ap-northeast-1.amazonaws.com\n
/\n
AWSAccessKeyId=AccessKeyId&Action=DescribeInstances&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2014-11-01T15%3A19%3A30&Version=2014-09-01
コード
ここまでの流れを踏まえたサンプルコードとなります。
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"time"
)
func main() {
host := "https://ec2.ap-northeast-1.amazonaws.com/"
accessKey := "Access Key ID" // アクセスキー
secretKey := "Secret Access Key" // シークレットキー
// Signature以外の必須共通パラメータ
params := map[string]string{
"Action": "DescribeInstances",
"Version": "2014-09-01",
"AWSAccessKeyId": accessKey,
"Timestamp": time.Now().UTC().Format(time.RFC3339),
"SignatureVersion": "2",
"SignatureMethod": "HmacSHA256",
}
// Action固有のパラメータがある場合はシグネチャ生成前に設定
params["MaxResults"] = "5"
endpoint, err := url.Parse(host)
if err != nil {
log.Fatal(err)
}
// シグネチャ生成
signature := getSignature(secretKey, endpoint.Host, params)
params["Signature"] = signature
//
endpoint.RawQuery = joinParams(params)
// リクエスト
resp, err := http.Get(endpoint.String())
if err != nil {
log.Fatal(err)
}
body, _ := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
// 結果を出力
fmt.Println(string(body))
}
// URLエンコードされたパラメータを生成する
func joinParams(params map[string]string) string {
p := make(url.Values, len(params))
for k, v := range params {
p[k] = []string{v}
}
return p.Encode()
}
// シグネチャを生成し取得する
func getSignature(secretKey string, host string, params map[string]string) string {
payload := "GET\n" + host + "\n" + "/\n" + joinParams(params)
hash := hmac.New(sha256.New, []byte(secretKey))
hash.Write([]byte(payload))
signature := make([]byte, base64.StdEncoding.EncodedLen(hash.Size()))
base64.StdEncoding.Encode(signature, hash.Sum(nil))
return string(signature)
}
上記を実行すると、以下のようなXML形式の結果が得られます。
<DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2014-09-01/">
<requestId>fdcdcab1-ae5c-489e-9c33-4637c5dda355</requestId>
<reservationSet>
<item>
<reservationId>r-1a2b3c4d</reservationId>
<ownerId>111122223333</ownerId>
<groupSet>
<item>
<groupId>sg-1a2b3c4d</groupId>
<groupName>my-security-group</groupName>
</item>
</groupSet>
<instancesSet>
<item>
<instanceId>i-1a2b3c4d</instanceId>
<imageId>ami-1a2b3c4d</imageId>
<instanceState>
<code>16</code>
<name>running</name>
</instanceState>
<privateDnsName/>
<dnsName/>
<reason/>
<keyName>my-key-pair</keyName>
<amiLaunchIndex>0</amiLaunchIndex>
<productCodes/>
<instanceType>c1.medium</instanceType>
<launchTime>YYYY-MM-DDTHH:MM:SS+0000</launchTime>
<placement>
<availabilityZone>us-west-2a</availabilityZone>
<groupName/>
<tenancy>default</tenancy>
</placement>
<platform>windows</platform>
<monitoring>
<state>disabled</state>
</monitoring>
<subnetId>subnet-1a2b3c4d</subnetId>
<vpcId>vpc-1a2b3c4d</vpcId>
<privateIpAddress>10.0.0.12</privateIpAddress>
<ipAddress>46.51.219.63</ipAddress>
<sourceDestCheck>true</sourceDestCheck>
<groupSet>
<item>
<groupId>sg-1a2b3c4d</groupId>
<groupName>my-security-group</groupName>
</item>
</groupSet>
<architecture>x86_64</architecture>
<rootDeviceType>ebs</rootDeviceType>
<rootDeviceName>/dev/sda1</rootDeviceName>
<blockDeviceMapping>
<item>
<deviceName>/dev/sda1</deviceName>
<ebs>
<volumeId>vol-1a2b3c4d</volumeId>
<status>attached</status>
<attachTime>YYYY-MM-DDTHH:MM:SS.SSSZ</attachTime>
<deleteOnTermination>true</deleteOnTermination>
</ebs>
</item>
</blockDeviceMapping>
<virtualizationType>hvm</virtualizationType>
<clientToken>ABCDE1234567890123</clientToken>
<tagSet>
<item>
<key>Name</key>
<value>Windows Instance</value>
</item>
</tagSet>
<hypervisor>xen</hypervisor>
<networkInterfaceSet>
<item>
<networkInterfaceId>eni-1a2b3c4d</networkInterfaceId>
<subnetId>subnet-1a2b3c4d</subnetId>
<vpcId>vpc-1a2b3c4d</vpcId>
<description>Primary network interface</description>
<ownerId>111122223333</ownerId>
<status>in-use</status>
<macAddress>1b:2b:3c:4d:5e:6f</macAddress>
<privateIpAddress>10.0.0.12</privateIpAddress>
<sourceDestCheck>true</sourceDestCheck>
<groupSet>
<item>
<groupId>sg-1a2b3c4d</groupId>
<groupName>my-security-group</groupName>
</item>
</groupSet>
<attachment>
<attachmentId>eni-attach-1a2b3c4d</attachmentId>
<deviceIndex>0</deviceIndex>
<status>attached</status>
<attachTime>YYYY-MM-DDTHH:MM:SS+0000</attachTime>
<deleteOnTermination>true</deleteOnTermination>
</attachment>
<association>
<publicIp>198.51.100.63</publicIp>
<ipOwnerId>111122223333</ipOwnerId>
</association>
<privateIpAddressesSet>
<item>
<privateIpAddress>10.0.0.12</privateIpAddress>
<primary>true</primary>
<association>
<publicIp>198.51.100.63</publicIp>
<ipOwnerId>111122223333</ipOwnerId>
</association>
</item>
<item>
<privateIpAddress>10.0.0.14</privateIpAddress>
<primary>false</primary>
<association>
<publicIp>198.51.100.177</publicIp>
<ipOwnerId>111122223333</ipOwnerId>
</association>
</item>
</privateIpAddressesSet>
</item>
</networkInterfaceSet>
</item>
</instancesSet>
</item>
</reservationSet>
</DescribeInstancesResponse>
以上です。
APIの仕様の詳細については正直理解があいまいなので、間違いがあるかもしれませんが、実行はできるかと思います。
結果はXMLで返ってくるのでパースして構造体にマッピングする必要がありますが、
ネストが深く構造体を作るのが大変だったので、XMLからGoの構造体を出力するツールを作成しました。
後日公開します。