2022.08.14  

【localstack】GoでS3にファイルを送信(put)する

Go,  AWS,  Docker    

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は次のように作成します。
この処理はlocalstacksample-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
コメント
現在コメントはありません。
コメントする
コメント入力

名前 (※ 必須)

メールアドレス (※ 必須 画面には表示されません)

送信