Skip to content

Commit d8a741f

Browse files
authored
Gov2: Add presign post request support - S3 (#6937)
1 parent 0e778f1 commit d8a741f

File tree

6 files changed

+106
-21
lines changed

6 files changed

+106
-21
lines changed

gov2/s3/actions/presigner.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,22 @@ func (presigner Presigner) DeleteObject(bucketName string, objectKey string) (*v
8383
}
8484

8585
// snippet-end:[gov2.s3.PresignDeleteObject]
86+
87+
// snippet-start:[gov2.s3.PresignPostObject]
88+
89+
func (presigner Presigner) PresignPostObject(bucketName string, objectKey string, lifetimeSecs int64) (*s3.PresignedPostRequest, error) {
90+
request, err := presigner.PresignClient.PresignPostObject(context.TODO(), &s3.PutObjectInput{
91+
Bucket: aws.String(bucketName),
92+
Key: aws.String(objectKey),
93+
}, func(options *s3.PresignPostOptions) {
94+
options.Expires = time.Duration(lifetimeSecs) * time.Second
95+
})
96+
if err != nil {
97+
log.Printf("Couldn't get a presigned post request to put %v:%v. Here's why: %v\n", bucketName, objectKey, err)
98+
}
99+
return request, nil
100+
}
101+
102+
// snippet-end:[gov2.s3.PresignPostObject]
103+
86104
// snippet-end:[gov2.s3.Presigner.complete]

gov2/s3/cmd/main.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ import (
2020
//
2121
// `-scenario` can be one of the following:
2222
//
23-
// * `getstarted` - Runs the interactive get started scenario that shows you how to use
24-
// Amazon Simple Storage Service (Amazon S3) actions to work with
25-
// S3 buckets and objects.
26-
// * `presigning` - Runs the interactive presigning scenario that shows you how to
27-
// get presigned requests that contain temporary credentials
28-
// and can be used to make requests from any HTTP client.
23+
// - `getstarted` - Runs the interactive get started scenario that shows you how to use
24+
// Amazon Simple Storage Service (Amazon S3) actions to work with
25+
// S3 buckets and objects.
26+
// - `presigning` - Runs the interactive presigning scenario that shows you how to
27+
// get presigned requests that contain temporary credentials
28+
// and can be used to make requests from any HTTP client.
2929
func main() {
3030
scenarioMap := map[string]func(sdkConfig aws.Config){
3131
"getstarted": runGetStartedScenario,

gov2/s3/go.sum

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,7 @@ github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools v0.0.0-20240907001412-a93
4040
github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools v0.0.0-20240907001412-a9375541143b/go.mod h1:iBzksyiv5HVU+cymGDQbbvcecca+rsARJlDFL8np8oE=
4141
github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools v0.0.0-20240907001412-a9375541143b h1:UmPy4pArM7SIhTX2Xn5bhOkgI9onSUQ1Y9fxgDJ3pHU=
4242
github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools v0.0.0-20240907001412-a9375541143b/go.mod h1:9Oj/8PZn3D5Ftp/Z1QWrIEFE0daERMqfJawL9duHRfc=
43-
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
44-
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
45-
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
46-
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
47-
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
48-
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
49-
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
50-
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
51-
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
52-
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
5343
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
5444
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
5545
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
5646
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
57-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
58-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
59-
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
60-
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

gov2/s3/scenarios/scenario_presigning.go

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
package scenarios
55

66
import (
7+
"bytes"
78
"fmt"
89
"io"
910
"log"
11+
"mime/multipart"
1012
"net/http"
1113
"os"
1214
"strings"
@@ -23,6 +25,7 @@ import (
2325
// unit testing.
2426
type IHttpRequester interface {
2527
Get(url string) (resp *http.Response, err error)
28+
Post(url, contentType string, body io.Reader) (resp *http.Response, err error)
2629
Put(url string, contentLength int64, body io.Reader) (resp *http.Response, err error)
2730
Delete(url string) (resp *http.Response, err error)
2831
}
@@ -33,6 +36,15 @@ type HttpRequester struct{}
3336
func (httpReq HttpRequester) Get(url string) (resp *http.Response, err error) {
3437
return http.Get(url)
3538
}
39+
func (httpReq HttpRequester) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
40+
postRequest, err := http.NewRequest("POST", url, body)
41+
if err != nil {
42+
return nil, err
43+
}
44+
postRequest.Header.Set("Content-Type", contentType)
45+
return http.DefaultClient.Do(postRequest)
46+
}
47+
3648
func (httpReq HttpRequester) Put(url string, contentLength int64, body io.Reader) (resp *http.Response, err error) {
3749
putRequest, err := http.NewRequest("PUT", url, body)
3850
if err != nil {
@@ -51,6 +63,43 @@ func (httpReq HttpRequester) Delete(url string) (resp *http.Response, err error)
5163

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

66+
// snippet-start:[gov2.s3.MultipartUpload.helper]
67+
func sendMultipartRequest(url string, fields map[string]string, file *os.File, filePath string, httpRequester IHttpRequester) (*http.Response, error) {
68+
// Create a buffer to hold the multipart data
69+
var requestBody bytes.Buffer
70+
writer := multipart.NewWriter(&requestBody)
71+
72+
// Add form fields
73+
for key, val := range fields {
74+
err := writer.WriteField(key, val)
75+
if err != nil {
76+
return nil, err
77+
}
78+
}
79+
80+
// Always has to be named like this, and always has to be the last one
81+
fileField := "file"
82+
part, err := writer.CreateFormFile(fileField, filePath)
83+
if err != nil {
84+
return nil, err
85+
}
86+
_, err = io.Copy(part, file)
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
// Close the writer to finalize the multipart message
92+
err = writer.Close()
93+
if err != nil {
94+
return nil, err
95+
}
96+
97+
// make the request
98+
return httpRequester.Post(url, writer.FormDataContentType(), &requestBody)
99+
}
100+
101+
// snippet-end:[gov2.s3.MultipartUpload.helper]
102+
54103
// snippet-start:[gov2.s3.Scenario_Presigning]
55104

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

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

211+
log.Println("Now we'll create a new request to put the same object using a presigned post request")
212+
presignPostRequest, err := presigner.PresignPostObject(bucketName, uploadKey, 60)
213+
if err != nil {
214+
panic(err)
215+
}
216+
log.Printf("Got a presigned post request to url %v with values %v\n", presignPostRequest.URL, presignPostRequest.Values)
217+
log.Println("Using net/http multipart to send the request...")
218+
uploadFile, err = os.Open(uploadFilename)
219+
if err != nil {
220+
panic(err)
221+
}
222+
defer uploadFile.Close()
223+
multiPartResponse, err := sendMultipartRequest(presignPostRequest.URL, presignPostRequest.Values, uploadFile, uploadKey, httpRequester)
224+
if err != nil {
225+
panic(err)
226+
}
227+
log.Printf("Presign post object %v with presigned URL returned %v.", uploadKey, multiPartResponse.StatusCode)
228+
162229
log.Println("Let's presign a request to delete the object.")
163230
questioner.Ask("Press Enter when you're ready.")
164231
presignedDelRequest, err := presigner.DeleteObject(bucketName, uploadKey)

gov2/s3/scenarios/scenario_presigning_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ type MockHttpRequester struct {
2727
func (httpReq MockHttpRequester) Get(url string) (resp *http.Response, err error) {
2828
return &http.Response{Status: "Testing", StatusCode: 200, Body: httpReq.GetBody}, nil
2929
}
30+
func (httpReq MockHttpRequester) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
31+
return &http.Response{Status: "Testing", StatusCode: 200}, nil
32+
}
3033
func (httpReq MockHttpRequester) Put(url string, contentLength int64, body io.Reader) (resp *http.Response, err error) {
3134
return &http.Response{Status: "Testing", StatusCode: 200}, nil
3235
}
@@ -68,6 +71,7 @@ func (scenTest *PresigningScenarioTest) SetupDataAndStubs() []testtools.Stub {
6871
stubList = append(stubList, stubs.StubCreateBucket(bucketName, testConfig.Region, nil))
6972
stubList = append(stubList, stubs.StubPresignedRequest("PUT", bucketName, objectKey, nil))
7073
stubList = append(stubList, stubs.StubPresignedRequest("GET", bucketName, objectKey, nil))
74+
stubList = append(stubList, stubs.StubPresignedRequest("POST", bucketName, objectKey, nil))
7175
stubList = append(stubList, stubs.StubPresignedRequest("DELETE", bucketName, objectKey, nil))
7276

7377
return stubList

gov2/s3/stubs/bucket_basics_stubs.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,16 @@ func StubPresignedRequest(method string, bucketName string, objectKey string, ra
208208
case "DELETE":
209209
opName = "DeleteObject"
210210
input = &s3.DeleteObjectInput{Bucket: aws.String(bucketName), Key: aws.String(objectKey)}
211+
case "POST":
212+
opName = "PutObject"
213+
input = &s3.PutObjectInput{Bucket: aws.String(bucketName), Key: aws.String(objectKey)}
214+
// special case since the object here is different
215+
return testtools.Stub{
216+
OperationName: opName,
217+
Input: input,
218+
Output: &s3.PresignedPostRequest{URL: "test-url", Values: map[string]string{}},
219+
Error: raiseErr,
220+
}
211221
}
212222
return testtools.Stub{
213223
OperationName: opName,

0 commit comments

Comments
 (0)