Skip to content

Commit 2661d06

Browse files
committedMar 3, 2017
feat(465): support notification plugins
1 parent 4728386 commit 2661d06

10 files changed

+173
-24
lines changed
 

‎bin/server

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ const ecosystem = config.get('ecosystem');
2323

2424
ecosystem.api = httpdConfig.uri;
2525

26+
// Notification config
27+
const notificationConfig = config.get('notifications');
28+
2629
// Setup Datastore
2730
const datastoreConfig = config.get('datastore');
2831
const DatastorePlugin = require(`screwdriver-datastore-${datastoreConfig.plugin}`);
@@ -92,6 +95,7 @@ datastore.setup()
9295
httpd: httpdConfig,
9396
auth: authConfig,
9497
webhooks: webhooksConfig,
98+
notifications: notificationConfig,
9599
ecosystem,
96100
pipelineFactory,
97101
jobFactory,

‎config/custom-environment-variables.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ bookends:
129129
__name: BOOKENDS_TEARDOWN
130130
__format: json
131131

132+
notifications:
133+
__name: NOTIFICATIONS
134+
__format: json
135+
132136
ecosystem:
133137
# URL for the User Interface
134138
ui: ECOSYSTEM_UI

‎config/default.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ bookends:
143143
teardown:
144144
- screwdriver-artifact-bookend
145145

146+
notifications:
147+
# Email notification when build finishes
148+
# email:
149+
# host: email-host
150+
# port: email-port
151+
# from: email-address-to-send-from
146152
ecosystem:
147153
# Externally routable URL for the User Interface
148154
ui: https://cd.screwdriver.cd

‎lib/registerPlugins.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,16 @@ module.exports = (server, config) => (
7171

7272
return resolve();
7373
});
74+
}).then(() => {
75+
// Register notification plugins
76+
const notificationConfig = config.notifications || {};
77+
78+
return Object.keys(notificationConfig).forEach((plugin) => {
79+
const Plugin = require(`screwdriver-notifications-${plugin}`);
80+
const notificationPlugin = new Plugin(
81+
notificationConfig[plugin], server, 'build_status');
82+
83+
notificationPlugin.notify();
84+
});
7485
})
75-
);
86+
);

‎lib/server.js

+3
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ module.exports = (config) => {
9393
// Write prettier errors
9494
server.ext('onPreResponse', prettyPrintErrors);
9595

96+
// Register build_status event for notifications plugin
97+
server.event('build_status');
98+
9699
// Register plugins
97100
return registrationMan(server, config)
98101
.then(() => {

‎package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,13 @@
7676
"requestretry": "^1.12.0",
7777
"screwdriver-artifact-bookend": "^1.0.1",
7878
"screwdriver-build-bookend": "^2.0.1",
79-
"screwdriver-config-parser": "^3.1.0",
80-
"screwdriver-data-schema": "^16.0.1",
79+
"screwdriver-config-parser": "^3.3.0",
80+
"screwdriver-data-schema": "^16.1.1",
8181
"screwdriver-datastore-sequelize": "^2.0.0",
8282
"screwdriver-executor-docker": "^2.0.0",
8383
"screwdriver-executor-k8s": "^10.0.0",
8484
"screwdriver-models": "^21.2.0",
85+
"screwdriver-notifications-email": "^1.0.3",
8586
"screwdriver-scm-bitbucket": "^2.6.0",
8687
"screwdriver-scm-github": "^4.6.0",
8788
"tinytim": "^0.1.1",

‎plugins/builds/update.js

+17-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ module.exports = () => ({
1717
scope: ['user', 'build']
1818
},
1919
handler: (request, reply) => {
20-
const factory = request.server.app.buildFactory;
20+
const buildFactory = request.server.app.buildFactory;
2121
const id = request.params.id;
2222
const desiredStatus = request.payload.status;
2323
const jobFactory = request.server.app.jobFactory;
@@ -29,7 +29,7 @@ module.exports = () => ({
2929
throw boom.forbidden(`Credential only valid for ${username}`);
3030
}
3131

32-
return factory.get(id)
32+
return buildFactory.get(id)
3333
.then((build) => {
3434
if (!build) {
3535
throw boom.notFound(`Build ${id} does not exist`);
@@ -67,13 +67,22 @@ module.exports = () => ({
6767
// Everyone is able to update the status
6868
build.status = desiredStatus;
6969

70-
// Guard against triggering non-successful builds
71-
if (desiredStatus !== 'SUCCESS') {
72-
return build.update();
73-
}
74-
7570
// Only trigger next build on success
7671
return build.job.then(job => job.pipeline.then((pipeline) => {
72+
request.server.emit('build_status', {
73+
settings: job.permutations[0].settings,
74+
status: build.status,
75+
pipelineName: pipeline.scmRepo.name,
76+
jobName: job.name,
77+
buildId: build.id,
78+
buildLink: `${buildFactory.uiUri}/builds/${id}`
79+
});
80+
81+
// Guard against triggering non-successful builds
82+
if (desiredStatus !== 'SUCCESS') {
83+
return null;
84+
}
85+
7786
const workflow = pipeline.workflow;
7887

7988
// No workflow to follow
@@ -100,7 +109,7 @@ module.exports = () => ({
100109
pipelineId: pipeline.id
101110
}).then((nextJobToTrigger) => {
102111
if (nextJobToTrigger.state === 'ENABLED') {
103-
return factory.create({
112+
return buildFactory.create({
104113
jobId: nextJobToTrigger.id,
105114
sha: build.sha,
106115
parentBuildId: id,

‎test/lib/registerPlugins.test.js

+32
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,38 @@ describe('Register Unit Test Case', () => {
101101
});
102102
});
103103

104+
it('registered notifications plugins', () => {
105+
serverMock.register.callsArgAsync(2);
106+
107+
const newConfig = {
108+
notifications: {
109+
email: {
110+
foo: 'abc'
111+
},
112+
slack: {
113+
baz: 'def'
114+
}
115+
}
116+
};
117+
118+
const notificationPlugins = [
119+
'screwdriver-notifications-email',
120+
'screwdriver-notifications-slack'
121+
];
122+
123+
notificationPlugins.forEach((plugin) => {
124+
mocks[plugin] = sinon.stub();
125+
mocks[plugin].prototype.notify = sinon.stub();
126+
mockery.registerMock(plugin, mocks[plugin]);
127+
});
128+
129+
return main(serverMock, newConfig).then(() =>
130+
notificationPlugins.forEach(plugin =>
131+
Assert.called(mocks[plugin].prototype.notify)
132+
)
133+
);
134+
});
135+
104136
it('bubbles failures up', () => {
105137
serverMock.register.callsArgWithAsync(2, new Error('failure loading'));
106138

‎test/plugins/builds.test.js

+86-10
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ describe('build plugin test', () => {
110110
}));
111111
server.auth.strategy('token', 'custom');
112112
server.auth.strategy('session', 'custom');
113+
server.event('build_status');
113114

114115
secretMock = {
115116
register: (s, o, next) => {
@@ -181,7 +182,15 @@ describe('build plugin test', () => {
181182

182183
describe('PUT /builds/{id}', () => {
183184
const id = 12345;
185+
const pipelineId = 123;
186+
const scmUri = 'github.com:12345:branchName';
187+
const scmRepo = {
188+
branch: 'master',
189+
name: 'screwdriver-cd/screwdriver',
190+
url: 'https://github.com/screwdriver-cd/screwdriver/tree/branchName'
191+
};
184192
let buildMock;
193+
let pipelineMock;
185194

186195
beforeEach(() => {
187196
testBuild.status = 'QUEUED';
@@ -193,6 +202,62 @@ describe('build plugin test', () => {
193202

194203
buildFactoryMock.get.resolves(buildMock);
195204
buildMock.update.resolves(buildMock);
205+
206+
pipelineMock = {
207+
id: pipelineId,
208+
scmUri,
209+
scmRepo,
210+
sync: sinon.stub().resolves()
211+
};
212+
});
213+
214+
it('emits event buid_status', () => {
215+
const jobMock = {
216+
id: 1234,
217+
name: 'main',
218+
pipelineId,
219+
permutations: [{
220+
settings: {
221+
email: 'foo@bar.com'
222+
}
223+
}]
224+
};
225+
226+
jobMock.pipeline = sinon.stub().resolves(pipelineMock)();
227+
buildMock.job = sinon.stub().resolves(jobMock)();
228+
buildMock.settings = {
229+
email: 'foo@bar.com'
230+
};
231+
232+
buildFactoryMock.get.resolves(buildMock);
233+
buildFactoryMock.uiUri = 'http://foo.bar';
234+
235+
const options = {
236+
method: 'PUT',
237+
url: `/builds/${id}`,
238+
payload: {
239+
status: 'ABORTED'
240+
},
241+
credentials: {
242+
scope: ['user']
243+
}
244+
};
245+
246+
server.emit = sinon.stub().resolves(null);
247+
248+
return server.inject(options).then((reply) => {
249+
assert.calledWith(server.emit, 'build_status', {
250+
buildId: 12345,
251+
buildLink: 'http://foo.bar/builds/12345',
252+
jobName: 'main',
253+
pipelineName: 'screwdriver-cd/screwdriver',
254+
settings: {
255+
email: 'foo@bar.com'
256+
},
257+
status: 'ABORTED'
258+
});
259+
assert.equal(reply.statusCode, 200);
260+
});
196261
});
197262

198263
it('returns 404 for updating a build that does not exist', () => {
@@ -235,6 +300,22 @@ describe('build plugin test', () => {
235300

236301
describe('user token', () => {
237302
it('returns 200 for updating a build that exists', () => {
303+
const jobMock = {
304+
id: 1234,
305+
name: 'main',
306+
pipelineId,
307+
permutations: [{
308+
settings: {
309+
email: 'foo@bar.com'
310+
}
311+
}]
312+
};
313+
314+
jobMock.pipeline = sinon.stub().resolves(pipelineMock)();
315+
buildMock.job = sinon.stub().resolves(jobMock)();
316+
317+
buildFactoryMock.get.resolves(buildMock);
318+
238319
const expected = hoek.applyToDefaults(testBuild, { status: 'ABORTED' });
239320
const options = {
240321
method: 'PUT',
@@ -250,9 +331,9 @@ describe('build plugin test', () => {
250331
buildMock.toJson.returns(expected);
251332

252333
return server.inject(options).then((reply) => {
253-
assert.equal(reply.statusCode, 200);
254334
assert.deepEqual(reply.result, expected);
255335
assert.calledWith(buildFactoryMock.get, id);
336+
assert.equal(reply.statusCode, 200);
256337
});
257338
});
258339

@@ -296,23 +377,18 @@ describe('build plugin test', () => {
296377

297378
describe('build token', () => {
298379
const jobId = 1234;
299-
const pipelineId = 123;
300380
const publishJobId = 1235;
301-
const scmUri = 'github.com:12345:branchName';
302381

303382
let jobMock;
304-
let pipelineMock;
305383

306384
beforeEach(() => {
307385
jobMock = {
308386
id: jobId,
309387
name: 'main',
310-
pipelineId
311-
};
312-
pipelineMock = {
313-
id: pipelineId,
314-
scmUri,
315-
sync: sinon.stub().resolves()
388+
pipelineId,
389+
permutations: [{
390+
settings: {}
391+
}]
316392
};
317393

318394
jobMock.pipeline = sinon.stub().resolves(pipelineMock)();

‎test/plugins/data/validator.output.json

+6-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"environment": {
2222
"NODE_VERSION": "4"
2323
},
24-
"secrets": []
24+
"secrets": [],
25+
"settings": {}
2526
},
2627
{
2728
"image": "node:6",
@@ -43,7 +44,8 @@
4344
"environment": {
4445
"NODE_VERSION": "6"
4546
},
46-
"secrets": []
47+
"secrets": [],
48+
"settings": {}
4749
}
4850
],
4951
"publish": [
@@ -56,7 +58,8 @@
5658
}
5759
],
5860
"environment": {},
59-
"secrets": []
61+
"secrets": [],
62+
"settings": {}
6063
}
6164
]
6265
},

0 commit comments

Comments
 (0)
Please sign in to comment.