GoでS3にファイル送信を行う処理とテスト環境を作成したのでメモ書きします。
テスト環境にはlocalstack
(Dockerで構築されたAWSの擬似環境)を利用します。
docker-compose.yml
で設定している環境変数(※)を本番環境用に設定することで、実際のAWSにS3ファイルを送信することが可能です。
※ S3_ENDPOINT
などGoコンテナで使用しているAWS関連の環境変数
gitに動作確認済みのコードをアップしています。
https://github.com/ruruyuki/localstack_s3_with_go
ディレクトリ構成
ディレクトリ構成は次のようになります。
# ディレクトリ構成
.
├── docker-compose.yml
├── docker
│ ├── golang
│ │ └── Dockerfile
│ └── localstack
│ └── s3.sh
├── go.mod
├── go.sum
└── go_app
├── main.go
└── sample.csv
sample.csv
は擬似S3に送信(put)する想定のファイルです。
ファイルの内容は何でも大丈夫です。
docker-compose.yml
docker-compose.yml
は次のように作成します。
version: '3.8'
services:
localstack:
image: localstack/localstack:0.13.3
platform: linux/amd64
container_name: localstack-container
environment:
SERVICES: 's3' # 使用するAWSサービスを指定。設定例: 's3,kms,secretsmanager,ssm,dynamodb,'
HOSTNAME_EXTERNAL: 'localstack' # 他のコンテナから呼び出される際のホスト名を指定。デフォルトは’localhost’
DATA_DIR: /tmp/localstack/data # S3に送信したデータを保存したりするディレクトリを指定
DEFAULT_REGION: 'ap-northeast-1' # AWSリージョンの設定
ports:
- '4566:4566'
volumes:
# docker-entrypoint-initaws.d に配置されたスクリプトは自動実行される
- ./docker/localstack:/docker-entrypoint-initaws.d:ro
golang:
image: golang:1.19.0-alpine3.15
build:
context: .
dockerfile: docker/golang/Dockerfile
container_name: golang-container
depends_on:
- localstack
environment:
# Go環境の設定
GOOS: linux
GOARCH: amd64
CGO_ENABLED: '0'
TZ: Asia/Tokyo
# localstackを使用するのに必要な設定
S3_ENDPOINT: 'http://localstack:4566' # localstackに接続するためのURLを設定
S3_BUCKET_NAME: 'sample-bucket' # 接続するバケット名を設定
AWS_DEFAULT_REGION: 'ap-northeast-1' # AWSリージョンの設定
AWS_ACCESS_KEY_ID: abcd # 任意の文字列を設定。空文字だとエラーになる
AWS_SECRET_ACCESS_KEY: abcd # 任意の文字列を設定。空文字だとエラーになる
ports:
- '4000:4000'
networks:
- default
volumes:
- .:/app:rw
working_dir: /app
tty: true
Dockerfile
golang
ディレクトリに配置するDockerfile
は次のように作成します。
FROM golang:1.19.0-alpine3.15
ENV CGO_ENABLED=0
RUN apk add git make tzdata
RUN go install golang.org/x/tools/cmd/goimports@latest \
&& go install honnef.co/go/tools/cmd/staticcheck@latest
s3.sh
localstack
ディレクトリに配置するs3.sh
は次のように作成します。
この処理はlocalstack
にsample-bucket
というバケットを作成するものです。
awslocal s3 mb s3://sample-bucket
awslocal s3 ls
main.go
goでS3にファイルをputする処理です。
殆どaws-sdk-go-v2
を利用した処理となります。
aws-sdk-go-v2
とは、GoでAWSサービスを使用するためのAPIやユーティリティを提供する公式SDKです。
package main
import (
"context"
"fmt"
"io"
"os"
"time"
"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"
)
type (
S3Config struct {
AwsDefaultRegion string
EndPoint string
BucketName string
}
S3Client struct {
clientCfg *S3Config
sdkCfg aws.Config
}
)
// 処理を開始するには、localstack_s3ディレクトリで下記コマンドを実行する
// docker compose exec golang go run ./go_app
func main() {
fmt.Println("処理開始")
// S3の設定を環境変数(docker-composeのenvironmentで設定したもの)から取得して設定する
cfg := S3Config{
AwsDefaultRegion: os.Getenv("AWS_DEFAULT_REGION"),
EndPoint: os.Getenv("S3_ENDPOINT"),
BucketName: os.Getenv("S3_BUCKET_NAME"),
}
// S3クライアントの作成
s3client := NewS3Client(&cfg)
// データ送信フォームの作成
api := s3.NewFromConfig(s3client.sdkCfg, func(options *s3.Options) {
options.UsePathStyle = true
})
// 送信するファイル名とファイルの配置先を設定
csvFileName := "sample.csv"
filePath := "/app/go_app"
// ファイルを開く
var csvFile io.ReadSeekCloser
csvFile, err := os.Open(filePath + "/" + csvFileName)
if err != nil {
fmt.Printf("FileOpenError: %s", err.Error())
return
}
// ファイルの保存先を設定
s3Location := fmt.Sprintf("%s/%s", "work", csvFileName)
// S3のタイムアウトの時間
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(3)*time.Second)
defer cancel()
// S3バケットにファイルを送信する
_, err = api.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(s3client.clientCfg.BucketName),
Key: aws.String(s3Location),
Body: csvFile,
})
if err != nil {
fmt.Printf("FileUploadFileError: %s", err.Error())
return
}
// S3バケットからファイルを取得したい場合に使用
// api.GetObject(ctx, &s3.GetObjectInput{
// Bucket: aws.String(s3client.clientCfg.BucketName),
// Key: aws.String(s3Location),
// })
fmt.Println("処理終了")
}
// S3クライアントの作成
func NewS3Client(cfg *S3Config) *S3Client {
loadOptions := []func(*config.LoadOptions) error{config.WithRegion(cfg.AwsDefaultRegion)}
if cfg.EndPoint != "" {
endpoint := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
URL: cfg.EndPoint,
}, nil
})
loadOptions = append(loadOptions, config.WithEndpointResolverWithOptions(endpoint))
}
sdkCfg, err := config.LoadDefaultConfig(context.TODO(), loadOptions...)
if err != nil {
panic(err)
}
return &S3Client{cfg, sdkCfg}
}
go.modとgo.sumの作成
docker-compose.yml
のあるディレクトリで次のようにgo.mod
ファイルを作成します。
module github.com/localstack_s3
go 1.19
require (
github.com/aws/aws-sdk-go-v2 v1.16.5
github.com/aws/aws-sdk-go-v2/config v1.15.10
github.com/aws/aws-sdk-go-v2/service/s3 v1.26.11
)
作成したら、go.mod
のあるディレクトリで次のコマンドを実行します。
go get github.com/localstack_s3/go_app
すると、go.sum
が作成されます。
動作検証
コードか書けたらコンテナを起動します。
docker-compose.yml
のあるディレクトリで次のコマンドを実行します。
docker compose up -d --build
コンテナの起動が成功したら、今度はGoコンテナにあるmain.goを実行します。
次のコマンドで実行できます。
docker compose exec golang go run ./go_app
実行後、エラーがでなければOKです。
最後にlocalstackのS3にファイルが送信できている確認します。
% aws s3 ls s3://sample-bucket/work/ --endpoint=http://localhost:4566
下記sample.csv
ファイルが存在すれば処理成功です。
2022-08-14 14:02:28 0 sample.csv