Created
August 28, 2023 21:58
-
-
Save sethp/68b2895a006ab749dff57cf949165d44 to your computer and use it in GitHub Desktop.
signed multipart uploads demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"errors" | |
"fmt" | |
"io" | |
"log" | |
"net/http" | |
"os" | |
"time" | |
"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/service/s3/types" | |
"github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools" | |
"github.com/awsdocs/aws-doc-sdk-examples/gov2/s3/actions" | |
) | |
func run(ctx context.Context) error { | |
var ( | |
questioner = demotools.NewQuestioner() | |
) | |
var ( | |
uploadFilename, uploadKey = "go.sum", "test-object" | |
bucketName = "my-bucket-19e02b5" | |
) | |
s3Client, presigner, err := configureClients(ctx) | |
if err != nil { | |
return err | |
} | |
uploadFile, err := os.Open(uploadFilename) | |
if err != nil { | |
return err | |
} | |
defer uploadFile.Close() | |
upload, err := s3Client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ | |
Bucket: aws.String(bucketName), | |
Key: aws.String(uploadKey), | |
}) | |
if err != nil { | |
return err | |
} | |
presignedPutRequest, err := presigner.PresignClient.PresignUploadPart(ctx, &s3.UploadPartInput{ | |
Bucket: aws.String(bucketName), | |
Key: aws.String(uploadKey), | |
UploadId: upload.UploadId, | |
// only one part to demonstrate the behavior, in reality chunking on time/size boundaries would be better | |
PartNumber: 1, | |
}, func(opts *s3.PresignOptions) { | |
// no idea if this is the amount of time to start or finish the request; I hope it's start | |
opts.Expires = time.Duration(60 * int64(time.Second)) | |
}) | |
if err != nil { | |
return err | |
} | |
log.Printf("Got a presigned %v request to URL:\n\t%v\n", presignedPutRequest.Method, | |
presignedPutRequest.URL) | |
log.Println("Using net/http to send the request...") | |
info, err := uploadFile.Stat() | |
if err != nil { | |
return err | |
} | |
putResponse, err := func() (*http.Response, error) { | |
putRequest, err := http.NewRequest(presignedPutRequest.Method, presignedPutRequest.URL, uploadFile) | |
if err != nil { | |
return nil, err | |
} | |
putRequest.ContentLength = info.Size() | |
return http.DefaultClient.Do(putRequest) | |
}() | |
if err != nil { | |
return err | |
} | |
log.Printf("%v object %v with presigned URL returned %v.", presignedPutRequest.Method, | |
uploadKey, putResponse.StatusCode) | |
if putResponse.StatusCode > 399 { | |
body, err := io.ReadAll(putResponse.Body) | |
return errors.Join(err, errors.New("bad response: "+string(body))) | |
} | |
_, err = s3Client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ | |
Bucket: aws.String(bucketName), | |
Key: aws.String(uploadKey), | |
UploadId: upload.UploadId, | |
MultipartUpload: &types.CompletedMultipartUpload{ | |
Parts: []types.CompletedPart{{ | |
ETag: &putResponse.Header[http.CanonicalHeaderKey("etag")][0], | |
PartNumber: 1, | |
}}, | |
}, | |
}) | |
if err != nil { | |
return err | |
} | |
log.Printf("Let's presign a request to download the object.") | |
questioner.Ask("Press Enter when you're ready.") | |
presignedGetRequest, err := presigner.GetObject(bucketName, uploadKey, 60) | |
if err != nil { | |
return err | |
} | |
log.Printf("Got a presigned %v request to URL:\n\t%v\n", presignedGetRequest.Method, | |
presignedGetRequest.URL) | |
log.Println("Using net/http to send the request...") | |
getResponse, err := http.Get(presignedGetRequest.URL) | |
if err != nil { | |
return err | |
} | |
log.Printf("%v object %v with presigned URL returned %v.", presignedGetRequest.Method, | |
uploadKey, getResponse.StatusCode) | |
defer getResponse.Body.Close() | |
downloadBody, err := io.ReadAll(getResponse.Body) | |
if err != nil { | |
return err | |
} | |
fmt.Printf("Downloaded %v bytes\n", len(downloadBody)) | |
log.Println("Let's presign a request to delete the object.") | |
questioner.Ask("Press Enter when you're ready.") | |
presignedDelRequest, err := presigner.DeleteObject(bucketName, uploadKey) | |
if err != nil { | |
return err | |
} | |
log.Printf("Got a presigned %v request to URL:\n\t%v\n", presignedDelRequest.Method, | |
presignedDelRequest.URL) | |
log.Println("Using net/http to send the request...") | |
delResponse, err := func() (*http.Response, error) { | |
delRequest, err := http.NewRequest(presignedDelRequest.Method, presignedDelRequest.URL, nil) | |
if err != nil { | |
return nil, err | |
} | |
return http.DefaultClient.Do(delRequest) | |
}() | |
if err != nil { | |
return err | |
} | |
log.Printf("%v object %v with presigned URL returned %v.\n", presignedDelRequest.Method, | |
uploadKey, delResponse.StatusCode) | |
return nil | |
} | |
func configureClients(ctx context.Context) (s3Client *s3.Client, presigner actions.Presigner, err error) { | |
var ( | |
sdkConfig aws.Config | |
provider = "s3" | |
) | |
if len(os.Args) > 1 { | |
provider = os.Args[1] | |
} | |
switch provider { | |
case "s3": | |
sdkConfig, err = config.LoadDefaultConfig(ctx, config.WithSharedConfigProfile("panoptesan")) | |
if err != nil { | |
return nil, actions.Presigner{}, err | |
} | |
case "b2": | |
// via https://help.backblaze.com/hc/en-us/articles/360047629713-Using-the-AWS-Go-SDK-with-B2 | |
// and https://stackoverflow.com/questions/66137645/how-do-i-configure-s3forcepathstyle-with-aws-golang-v2-sdk | |
sdkConfig = aws.Config{ | |
Region: "us-west-004", | |
Credentials: credentials.NewStaticCredentialsProvider(os.Getenv("B2_KEY"), os.Getenv("B2_SECRET"), ""), | |
EndpointResolverWithOptions: aws.EndpointResolverWithOptionsFunc(func(_, region string, _ ...interface{}) (aws.Endpoint, error) { | |
return aws.Endpoint{ | |
PartitionID: "aws", | |
URL: fmt.Sprintf("https://s3.%s.backblazeb2.com", region), | |
SigningRegion: "us-east-1", | |
}, nil | |
}), | |
} | |
default: | |
return nil, actions.Presigner{}, fmt.Errorf("unrecognized provider: %s", provider) | |
} | |
s3Client = s3.NewFromConfig(sdkConfig, func(o *s3.Options) { | |
o.UsePathStyle = true | |
}) | |
presigner = actions.Presigner{PresignClient: s3.NewPresignClient(s3Client)} | |
return | |
} | |
func main() { | |
ctx, cancel := context.WithCancel(context.Background()) | |
defer cancel() | |
err := run(ctx) | |
if err != nil { | |
fmt.Fprintln(os.Stderr, err) | |
os.Exit(1) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment