Skip to content

Commit a4acbb8

Browse files
堀家隆宏堀家隆宏
authored andcommitted
fix conflict from master
2 parents bcd00e1 + 68191ce commit a4acbb8

27 files changed

+1314
-57
lines changed

README.md

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
# Serverless APIGateway Service Proxy
55

6-
This Serverless Framewrok plugin supports the AWS service proxy integration feature of API Gateway. You can directly connect API Gateway to AWS services without Lambda.
6+
This Serverless Framework plugin supports the AWS service proxy integration feature of API Gateway. You can directly connect API Gateway to AWS services without Lambda.
77

88
## TOC
99

@@ -28,7 +28,7 @@ This Serverless Framewrok plugin supports the AWS service proxy integration feat
2828

2929
## Install
3030

31-
Run `servelress plugin install` in your Serverless project.
31+
Run `serverless plugin install` in your Serverless project.
3232

3333
```bash
3434
serverless plugin install -n serverless-apigateway-service-proxy
@@ -198,6 +198,83 @@ Sample request after deploying.
198198
curl https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/s3 -d '{"message": "testtest"}' -H 'Content-Type:application/json'
199199
```
200200

201+
#### Customizing request parameters
202+
203+
Similar to the [SQS](#sqs) support, you can customize the default request parameters `serverless.yml` like so:
204+
205+
```yml
206+
custom:
207+
apiGatewayServiceProxies:
208+
- s3:
209+
path: /s3
210+
method: post
211+
action: PutObject
212+
bucket:
213+
Ref: S3Bucket
214+
cors: true
215+
216+
requestParameters:
217+
# if requestParameters has a 'integration.request.path.object' property you should remove the key setting
218+
'integration.request.path.object': 'context.requestId'
219+
'integration.request.header.cache-control': "'public, max-age=31536000, immutable'"
220+
```
221+
222+
#### Customize the Path Override in API Gateway
223+
224+
Added the new customization parameter that lets the user set a custom Path Override in API Gateway other than the `{bucket}/{object}`
225+
This parameter is optional and if not set, will fall back to `{bucket}/{object}`
226+
The Path Override will add `{bucket}/` automatically in front
227+
228+
Please keep in mind, that key or path.object still needs to be set at the moment (maybe this will be made optional later on with this)
229+
230+
Usage (With 2 Path Parameters (folder and file and a fixed file extension)):
231+
232+
```yaml
233+
custom:
234+
apiGatewayServiceProxies:
235+
- s3:
236+
path: /s3/{folder}/{file}
237+
method: get
238+
action: GetObject
239+
pathOverride: '{folder}/{file}.xml'
240+
bucket:
241+
Ref: S3Bucket
242+
cors: true
243+
244+
requestParameters:
245+
# if requestParameters has a 'integration.request.path.object' property you should remove the key setting
246+
'integration.request.path.folder': 'method.request.path.folder'
247+
'integration.request.path.file': 'method.request.path.file'
248+
'integration.request.path.object': 'context.requestId'
249+
'integration.request.header.cache-control': "'public, max-age=31536000, immutable'"
250+
```
251+
This will result in API Gateway setting the Path Override attribute to `{bucket}/{folder}/{file}.xml`
252+
So for example if you navigate to the API Gatway endpoint `/language/en` it will fetch the file in S3 from `{bucket}/language/en.xml`
253+
254+
##### Can use greedy, for deeper Folders
255+
The forementioned example can also be shortened by a greedy approach. Thanks to @taylorreece for mentioning this.
256+
257+
```yaml
258+
custom:
259+
apiGatewayServiceProxies:
260+
- s3:
261+
path: /s3/{myPath+}
262+
method: get
263+
action: GetObject
264+
pathOverride: '{myPath}.xml'
265+
bucket:
266+
Ref: S3Bucket
267+
cors: true
268+
269+
requestParameters:
270+
# if requestParameters has a 'integration.request.path.object' property you should remove the key setting
271+
'integration.request.path.myPath': 'method.request.path.myPath'
272+
'integration.request.path.object': 'context.requestId'
273+
'integration.request.header.cache-control': "'public, max-age=31536000, immutable'"
274+
```
275+
276+
This will translate for example `/s3/a/b/c` to `a/b/c.xml`
277+
201278
### SNS
202279

203280
Sample syntax for SNS proxy in `serverless.yml`.
@@ -450,6 +527,60 @@ resources:
450527
451528
Source: [AWS::ApiGateway::Method docs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-method.html#cfn-apigateway-method-authorizationtype)
452529
530+
### Using a Custom IAM Role
531+
532+
By default, the plugin will generate a role with the required permissions for each service type that is configured.
533+
534+
You can configure your own role by setting the `roleArn` attribute:
535+
536+
```yaml
537+
custom:
538+
apiGatewayServiceProxies:
539+
- sqs:
540+
path: /sqs
541+
method: post
542+
queueName: { 'Fn::GetAtt': ['SQSQueue', 'QueueName'] }
543+
cors: true
544+
roleArn: # Optional. A default role is created when not configured
545+
Fn::GetAtt: [CustomS3Role, Arn]
546+
547+
resources:
548+
Resources:
549+
SQSQueue:
550+
Type: 'AWS::SQS::Queue'
551+
CustomS3Role:
552+
# Custom Role definition
553+
Type: 'AWS::IAM::Role'
554+
```
555+
556+
### Customizing API Gateway parameters
557+
558+
The plugin allows one to specify which [parameters the API Gateway method accepts](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-method.html#cfn-apigateway-method-requestparameters).
559+
560+
A common use case is to pass custom data to the integration request:
561+
562+
```yaml
563+
custom:
564+
apiGatewayServiceProxies:
565+
- sqs:
566+
path: /sqs
567+
method: post
568+
queueName: { 'Fn::GetAtt': ['SqsQueue', 'QueueName'] }
569+
cors: true
570+
acceptParameters:
571+
'method.request.header.Custom-Header': true
572+
requestParameters:
573+
'integration.request.querystring.MessageAttribute.1.Name': "'custom-Header'"
574+
'integration.request.querystring.MessageAttribute.1.Value.StringValue': 'method.request.header.Custom-Header'
575+
'integration.request.querystring.MessageAttribute.1.Value.DataType': "'String'"
576+
resources:
577+
Resources:
578+
SqsQueue:
579+
Type: 'AWS::SQS::Queue'
580+
```
581+
582+
Any published SQS message will have the `Custom-Header` value added as a message attribute.
583+
453584
### Customizing request body mapping templates
454585

455586
#### Kinesis
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
service: accept-parameters-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+
- sqs:
15+
path: /sqs
16+
method: post
17+
queueName: { 'Fn::GetAtt': ['SqsQueue', 'QueueName'] }
18+
cors: true
19+
acceptParameters:
20+
'method.request.header.Custom-Header': true
21+
requestParameters:
22+
'integration.request.querystring.MessageAttribute.1.Name': "'custom-Header'"
23+
'integration.request.querystring.MessageAttribute.1.Value.StringValue': 'method.request.header.Custom-Header'
24+
'integration.request.querystring.MessageAttribute.1.Value.DataType': "'String'"
25+
26+
resources:
27+
Resources:
28+
SqsQueue:
29+
Type: 'AWS::SQS::Queue'
30+
Outputs:
31+
SqsQueueUrl:
32+
Value: { Ref: SqsQueue }
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use strict'
2+
const AWS = require('aws-sdk')
3+
const expect = require('chai').expect
4+
5+
const fetch = require('node-fetch')
6+
const { deployWithRandomStage, removeService } = require('../../../utils')
7+
8+
describe('Single SQS Proxy Integration Test', () => {
9+
let endpoint
10+
let region
11+
let stage
12+
let queueUrl
13+
const config = '__tests__/integration/common/accept-parameters/service/serverless.yml'
14+
15+
beforeAll(async () => {
16+
const result = await deployWithRandomStage(config)
17+
18+
region = result.region
19+
stage = result.stage
20+
endpoint = result.endpoint
21+
queueUrl = result.outputs.SqsQueueUrl
22+
})
23+
24+
afterAll(() => {
25+
removeService(stage, config)
26+
})
27+
28+
it('should pass custom header to sqs message attribute', async () => {
29+
const testEndpoint = `${endpoint}/sqs`
30+
31+
const body = JSON.stringify({ message: 'test accept parameters' })
32+
const response = await fetch(testEndpoint, {
33+
method: 'POST',
34+
headers: { 'Content-Type': 'application/json', 'Custom-Header': 'custom header value' },
35+
body
36+
})
37+
38+
expect(response.headers.get('access-control-allow-origin')).to.deep.equal('*')
39+
expect(response.status).to.be.equal(200)
40+
41+
const json = await response.json()
42+
43+
expect(json.SendMessageResponse.SendMessageResult).to.have.own.property(
44+
'MD5OfMessageAttributes'
45+
)
46+
expect(json.SendMessageResponse.SendMessageResult).to.have.own.property('MD5OfMessageBody')
47+
expect(json.SendMessageResponse.SendMessageResult).to.have.own.property('MessageId')
48+
expect(json.SendMessageResponse.SendMessageResult).to.have.own.property('SequenceNumber')
49+
expect(json.SendMessageResponse.ResponseMetadata).to.have.own.property('RequestId')
50+
51+
const sqs = new AWS.SQS({ region })
52+
const { Messages = [] } = await sqs
53+
.receiveMessage({ QueueUrl: queueUrl, WaitTimeSeconds: 20, MessageAttributeNames: ['.*'] })
54+
.promise()
55+
56+
expect(Messages).to.have.length(1)
57+
expect(Messages[0].Body).to.deep.equal(body)
58+
59+
expect(Messages[0].MessageAttributes).to.deep.equal({
60+
'custom-Header': {
61+
StringValue: 'custom header value',
62+
StringListValues: [],
63+
BinaryListValues: [],
64+
DataType: 'String'
65+
}
66+
})
67+
})
68+
})
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
service: custom-role-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-custom-role/{key}
16+
method: post
17+
action: PutObject
18+
bucket:
19+
Ref: S3Bucket
20+
key:
21+
pathParam: key
22+
cors: true
23+
roleArn:
24+
Fn::GetAtt: [CustomS3Role, Arn]
25+
26+
resources:
27+
Resources:
28+
S3Bucket:
29+
Type: 'AWS::S3::Bucket'
30+
CustomS3Role:
31+
Type: 'AWS::IAM::Role'
32+
Properties:
33+
AssumeRolePolicyDocument:
34+
Version: '2012-10-17'
35+
Statement:
36+
- Effect: 'Allow'
37+
Principal: {
38+
Service: 'apigateway.amazonaws.com'
39+
}
40+
Action: 'sts:AssumeRole'
41+
Policies:
42+
- PolicyName: 'apigatewaytos3'
43+
PolicyDocument:
44+
Version: '2012-10-17'
45+
Statement:
46+
- Effect: 'Allow'
47+
Action: 's3:PutObject*'
48+
Resource:
49+
Fn::Join:
50+
- ''
51+
- - Fn::GetAtt: [S3Bucket, Arn]
52+
- '/*'
53+
Outputs:
54+
S3BucketName:
55+
Value:
56+
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('Proxy With Custom Role Integration Test', () => {
13+
let endpoint
14+
let stage
15+
let bucket
16+
const config = '__tests__/integration/common/custom-role/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 with custom role', async () => {
33+
const testEndpoint = `${endpoint}/s3-custom-role/${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+
})

0 commit comments

Comments
 (0)