Skip to content

Commit 2218a99

Browse files
committed
add usageplan
1 parent 6bcd795 commit 2218a99

File tree

6 files changed

+368
-2
lines changed

6 files changed

+368
-2
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const BbPromise = require('bluebird');
5+
6+
module.exports = {
7+
compileUsagePlan() {
8+
if (this.serverless.service.provider.usagePlan || this.serverless.service.provider.apiKeys) {
9+
this.apiGatewayUsagePlanLogicalId = this.provider.naming.getUsagePlanLogicalId();
10+
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
11+
[this.apiGatewayUsagePlanLogicalId]: {
12+
Type: 'AWS::ApiGateway::UsagePlan',
13+
DependsOn: this.apiGatewayDeploymentLogicalId,
14+
Properties: {
15+
ApiStages: [
16+
{
17+
ApiId: {
18+
Ref: this.apiGatewayRestApiLogicalId,
19+
},
20+
Stage: this.provider.getStage(),
21+
},
22+
],
23+
Description: `Usage plan for ${this.serverless.service.service} ${
24+
this.provider.getStage()} stage`,
25+
UsagePlanName: `${this.serverless.service.service}-${
26+
this.provider.getStage()}`,
27+
},
28+
},
29+
});
30+
if (_.has(this.serverless.service.provider, 'usagePlan.quota')
31+
&& this.serverless.service.provider.usagePlan.quota !== null) {
32+
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
33+
[this.apiGatewayUsagePlanLogicalId]: {
34+
Properties: {
35+
Quota: _.merge(
36+
{ Limit: this.serverless.service.provider.usagePlan.quota.limit },
37+
{ Offset: this.serverless.service.provider.usagePlan.quota.offset },
38+
{ Period: this.serverless.service.provider.usagePlan.quota.period }),
39+
},
40+
},
41+
});
42+
}
43+
if (_.has(this.serverless.service.provider, 'usagePlan.throttle')
44+
&& this.serverless.service.provider.usagePlan.throttle !== null) {
45+
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
46+
[this.apiGatewayUsagePlanLogicalId]: {
47+
Properties: {
48+
Throttle: _.merge(
49+
{ BurstLimit: this.serverless.service.provider.usagePlan.throttle.burstLimit },
50+
{ RateLimit: this.serverless.service.provider.usagePlan.throttle.rateLimit }),
51+
},
52+
},
53+
});
54+
}
55+
}
56+
return BbPromise.resolve();
57+
},
58+
};
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
'use strict';
2+
3+
const expect = require('chai').expect;
4+
const Serverless = require('serverless/lib/Serverless');
5+
const AwsProvider = require('serverless/lib/plugins/aws/provider/awsProvider');
6+
const ServerlessStepFunctions = require('./../../../index');
7+
8+
describe('#compileUsagePlan()', () => {
9+
let serverless;
10+
let serverlessStepFunctions;
11+
12+
beforeEach(() => {
13+
serverless = new Serverless();
14+
const options = {
15+
stage: 'dev',
16+
region: 'us-east-1',
17+
};
18+
serverless.service.service = 'first-service';
19+
serverless.setProvider('aws', new AwsProvider(serverless));
20+
serverless.service.provider.apiKeys = ['1234567890'];
21+
serverless.service.provider.compiledCloudFormationTemplate = {
22+
Resources: {},
23+
};
24+
serverlessStepFunctions = new ServerlessStepFunctions(serverless, options);
25+
serverlessStepFunctions.serverless.service.stepFunctions = {
26+
stateMachines: {
27+
first: {},
28+
},
29+
};
30+
serverlessStepFunctions.apiGatewayDeploymentLogicalId = 'ApiGatewayDeploymentTest';
31+
serverlessStepFunctions.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi';
32+
});
33+
34+
it('should compile default usage plan resource', () => {
35+
serverless.service.provider.apiKeys = ['1234567890'];
36+
return serverlessStepFunctions.compileUsagePlan().then(() => {
37+
expect(
38+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
39+
.Resources[
40+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
41+
].Type
42+
).to.equal('AWS::ApiGateway::UsagePlan');
43+
44+
expect(
45+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
46+
.Resources[
47+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
48+
].DependsOn
49+
).to.equal('ApiGatewayDeploymentTest');
50+
51+
expect(
52+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
53+
.Resources[
54+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
55+
].Properties.ApiStages[0].ApiId.Ref
56+
).to.equal('ApiGatewayRestApi');
57+
58+
expect(
59+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
60+
.Resources[
61+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
62+
].Properties.ApiStages[0].Stage
63+
).to.equal('dev');
64+
65+
expect(
66+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
67+
.Resources[
68+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
69+
].Properties.Description
70+
).to.equal('Usage plan for first-service dev stage');
71+
72+
expect(
73+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
74+
.Resources[
75+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
76+
].Properties.UsagePlanName
77+
).to.equal('first-service-dev');
78+
});
79+
});
80+
81+
it('should compile custom usage plan resource', () => {
82+
serverless.service.provider.usagePlan = {
83+
quota: {
84+
limit: 500,
85+
offset: 10,
86+
period: 'MONTH',
87+
},
88+
throttle: {
89+
burstLimit: 200,
90+
rateLimit: 100,
91+
},
92+
};
93+
94+
return serverlessStepFunctions.compileUsagePlan().then(() => {
95+
expect(
96+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
97+
.Resources[
98+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
99+
].Type
100+
).to.equal('AWS::ApiGateway::UsagePlan');
101+
102+
expect(
103+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
104+
.Resources[
105+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
106+
].DependsOn
107+
).to.equal('ApiGatewayDeploymentTest');
108+
109+
expect(
110+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
111+
.Resources[
112+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
113+
].Properties.ApiStages[0].ApiId.Ref
114+
).to.equal('ApiGatewayRestApi');
115+
116+
expect(
117+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
118+
.Resources[
119+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
120+
].Properties.ApiStages[0].Stage
121+
).to.equal('dev');
122+
123+
expect(
124+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
125+
.Resources[
126+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
127+
].Properties.Description
128+
).to.equal('Usage plan for first-service dev stage');
129+
130+
expect(
131+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
132+
.Resources[
133+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
134+
].Properties.Quota
135+
).to.deep.equal({
136+
Limit: 500,
137+
Offset: 10,
138+
Period: 'MONTH',
139+
});
140+
141+
expect(
142+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
143+
.Resources[
144+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
145+
].Properties.Throttle
146+
).to.deep.equal({
147+
BurstLimit: 200,
148+
RateLimit: 100,
149+
});
150+
151+
expect(
152+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
153+
.Resources[
154+
serverlessStepFunctions.provider.naming.getUsagePlanLogicalId()
155+
].Properties.UsagePlanName
156+
).to.equal('first-service-dev');
157+
});
158+
});
159+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const BbPromise = require('bluebird');
5+
6+
module.exports = {
7+
compileUsagePlanKeys() {
8+
if (this.serverless.service.provider.apiKeys) {
9+
if (!Array.isArray(this.serverless.service.provider.apiKeys)) {
10+
throw new this.serverless.classes.Error('apiKeys property must be an array');
11+
}
12+
13+
_.forEach(this.serverless.service.provider.apiKeys, (apiKey, i) => {
14+
const usagePlanKeyNumber = i + 1;
15+
16+
if (typeof apiKey !== 'string') {
17+
throw new this.serverless.classes.Error('API Keys must be strings');
18+
}
19+
20+
const usagePlanKeyLogicalId = this.provider.naming
21+
.getUsagePlanKeyLogicalId(usagePlanKeyNumber);
22+
23+
const apiKeyLogicalId = this.provider.naming
24+
.getApiKeyLogicalId(usagePlanKeyNumber);
25+
26+
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
27+
[usagePlanKeyLogicalId]: {
28+
Type: 'AWS::ApiGateway::UsagePlanKey',
29+
Properties: {
30+
KeyId: {
31+
Ref: apiKeyLogicalId,
32+
},
33+
KeyType: 'API_KEY',
34+
UsagePlanId: {
35+
Ref: this.apiGatewayUsagePlanLogicalId,
36+
},
37+
},
38+
},
39+
});
40+
});
41+
}
42+
return BbPromise.resolve();
43+
},
44+
};
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use strict';
2+
3+
const expect = require('chai').expect;
4+
const Serverless = require('serverless/lib/Serverless');
5+
const AwsProvider = require('serverless/lib/plugins/aws/provider/awsProvider');
6+
const ServerlessStepFunctions = require('./../../../index');
7+
8+
describe('#compileUsagePlanKeys()', () => {
9+
let serverless;
10+
let serverlessStepFunctions;
11+
12+
beforeEach(() => {
13+
const options = {
14+
stage: 'dev',
15+
region: 'us-east-1',
16+
};
17+
serverless = new Serverless();
18+
serverless.setProvider('aws', new AwsProvider(serverless, options));
19+
serverless.service.service = 'first-service';
20+
serverless.service.provider = {
21+
name: 'aws',
22+
apiKeys: ['1234567890'],
23+
};
24+
serverless.service.provider.compiledCloudFormationTemplate = {
25+
Resources: {},
26+
Outputs: {},
27+
};
28+
serverlessStepFunctions = new ServerlessStepFunctions(serverless, options);
29+
serverlessStepFunctions.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi';
30+
serverlessStepFunctions.apiGatewayDeploymentLogicalId = 'ApiGatewayDeploymentTest';
31+
serverlessStepFunctions.apiGatewayUsagePlanLogicalId = 'UsagePlan';
32+
});
33+
34+
it('should compile usage plan key resource', () =>
35+
serverlessStepFunctions.compileUsagePlanKeys().then(() => {
36+
expect(
37+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
38+
.Resources[
39+
serverlessStepFunctions.provider.naming.getUsagePlanKeyLogicalId(1)
40+
].Type
41+
).to.equal('AWS::ApiGateway::UsagePlanKey');
42+
43+
expect(
44+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
45+
.Resources[
46+
serverlessStepFunctions.provider.naming.getUsagePlanKeyLogicalId(1)
47+
].Properties.KeyId.Ref
48+
).to.equal('ApiGatewayApiKey1');
49+
50+
expect(
51+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
52+
.Resources[
53+
serverlessStepFunctions.provider.naming.getUsagePlanKeyLogicalId(1)
54+
].Properties.KeyType
55+
).to.equal('API_KEY');
56+
57+
expect(
58+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
59+
.Resources[
60+
serverlessStepFunctions.provider.naming.getUsagePlanKeyLogicalId(1)
61+
].Properties.UsagePlanId.Ref
62+
).to.equal('UsagePlan');
63+
})
64+
);
65+
66+
it('throw error if apiKey property is not an array', () => {
67+
serverlessStepFunctions.serverless.service.provider.apiKeys = 2;
68+
expect(() => serverlessStepFunctions.compileUsagePlanKeys()).to.throw(Error);
69+
});
70+
71+
it('throw error if an apiKey is not a string', () => {
72+
serverlessStepFunctions.serverless.service.provider.apiKeys = [2];
73+
expect(() => serverlessStepFunctions.compileUsagePlanKeys()).to.throw(Error);
74+
});
75+
});

lib/index.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const httpResources = require('./deploy/events/apiGateway/resources');
99
const httpMethods = require('./deploy/events/apiGateway/methods');
1010
const httpCors = require('./deploy/events/apiGateway/cors');
1111
const httpApiKeys = require('./deploy/events/apiGateway/apiKeys');
12+
const httpUsagePlan = require('./deploy/events/apiGateway/usagePlan');
13+
const httpUsagePlanKeys = require('./deploy/events/apiGateway/usagePlanKeys');
1214
const httpIamRole = require('./deploy/events/apiGateway/iamRole');
1315
const httpDeployment = require('./deploy/events/apiGateway/deployment');
1416
const httpRestApi = require('./deploy/events/apiGateway/restApi');
@@ -40,6 +42,8 @@ class ServerlessStepFunctions {
4042
httpMethods,
4143
httpCors,
4244
httpApiKeys,
45+
httpUsagePlan,
46+
httpUsagePlanKeys,
4347
httpIamRole,
4448
httpDeployment,
4549
invoke,
@@ -108,9 +112,11 @@ class ServerlessStepFunctions {
108112
.then(this.compileResources)
109113
.then(this.compileMethods)
110114
.then(this.compileCors)
111-
.then(this.compileApiKeys)
112115
.then(this.compileHttpIamRole)
113-
.then(this.compileDeployment);
116+
.then(this.compileDeployment)
117+
.then(this.compileApiKeys)
118+
.then(this.compileUsagePlan)
119+
.then(this.compileUsagePlanKeys);
114120
}
115121
),
116122
'after:deploy:deploy': () => BbPromise.bind(this)

0 commit comments

Comments
 (0)