Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow configurable master alias #156

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
1 change: 1 addition & 0 deletions lib/stackops/apiGateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
// Adjust permission to reference the function aliases
_.forOwn(apiLambdaPermissions, (permission, name) => {
const functionName = _.replace(name, /LambdaPermissionApiGateway$/, '');

const versionName = utils.getFunctionVersionName(versions, functionName);
const aliasName = utils.getAliasVersionName(aliases, functionName);
const isExternalRef = isExternalRefAuthorizerPredicate(permission.Properties.FunctionName);
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