Skip to content

Commit 84bdc00

Browse files
authored
Merge pull request #22 from theburningmonk/feature/support_s3
Feature/support s3
2 parents 9c450d0 + bed787c commit 84bdc00

27 files changed

+1598
-71
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ tmp
4343
tmpdirs-serverless
4444
.eslintcache
4545
.serverless
46+
.vscode

README.md

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ Please pull request if you are intersted in it.
2020

2121
- Kinesis Streams
2222
- SQS
23+
- S3
2324

2425
## How to use
2526

2627
Define settings of the AWS services you want to integrate under `custom > apiGatewayServiceProxies` and run `serverless deploy`.
2728

2829
### Kinesis
2930

30-
Sample syntax for Kinesis proxy in serverless.yml.
31+
Sample syntax for Kinesis proxy in `serverless.yml`.
3132

3233
```yaml
3334
custom:
@@ -49,12 +50,12 @@ resources:
4950
Sample request after deploying.
5051
5152
```bash
52-
curl -XPOST https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/kinesis -d '{"Data": "some data","PartitionKey": "some key"}' -H 'Content-Type:application/json'
53+
curl -X POST https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/kinesis -d '{"Data": "some data","PartitionKey": "some key"}' -H 'Content-Type:application/json'
5354
```
5455
5556
### SQS
5657
57-
Sample syntax for SQS proxy in serverless.yml.
58+
Sample syntax for SQS proxy in `serverless.yml`.
5859

5960
```yaml
6061
custom:
@@ -74,7 +75,55 @@ resources:
7475
Sample request after deploying.
7576

7677
```bash
77-
curl -XPOST https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/sqs -d '{"message": "testtest"}' -H 'Content-Type:application/json'
78+
curl -X POST https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/sqs -d '{"message": "testtest"}' -H 'Content-Type:application/json'
79+
```
80+
81+
### S3
82+
83+
Sample syntax for S3 proxy in `serverless.yml`.
84+
85+
```yaml
86+
custom:
87+
apiGatewayServiceProxies:
88+
- s3:
89+
path: /s3
90+
method: post
91+
action: PutObject
92+
bucket:
93+
Ref: S3Bucket
94+
key: static-key.json # use static key
95+
cors: true
96+
97+
- s3:
98+
path: /s3/{myKey} # use path param
99+
method: get
100+
action: GetObject
101+
bucket:
102+
Ref: S3Bucket
103+
key:
104+
pathParam: myKey
105+
cors: true
106+
107+
- s3:
108+
path: /s3
109+
method: delete
110+
action: DeleteObject
111+
bucket:
112+
Ref: S3Bucket
113+
key:
114+
queryStringParam: key # use query string param
115+
cors: true
116+
117+
resources:
118+
Resources:
119+
S3Bucket:
120+
Type: 'AWS::S3::Bucket'
121+
```
122+
123+
Sample request after deploying.
124+
125+
```bash
126+
curl -X POST https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/s3 -d '{"message": "testtest"}' -H 'Content-Type:application/json'
78127
```
79128

80129
## Common API Gateway features
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
service: multiple-s3-proxy
2+
3+
provider:
4+
name: aws
5+
runtime: nodejs10.x
6+
7+
plugins:
8+
localPath: './../../../../../../'
9+
modules:
10+
- serverless-apigateway-service-proxy
11+
12+
custom:
13+
apiGatewayServiceProxies:
14+
- s3:
15+
path: /s3
16+
method: post
17+
action: PutObject
18+
bucket:
19+
Ref: S3Bucket
20+
key: my-test-object.json # static key
21+
cors: true
22+
23+
- s3:
24+
path: /s3/{key} # path param
25+
method: get
26+
action: GetObject
27+
bucket:
28+
Ref: S3Bucket
29+
key:
30+
pathParam: key
31+
cors: true
32+
33+
- s3:
34+
path: /s3
35+
method: delete
36+
action: DeleteObject
37+
bucket:
38+
Ref: S3Bucket
39+
key:
40+
queryStringParam: key # query string param
41+
cors: true
42+
43+
resources:
44+
Resources:
45+
S3Bucket:
46+
Type: 'AWS::S3::Bucket'
47+
48+
Outputs:
49+
S3BucketName:
50+
Value:
51+
Ref: S3Bucket
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use strict'
2+
3+
const expect = require('chai').expect
4+
const fetch = require('node-fetch')
5+
const {
6+
deployWithRandomStage,
7+
removeService,
8+
getS3Object,
9+
deleteS3Object
10+
} = require('../../../utils')
11+
12+
describe('Multiple S3 Proxies Integration Test', () => {
13+
let endpoint
14+
let stage
15+
let bucket
16+
const config = '__tests__/integration/s3/multiple-integrations/service/serverless.yml'
17+
const key = 'my-test-object.json'
18+
19+
beforeAll(async () => {
20+
const result = await deployWithRandomStage(config)
21+
22+
stage = result.stage
23+
endpoint = result.endpoint
24+
bucket = result.outputs.S3BucketName
25+
})
26+
27+
afterAll(async () => {
28+
await deleteS3Object(bucket, key)
29+
removeService(stage, config)
30+
})
31+
32+
it('should get correct response from s3 put endpoint', async () => {
33+
const putEndpoint = `${endpoint}/s3`
34+
35+
const putResponse = await fetch(putEndpoint, {
36+
method: 'POST',
37+
headers: { 'Content-Type': 'application/json' },
38+
body: JSON.stringify({ message: 'test' })
39+
})
40+
expect(putResponse.headers.get('access-control-allow-origin')).to.deep.equal('*')
41+
expect(putResponse.status).to.be.equal(200)
42+
43+
const uploadedObject = await getS3Object(bucket, key)
44+
expect(uploadedObject.toString()).to.equal(JSON.stringify({ message: 'test' }))
45+
})
46+
47+
it('should get correct response from s3 get endpoint', async () => {
48+
const getEndpoint = `${endpoint}/s3/${key}`
49+
50+
const getResponse = await fetch(getEndpoint, {
51+
method: 'GET',
52+
headers: { Accept: 'application/json' }
53+
})
54+
expect(getResponse.headers.get('access-control-allow-origin')).to.deep.equal('*')
55+
expect(getResponse.status).to.be.equal(200)
56+
57+
const body = await getResponse.json()
58+
expect(body).to.deep.equal({ message: 'test' })
59+
})
60+
61+
it('should get correct response from s3 delete endpoint', async () => {
62+
const deleteEndpoint = `${endpoint}/s3?key=${key}`
63+
64+
const deleteResponse = await fetch(deleteEndpoint, {
65+
method: 'DELETE'
66+
})
67+
expect(deleteResponse.status).to.be.equal(200)
68+
})
69+
})
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
service: s3-proxy
2+
3+
provider:
4+
name: aws
5+
runtime: nodejs10.x
6+
7+
plugins:
8+
localPath: './../../../../../../'
9+
modules:
10+
- serverless-apigateway-service-proxy
11+
12+
custom:
13+
apiGatewayServiceProxies:
14+
- s3:
15+
path: /s3/{key}
16+
method: post
17+
action: PutObject
18+
bucket:
19+
Ref: S3Bucket
20+
key:
21+
pathParam: key
22+
cors: true
23+
24+
resources:
25+
Resources:
26+
S3Bucket:
27+
Type: 'AWS::S3::Bucket'
28+
29+
Outputs:
30+
S3BucketName:
31+
Value:
32+
Ref: S3Bucket
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict'
2+
3+
const expect = require('chai').expect
4+
const fetch = require('node-fetch')
5+
const {
6+
deployWithRandomStage,
7+
removeService,
8+
getS3Object,
9+
deleteS3Object
10+
} = require('../../../utils')
11+
12+
describe('Single S3 Proxy Integration Test', () => {
13+
let endpoint
14+
let stage
15+
let bucket
16+
const config = '__tests__/integration/s3/single-integration/service/serverless.yml'
17+
const key = 'my-test-object.json'
18+
19+
beforeAll(async () => {
20+
const result = await deployWithRandomStage(config)
21+
22+
stage = result.stage
23+
endpoint = result.endpoint
24+
bucket = result.outputs.S3BucketName
25+
})
26+
27+
afterAll(async () => {
28+
await deleteS3Object(bucket, key)
29+
removeService(stage, config)
30+
})
31+
32+
it('should get correct response from s3 proxy endpoint', async () => {
33+
const testEndpoint = `${endpoint}/s3/${key}`
34+
35+
const response = await fetch(testEndpoint, {
36+
method: 'POST',
37+
headers: { 'Content-Type': 'application/json' },
38+
body: JSON.stringify({ message: 'test' })
39+
})
40+
expect(response.headers.get('access-control-allow-origin')).to.deep.equal('*')
41+
expect(response.status).to.be.equal(200)
42+
43+
const uploadedObject = await getS3Object(bucket, key)
44+
expect(uploadedObject.toString()).to.equal(JSON.stringify({ message: 'test' }))
45+
})
46+
})

__tests__/utils.js

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,41 @@ const fs = require('fs')
66
const path = require('path')
77
const execSync = require('child_process').execSync
88
const aws = require('aws-sdk')
9+
const s3 = new aws.S3()
910
const cloudformation = new aws.CloudFormation({ region: 'us-east-1' })
1011

11-
async function getApiGatewayEndpoint(stackName) {
12+
function getApiGatewayEndpoint(outputs) {
13+
return outputs.ServiceEndpoint.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]
14+
}
15+
16+
async function getStackOutputs(stackName) {
1217
const result = await cloudformation.describeStacks({ StackName: stackName }).promise()
18+
const stack = result.Stacks[0]
19+
20+
const keys = stack.Outputs.map((x) => x.OutputKey)
21+
const values = stack.Outputs.map((x) => x.OutputValue)
22+
23+
return _.zipObject(keys, values)
24+
}
25+
26+
async function getS3Object(bucket, key) {
27+
const resp = await s3
28+
.getObject({
29+
Bucket: bucket,
30+
Key: key
31+
})
32+
.promise()
33+
34+
return resp.Body
35+
}
1336

14-
const endpointOutput = _.find(result.Stacks[0].Outputs, { OutputKey: 'ServiceEndpoint' })
15-
.OutputValue
16-
return endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]
37+
async function deleteS3Object(bucket, key) {
38+
await s3
39+
.deleteObject({
40+
Bucket: bucket,
41+
Key: key
42+
})
43+
.promise()
1744
}
1845

1946
function deployService(stage, config) {
@@ -37,14 +64,16 @@ async function deployWithRandomStage(config) {
3764
.substring(2)
3865
const stackName = `${serviceName}-${stage}`
3966
deployService(stage, config)
40-
const endpoint = await getApiGatewayEndpoint(stackName)
67+
const outputs = await getStackOutputs(stackName)
68+
const endpoint = getApiGatewayEndpoint(outputs)
4169

42-
return { stage, endpoint }
70+
return { stackName, stage, outputs, endpoint }
4371
}
4472

4573
module.exports = {
46-
getApiGatewayEndpoint,
4774
deployService,
4875
removeService,
49-
deployWithRandomStage
76+
deployWithRandomStage,
77+
getS3Object,
78+
deleteS3Object
5079
}

lib/apiGateway/methods.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict'
22

33
module.exports = {
4-
async getMethodResponses(http) {
4+
getMethodResponses(http) {
55
const methodResponse = {
66
Properties: {
77
MethodResponses: [
@@ -14,6 +14,11 @@ module.exports = {
1414
ResponseParameters: {},
1515
ResponseModels: {},
1616
StatusCode: 400
17+
},
18+
{
19+
ResponseParameters: {},
20+
ResponseModels: {},
21+
StatusCode: 500
1722
}
1823
]
1924
}
@@ -25,7 +30,7 @@ module.exports = {
2530
origin = http.cors.origins.join(',')
2631
}
2732

28-
methodResponse.Properties.MethodResponses.forEach(async (val, i) => {
33+
methodResponse.Properties.MethodResponses.forEach((val, i) => {
2934
methodResponse.Properties.MethodResponses[i].ResponseParameters = {
3035
'method.response.header.Access-Control-Allow-Origin': `'${origin}'`
3136
}

0 commit comments

Comments
 (0)