Skip to content

Commit 88d7433

Browse files
authored
Stage configuration (#59)
* Added multilevel stage configuration * Updated README * Do not set default values explicitly on methods * Fixed layout issues in README stage example * Added unit tests for stage generation and configuration * Fixed layout issues in README. Removed alpha release notes.
1 parent 7cc0d82 commit 88d7433

File tree

3 files changed

+702
-47
lines changed

3 files changed

+702
-47
lines changed

README.md

Lines changed: 96 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,89 @@ Lambda invocation. This will call the aliased function version.
9292
Deployed stages have the alias stage variable set fixed, so a deployed alias stage is
9393
hard-wired to the aliased Lambda versions.
9494

95+
### Stage configuration (NEW)
96+
97+
The alias plugin supports configuring the deployed API Gateway stages, exactly as
98+
you can do it within the AWS APIG console, e.g. you can configure logging (with
99+
or without data/request tracing), setup caching or throttling on your endpoints.
100+
101+
The configuration can be done on a service wide level, function level or method level
102+
by adding an `aliasStage` object either to `provider`, `any function` or a `http event`
103+
within a function in your _serverless.yml_. The configuration is applied hierarchically,
104+
where the inner configurations overwrite the outer ones.
105+
106+
`HTTP Event -> FUNCTION -> SERVICE`
107+
108+
#### The aliasStage configuration object
109+
110+
All settings are optional, and if not specified will be set to the AWS stage defaults.
111+
112+
```
113+
aliasStage:
114+
cacheDataEncrypted: (Boolean)
115+
cacheTtlInSeconds: (Integer)
116+
cachingEnabled: (Boolean)
117+
dataTraceEnabled: (Boolean) - Log full request/response bodies
118+
loggingLevel: ("OFF", "INFO" or "ERROR")
119+
metricsEnabled: (Boolean) - Enable detailed CW metrics
120+
throttlingBurstLimit: (Integer)
121+
throttlingRateLimit: (Number)
122+
```
123+
124+
There are two further options that can only be specified on a service level and that
125+
affect the whole stage:
126+
127+
```
128+
aliasStage:
129+
cacheClusterEnabled: (Boolean)
130+
cacheClusterSize: (Integer)
131+
```
132+
133+
For more information see the [AWS::APIGateway::Stage](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-stage.html) or [MethodSettings](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html) documentation
134+
on the AWS website.
135+
136+
Sample serverless.yml (partial):
137+
138+
```
139+
service: sls-test-project
140+
141+
provider:
142+
...
143+
# Enable detailed error logging on all endpoints
144+
aliasStage:
145+
loggingLevel: "ERROR"
146+
dataTraceEnabled: true
147+
...
148+
149+
functions:
150+
myFunc1:
151+
...
152+
# myFunc1 should generally not log anything
153+
aliasStage:
154+
loggingLevel: "OFF"
155+
dataTraceEnabled: false
156+
events:
157+
- http:
158+
method: GET
159+
path: /func1
160+
- http:
161+
method: POST
162+
path: /func1/create
163+
- http:
164+
method: PATCH
165+
path: /func1/update
166+
# The update endpoint needs special settings
167+
aliasStage:
168+
loggingLevel: "INFO"
169+
dataTraceEnabled: true
170+
throttlingBurstLimit: 200
171+
throttlingRateLimit: 100
172+
173+
myFunc2:
174+
...
175+
# Will inherit the global settings if nothing is set on function level
176+
```
177+
95178
## Reference the current alias in your service
96179

97180
You can reference the currently deployed alias with `${self:provider.alias}` in
@@ -174,11 +257,11 @@ functions:
174257
path: /func1
175258
resources:
176259
Resources:
177-
myKinesis:
178-
Type: AWS::Kinesis::Stream
179-
Properties:
180-
Name: my-kinesis
181-
ShardCount: 1
260+
myKinesis:
261+
Type: AWS::Kinesis::Stream
262+
Properties:
263+
Name: my-kinesis
264+
ShardCount: 1
182265
```
183266

184267
When a function is deployed to an alias it will now also listen to the *my-kinesis*
@@ -214,11 +297,11 @@ functions:
214297
path: /func1
215298
resources:
216299
Resources:
217-
myKinesis${self:provider.alias}:
218-
Type: AWS::Kinesis::Stream
219-
Properties:
220-
Name: my-kinesis-${self.provider.alias}
221-
ShardCount: 1
300+
myKinesis${self:provider.alias}:
301+
Type: AWS::Kinesis::Stream
302+
Properties:
303+
Name: my-kinesis-${self.provider.alias}
304+
ShardCount: 1
222305
```
223306

224307
### Named streams
@@ -343,7 +426,7 @@ The plugin adds the following lifecycle events that can be hooked by other plugi
343426
* alias:deploy:done
344427

345428
The Alias plugin is successfully finished. Hook this instead of 'after:deploy:deploy'
346-
to make sure that your plugin gets triggered right after the alias plugin is done.
429+
to make sure that your plugin gets triggered right after the alias plugin is done.
347430

348431
* alias:remove:removeStack
349432

@@ -360,8 +443,8 @@ and _serverless.service.provider.deployedAliasTemplates[]_.
360443

361444
* The master alias for a stage could be protected by a separate stack policy that
362445
only allows admin users to deploy or change it. The stage stack does not have
363-
to be protected individually because the stack cross references prohibit changes
364-
naturally. It might be possible to introduce some kind of per alias policy.
446+
to be protected individually because the stack cross references prohibit changes
447+
naturally. It might be possible to introduce some kind of per alias policy.
365448

366449
## Version history
367450

@@ -381,19 +464,3 @@ and _serverless.service.provider.deployedAliasTemplates[]_.
381464

382465

383466
* 1.0.0 Support "serverless logs" with aliases. First non-alpha!
384-
* 0.5.1-alpha1 Use separate Lambda roles per alias
385-
* 0.5.0-alpha1 Fixes a bug with deploying event sources introduced with 0.4.0
386-
Use new event model introduced in SLS 1.12. Needs SLS 1.12 or greater from now on.
387-
Add support for CW events.
388-
Set SERVERLESS_ALIAS environment variable on deployed functions.
389-
* 0.4.0-alpha1 APIG support fixed. Support external IAM roles. BREAKING.
390-
* 0.3.4-alpha1 Bugfixes. IAM policy consolitaion. Show master alias information.
391-
* 0.3.3-alpha1 Bugfixes. Allow manual resource overrides. Allow methods attached to APIG root resource.
392-
* 0.3.2-alpha1 Allow initial project creation with activated alias plugin
393-
* 0.3.1-alpha1 Support Serverless 1.6 again with upgrade to 1.7+
394-
* 0.3.0-alpha1 Support lambda event subscriptions
395-
* 0.2.1-alpha1 Alias remove command removes unused resources
396-
* 0.2.0-alpha1 Support custom resources
397-
* 0.1.2-alpha1 Integration with "serverless info"
398-
* 0.1.1-alpha1 Full APIG support
399-
* 0.1.0-alpha1 Lambda function alias support

lib/stackops/apiGateway.js

Lines changed: 102 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,103 @@ const _ = require('lodash');
1010
const BbPromise = require('bluebird');
1111
const utils = require('../utils');
1212

13+
const stageMethodConfigMappings = {
14+
cacheDataEncrypted: { prop: 'CacheDataEncrypted', validate: _.isBoolean, default: false },
15+
cacheTtlInSeconds: { prop: 'CacheTtlInSeconds', validate: _.isInteger },
16+
cachingEnabled: { prop: 'CachingEnabled', validate: _.isBoolean, default: false },
17+
dataTraceEnabled: { prop: 'DataTraceEnabled', validate: _.isBoolean, default: false },
18+
loggingLevel: { prop: 'LoggingLevel', validate: value => _.includes([ 'OFF', 'INFO', 'ERROR' ], value), default: 'OFF' },
19+
metricsEnabled: { prop: 'MetricsEnabled', validate: _.isBoolean, default: false },
20+
throttlingBurstLimit: { prop: 'ThrottlingBurstLimit', validate: _.isInteger },
21+
throttlingRateLimit: { prop: 'ThrottlingRateLimit', validate: _.isNumber }
22+
};
23+
24+
/**
25+
* Namespace for APIG processing internal functions
26+
*/
27+
const internal = {
28+
/**
29+
* Creates a stage resource and configures it depending on the project settings.
30+
* @this The current instance of the alias plugin
31+
* @param restApiRef {String} Stack reference to rest API id
32+
* @param deploymentName {String} Current deployment.
33+
* @returns {Object} - AWS::ApiGateway::Stage
34+
*/
35+
createStageResource(restApiRef, deploymentName) {
36+
// Create stage resource
37+
const stageResource = {
38+
Type: 'AWS::ApiGateway::Stage',
39+
Properties: {
40+
StageName: this._alias,
41+
DeploymentId: {
42+
Ref: deploymentName
43+
},
44+
RestApiId: {
45+
'Fn::ImportValue': restApiRef
46+
},
47+
Variables: {
48+
SERVERLESS_ALIAS: this._alias,
49+
SERVERLESS_STAGE: this._stage
50+
}
51+
},
52+
DependsOn: [ deploymentName ]
53+
};
54+
55+
// Set a reasonable description
56+
const serviceName = _.get(this.serverless.service.getServiceObject() || {}, 'name');
57+
stageResource.Properties.Description = `Alias stage '${this._alias}' for ${serviceName}`;
58+
59+
// Configure stage (service level)
60+
const serviceLevelConfig = _.cloneDeep(_.get(this.serverless.service, 'provider.aliasStage', {}));
61+
if (serviceLevelConfig.cacheClusterEnabled === true) {
62+
stageResource.Properties.CacheClusterEnabled = true;
63+
if (_.has(serviceLevelConfig, 'cacheClusterSize')) {
64+
stageResource.Properties.CacheClusterSize = serviceLevelConfig.cacheClusterSize;
65+
}
66+
}
67+
delete serviceLevelConfig.cacheClusterEnabled;
68+
delete serviceLevelConfig.cacheClusterSize;
69+
70+
// Configure methods/functions
71+
const methodSettings = [];
72+
const functions = this.serverless.service.getAllFunctions();
73+
_.forEach(functions, funcName => {
74+
const func = this.serverless.service.getFunction(funcName);
75+
const funcStageConfig = _.defaults({}, func.aliasStage, serviceLevelConfig);
76+
const funcHttpEvents = _.compact(_.map(this.serverless.service.getAllEventsInFunction(funcName), event => event.http));
77+
78+
_.forEach(funcHttpEvents, httpEvent => {
79+
const eventStageConfig = _.defaults({}, httpEvent.aliasStage, funcStageConfig);
80+
if (!_.isEmpty(eventStageConfig)) {
81+
const methodType = _.toUpper(httpEvent.method);
82+
const methodSetting = {};
83+
_.forOwn(eventStageConfig, (value, key) => {
84+
if (!_.has(stageMethodConfigMappings, key)) {
85+
throw new this.serverless.classes.Error(`Invalid stage config '${key}' at method '${methodType} /${httpEvent.path}'`);
86+
} else if (!stageMethodConfigMappings[key].validate(value)) {
87+
throw new this.serverless.classes.Error(`Invalid value for stage config '${key}: ${value}' at method '${methodType} /${httpEvent.path}'`);
88+
}
89+
if (!_.has(stageMethodConfigMappings[key], 'default') || stageMethodConfigMappings[key].default !== value) {
90+
methodSetting[stageMethodConfigMappings[key].prop] = value;
91+
}
92+
});
93+
if (!_.isEmpty(methodSetting)) {
94+
methodSetting.HttpMethod = methodType;
95+
methodSetting.ResourcePath = '/' + _.replace('/' + _.trimStart(httpEvent.path, '/'), /\//g, '~1');
96+
methodSettings.push(methodSetting);
97+
}
98+
}
99+
});
100+
});
101+
102+
if (!_.isEmpty(methodSettings)) {
103+
stageResource.Properties.MethodSettings = methodSettings;
104+
}
105+
106+
return stageResource;
107+
}
108+
};
109+
13110
module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
14111
const stackName = this._provider.naming.getStackName();
15112
const stageStack = this._serverless.service.provider.compiledCloudFormationTemplate;
@@ -74,25 +171,9 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
74171
delete stageStack.Resources[deploymentName];
75172

76173
// Create stage resource
77-
const stageResource = {
78-
Type: 'AWS::ApiGateway::Stage',
79-
Properties: {
80-
StageName: this._alias,
81-
DeploymentId: {
82-
Ref: deploymentName
83-
},
84-
RestApiId: {
85-
'Fn::ImportValue': `${stackName}-ApiGatewayRestApi`
86-
},
87-
Variables: {
88-
SERVERLESS_ALIAS: this._alias,
89-
SERVERLESS_STAGE: this._stage
90-
}
91-
},
92-
DependsOn: [ deploymentName ]
93-
};
174+
this.options.verbose && this._serverless.cli.log('Configuring stage');
175+
const stageResource = internal.createStageResource.call(this, `${stackName}-ApiGatewayRestApi`, deploymentName);
94176
aliasResources.push({ ApiGatewayStage: stageResource });
95-
96177
}
97178

98179
// Fetch lambda permissions, methods and resources. These have to be updated later to allow the aliased functions.
@@ -207,3 +288,6 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
207288

208289
return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
209290
};
291+
292+
// Exports to make internal functions available for unit tests
293+
module.exports.internal = internal;

0 commit comments

Comments
 (0)