aws-sdk-go v2を使ってLocalstackのs3, DynamoDBに接続する
はじめに
testcontainers-goを利用してlocalstackのコンテナを起動してs3やDynamoDBにデータを保存や取得をするテストを書いています。
先日、ローカル環境ではテスト通るのですが、Github Actionsで実行すると動いてくれない事象が発生してハマったので記事にしておきます。
localstackのコンテナを起動する
func setupDynamoDBTestContainer(t *testing.T) {
t.Helper()
ctx := context.Background()
// Container start
initScriptPath, err := filepath.Abs("../test/localstack")
require.NoError(t, err)
l, err := localstack.StartContainer(ctx,
localstack.OverrideContainerRequest(testcontainers.ContainerRequest{
Name: "testcontainers-localstack",
Env: map[string]string{
"SERVICES": "dynamodb",
},
Mounts: testcontainers.ContainerMounts{
testcontainers.BindMount(initScriptPath, "/etc/localstack/init/ready.d"),
},
WaitingFor: wait.ForLog("AWS dynamodb.CreateTable => 200").
WithPollInterval(5 * time.Second).
WithStartupTimeout(180 * time.Second),
}))
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, l.Terminate(ctx))
})
// Get LocalStack Container Host
host, err := l.Host(ctx)
require.NoError(t, err)
// Get the mapped port for LocalStack
port, err := l.MappedPort(ctx, "4566/tcp")
require.NoError(t, err)
awsEndpoint := fmt.Sprintf("http://%s:%d", host, port.Int()) //nolint:nosprintfhostport
awsRegion := "ap-northeast-1"
t.Setenv("AWS_DYNAMODB_ENDPOINT", awsEndpoint)
t.Setenv("AWS_DYNAMODB_REGION", awsRegion)
}
../test/localstack
ここにはDynamoDBのテーブル作成するためのスクリプトを配置しています。
awslocal dynamodb create-table --table-name Test \
--attribute-definitions \
AttributeName=MessageID,AttributeType=S \
--key-schema \
AttributeName=MessageID,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1
起動時にエンドポイントを取得して次の認証時に利用します。
AWS認証の方法
以下のように LoadDefaultConfig
を利用して認証情報を取得することができます。
// AWS認証情報を取得する
customResolver := aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
if awsEndpoint != "" {
return aws.Endpoint{
PartitionID: "aws",
URL: awsEndpoint,
SigningRegion: awsRegion,
}, nil
}
// returning EndpointNotFoundError will allow the service to fallback to its default resolution
return aws.Endpoint{}, &aws.EndpointNotFoundError{}
})
cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithRegion(awsRegion),
config.WithEndpointResolver(customResolver),
)
if err != nil {
log.Fatalf("Failed to load AWS config: %v", err)
}
// DynamoDBサービスクライアントの初期化
svc := dynamodb.NewFromConfig(cfg)
localstackで実行させるときには awsEndpoint
にlocalstackのエンドポイントを設定します。
上記のような customResolver
を用意しておくと、例えばローカルで実行するときには以下のような環境変数を設定しておきAWS上で実行するときにはこれらの環境変数を設定しないようにすれば認証されたAWSアカウント上のサービスに接続するといった切り替えができます。
# DynamoDB
AWS_DYNAMODB_ENDPOINT=http://localhost:4566
AWS_DYNAMODB_REGION=ap-northeast-1
ハマったところ
上記のような設定でlocalstackに接続に行くはずなのですが、Github Actionsで実行すると以下のようなエラーが発生してしまいました。
SDK 2023/05/18 01:04:45 DEBUG Request
PUT /latest/api/token HTTP/1.1
Host: 169.254.169.254
User-Agent: aws-sdk-go-v2/1.18.0 os/linux lang/go/1.20.4 md/GOOS/linux md/GOARCH/amd64 ft/ec2-imds
Content-Length: 0
Amz-Sdk-Request: attempt=1; max=3
X-Aws-Ec2-Metadata-Token-Ttl-Seconds: 300
Accept-Encoding: gzip
SDK 2023/05/18 01:04:45 DEBUG Response
HTTP/1.1 400 Bad Request
Content-Length: 322
Content-Type: text/xml; charset=utf-8
Date: Thu, 18 May 2023 01:04:44 GMT
Server: Microsoft-IIS/10.0
<?xml version="1.0" encoding="utf-8"?>
<Error xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Code>InvalidHttpVerb</Code>
<Message>The HTTP verb specified was not recognized by the server.</Message>
<Details>'PUT' is not a supported verb.</Details>
</Error>
2023/05/18 01:04:45 Failed to put item: operation error DynamoDB: PutItem, failed to sign request: failed to retrieve credentials: failed to refresh cached credentials, no EC2 IMDS role found, operation error ec2imds: GetMetadata, failed to get API token, operation error ec2imds: getToken, http response error StatusCode: 400, request to EC2 IMDS failed
aws-sdkのログは以下のような設定をデバッグのために追加して出したものです。
config.WithClientLogMode(aws.LogRequestWithBody|aws.LogResponseWithBody),
customResolver周りを疑って色々と試行錯誤をしていたのですが、結局原因はAWSの認証情報が設定されていなかったことでした。
解決策
ローカルでは .aws/credentials
に認証情報を設定していたのですが、Github Actionsではこれが設定されていないためにエラーが発生していました。
Github Actionsで実行するときには以下のように実行するようにしました。
- name: Run tests
env:
AWS_ACCESS_KEY_ID: "dummy"
AWS_SECRET_ACCESS_KEY: "dummy"
run: |
go test -short -race ./...
localstackのサービスに接続するだけなのでdummyの値は何でも良いです。
まとめ
わかってみればなんともないことでした。
ただエラーの内容から原因の想像がなかなかつかず、ローカルとGithub Actionsで実行されているコンテナ環境の違いは何かを考えていった結果としてこの解決策にたどり着くことができました。
おわり。
Discussion
ユニットテストでは常にダミーのCredential情報で良いのでテストコードの中で以下のような記述をしても良いですね。