golang s3 download to buffer using s3manager.downloader - go

I'm using the Amazon s3 SDK to download files like below:
file, err := os.Create("/tmp/download_file")
downloader := s3manager.NewDownloader(session.New(&aws.Config{
Region: aws.String("us-west-2")}))
numBytes, err := downloader.Download(file,
&s3.GetObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(fileName),
})
It downloads to a file.
How do I get the download content into a []byte slice (buffer) directly.
I tried something like:
var tBuf bytes.Buffer
tBufIo := bufio.NewWriter(&tBuf)
instead of "file". But I get an error for io.WriterAt interface
cannot use *tBufIo (type bufio.Writer) as type io.WriterAt in argument to downloader.Download

Found it from the link
https://groups.google.com/forum/#!topic/Golang-Nuts/4z8rcWEZ8Os
buff := &aws.WriteAtBuffer{}
downloader := s3manager.NewDownloader(session.New(&aws.Config{
Region: aws.String(S3_Region)}))
numBytes, err := downloader.Download(buff,....
data := buff.Bytes() // now data is my []byte array
Works and fits the need.
See also: https://docs.aws.amazon.com/sdk-for-go/api/aws/#WriteAtBuffer

If you are running AWS SDK v2 then you can create a WriterAt like this
import (
"context"
"errors"
"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/service/s3"
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
)
func DownloadS3File(objectKey string, bucket string, s3Client *s3.Client) ([]byte, error) {
buffer := manager.NewWriteAtBuffer([]byte{})
downloader := manager.NewDownloader(s3Client)
numBytes, err := downloader.Download(context.TODO(), buffer, &s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(objectKey),
})
if err != nil {
return nil, err
}
if numBytes < 1 {
return nil, errors.New("zero bytes written to memory")
}
return buffer.Bytes(), nil
}

Related

How can I use the AWS SDK v2 for Go with DigitalOcean Spaces?

I'm trying to use the AWS v2 SDK for Go to list all objects in a given bucket on DigitalOcean Spaces. Their documentation gives examples of how to use the v1 SDK to do this, but my app uses v2. I know I could technically use both, but I'd rather not if possible.
Here's what I've got so far:
package main
import (
"context"
"fmt"
"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"
)
func main() {
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
URL: "https://sfo2.digitaloceanspaces.com",
}, nil
})
cfg, err := config.LoadDefaultConfig(
context.TODO(),
config.WithRegion("us-east-1"),
config.WithEndpointResolverWithOptions(customResolver),
config.WithCredentialsProvider(aws.AnonymousCredentials{}),
)
if err != nil {
fmt.Println(err)
}
s3Client := s3.NewFromConfig(cfg)
var continuationToken *string
continuationToken = nil
for {
output, err := s3Client.ListObjectsV2(context.TODO(), &s3.ListObjectsV2Input{
Bucket: aws.String("stats"),
ContinuationToken: continuationToken},
)
if err != nil {
fmt.Println(err)
}
for _, obj := range output.Contents {
fmt.Println(obj)
}
if output.IsTruncated == false {
break
}
continuationToken = output.ContinuationToken
}
}
This is the error I'm getting:
operation error S3: ListObjectsV2, https response error StatusCode: 400, RequestID: tx0000000000000051339d4-00620701db-2174fe1c-sfo2a, HostID: 2174fe1c-sfo2a-sfo, api error InvalidArgument: UnknownError
The error seems to indicate there's something wrong with my request but I don't know what.
For pagination i think you need to do it via a pagination function
like this
// Create the Paginator for the ListObjectsV2 operation.
p := s3.NewListObjectsV2Paginator(client, params, func(o *s3.ListObjectsV2PaginatorOptions) {
if v := int32(maxKeys); v != 0 {
o.Limit = v
}
})
Here's a fully working example I'm using to read from a digital ocean spaces bucket
package s3
import (
"context"
"os"
"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"
)
func read(ctx context.Context) error {
// Define the parameters for the session you want to create.
spacesKey := os.Getenv("SPACES_KEY")
spacesSecret := os.Getenv("SPACES_SECRET")
creds := credentials.NewStaticCredentialsProvider(spacesKey, spacesSecret, "")
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
URL: "https://sfo3.digitaloceanspaces.com",
}, nil
})
cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion("us-east-1"),
config.WithCredentialsProvider(creds),
config.WithEndpointResolverWithOptions(customResolver))
if err != nil {
return err
}
// Create an Amazon S3 service client
awsS3Client := s3.NewFromConfig(cfg)
input := &s3.GetObjectInput{
Bucket: aws.String("zeus-fyi"),
Key: aws.String("test.txt"),
}
downloader := manager.NewDownloader(awsS3Client)
newFile, err := os.Create("./local-test.txt")
if err != nil {
return err
}
defer newFile.Close()
_, err = downloader.Download(ctx, newFile, input)
if err != nil {
return err
}
return err
}

How to pass variadic functions into another function

i am working with aws-sdk-v2 and I want to make a minimum working example using "secretsmanager" service.
I am trying to follow the steps in this similiar example which is using "kms" service.
here is my script:
package main
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
)
func main() {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Printf("error: %v", err)
return
}
client := imds.NewFromConfig(cfg)
region, err := client.GetRegion(context.TODO(), &imds.GetRegionInput{})
if err != nil {
log.Printf("Unable to retrieve the region from the EC2 instance %v\n", err)
}
fmt.Printf(region.Region)
svc := secretsmanager.NewFromConfig(cfg)
input := &secretsmanager.CreateSecretInput{Name: aws.String("test")}
opts := &secretsmanager.Options{Region: region.Region}
result, err := svc.CreateSecret(context.TODO(), input, opts)
if err != nil {
fmt.Println(err)
}
fmt.Println(result)
}
error:
./main.go:38:46: cannot use opts (type *secretsmanager.Options) as type func(*secretsmanager.Options) in argument to svc.CreateSecret
so the error is obviously in those line:
opts := &secretsmanager.Options{Region: region.Region}
result, err := svc.CreateSecret(context.TODO(), input, opts)
from the documentation, the function CreateSecret takes these input types:
func (c *Client) CreateSecret(ctx context.Context, params *CreateSecretInput, optFns ...func(*Options)) (*CreateSecretOutput, error)
I can't find out how can I create this ...func(*Options) part in my context. Can someone please help me with this part?
I figured it out:
opts := func(o *secretsmanager.Options) {
o.Region = region.Region
}
result, err := svc.CreateSecret(context.TODO(), input, opts)

Reading files from AWS S3 in Golang

I am trying to deploy a golang code on Heroku. My code needs a text file as input and I need to fetch this text file from S3 bucket. My go-code takes the filename as input, Can someone provide a code snippet for reading a file from S3 and storing its contents into a file?
My GOlang code-
func getDomains(path string) (lines []string, Error error) {
file, err := os.Open(path)
if err != nil {
log.Fatalln(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
return lines, scanner.Err()
}
func Process(w http.ResponseWriter, r *http.Request) {
urls := make(chan *Http, Threads*10)
list, err := getDomains("**NEED A TEXT FILE FROM S3 HERE as an argument**")
if err != nil {
log.Fatalln(err)
}
var wg sync.WaitGroup
for i := 0; i < Threads; i++ {
wg.Add(1)
go func() {
for url := range urls {
url.DNS()
}
wg.Done()
}()
}
for i := 0; i < len(list); i++ {
Progress := fmt.Sprintln(w, len(list))
urls <- &Http{Url: list[i], Num: Progress}
}
close(urls)
wg.Wait()
fmt.Printf("\r%s", strings.Repeat(" ", 100))
fmt.Fprintln(w, "\rTask completed.\n")
}
Can someone suggest a good library for reading the file from S3 into a text file? I cannot download the file from S3 because I have to deploy it on Heroku.
A code snippet for example will be highly appreciated!
The code snippet below should work (given that you have installed the proper dependencies):
package main
import (
"github.com/aws/aws-sdk-go/aws"
"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"
"fmt"
"log"
"os"
)
func main() {
// NOTE: you need to store your AWS credentials in ~/.aws/credentials
// 1) Define your bucket and item names
bucket := "<YOUR_BUCKET_NAME>"
item := "<YOUR_ITEM_NAME>"
// 2) Create an AWS session
sess, _ := session.NewSession(&aws.Config{
Region: aws.String("us-west-2")},
)
// 3) Create a new AWS S3 downloader
downloader := s3manager.NewDownloader(sess)
// 4) Download the item from the bucket. If an error occurs, log it and exit. Otherwise, notify the user that the download succeeded.
file, err := os.Create(item)
numBytes, err := downloader.Download(file,
&s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(item),
})
if err != nil {
log.Fatalf("Unable to download item %q, %v", item, err)
}
fmt.Println("Downloaded", file.Name(), numBytes, "bytes")
}
For more details you can check the AWS Go SDK and the Github Example
Using current stable AWS lib for go:
sess := session.Must(session.NewSession(&aws.Config{
....
}))
svc := s3.New(sess)
rawObject, err := svc.GetObject(
&s3.GetObjectInput{
Bucket: aws.String("toto"),
Key: aws.String("toto.txt"),
})
buf := new(bytes.Buffer)
buf.ReadFrom(rawObject.Body)
myFileContentAsString := buf.String()
Here is a function for getting an object using V2 of the SDK (adapted from examples in https://github.com/aws/aws-sdk-go-v2):
Note: No Error handling - demo code only.
package s3demo
import (
"os"
"context"
"fmt"
"io/ioutil"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/awserr"
"github.com/aws/aws-sdk-go-v2/aws/external"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func GetObjectWithV2SDKDemo() {
bucket := "YOUR_BUCKET"
key := "YOUR_OBJECT_KEY"
fileName := "YOUR_FILE_PATH"
// may need AWS_PROFILE and AWS_REGION populated as environment variables
cfg, err := external.LoadDefaultAWSConfig()
if err != nil {
panic("failed to load config, " + err.Error())
}
svc := s3.New(cfg)
ctx := context.Background()
req := svc.GetObjectRequest(&s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})
resp, err := req.Send(ctx)
if err != nil {
panic(err)
}
s3objectBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
// create file
f, err := os.Create(fileName)
defer f.Close()
if err != nil {
panic(err)
}
bytesWritten, err := f.Write(s3objectBytes)
if err != nil {
panic(err)
}
fmt.Printf("Fetched %d bytes for S3Object\n", bytesWritten)
fmt.Printf("successfully downloaded data from %s/%s\n to file %s", bucket, key, fileName)
}

How to upload file to amazon s3 using gin framework

I am trying to upload a file to Amazon S3 Using gin framework of Go. Since aws-sdc requires to read the file i need to open file using os.open('filename').But since i am getting the file from "formFile" I don't have the path of the file to open, so os.Open() is giving error
The system cannot find the file specified.
My approach is as follows
package controllers
import (
"bytes"
"log"
"net/http"
"os"
"github.com/gin-gonic/gin"
"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"
)
const (
S3_REGION = "MY REGION"
S3_BUCKET = "<MY BUCKET>"
)
func UploadDocument(c *gin.Context) {
var Buf bytes.Buffer
file, _ := c.FormFile("file")
creds := credentials.NewSharedCredentials("", "default")
s, err := session.NewSession(&aws.Config{
Region: aws.String(S3_REGION),
Credentials: creds,
})
if err != nil {
log.Fatal(err)
}
err = AddFilesToS3(s, file.fileName)
if err != nil {
log.Fatal(err)
}
}
func AddFilesToS3(s *session.Session, fileDir string) error {
file, err := os.Open(fileDir)
if err != nil {
return err
}
defer file.Close()
fileInfo, _ := file.Stat()
var size int64 = fileInfo.Size()
buffer := make([]byte, size)
file.Read(buffer)
_, err = s3.New(s).PutObject(&s3.PutObjectInput{
Bucket: aws.String(S3_BUCKET),
Key: aws.String("myfolder" + "/" + fileDir),
ACL: aws.String("private"),
Body: bytes.NewReader(buffer),
ContentLength: aws.Int64(size),
ContentType: aws.String(http.DetectContentType(buffer)),
ContentDisposition: aws.String("attachment"),
ServerSideEncryption: aws.String("AES256"),
})
return err
}
I am sending my file through POSTMAN like this
what I need to pass to my 'AddFilesToS3' function, since I am sending just the file name, os.Open(fileDir) is failing to look to the actual path of the file.
Where am I doing wrong or is there any better method available to do this?
You're not even reading the file from the form.
You need to call FileHeader.Open. This returns a multipart.File which implements the standard io.Reader interface.
You should change AddFilesToS3 to take a filename and io.Reader. This can then be called for files from gin as well as regular files.
fileHeader, err := c.FormFile("file")
// check err
f, err := fileHeader.Open()
// check err
err = AddFilesToS3(s, fileHeader.fileName, f)
And your AddFilesToS3 is now:
func AddFilesToS3(s *session.Session, fileDir string, r io.Reader) error {
// left as an exercise for the OP
You may need to pass fileHeader.Size() as well.

Converting multipart file to an image object in golang

I'm attempting to upload an image, resize it and then upload it to Amazon S3 in go, however I'm struggling to figure out how to convert the image from multipart.File to image.Image
package controllers
import (
"github.com/gin-gonic/gin"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/s3"
"github.com/nfnt/resize"
_ "image/jpeg"
"log"
"os"
"strconv"
)
type ResizeController struct {
}
func NewResizeController() *ResizeController {
return &ResizeController{}
}
func (rc ResizeController) Resize(c *gin.Context) {
auth, err := aws.EnvAuth()
if err != nil {
log.Fatal(err)
}
client := s3.New(auth, aws.EUWest)
bucket := client.Bucket(os.Getenv("AWS_BUCKET_NAME"))
file, header, err := c.Request.FormFile("file")
filename := header.Filename
height := c.Query("height")
width := c.Query("width")
h64, err := strconv.ParseUint(height, 10, 32)
w64, err := strconv.ParseUint(width, 10, 32)
h := uint(h64)
w := uint(w64)
m := resize.Resize(w, h, file, resize.Lanczos3)
err = bucket.Put("/content/"+filename, m, "content-type", s3.Private)
c.JSON(200, gin.H{"filename": header.Filename})
}
I'm getting the error controllers/resize_controller.go:43: cannot use file (type multipart.File) as type image.Image in argument to resize.Resize:
Figured it out, I just needed to use
image, err := jpeg.Decode(file)

Resources