Skip to content

Commit cbee760

Browse files
ChristophWindpassingerChristoph Windpassinger
andauthored
feat: add VPC Lattice support (#696)
* First draft * Add logging * Logic analog to ALB * Logs * Fix for method * Remove log * Test event body without Buffer * Keep using the Buffer * Adapt headers logic * Header adaptions in response * Send response headers as array * feat: unit tests and fixes * feat: update README * feat: handle v1 event * feat: fix * feat: update docs * feat: remove query from path * feat: fix test * feat: fix test * feat: another try * feat: test * feat: cleanup --------- Co-authored-by: Christoph Windpassinger <extern.christoph.windpassinger@vwfs.io>
1 parent f4f9fd5 commit cbee760

File tree

6 files changed

+113
-3
lines changed

6 files changed

+113
-3
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ The _out-binding_ parameter `"name": "$return"` is important for Serverless Expr
144144

145145
1. Improved API - Simpler for end-user to use and configure.
146146
1. Promise resolution mode by default. Can specify `resolutionMode` to use `"CONTEXT"` or `"CALLBACK"`
147-
1. Additional event sources - API Gateway V1 (REST API), API Gateway V2 (HTTP API), ALB, Lambda@Edge
147+
1. Additional event sources - API Gateway V1 (REST API), API Gateway V2 (HTTP API), ALB, Lambda@Edge, VPC Lattice
148148
1. Custom event source - If you have another event source you'd like to use that we don't natively support, check out the [DynamoDB Example](examples/custom-mapper-dynamodb)
149149
1. Implementation uses mock Request/Response objects instead of running a server listening on a local socket. Thanks to @dougmoscrop from https://github.com/dougmoscrop/serverless-http
150150
1. Automatic `isBase64Encoded` without specifying `binaryMimeTypes`. Use `binarySettings` to customize. Thanks to @dougmoscrop from https://github.com/dougmoscrop/serverless-http
@@ -201,7 +201,7 @@ Set this to true to have serverless-express include the error stack trace in the
201201

202202
### eventSource
203203

204-
serverless-express natively supports API Gateway, ALB, and Lambda@Edge. If you want to use Express with other AWS Services integrated with Lambda you can provide your own custom request/response mappings via `eventSource`. See the [custom-mapper-dynamodb example](examples/custom-mapper-dynamodb).
204+
serverless-express natively supports API Gateway, ALB, Lambda@Edge and VPC Lattice (only V2 events - event source `AWS_VPC_LATTICE_V2`). If you want to use Express with other AWS Services integrated with Lambda you can provide your own custom request/response mappings via `eventSource`. See the [custom-mapper-dynamodb example](examples/custom-mapper-dynamodb).
205205

206206
```js
207207
function requestMapper ({ event }) {

__tests__/unit.lattice.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const eventSources = require('../src/event-sources')
2+
const testUtils = require('./utils')
3+
4+
const latticeEventSource = eventSources.getEventSource({
5+
eventSourceName: 'AWS_VPC_LATTICE_V2'
6+
})
7+
8+
test('request is correct', () => {
9+
const req = getReq()
10+
expect(typeof req).toEqual('object')
11+
expect(req.method).toEqual('GET')
12+
expect(req.path).toEqual('/test-path?key=value')
13+
expect(req.headers).toEqual({
14+
'x-custom-header': 'test-header',
15+
'content-length': 15
16+
})
17+
expect(Buffer.isBuffer(req.body)).toEqual(true)
18+
})
19+
20+
test('response is correct', () => {
21+
const res = getRes()
22+
expect(typeof res).toEqual('object')
23+
expect(res.statusCode).toEqual(200)
24+
expect(res.body).toEqual('{"message":"Hello, world!"}')
25+
expect(res.headers).toEqual({ 'Content-Type': 'application/json' })
26+
expect(res.isBase64Encoded).toEqual(false)
27+
})
28+
29+
function getReq () {
30+
const event = testUtils.latticeEvent
31+
const request = latticeEventSource.getRequest({ event })
32+
return request
33+
}
34+
35+
function getRes () {
36+
const event = testUtils.latticeEvent
37+
const response = latticeEventSource.getResponse({
38+
event,
39+
statusCode: 200,
40+
body: '{"message":"Hello, world!"}',
41+
headers: { 'Content-Type': 'application/json' },
42+
isBase64Encoded: false
43+
})
44+
return response
45+
}

__tests__/utils.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,23 @@ const selfManagedKafkaEvent = {
267267
}
268268
}
269269

270+
const latticeEvent = {
271+
version: '2.0',
272+
method: 'GET',
273+
path: '/test-path?key=value',
274+
queryStringParameters: { key: ['value'] },
275+
headers: { 'x-custom-header': ['test-header'] },
276+
body: JSON.stringify({ key: 'value' }),
277+
requestContext: {
278+
serviceNetworkArn: 'arn:aws:vpc-lattice:eu-central-1:123456789:servicenetwork/sn-123',
279+
serviceArn: 'arn:aws:vpc-lattice:eu-central-1:014538609594:service/svc-123',
280+
targetGroupArn: 'arn:aws:vpc-lattice:eu-central-1:014538609594:targetgroup/tg-123',
281+
identity: {
282+
sourceVpcArn: 'arn:aws:ec2:eu-central-1:123456789:vpc/vpc-123'
283+
}
284+
}
285+
}
286+
270287
describe('getEventSourceNameBasedOnEvent', () => {
271288
test('throws error on empty event', () => {
272289
expect(() => getEventSourceNameBasedOnEvent({ event: {} })).toThrow(
@@ -318,6 +335,11 @@ describe('getEventSourceNameBasedOnEvent', () => {
318335
const result = getEventSourceNameBasedOnEvent({ event: eventbridgeCustomerEvent })
319336
expect(result).toEqual('AWS_EVENTBRIDGE')
320337
})
338+
339+
test('recognizes lattice event', () => {
340+
const result = getEventSourceNameBasedOnEvent({ event: latticeEvent })
341+
expect(result).toEqual('AWS_VPC_LATTICE_V2')
342+
})
321343
})
322344

323345
module.exports = {
@@ -329,5 +351,6 @@ module.exports = {
329351
eventbridgeScheduledEvent,
330352
eventbridgeCustomerEvent,
331353
kinesisDataStreamEvent,
332-
selfManagedKafkaEvent
354+
selfManagedKafkaEvent,
355+
latticeEvent
333356
}

src/event-sources/aws/lattice.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const { getRequestValuesFromEvent, getCommaDelimitedHeaders } = require('../utils')
2+
3+
const getRequestValuesFromLatticeEvent = ({ event }) => {
4+
const values = getRequestValuesFromEvent({
5+
event,
6+
method: event.method,
7+
path: event.path // query parameters are already included in the path
8+
})
9+
10+
// Lattice always sends the headers as array that needs to be converted to a comma delimited string
11+
values.headers = getCommaDelimitedHeaders({ headersMap: event.headers, lowerCaseKey: true })
12+
13+
return values
14+
}
15+
16+
const getResponseToLattice = ({
17+
statusCode,
18+
body,
19+
headers: responseHeaders,
20+
isBase64Encoded
21+
}) => {
22+
const headers = getCommaDelimitedHeaders({ headersMap: responseHeaders })
23+
24+
return {
25+
statusCode,
26+
body,
27+
headers,
28+
isBase64Encoded
29+
}
30+
}
31+
32+
module.exports = {
33+
getRequest: getRequestValuesFromLatticeEvent,
34+
getResponse: getResponseToLattice
35+
}

src/event-sources/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const awsKinesisEventSource = require('./aws/kinesis')
1212
const awsS3 = require('./aws/s3')
1313
const awsStepFunctionsEventSource = require('./aws/step-functions')
1414
const awsSelfManagedKafkaEventSource = require('./aws/self-managed-kafka')
15+
const awsLatticeEventSource = require('./aws/lattice')
1516

1617
function getEventSource ({ eventSourceName }) {
1718
switch (eventSourceName) {
@@ -43,6 +44,8 @@ function getEventSource ({ eventSourceName }) {
4344
return awsStepFunctionsEventSource
4445
case 'AWS_SELF_MANAGED_KAFKA':
4546
return awsSelfManagedKafkaEventSource
47+
case 'AWS_VPC_LATTICE_V2':
48+
return awsLatticeEventSource
4649
default:
4750
throw new Error('Couldn\'t detect valid event source.')
4851
}

src/event-sources/utils.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ function getEventSourceNameBasedOnEvent ({
7171
event
7272
}) {
7373
if (event.requestContext && event.requestContext.elb) return 'AWS_ALB'
74+
if (event.headers?.['x-amzn-lattice-network']) {
75+
console.warn('Lattice event v1 is not supported. Please use Lattice event v2.')
76+
}
77+
if (event.requestContext && event.requestContext.serviceNetworkArn && event.requestContext.serviceArn) return 'AWS_VPC_LATTICE_V2'
7478
if (event.eventSource === 'SelfManagedKafka') return 'AWS_SELF_MANAGED_KAFKA'
7579
if (event.Records) {
7680
const eventSource = event.Records[0] ? event.Records[0].EventSource || event.Records[0].eventSource : undefined

0 commit comments

Comments
 (0)