Skip to content

Commit 046d453

Browse files
authored
Merge pull request #4 from gitgitgadget/migrate-gitgitgadget-to-github-actions
Trigger GitHub workflows instead of Azure Pipelines
2 parents b0287bc + 3f63a6a commit 046d453

File tree

6 files changed

+206
-107
lines changed

6 files changed

+206
-107
lines changed

.vscode/launch.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{
55
"type": "node",
66
"request": "launch",
7+
"console": "integratedTerminal",
78
"name": "Debug Tests",
89
"program": "${workspaceRoot}/node_modules/.bin/jest",
910
"cwd": "${workspaceRoot}",

GitGitGadget/index.js

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
*/
1111
const { validateGitHubWebHook } = require('./validate-github-webhook');
1212

13-
const { triggerAzurePipeline } = require('./trigger-azure-pipeline');
14-
15-
const { triggerWorkflowDispatch } = require('./trigger-workflow-dispatch')
13+
const { triggerWorkflowDispatch, listWorkflowRuns } = require('./trigger-workflow-dispatch')
1614

1715
module.exports = async (context, req) => {
1816
try {
@@ -29,56 +27,74 @@ module.exports = async (context, req) => {
2927

3028
try {
3129
/*
32-
* The Azure Pipeline needs to be installed as a PR build on _the very
33-
* same_ repository that triggers this function. That is, when the
34-
* Azure Function triggers GitGitGadget for gitgitgadget/git, it needs
35-
* to know that pipelineId 3 is installed on gitgitgadget/git, and
36-
* trigger that very pipeline.
37-
*
38-
* So whenever we extend GitGitGadget to handle another repository, we
39-
* will have to add an Azure Pipeline, install it on that repository as
40-
* a PR build, and add the information here.
30+
* For various reasons, the GitGitGadget GitHub App can be installed
31+
* on any random repository. However, GitGitGadget only wants to support
32+
* the `gitgitgadget/git` and the `git/git` repository (with the
33+
* `dscho/git` one thrown in for debugging purposes).
4134
*/
42-
const pipelines = {
43-
'dscho': 12,
44-
'git': 13,
45-
'gitgitgadget': 3,
46-
};
35+
const orgs = ['gitgitgadget', 'git', 'dscho']
36+
const a = [context, undefined, 'gitgitgadget-workflows', 'gitgitgadget-workflows']
4737

4838
const eventType = context.req.headers['x-github-event'];
4939
context.log(`Got eventType: ${eventType}`);
5040
const repositoryOwner = req.body.repository.owner.login;
51-
if (pipelines[repositoryOwner] === undefined) {
41+
if (!orgs.includes(repositoryOwner)) {
5242
context.res = {
5343
status: 403,
5444
body: 'Refusing to work on a repository other than gitgitgadget/git or git/git'
5545
};
46+
} else if (eventType === 'pull_request') {
47+
if (req.body.action !== 'opened' && req.body.action !== 'synchronize') {
48+
context.res = {
49+
body: `Ignoring pull request action: ${req.body.action}`,
50+
};
51+
} else {
52+
const run = await triggerWorkflowDispatch(...a, 'handle-pr-push.yml', 'main', {
53+
'pr-url': req.body.pull_request.html_url
54+
})
55+
context.res = { body: `Okay, triggered ${run.html_url}!` };
56+
}
5657
} else if ((new Set(['check_run', 'status']).has(eventType))) {
5758
context.res = {
5859
body: `Ignored event type: ${eventType}`,
5960
};
6061
} else if (eventType === 'push') {
61-
if (req.body.repository.full_name !== 'git/git') {
62+
if (req.body.repository.full_name ==='gitgitgadget/git-mailing-list-mirror') {
63+
context.res = { body: `push(${req.body.ref} in ${req.body.repository.full_name}): ` }
64+
if (req.body.ref === 'refs/heads/lore-1') {
65+
const queued = await listWorkflowRuns(...a, 'handle-new-mails.yml', 'queued')
66+
if (queued.length) {
67+
context.res.body += [
68+
`skip triggering handle-new-emails, ${queued} already queued:`,
69+
queued.map(e => `- ${e.html_url}`)
70+
].join('\n')
71+
} else {
72+
const run = await triggerWorkflowDispatch(...a, 'handle-new-mails.yml', 'main')
73+
context.res.body += `triggered ${run.html_url}`
74+
}
75+
} else context.res.body += `Ignoring non-default branches`
76+
} else if (req.body.repository.full_name !== 'git/git') {
6277
context.res = { body: `Ignoring pushes to ${req.body.repository.full_name}` }
6378
} else {
6479
const run = await triggerWorkflowDispatch(
65-
context,
66-
undefined,
67-
'gitgitgadget-workflows',
68-
'gitgitgadget-workflows',
80+
...a,
6981
'sync-ref.yml',
7082
'main', {
7183
ref: req.body.ref
7284
}
7385
)
74-
context.res = { body: `push(${req.body.ref}): triggered ${run.html_url}` }
86+
const extra = []
87+
if (req.body.ref === 'refs/heads/seen') {
88+
for (const workflow of ['update-prs.yml', 'update-mail-to-commit-notes.yml']) {
89+
if ((await listWorkflowRuns(...a, workflow, 'main', 'queued')).length === 0) {
90+
const run = await triggerWorkflowDispatch(...a, workflow, 'main')
91+
extra.push(` and ${run.html_url}`)
92+
}
93+
}
94+
}
95+
context.res = { body: `push(${req.body.ref}): triggered ${run.html_url}${extra.join('')}` }
7596
}
7697
} else if (eventType === 'issue_comment') {
77-
const triggerToken = process.env['GITGITGADGET_TRIGGER_TOKEN'];
78-
if (!triggerToken) {
79-
throw new Error('No configured trigger token');
80-
}
81-
8298
const comment = req.body.comment;
8399
const prNumber = req.body.issue.number;
84100
if (!comment || !comment.id || !prNumber) {
@@ -100,19 +116,13 @@ module.exports = async (context, req) => {
100116
return;
101117
}
102118

103-
const sourceBranch = `refs/pull/${prNumber}/head`;
104-
const parameters = {
105-
'pr.comment.id': comment.id,
106-
};
107-
const pipelineId = pipelines[repositoryOwner];
108-
if (!pipelineId || pipelineId < 1)
109-
throw new Error(`No pipeline set up for org ${repositoryOwner}`);
110-
context.log(`Queuing with branch ${sourceBranch} and parameters ${JSON.stringify(parameters)}`);
111-
await triggerAzurePipeline(triggerToken, 'gitgitgadget', 'git', pipelineId, sourceBranch, parameters);
119+
const run = await triggerWorkflowDispatch(...a, 'handle-pr-comment.yml', 'main', {
120+
'pr-comment-url': comment.html_url
121+
})
112122

113123
context.res = {
114124
// status: 200, /* Defaults to 200 */
115-
body: 'Okay!',
125+
body: `Okay, triggered ${run.html_url}!`,
116126
};
117127
} else {
118128
context.log(`Unhandled request:\n${JSON.stringify(req, null, 4)}`);

GitGitGadget/trigger-azure-pipeline.js

Lines changed: 0 additions & 48 deletions
This file was deleted.

GitGitGadget/trigger-workflow-dispatch.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,34 @@ const triggerWorkflowDispatch = async (context, token, owner, repo, workflow_id,
5656
return runs[0]
5757
}
5858

59+
const listWorkflowRuns = async (context, token, owner, repo, workflow_id, branch, status) => {
60+
if (token === undefined) {
61+
const { getInstallationIdForRepo } = require('./get-installation-id-for-repo')
62+
const installationID = await getInstallationIdForRepo(context, owner, repo)
63+
64+
const { getInstallationAccessToken } = require('./get-installation-access-token')
65+
token = await getInstallationAccessToken(context, installationID)
66+
}
67+
68+
const query = [
69+
branch && `branch=${branch}`,
70+
status && `status=${status}`,
71+
]
72+
.filter((e) => e)
73+
.map((e, i) => `${i === 0 ? '?' : '&'}${e}`)
74+
.join('')
75+
76+
const result = await gitHubAPIRequest(
77+
context,
78+
token,
79+
'GET',
80+
`/repos/${owner}/${repo}/actions/workflows/${workflow_id}/runs${query}`,
81+
)
82+
return result.workflow_runs
83+
}
84+
5985
module.exports = {
6086
triggerWorkflowDispatch,
61-
waitForWorkflowRun
87+
waitForWorkflowRun,
88+
listWorkflowRuns,
6289
}

__tests__/index.test.js

Lines changed: 113 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
const mockTriggerWorkflowDispatch = jest.fn(async (_context, _token, owner, repo, workflow_id, ref, inputs) => {
2-
expect(`${owner}/${repo}`).toEqual('gitgitgadget-workflows/gitgitgadget-workflows')
3-
expect(workflow_id).toEqual('sync-ref.yml')
4-
expect(ref).toEqual('main')
5-
expect(inputs).toEqual({ ref: 'refs/heads/next' })
6-
return { html_url: '<the URL to the workflow run>'}
1+
const mockTriggerWorkflowDispatch = jest.fn(async (_context, _token, _owner, _repo, workflow_id, ref, inputs) => {
2+
return { html_url: `<the URL to the workflow ${workflow_id} run on ${ref} with inputs ${JSON.stringify(inputs)}>` }
73
})
84
jest.mock('../GitGitGadget/trigger-workflow-dispatch', () => ({
9-
triggerWorkflowDispatch: mockTriggerWorkflowDispatch
5+
triggerWorkflowDispatch: mockTriggerWorkflowDispatch,
6+
listWorkflowRuns: jest.fn(async (_context, _token, _owner, _repo, workflow_id, branch, status) => {
7+
if (workflow_id === 'update-prs.yml' && branch === 'main' && status === 'queued') {
8+
// pretend that `update-prs` is clogged up, for whatever reason
9+
return [
10+
{ id: 1, head_branch: 'main', status: 'queued', html_url: '<the URL to the workflow run>' },
11+
{ id: 2, head_branch: 'main', status: 'queued', html_url: '<another URL to the workflow run>' }
12+
]
13+
}
14+
return []
15+
})
1016
}))
1117

1218
const index = require('../GitGitGadget/index')
@@ -147,17 +153,10 @@ const testIssueComment = (comment, repoOwner, fn) => {
147153
testIssueComment('/test', async (context) => {
148154
expect(context.done).toHaveBeenCalledTimes(1)
149155
expect(context.res).toEqual({
150-
body: 'Okay!'
151-
})
152-
expect(mockRequest.write).toHaveBeenCalledTimes(1)
153-
expect(JSON.parse(mockRequest.write.mock.calls[0][0])).toEqual({
154-
definition: {
155-
id: 3
156-
},
157-
sourceBranch: 'refs/pull/1886743660/head',
158-
parameters: '{"pr.comment.id":27988538471837300}'
156+
body: 'Okay, triggered <the URL to the workflow handle-pr-comment.yml run on main with inputs {"pr-comment-url":"https://github.com/gitgitgadget/git/pull/1886743660"}>!'
159157
})
160-
expect(mockRequest.end).toHaveBeenCalledTimes(1)
158+
expect(mockRequest.write).not.toHaveBeenCalled()
159+
expect(mockRequest.end).not.toHaveBeenCalled()
161160
})
162161

163162
testIssueComment('/verify-repository', 'nope', (context) => {
@@ -197,7 +196,7 @@ testWebhookPayload('react to `next` being pushed to git/git', 'push', {
197196
}
198197
}, (context) => {
199198
expect(context.res).toEqual({
200-
body: 'push(refs/heads/next): triggered <the URL to the workflow run>'
199+
body: 'push(refs/heads/next): triggered <the URL to the workflow sync-ref.yml run on main with inputs {"ref":"refs/heads/next"}>'
201200
})
202201
expect(mockTriggerWorkflowDispatch).toHaveBeenCalledTimes(1)
203202
expect(mockTriggerWorkflowDispatch.mock.calls[0]).toEqual([
@@ -210,4 +209,100 @@ testWebhookPayload('react to `next` being pushed to git/git', 'push', {
210209
ref: 'refs/heads/next'
211210
}
212211
])
212+
})
213+
214+
testWebhookPayload('react to `seen` being pushed to git/git', 'push', {
215+
ref: 'refs/heads/seen',
216+
repository: {
217+
full_name: 'git/git',
218+
owner: {
219+
login: 'git'
220+
}
221+
}
222+
}, (context) => {
223+
expect(context.res).toEqual({
224+
body: [
225+
'push(refs/heads/seen):',
226+
'triggered <the URL to the workflow sync-ref.yml run on main with inputs {"ref":"refs/heads/seen"}>',
227+
'and <the URL to the workflow update-mail-to-commit-notes.yml run on main with inputs undefined>'
228+
].join(' ')
229+
})
230+
// we expect `update-prs` _not_ to be triggered here because we pretend that it is already queued
231+
expect(mockTriggerWorkflowDispatch).toHaveBeenCalledTimes(2)
232+
expect(mockTriggerWorkflowDispatch.mock.calls[0]).toEqual([
233+
context,
234+
undefined,
235+
'gitgitgadget-workflows',
236+
'gitgitgadget-workflows',
237+
'sync-ref.yml',
238+
'main', {
239+
ref: 'refs/heads/seen'
240+
}
241+
])
242+
expect(mockTriggerWorkflowDispatch.mock.calls[1]).toEqual([
243+
context,
244+
undefined,
245+
'gitgitgadget-workflows',
246+
'gitgitgadget-workflows',
247+
'update-mail-to-commit-notes.yml',
248+
'main',
249+
undefined
250+
])
251+
})
252+
253+
testWebhookPayload('react to `lore-1` being pushed to https://github.com/gitgitgadget/git-mailing-list-mirror', 'push', {
254+
ref: 'refs/heads/lore-1',
255+
repository: {
256+
full_name: 'gitgitgadget/git-mailing-list-mirror',
257+
owner: {
258+
login: 'gitgitgadget'
259+
}
260+
}
261+
}, (context) => {
262+
expect(context.res).toEqual({
263+
body: [
264+
'push(refs/heads/lore-1 in gitgitgadget/git-mailing-list-mirror):',
265+
'triggered <the URL to the workflow handle-new-mails.yml run on main with inputs undefined>'
266+
].join(' ')
267+
})
268+
expect(mockTriggerWorkflowDispatch).toHaveBeenCalledTimes(1)
269+
expect(mockTriggerWorkflowDispatch.mock.calls[0]).toEqual([
270+
context,
271+
undefined,
272+
'gitgitgadget-workflows',
273+
'gitgitgadget-workflows',
274+
'handle-new-mails.yml',
275+
'main',
276+
undefined
277+
])
278+
})
279+
testWebhookPayload('react to PR push', 'pull_request', {
280+
action: 'synchronize',
281+
pull_request: {
282+
html_url: 'https://github.com/gitgitgadget/git/pull/1956',
283+
},
284+
repository: {
285+
full_name: 'gitgitgadget/git',
286+
owner: {
287+
login: 'gitgitgadget'
288+
}
289+
}
290+
}, (context) => {
291+
expect(context.res).toEqual({
292+
body: [
293+
'Okay, triggered <the URL to the workflow handle-pr-push.yml run on main with inputs',
294+
'{"pr-url":"https://github.com/gitgitgadget/git/pull/1956"}>!'
295+
].join(' ')
296+
})
297+
expect(mockTriggerWorkflowDispatch).toHaveBeenCalledTimes(1)
298+
expect(mockTriggerWorkflowDispatch.mock.calls[0]).toEqual([
299+
context,
300+
undefined,
301+
'gitgitgadget-workflows',
302+
'gitgitgadget-workflows',
303+
'handle-pr-push.yml',
304+
'main', {
305+
'pr-url': 'https://github.com/gitgitgadget/git/pull/1956',
306+
}
307+
])
213308
})

0 commit comments

Comments
 (0)