Goを使ってS3にファイルを一括アップロードする

Amazon S3

Amazon S3は、ウェブ上のどこからでもファイルを保存し、取り出すことができるAmazonのストレージです。Amazonが提供する、拡張性、信頼性、高速性、安価性に優れたストレージになります。ファイルの保存と取り出しのための使いやすい開発キットを提供されています。

今回は、S3のバケットgolangを使って、大量のファイルをアップロードしてみたいと思います。S3にファイルをアップロードするために、Go AWS SDKを使用します。(AWS SDK for Goは、Go 1.15以降のバージョンが必要です)

AWS Golang SDKをダウンロードします。

$ go get github.com/aws/aws-sdk-go/aws

main.goファイルを作成し、必要なパッケージをインポートして、S3バケットにファイルをアップロードします。

package main
import (
    "bytes"
    "fmt"
    "log"
    "net/http"
    "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"
)

Session構造体を使うので、このレシーバをメインのアップロード関数に渡します。

type Session struct {
    S3Session *session.Session
}

AWSセッションをセットアップする

S3 REGIONとSECRET ID、SECRET KEYを使って設定を行い、複数のファイルをS3にアップロードするためのAWSセッションを1つ作成します。そして、upload()メソッドを呼び出し、AWSセッションインスタンスとファイルの詳細を渡し、S3サーバーにファイルをアップロードします。

func main() {
    paths := []string{"", ""}
    credential := credentials.NewStaticCredentials(
        os.Getenv("SECRET_ID"),
        os.Getenv("SECRET_KEY"),
        "",
    )
    awsConfig := aws.Config{
        Region:      aws.String(os.Getenv("REGION")),
        Credentials: credential,
    }
    s, err := session.NewSession(&awsConfig)
    if err != nil {
        log.Println("failed to create S3 session:", err.Error())
        return
    }
    se := Session{s}
    err = se.upload(paths)
    if err != nil {
        log.Println(err.Error())
        return
    }
}

まずos.Getenv()を使ってすべての設定情報を読み込み、aws sdkの関数credentials.NewStaticCredentials()に渡しています。またaws.Config構造体を呼び出してその中のcredential変数を渡し、環境変数からREGION値も読み込んでいます。

aws.Config型のawsConfigをsession.NewSession()に渡し、awsのs3セッションを返すことで、1つのセッションで複数のファイルをアップロードできるようにしています。

Session構造体にセッションを渡し、その後、uploadメソッドを呼び出して、アップロードする必要があるすべてのファイルのパスとセッション情報を渡しています。

S3にファイルを一括アップロードするためのアップロードメソッドを作成する

S3にファイルをアップロードするためのメソッドupload()を作成します。ファイルを1つずつ開いてバッファに格納し、S3からPutObject()メソッドを使用してS3にファイルを配置します。ファイルをアップロードする際に、PutObjectInputにオプションを指定します。s3.PutObjectInput構造体のServerSideEncryptionオプションを使用して、ファイルのAES256暗号化を有効にします。

func (s Session) upload(paths []string) error {
    for _, path := range paths {
        upFile, err := os.Open(path)
        if err != nil {
            log.Printf("failed %s, error: %v", path, err.Error())
            continue
        }
        defer upFile.Close()
        upFileInfo, err := upFile.Stat()
        if err != nil {
            log.Printf("failed to get stat %s, error: %v", path, err.Error())
            continue
        }
        var fileSize int64 = upFileInfo.Size()
        fileBuffer := make([]byte, fileSize)
        upFile.Read(fileBuffer)
        // uploading
        _, err = s3.New(s.S3Session).PutObject(&s3.PutObjectInput{
            Bucket:               aws.String(os.Getenv("BUCKET_NAME")),
            Key:                  aws.String(path),
            ACL:                  aws.String("public-read"), 
            Body:                 bytes.NewReader(fileBuffer),
            ContentLength:        aws.Int64(int64(fileSize)),
            ContentType:          aws.String(http.DetectContentType(fileBuffer)),
            ContentDisposition:   aws.String("attachment"),
            ServerSideEncryption: aws.String("AES256"),
            StorageClass:         aws.String("INTELLIGENT_TIERING"),
        })
        if err != nil {
            log.Printf("failed to upload %s, error: %v", path, err.Error())
            continue
        }
        url := "https://%s.s3-%s.amazonaws.com/%s"
        url = fmt.Sprintf(url, os.Getenv("BUCKET_NAME"), os.Getenv("REGION"), path)
        fmt.Printf("Uploaded File Url %s\n", url)
    }
    return nil
}

Sessionをレシーバとするupload()というメソッドを作成しました。Session構造体はmain関数で作成したawsセッションで、upload()メソッドにはアップロードする必要があるすべてのファイルも渡しています。

upload()メソッドでは、path変数にループをかけ、ループ内でまずos.Openを呼び出し、ファイルを開いた後にStatメソッドを呼び出し、aws s3 sdkメソッドに渡す必要がある、開いたファイルに関するすべての情報を取得しています。

s3.PutObjectInputに渡す情報は、パス、ファイルサイズ、ファイルバッファのみで、s3バケットにファイルをアップロードするための有効なhttpリクエストを作成するために使用することが可能となっています。