Skip to content

Commit daa1784

Browse files
committed
feat(eventbridge): add eventbridge support
1 parent fc0ad38 commit daa1784

File tree

14 files changed

+1585
-2
lines changed

14 files changed

+1585
-2
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 and source to api event id by default
43+
- eventbridge:
44+
path: /eventbridge4
45+
method: post
46+
eventBusName: { Ref: 'YourBus' }
47+
cors: true
48+
49+
resources:
50+
Resources:
51+
YourBus:
52+
Type: AWS::Events::EventBus
53+
Properties:
54+
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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
cors: true
19+
20+
resources:
21+
Resources:
22+
YourBus:
23+
Type: AWS::Events::EventBus
24+
Properties:
25+
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: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,40 @@ 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+
183+
// EventBridge source parameter
184+
const source = Joi.alternatives().try([
185+
Joi.string(),
186+
Joi.object()
187+
.keys({
188+
pathParam: Joi.string(),
189+
queryStringParam: Joi.string()
190+
})
191+
.xor('pathParam', 'queryStringParam')
192+
.error(
193+
customErrorBuilder(
194+
'object.xor',
195+
'key must contain "pathParam" or "queryStringParam and only one'
196+
)
197+
)
198+
])
199+
166200
const request = Joi.object({
167201
template: Joi.object().required()
168202
})
@@ -175,7 +209,7 @@ const response = Joi.object({
175209
})
176210
})
177211

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

180214
const proxiesSchemas = {
181215
kinesis: Joi.object({
@@ -217,6 +251,9 @@ const proxiesSchemas = {
217251
hashKey: dynamodbDefaultKeyScheme.required(),
218252
rangeKey: dynamodbDefaultKeyScheme
219253
})
254+
}),
255+
eventbridge: Joi.object({
256+
eventbridge: proxy.append({ eventBusName: stringOrRef.required(), source, detailType })
220257
})
221258
}
222259

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
},

lib/index.test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ describe('#index()', () => {
9898
path: '/dynamodb',
9999
method: 'post'
100100
}
101+
},
102+
{
103+
eventbridge: {
104+
path: '/eventbridge',
105+
method: 'post'
106+
}
101107
}
102108
]
103109
}
@@ -129,6 +135,9 @@ describe('#index()', () => {
129135
const compileDynamodbServiceProxyStub = sinon
130136
.stub(serverlessApigatewayServiceProxy, 'compileDynamodbServiceProxy')
131137
.returns(BbPromise.resolve())
138+
const compileEventBridgeServiceProxyStub = sinon
139+
.stub(serverlessApigatewayServiceProxy, 'compileEventBridgeServiceProxy')
140+
.returns(BbPromise.resolve())
132141
const mergeDeploymentStub = sinon
133142
.stub(serverlessApigatewayServiceProxy, 'mergeDeployment')
134143
.returns(BbPromise.resolve())
@@ -145,6 +154,7 @@ describe('#index()', () => {
145154
expect(compileS3ServiceProxyStub.calledOnce).to.be.equal(true)
146155
expect(compileSnsServiceProxyStub.calledOnce).to.be.equal(true)
147156
expect(compileDynamodbServiceProxyStub.calledOnce).to.be.equal(true)
157+
expect(compileEventBridgeServiceProxyStub.calledOnce).to.be.equal(true)
148158
expect(mergeDeploymentStub.calledOnce).to.be.equal(true)
149159
})
150160

@@ -157,6 +167,7 @@ describe('#index()', () => {
157167
serverlessApigatewayServiceProxy.compileS3ServiceProxy.restore()
158168
serverlessApigatewayServiceProxy.compileSnsServiceProxy.restore()
159169
serverlessApigatewayServiceProxy.compileDynamodbServiceProxy.restore()
170+
serverlessApigatewayServiceProxy.compileEventBridgeServiceProxy.restore()
160171
serverlessApigatewayServiceProxy.mergeDeployment.restore()
161172
})
162173

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict'
2+
module.exports = {
3+
compileEventBridgeServiceProxy() {
4+
this.compileIamRoleToEventBridge()
5+
this.compileMethodsToEventBridge()
6+
}
7+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const sinon = require('sinon')
2+
const chai = require('chai')
3+
const Serverless = require('serverless/lib/Serverless')
4+
const AwsProvider = require('serverless/lib/plugins/aws/provider/awsProvider')
5+
const ServerlessApigatewayServiceProxy = require('../../index')
6+
7+
chai.use(require('sinon-chai'))
8+
9+
const expect = chai.expect
10+
11+
describe('#compileEventBridgeServiceProxy', () => {
12+
let serverless
13+
let serverlessApigatewayServiceProxy
14+
15+
beforeEach(() => {
16+
serverless = new Serverless()
17+
serverless.servicePath = true
18+
serverless.service.service = 'apigw-service-proxy'
19+
const options = {
20+
stage: 'dev',
21+
region: 'us-east-1'
22+
}
23+
serverless.setProvider('aws', new AwsProvider(serverless))
24+
serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }
25+
serverlessApigatewayServiceProxy = new ServerlessApigatewayServiceProxy(serverless, options)
26+
})
27+
28+
it('should call compileIamRoleToEventBridge and compileMethodsToEventBridge once', () => {
29+
const compileIamRoleToEventBridgeStub = sinon.stub(
30+
serverlessApigatewayServiceProxy,
31+
'compileIamRoleToEventBridge'
32+
)
33+
const compileMethodsToEventBridgeStub = sinon.stub(
34+
serverlessApigatewayServiceProxy,
35+
'compileMethodsToEventBridge'
36+
)
37+
38+
serverlessApigatewayServiceProxy.compileEventBridgeServiceProxy()
39+
40+
expect(compileIamRoleToEventBridgeStub).to.have.been.calledOnce
41+
expect(compileMethodsToEventBridgeStub).to.have.been.calledOnce
42+
43+
serverlessApigatewayServiceProxy.compileIamRoleToEventBridge.restore()
44+
serverlessApigatewayServiceProxy.compileMethodsToEventBridge.restore()
45+
})
46+
})

0 commit comments

Comments
 (0)