Go에서 S3 사용해보기

임태빈·2022년 1월 5일
1

go

목록 보기
7/13

안녕하세요.

이번 포스팅에서는 Go를 활용해 S3를 사용해보는 것을 공유해보려고 합니다.
라이브러리를 다운 받아야 하기에 다음 명령어들을 실행해주시면 됩니다:)

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

먼저 전체 코드 부터 공유드리겠습니다.

전체 코드

package main

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/aws/aws-sdk-go-v2/service/s3/types"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
)

type S3Info struct {
	AwsS3Region    string
	AwsAccessKey   string
	AwsSecretKey   string
	AwsProfileName string
	BucketName     string
	S3Client       *s3.Client
}

func (s *S3Info) SetS3ConfigByDefault() error {
	cfg, err := config.LoadDefaultConfig(context.TODO())
	if err != nil {
		log.Fatal(err)
		return errors.New(err.Error())
	}
	s.S3Client = s3.NewFromConfig(cfg)
	return nil
}

// profile Name을 활용해서 Client 생성
func (s *S3Info) SetS3ConfigByProfile() error {
	cfg, err := config.LoadDefaultConfig(context.TODO(),
		config.WithRegion(s.AwsS3Region),
		config.WithSharedConfigProfile(s.AwsProfileName))
	if err != nil {
		log.Fatal(err)
		return errors.New(err.Error())
	}
	s.S3Client = s3.NewFromConfig(cfg)
	return nil
}

//key를 활용해서 Client 생성
func (s *S3Info) SetS3ConfigByKey() error {
	creds := credentials.NewStaticCredentialsProvider(s.AwsAccessKey, s.AwsSecretKey, "")
	cfg, err := config.LoadDefaultConfig(context.TODO(), 		config.WithCredentialsProvider(creds), 
    config.WithRegion(s.AwsS3Region),
)
	if err != nil {
		log.Printf("error: %v", err)
		panic(err)
		return errors.New(err.Error())
	}
	s.S3Client = s3.NewFromConfig(cfg)
	return nil
}

// 서버를 통해 파일을 받아왔을 때 사용
func (s *S3Info) UploadFile(file io.Reader, filename, preFix string) *manager.UploadOutput {
	uploader := manager.NewUploader(s.S3Client)
	result, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{
		Bucket: aws.String(s.BucketName),
		Key:    aws.String(filename),
		Body:   file,
	})
	if err != nil {
		log.Fatal(err)
		panic(err)
	}
	return result
}

//파일이름을 통해 파일을 불러와 서버에 업로드
func (s *S3Info) UploadFileByFileName(filename, preFix string) *manager.UploadOutput {
	file, err := ioutil.ReadFile(filename)
	uploader := manager.NewUploader(s.S3Client)
	result, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{
		Bucket: aws.String(s.BucketName),
		Key:    aws.String(filename),
		Body:   bytes.NewReader(file),
	})
	if err != nil {
		log.Fatal(err)
		panic(err)
	}
	return result
}

//원하는 파일을 다운로드 받을 때 사용
func (s *S3Info) DownloadFile(targetDirectory, key string) error {
	// Create the directories in the path
	splitKeyArr := strings.Split(key, "/")
	file := filepath.Join(targetDirectory, splitKeyArr[len(splitKeyArr)-1])
	if err := os.MkdirAll(filepath.Dir(file), 0775); err != nil {
		return err
	}

	// Set up the local file
	fd, err := os.Create(file)
	if err != nil {
		return err
	}

	defer fd.Close()

	downloader := manager.NewDownloader(s.S3Client)
	_, err = downloader.Download(context.TODO(), fd,
		&s3.GetObjectInput{
			Bucket: &s.BucketName,
			Key:    &key,
		})
	return err
}

//버킷안에 있는 Objects 확인할때 사용
func (s *S3Info) GetItems(prefix string) {
	paginator := s3.NewListObjectsV2Paginator(s.S3Client, &s3.ListObjectsV2Input{
		Bucket: &s.BucketName,
		Prefix: aws.String(prefix),
	})
	for paginator.HasMorePages() {
		page, err := paginator.NextPage(context.TODO())
		if err != nil {
			log.Fatalln("error:", err)
		}
		for _, obj := range page.Contents {

			fmt.Println(aws.ToString(obj.Key))
		}
	}
}

// 버킷 리스트 확인
func (s *S3Info) GetBucketList() {
	output, err := s.S3Client.ListBuckets(context.TODO(), &s3.ListBucketsInput{})
	if err != nil {
		panic(err)
	}
	for _, bucket := range output.Buckets {
		fmt.Println(*bucket.Name)
	}
}

// 버킷 생성하기
func (s *S3Info) CreateBucket(bucketName string, region types.BucketLocationConstraint) {
	output, err := s.S3Client.CreateBucket(context.TODO(), &s3.CreateBucketInput{
		Bucket: &bucketName,
		CreateBucketConfiguration: &types.CreateBucketConfiguration{
			LocationConstraint: region,
		},
	})

	if err != nil {
		panic(err)
	}
	fmt.Println(output.Location)
}

위 코드들을 사용하시면 편하게 파일을 업로드하거나 다운로드가 가능해집니다:)
코드를 구현하면서 자료가 별로 없어서 파이썬 코드를 보면서 필요한 게 무엇이 있는지 하나씩 확인하는 과정이
정말 힘들었습니다. ㅠㅠ
자세한 코드 설명은 밑에서 진행하도록 하겠습니다!!

코드 설명

  1. S3Info 생성
type S3Info struct {
	AwsS3Region    string
	AwsAccessKey   string
	AwsSecretKey   string
	AwsProfileName string
	BucketName     string
	S3Client       *s3.Client
}

S3Info 구조체는 Aws Config설정을 위한 것들을 저장하기 위해 만들었습니다.
이를 활용해서 쉽게 함수들을 사용할 수 있게 만들기 위해서 작성했습니다.

  1. S3 Client 생성하기
func (s *S3Info) SetS3ConfigByDefault() error {
	cfg, err := config.LoadDefaultConfig(context.TODO())
	if err != nil {
		log.Fatal(err)
		return errors.New(err.Error())
	}
	s.S3Client = s3.NewFromConfig(cfg)
	return nil
}

// profile Name을 활용해서 Client 생성
func (s *S3Info) SetS3ConfigByProfile() error {
	cfg, err := config.LoadDefaultConfig(context.TODO(),
		config.WithRegion(s.AwsS3Region),
		config.WithSharedConfigProfile(s.AwsProfileName))
	if err != nil {
		log.Fatal(err)
		return errors.New(err.Error())
	}
	s.S3Client = s3.NewFromConfig(cfg)
	return nil
}

//key를 활용해서 Client 생성
func (s *S3Info) SetS3ConfigByKey() error {
	creds := credentials.NewStaticCredentialsProvider(s.AwsAccessKey, s.AwsSecretKey, "")
	cfg, err := config.LoadDefaultConfig(context.TODO(), 		config.WithCredentialsProvider(creds),
    config.WithRegion(s.AwsS3Region),
 )
	if err != nil {
		log.Printf("error: %v", err)
		panic(err)
		return errors.New(err.Error())
	}
	s.S3Client = s3.NewFromConfig(cfg)
	return nil
}

위 코드들은 S3 client를 생성하는 함수들입니다.
Default함수에 경우 ec2에 역할을 부여했을 때 사용하면 될거라 생각이 들어 구현했습니다.
SetS3ConfigByProfile함수는 awscli에서 profile을 만들었을 경우 사용하고자 구현했습니다. .aws폴더에 보시면 credentials에 [default]라고 있는 것을 말하며 이를 사용하면 accesskey와 secretkey를 사용하지 않으셔도 됩니다.
SetS3ConfigByKey함수는 accesskey와 secretkey를 사용해서 client를 생성하는 함수입니다.
이 함수들을 통해 만들어진 client들은 s3를 사용하는데 중요한 것들입니다.
파이썬으로 구현했을 때도 client를 활용하는 것을 보실 수 있습니다.
그러므로 꼭 생성하셔야 하는 함수입니다.

  1. File Upload 하기
// 서버를 통해 파일을 받아왔을 때 사용
func (s *S3Info) UploadFile(file io.Reader, filename, preFix string) *manager.UploadOutput {
	uploader := manager.NewUploader(s.S3Client)
	result, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{
		Bucket: aws.String(s.BucketName),
		Key:    aws.String(filename),
		Body:   file,
	})
	if err != nil {
		log.Fatal(err)
		panic(err)
	}
	return result
}

//파일이름을 통해 파일을 불러와 서버에 업로드
func (s *S3Info) UploadFileByFileName(filename, preFix string) *manager.UploadOutput {
	file, err := ioutil.ReadFile(filename)
	uploader := manager.NewUploader(s.S3Client)
	result, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{
		Bucket: aws.String(s.BucketName),
		Key:    aws.String(filename),
		Body:   bytes.NewReader(file),
	})
	if err != nil {
		log.Fatal(err)
		panic(err)
	}
	return result
}

File을 upload하기 위한 코드들입니다. 두 함수의 차이점은 파일을 받아서 사용하는지에 차이입니다. 첫번째 함수에 경우 서버에서 파일을 받았을때
를 고려해서 만든 코드입니다. 두 번째 함수는 로컬에서 가볍게 자신의 파일을 올리기 위한 코드입니다.

  1. File Download 하기
//원하는 파일을 다운로드 받을 때 사용
func (s *S3Info) DownloadFile(targetDirectory, key string) error {
	// Create the directories in the path
	splitKeyArr := strings.Split(key, "/")
	file := filepath.Join(targetDirectory, splitKeyArr[len(splitKeyArr)-1])
	if err := os.MkdirAll(filepath.Dir(file), 0775); err != nil {
		return err
	}

	// Set up the local file
	fd, err := os.Create(file)
	if err != nil {
		return err
	}

	defer fd.Close()

	downloader := manager.NewDownloader(s.S3Client)
	_, err = downloader.Download(context.TODO(), fd,
		&s3.GetObjectInput{
			Bucket: &s.BucketName,
			Key:    &key,
		})
	return err
}

이 함수는 파일을 다운로드 하는 함수입니다. 여기서 저장할 곳과 다운로드 받을 파일을 인자로 받습니다.
저장할 곳에 경로를 만들어서 저장하는 코드를 구현했습니다.
split을 한 이유는 filepath.join을 하고 mkdirall을 하면 경로상에 폴더까지 생성이 되어서 보기가 좋지 않아 하게 되었습니다.
다른 부분은 Download를 사용했다 정도인것 같습니다.

  1. File List 불러오기
//버킷안에 있는 Objects 확인할때 사용
func (s *S3Info) GetItems(prefix string) {
	paginator := s3.NewListObjectsV2Paginator(s.S3Client, &s3.ListObjectsV2Input{
		Bucket: &s.BucketName,
		Prefix: aws.String(prefix),
	})
	for paginator.HasMorePages() {
		page, err := paginator.NextPage(context.TODO())
		if err != nil {
			log.Fatalln("error:", err)
		}
		for _, obj := range page.Contents {

			fmt.Println(aws.ToString(obj.Key))
		}
	}
}

버킷에 어떤 것들이 존재하는지 찾아보고 싶을 때 사용하는 함수입니다. 여기서 눈여겨 봐야 하는 부분은 Paginator를 사용한 것입니다.
s3에 경우 정확하게 기억이 안나지만 300개 정도를 불러올 수 있고 그 이상의 데이터를 불러올 수 없어서 Paginator를 사용해야만이 데이터를 불러올 수 있는 것으로 알고 있습니다. 저는 그러한 이유 때문에 Paginator를 사용하여 코드를 구현했습니다.
지금 코드에 경우 Print만 하고 있지만 사용하실 때 return을 하셔도 좋을거 같습니다.
6. Bucket List 조회하기

// 버킷 리스트 확인
func (s *S3Info) GetBucketList() {
	output, err := s.S3Client.ListBuckets(context.TODO(), &s3.ListBucketsInput{})
	if err != nil {
		panic(err)
	}
	for _, bucket := range output.Buckets {
		fmt.Println(*bucket.Name)
	}
}

이 함수에 경우 관련된 코드가 없어서 파이썬을 보고 함수들 찾아 만들게 되었습니다.ㅠㅠ
이 함수를 사용하시면 생성된 버킷 리스트들을 보실 수 있습니다.
다만, 권한이 없다면 에러가 발생합니다!!

  1. Bucket 생성하기
// 버킷 생성하기
func (s *S3Info) CreateBucket(bucketName string, region types.BucketLocationConstraint) {
	output, err := s.S3Client.CreateBucket(context.TODO(), &s3.CreateBucketInput{
		Bucket: &bucketName,
		CreateBucketConfiguration: &types.CreateBucketConfiguration{
			LocationConstraint: region,
		},
	})

	if err != nil {
		panic(err)
	}
	fmt.Println(output.Location)
}

이 함수 또한 구현한걸 못찾아서 만들었습니다. 구현을 하실때 지역을 받는 부분이 있어 이점 유의해서 다른 분들은 이점 참고 하셔서 만드시면 좋겠습니다.

결론

자료가 많이 없어서 애먹은 부분도 있지만 파이썬에 있는 코드를 Go로 변환하는 것을 해보니 재밌다는 생각이 들었습니다.
Delete하는 함수에 경우 필요할 수도 있지만 데이터는 삭제 하지 않는게 좋을거 같아 코드 구현은 안했습니다:)
혹시나 하시는 분이 계시다면 댓글에 남겨주시면 감사드리겠습니다!!
추가로 궁금하시거나 잘못된 부분이 있다면 언제나 댓글 달아주시면 감사드립니다~~

깃허브에도 이 관련된 코드가 있으니 관심있으신 분들은 여길 보셔도 좋을거 같습니다.

이번 포스팅은 이걸로 마무리하겠습니다
갈비만두를 좋아하는 개발자 임태빈이었습니다

profile
golang과 서버 개발을 하고 있는 개발자입니다.

1개의 댓글

comment-user-thumbnail
2022년 2월 19일

안녕하세요. 궁금한 게 하나 있는데요.. 실질적으로 go 에서 s3로 설정을 연결하는 부분은 어떤 부분인가요? 구글 공식 문서에도 나와 있지 않고, .aws폴더에 보시면 credentials에 [default]라고 있는 것을 말하며 이를 사용하면 accesskey와 secretkey를 사용하지 않으셔도 됩니다. 라고 하셨는데, .aws 폴더는 어디를 말 하는건지요? 제가 aws 문맹이라 드리는 질문 일 수도 있습니다만, 너무 답답해서 질문 드립니다..

답글 달기