AWS SDK for Go v2を使ったS3のオブジェクトアップロードとダウンロードを使ってみる


## 概要

* まだRC版だけど、AWS SDK for Go v2を使ったS3のオブジェクトアップロードとダウンロードを使ってみた。
https://github.com/aws/aws-sdk-go-v2

* goを普段触らなけど練習で書いてみた。


## 前提条件

Goのバージョンがv1.15であること

```
$ go version
go version go1.15 linux/amd64
```

## 準備

```bash
go mod init awssdkapp

go get -u github.com/aws/aws-sdk-go-v2
go get -u github.com/aws/aws-sdk-go-v2/config
go get -u github.com/aws/aws-sdk-go-v2/feature/s3/manager
go get -u github.com/aws/aws-sdk-go-v2/service/s3
```

```
touch main.go
mkdir myclient
touch myclient/myclient.go
```

## S3に接続するクライアントのコード

シンプルにオブジェクトをアップロードしたり、ダウンロードする。

```myclient/myclient.go
package myclient

import (
	"context"
	"io"
	"log"
	"os"

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

type MyS3Client struct {
	downloader *manager.Downloader
	uploader   *manager.Uploader
	client     *s3.Client
}

func NewMyS3Client(cfg aws.Config) *MyS3Client {
	client := s3.NewFromConfig(cfg)
	downloader := manager.NewDownloader(client)
	uploader := manager.NewUploader(client)

	return &MyS3Client{
		downloader: downloader,
		uploader:   uploader,
		client:     client,
	}
}

// オブジェクトをダウンロードするメソッド
func (c *MyS3Client) UploadSingleObject(bucket, key string, reader io.Reader) {
	_, err := c.uploader.Upload(context.Background(), &s3.PutObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
		Body:   reader,
	})

	if err != nil {
		log.Fatal(err)
	}

	log.Println("upload successed")
}

// オブジェクトをダウンロードするメソッド
func (c *MyS3Client) DownloadSingleObject(bucket, key, filename string) {
	file, _ := os.Create(filename)
	defer file.Close()

	_, err := c.downloader.Download(context.Background(), file, &s3.GetObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	})

	if err != nil {
		log.Fatal(err)
	}

	log.Println("download successed")

}
```


```main.go
package main

import (
	"context"
	"flag"
	"log"
	"strings"

	"awsdkapp/myclient"

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

func main() {

	bucket := flag.String("bucket", "", "specify bucket name")
	key := flag.String("key", "", "specify key name ")
	flag.Parse()

	if *bucket == "" || *key == "" {
		log.Fatal("error invalid arugments")
	}

	// コンフィグの作成
	cfg, err := config.LoadDefaultConfig(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	client := myclient.NewMyS3Client(cfg)

	// コンテンツをアップロードする
	client.UploadSingleObject(*bucket, *key, strings.NewReader("hello, aws sdk"))

	// ファイルとしてダウンロードする
	client.DownloadSingleObject(*bucket, *key, "hello.txt")
}
```

実行してみると、アップロード/ダウンロードできるはず。

```bash
$ go run main.go -bucket the-chocomintkusoyaro-demo -key hoge
2020/12/30 23:05:32 upload successed
2020/12/30 23:05:32 download successed

$ ls hello.txt 
hello.txt

$ cat hello.txt 
hello, aws sdk
```

## 複数のオブジェクトをダウンロードする場合

`NewListObjectsV2Paginator`でページネータを生成してページ単位でオブジェクト群を取得して、オブジェクト一つ一つをダウンロード書き込みする。

```myclient/myclient.go
...

// 複数のオブジェクトをダウンロードする
func (c *MyS3Client) DownloadMultipleObjects(bucket, prefix, localDir string) {

	// ページネーターを作成する
	paginator := s3.NewListObjectsV2Paginator(c.client, &s3.ListObjectsV2Input{
		Bucket: aws.String(bucket),
		Prefix: aws.String(prefix),
	})

	for paginator.HasMorePages() {
		page, err := paginator.NextPage(context.Background())
		if err != nil {
			log.Fatal(err)
		}

		for _, obj := range page.Contents {
			if err != nil {
				log.Fatal(err)
			}

			filepath := filepath.Join(localDir, *obj.Key)

			c.DownloadSingleObject(bucket, *obj.Key, filepath)
		}
	}
	log.Println("all file downloaded")
}
```

```main.go
package main

import (
	"context"
	"flag"
	"log"

	"awsdkapp/myclient"

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

func main() {

	bucket := flag.String("bucket", "", "specify bucket name")
	prefix := flag.String("prefix", "", "specify prefix name")
	localDir := flag.String("localDir", "", "specify local dir ")
	flag.Parse()

	if *bucket == "" || *localDir == "" {
		log.Fatal("error invalid arugments")
	}

	// コンフィグの作成
	cfg, err := config.LoadDefaultConfig(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	client := myclient.NewMyS3Client(cfg)

	// オブジェクトをダウンロードする
	client.DownloadMultipleObjects(*bucket, *prefix, *localDir)
}
```

## 複数のオブジェクトをアップロードする

ドキュメントの[Example](https://aws.github.io/aws-sdk-go-v2/docs/sdk-utilities/s3/#upload-a-folder-to-amazon-s3)を真似てみた。

```myclient/myclient.go
...

// 複数のオブジェクトをまとめてアップロードする
func (c *MyS3Client) UploadMultipleObjects(bucket, prefix, localDir string) {
	fileWalker := make(chan string)

	// チャネルにローカルファイルパスを送信する
	go func() {
		err := filepath.Walk(localDir, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			if !info.IsDir() {
				// ファイルパスをチャネルに送る
				fileWalker <- path
			}

			return nil
		})

		if err != nil {
			log.Fatal(err)
		}

		
		// チャネルのクローズをお知らせるする
		close(fileWalker)
	}()

	// ファイルパスをチャネルから受信する
	for path := range fileWalker {
		rel, err := filepath.Rel(localDir, path)

		if err != nil {
			log.Println("unable to get relative path", path, err)
			continue
		}

		file, err := os.Open(path)
		if err != nil {
			log.Println("failed open file", path, err)
			continue
		}
		defer file.Close()

		key := filepath.Join(prefix, rel)

		// オブジェクトをダウンロードする
		c.UploadSingleObject(bucket, key, file)
	}
}
```


```main.go
package main

import (
	"context"
	"flag"
	"log"

	"awsdkapp/myclient"

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

func main() {

	bucket := flag.String("bucket", "", "specify bucket name")
	prefix := flag.String("prefix", "", "specify prefix name")
	localDir := flag.String("localDir", "", "specify local dir ")
	flag.Parse()

	if *bucket == "" || *localDir == "" {
		log.Fatal("error invalid arugments")
	}

	// コンフィグの作成
	cfg, err := config.LoadDefaultConfig(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	client := myclient.NewMyS3Client(cfg)

	// オブジェクトをまとめてアップロードする
	client.UploadMultipleObjects(*bucket, *prefix, *localDir)
}
```