Skip to content

Adding Dynamic Path Override #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,62 @@ custom:
'integration.request.header.cache-control': "'public, max-age=31536000, immutable'"
```

#### Customize the Path Override in API Gateway

Added the new customization parameter that lets the user set a custom Path Override in API Gateway other than the `{bucket}/{object}`
This parameter is optional and if not set, will fall back to `{bucket}/{object}`
The Path Override will add `{bucket}/` automatically in front

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)

Usage (With 2 Path Parameters (folder and file and a fixed file extension)):

```yaml
custom:
apiGatewayServiceProxies:
- s3:
path: /s3/{folder}/{file}
method: get
action: GetObject
pathOverride: '{folder}/{file}.xml'
bucket:
Ref: S3Bucket
cors: true

requestParameters:
# if requestParameters has a 'integration.request.path.object' property you should remove the key setting
'integration.request.path.folder': 'method.request.path.folder'
'integration.request.path.file': 'method.request.path.file'
'integration.request.path.object': 'context.requestId'
'integration.request.header.cache-control': "'public, max-age=31536000, immutable'"
```
This will result in API Gateway setting the Path Override attribute to `{bucket}/{folder}/{file}.xml`
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`

##### Can use greedy, for deeper Folders
The forementioned example can also be shortened by a greedy approach. Thanks to @taylorreece for mentioning this.

```yaml
custom:
apiGatewayServiceProxies:
- s3:
path: /s3/{myPath+}
method: get
action: GetObject
pathOverride: '{myPath}.xml'
bucket:
Ref: S3Bucket
cors: true

requestParameters:
# if requestParameters has a 'integration.request.path.object' property you should remove the key setting
'integration.request.path.myPath': 'method.request.path.myPath'
'integration.request.path.object': 'context.requestId'
'integration.request.header.cache-control': "'public, max-age=31536000, immutable'"
```

This will translate for example `/s3/a/b/c` to `a/b/c.xml`

### SNS

Sample syntax for SNS proxy in `serverless.yml`.
Expand Down
3 changes: 3 additions & 0 deletions lib/apiGateway/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,11 @@ const roleArn = stringOrGetAtt('roleArn', 'Arn')

const acceptParameters = Joi.object().pattern(Joi.string(), Joi.boolean().required())

const pathOverride = Joi.string()

const proxy = Joi.object({
path,
pathOverride,
method,
cors,
authorizationType,
Expand Down
8 changes: 7 additions & 1 deletion lib/package/s3/compileMethodsToS3.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,18 @@ module.exports = {
'Fn::GetAtt': ['ApigatewayToS3Role', 'Arn']
}

let pather = '{bucket}/{object}'

if (_.has(http, 'pathOverride')) {
pather = '{bucket}/' + http.pathOverride
}

const integration = {
IntegrationHttpMethod: httpMethod,
Type: 'AWS',
Credentials: roleArn,
Uri: {
'Fn::Sub': ['arn:aws:apigateway:${AWS::Region}:s3:path/{bucket}/{object}', {}]
'Fn::Sub': ['arn:aws:apigateway:${AWS::Region}:s3:path/' + pather, {}]
},
PassthroughBehavior: 'WHEN_NO_MATCH',
RequestParameters: _.merge(requestParams, http.requestParameters)
Expand Down
242 changes: 242 additions & 0 deletions lib/package/s3/compileMethodsToS3.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -681,4 +681,246 @@ describe('#compileMethodsToS3()', () => {
'method.request.path.key': true
})
})

it('should create corresponding resources when s3 GetObject proxy is given with path override', () => {
serverlessApigatewayServiceProxy.validated = {
events: [
{
serviceName: 's3',
http: {
path: '/{folder}/{item}',
method: 'get',
bucket: {
Ref: 'MyBucket'
},
action: 'GetObject',
key: {
pathParam: 'item'
},
pathOverride: '{folder}/{item}.xml',
auth: { authorizationType: 'NONE' },
requestParameters: {
'integration.request.path.folder': 'method.request.path.folder',
'integration.request.path.item': 'method.request.path.item'
}
}
}
]
}
serverlessApigatewayServiceProxy.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'
serverlessApigatewayServiceProxy.apiGatewayResources = {
'/{folder}/{item}': {
name: 'po',
resourceLogicalId: 'ApiGatewayPathOverrideS3'
}
}

serverlessApigatewayServiceProxy.compileMethodsToS3()
expect(serverless.service.provider.compiledCloudFormationTemplate.Resources).to.deep.equal({
ApiGatewayMethodpoGet: {
Type: 'AWS::ApiGateway::Method',
Properties: {
HttpMethod: 'GET',
RequestParameters: {
'method.request.path.folder': true,
'method.request.path.item': true
},
AuthorizationType: 'NONE',
AuthorizationScopes: undefined,
AuthorizerId: undefined,
ApiKeyRequired: false,
ResourceId: { Ref: 'ApiGatewayPathOverrideS3' },
RestApiId: { Ref: 'ApiGatewayRestApi' },
Integration: {
Type: 'AWS',
IntegrationHttpMethod: 'GET',
Credentials: { 'Fn::GetAtt': ['ApigatewayToS3Role', 'Arn'] },
Uri: {
'Fn::Sub': [
'arn:aws:apigateway:${AWS::Region}:s3:path/{bucket}/{folder}/{item}.xml',
{}
]
},
PassthroughBehavior: 'WHEN_NO_MATCH',
RequestParameters: {
'integration.request.path.bucket': {
'Fn::Sub': [
"'${bucket}'",
{
bucket: {
Ref: 'MyBucket'
}
}
]
},
'integration.request.path.object': 'method.request.path.item',
'integration.request.path.folder': 'method.request.path.folder',
'integration.request.path.item': 'method.request.path.item'
},
IntegrationResponses: [
{
StatusCode: 400,
SelectionPattern: '4\\d{2}',
ResponseParameters: {},
ResponseTemplates: {}
},
{
StatusCode: 500,
SelectionPattern: '5\\d{2}',
ResponseParameters: {},
ResponseTemplates: {}
},
{
StatusCode: 200,
SelectionPattern: '2\\d{2}',
ResponseParameters: {
'method.response.header.Content-Type': 'integration.response.header.Content-Type',
'method.response.header.content-type': 'integration.response.header.content-type'
},
ResponseTemplates: {}
}
]
},
MethodResponses: [
{
ResponseParameters: {
'method.response.header.Content-Type': true,
'method.response.header.content-type': true
},
ResponseModels: {},
StatusCode: 200
},
{
ResponseParameters: {},
ResponseModels: {},
StatusCode: 400
},
{
ResponseParameters: {},
ResponseModels: {},
StatusCode: 500
}
]
}
}
})
})

it('should create corresponding resources when s3 GetObject proxy is given with a greedy path override', () => {
serverlessApigatewayServiceProxy.validated = {
events: [
{
serviceName: 's3',
http: {
path: '/{myPath+}',
method: 'get',
bucket: {
Ref: 'MyBucket'
},
action: 'GetObject',
key: {
pathParam: 'myPath'
},
pathOverride: '{myPath}.xml',
auth: { authorizationType: 'NONE' },
requestParameters: {
'integration.request.path.myPath': 'method.request.path.myPath'
}
}
}
]
}
serverlessApigatewayServiceProxy.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'
serverlessApigatewayServiceProxy.apiGatewayResources = {
'/{myPath+}': {
name: 'greedyPath',
resourceLogicalId: 'ApiGatewayPathOverrideS3'
}
}

serverlessApigatewayServiceProxy.compileMethodsToS3()
expect(serverless.service.provider.compiledCloudFormationTemplate.Resources).to.deep.equal({
ApiGatewayMethodgreedyPathGet: {
Type: 'AWS::ApiGateway::Method',
Properties: {
HttpMethod: 'GET',
RequestParameters: {
'method.request.path.myPath': true
},
AuthorizationType: 'NONE',
AuthorizationScopes: undefined,
AuthorizerId: undefined,
ApiKeyRequired: false,
ResourceId: { Ref: 'ApiGatewayPathOverrideS3' },
RestApiId: { Ref: 'ApiGatewayRestApi' },
Integration: {
Type: 'AWS',
IntegrationHttpMethod: 'GET',
Credentials: { 'Fn::GetAtt': ['ApigatewayToS3Role', 'Arn'] },
Uri: {
'Fn::Sub': ['arn:aws:apigateway:${AWS::Region}:s3:path/{bucket}/{myPath}.xml', {}]
},
PassthroughBehavior: 'WHEN_NO_MATCH',
RequestParameters: {
'integration.request.path.bucket': {
'Fn::Sub': [
"'${bucket}'",
{
bucket: {
Ref: 'MyBucket'
}
}
]
},
'integration.request.path.object': 'method.request.path.myPath',
'integration.request.path.myPath': 'method.request.path.myPath'
},
IntegrationResponses: [
{
StatusCode: 400,
SelectionPattern: '4\\d{2}',
ResponseParameters: {},
ResponseTemplates: {}
},
{
StatusCode: 500,
SelectionPattern: '5\\d{2}',
ResponseParameters: {},
ResponseTemplates: {}
},
{
StatusCode: 200,
SelectionPattern: '2\\d{2}',
ResponseParameters: {
'method.response.header.Content-Type': 'integration.response.header.Content-Type',
'method.response.header.content-type': 'integration.response.header.content-type'
},
ResponseTemplates: {}
}
]
},
MethodResponses: [
{
ResponseParameters: {
'method.response.header.Content-Type': true,
'method.response.header.content-type': true
},
ResponseModels: {},
StatusCode: 200
},
{
ResponseParameters: {},
ResponseModels: {},
StatusCode: 400
},
{
ResponseParameters: {},
ResponseModels: {},
StatusCode: 500
}
]
}
}
})
})
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@
"type": "git",
"url": "https://github.com/horike37/serverless-apigateway-service-proxy.git"
}
}
}