potpro (ぽとぷろ)
Full-stuck engineer(Not Full-stack)
JS/PHP/Go/Docker/Nginxなど。技術または趣味寄りの発信ブログです。
全 85 記事Go言語(golang)でAmazon S3から複数ファイルをダウンロードする
Go言語(golang)でAmazon S3から複数ファイルをダウンロードする
Go言語(golang)でAmazon S3へ複数ファイルをダウンロードする。
ここで重要なのはまとめてダウンロード。
単体のダウンロードが出来るんだから、それをforなりで回せばいいじゃないという話ではあるが、毎回S3 Getが飛ぶしパフォーマンス的にも良くない。1回で複数ダウンロードしたい。
割と簡単な感じがあるんですけど、実際調べても全然国内の記事が見つからない。
それで探したところ、ドキュメント上にはちゃんと書いてあった。でもやっぱりというか説明不足感はある感じではあったので、記事を書く。
一番乗りの記事ということで。単に出てこなかっただけかもしれないけど。
aws-sdk-goをインストール
今回使用しているものはAWS公式のaws-sdk-goです。
これはAWS SDKでS3以外にも使用可能。s3を操作するときは基本的にs3managerを使用すればOK。
単体ダウンロードの場合
s3managerのDownload
関数を使用することで行える。
package main
import (
"fmt"
"os"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
func main() {
creds := credentials.NewStaticCredentials("AccessKey", "secret", "")
sess := session.Must(session.NewSession(&aws.Config{
Credentials: creds,
Region: aws.String("ap-northeast-1"),
}))
filename := "test.txt"
// Create a downloader with the session and default options
svc := s3manager.NewDownloader(sess)
// Create a file to write the S3 Object contents to.
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println(fmt.Errorf("failed to create file %q, %v", filename, err))
return
}
defer f.Close()
// Write the contents of S3 Object to the file
n, err := svc.Download(f, &s3.GetObjectInput{
Bucket: aws.String("bucketName"),
Key: aws.String("/test.txt"),
})
if err != nil {
fmt.Println(fmt.Errorf("failed to download file, %v", err))
f.Close()
os.Remove(fileName)
return
}
fmt.Printf("file downloaded, %d bytes\n", n)
}
単体の場合はこんな感じ。ダウンロードはそのままファイルに出力してくれないので、Writerを使用して書き込む必要がある。なので、ファイル出力せず使用することも可能となっている。
でも一度createする必要があるということでエラーだと空ファイルが出来ちゃったりする。 なのでエラー時os.Removeで削除するコードを入れてます。
複数ダウンロードの場合
s3managerのDownloadWithIterator
関数を使用することで行える。
これを使用する場合はs3manager.BatchDownloadObject
のスライスを作成し、イテレータにセットすることで動作する。
関数とかはいろいろ略。
creds := credentials.NewStaticCredentials("AccessKey", "secret", "")
sess := session.Must(session.NewSession(&aws.Config{
Credentials: creds,
Region: aws.String("ap-northeast-1"),
}))
svc := s3manager.NewDownloader(sess)
objects := []s3manager.BatchDownloadObject{}
fileNames := []string{
"test1.txt",
"test2.txt",
}
for _, fileName := range files {
file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println(err)
}
defer file.Close()
objects = append(objects, s3manager.BatchDownloadObject{
Object: &s3.GetObjectInput{
Bucket: aws.String("bucketName"),
Key: aws.String(fileName),
},
Writer: file,
})
}
iter := &s3manager.DownloadObjectsIterator{Objects: objects}
err := svc.DownloadWithIterator(aws.BackgroundContext(), iter)
if err != nil {
fmt.Println(err)
}
こんな感じ。DownloadObjectsIteratorにダウンロードしたいものを全て書き込んだ後にダウンロードする。
エラー処理
この場合、失敗した時のエラーはどうなるのか?という話なのだが・・・
DownloadWithIteratorのエラーは通常のerrorなので、全てのファイルのエラーが固まった状態で帰ってくる。
つまり、100ファイルをダウンロードして、1ファイルがエラーになってもerrが返ってきてしまう。
なので1ファイルごとのエラーを取得できないか、という話になってくるんだけど、実はできる。
DownloadObjectにAfterという関数が用意されていて、これが処理後にエラーがあれば格納されるっぽい。
エラーが無ければ何も入らない。
# 処理後
svc.DownloadWithIterator(aws.BackgroundContext(), iter)
if iter.Next(){
if iter.DownloadObject().After(){
fmt.Println(iter.DownloadObject().After())
}
}
また、iter.DownloadObject().Object
にKeyやWriterの情報が入っているので、ここを使うことで1つずつのエラーの処理が出来る。
しかし、個別ダウンロードでやった"エラー時は削除"の処理は難しかった・・・というかiter.DownloadObject().Objectにfilenameが存在しなくてどうしよう・・・
ということで、処理が終わった後に全部見て、空のファイルを削除するという処理を入れた。
// emptyFileDelete 空のファイルを削除
func emptyFileDelete(dir string) {
files, _ := ioutil.ReadDir(dir)
for _, file := range files {
if !file.IsDir() && file.Size() == 0 {
os.Remove(filepath.Join(dir, file.Name()))
}
}
return
}
しかしこれだとS3に置いてあるファイルが空だと消されちゃうんじゃ・・・という気もしてきた。難しいですね・・・。
やっぱり普通よりエラー処理がかなり難しくなるので、本当に大量のファイルを取得するバッチ処理とかじゃなければ1ファイルずつ処理でいいかもしれない。
以上、今回は小ネタな感じです。