Merge pull request #5299 from weeniearms/master

Added possibility to specify custom S3 key prefix instead of the stan…
This commit is contained in:
Takahiro Horike 2018-10-15 02:59:49 +09:00 committed by GitHub
commit bf2ee2e56b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 62 additions and 23 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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);

View File

@ -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();

View File

@ -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.');

View File

@ -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 = {

View File

@ -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();
},

View File

@ -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'],

View File

@ -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 = {

View File

@ -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) => {

View File

@ -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' });

View File

@ -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.';

View File

@ -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(() => {

View File

@ -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);

View File

@ -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);
});
});

View File

@ -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}` })
)
);

View File

@ -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);
});
});