Skip to content

Commit

Permalink
Add better logging
Browse files Browse the repository at this point in the history
  • Loading branch information
simonihmig committed May 23, 2018
1 parent 67822c8 commit b2e1440
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 21 deletions.
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//const RSVP = require('rsvp');
const DeployPluginBase = require('ember-cli-deploy-plugin');
const CfnClient = require('./lib/cfn');
const Logger = require('./lib/logger');

module.exports = {
name: 'ember-cli-deploy-cloudformation',
Expand Down Expand Up @@ -35,6 +36,7 @@ module.exports = {
.reduce((result, item) => Object.assign(result, item), {});

this.cfnClient = this.readConfig('cfnClient') || new CfnClient(options);
this.cfnClient.logger = new Logger(this.log.bind(this));

return this.cfnClient.validateTemplate()
.catch(this._errorMessage.bind(this));
Expand Down
34 changes: 28 additions & 6 deletions lib/cfn.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ class CfnClient {
constructor(options) {

let awsOptions = {
apiVersion: '2010-05-15',
region: options.region,
accessKeyId: options.accessKeyId,
secretAccessKey: options.secretAccessKey
};

if (options.profile) {
awsOptions.credentials = new AWS.SharedIniFileCredentials({ profile });
awsOptions.credentials = new AWS.SharedIniFileCredentials({ profile: options.profile });
}

let cfnOptions = Object.assign({}, options);
Expand Down Expand Up @@ -68,21 +69,27 @@ class CfnClient {
}

createStack() {
this.log(`Creating new CloudFormation stack '${this.options.stackName}'...`, 'debug');
return this.awsClient
.createStack(this.awsOptions)
.promise()
.then(() => this.awsClient.waitFor('stackCreateComplete', { StackName: this.options.stackName }).promise());
.then(() => this.awsClient.waitFor('stackCreateComplete', { StackName: this.options.stackName }).promise())
.then(() => this.log(`New CloudFormation stack '${this.options.stackName}' has been created!`));
}

updateStack() {
this.log(`Updating CloudFormation stack '${this.options.stackName}'...`, 'debug');
return this.awsClient
.updateStack(this.awsOptions)
.promise()
.then(() => this.awsClient.waitFor('stackUpdateComplete', { StackName: this.options.stackName }).promise())
.then(() => this.log(`CloudFormation stack '${this.options.stackName}' has been updated!`))
.catch(err => {
if (!String(err).includes('No updates are to be performed')) {
throw err;
if (String(err).includes('No updates are to be performed')) {
this.log(`No updates are to be performed to CloudFormation stack '${this.options.stackName}'`, 'debug');
return;
}
throw err;
});
}

Expand All @@ -100,9 +107,15 @@ class CfnClient {
return this.awsClient
.describeStacks({ StackName: this.options.stackName })
.promise()
.then((data) => {
.then((result) => {
if (!result.Stacks || !result.Stacks[0]) {
throw new Error('No stack data found from `describeStacks` call');
}

let data = result.Stacks[0];

if (!data.Outputs) {
throw new Error('No Outputs found in `describeStacks` data');
return {};
}

return data.Outputs
Expand Down Expand Up @@ -141,6 +154,15 @@ class CfnClient {
.reduce((result, item) => Object.assign(result, item), {});
}

log(message, type = 'log') {
if (this.logger) {
if (typeof this.logger[type] !== 'function') {
throw new Error(`Logger does not implement ${type} type`);
}
this.logger[type](message);
}
}

}

module.exports = CfnClient;
19 changes: 19 additions & 0 deletions lib/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class MappingLogger {
constructor(logFn) {
this._log = logFn;
}

log(msg) {
this._log(msg);
}

error(msg) {
this._log(msg, { color: 'red' });
}

debug(msg) {
this._log(msg, { verbose: true });
}
}

module.exports = MappingLogger;
66 changes: 51 additions & 15 deletions tests/unit/cfn-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,25 @@ const expectedOptions = {
};

const describeData = {
StackName: 'myStack',
StackStatus: 'CREATE_COMPLETE',
Outputs: [
{
OutputKey: 'AssetsBucket',
OutputValue: 'abc-123456789'
},
{
OutputKey: 'CloudFrontDistribution',
OutputValue: 'EFG123456789'
}
]
Stacks: [{
StackName: 'myStack',
StackStatus: 'CREATE_COMPLETE',
Outputs: [
{
OutputKey: 'AssetsBucket',
OutputValue: 'abc-123456789'
},
{
OutputKey: 'CloudFrontDistribution',
OutputValue: 'EFG123456789'
}
]
}]
};

describe('Cloudformation client', function() {
let client;
let logger;

beforeEach(function() {
client = new CfnClient(options);
Expand All @@ -115,6 +118,15 @@ describe('Cloudformation client', function() {
sinon.stub(client.awsClient, 'validateTemplate').returns({
promise: sinon.fake.resolves()
});

// minimal logger interface
logger = {
log: sinon.fake(),
error: sinon.fake(),
debug: sinon.fake()
};

client.logger = logger;
});

afterEach(function() {
Expand All @@ -134,6 +146,7 @@ describe('Cloudformation client', function() {

expect(constructor).to.always.have.been.calledWithNew;
expect(constructor).to.have.been.calledWith({
apiVersion: '2010-05-15',
accessKeyId: 'abc',
secretAccessKey: 'def',
region: 'us-east-1'
Expand All @@ -148,7 +161,12 @@ describe('Cloudformation client', function() {

it('it waits for stackCreateComplete', function() {
return expect(callFn()).to.be.fulfilled
.then(() => expect(client.awsClient.waitFor).to.have.been.calledWith('stackCreateComplete', { StackName: 'myStack' }));
.then(() => {
expect(client.awsClient.waitFor).to.have.been.calledWith('stackCreateComplete', { StackName: 'myStack' });
expect(logger.debug).to.have.been.calledWith(`Creating new CloudFormation stack 'myStack'...`);
expect(logger.log).to.have.been.calledWith(`New CloudFormation stack 'myStack' has been created!`);
expect(logger.debug).to.have.been.calledBefore(logger.log);
});
});

it('rejects when createStack fails', function() {
Expand All @@ -168,7 +186,12 @@ describe('Cloudformation client', function() {

it('it waits for stackUpdateComplete', function() {
return expect(callFn()).to.be.fulfilled
.then(() => expect(client.awsClient.waitFor).to.have.been.calledWith('stackUpdateComplete', { StackName: 'myStack' }));
.then(() => {
expect(client.awsClient.waitFor).to.have.been.calledWith('stackUpdateComplete', { StackName: 'myStack' });
expect(logger.debug).to.have.been.calledWith(`Updating CloudFormation stack 'myStack'...`);
expect(logger.log).to.have.been.calledWith(`CloudFormation stack 'myStack' has been updated!`);
expect(logger.debug).to.have.been.calledBefore(logger.log);
});
});

it('rejects when updateStack fails', function() {
Expand All @@ -185,7 +208,10 @@ describe('Cloudformation client', function() {
});

return expect(callFn()).to.be.fulfilled
.then(() => expect(client.awsClient.waitFor).to.not.have.been.called);
.then(() => {
expect(client.awsClient.waitFor).to.not.have.been.called;
expect(logger.debug).to.have.been.calledWith(`No updates are to be performed to CloudFormation stack 'myStack'`);
});
});
}

Expand Down Expand Up @@ -258,6 +284,16 @@ describe('Cloudformation client', function() {
CloudFrontDistribution: 'EFG123456789'
});
});

it('returns empty hash when no outputs are found', function() {
let emptyDescribeData = JSON.parse(JSON.stringify(describeData));
delete emptyDescribeData.Stacks[0].Outputs;
client.awsClient.describeStacks.returns({
promise: sinon.fake.resolves(emptyDescribeData)
});

return expect(client.fetchOutputs()).to.eventually.deep.equal({});
});
});

});

0 comments on commit b2e1440

Please sign in to comment.