Skip to content

Commit

Permalink
Added possibility to specify custom S3 key prefix instead of the stan…
Browse files Browse the repository at this point in the history
…dard 'serverless'
  • Loading branch information
weeniearms committed Sep 17, 2018
1 parent cfc2ac8 commit 00fb7c6
Show file tree
Hide file tree
Showing 19 changed files with 62 additions and 23 deletions.
3 changes: 3 additions & 0 deletions docs/providers/aws/guide/deploying.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions docs/providers/aws/guide/serverless.yml.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/providers/aws/guide/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/plugins/aws/deploy/lib/checkForChanges.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
7 changes: 4 additions & 3 deletions lib/plugins/aws/deploy/lib/cleanupS3Bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion lib/plugins/aws/deploy/lib/cleanupS3Bucket.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
5 changes: 3 additions & 2 deletions lib/plugins/aws/deployList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,18 @@ class AwsDeployList {
listDeployments() {
const service = this.serverless.service.service;
const stage = this.provider.getStage();
const deploymentPrefix = this.provider.getDeploymentPrefix();

return this.provider.request('S3',
'listObjectsV2',
{
Bucket: this.bucketName,
Prefix: `serverless/${service}/${stage}`,
Prefix: `${deploymentPrefix}/${service}/${stage}`,
}
)
.then((response) => {
const directoryRegex = new RegExp('(.+)-(.+-.+-.+)');
const deployments = findAndGroupDeployments(response, service, stage);
const deployments = findAndGroupDeployments(response, deploymentPrefix, service, stage);

if (deployments.length === 0) {
this.serverless.cli.log('Couldn\'t find any existing deployments.');
Expand Down
3 changes: 2 additions & 1 deletion lib/plugins/aws/deployList/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
3 changes: 2 additions & 1 deletion lib/plugins/aws/package/lib/generateArtifactDirectoryName.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
},
Expand Down
5 changes: 5 additions & 0 deletions lib/plugins/aws/provider/awsProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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'],
Expand Down
15 changes: 15 additions & 0 deletions lib/plugins/aws/provider/awsProvider.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
2 changes: 1 addition & 1 deletion lib/plugins/aws/remove/lib/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
10 changes: 8 additions & 2 deletions lib/plugins/aws/remove/lib/bucket.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,17 @@ 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(
'S3',
'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);
Expand All @@ -66,14 +69,17 @@ 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(
'S3',
'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' });
Expand Down
5 changes: 3 additions & 2 deletions lib/plugins/aws/rollback/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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.';
Expand Down
3 changes: 2 additions & 1 deletion lib/plugins/aws/rollback/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
4 changes: 2 additions & 2 deletions lib/plugins/aws/utils/findAndGroupDeployments.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions lib/plugins/aws/utils/findAndGroupDeployments.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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);
});
});
4 changes: 2 additions & 2 deletions lib/plugins/aws/utils/getS3ObjectsFromStacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}` })
)
);
4 changes: 2 additions & 2 deletions lib/plugins/aws/utils/getS3ObjectsFromStacks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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);
});
});

0 comments on commit 00fb7c6

Please sign in to comment.