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

Add Serverless instanceId concept #5926

Merged
merged 5 commits into from
Mar 25, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/providers/aws/guide/resources.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ You can overwrite/attach any kind of resource to your CloudFormation stack. You


To have consistent naming in the CloudFormation Templates that get deployed we use a standard pattern: To have consistent naming in the CloudFormation Templates that get deployed we use a standard pattern:


`{Function Name}{Cloud Formation Resource Type}{Resource Name}{SequentialID or Random String}` `{Function Name}{Cloud Formation Resource Type}{Resource Name}{SequentialID, instanceId or Random String}`


* `Function Name` - This is optional for Resources that should be recreated when the function name gets changed. Those resources are also called *function bound* * `Function Name` - This is optional for Resources that should be recreated when the function name gets changed. Those resources are also called *function bound*
* `Cloud Formation Resource Type` - E.g., S3Bucket * `Cloud Formation Resource Type` - E.g., S3Bucket
* `Resource Name` - An identifier for the specific resource, e.g. for an S3 Bucket the configured bucket name. * `Resource Name` - An identifier for the specific resource, e.g. for an S3 Bucket the configured bucket name.
* `SequentialID or Random String` - For a few resources we need to add an optional sequential id or random string to identify them * `SequentialID, instanceId or Random String` - For a few resources we need to add an optional sequential id, the Serverless instanceId (accessible via `${sls:instanceId}`) or a random string to identify them


All resource names that are deployed by Serverless have to follow this naming scheme. The only exception (for backwards compatibility reasons) is the S3 Bucket that is used to upload artifacts so they can be deployed to your function. All resource names that are deployed by Serverless have to follow this naming scheme. The only exception (for backwards compatibility reasons) is the S3 Bucket that is used to upload artifacts so they can be deployed to your function.


Expand All @@ -81,7 +81,7 @@ If you are unsure how a resource is named, that you want to reference from your
|ApiGateway::Resource | ApiGatewayResource{normalizedPath} | ApiGatewayResourceUsers | |ApiGateway::Resource | ApiGatewayResource{normalizedPath} | ApiGatewayResourceUsers |
|ApiGateway::Method | ApiGatewayMethod{normalizedPath}{normalizedMethod} | ApiGatewayMethodUsersGet | |ApiGateway::Method | ApiGatewayMethod{normalizedPath}{normalizedMethod} | ApiGatewayMethodUsersGet |
|ApiGateway::Authorizer | {normalizedFunctionName}ApiGatewayAuthorizer | HelloApiGatewayAuthorizer | |ApiGateway::Authorizer | {normalizedFunctionName}ApiGatewayAuthorizer | HelloApiGatewayAuthorizer |
|ApiGateway::Deployment | ApiGatewayDeployment{randomNumber} | ApiGatewayDeployment12356789 | |ApiGateway::Deployment | ApiGatewayDeployment{instanceId} | ApiGatewayDeployment12356789 |
|ApiGateway::ApiKey | ApiGatewayApiKey{SequentialID} | ApiGatewayApiKey1 | |ApiGateway::ApiKey | ApiGatewayApiKey{SequentialID} | ApiGatewayApiKey1 |
|ApiGateway::UsagePlan | ApiGatewayUsagePlan | ApiGatewayUsagePlan | |ApiGateway::UsagePlan | ApiGatewayUsagePlan | ApiGatewayUsagePlan |
|ApiGateway::UsagePlanKey | ApiGatewayUsagePlanKey{SequentialID} | ApiGatewayUsagePlanKey1 | |ApiGateway::UsagePlanKey | ApiGatewayUsagePlanKey{SequentialID} | ApiGatewayUsagePlanKey1 |
Expand Down
24 changes: 23 additions & 1 deletion docs/providers/aws/guide/variables.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ You can define your own variable syntax (regex) if it conflicts with CloudFormat


## Current variable sources: ## Current variable sources:


- [Serverless Core variables](#referencing-serverless-core-variables)
- [Environment variables](#referencing-environment-variables) - [Environment variables](#referencing-environment-variables)
- [CLI options](#referencing-cli-options) - [CLI options](#referencing-cli-options)
- [Other properties defined in `serverless.yml`](#reference-properties-in-serverlessyml) - [Other properties defined in `serverless.yml`](#reference-properties-in-serverlessyml)
Expand Down Expand Up @@ -103,6 +104,27 @@ resources:


In the above example you're setting a global schedule for all functions by referencing the `globalSchedule` property in the same `serverless.yml` file. This way, you can easily change the schedule for all functions whenever you like. In the above example you're setting a global schedule for all functions by referencing the `globalSchedule` property in the same `serverless.yml` file. This way, you can easily change the schedule for all functions whenever you like.


## Referencing Serverless Core Variables
Serverless initializes core variables which are used internally by the Framework itself. Those values are exposed via the Serverless Variables system and can be re-used with the `{sls:}` variable prefix.

The following variables are available:

**instanceId**

A random id which will be generated whenever the Serverless CLI is run. This value can be used when predictable random variables are required.

```yml
service: new-service
provider: aws

functions:
func1:
name: function-1
handler: handler.func1
environment:
APIG_DEPLOYMENT_ID: ApiGatewayDeployment${sls:instanceId}
```

## Referencing Environment Variables ## Referencing Environment Variables
To reference environment variables, use the `${env:SOME_VAR}` syntax in your `serverless.yml` configuration file. It is valid to use the empty string in place of `SOME_VAR`. This looks like "`${env:}`" and the result of declaring this in your `serverless.yml` is to embed the complete `process.env` object (i.e. all the variables defined in your environment). To reference environment variables, use the `${env:SOME_VAR}` syntax in your `serverless.yml` configuration file. It is valid to use the empty string in place of `SOME_VAR`. This looks like "`${env:}`" and the result of declaring this in your `serverless.yml` is to embed the complete `process.env` object (i.e. all the variables defined in your environment).


Expand Down Expand Up @@ -296,7 +318,7 @@ functions:
name: hello name: hello
handler: handler.hello handler: handler.hello
custom: custom:
supersecret: supersecret:
num: 1 num: 1
str: secret str: secret
arr: arr:
Expand Down
3 changes: 3 additions & 0 deletions lib/Serverless.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class Serverless {
} }


init() { init() {
// create an instanceId (can be e.g. used when a predictable random value is needed)
this.instanceId = (new Date()).getTime().toString();

// create a new CLI instance // create a new CLI instance
this.cli = new this.classes.CLI(this); this.cli = new this.classes.CLI(this);


Expand Down
4 changes: 4 additions & 0 deletions lib/Serverless.test.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ describe('Serverless', () => {
serverless.pluginManager.updateAutocompleteCacheFile.restore(); serverless.pluginManager.updateAutocompleteCacheFile.restore();
}); });


it('should set an instanceId', () => serverless.init().then(() => {
expect(serverless.instanceId).to.match(/\d/);
}));

it('should create a new CLI instance', () => serverless.init().then(() => { it('should create a new CLI instance', () => serverless.init().then(() => {
expect(serverless.cli).to.be.instanceof(CLI); expect(serverless.cli).to.be.instanceof(CLI);
})); }));
Expand Down
14 changes: 13 additions & 1 deletion lib/classes/Variables.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Variables {
this.deepRefSyntax = RegExp(/(\${)?deep:\d+(\.[^}]+)*()}?/); this.deepRefSyntax = RegExp(/(\${)?deep:\d+(\.[^}]+)*()}?/);
this.overwriteSyntax = RegExp(/\s*(?:,\s*)+/g); this.overwriteSyntax = RegExp(/\s*(?:,\s*)+/g);
this.fileRefSyntax = RegExp(/^file\(([^?%*:|"<>]+?)\)/g); this.fileRefSyntax = RegExp(/^file\(([^?%*:|"<>]+?)\)/g);
this.slsRefSyntax = RegExp(/^sls:/g);
this.envRefSyntax = RegExp(/^env:/g); this.envRefSyntax = RegExp(/^env:/g);
this.optRefSyntax = RegExp(/^opt:/g); this.optRefSyntax = RegExp(/^opt:/g);
this.selfRefSyntax = RegExp(/^self:/g); this.selfRefSyntax = RegExp(/^self:/g);
Expand Down Expand Up @@ -516,7 +517,9 @@ class Variables {
if (this.tracker.contains(variableString)) { if (this.tracker.contains(variableString)) {
ret = this.tracker.get(variableString, propertyString); ret = this.tracker.get(variableString, propertyString);
} else { } else {
if (variableString.match(this.envRefSyntax)) { if (variableString.match(this.slsRefSyntax)) {
ret = this.getValueFromSls(variableString);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. what about adding a special case to self: like we already have for self:provider for example?

} else if (variable === 'self:provider') {

Copy link
Contributor Author

@pmuens pmuens Mar 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was also thinking about that. I considered ${self:} and ${env:}. The problem with self is that it's always referencing the serverless.yml (at least that's the mental model).

So I thought it would be confusing to add it and it's not visible in the YAML.

But good feedback 👍

} else if (variableString.match(this.envRefSyntax)) {
ret = this.getValueFromEnv(variableString); ret = this.getValueFromEnv(variableString);
} else if (variableString.match(this.optRefSyntax)) { } else if (variableString.match(this.optRefSyntax)) {
ret = this.getValueFromOptions(variableString); ret = this.getValueFromOptions(variableString);
Expand Down Expand Up @@ -547,6 +550,15 @@ class Variables {
return ret; return ret;
} }


getValueFromSls(variableString) {
let valueToPopulate = {};
const requestedSlsVar = variableString.split(':')[1];
if (requestedSlsVar === 'instanceId') {
valueToPopulate = this.serverless.instanceId;
}
return BbPromise.resolve(valueToPopulate);
}

getValueFromEnv(variableString) { // eslint-disable-line class-methods-use-this getValueFromEnv(variableString) { // eslint-disable-line class-methods-use-this
const requestedEnvVar = variableString.split(':')[1]; const requestedEnvVar = variableString.split(':')[1];
let valueToPopulate; let valueToPopulate;
Expand Down
156 changes: 93 additions & 63 deletions lib/classes/Variables.test.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1328,97 +1328,119 @@ module.exports = {
}); });


describe('#getValueFromSource()', () => { describe('#getValueFromSource()', () => {
it('should call getValueFromEnv if referencing env var', () => { const variableValue = 'variableValue';
const getValueFromEnvStub = sinon.stub(serverless.variables, 'getValueFromEnv') let getValueFromSlsStub;
let getValueFromEnvStub;
let getValueFromOptionsStub;
let getValueFromSelfStub;
let getValueFromFileStub;
let getValueFromCfStub;
let getValueFromS3Stub;
let getValueFromSsmStub;

beforeEach(() => {
getValueFromSlsStub = sinon.stub(serverless.variables, 'getValueFromSls')
.resolves('variableValue');
getValueFromEnvStub = sinon.stub(serverless.variables, 'getValueFromEnv')
.resolves('variableValue');
getValueFromOptionsStub = sinon.stub(serverless.variables, 'getValueFromOptions')
.resolves('variableValue');
getValueFromSelfStub = sinon.stub(serverless.variables, 'getValueFromSelf')
.resolves('variableValue');
getValueFromFileStub = sinon.stub(serverless.variables, 'getValueFromFile')
.resolves('variableValue'); .resolves('variableValue');
return serverless.variables.getValueFromSource('env:TEST_VAR').should.be.fulfilled getValueFromCfStub = sinon.stub(serverless.variables, 'getValueFromCf')
.resolves('variableValue');
getValueFromS3Stub = sinon.stub(serverless.variables, 'getValueFromS3')
.resolves('variableValue');
getValueFromSsmStub = sinon.stub(serverless.variables, 'getValueFromSsm')
.resolves('variableValue');
});

afterEach(() => {
serverless.variables.getValueFromSls.restore();
serverless.variables.getValueFromEnv.restore();
serverless.variables.getValueFromOptions.restore();
serverless.variables.getValueFromSelf.restore();
serverless.variables.getValueFromFile.restore();
serverless.variables.getValueFromCf.restore();
serverless.variables.getValueFromS3.restore();
serverless.variables.getValueFromSsm.restore();
});

it('should call getValueFromSls if referencing sls var', () => serverless.variables
.getValueFromSource('sls:instanceId').should.be.fulfilled
.then((valueToPopulate) => { .then((valueToPopulate) => {
expect(valueToPopulate).to.equal('variableValue'); expect(valueToPopulate).to.equal(variableValue);
expect(getValueFromSlsStub).to.have.been.called;
expect(getValueFromSlsStub).to.have.been.calledWith('sls:instanceId');
}));

it('should call getValueFromEnv if referencing env var', () => serverless.variables
.getValueFromSource('env:TEST_VAR').should.be.fulfilled
.then((valueToPopulate) => {
expect(valueToPopulate).to.equal(variableValue);
expect(getValueFromEnvStub).to.have.been.called; expect(getValueFromEnvStub).to.have.been.called;
expect(getValueFromEnvStub).to.have.been.calledWith('env:TEST_VAR'); expect(getValueFromEnvStub).to.have.been.calledWith('env:TEST_VAR');
}) }));
.finally(() => getValueFromEnvStub.restore());
});


it('should call getValueFromOptions if referencing an option', () => { it('should call getValueFromOptions if referencing an option', () => serverless.variables
const getValueFromOptionsStub = sinon .getValueFromSource('opt:stage').should.be.fulfilled
.stub(serverless.variables, 'getValueFromOptions')
.resolves('variableValue');
return serverless.variables.getValueFromSource('opt:stage').should.be.fulfilled
.then((valueToPopulate) => { .then((valueToPopulate) => {
expect(valueToPopulate).to.equal('variableValue'); expect(valueToPopulate).to.equal(variableValue);
expect(getValueFromOptionsStub).to.have.been.called; expect(getValueFromOptionsStub).to.have.been.called;
expect(getValueFromOptionsStub).to.have.been.calledWith('opt:stage'); expect(getValueFromOptionsStub).to.have.been.calledWith('opt:stage');
}) }));
.finally(() => getValueFromOptionsStub.restore());
});


it('should call getValueFromSelf if referencing from self', () => { it('should call getValueFromSelf if referencing from self', () => serverless.variables
const getValueFromSelfStub = sinon.stub(serverless.variables, 'getValueFromSelf') .getValueFromSource('self:provider').should.be.fulfilled
.resolves('variableValue');
return serverless.variables.getValueFromSource('self:provider').should.be.fulfilled
.then((valueToPopulate) => { .then((valueToPopulate) => {
expect(valueToPopulate).to.equal('variableValue'); expect(valueToPopulate).to.equal(variableValue);
expect(getValueFromSelfStub).to.have.been.called; expect(getValueFromSelfStub).to.have.been.called;
expect(getValueFromSelfStub).to.have.been.calledWith('self:provider'); expect(getValueFromSelfStub).to.have.been.calledWith('self:provider');
}) }));
.finally(() => getValueFromSelfStub.restore());
});


it('should call getValueFromFile if referencing from another file', () => { it('should call getValueFromFile if referencing from another file', () => serverless.variables
const getValueFromFileStub = sinon.stub(serverless.variables, 'getValueFromFile') .getValueFromSource('file(./config.yml)').should.be.fulfilled
.resolves('variableValue');
return serverless.variables.getValueFromSource('file(./config.yml)').should.be.fulfilled
.then((valueToPopulate) => { .then((valueToPopulate) => {
expect(valueToPopulate).to.equal('variableValue'); expect(valueToPopulate).to.equal(variableValue);
expect(getValueFromFileStub).to.have.been.called; expect(getValueFromFileStub).to.have.been.called;
expect(getValueFromFileStub).to.have.been.calledWith('file(./config.yml)'); expect(getValueFromFileStub).to.have.been.calledWith('file(./config.yml)');
}) }));
.finally(() => getValueFromFileStub.restore());
});


it('should call getValueFromCf if referencing CloudFormation Outputs', () => { it('should call getValueFromCf if referencing CloudFormation Outputs', () => serverless
const getValueFromCfStub = sinon.stub(serverless.variables, 'getValueFromCf') .variables.getValueFromSource('cf:test-stack.testOutput').should.be.fulfilled
.resolves('variableValue');
return serverless.variables.getValueFromSource('cf:test-stack.testOutput').should.be.fulfilled
.then((valueToPopulate) => { .then((valueToPopulate) => {
expect(valueToPopulate).to.equal('variableValue'); expect(valueToPopulate).to.equal(variableValue);
expect(getValueFromCfStub).to.have.been.called; expect(getValueFromCfStub).to.have.been.called;
expect(getValueFromCfStub).to.have.been.calledWith('cf:test-stack.testOutput'); expect(getValueFromCfStub).to.have.been.calledWith('cf:test-stack.testOutput');
}) }));
.finally(() => getValueFromCfStub.restore());
});


it('should call getValueFromS3 if referencing variable in S3', () => { it('should call getValueFromS3 if referencing variable in S3', () => serverless.variables
const getValueFromS3Stub = sinon.stub(serverless.variables, 'getValueFromS3') .getValueFromSource('s3:test-bucket/path/to/key')
.resolves('variableValue');
return serverless.variables.getValueFromSource('s3:test-bucket/path/to/key')
.should.be.fulfilled .should.be.fulfilled
.then((valueToPopulate) => { .then((valueToPopulate) => {
expect(valueToPopulate).to.equal('variableValue'); expect(valueToPopulate).to.equal(variableValue);
expect(getValueFromS3Stub).to.have.been.called; expect(getValueFromS3Stub).to.have.been.called;
expect(getValueFromS3Stub).to.have.been.calledWith('s3:test-bucket/path/to/key'); expect(getValueFromS3Stub).to.have.been.calledWith('s3:test-bucket/path/to/key');
}) }));
.finally(() => getValueFromS3Stub.restore());
});


it('should call getValueFromSsm if referencing variable in SSM', () => { it('should call getValueFromSsm if referencing variable in SSM', () => serverless.variables
const getValueFromSsmStub = sinon.stub(serverless.variables, 'getValueFromSsm') .getValueFromSource('ssm:/test/path/to/param')
.resolves('variableValue');
return serverless.variables.getValueFromSource('ssm:/test/path/to/param')
.should.be.fulfilled .should.be.fulfilled
.then((valueToPopulate) => { .then((valueToPopulate) => {
expect(valueToPopulate).to.equal('variableValue'); expect(valueToPopulate).to.equal(variableValue);
expect(getValueFromSsmStub).to.have.been.called; expect(getValueFromSsmStub).to.have.been.called;
expect(getValueFromSsmStub).to.have.been.calledWith('ssm:/test/path/to/param'); expect(getValueFromSsmStub).to.have.been.calledWith('ssm:/test/path/to/param');
}) }));
.finally(() => getValueFromSsmStub.restore());
});
it('should reject invalid sources', () => it('should reject invalid sources', () =>
serverless.variables.getValueFromSource('weird:source') serverless.variables.getValueFromSource('weird:source')
.should.be.rejectedWith(serverless.classes.Error)); .should.be.rejectedWith(serverless.classes.Error));

describe('caching', () => { describe('caching', () => {
const sources = [ const sources = [
{ function: 'getValueFromSls', variableString: 'sls:instanceId' },
{ function: 'getValueFromEnv', variableString: 'env:NODE_ENV' }, { function: 'getValueFromEnv', variableString: 'env:NODE_ENV' },
{ function: 'getValueFromOptions', variableString: 'opt:stage' }, { function: 'getValueFromOptions', variableString: 'opt:stage' },
{ function: 'getValueFromSelf', variableString: 'self:provider' }, { function: 'getValueFromSelf', variableString: 'self:provider' },
Expand All @@ -1429,23 +1451,31 @@ module.exports = {
]; ];
sources.forEach((source) => { sources.forEach((source) => {
it(`should only call ${source.function} once, returning the cached value otherwise`, () => { it(`should only call ${source.function} once, returning the cached value otherwise`, () => {
const value = 'variableValue'; const getValueFunctionStub = serverless.variables[source.function];
const getValueFunctionStub = sinon.stub(serverless.variables, source.function)
.resolves(value);
return BbPromise.all([ return BbPromise.all([
serverless.variables.getValueFromSource(source.variableString).should.become(value), serverless.variables.getValueFromSource(source.variableString)
.should.become(variableValue),
BbPromise.delay(100).then(() => BbPromise.delay(100).then(() =>
serverless.variables.getValueFromSource(source.variableString).should.become(value)), serverless.variables.getValueFromSource(source.variableString)
.should.become(variableValue)),
]).then(() => { ]).then(() => {
expect(getValueFunctionStub).to.have.been.calledOnce; expect(getValueFunctionStub).to.have.been.calledOnce;
expect(getValueFunctionStub).to.have.been.calledWith(source.variableString); expect(getValueFunctionStub).to.have.been.calledWith(source.variableString);
}).finally(() => });
getValueFunctionStub.restore());
}); });
}); });
}); });
}); });


describe('#getValueFromSls()', () => {
it('should get variable from Serverless Framework provided variables', () => {
serverless.instanceId = 12345678;
return serverless.variables.getValueFromSls('sls:instanceId').then((valueToPopulate) => {
expect(valueToPopulate).to.equal(12345678);
});
});
});

describe('#getValueFromEnv()', () => { describe('#getValueFromEnv()', () => {
it('should get variable from environment variables', () => { it('should get variable from environment variables', () => {
process.env.TEST_VAR = 'someValue'; process.env.TEST_VAR = 'someValue';
Expand Down
8 changes: 4 additions & 4 deletions lib/plugins/aws/lib/naming.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ module.exports = {
return `${this.getNormalizedWebsocketsRouteKey(route)}WebsocketsRoute`; return `${this.getNormalizedWebsocketsRouteKey(route)}WebsocketsRoute`;
}, },


getWebsocketsDeploymentLogicalId() { getWebsocketsDeploymentLogicalId(id) {
return `WebsocketsDeployment${(new Date()).getTime().toString()}`; return `WebsocketsDeployment${id}`;
}, },


getWebsocketsStageLogicalId() { getWebsocketsStageLogicalId() {
Expand All @@ -213,8 +213,8 @@ module.exports = {
} }
return `${this.provider.getStage()}-${this.provider.serverless.service.service}`; return `${this.provider.getStage()}-${this.provider.serverless.service.service}`;
}, },
generateApiGatewayDeploymentLogicalId() { generateApiGatewayDeploymentLogicalId(id) {
return `ApiGatewayDeployment${(new Date()).getTime().toString()}`; return `ApiGatewayDeployment${id}`;
}, },
getRestApiLogicalId() { getRestApiLogicalId() {
return 'ApiGatewayRestApi'; return 'ApiGatewayRestApi';
Expand Down
11 changes: 5 additions & 6 deletions lib/plugins/aws/lib/naming.test.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -283,8 +283,8 @@ describe('#naming()', () => {


describe('#getWebsocketsDeploymentLogicalId()', () => { describe('#getWebsocketsDeploymentLogicalId()', () => {
it('should return the websockets deployment logical id', () => { it('should return the websockets deployment logical id', () => {
expect(sdk.naming.getWebsocketsDeploymentLogicalId()) expect(sdk.naming.getWebsocketsDeploymentLogicalId(1234))
.to.match(/WebsocketsDeployment.+/); .to.equal('WebsocketsDeployment1234');
}); });
}); });


Expand Down Expand Up @@ -318,10 +318,9 @@ describe('#naming()', () => {
}); });


describe('#generateApiGatewayDeploymentLogicalId()', () => { describe('#generateApiGatewayDeploymentLogicalId()', () => {
it('should return ApiGatewayDeployment with a date based suffix', () => { it('should return ApiGatewayDeployment with a suffix', () => {
expect(sdk.naming.generateApiGatewayDeploymentLogicalId() expect(sdk.naming.generateApiGatewayDeploymentLogicalId(1234))
.match(/ApiGatewayDeployment(.*)/).length) .to.equal('ApiGatewayDeployment1234');
.to.be.greaterThan(1);
}); });
}); });


Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const BbPromise = require('bluebird');
module.exports = { module.exports = {
compileDeployment() { compileDeployment() {
this.apiGatewayDeploymentLogicalId = this.provider.naming this.apiGatewayDeploymentLogicalId = this.provider.naming
.generateApiGatewayDeploymentLogicalId(); .generateApiGatewayDeploymentLogicalId(this.serverless.instanceId);


_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
[this.apiGatewayDeploymentLogicalId]: { [this.apiGatewayDeploymentLogicalId]: {
Expand Down