Skip to content

Commit

Permalink
allow configurable master alias
Browse files Browse the repository at this point in the history
Update README with configurable master alias usage

Remove Merge markers
  • Loading branch information
Aleksander Dikanski authored and Aleksander Dikanski committed May 12, 2019
1 parent c0a2079 commit f14445c
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 35 deletions.
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,17 @@ functionality for aliases.
## Deploy the default alias

The default alias (for the stage) is deployed just by doing a standard stage
deployment with `serverless deploy`. From now on you can reference the aliased
versions on Lambda invokes with the stage qualifier. The aliased version is
read only in the AWS console, so it is guaranteed that the environment and
function parameters (memory, etc.) cannot be changed for a deployed version
by accident, as it can be done with the `$LATEST` qualifier.
This adds an additional level of stability to your deployment process.
deployment with `serverless deploy`. By default the alias is set to the stage
name. Optionally you can set the name of the default (master) alias using the
option `--masterAlias`, e.g., `serverless deploy --masterAlias`. (If you have
already created a serverless deployment without manually setting the default
alias, this option should not be used.)
From now on you can reference the aliased versions on Lambda invokes with the
stage qualifier. The aliased version is read only in the AWS console, so it is
guaranteed that the environment and function parameters (memory, etc.) cannot
be changed for a deployed version by accident, as it can be done with the
`$LATEST` qualifier.This adds an additional level of stability to your deployment
process.

## Deploy a single function

Expand Down
21 changes: 18 additions & 3 deletions lib/aliasRestructureStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,30 @@ module.exports = {
return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
},

aliasRestructureStack(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
addMasterAliasName(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
const stageStack = this._serverless.service.provider.compiledCloudFormationTemplate;
if (stageStack && !stageStack.Outputs.MasterAliasName) {
const masterAlias = this._masterAlias || currentTemplate.Outputs.MasterAliasName.Value;
stageStack.Outputs.MasterAliasName = {
Description: 'Master Alias name (serverless-aws-alias plugin)',
Value: masterAlias,
Export: {
Name: `${this._provider.naming.getStackName()}-MasterAliasName`
}
};
}
return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
},

aliasRestructureStack(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
this._serverless.cli.log('Preparing alias ...');

if (_.isEmpty(aliasStackTemplates) && this._stage !== this._alias) {
throw new this._serverless.classes.Error(new Error('You have to deploy the master alias at least once with "serverless deploy"'));
if (_.isEmpty(aliasStackTemplates) && this._masterAlias !== this._alias) {
throw new this._serverless.classes.Error(new Error('You have to deploy the master alias at least once with "serverless deploy [--masterAlias]"'));
}

return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]).bind(this)
.spread(this.addMasterAliasName)
.spread(this.aliasInit)
.spread(this.aliasHandleUserResources)
.spread(this.aliasHandleLambdaRole)
Expand Down
13 changes: 7 additions & 6 deletions lib/removeAlias.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,26 +221,27 @@ module.exports = {
this._serverless.cli.log('noDeploy option active - will do nothing');
return BbPromise.resolve();
}

if (this._stage && this._stage === this._alias) {

this._masterAlias = currentTemplate.Outputs.MasterAliasName.Value;
if (this._stage && this._masterAlias === this._alias) {
// Removal of the master alias is requested -> check if any other aliases are still deployed.
const aliases = _.map(aliasStackTemplates, aliasTemplate => _.get(aliasTemplate, 'Outputs.ServerlessAliasName.Value'));
if (!_.isEmpty(aliases)) {
throw new this._serverless.classes.Error(`Remove the other deployed aliases before removing the service: ${_.without(aliases, this._alias)}`);
throw new this._serverless.classes.Error(`Remove the other deployed aliases before removing the service: ${_.without(aliases, this._masterAlias)}`);
}
if (_.isEmpty(currentAliasStackTemplate)) {
throw new this._serverless.classes.Error(`Internal error: Stack for master alias ${this._alias} is not deployed. Try to solve the problem by manual interaction with the AWS console.`);
throw new this._serverless.classes.Error(`Internal error: Stack for master alias ${this._masterAlias} is not deployed. Try to solve the problem by manual interaction with the AWS console.`);
}

// We're ready for removal
this._serverless.cli.log(`Removing master alias and stage ${this._alias} ...`);
this._serverless.cli.log(`Removing master alias and stage ${this._masterAlias} ...`);

return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]).bind(this)
.spread(this.aliasRemoveAliasStack)
.then(() => this._serverless.pluginManager.spawn('remove'));
}

this._serverless.cli.log(`Removing alias ${this._alias} ...`);
this._serverless.cli.log(`Removing alias ${this._masterAlias} ...`);

return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]).bind(this)
.spread(this.aliasCreateStackChanges)
Expand Down
15 changes: 0 additions & 15 deletions lib/stackops/snsEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,8 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
_.forOwn(lambdaSubscriptions, subscription => {
const functionNameRef = utils.findAllReferences(_.get(subscription, 'Endpoint'));
const functionName = _.replace(_.get(functionNameRef, '[0].ref', ''), /LambdaFunction$/, '');
<<<<<<< HEAD
const versionName = utils.getFunctionVersionName(versions, functionName);
const aliasName = utils.getAliasVersionName(aliases, functionName);
=======
const versionName = _.find(_.keys(versions), version => _.startsWith(version, `${functionName}LambdaVersion`));
const aliasName = _.find(_.keys(aliases), alias => _.startsWith(alias, `${functionName}Alias`));
>>>>>>> 6ac8882... Fix SNS for case when function names where function name A startwith function name B

subscription.Endpoint = { Ref: aliasName };

Expand All @@ -59,13 +54,8 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac

const functionNameRef = utils.findAllReferences(_.get(subscription.Properties, 'Endpoint'));
const functionName = _.replace(_.get(functionNameRef, '[0].ref', ''), /LambdaFunction$/, '');
<<<<<<< HEAD
const versionName = utils.getFunctionVersionName(versions, functionName);
const aliasName = utils.getAliasVersionName(aliases, functionName);
=======
const versionName = _.find(_.keys(versions), version => _.startsWith(version, `${functionName}LambdaVersion`));
const aliasName = _.find(_.keys(aliases), alias => _.startsWith(alias, `${functionName}Alias`));
>>>>>>> 6ac8882... Fix SNS for case when function names where function name A startwith function name B

subscription.Properties.Endpoint = { Ref: aliasName };
subscription.DependsOn = [ versionName, aliasName ];
Expand All @@ -82,13 +72,8 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
// Adjust permission to reference the function aliases
_.forOwn(snsLambdaPermissions, (permission, name) => {
const functionName = _.replace(name, /LambdaPermission.*$/, '');
<<<<<<< HEAD
const versionName = utils.getFunctionVersionName(versions, functionName);
const aliasName = utils.getAliasVersionName(aliases, functionName);
=======
const versionName = _.find(_.keys(versions), version => _.startsWith(version, `${functionName}LambdaVersion`));
const aliasName = _.find(_.keys(aliases), alias => _.startsWith(alias, `${functionName}Alias`));
>>>>>>> 6ac8882... Fix SNS for case when function names where function name A startwith function name B

// Adjust references and alias permissions
permission.Properties.FunctionName = { Ref: aliasName };
Expand Down
3 changes: 2 additions & 1 deletion lib/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ module.exports = {

// Set configuration
this._stage = this._provider.getStage();
this._alias = this._options.alias || this._stage;
this._masterAlias = this._options.masterAlias || this._stage;
this._alias = this._options.alias || this._masterAlias;
this._stackName = this._provider.naming.getStackName();

// Make alias available as ${self:provider.alias}
Expand Down
41 changes: 41 additions & 0 deletions test/aliasRestructureStack.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,45 @@ describe('aliasRestructureStack', () => {
sandbox.restore();
});

describe('#addMasterAliasName', () => {
it('should add the master alias name as output from command line option', () => {
serverless.service.provider.compiledCloudFormationTemplate = _.cloneDeep({
Resources: {},
Outputs: {}
});
awsAlias._masterAlias = 'master'
return expect(awsAlias.addMasterAliasName()).to.be.fulfilled
.then(() =>
expect(serverless.service.provider.compiledCloudFormationTemplate.Outputs.MasterAliasName.Value)
.to.equal('master')
);
});

it('should add the master alias name as output from existing stack', () => {
const masterAliasStackOutput = {
MasterAliasName: {
Description: 'Master Alias name (serverless-aws-alias plugin)',
Value: 'master',
Export: {
Name: 'sls-test-project-dev-master'
}
}
};
const currentTemplate = {
Outputs: masterAliasStackOutput
};
serverless.service.provider.compiledCloudFormationTemplate = _.cloneDeep({
Resources: {},
Outputs: {}
});
return expect(awsAlias.addMasterAliasName(currentTemplate)).to.be.fulfilled
.then(() =>
expect(serverless.service.provider.compiledCloudFormationTemplate.Outputs.MasterAliasName.Value)
.to.equal('master')
);
});
});

describe('#aliasFinalize()', () => {
it('should stringify flags', () => {
serverless.service.provider.compiledCloudFormationAliasTemplate = {
Expand Down Expand Up @@ -85,6 +124,7 @@ describe('aliasRestructureStack', () => {
});

it('should propagate templates through all stack operations', () => {
const addMasterAliasNameSpy = sandbox.spy(awsAlias, 'addMasterAliasName');
const aliasInitSpy = sandbox.spy(awsAlias, 'aliasInit');
const aliasHandleUserResourcesSpy = sandbox.spy(awsAlias, 'aliasHandleUserResources');
const aliasHandleLambdaRoleSpy = sandbox.spy(awsAlias, 'aliasHandleLambdaRole');
Expand All @@ -102,6 +142,7 @@ describe('aliasRestructureStack', () => {
return expect(awsAlias.aliasRestructureStack(currentTemplate, [ aliasTemplate ], currentAliasStackTemplate))
.to.be.fulfilled
.then(() => BbPromise.all([
expect(addMasterAliasNameSpy).to.have.been.calledWithExactly(currentTemplate, [ aliasTemplate ], currentAliasStackTemplate),
expect(aliasInitSpy).to.have.been.calledWithExactly(currentTemplate, [ aliasTemplate ], currentAliasStackTemplate),
expect(aliasHandleUserResourcesSpy).to.have.been.calledWithExactly(currentTemplate, [ aliasTemplate ], currentAliasStackTemplate),
expect(aliasHandleLambdaRoleSpy).to.have.been.calledWithExactly(currentTemplate, [ aliasTemplate ], currentAliasStackTemplate),
Expand Down
7 changes: 7 additions & 0 deletions test/data/sls-stack-2.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,13 @@
]
]
}
},
"MasterAliasName": {
"Description": "Master Alias name (serverless-aws-alias plugin)",
"Value": "master",
"Export": {
"Name": "sls-test-project-dev-master"
}
}
}
}
18 changes: 15 additions & 3 deletions test/removeAlias.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ describe('removeAlias', () => {

it('should error if an alias is deployed on stage removal', () => {
awsAlias._options = { alias: 'myStage' };
awsAlias._alias = 'myStage';
awsAlias._alias = 'master';
slsStack1.Outputs.MasterAliasName = {
Value: 'master'
};

expect(() => awsAlias.removeAlias(slsStack1, [ aliasStack1 ], aliasStack2)).to.throw(/myAlias/);
return BbPromise.all([
Expand All @@ -103,7 +106,10 @@ describe('removeAlias', () => {

it('should error if the master alias is not deployed on stage removal', () => {
awsAlias._options = { alias: 'myStage' };
awsAlias._alias = 'myStage';
awsAlias._alias = 'master';
slsStack1.Outputs.MasterAliasName = {
Value: 'master'
};

expect(() => awsAlias.removeAlias(slsStack1, [], {})).to.throw(/Internal error/);
return BbPromise.all([
Expand All @@ -116,7 +122,10 @@ describe('removeAlias', () => {

it('should remove alias and service stack on stage removal', () => {
awsAlias._options = { alias: 'myStage' };
awsAlias._alias = 'myStage';
awsAlias._alias = 'master';
slsStack1.Outputs.MasterAliasName = {
Value: 'master'
};

return expect(awsAlias.removeAlias(slsStack1, [], aliasStack2)).to.be.fulfilled
.then(() => BbPromise.all([
Expand All @@ -128,6 +137,9 @@ describe('removeAlias', () => {
});

it('should remove alias stack', () => {
slsStack1.Outputs.MasterAliasName = {
Value: 'master'
};
aliasApplyStackChangesStub.returns([ slsStack1, [ aliasStack2 ], aliasStack1 ]);
aliasCreateStackChangesStub.returns([ slsStack1, [ aliasStack2 ], aliasStack1 ]);
aliasRemoveAliasStackStub.returns([ slsStack1, [ aliasStack2 ], aliasStack1 ]);
Expand Down
1 change: 0 additions & 1 deletion test/updateAliasStack.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,6 @@ describe('updateAliasStack', () => {
it('should resolve in case no updates are performed', () => {
providerRequestStub.returns(BbPromise.resolve("done"));
monitorStackStub.rejects(new Error('No updates are to be performed.'));

return expect(awsAlias.updateAlias()).to.be.fulfilled
.then(() => expect(providerRequestStub).to.have.been.calledOnce);
});
Expand Down

0 comments on commit f14445c

Please sign in to comment.