Skip to content

Commit b5787c0

Browse files
authored
Merge pull request #6272 from christophgysin/feature/support-external-websocket-api
Feature/support external websocket api
2 parents de12ed3 + 142a9d8 commit b5787c0

File tree

12 files changed

+193
-103
lines changed

12 files changed

+193
-103
lines changed

docs/providers/aws/events/apigateway.md

+26-2
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,7 @@ provider:
11031103
apiGateway:
11041104
restApiId: xxxxxxxxxx # REST API resource ID. Default is generated by the framework
11051105
restApiRootResourceId: xxxxxxxxxx # Root resource, represent as / path
1106+
websocketApiId: xxxxxxxxxx # Websocket API resource ID. Default is generated by the framewok
11061107
description: Some Description # optional - description of deployment history
11071108
11081109
functions:
@@ -1119,6 +1120,7 @@ provider:
11191120
apiGateway:
11201121
restApiId: xxxxxxxxxx
11211122
restApiRootResourceId: xxxxxxxxxx
1123+
websocketApiId: xxxxxxxxxx
11221124
description: Some Description
11231125
11241126
functions:
@@ -1136,6 +1138,7 @@ provider:
11361138
apiGateway:
11371139
restApiId: xxxxxxxxxx
11381140
restApiRootResourceId: xxxxxxxxxx
1141+
websocketApiId: xxxxxxxxxx
11391142
description: Some Description
11401143
11411144
functions:
@@ -1155,6 +1158,7 @@ provider:
11551158
apiGateway:
11561159
restApiId: xxxxxxxxxx
11571160
restApiRootResourceId: xxxxxxxxxx
1161+
websocketApiId: xxxxxxxxxx
11581162
description: Some Description
11591163
restApiResources:
11601164
/posts: xxxxxxxxxx
@@ -1170,6 +1174,7 @@ provider:
11701174
apiGateway:
11711175
restApiId: xxxxxxxxxx
11721176
restApiRootResourceId: xxxxxxxxxx
1177+
websocketApiId: xxxxxxxxxx
11731178
description: Some Description
11741179
restApiResources:
11751180
/posts: xxxxxxxxxx
@@ -1188,6 +1193,7 @@ provider:
11881193
apiGateway:
11891194
restApiId: xxxxxxxxxx
11901195
# restApiRootResourceId: xxxxxxxxxx # Optional
1196+
websocketApiId: xxxxxxxxxx
11911197
description: Some Description
11921198
restApiResources:
11931199
/posts: xxxxxxxxxx
@@ -1213,7 +1219,7 @@ functions:
12131219

12141220
### Easiest and CI/CD friendly example of using shared API Gateway and API Resources.
12151221

1216-
You can define your API Gateway resource in its own service and export the `restApiId` and `restApiRootResourceId` using cloudformation cross-stack references.
1222+
You can define your API Gateway resource in its own service and export the `restApiId`, `restApiRootResourceId` and `websocketApiId` using cloudformation cross-stack references.
12171223

12181224
```yml
12191225
service: my-api
@@ -1231,6 +1237,13 @@ resources:
12311237
Properties:
12321238
Name: MyApiGW
12331239
1240+
MyWebsocketApi:
1241+
Type: AWS::ApiGatewayV2::Api
1242+
Properties:
1243+
Name: MyWebsocketApi
1244+
ProtocolType: WEBSOCKET
1245+
RouteSelectionExpression: '$request.body.action'
1246+
12341247
Outputs:
12351248
apiGatewayRestApiId:
12361249
Value:
@@ -1245,9 +1258,16 @@ resources:
12451258
- RootResourceId
12461259
Export:
12471260
Name: MyApiGateway-rootResourceId
1261+
1262+
websocketApiId:
1263+
Value:
1264+
Ref: MyWebsocketApi
1265+
Export:
1266+
Name: MyApiGateway-websocketApiId
1267+
12481268
```
12491269

1250-
This creates API gateway and then exports the `restApiId` and `rootResourceId` values using cloudformation cross stack output.
1270+
This creates API gateway and then exports the `restApiId`, `rootResourceId` and `websocketApiId` values using cloudformation cross stack output.
12511271
We will import this and reference in future services.
12521272

12531273
```yml
@@ -1259,6 +1279,8 @@ provider:
12591279
'Fn::ImportValue': MyApiGateway-restApiId
12601280
restApiRootResourceId:
12611281
'Fn::ImportValue': MyApiGateway-rootResourceId
1282+
websocketApiId:
1283+
'Fn::ImportValue': MyApiGateway-websocketApiId
12621284
12631285
functions:
12641286
service-a-functions
@@ -1272,6 +1294,8 @@ provider:
12721294
'Fn::ImportValue': MyApiGateway-restApiId
12731295
restApiRootResourceId:
12741296
'Fn::ImportValue': MyApiGateway-rootResourceId
1297+
websocketApiId:
1298+
'Fn::ImportValue': MyApiGateway-websocketApiId
12751299
12761300
functions:
12771301
service-b-functions

docs/providers/aws/guide/serverless.yml.md

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ provider:
6060
restApiResources: # List of existing resources that were created in the REST API. This is required or the stack will be conflicted
6161
'/users': xxxxxxxxxx
6262
'/users/create': xxxxxxxxxx
63+
websocketApiId: # Websocket API resource ID. Default is generated by the framewok
6364
apiKeySourceType: HEADER # Source of API key for usage plan. HEADER or AUTHORIZER.
6465
minimumCompressionSize: 1024 # Compress response when larger than specified size in bytes (must be between 0 and 10485760)
6566
description: Some Description # Optional description for the API Gateway stage deployment

lib/plugins/aws/package/compile/events/websockets/lib/api.js

+7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ const BbPromise = require('bluebird');
55

66
module.exports = {
77
compileApi() {
8+
const apiGateway = this.serverless.service.provider.apiGateway || {};
9+
10+
// immediately return if we're using an external websocket API id
11+
if (apiGateway.websocketApiId) {
12+
return BbPromise.resolve();
13+
}
14+
815
this.websocketsApiLogicalId = this.provider.naming.getWebsocketsApiLogicalId();
916

1017
const RouteSelectionExpression = this.serverless.service.provider

lib/plugins/aws/package/compile/events/websockets/lib/api.test.js

+12
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ describe('#compileApi()', () => {
5050
});
5151
}));
5252

53+
it('should ignore API resource creation if there is predefined websocketApi config', () => {
54+
awsCompileWebsocketsEvents.serverless.service.provider.apiGateway = {
55+
websocketApiId: '5ezys3sght',
56+
};
57+
return awsCompileWebsocketsEvents.compileApi().then(() => {
58+
const resources = awsCompileWebsocketsEvents.serverless.service.provider
59+
.compiledCloudFormationTemplate.Resources;
60+
61+
expect(resources).to.not.have.property('WebsocketsApi');
62+
});
63+
});
64+
5365
it('should add the websockets policy', () => awsCompileWebsocketsEvents
5466
.compileApi().then(() => {
5567
const resources = awsCompileWebsocketsEvents.serverless.service.provider

lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ module.exports = {
1616
[websocketsAuthorizerLogicalId]: {
1717
Type: 'AWS::ApiGatewayV2::Authorizer',
1818
Properties: {
19-
ApiId: {
20-
Ref: this.websocketsApiLogicalId,
21-
},
19+
ApiId: this.provider.getApiGatewayWebsocketApiId(),
2220
Name: event.authorizer.name,
2321
AuthorizerType: 'REQUEST',
2422
AuthorizerUri: event.authorizer.uri,

lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js

+122-83
Original file line numberDiff line numberDiff line change
@@ -8,102 +8,141 @@ const AwsProvider = require('../../../../../provider/awsProvider');
88
describe('#compileAuthorizers()', () => {
99
let awsCompileWebsocketsEvents;
1010

11-
beforeEach(() => {
12-
const serverless = new Serverless();
13-
serverless.setProvider('aws', new AwsProvider(serverless));
14-
serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} };
11+
describe('for routes with authorizer definition', () => {
12+
beforeEach(() => {
13+
const serverless = new Serverless();
14+
serverless.setProvider('aws', new AwsProvider(serverless));
15+
serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} };
1516

16-
awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless);
17+
awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless);
1718

18-
awsCompileWebsocketsEvents.websocketsApiLogicalId
19-
= awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId();
20-
});
19+
awsCompileWebsocketsEvents.websocketsApiLogicalId
20+
= awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId();
2121

22-
it('should create an authorizer resource for routes with authorizer definition', () => {
23-
awsCompileWebsocketsEvents.validated = {
24-
events: [
25-
{
26-
functionName: 'First',
27-
route: '$connect',
28-
authorizer: {
29-
name: 'auth',
30-
uri: {
31-
'Fn::Join': ['',
32-
[
33-
'arn:',
34-
{ Ref: 'AWS::Partition' },
35-
':apigateway:',
36-
{ Ref: 'AWS::Region' },
37-
':lambda:path/2015-03-31/functions/',
38-
{ 'Fn::GetAtt': ['AuthLambdaFunction', 'Arn'] },
39-
'/invocations',
22+
awsCompileWebsocketsEvents.validated = {
23+
events: [
24+
{
25+
functionName: 'First',
26+
route: '$connect',
27+
authorizer: {
28+
name: 'auth',
29+
uri: {
30+
'Fn::Join': ['',
31+
[
32+
'arn:',
33+
{ Ref: 'AWS::Partition' },
34+
':apigateway:',
35+
{ Ref: 'AWS::Region' },
36+
':lambda:path/2015-03-31/functions/',
37+
{ 'Fn::GetAtt': ['AuthLambdaFunction', 'Arn'] },
38+
'/invocations',
39+
],
4040
],
41-
],
41+
},
42+
identitySource: ['route.request.header.Auth'],
4243
},
43-
identitySource: ['route.request.header.Auth'],
4444
},
45-
},
46-
],
47-
};
48-
49-
return awsCompileWebsocketsEvents.compileAuthorizers().then(() => {
50-
const resources = awsCompileWebsocketsEvents.serverless.service.provider
51-
.compiledCloudFormationTemplate.Resources;
52-
53-
expect(resources).to.deep.equal({
54-
AuthWebsocketsAuthorizer: {
55-
Type: 'AWS::ApiGatewayV2::Authorizer',
56-
Properties: {
57-
ApiId: {
58-
Ref: 'WebsocketsApi',
59-
},
60-
Name: 'auth',
61-
AuthorizerType: 'REQUEST',
62-
AuthorizerUri: {
63-
'Fn::Join': [
64-
'',
65-
[
66-
'arn:',
67-
{
68-
Ref: 'AWS::Partition',
69-
},
70-
':apigateway:',
71-
{
72-
Ref: 'AWS::Region',
73-
},
74-
':lambda:path/2015-03-31/functions/',
75-
{
76-
'Fn::GetAtt': [
77-
'AuthLambdaFunction',
78-
'Arn',
79-
],
80-
},
81-
'/invocations',
45+
],
46+
};
47+
});
48+
49+
it('should create an authorizer resource', () => {
50+
return awsCompileWebsocketsEvents.compileAuthorizers().then(() => {
51+
const resources = awsCompileWebsocketsEvents.serverless.service.provider
52+
.compiledCloudFormationTemplate.Resources;
53+
54+
expect(resources).to.deep.equal({
55+
AuthWebsocketsAuthorizer: {
56+
Type: 'AWS::ApiGatewayV2::Authorizer',
57+
Properties: {
58+
ApiId: {
59+
Ref: 'WebsocketsApi',
60+
},
61+
Name: 'auth',
62+
AuthorizerType: 'REQUEST',
63+
AuthorizerUri: {
64+
'Fn::Join': [
65+
'',
66+
[
67+
'arn:',
68+
{
69+
Ref: 'AWS::Partition',
70+
},
71+
':apigateway:',
72+
{
73+
Ref: 'AWS::Region',
74+
},
75+
':lambda:path/2015-03-31/functions/',
76+
{
77+
'Fn::GetAtt': [
78+
'AuthLambdaFunction',
79+
'Arn',
80+
],
81+
},
82+
'/invocations',
83+
],
8284
],
83-
],
85+
},
86+
IdentitySource: ['route.request.header.Auth'],
8487
},
85-
IdentitySource: ['route.request.header.Auth'],
8688
},
87-
},
89+
});
90+
});
91+
});
92+
93+
it('should use existing Api if there is predefined websocketApi config', () => {
94+
awsCompileWebsocketsEvents.serverless.service.provider.apiGateway = {
95+
websocketApiId: '5ezys3sght',
96+
};
97+
98+
return awsCompileWebsocketsEvents.compileAuthorizers().then(() => {
99+
const resources = awsCompileWebsocketsEvents.serverless.service.provider
100+
.compiledCloudFormationTemplate.Resources;
101+
102+
expect(resources.AuthWebsocketsAuthorizer.Properties).to.contain({
103+
ApiId: '5ezys3sght',
104+
});
88105
});
89106
});
90107
});
91108

92-
it('should NOT create an authorizer resource for routes with not authorizer definition', () => {
93-
awsCompileWebsocketsEvents.validated = {
94-
events: [
95-
{
96-
functionName: 'First',
97-
route: '$connect',
98-
},
99-
],
100-
};
101-
102-
return awsCompileWebsocketsEvents.compileAuthorizers().then(() => {
103-
const resources = awsCompileWebsocketsEvents.serverless.service.provider
104-
.compiledCloudFormationTemplate.Resources;
105-
106-
expect(resources).to.deep.equal({});
109+
describe('for routes without authorizer definition', () => {
110+
beforeEach(() => {
111+
const serverless = new Serverless();
112+
serverless.setProvider('aws', new AwsProvider(serverless));
113+
serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} };
114+
115+
awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless);
116+
117+
awsCompileWebsocketsEvents.websocketsApiLogicalId
118+
= awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId();
119+
120+
awsCompileWebsocketsEvents.validated = {
121+
events: [
122+
{
123+
functionName: 'First',
124+
route: '$connect',
125+
},
126+
],
127+
};
128+
});
129+
130+
it('should NOT create an authorizer resource for routes with not authorizer definition', () => {
131+
awsCompileWebsocketsEvents.validated = {
132+
events: [
133+
{
134+
functionName: 'First',
135+
route: '$connect',
136+
},
137+
],
138+
};
139+
140+
return awsCompileWebsocketsEvents.compileAuthorizers().then(() => {
141+
const resources = awsCompileWebsocketsEvents.serverless.service.provider
142+
.compiledCloudFormationTemplate.Resources;
143+
144+
expect(resources).to.deep.equal({});
145+
});
107146
});
108147
});
109148
});

0 commit comments

Comments
 (0)