diff --git a/docs/providers/aws/guide/deploying.md b/docs/providers/aws/guide/deploying.md index fa05211eb..53b94e23c 100644 --- a/docs/providers/aws/guide/deploying.md +++ b/docs/providers/aws/guide/deploying.md @@ -68,6 +68,9 @@ The Serverless Framework translates all syntax in `serverless.yml` to a single A * You can specify your own S3 bucket which should be used to store all the deployment artifacts. The `deploymentBucket` config which is nested under `provider` lets you e.g. set the `name` or the `serverSideEncryption` method for this bucket +* You can specify your own S3 prefix which should be used to store all the deployment artifacts. + The `deploymentPrefix` config which is nested under `provider` lets you set the prefix under which the deployment artifacts will be stored. If not specified, defaults to `serverless`. + * You can make uploading to S3 faster by adding `--aws-s3-accelerate` Check out the [deploy command docs](../cli-reference/deploy.md) for all details and options. diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index c48c0f6d4..75b8b5248 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -37,6 +37,7 @@ provider: deploymentBucket: name: com.serverless.${self:provider.region}.deploys # Deployment bucket name. Default is generated by the framework serverSideEncryption: AES256 # when using server-side encryption + deploymentPrefix: serverless # The S3 prefix under which deployed artifacts should be stored. Default is serverless role: arn:aws:iam::XXXXXX:role/role # Overwrite the default IAM role which is used for all functions cfnRole: arn:aws:iam::XXXXXX:role/role # ARN of an IAM role for CloudFormation service. If specified, CloudFormation uses the role's credentials versionFunctions: false # Optional function versioning diff --git a/docs/providers/aws/guide/services.md b/docs/providers/aws/guide/services.md index f477df5a0..0d20767c7 100644 --- a/docs/providers/aws/guide/services.md +++ b/docs/providers/aws/guide/services.md @@ -107,6 +107,7 @@ provider: deploymentBucket: name: com.serverless.${self:provider.region}.deploys # Overwrite the default deployment bucket serverSideEncryption: AES256 # when using server-side encryption + deploymentPrefix: serverless # Overwrite the default S3 prefix under which deployed artifacts should be stored. Default is serverless versionFunctions: false # Optional function versioning stackTags: # Optional CF stack tags key: value diff --git a/lib/plugins/aws/deploy/lib/checkForChanges.js b/lib/plugins/aws/deploy/lib/checkForChanges.js index 0254de25c..ec8640875 100644 --- a/lib/plugins/aws/deploy/lib/checkForChanges.js +++ b/lib/plugins/aws/deploy/lib/checkForChanges.js @@ -27,7 +27,7 @@ module.exports = { const params = { Bucket: this.bucketName, - Prefix: `serverless/${service}/${this.provider.getStage()}`, + Prefix: `${this.provider.getDeploymentPrefix()}/${service}/${this.provider.getStage()}`, }; return this.provider.request('S3', diff --git a/lib/plugins/aws/deploy/lib/cleanupS3Bucket.js b/lib/plugins/aws/deploy/lib/cleanupS3Bucket.js index e54403609..4f2bbfb2d 100644 --- a/lib/plugins/aws/deploy/lib/cleanupS3Bucket.js +++ b/lib/plugins/aws/deploy/lib/cleanupS3Bucket.js @@ -10,18 +10,19 @@ module.exports = { const stacksToKeepCount = 5; const service = this.serverless.service.service; const stage = this.provider.getStage(); + const prefix = this.provider.getDeploymentPrefix(); return this.provider.request('S3', 'listObjectsV2', { Bucket: this.bucketName, - Prefix: `serverless/${service}/${stage}`, + Prefix: `${prefix}/${service}/${stage}`, }) .then((response) => { - const stacks = findAndGroupDeployments(response, service, stage); + const stacks = findAndGroupDeployments(response, prefix, service, stage); const stacksToKeep = _.takeRight(stacks, stacksToKeepCount); const stacksToRemove = _.pullAllWith(stacks, stacksToKeep, _.isEqual); - const objectsToRemove = getS3ObjectsFromStacks(stacksToRemove, service, stage); + const objectsToRemove = getS3ObjectsFromStacks(stacksToRemove, prefix, service, stage); if (objectsToRemove.length) { return BbPromise.resolve(objectsToRemove); diff --git a/lib/plugins/aws/deploy/lib/cleanupS3Bucket.test.js b/lib/plugins/aws/deploy/lib/cleanupS3Bucket.test.js index d15d6cd7a..84447eedc 100644 --- a/lib/plugins/aws/deploy/lib/cleanupS3Bucket.test.js +++ b/lib/plugins/aws/deploy/lib/cleanupS3Bucket.test.js @@ -29,7 +29,8 @@ describe('cleanupS3Bucket', () => { provider = new AwsProvider(serverless, options); serverless.setProvider('aws', provider); serverless.service.service = 'cleanupS3Bucket'; - s3Key = `serverless/${serverless.service.service}/${provider.getStage()}`; + const prefix = provider.getDeploymentPrefix(); + s3Key = `${prefix}/${serverless.service.service}/${provider.getStage()}`; awsDeploy = new AwsDeploy(serverless, options); awsDeploy.bucketName = 'deployment-bucket'; awsDeploy.serverless.cli = new serverless.classes.CLI(); diff --git a/lib/plugins/aws/deployList/index.js b/lib/plugins/aws/deployList/index.js index 00a70f98b..81f651854 100644 --- a/lib/plugins/aws/deployList/index.js +++ b/lib/plugins/aws/deployList/index.js @@ -35,17 +35,18 @@ class AwsDeployList { listDeployments() { const service = this.serverless.service.service; const stage = this.provider.getStage(); + const prefix = this.provider.getDeploymentPrefix(); return this.provider.request('S3', 'listObjectsV2', { Bucket: this.bucketName, - Prefix: `serverless/${service}/${stage}`, + Prefix: `${prefix}/${service}/${stage}`, } ) .then((response) => { const directoryRegex = new RegExp('(.+)-(.+-.+-.+)'); - const deployments = findAndGroupDeployments(response, service, stage); + const deployments = findAndGroupDeployments(response, prefix, service, stage); if (deployments.length === 0) { this.serverless.cli.log('Couldn\'t find any existing deployments.'); diff --git a/lib/plugins/aws/deployList/index.test.js b/lib/plugins/aws/deployList/index.test.js index e6aea30bc..92ea6568b 100644 --- a/lib/plugins/aws/deployList/index.test.js +++ b/lib/plugins/aws/deployList/index.test.js @@ -21,7 +21,8 @@ describe('AwsDeployList', () => { provider = new AwsProvider(serverless, options); serverless.setProvider('aws', provider); serverless.service.service = 'listDeployments'; - s3Key = `serverless/${serverless.service.service}/${provider.getStage()}`; + const prefix = provider.getDeploymentPrefix(); + s3Key = `${prefix}/${serverless.service.service}/${provider.getStage()}`; awsDeployList = new AwsDeployList(serverless, options); awsDeployList.bucketName = 'deployment-bucket'; awsDeployList.serverless.cli = { diff --git a/lib/plugins/aws/package/lib/generateArtifactDirectoryName.js b/lib/plugins/aws/package/lib/generateArtifactDirectoryName.js index 8a3840754..795fd3081 100644 --- a/lib/plugins/aws/package/lib/generateArtifactDirectoryName.js +++ b/lib/plugins/aws/package/lib/generateArtifactDirectoryName.js @@ -7,8 +7,9 @@ module.exports = { const date = new Date(); const serviceStage = `${this.serverless.service.service}/${this.provider.getStage()}`; const dateString = `${date.getTime().toString()}-${date.toISOString()}`; + const prefix = this.provider.getDeploymentPrefix(); this.serverless.service.package - .artifactDirectoryName = `serverless/${serviceStage}/${dateString}`; + .artifactDirectoryName = `${prefix}/${serviceStage}/${dateString}`; return BbPromise.resolve(); }, diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index 440735642..53b49ee01 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -266,6 +266,7 @@ class AwsProvider { { providerError: _.assign({}, err, { retryable: false }) } )); } + return BbPromise.reject(Object.assign( new this.serverless.classes.Error(message, err.statusCode), { providerError: err } @@ -390,6 +391,10 @@ class AwsProvider { ).then((result) => result.StackResourceDetail.PhysicalResourceId); } + getDeploymentPrefix() { + return this.serverless.service.provider.deploymentPrefix || 'serverless'; + } + getStageSourceValue() { const values = this.getValues(this, [ ['options', 'stage'], diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index b518cc28d..cc31e3be6 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -911,6 +911,21 @@ describe('AwsProvider', () => { }); }); + describe('#getDeploymentPrefix()', () => { + it('should return custom deployment prefix if defined', () => { + serverless.service.provider.deploymentPrefix = 'providerPrefix'; + + expect(awsProvider.getDeploymentPrefix()) + .to.equal(serverless.service.provider.deploymentPrefix); + }); + + it('should use the default serverless if not defined', () => { + serverless.service.provider.deploymentPrefix = undefined; + + expect(awsProvider.getDeploymentPrefix()).to.equal('serverless'); + }); + }); + describe('#getStage()', () => { it('should prefer options over config or provider', () => { const newOptions = { diff --git a/lib/plugins/aws/remove/lib/bucket.js b/lib/plugins/aws/remove/lib/bucket.js index d9308d2c1..b67abf82e 100644 --- a/lib/plugins/aws/remove/lib/bucket.js +++ b/lib/plugins/aws/remove/lib/bucket.js @@ -18,7 +18,7 @@ module.exports = { return this.provider.request('S3', 'listObjectsV2', { Bucket: this.bucketName, - Prefix: `serverless/${serviceStage}`, + Prefix: `${this.provider.getDeploymentPrefix()}/${serviceStage}`, }).then((result) => { if (result) { result.Contents.forEach((object) => { diff --git a/lib/plugins/aws/remove/lib/bucket.test.js b/lib/plugins/aws/remove/lib/bucket.test.js index e4b8b77eb..8714e96fc 100644 --- a/lib/plugins/aws/remove/lib/bucket.test.js +++ b/lib/plugins/aws/remove/lib/bucket.test.js @@ -42,6 +42,9 @@ describe('emptyS3Bucket', () => { const listObjectsStub = sinon.stub(awsRemove.provider, 'request') .resolves(); + const stage = awsRemove.provider.getStage(); + const prefix = awsRemove.provider.getDeploymentPrefix(); + return awsRemove.listObjects().then(() => { expect(listObjectsStub.calledOnce).to.be.equal(true); expect(listObjectsStub.calledWithExactly( @@ -49,7 +52,7 @@ describe('emptyS3Bucket', () => { 'listObjectsV2', { Bucket: awsRemove.bucketName, - Prefix: `serverless/${serverless.service.service}/${awsRemove.provider.getStage()}`, + Prefix: `${prefix}/${serverless.service.service}/${stage}`, } )).to.be.equal(true); expect(awsRemove.objectsInBucket.length).to.equal(0); @@ -66,6 +69,9 @@ describe('emptyS3Bucket', () => { ], }); + const stage = awsRemove.provider.getStage(); + const prefix = awsRemove.provider.getDeploymentPrefix(); + return awsRemove.listObjects().then(() => { expect(listObjectsStub.calledOnce).to.be.equal(true); expect(listObjectsStub.calledWithExactly( @@ -73,7 +79,7 @@ describe('emptyS3Bucket', () => { 'listObjectsV2', { Bucket: awsRemove.bucketName, - Prefix: `serverless/${serverless.service.service}/${awsRemove.provider.getStage()}`, + Prefix: `${prefix}/${serverless.service.service}/${stage}`, } )).to.be.equal(true); expect(awsRemove.objectsInBucket[0]).to.deep.equal({ Key: 'object1' }); diff --git a/lib/plugins/aws/rollback/index.js b/lib/plugins/aws/rollback/index.js index 9a7ca5edc..296da4df1 100644 --- a/lib/plugins/aws/rollback/index.js +++ b/lib/plugins/aws/rollback/index.js @@ -47,7 +47,8 @@ class AwsRollback { const service = this.serverless.service; const serviceName = this.serverless.service.service; const stage = this.provider.getStage(); - const prefix = `serverless/${serviceName}/${stage}`; + const deploymentPrefix = this.provider.getDeploymentPrefix(); + const prefix = `${deploymentPrefix}/${serviceName}/${stage}`; return this.provider.request('S3', 'listObjectsV2', @@ -56,7 +57,7 @@ class AwsRollback { Prefix: prefix, }) .then((response) => { - const deployments = findAndGroupDeployments(response, serviceName, stage); + const deployments = findAndGroupDeployments(response, deploymentPrefix, serviceName, stage); if (deployments.length === 0) { const msg = 'Couldn\'t find any existing deployments.'; diff --git a/lib/plugins/aws/rollback/index.test.js b/lib/plugins/aws/rollback/index.test.js index edd096c5f..93549df5c 100644 --- a/lib/plugins/aws/rollback/index.test.js +++ b/lib/plugins/aws/rollback/index.test.js @@ -27,7 +27,8 @@ describe('AwsRollback', () => { spawnStub = sinon.stub(serverless.pluginManager, 'spawn'); awsRollback = new AwsRollback(serverless, options); awsRollback.serverless.cli = new serverless.classes.CLI(); - s3Key = `serverless/${serverless.service.service}/${provider.getStage()}`; + const prefix = provider.getDeploymentPrefix(); + s3Key = `${prefix}/${serverless.service.service}/${provider.getStage()}`; }); afterEach(() => { diff --git a/lib/plugins/aws/utils/findAndGroupDeployments.js b/lib/plugins/aws/utils/findAndGroupDeployments.js index 0dc9a570a..aef6e7d36 100644 --- a/lib/plugins/aws/utils/findAndGroupDeployments.js +++ b/lib/plugins/aws/utils/findAndGroupDeployments.js @@ -2,9 +2,9 @@ const _ = require('lodash'); -module.exports = (s3Response, service, stage) => { +module.exports = (s3Response, prefix, service, stage) => { if (s3Response.Contents.length) { - const regex = new RegExp(`serverless/${service}/${stage}/(.+-.+-.+-.+)/(.+)`); + const regex = new RegExp(`${prefix}/${service}/${stage}/(.+-.+-.+-.+)/(.+)`); const s3Objects = s3Response.Contents.filter((s3Object) => s3Object.Key.match(regex)); const names = s3Objects.map((s3Object) => { const match = s3Object.Key.match(regex); diff --git a/lib/plugins/aws/utils/findAndGroupDeployments.test.js b/lib/plugins/aws/utils/findAndGroupDeployments.test.js index 0977869b1..17406cf84 100644 --- a/lib/plugins/aws/utils/findAndGroupDeployments.test.js +++ b/lib/plugins/aws/utils/findAndGroupDeployments.test.js @@ -9,7 +9,7 @@ describe('#findAndGroupDeployments()', () => { Contents: [], }; - expect(findAndGroupDeployments(s3Response, 'test', 'dev')).to.deep.equal([]); + expect(findAndGroupDeployments(s3Response, 'serverless', 'test', 'dev')).to.deep.equal([]); }); it('should group stacks', () => { @@ -73,6 +73,7 @@ describe('#findAndGroupDeployments()', () => { ], ]; - expect(findAndGroupDeployments(s3Response, 'test', 'dev')).to.deep.equal(expected); + expect(findAndGroupDeployments(s3Response, 'serverless', 'test', 'dev')) + .to.deep.equal(expected); }); }); diff --git a/lib/plugins/aws/utils/getS3ObjectsFromStacks.js b/lib/plugins/aws/utils/getS3ObjectsFromStacks.js index 8ae79200a..1a98dc822 100644 --- a/lib/plugins/aws/utils/getS3ObjectsFromStacks.js +++ b/lib/plugins/aws/utils/getS3ObjectsFromStacks.js @@ -2,8 +2,8 @@ const _ = require('lodash'); -module.exports = (stacks, service, stage) => ( +module.exports = (stacks, prefix, service, stage) => ( _.flatten(stacks).map((entry) => ( - { Key: `serverless/${service}/${stage}/${entry.directory}/${entry.file}` }) + { Key: `${prefix}/${service}/${stage}/${entry.directory}/${entry.file}` }) ) ); diff --git a/lib/plugins/aws/utils/getS3ObjectsFromStacks.test.js b/lib/plugins/aws/utils/getS3ObjectsFromStacks.test.js index d5f9851a7..df6f4beda 100644 --- a/lib/plugins/aws/utils/getS3ObjectsFromStacks.test.js +++ b/lib/plugins/aws/utils/getS3ObjectsFromStacks.test.js @@ -5,7 +5,7 @@ const getS3ObjectsFromStacks = require('./getS3ObjectsFromStacks'); describe('#getS3ObjectsFromStacks()', () => { it('should return an empty result in case no stacks are provided', () => { - expect(getS3ObjectsFromStacks([], 'test', 'dev')).to.deep.equal([]); + expect(getS3ObjectsFromStacks([], 'serverless', 'test', 'dev')).to.deep.equal([]); }); it('should return an empty result in case no stacks are provided', () => { @@ -41,6 +41,6 @@ describe('#getS3ObjectsFromStacks()', () => { { Key: 'serverless/test/dev/1476779278222-2016-10-18T08:27:58.222Z/test.zip' }, ]; - expect(getS3ObjectsFromStacks(stacks, 'test', 'dev')).to.deep.equal(expected); + expect(getS3ObjectsFromStacks(stacks, 'serverless', 'test', 'dev')).to.deep.equal(expected); }); });