Merge pull request #3668 from hassankhan/validate-cf-template-locally

Add support for validating CF templates
This commit is contained in:
Philipp Muens 2017-06-05 11:50:00 +02:00 committed by GitHub
commit 9e068b81b3
4 changed files with 137 additions and 1 deletions

View File

@ -15,6 +15,7 @@ const createStack = require('./lib/createStack');
const setBucketName = require('../lib/setBucketName');
const cleanupS3Bucket = require('./lib/cleanupS3Bucket');
const uploadArtifacts = require('./lib/uploadArtifacts');
const validateTemplate = require('./lib/validateTemplate');
const updateStack = require('../lib/updateStack');
const path = require('path');
@ -35,6 +36,7 @@ class AwsDeploy {
setBucketName,
cleanupS3Bucket,
uploadArtifacts,
validateTemplate,
updateStack,
monitorStack
);
@ -50,6 +52,7 @@ class AwsDeploy {
lifecycleEvents: [
'createStack',
'uploadArtifacts',
'validateTemplate',
'updateStack',
],
},
@ -95,6 +98,9 @@ class AwsDeploy {
.then(this.setBucketName)
.then(this.uploadArtifacts),
'aws:deploy:deploy:validateTemplate': () => BbPromise.bind(this)
.then(this.validateTemplate),
'aws:deploy:deploy:updateStack': () => BbPromise.bind(this)
.then(this.updateStack),

View File

@ -1,12 +1,19 @@
'use strict';
/* eslint-disable no-unused-expressions */
const AwsProvider = require('../provider/awsProvider');
const AwsDeploy = require('./index');
const chai = require('chai');
const Serverless = require('../../../Serverless');
const expect = require('chai').expect;
const sinon = require('sinon');
const path = require('path');
// Configure chai
chai.use(require('chai-as-promised'));
chai.use(require('sinon-chai'));
const expect = require('chai').expect;
describe('AwsDeploy', () => {
let awsDeploy;
let serverless;
@ -78,6 +85,7 @@ describe('AwsDeploy', () => {
let createStackStub;
let setBucketNameStub;
let uploadArtifactsStub;
let validateTemplateStub;
let updateStackStub;
beforeEach(() => {
@ -89,6 +97,8 @@ describe('AwsDeploy', () => {
.stub(awsDeploy, 'setBucketName').resolves();
uploadArtifactsStub = sinon
.stub(awsDeploy, 'uploadArtifacts').resolves();
validateTemplateStub = sinon
.stub(awsDeploy, 'validateTemplate').resolves();
updateStackStub = sinon
.stub(awsDeploy, 'updateStack').resolves();
});
@ -98,6 +108,7 @@ describe('AwsDeploy', () => {
awsDeploy.createStack.restore();
awsDeploy.setBucketName.restore();
awsDeploy.uploadArtifacts.restore();
awsDeploy.validateTemplate.restore();
awsDeploy.updateStack.restore();
});
@ -189,6 +200,12 @@ describe('AwsDeploy', () => {
})
);
it('should run "aws:deploy:deploy:validateTemplate" hook', () => expect(awsDeploy
.hooks['aws:deploy:deploy:validateTemplate']()).to.be.fulfilled.then(() => {
expect(validateTemplateStub).to.have.been.calledOnce;
})
);
it('should run "aws:deploy:deploy:updateStack" hook', () => awsDeploy
.hooks['aws:deploy:deploy:updateStack']().then(() => {
expect(updateStackStub.calledOnce).to.equal(true);

View File

@ -0,0 +1,28 @@
'use strict';
module.exports = {
validateTemplate() {
const bucketName = this.bucketName;
const artifactDirectoryName = this.serverless.service.package.artifactDirectoryName;
const compiledTemplateFileName = 'compiled-cloudformation-template.json';
this.serverless.cli.log('Validating template...');
const params = {
TemplateURL: `https://s3.amazonaws.com/${bucketName}/${artifactDirectoryName}/${compiledTemplateFileName}`,
};
return this.provider.request(
'CloudFormation',
'validateTemplate',
params,
this.options.stage,
this.options.region
).catch((error) => {
const errorMessage = [
'The CloudFormation template is invalid:',
` ${error.message}`,
].join('');
throw new Error(errorMessage);
});
},
};

View File

@ -0,0 +1,85 @@
'use strict';
/* eslint-disable no-unused-expressions */
const sinon = require('sinon');
const chai = require('chai');
const AwsProvider = require('../../provider/awsProvider');
const AwsDeploy = require('../index');
const Serverless = require('../../../../Serverless');
// Configure chai
chai.use(require('chai-as-promised'));
chai.use(require('sinon-chai'));
const expect = require('chai').expect;
describe('validateTemplate', () => {
let awsDeploy;
let serverless;
let validateTemplateStub;
beforeEach(() => {
serverless = new Serverless();
serverless.config.servicePath = 'foo';
serverless.setProvider('aws', new AwsProvider(serverless));
const options = {
stage: 'dev',
region: 'us-east-1',
};
awsDeploy = new AwsDeploy(serverless, options);
awsDeploy.bucketName = 'deployment-bucket';
awsDeploy.serverless.service.package.artifactDirectoryName = 'somedir';
awsDeploy.serverless.service.functions = {
first: {
handler: 'foo',
},
};
validateTemplateStub = sinon.stub(awsDeploy.provider, 'request');
awsDeploy.serverless.cli = {
log: sinon.spy(),
};
});
afterEach(() => {
awsDeploy.provider.request.restore();
});
describe('#validateTemplate()', () => {
it('should resolve if the CloudFormation template is valid', () => {
validateTemplateStub.resolves();
return expect(awsDeploy.validateTemplate()).to.be.fulfilled.then(() => {
expect(awsDeploy.serverless.cli.log).to.have.been.called;
expect(validateTemplateStub).to.have.been.calledOnce;
expect(validateTemplateStub).to.have.been.calledWithExactly(
'CloudFormation',
'validateTemplate',
{
TemplateURL: 'https://s3.amazonaws.com/deployment-bucket/somedir/compiled-cloudformation-template.json',
},
awsDeploy.options.stage,
awsDeploy.options.region
);
});
});
it('should throw an error if the CloudFormation template is invalid', () => {
validateTemplateStub.rejects({ message: 'Some error while validating' });
return expect(awsDeploy.validateTemplate()).to.be.rejected.then((error) => {
expect(awsDeploy.serverless.cli.log).to.have.been.called;
expect(validateTemplateStub).to.have.been.calledOnce;
expect(validateTemplateStub).to.have.been.calledWithExactly(
'CloudFormation',
'validateTemplate',
{
TemplateURL: 'https://s3.amazonaws.com/deployment-bucket/somedir/compiled-cloudformation-template.json',
},
awsDeploy.options.stage,
awsDeploy.options.region
);
expect(error.message).to.match(/is invalid: Some error while validating/);
});
});
});
});