Skip to content

Commit

Permalink
Gov2: Add presign post request support - S3 (#6937)
Browse files Browse the repository at this point in the history
  • Loading branch information
Madrigal authored Sep 27, 2024
1 parent 0e778f1 commit d8a741f
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 21 deletions.
18 changes: 18 additions & 0 deletions gov2/s3/actions/presigner.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,22 @@ func (presigner Presigner) DeleteObject(bucketName string, objectKey string) (*v
}

// snippet-end:[gov2.s3.PresignDeleteObject]

// snippet-start:[gov2.s3.PresignPostObject]

func (presigner Presigner) PresignPostObject(bucketName string, objectKey string, lifetimeSecs int64) (*s3.PresignedPostRequest, error) {
request, err := presigner.PresignClient.PresignPostObject(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
}, func(options *s3.PresignPostOptions) {
options.Expires = time.Duration(lifetimeSecs) * time.Second
})
if err != nil {
log.Printf("Couldn't get a presigned post request to put %v:%v. Here's why: %v\n", bucketName, objectKey, err)
}
return request, nil
}

// snippet-end:[gov2.s3.PresignPostObject]

// snippet-end:[gov2.s3.Presigner.complete]
12 changes: 6 additions & 6 deletions gov2/s3/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import (
//
// `-scenario` can be one of the following:
//
// * `getstarted` - Runs the interactive get started scenario that shows you how to use
// Amazon Simple Storage Service (Amazon S3) actions to work with
// S3 buckets and objects.
// * `presigning` - Runs the interactive presigning scenario that shows you how to
// get presigned requests that contain temporary credentials
// and can be used to make requests from any HTTP client.
// - `getstarted` - Runs the interactive get started scenario that shows you how to use
// Amazon Simple Storage Service (Amazon S3) actions to work with
// S3 buckets and objects.
// - `presigning` - Runs the interactive presigning scenario that shows you how to
// get presigned requests that contain temporary credentials
// and can be used to make requests from any HTTP client.
func main() {
scenarioMap := map[string]func(sdkConfig aws.Config){
"getstarted": runGetStartedScenario,
Expand Down
14 changes: 0 additions & 14 deletions gov2/s3/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,7 @@ github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools v0.0.0-20240907001412-a93
github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools v0.0.0-20240907001412-a9375541143b/go.mod h1:iBzksyiv5HVU+cymGDQbbvcecca+rsARJlDFL8np8oE=
github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools v0.0.0-20240907001412-a9375541143b h1:UmPy4pArM7SIhTX2Xn5bhOkgI9onSUQ1Y9fxgDJ3pHU=
github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools v0.0.0-20240907001412-a9375541143b/go.mod h1:9Oj/8PZn3D5Ftp/Z1QWrIEFE0daERMqfJawL9duHRfc=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
69 changes: 68 additions & 1 deletion gov2/s3/scenarios/scenario_presigning.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
package scenarios

import (
"bytes"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"os"
"strings"
Expand All @@ -23,6 +25,7 @@ import (
// unit testing.
type IHttpRequester interface {
Get(url string) (resp *http.Response, err error)
Post(url, contentType string, body io.Reader) (resp *http.Response, err error)
Put(url string, contentLength int64, body io.Reader) (resp *http.Response, err error)
Delete(url string) (resp *http.Response, err error)
}
Expand All @@ -33,6 +36,15 @@ type HttpRequester struct{}
func (httpReq HttpRequester) Get(url string) (resp *http.Response, err error) {
return http.Get(url)
}
func (httpReq HttpRequester) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
postRequest, err := http.NewRequest("POST", url, body)
if err != nil {
return nil, err
}
postRequest.Header.Set("Content-Type", contentType)
return http.DefaultClient.Do(postRequest)
}

func (httpReq HttpRequester) Put(url string, contentLength int64, body io.Reader) (resp *http.Response, err error) {
putRequest, err := http.NewRequest("PUT", url, body)
if err != nil {
Expand All @@ -51,6 +63,43 @@ func (httpReq HttpRequester) Delete(url string) (resp *http.Response, err error)

// snippet-end:[gov2.s3.IHttpRequester.helper]

// snippet-start:[gov2.s3.MultipartUpload.helper]
func sendMultipartRequest(url string, fields map[string]string, file *os.File, filePath string, httpRequester IHttpRequester) (*http.Response, error) {
// Create a buffer to hold the multipart data
var requestBody bytes.Buffer
writer := multipart.NewWriter(&requestBody)

// Add form fields
for key, val := range fields {
err := writer.WriteField(key, val)
if err != nil {
return nil, err
}
}

// Always has to be named like this, and always has to be the last one
fileField := "file"
part, err := writer.CreateFormFile(fileField, filePath)
if err != nil {
return nil, err
}
_, err = io.Copy(part, file)
if err != nil {
return nil, err
}

// Close the writer to finalize the multipart message
err = writer.Close()
if err != nil {
return nil, err
}

// make the request
return httpRequester.Post(url, writer.FormDataContentType(), &requestBody)
}

// snippet-end:[gov2.s3.MultipartUpload.helper]

// snippet-start:[gov2.s3.Scenario_Presigning]

// RunPresigningScenario is an interactive example that shows you how to get presigned
Expand All @@ -76,7 +125,7 @@ func (httpReq HttpRequester) Delete(url string) (resp *http.Response, err error)
func RunPresigningScenario(sdkConfig aws.Config, questioner demotools.IQuestioner, httpRequester IHttpRequester) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Something went wrong with the demo.")
fmt.Printf("Something went wrong with the demo")
}
}()

Expand Down Expand Up @@ -159,6 +208,24 @@ func RunPresigningScenario(sdkConfig aws.Config, questioner demotools.IQuestione
log.Println(string(downloadBody[:100]))
log.Println(strings.Repeat("-", 88))

log.Println("Now we'll create a new request to put the same object using a presigned post request")
presignPostRequest, err := presigner.PresignPostObject(bucketName, uploadKey, 60)
if err != nil {
panic(err)
}
log.Printf("Got a presigned post request to url %v with values %v\n", presignPostRequest.URL, presignPostRequest.Values)
log.Println("Using net/http multipart to send the request...")
uploadFile, err = os.Open(uploadFilename)
if err != nil {
panic(err)
}
defer uploadFile.Close()
multiPartResponse, err := sendMultipartRequest(presignPostRequest.URL, presignPostRequest.Values, uploadFile, uploadKey, httpRequester)
if err != nil {
panic(err)
}
log.Printf("Presign post object %v with presigned URL returned %v.", uploadKey, multiPartResponse.StatusCode)

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)
Expand Down
4 changes: 4 additions & 0 deletions gov2/s3/scenarios/scenario_presigning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type MockHttpRequester struct {
func (httpReq MockHttpRequester) Get(url string) (resp *http.Response, err error) {
return &http.Response{Status: "Testing", StatusCode: 200, Body: httpReq.GetBody}, nil
}
func (httpReq MockHttpRequester) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
return &http.Response{Status: "Testing", StatusCode: 200}, nil
}
func (httpReq MockHttpRequester) Put(url string, contentLength int64, body io.Reader) (resp *http.Response, err error) {
return &http.Response{Status: "Testing", StatusCode: 200}, nil
}
Expand Down Expand Up @@ -68,6 +71,7 @@ func (scenTest *PresigningScenarioTest) SetupDataAndStubs() []testtools.Stub {
stubList = append(stubList, stubs.StubCreateBucket(bucketName, testConfig.Region, nil))
stubList = append(stubList, stubs.StubPresignedRequest("PUT", bucketName, objectKey, nil))
stubList = append(stubList, stubs.StubPresignedRequest("GET", bucketName, objectKey, nil))
stubList = append(stubList, stubs.StubPresignedRequest("POST", bucketName, objectKey, nil))
stubList = append(stubList, stubs.StubPresignedRequest("DELETE", bucketName, objectKey, nil))

return stubList
Expand Down
10 changes: 10 additions & 0 deletions gov2/s3/stubs/bucket_basics_stubs.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,16 @@ func StubPresignedRequest(method string, bucketName string, objectKey string, ra
case "DELETE":
opName = "DeleteObject"
input = &s3.DeleteObjectInput{Bucket: aws.String(bucketName), Key: aws.String(objectKey)}
case "POST":
opName = "PutObject"
input = &s3.PutObjectInput{Bucket: aws.String(bucketName), Key: aws.String(objectKey)}
// special case since the object here is different
return testtools.Stub{
OperationName: opName,
Input: input,
Output: &s3.PresignedPostRequest{URL: "test-url", Values: map[string]string{}},
Error: raiseErr,
}
}
return testtools.Stub{
OperationName: opName,
Expand Down

0 comments on commit d8a741f

Please sign in to comment.