AWSサービスのローカル環境を構築 (LocalStack)

AWSサービスに接続するソフトウェア開発の際の、
ローカル環境構築に便利な [LocalStack](https://localstack.cloud/) の紹介

## 前提知識

- Docker と docker-compose の知識
- AWS CLI コマンド の知識

## Dockerで実行する

[LocalStackのDockerイメージ](https://hub.docker.com/r/localstack/localstack)は単一のDockerコンテナで各種AWSのローカルモック環境を実行できます

```yaml:docker-compose.yml設定例
version: '3.8'

services:
  localstack:
    container_name: localstack
    image: localstack/localstack:0.13.3
    environment:
      SERVICES: s3,dynamodb
      EDGE_PORT: 4566
    ports:
      - '4566:4566'
```

#### 環境変数

- 起動するサービスを`SERVICES` 環境変数でカンマ区切りで指定します  
上記の例では S3 と DynamoDB を起動しています
- `EDGE_PORT` エンドポイントのポート番号を設定。デフォルトは 4566
- その他の環境変数の詳細は[こちら](https://docs.localstack.cloud/localstack/configuration/)

### 起動

```sh:
docker compose up -d
```


## ホスト側から AWS CLI で接続

Access Key ID と Secret Access Key を `test` にする

```
$ aws configure --profile default
AWS Access Key ID [None]: test
AWS Secret Access Key [None]: test
Default region name [None]: ap-northeast-1
Default output format [None]: json
```

awsコマンドに endpoint パラメータを指定して実行

```sh:S3バケット一覧を取得する例
aws s3 ls --endpoint=http://localhost:4566
```


## プログラムからの接続

### Go (AWS-SDK-Go v2)

https://github.com/aws/aws-sdk-go-v2

```go:S3バケット一覧を取得する例
package main

import (
	"context"
	"log"

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

var EndpointURL = "http://localhost:4566"

func main() {
	cfg, err := config.LoadDefaultConfig(
		context.TODO(),
		// EndpointResolverでエンドポイントURLを設定
		config.WithEndpointResolverWithOptions(
			aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
				if EndpointURL != "" {
					return aws.Endpoint{
						URL: EndpointURL,
					}, nil
				}
				return aws.Endpoint{}, &aws.EndpointNotFoundError{}
			}),
		),
	)
	if err != nil {
		log.Fatalf("LoadDefaultConfig failed:%v", err)
	}
	client := s3.NewFromConfig(cfg, func(o *s3.Options) {
		// UsePathStyle を設定する
		o.UsePathStyle = true
	})

	// 3S バケット一覧を表示
	output, err := client.ListBuckets(context.TODO(), &s3.ListBucketsInput{})
	if err != nil {
		log.Fatalf("s3.ListBuckets failed:%v", err)
	}
	for _, v := range output.Buckets {
		log.Println(*v.Name)
	}
}

```


### NodeJS (aws-sdk-v3)

https://github.com/aws/aws-sdk-js-v3

```ts:S3バケット一覧を取得する例
import { S3 } from "@aws-sdk/client-s3";

const s3 = new S3({
    endpoint: "http://localhost:4566",
});

(async () => {
    // バケット一覧を取得
    const output = await s3.listBuckets({});
    const bucketList = output?.Buckets?.map((bucket) => {
        return bucket.Name;
    });
    console.log(bucketList);
})().catch((err) => {
    console.error(err);
});
```


## 初期化スクリプト

コンテナ起動時に自動実行するスクリプトを実行できる

```yaml:docker-compose.yml
version: '3.8'

services:
  localstack:
    container_name: localstack
    image: localstack/localstack:0.13.3
    platform: linux/amd64
    environment:
      SERVICES: s3,dynamodb
      INIT_SCRIPTS_PATH: /init/scripts
      DEFAULT_REGION: 'ap-northeast-1'
    ports:
      - '4566:4566'
    volumes:
      - ./init/scripts:/init/scripts:ro
      - ./init/data:/init/data:rw
```

- 環境変数`INIT_SCRIPTS_PATH` でコンテナ起動時に自動的に実行されるスクリプトの配置ディレクトリを指定する
- この例ではコンテナ内の /init/scripts ディレクトリ指定し、ここにホスト側のディレクトリ ./init/scripts をマウント
- コマンドで使用するJSONやアップロードするデータを格納する ./init/data ディレクトリを /init/data にマウント

### 起動時にS3バケットを作成しファイルを配置する例

```sh:./init/scripts/xxx.sh
# S3バケット作成
awslocal s3 mb s3://backat-name

# S3バケットにファイルをPUT
awslocal s3 cp /data/hoge.txt s3://backet-name/hoge.txt
```

LocalStackコンテナ内のシェルで実行する場合
`awslocal` という `aws` コマンドのラッパを利用することで
エンドポイント指定などを省略できます

## 参考資料

https://github.com/localstack/localstack

https://docs.localstack.cloud/localstack/configuration/