Skip to content

Commit 69867e5

Browse files
authored
Merge pull request #79 from jcortega/master
Feature: Support for EventBridge / CloudWatchEvent
2 parents fc0ad38 + 5021258 commit 69867e5

File tree

15 files changed

+1685
-3
lines changed

15 files changed

+1685
-3
lines changed

README.md

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This Serverless Framework plugin supports the AWS service proxy integration feat
1919
- [Can use greedy, for deeper Folders](#can-use-greedy--for-deeper-folders)
2020
- [SNS](#sns)
2121
- [DynamoDB](#dynamodb)
22+
- [EventBridge](#eventbridge)
2223
- [Common API Gateway features](#common-api-gateway-features)
2324
- [Enabling CORS](#enabling-cors)
2425
- [Adding Authorization](#adding-authorization)
@@ -47,6 +48,7 @@ Please pull request if you are intersted in it.
4748
- S3
4849
- SNS
4950
- DynamoDB
51+
- EventBridge
5052

5153
## How to use
5254

@@ -376,6 +378,56 @@ curl -XPUT https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/dynamodb/<has
376378
-H 'Content-Type:application/json'
377379
```
378380

381+
382+
### EventBridge
383+
384+
Sample syntax for EventBridge proxy in `serverless.yml`.
385+
386+
```yaml
387+
custom:
388+
apiGatewayServiceProxies:
389+
- eventbridge: # source and detailType are hardcoded; detail defaults to POST body
390+
path: /eventbridge
391+
method: post
392+
source: 'hardcoded_source'
393+
detailType: 'hardcoded_detailType'
394+
eventBusName: { Ref: 'YourBusName' }
395+
cors: true
396+
- eventbridge: # source and detailType as path parameters
397+
path: /eventbridge/{detailTypeKey}/{sourceKey}
398+
method: post
399+
detailType:
400+
pathParam: detailTypeKey
401+
source:
402+
pathParam: sourceKey
403+
eventBusName: { Ref: 'YourBusName' }
404+
cors: true
405+
- eventbridge: # source, detail, and detailType as body parameters
406+
path: /eventbridge/{detailTypeKey}/{sourceKey}
407+
method: post
408+
detailType:
409+
bodyParam: data.detailType
410+
source:
411+
bodyParam: data.source
412+
detail:
413+
bodyParam: data.detail
414+
eventBusName: { Ref: 'YourBusName' }
415+
cors: true
416+
417+
resources:
418+
Resources:
419+
YourBus:
420+
Type: AWS::Events::EventBus
421+
Properties:
422+
Name: YourEventBus
423+
```
424+
425+
Sample request after deploying.
426+
427+
```bash
428+
curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/eventbridge -d '{"message": "some data"}' -H 'Content-Type:application/json'
429+
```
430+
379431
## Common API Gateway features
380432

381433
### Enabling CORS
@@ -659,4 +711,4 @@ custom:
659711
"success": false,
660712
"errorMessage": "Client Error"
661713
}
662-
```
714+
```
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
service: multiple-eventbridge-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+
# set static detailType and source
15+
- eventbridge:
16+
path: /eventbridge1
17+
method: post
18+
detailType: 'hardcordeddetailtype'
19+
source: 'hardcodedsource'
20+
eventBusName: { Ref: 'YourBus' }
21+
cors: true
22+
# set detailType and source as path parameter
23+
- eventbridge:
24+
path: /eventbridge2/{myDetailType}/{mySource}
25+
method: post
26+
detailType:
27+
pathParam: myDetailType
28+
source:
29+
pathParam: mySource
30+
eventBusName: { Ref: 'YourBus' }
31+
cors: true
32+
# set detailType and source as query parameter
33+
- eventbridge:
34+
path: /eventbridge3
35+
method: post
36+
detailType:
37+
queryStringParam: myDetailTypeKey
38+
source:
39+
queryStringParam: mySourceKey
40+
eventBusName: { Ref: 'YourBus' }
41+
cors: true
42+
# set value of detailType api event id by default
43+
- eventbridge:
44+
path: /eventbridge4
45+
method: post
46+
eventBusName: { Ref: 'YourBus' }
47+
source: mySourceName
48+
cors: true
49+
50+
resources:
51+
Resources:
52+
YourBus:
53+
Type: AWS::Events::EventBus
54+
Properties:
55+
Name: YourEventBus-${self:provider.stage}-multi
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
'use strict'
2+
3+
const expect = require('chai').expect
4+
const fetch = require('node-fetch')
5+
const { deployWithRandomStage, removeService } = require('../../../utils')
6+
7+
describe('Multiple EventBridge Proxy Integrations Test', () => {
8+
let endpoint
9+
let stage
10+
const config = '__tests__/integration/eventbridge/multiple-integrations/service/serverless.yml'
11+
12+
beforeAll(async () => {
13+
const result = await deployWithRandomStage(config)
14+
stage = result.stage
15+
endpoint = result.endpoint
16+
})
17+
18+
afterAll(() => {
19+
removeService(stage, config)
20+
})
21+
22+
it('should get correct response from eventbridge proxy endpoints with static detailType and source', async () => {
23+
const testEndpoint = `${endpoint}/eventbridge1`
24+
const response = await fetch(testEndpoint, {
25+
method: 'POST',
26+
headers: { 'Content-Type': 'application/json' },
27+
body: JSON.stringify({ message: `data for event bus` })
28+
})
29+
expect(response.headers.get('access-control-allow-origin')).to.deep.equal('*')
30+
expect(response.status).to.be.equal(200)
31+
const body = await response.json()
32+
expect(body).to.have.own.property('Entries')
33+
expect(body).to.have.own.property('FailedEntryCount')
34+
expect(body.FailedEntryCount).to.equal(0)
35+
})
36+
37+
it('should get correct response from eventbridge proxy endpoints with detailType and source as path parameters', async () => {
38+
const testEndpoint = `${endpoint}/eventbridge2/myDetailType/mySource`
39+
const response = await fetch(testEndpoint, {
40+
method: 'POST',
41+
headers: { 'Content-Type': 'application/json' },
42+
body: JSON.stringify({ message: `data for event bus` })
43+
})
44+
expect(response.headers.get('access-control-allow-origin')).to.deep.equal('*')
45+
expect(response.status).to.be.equal(200)
46+
const body = await response.json()
47+
expect(body).to.have.own.property('Entries')
48+
expect(body).to.have.own.property('FailedEntryCount')
49+
expect(body.FailedEntryCount).to.equal(0)
50+
})
51+
52+
it('should get correct response from eventbridge proxy endpoints with detailType and source as query parameters', async () => {
53+
const testEndpoint = `${endpoint}/eventbridge3?myDetailTypeKey=myDetailTypeValue&mySourceKey=mySourceValue`
54+
const response = await fetch(testEndpoint, {
55+
method: 'POST',
56+
headers: { 'Content-Type': 'application/json' },
57+
body: JSON.stringify({ message: `data for event bus` })
58+
})
59+
expect(response.headers.get('access-control-allow-origin')).to.deep.equal('*')
60+
expect(response.status).to.be.equal(200)
61+
const body = await response.json()
62+
expect(body).to.have.own.property('Entries')
63+
expect(body).to.have.own.property('FailedEntryCount')
64+
expect(body.FailedEntryCount).to.equal(0)
65+
})
66+
67+
it('should get correct response from eventbridge proxy endpoints without given detailType and source parameters', async () => {
68+
const testEndpoint = `${endpoint}/eventbridge4`
69+
const response = await fetch(testEndpoint, {
70+
method: 'POST',
71+
headers: { 'Content-Type': 'application/json' },
72+
body: JSON.stringify({ message: `data for event bus` })
73+
})
74+
expect(response.headers.get('access-control-allow-origin')).to.deep.equal('*')
75+
expect(response.status).to.be.equal(200)
76+
const body = await response.json()
77+
expect(body).to.have.own.property('Entries')
78+
expect(body).to.have.own.property('FailedEntryCount')
79+
expect(body.FailedEntryCount).to.equal(0)
80+
})
81+
})
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
service: eventbridge-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+
- eventbridge:
15+
path: /eventbridge
16+
method: post
17+
eventBusName: { Ref: 'YourBus' }
18+
source: mySourceName
19+
cors: true
20+
21+
resources:
22+
Resources:
23+
YourBus:
24+
Type: AWS::Events::EventBus
25+
Properties:
26+
Name: YourEventBus-${self:provider.stage}-single
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict'
2+
3+
const expect = require('chai').expect
4+
const fetch = require('node-fetch')
5+
const { deployWithRandomStage, removeService } = require('../../../utils')
6+
7+
describe('Single EventBridge Proxy Integration Test', () => {
8+
let endpoint
9+
let stage
10+
const config = '__tests__/integration/eventbridge/single-integration/service/serverless.yml'
11+
12+
beforeAll(async () => {
13+
const result = await deployWithRandomStage(config)
14+
stage = result.stage
15+
endpoint = result.endpoint
16+
})
17+
18+
afterAll(() => {
19+
removeService(stage, config)
20+
})
21+
22+
it('should get correct response from eventbridge proxy endpoint', async () => {
23+
const testEndpoint = `${endpoint}/eventbridge`
24+
25+
const response = await fetch(testEndpoint, {
26+
method: 'POST',
27+
headers: { 'Content-Type': 'application/json' },
28+
body: JSON.stringify({ message: 'some data' })
29+
})
30+
expect(response.headers.get('access-control-allow-origin')).to.deep.equal('*')
31+
expect(response.status).to.be.equal(200)
32+
const body = await response.json()
33+
expect(body).to.have.own.property('Entries')
34+
expect(body).to.have.own.property('FailedEntryCount')
35+
expect(body.FailedEntryCount).to.equal(0)
36+
})
37+
})

lib/apiGateway/schema.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,23 @@ const dynamodbDefaultKeyScheme = Joi.object()
163163
)
164164
)
165165

166+
// EventBridge detailType parameter
167+
const detailType = Joi.alternatives().try([
168+
Joi.string(),
169+
Joi.object()
170+
.keys({
171+
pathParam: Joi.string(),
172+
queryStringParam: Joi.string()
173+
})
174+
.xor('pathParam', 'queryStringParam')
175+
.error(
176+
customErrorBuilder(
177+
'object.xor',
178+
'key must contain "pathParam" or "queryStringParam and only one'
179+
)
180+
)
181+
])
182+
166183
const request = Joi.object({
167184
template: Joi.object().required()
168185
})
@@ -175,7 +192,7 @@ const response = Joi.object({
175192
})
176193
})
177194

178-
const allowedProxies = ['kinesis', 'sqs', 's3', 'sns', 'dynamodb']
195+
const allowedProxies = ['kinesis', 'sqs', 's3', 'sns', 'dynamodb', 'eventbridge']
179196

180197
const proxiesSchemas = {
181198
kinesis: Joi.object({
@@ -217,6 +234,13 @@ const proxiesSchemas = {
217234
hashKey: dynamodbDefaultKeyScheme.required(),
218235
rangeKey: dynamodbDefaultKeyScheme
219236
})
237+
}),
238+
eventbridge: Joi.object({
239+
eventbridge: proxy.append({
240+
eventBusName: stringOrRef.required(),
241+
source: stringOrRef.required(),
242+
detailType
243+
})
220244
})
221245
}
222246

lib/apiGateway/validate.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe('#validateServiceProxies()', () => {
4040

4141
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
4242
serverless.classes.Error,
43-
'Invalid APIG proxy "xxxxx". This plugin supported Proxies are: kinesis, sqs, s3, sns, dynamodb.'
43+
'Invalid APIG proxy "xxxxx". This plugin supported Proxies are: kinesis, sqs, s3, sns, dynamodb, eventbridge.'
4444
)
4545
})
4646

lib/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ const compileSnsServiceProxy = require('./package/sns/compileSnsServiceProxy')
2929
const compileMethodsToDynamodb = require('./package/dynamodb/compileMethodsToDynamodb')
3030
const compileIamRoleToDynamodb = require('./package/dynamodb/compileIamRoleToDynamodb')
3131
const compileDynamodbServiceProxy = require('./package/dynamodb/compileDynamodbServiceProxy')
32+
// EventBridge
33+
const compileMethodsToEventBridge = require('./package/eventbridge/compileMethodsToEventBridge')
34+
const compileIamRoleToEventBridge = require('./package/eventbridge/compileIamRoleToEventBridge')
35+
const compileEventBridgeServiceProxy = require('./package/eventbridge/compileEventBridgeServiceProxy')
3236

3337
class ServerlessApigatewayServiceProxy {
3438
constructor(serverless, options) {
@@ -60,6 +64,9 @@ class ServerlessApigatewayServiceProxy {
6064
compileMethodsToDynamodb,
6165
compileIamRoleToDynamodb,
6266
compileDynamodbServiceProxy,
67+
compileMethodsToEventBridge,
68+
compileIamRoleToEventBridge,
69+
compileEventBridgeServiceProxy,
6370
getStackInfo,
6471
validate,
6572
methods,
@@ -90,6 +97,9 @@ class ServerlessApigatewayServiceProxy {
9097
// DynamoDB getProxy
9198
await this.compileDynamodbServiceProxy()
9299

100+
// EventBridge proxy
101+
await this.compileEventBridgeServiceProxy()
102+
93103
await this.mergeDeployment()
94104
}
95105
},

0 commit comments

Comments
 (0)