Skip to content

Commit b2e1440

Browse files
committed
Add better logging
1 parent 67822c8 commit b2e1440

File tree

4 files changed

+100
-21
lines changed

4 files changed

+100
-21
lines changed

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//const RSVP = require('rsvp');
55
const DeployPluginBase = require('ember-cli-deploy-plugin');
66
const CfnClient = require('./lib/cfn');
7+
const Logger = require('./lib/logger');
78

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

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

3941
return this.cfnClient.validateTemplate()
4042
.catch(this._errorMessage.bind(this));

lib/cfn.js

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ class CfnClient {
2323
constructor(options) {
2424

2525
let awsOptions = {
26+
apiVersion: '2010-05-15',
2627
region: options.region,
2728
accessKeyId: options.accessKeyId,
2829
secretAccessKey: options.secretAccessKey
2930
};
3031

3132
if (options.profile) {
32-
awsOptions.credentials = new AWS.SharedIniFileCredentials({ profile });
33+
awsOptions.credentials = new AWS.SharedIniFileCredentials({ profile: options.profile });
3334
}
3435

3536
let cfnOptions = Object.assign({}, options);
@@ -68,21 +69,27 @@ class CfnClient {
6869
}
6970

7071
createStack() {
72+
this.log(`Creating new CloudFormation stack '${this.options.stackName}'...`, 'debug');
7173
return this.awsClient
7274
.createStack(this.awsOptions)
7375
.promise()
74-
.then(() => this.awsClient.waitFor('stackCreateComplete', { StackName: this.options.stackName }).promise());
76+
.then(() => this.awsClient.waitFor('stackCreateComplete', { StackName: this.options.stackName }).promise())
77+
.then(() => this.log(`New CloudFormation stack '${this.options.stackName}' has been created!`));
7578
}
7679

7780
updateStack() {
81+
this.log(`Updating CloudFormation stack '${this.options.stackName}'...`, 'debug');
7882
return this.awsClient
7983
.updateStack(this.awsOptions)
8084
.promise()
8185
.then(() => this.awsClient.waitFor('stackUpdateComplete', { StackName: this.options.stackName }).promise())
86+
.then(() => this.log(`CloudFormation stack '${this.options.stackName}' has been updated!`))
8287
.catch(err => {
83-
if (!String(err).includes('No updates are to be performed')) {
84-
throw err;
88+
if (String(err).includes('No updates are to be performed')) {
89+
this.log(`No updates are to be performed to CloudFormation stack '${this.options.stackName}'`, 'debug');
90+
return;
8591
}
92+
throw err;
8693
});
8794
}
8895

@@ -100,9 +107,15 @@ class CfnClient {
100107
return this.awsClient
101108
.describeStacks({ StackName: this.options.stackName })
102109
.promise()
103-
.then((data) => {
110+
.then((result) => {
111+
if (!result.Stacks || !result.Stacks[0]) {
112+
throw new Error('No stack data found from `describeStacks` call');
113+
}
114+
115+
let data = result.Stacks[0];
116+
104117
if (!data.Outputs) {
105-
throw new Error('No Outputs found in `describeStacks` data');
118+
return {};
106119
}
107120

108121
return data.Outputs
@@ -141,6 +154,15 @@ class CfnClient {
141154
.reduce((result, item) => Object.assign(result, item), {});
142155
}
143156

157+
log(message, type = 'log') {
158+
if (this.logger) {
159+
if (typeof this.logger[type] !== 'function') {
160+
throw new Error(`Logger does not implement ${type} type`);
161+
}
162+
this.logger[type](message);
163+
}
164+
}
165+
144166
}
145167

146168
module.exports = CfnClient;

lib/logger.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class MappingLogger {
2+
constructor(logFn) {
3+
this._log = logFn;
4+
}
5+
6+
log(msg) {
7+
this._log(msg);
8+
}
9+
10+
error(msg) {
11+
this._log(msg, { color: 'red' });
12+
}
13+
14+
debug(msg) {
15+
this._log(msg, { verbose: true });
16+
}
17+
}
18+
19+
module.exports = MappingLogger;

tests/unit/cfn-test.js

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,25 @@ const expectedOptions = {
8181
};
8282

8383
const describeData = {
84-
StackName: 'myStack',
85-
StackStatus: 'CREATE_COMPLETE',
86-
Outputs: [
87-
{
88-
OutputKey: 'AssetsBucket',
89-
OutputValue: 'abc-123456789'
90-
},
91-
{
92-
OutputKey: 'CloudFrontDistribution',
93-
OutputValue: 'EFG123456789'
94-
}
95-
]
84+
Stacks: [{
85+
StackName: 'myStack',
86+
StackStatus: 'CREATE_COMPLETE',
87+
Outputs: [
88+
{
89+
OutputKey: 'AssetsBucket',
90+
OutputValue: 'abc-123456789'
91+
},
92+
{
93+
OutputKey: 'CloudFrontDistribution',
94+
OutputValue: 'EFG123456789'
95+
}
96+
]
97+
}]
9698
};
9799

98100
describe('Cloudformation client', function() {
99101
let client;
102+
let logger;
100103

101104
beforeEach(function() {
102105
client = new CfnClient(options);
@@ -115,6 +118,15 @@ describe('Cloudformation client', function() {
115118
sinon.stub(client.awsClient, 'validateTemplate').returns({
116119
promise: sinon.fake.resolves()
117120
});
121+
122+
// minimal logger interface
123+
logger = {
124+
log: sinon.fake(),
125+
error: sinon.fake(),
126+
debug: sinon.fake()
127+
};
128+
129+
client.logger = logger;
118130
});
119131

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

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

149162
it('it waits for stackCreateComplete', function() {
150163
return expect(callFn()).to.be.fulfilled
151-
.then(() => expect(client.awsClient.waitFor).to.have.been.calledWith('stackCreateComplete', { StackName: 'myStack' }));
164+
.then(() => {
165+
expect(client.awsClient.waitFor).to.have.been.calledWith('stackCreateComplete', { StackName: 'myStack' });
166+
expect(logger.debug).to.have.been.calledWith(`Creating new CloudFormation stack 'myStack'...`);
167+
expect(logger.log).to.have.been.calledWith(`New CloudFormation stack 'myStack' has been created!`);
168+
expect(logger.debug).to.have.been.calledBefore(logger.log);
169+
});
152170
});
153171

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

169187
it('it waits for stackUpdateComplete', function() {
170188
return expect(callFn()).to.be.fulfilled
171-
.then(() => expect(client.awsClient.waitFor).to.have.been.calledWith('stackUpdateComplete', { StackName: 'myStack' }));
189+
.then(() => {
190+
expect(client.awsClient.waitFor).to.have.been.calledWith('stackUpdateComplete', { StackName: 'myStack' });
191+
expect(logger.debug).to.have.been.calledWith(`Updating CloudFormation stack 'myStack'...`);
192+
expect(logger.log).to.have.been.calledWith(`CloudFormation stack 'myStack' has been updated!`);
193+
expect(logger.debug).to.have.been.calledBefore(logger.log);
194+
});
172195
});
173196

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

187210
return expect(callFn()).to.be.fulfilled
188-
.then(() => expect(client.awsClient.waitFor).to.not.have.been.called);
211+
.then(() => {
212+
expect(client.awsClient.waitFor).to.not.have.been.called;
213+
expect(logger.debug).to.have.been.calledWith(`No updates are to be performed to CloudFormation stack 'myStack'`);
214+
});
189215
});
190216
}
191217

@@ -258,6 +284,16 @@ describe('Cloudformation client', function() {
258284
CloudFrontDistribution: 'EFG123456789'
259285
});
260286
});
287+
288+
it('returns empty hash when no outputs are found', function() {
289+
let emptyDescribeData = JSON.parse(JSON.stringify(describeData));
290+
delete emptyDescribeData.Stacks[0].Outputs;
291+
client.awsClient.describeStacks.returns({
292+
promise: sinon.fake.resolves(emptyDescribeData)
293+
});
294+
295+
return expect(client.fetchOutputs()).to.eventually.deep.equal({});
296+
});
261297
});
262298

263299
});

0 commit comments

Comments
 (0)