Skip to content

Commit

Permalink
feat: add Eventbridge events (#477)
Browse files Browse the repository at this point in the history
  • Loading branch information
cnuss authored Apr 18, 2022
1 parent 8a3e1f0 commit a25cbad
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 8 deletions.
29 changes: 23 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,10 @@ serverlessExpress({

#### eventSourceRoutes

Introduced in `@vendia/serverless-express@4.4.0` native support for `aws:sns` and `aws:dynamodb` events were introduced.

A single function can be configured to handle events from SNS and DynamoDB, as well as the previously supported events.
A single function can be configured to handle additional kinds of AWS events:
- SNS
- DynamoDB Streams
- EventBridge Events (formerlly CloudWatch Events)

Assuming the following function configuration in `serverless.yml`:

Expand All @@ -190,6 +191,10 @@ functions:
- stream:
type: dynamodb
arn: arn:aws:dynamodb:us-east-1:012345678990:table/my-table/stream/2021-07-15T15:05:51.683
- eventBridge:
pattern:
source:
- aws.cloudformation
```

And the following configuration:
Expand All @@ -199,19 +204,31 @@ serverlessExpress({
app,
eventSourceRoutes: {
'AWS_SNS': '/sns',
'AWS_DYNAMODB': '/dynamodb'
'AWS_DYNAMODB': '/dynamodb',
'AWS_EVENTBRIDGE': '/eventbridge',
}
})
```

Alternatively, to handle only SNS events (the keys in the map are **optional**)

```js
serverlessExpress({
app,
eventSourceRoutes: {
'AWS_SNS': '/sns',
}
})
```

Events from SNS and DynamoDB will `POST` to the routes configured in Express to handle `/sns` and `/dynamodb`,
respectively.
Events will `POST` to the routes configured.

Also, to ensure the events propagated from an internal event and not externally, it is **highly recommended** to
ensure the `Host` header matches:

- SNS: `sns.amazonaws.com`
- DynamoDB: `dynamodb.amazonaws.com`
- EventBridge: `events.amazonaws.com`

### logSettings

Expand Down
27 changes: 27 additions & 0 deletions __tests__/unit.eventbridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const eventSources = require('../src/event-sources')
const testUtils = require('./utils')

const dynamodbEventSource = eventSources.getEventSource({
eventSourceName: 'AWS_EVENTBRIDGE'
})

test('request is correct', () => {
const req = getReq({ event: testUtils.eventbridgeEvent })
expect(typeof req).toEqual('object')
expect(req.headers).toEqual({ host: 'events.amazonaws.com' })
expect(req.body).toEqual(testUtils.eventbridgeEvent)
expect(req.method).toEqual('POST')
})

test('request is correct (scheduled)', () => {
const req = getReq({ event: testUtils.eventbridgeScheduledEvent })
expect(typeof req).toEqual('object')
expect(req.headers).toEqual({ host: 'events.amazonaws.com' })
expect(req.body).toEqual(testUtils.eventbridgeScheduledEvent)
expect(req.method).toEqual('POST')
})

function getReq ({ event }) {
const request = dynamodbEventSource.getRequest({ event })
return request
}
46 changes: 45 additions & 1 deletion __tests__/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,38 @@ const snsEvent = {
]
}

// Sample event from https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents.html
const eventbridgeEvent = {
version: '0',
id: 'fe8d3c65-xmpl-c5c3-2c87-81584709a377',
'detail-type': 'RDS DB Instance Event',
source: 'aws.rds',
account: '123456789012',
time: '2020-04-28T07:20:20Z',
region: 'us-east-2',
resources: ['arn:aws:rds:us-east-2:123456789012:db:rdz6xmpliljlb1'],
detail: {
EventCategories: ['backup'],
SourceType: 'DB_INSTANCE',
SourceArn: 'arn:aws:rds:us-east-2:123456789012:db:rdz6xmpliljlb1',
Date: '2020-04-28T07:20:20.112Z',
Message: 'Finished DB Instance backup',
SourceIdentifier: 'rdz6xmpliljlb1'
}
}

const eventbridgeScheduledEvent = {
version: '0',
account: '123456789012',
region: 'us-east-2',
detail: {},
'detail-type': 'Scheduled Event',
source: 'aws.events',
time: '2019-03-01T01:23:45Z',
id: 'cdc73f9d-aea9-11e3-9d5a-835b769c0d9c',
resources: ['arn:aws:events:us-east-2:123456789012:rule/my-schedule']
}

describe('getEventSourceNameBasedOnEvent', () => {
test('throws error on empty event', () => {
expect(() => getEventSourceNameBasedOnEvent({ event: {} })).toThrow(
Expand All @@ -161,10 +193,22 @@ describe('getEventSourceNameBasedOnEvent', () => {
const result = getEventSourceNameBasedOnEvent({ event: snsEvent })
expect(result).toEqual('AWS_SNS')
})

test('recognizes eventbridge event', () => {
const result = getEventSourceNameBasedOnEvent({ event: eventbridgeEvent })
expect(result).toEqual('AWS_EVENTBRIDGE')
})

test('recognizes eventbridge scheduled event', () => {
const result = getEventSourceNameBasedOnEvent({ event: eventbridgeScheduledEvent })
expect(result).toEqual('AWS_EVENTBRIDGE')
})
})

module.exports = {
samHttpApiEvent,
dynamoDbEvent,
snsEvent
snsEvent,
eventbridgeEvent,
eventbridgeScheduledEvent
}
2 changes: 1 addition & 1 deletion src/configure.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Handler } from "aws-lambda";
import Logger from "./logger";
import Framework from "./frameworks";

type EventSources = "AWS_SNS" | "AWS_DYNAMODB";
type EventSources = "AWS_SNS" | "AWS_DYNAMODB" | "AWS_EVENTBRIDGE";

interface EventSource {
getRequest?: any; // TODO:
Expand Down
18 changes: 18 additions & 0 deletions src/event-sources/aws/eventbridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const { emptyResponseMapper } = require('../utils')

const getRequestValuesFromEventBridge = ({ event }) => {
const method = 'POST'
const headers = { host: 'events.amazonaws.com' }
const body = event

return {
method,
headers,
body
}
}

module.exports = {
getRequest: getRequestValuesFromEventBridge,
getResponse: emptyResponseMapper
}
3 changes: 3 additions & 0 deletions src/event-sources/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const awsAlbEventSource = require('./aws/alb')
const awsLambdaEdgeEventSource = require('./aws/lambda-edge')
const awsSnsEventSource = require('./aws/sns')
const awsDynamoDbEventSource = require('./aws/dynamodb')
const awsEventBridgeEventSource = require('./aws/eventbridge')

function getEventSource ({ eventSourceName }) {
switch (eventSourceName) {
Expand All @@ -19,6 +20,8 @@ function getEventSource ({ eventSourceName }) {
return awsDynamoDbEventSource
case 'AWS_SNS':
return awsSnsEventSource
case 'AWS_EVENTBRIDGE':
return awsEventBridgeEventSource
default:
throw new Error('Couldn\'t detect valid event source.')
}
Expand Down
20 changes: 20 additions & 0 deletions src/event-sources/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ function getEventSourceNameBasedOnEvent ({
if (event.requestContext) {
return event.version === '2.0' ? 'AWS_API_GATEWAY_V2' : 'AWS_API_GATEWAY_V1'
}
if (
event.version &&
event.version === '0' &&
event.id &&
event['detail-type'] &&
event.source &&
event.source.startsWith('aws.') && // Might need to adjust this for "Partner Sources", e.g. Auth0, Datadog, etc
event.account &&
event.time &&
event.region &&
event.resources &&
Array.isArray(event.resources) &&
event.detail &&
typeof event.detail === 'object' &&
!Array.isArray(event.detail)
) {
// AWS doesn't have a defining Event Source here, so we're being incredibly selective on the structure
// Ref: https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents.html
return 'AWS_EVENTBRIDGE'
}

throw new Error('Unable to determine event source based on event.')
}
Expand Down

0 comments on commit a25cbad

Please sign in to comment.