Skip to content
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
29 changes: 23 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,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 @@ -191,6 +192,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 @@ -200,19 +205,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 @@ -2,7 +2,7 @@ import { RequestListener } from "http";
import { Handler } from "aws-lambda";
import Logger from "./logger";

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