Skip to content

Commit a952250

Browse files
author
Frank Schmid
committed
Support _ - and + in alias names
Turn hyphens to underscores in stage names Added unit tests for normalization Fixed regex. Updated dependencies and test framework. Fixed ESLint Normalize logical id of alias part in Lambda roles
1 parent e0215d2 commit a952250

31 files changed

+4460
-2070
lines changed

.eslintrc.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
"plugin:import/errors",
1111
"plugin:import/warnings"
1212
],
13-
"installedESLint": true,
1413
"plugins": [
1514
"promise",
1615
"lodash",
@@ -19,7 +18,10 @@
1918
"rules": {
2019
"indent": [
2120
"error",
22-
"tab"
21+
"tab",
22+
{
23+
"MemberExpression": "off"
24+
}
2325
],
2426
"linebreak-style": [
2527
"error",

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/node_modules
22
/coverage
3+
/.nyc_output

lib/configureAliasStack.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ module.exports = {
3333
*/
3434
this._serverless.service.provider
3535
.compiledCloudFormationAliasTemplate = this._serverless.utils.readFileSync(
36-
path.join(__dirname, 'alias-cloudformation-template.json')
37-
);
36+
path.join(__dirname, 'alias-cloudformation-template.json')
37+
);
3838

3939
const aliasTemplate = this._serverless.service.provider.compiledCloudFormationAliasTemplate;
4040

lib/createAliasStack.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ module.exports = {
3636
};
3737

3838
return this._provider.request(
39-
'CloudFormation',
40-
'createStack',
41-
params,
42-
this._options.stage,
43-
this._options.region
44-
).then(cfData => this.monitorStack('create', cfData));
39+
'CloudFormation',
40+
'createStack',
41+
params,
42+
this._options.stage,
43+
this._options.region
44+
).then(cfData => this.monitorStack('create', cfData));
4545

4646
},
4747

@@ -60,9 +60,9 @@ module.exports = {
6060
}
6161

6262
return BbPromise.bind(this)
63-
// always write the template to disk, whether we are deploying or not
64-
.then(this.writeAliasTemplateToDisk)
65-
.then(this.checkAliasStack);
63+
// always write the template to disk, whether we are deploying or not
64+
.then(this.writeAliasTemplateToDisk)
65+
.then(this.checkAliasStack);
6666
},
6767

6868
checkAliasStack() {

lib/logs.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ module.exports = {
159159
if (this.options.tail) {
160160
return setTimeout((() => getLogStreams()
161161
.then(nextLogStreamNames => this.logsShowLogs(nextLogStreamNames, formatter, getLogStreams))),
162-
this.options.interval);
162+
this.options.interval);
163163
}
164164
}
165165

@@ -210,7 +210,7 @@ module.exports = {
210210

211211
return setTimeout((() => getLogStreams()
212212
.then(nextLogStreamNames => this.logsShowLogs(nextLogStreamNames, formatter, getLogStreams))),
213-
this.options.interval);
213+
this.options.interval);
214214
}
215215

216216
return BbPromise.resolve();

lib/removeAlias.js

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ module.exports = {
1515
const usedFuncRefs = _.uniq(
1616
_.flatMap(aliasStackTemplates, template => {
1717
const funcRefs = _.map(
18-
_.assign({},
19-
_.pickBy(
20-
_.get(template, 'Resources', {}),
21-
[ 'Type', 'AWS::Lambda::Alias' ])),
22-
(value, key) => {
23-
return _.replace(key, /Alias$/, '');
24-
});
25-
18+
_.assign({},
19+
_.pickBy(
20+
_.get(template, 'Resources', {}),
21+
[ 'Type', 'AWS::Lambda::Alias' ])),
22+
(value, key) => {
23+
return _.replace(key, /Alias$/, '');
24+
}
25+
);
2626
return funcRefs;
2727
})
2828
);
@@ -143,7 +143,7 @@ module.exports = {
143143

144144
let stackTags = { STAGE: this._stage };
145145

146-
// Merge additional stack tags
146+
// Merge additional stack tags
147147
if (_.isObject(this.serverless.service.provider.stackTags)) {
148148
stackTags = _.extend(stackTags, this.serverless.service.provider.stackTags);
149149
}
@@ -161,7 +161,7 @@ module.exports = {
161161

162162
this.options.verbose && this._serverless.cli.log(`Checking stack policy`);
163163

164-
// Policy must have at least one statement, otherwise no updates would be possible at all
164+
// Policy must have at least one statement, otherwise no updates would be possible at all
165165
if (this.serverless.service.provider.stackPolicy &&
166166
this.serverless.service.provider.stackPolicy.length) {
167167
params.StackPolicyBody = JSON.stringify({
@@ -170,18 +170,18 @@ module.exports = {
170170
}
171171

172172
return this._provider.request('CloudFormation',
173-
'updateStack',
174-
params,
175-
this.options.stage,
176-
this.options.region)
177-
.then(cfData => this.monitorStack('update', cfData))
173+
'updateStack',
174+
params,
175+
this.options.stage,
176+
this.options.region)
177+
.then(cfData => this.monitorStack('update', cfData))
178178
.then(() => BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]))
179-
.catch(err => {
180-
if (err.message === NO_UPDATE_MESSAGE) {
181-
return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
182-
}
183-
throw err;
184-
});
179+
.catch(err => {
180+
if (err.message === NO_UPDATE_MESSAGE) {
181+
return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
182+
}
183+
throw err;
184+
});
185185

186186
},
187187

@@ -192,10 +192,10 @@ module.exports = {
192192
this.options.verbose && this._serverless.cli.log(`Removing CF stack ${stackName}`);
193193

194194
return this._provider.request('CloudFormation',
195-
'deleteStack',
196-
{ StackName: stackName },
197-
this._options.stage,
198-
this._options.region)
195+
'deleteStack',
196+
{ StackName: stackName },
197+
this._options.stage,
198+
this._options.region)
199199
.then(cfData => {
200200
// monitorStack wants a StackId member
201201
cfData.StackId = stackName;
@@ -204,14 +204,14 @@ module.exports = {
204204
.then(() =>{
205205
return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
206206
})
207-
.catch(e => {
208-
if (_.includes(e.message, 'does not exist')) {
209-
const message = `Alias ${this._alias} is not deployed.`;
210-
throw new this._serverless.classes.Error(message);
211-
}
207+
.catch(e => {
208+
if (_.includes(e.message, 'does not exist')) {
209+
const message = `Alias ${this._alias} is not deployed.`;
210+
throw new this._serverless.classes.Error(message);
211+
}
212212

213-
throw e;
214-
});
213+
throw e;
214+
});
215215

216216
},
217217

lib/stackops/apiGateway.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const internal = {
3737
const stageResource = {
3838
Type: 'AWS::ApiGateway::Stage',
3939
Properties: {
40-
StageName: this._alias,
40+
StageName: _.replace(this._alias, /-/g, '_'),
4141
DeploymentId: {
4242
Ref: deploymentName
4343
},
@@ -180,7 +180,7 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
180180
const apiLambdaPermissions =
181181
_.assign({},
182182
_.pickBy(_.pickBy(stageStack.Resources, [ 'Type', 'AWS::Lambda::Permission' ]),
183-
['Properties.Principal', 'apigateway.amazonaws.com']));
183+
['Properties.Principal', 'apigateway.amazonaws.com']));
184184

185185
const apiMethods = _.assign({}, _.pickBy(stageStack.Resources, [ 'Type', 'AWS::ApiGateway::Method' ]));
186186
const authorizers = _.assign({}, _.pickBy(stageStack.Resources, [ 'Type', 'AWS::ApiGateway::Authorizer' ]));

lib/stackops/cwEvents.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
1616
const cwEventLambdaPermissions =
1717
_.assign({},
1818
_.pickBy(_.pickBy(stageStack.Resources, [ 'Type', 'AWS::Lambda::Permission' ]),
19-
['Properties.Principal', 'events.amazonaws.com']));
19+
['Properties.Principal', 'events.amazonaws.com']));
2020

2121
_.forOwn(cwEvents, (cwEvent, name) => {
2222
// Reference alias as FunctionName

lib/stackops/events.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
5757
delete stageStack.Resources[name];
5858
});
5959

60-
// Move event subscriptions to alias stack
60+
// Move event subscriptions to alias stack
6161
_.defaults(aliasStack.Resources, subscriptions);
6262

63-
// Forward inputs to the promise chain
63+
// Forward inputs to the promise chain
6464
return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
6565
};

lib/stackops/functions.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ function mergeAliases(stackName, newTemplate, currentTemplate, aliasStackTemplat
3939
// Get currently deployed function definitions and versions and retain them in the stack update
4040
const usedFunctionElements = {
4141
Resources: _.map(aliasedFunctions, aliasedFunction => _.assign(
42-
{},
43-
_.pick(currentTemplate.Resources, [ aliasedFunction.name, aliasedFunction.version ])
44-
)),
42+
{},
43+
_.pick(currentTemplate.Resources, [ aliasedFunction.name, aliasedFunction.version ])
44+
)),
4545
Outputs: _.map(aliasedFunctions, aliasedFunction => _.assign(
46-
{},
47-
_.pick(currentTemplate.Outputs, [ `${aliasedFunction.name}Arn`, aliasedFunction.version ])
48-
))
46+
{},
47+
_.pick(currentTemplate.Outputs, [ `${aliasedFunction.name}Arn`, aliasedFunction.version ])
48+
))
4949
};
5050

5151
_.forEach(usedFunctionElements.Resources, resources => _.defaults(newTemplate.Resources, resources));

lib/stackops/lambdaRole.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,28 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
2929
return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
3030
}
3131

32-
const roleName = `IamRoleLambdaExecution${this._alias}`;
32+
// Role name allows [\w+=,.@-]+
33+
const normalizedAlias = utils.normalizeAliasForLogicalId(this._alias);
34+
const roleLogicalId = `IamRoleLambdaExecution${normalizedAlias}`;
3335
const role = stageStack.Resources.IamRoleLambdaExecution;
3436

3537
// Set role name
3638
_.last(role.Properties.RoleName['Fn::Join']).push(this._alias);
3739

38-
stageStack.Resources[roleName] = stageStack.Resources.IamRoleLambdaExecution;
40+
stageStack.Resources[roleLogicalId] = stageStack.Resources.IamRoleLambdaExecution;
3941
delete stageStack.Resources.IamRoleLambdaExecution;
4042

4143
// Replace references
4244
const functions = _.filter(stageStack.Resources, ['Type', 'AWS::Lambda::Function']);
4345
_.forEach(functions, func => {
4446
func.Properties.Role = {
4547
'Fn::GetAtt': [
46-
roleName,
48+
roleLogicalId,
4749
'Arn'
4850
]
4951
};
5052
const dependencyIndex = _.indexOf(func.DependsOn, 'IamRoleLambdaExecution');
51-
func.DependsOn[dependencyIndex] = roleName;
53+
func.DependsOn[dependencyIndex] = roleLogicalId;
5254
});
5355

5456
if (_.has(currentTemplate, 'Resources.IamRoleLambdaExecution')) {
@@ -61,10 +63,11 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
6163
// Retain the roles of all currently deployed aliases
6264
_.forEach(aliasStackTemplates, aliasTemplate => {
6365
const alias = _.get(aliasTemplate, 'Outputs.ServerlessAliasName.Value');
64-
const aliasRoleName = `IamRoleLambdaExecution${alias}`;
65-
const aliasRole = _.get(currentTemplate, `Resources.${aliasRoleName}`);
66+
const aliasNormalizedAlias = utils.normalizeAliasForLogicalId(alias);
67+
const aliasRoleLogicalId = `IamRoleLambdaExecution${aliasNormalizedAlias}`;
68+
const aliasRole = _.get(currentTemplate, `Resources.${aliasRoleLogicalId}`);
6669
if (alias && aliasRole) {
67-
stageStack.Resources[aliasRoleName] = aliasRole;
70+
stageStack.Resources[aliasRoleLogicalId] = aliasRole;
6871
}
6972
});
7073

lib/stackops/snsEvents.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
5050
const snsLambdaPermissions =
5151
_.assign({},
5252
_.pickBy(_.pickBy(stageStack.Resources, [ 'Type', 'AWS::Lambda::Permission' ]),
53-
[ 'Properties.Principal', 'sns.amazonaws.com' ]));
53+
[ 'Properties.Principal', 'sns.amazonaws.com' ]));
5454

5555
// Adjust permission to reference the function aliases
5656
_.forOwn(snsLambdaPermissions, (permission, name) => {

lib/updateAliasStack.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,7 @@ module.exports = {
1414

1515
const stackName = `${this._provider.naming.getStackName()}-${this._alias}`;
1616
let stackTags = { STAGE: this._options.stage, ALIAS: this._alias };
17-
const templateUrl = `https://s3.amazonaws.com/${
18-
this.bucketName
19-
}/${
20-
this._serverless.service.package.artifactDirectoryName
21-
}/compiled-cloudformation-template-alias.json`;
17+
const templateUrl = `https://s3.amazonaws.com/${this.bucketName}/${this._serverless.service.package.artifactDirectoryName}/compiled-cloudformation-template-alias.json`;
2218
// Merge additional stack tags
2319
if (_.isObject(this._serverless.service.provider.stackTags)) {
2420
stackTags = _.extend(stackTags, this._serverless.service.provider.stackTags);
@@ -49,11 +45,7 @@ module.exports = {
4945
},
5046

5147
updateAlias() {
52-
const templateUrl = `https://s3.amazonaws.com/${
53-
this.bucketName
54-
}/${
55-
this._serverless.service.package.artifactDirectoryName
56-
}/compiled-cloudformation-template-alias.json`;
48+
const templateUrl = `https://s3.amazonaws.com/${this.bucketName}/${this._serverless.service.package.artifactDirectoryName}/compiled-cloudformation-template-alias.json`;
5749

5850
this.serverless.cli.log('Updating alias stack...');
5951
const stackName = `${this._provider.naming.getStackName()}-${this._alias}`;

lib/updateFunctionAlias.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

3-
const BbPromise = require("bluebird");
3+
const BbPromise = require('bluebird');
4+
const _ = require('lodash');
45

56
module.exports = {
67

@@ -15,7 +16,7 @@ module.exports = {
1516
// Get the hash of the deployed function package
1617
const params = {
1718
FunctionName: func.name,
18-
Qualifier: "$LATEST"
19+
Qualifier: '$LATEST'
1920
};
2021

2122
return this.provider.request(
@@ -31,7 +32,7 @@ module.exports = {
3132
const params = {
3233
FunctionName: func.name,
3334
CodeSha256: sha256,
34-
Description: "Deployed manually"
35+
Description: 'Deployed manually'
3536
};
3637
return this.provider.request(
3738
'Lambda',
@@ -57,7 +58,17 @@ module.exports = {
5758
);
5859
})
5960
.then(result => {
60-
this.serverless.cli.log(`Successfully updated alias: ${this.options.function}@${this._alias} -> ${result.FunctionVersion}`);
61+
this.serverless.cli.log(_.join(
62+
[
63+
'Successfully updated alias: ',
64+
this.options.function,
65+
'@',
66+
this._alias,
67+
' -> ',
68+
result.FunctionVersion
69+
],
70+
''
71+
));
6172
return BbPromise.resolve();
6273
});
6374
}

0 commit comments

Comments
 (0)