Skip to content

Commit f56cbc2

Browse files
committed
feat: protected addWebhook method
1 parent 909a2d8 commit f56cbc2

File tree

2 files changed

+255
-1
lines changed

2 files changed

+255
-1
lines changed

index.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* eslint no-underscore-dangle: ["error", { "allowAfterThis": true }] */
2+
13
'use strict';
24

35
const Breaker = require('circuit-fuses');
@@ -17,6 +19,7 @@ const MATCH_COMPONENT_BRANCH_NAME = 4;
1719
const MATCH_COMPONENT_REPO_NAME = 3;
1820
const MATCH_COMPONENT_USER_NAME = 2;
1921
const MATCH_COMPONENT_HOST_NAME = 1;
22+
const WEBHOOK_PAGE_SIZE = 30;
2023
const STATE_MAP = {
2124
SUCCESS: 'success',
2225
RUNNING: 'pending',
@@ -148,6 +151,108 @@ class GithubScm extends Scm {
148151
});
149152
}
150153

154+
/**
155+
* Look up a webhook from a repo
156+
* @method _findWebhook
157+
* @param {Object} config
158+
* @param {Object} config.scmInfo Data about repo
159+
* @param {String} config.token Admin token for repo
160+
* @param {Number} config.page pagination: page number to search next
161+
* @param {String} config.url url for webhook notifications
162+
* @return {Promise} Resolves a list of hooks
163+
*/
164+
_findWebhook(config) {
165+
return this.breaker.runCommand({
166+
action: 'getHooks',
167+
token: config.token,
168+
params: {
169+
owner: config.scmInfo.owner,
170+
repo: config.scmInfo.repo,
171+
page: config.page,
172+
per_page: WEBHOOK_PAGE_SIZE
173+
}
174+
}).then((hooks) => {
175+
const screwdriverHook = hooks.find(hook =>
176+
hoek.reach(hook, 'config.url') === config.url
177+
);
178+
179+
if (!screwdriverHook && hooks.length === WEBHOOK_PAGE_SIZE) {
180+
config.page += 1;
181+
182+
return this._findWebhook(config);
183+
}
184+
185+
return screwdriverHook;
186+
});
187+
}
188+
189+
/**
190+
* Create or edit a webhook (edits if hookInfo exists)
191+
* @method _createWebhook
192+
* @param {Object} config
193+
* @param {Object} [config.hookInfo] Information about a existing webhook
194+
* @param {Object} config.scmInfo Information about the repo
195+
* @param {String} config.token admin token for repo
196+
* @param {String} config.url url for webhook notifications
197+
* @return {Promise} resolves when complete
198+
*/
199+
_createWebhook(config) {
200+
let action = 'createHook';
201+
const params = {
202+
active: true,
203+
events: ['push', 'pull_request'],
204+
owner: config.scmInfo.owner,
205+
repo: config.scmInfo.repo,
206+
name: 'web',
207+
config: {
208+
content_type: 'json',
209+
secret: this.config.secret,
210+
url: config.url
211+
}
212+
};
213+
214+
if (config.hookInfo) {
215+
action = 'editHook';
216+
Object.assign(params, { id: config.hookInfo.id });
217+
}
218+
219+
return this.breaker.runCommand({
220+
action,
221+
token: config.token,
222+
params
223+
});
224+
}
225+
226+
/**
227+
* Adds the Screwdriver webhook to the Github repository
228+
* @method _addWebhook
229+
* @param {Object} config Config object
230+
* @param {String} config.scmUri The SCM URI to add the webhook to
231+
* @param {String} config.token Service token to authenticate with Github
232+
* @param {String} config.webhookUrl The URL to use for the webhook notifications
233+
* @return {Promise} Resolve means operation completed without failure.
234+
*/
235+
_addWebhook(config) {
236+
return this.lookupScmUri({
237+
scmUri: config.scmUri,
238+
token: config.token
239+
}).then(scmInfo =>
240+
this._findWebhook({
241+
scmInfo,
242+
url: config.webhookUrl,
243+
page: 1,
244+
token: config.token
245+
}).then(hookInfo =>
246+
this._createWebhook({
247+
hookInfo,
248+
scmInfo,
249+
token: config.token,
250+
url: config.webhookUrl
251+
})
252+
)
253+
);
254+
}
255+
151256
/**
152257
* Checkout the source code from a repository; resolves as an object with checkout commands
153258
* @method getCheckoutCommand

test/index.test.js

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ describe('index', () => {
3434
githubMock = {
3535
authenticate: sinon.stub(),
3636
repos: {
37+
createHook: sinon.stub(),
3738
createStatus: sinon.stub(),
39+
editHook: sinon.stub(),
3840
get: sinon.stub(),
3941
getBranch: sinon.stub(),
4042
getById: sinon.stub(),
4143
getCommit: sinon.stub(),
42-
getContent: sinon.stub()
44+
getContent: sinon.stub(),
45+
getHooks: sinon.stub()
4346
},
4447
users: {
4548
getForUser: sinon.stub()
@@ -1179,4 +1182,150 @@ jobs:
11791182
});
11801183
});
11811184
});
1185+
1186+
describe('_addWebhook', () => {
1187+
const webhookConfig = {
1188+
scmUri: 'github.com:1263:branchName',
1189+
token: 'fakeToken',
1190+
webhookUrl: 'https://somewhere.in/the/interwebs'
1191+
};
1192+
1193+
beforeEach(() => {
1194+
githubMock.repos.getById.yieldsAsync(null, {
1195+
full_name: 'dolores/violentdelights'
1196+
});
1197+
githubMock.repos.getHooks.yieldsAsync(null, [{
1198+
config: { url: 'https://somewhere.in/the/interwebs' },
1199+
id: 783150
1200+
}]);
1201+
});
1202+
1203+
it('add a hook', () => {
1204+
githubMock.repos.getHooks.yieldsAsync(null, []);
1205+
githubMock.repos.createHook.yieldsAsync(null);
1206+
1207+
/* eslint-disable no-underscore-dangle */
1208+
return scm._addWebhook(webhookConfig).then(() => {
1209+
/* eslint-enable no-underscore-dangle */
1210+
assert.calledWith(githubMock.authenticate, sinon.match({
1211+
token: 'fakeToken'
1212+
}));
1213+
assert.calledWith(githubMock.repos.getById, {
1214+
id: '1263'
1215+
});
1216+
assert.calledWith(githubMock.repos.createHook, {
1217+
active: true,
1218+
config: {
1219+
content_type: 'json',
1220+
secret: 'somesecret',
1221+
url: 'https://somewhere.in/the/interwebs'
1222+
},
1223+
events: ['push', 'pull_request'],
1224+
name: 'web',
1225+
owner: 'dolores',
1226+
repo: 'violentdelights'
1227+
});
1228+
});
1229+
});
1230+
1231+
it('updates a pre-existing hook', () => {
1232+
githubMock.repos.editHook.yieldsAsync(null);
1233+
1234+
/* eslint-disable no-underscore-dangle */
1235+
return scm._addWebhook(webhookConfig).then(() => {
1236+
/* eslint-enable no-underscore-dangle */
1237+
assert.calledWith(githubMock.repos.getHooks, {
1238+
owner: 'dolores',
1239+
repo: 'violentdelights',
1240+
page: 1,
1241+
per_page: 30
1242+
});
1243+
assert.calledWith(githubMock.repos.editHook, {
1244+
active: true,
1245+
config: {
1246+
content_type: 'json',
1247+
secret: 'somesecret',
1248+
url: 'https://somewhere.in/the/interwebs'
1249+
},
1250+
events: ['push', 'pull_request'],
1251+
id: 783150,
1252+
name: 'web',
1253+
owner: 'dolores',
1254+
repo: 'violentdelights'
1255+
});
1256+
});
1257+
});
1258+
1259+
it('updates hook on a repo with a lot of other hooks', () => {
1260+
const invalidHooks = [];
1261+
1262+
for (let i = 0; i < 30; i += 1) {
1263+
invalidHooks.push({});
1264+
}
1265+
1266+
githubMock.repos.getHooks.onCall(0).yieldsAsync(null, invalidHooks);
1267+
githubMock.repos.editHook.yieldsAsync(null);
1268+
1269+
/* eslint-disable no-underscore-dangle */
1270+
return scm._addWebhook(webhookConfig).then(() => {
1271+
/* eslint-enable no-underscore-dangle */
1272+
assert.calledWith(githubMock.repos.getHooks, {
1273+
owner: 'dolores',
1274+
repo: 'violentdelights',
1275+
page: 2,
1276+
per_page: 30
1277+
});
1278+
assert.calledWith(githubMock.repos.editHook, {
1279+
active: true,
1280+
config: {
1281+
content_type: 'json',
1282+
secret: 'somesecret',
1283+
url: 'https://somewhere.in/the/interwebs'
1284+
},
1285+
events: ['push', 'pull_request'],
1286+
id: 783150,
1287+
name: 'web',
1288+
owner: 'dolores',
1289+
repo: 'violentdelights'
1290+
});
1291+
});
1292+
});
1293+
1294+
it('throws an error when failing to getHooks', () => {
1295+
const testError = new Error('getHooksError');
1296+
1297+
githubMock.repos.getHooks.yieldsAsync(testError);
1298+
1299+
/* eslint-disable no-underscore-dangle */
1300+
return scm._addWebhook(webhookConfig).then(assert.fail, (err) => {
1301+
/* eslint-enable no-underscore-dangle */
1302+
assert.equal(err, testError);
1303+
});
1304+
});
1305+
1306+
it('throws an error when failing to createHook', () => {
1307+
const testError = new Error('createHookError');
1308+
1309+
githubMock.repos.getHooks.yieldsAsync(null, []);
1310+
githubMock.repos.createHook.yieldsAsync(testError);
1311+
1312+
/* eslint-disable no-underscore-dangle */
1313+
return scm._addWebhook(webhookConfig).then(assert.fail, (err) => {
1314+
/* eslint-enable no-underscore-dangle */
1315+
assert.equal(err, testError);
1316+
});
1317+
});
1318+
1319+
it('throws an error when failing to editHook', () => {
1320+
const testError = new Error('editHookError');
1321+
1322+
githubMock.repos.editHook.yieldsAsync(testError);
1323+
1324+
/* eslint-disable no-underscore-dangle */
1325+
return scm._addWebhook(webhookConfig).then(assert.fail, (err) => {
1326+
/* eslint-enable no-underscore-dangle */
1327+
assert.equal(err, testError);
1328+
});
1329+
});
1330+
});
11821331
});

0 commit comments

Comments
 (0)