Skip to content

Commit 01c51fa

Browse files
committed
Automatically trigger the git-artifacts runs upon a completed tag-git run
This commit is part of Git for Windows' transition off of Azure Pipelines onto GitHub workflows. One thing that was changed during that transition is that the tasks were split up into several workflow runs instead of having one ginormous `Git Artifacts` Pipeline that does everything from tagging the Git version to building the Pacman packages in matrix builds, to building the installers/portable Gits/MinGits/etc in another set of matrix builds, to generating the release notes as well as the announcement mail. Now, the Git version is tagged in the `tag-git` workflow, and that workflow also generates the release notes and announcement mail template (the SHA-256s of the generated artifacts are part of both, therefore the final version can only be constructed once those artifacts are available). Once the `tag-git` workflow has run successfully, the `git-artifacts` workflow needs to be started multiple times, once per supported architecture (currently x86_64 and i686, hopefully soon also aarch64). This commit teaches GitForWindowsHelper to do that by reacting when a `tag-git` run completed, triggering the `git-artifacts` runs. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent 3a1c853 commit 01c51fa

4 files changed

Lines changed: 211 additions & 2 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
module.exports = async (context, req) => {
2+
const action = req.body.action
3+
const checkRunOwner = req.body.repository.owner.login
4+
const checkRunRepo = req.body.repository.name
5+
const checkRun = req.body.check_run
6+
const commitSHA = checkRun.head_sha
7+
const name = checkRun.name
8+
const conclusion = checkRun.conclusion
9+
const text = checkRun.output.text
10+
11+
const getToken = (() => {
12+
const tokens = {}
13+
14+
const get = async (owner, repo) => {
15+
const getInstallationIdForRepo = require('./get-installation-id-for-repo')
16+
const installationId = await getInstallationIdForRepo(context, owner, repo)
17+
const getInstallationAccessToken = require('./get-installation-access-token')
18+
return await getInstallationAccessToken(context, installationId)
19+
}
20+
21+
return async (owner, repo) => tokens[[owner, repo]] || (tokens[[owner, repo]] = await get(owner, repo))
22+
})()
23+
24+
if (action === 'completed') {
25+
if (name === 'tag-git') {
26+
if (checkRunOwner !== 'git-for-windows' || checkRunRepo !== 'git') {
27+
throw new Error(`Refusing to handle cascading run in ${checkRunOwner}/${checkRunRepo}`)
28+
}
29+
30+
if (conclusion !== 'success') {
31+
throw new Error(`tag-git run ${checkRun.id} completed with ${conclusion}: ${checkRun.html_url}`)
32+
}
33+
34+
const match = text.match(/For details, see \[this run\]\(https:\/\/github.com\/([^/]+)\/([^/]+)\/actions\/runs\/(\d+)\)/)
35+
if (!match) throw new Error(`Unhandled 'text' attribute of tag-git run ${checkRun.id}: ${checkRun.url}`)
36+
const owner = match[1]
37+
const repo = match[2]
38+
const workflowRunId = Number(match[3])
39+
if (owner !== 'git-for-windows' || repo !== 'git-for-windows-automation') {
40+
throw new Error(`Unexpected repository ${owner}/${repo} for tag-git run ${checkRun.id}: ${checkRun.url}`)
41+
}
42+
43+
let res = ''
44+
45+
const architecturesToTrigger = []
46+
const { listCheckRunsForCommit, queueCheckRun } = require('./check-runs')
47+
for (const architecture of ['x86_64', 'i686']) {
48+
const workflowName = `git-artifacts-${architecture}`
49+
const runs = await listCheckRunsForCommit(
50+
context,
51+
await getToken(checkRunOwner, checkRunRepo),
52+
checkRunOwner,
53+
checkRunRepo,
54+
commitSHA,
55+
workflowName
56+
)
57+
const latest = runs
58+
.filter(run => run.output.summary.endsWith(`(tag-git run #${workflowRunId})`))
59+
.sort((a, b) => a.id - b.id)
60+
.pop()
61+
if (latest && (latest.status !== 'completed' || latest.conclusion === 'success')) {
62+
// It either succeeded or is still running
63+
res = `${res}${workflowName} run already exists at ${latest.html_url}.\n`
64+
} else {
65+
architecturesToTrigger.push(architecture)
66+
}
67+
}
68+
69+
if (architecturesToTrigger.length === 0) return `${res}No workflows need to be run!\n`
70+
71+
const gitVersionMatch = checkRun.output.summary.match(/^Tag Git (\S+) @([0-9a-f]+)$/)
72+
if (!gitVersionMatch) {
73+
throw new Error(`Could not parse Git version from summary '${checkRun.output.summary}' of tag-git run ${checkRun.id}: ${checkRun.url}`)
74+
}
75+
if (commitSHA !== gitVersionMatch[2]) {
76+
throw new Error(`Expected ${commitSHA} in summary '${checkRun.output.summary}' of tag-git run ${checkRun.id}: ${checkRun.url}`)
77+
}
78+
const gitVersion = gitVersionMatch[1]
79+
80+
for (const architecture of architecturesToTrigger) {
81+
const workflowName = `git-artifacts-${architecture}`
82+
const title = `Build Git ${gitVersion} artifacts`
83+
const summary = `Build Git ${gitVersion} artifacts from commit ${commitSHA} (tag-git run #${workflowRunId})`
84+
await queueCheckRun(
85+
context,
86+
await getToken(checkRunOwner, checkRunRepo),
87+
checkRunOwner,
88+
checkRunRepo,
89+
commitSHA,
90+
workflowName,
91+
title,
92+
summary
93+
)
94+
}
95+
96+
const triggerWorkflowDispatch = require('./trigger-workflow-dispatch')
97+
for (const architecture of architecturesToTrigger) {
98+
const run = await triggerWorkflowDispatch(
99+
context,
100+
await getToken(owner, repo),
101+
owner,
102+
repo,
103+
'git-artifacts.yml',
104+
'main', {
105+
architecture,
106+
tag_git_workflow_run_id: workflowRunId
107+
}
108+
)
109+
res = `${res}The \`git-artifacts-${architecture}\` workflow run [was started](${run.html_url}).\n`
110+
}
111+
112+
return res
113+
}
114+
return `Not a cascading run: ${name}; Doing nothing.`
115+
}
116+
return `Unhandled action: ${action}`
117+
}

GitForWindowsHelper/check-runs.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,23 @@ const cancelWorkflowRun = async (context, token, owner, repo, workflowRunId) =>
7171
console.log(answer)
7272
}
7373

74+
const listCheckRunsForCommit = async (context, token, owner, repo, rev, checkRunName) => {
75+
const githubApiRequest = require('./github-api-request')
76+
77+
const answer = await githubApiRequest(
78+
context,
79+
token,
80+
'GET',
81+
`/repos/${owner}/${repo}/commits/${rev}/check-runs?per_page=100${
82+
checkRunName ? `&check_name=${checkRunName}` : ''
83+
}`
84+
)
85+
return answer.check_runs
86+
}
87+
7488
module.exports = {
7589
queueCheckRun,
7690
updateCheckRun,
77-
cancelWorkflowRun
91+
cancelWorkflowRun,
92+
listCheckRunsForCommit
7893
}

GitForWindowsHelper/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ module.exports = async function (context, req) {
4444
return withStatus(500, undefined, e.toString('utf-8'))
4545
}
4646

47+
try {
48+
const cascadingRuns = require('./cascading-runs.js')
49+
if (req.headers['x-github-event'] === 'check_run'
50+
&& req.body.repository.full_name === 'git-for-windows/git'
51+
&& req.body.action === 'completed') return ok(await cascadingRuns(context, req))
52+
} catch (e) {
53+
context.log(e)
54+
return withStatus(500, undefined, e.toString('utf-8'))
55+
}
56+
4757
context.log("Got headers")
4858
context.log(req.headers)
4959
context.log("Got body")

__tests__/index.test.js

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,23 @@ The MINGW workflow run [was started](dispatched-workflow-open-pr.yml)`
259259

260260
let mockQueueCheckRun = jest.fn(() => 'check-run-id')
261261
let mockUpdateCheckRun = jest.fn()
262+
let mockListCheckRunsForCommit = jest.fn((_context, _token, _owner, _repo, _rev, checkRunName) => {
263+
if (checkRunName === 'git-artifacts-x86_64') return [{
264+
status: 'completed',
265+
conclusion: 'success',
266+
html_url: '<url-to-existing-x86_64-run>',
267+
output: {
268+
title: 'Build Git -rc1',
269+
summary: 'Build Git -rc1 from commit c8edb521bdabec14b07e9142e48cab77a40ba339 (tag-git run #4322343196)'
270+
}
271+
}]
272+
return []
273+
})
262274
jest.mock('../GitForWindowsHelper/check-runs', () => {
263275
return {
264276
queueCheckRun: mockQueueCheckRun,
265-
updateCheckRun: mockUpdateCheckRun
277+
updateCheckRun: mockUpdateCheckRun,
278+
listCheckRunsForCommit: mockListCheckRunsForCommit
266279
}
267280
})
268281

@@ -323,3 +336,57 @@ The workflow run [was started](dispatched-workflow-add-release-note.yml)`,
323336
type: 'feature'
324337
})
325338
})
339+
340+
test('a completed `tag-git` run triggers `git-artifacts` runs', async () => {
341+
const context = makeContext({
342+
action: 'completed',
343+
check_run: {
344+
name: 'tag-git',
345+
head_sha: 'c8edb521bdabec14b07e9142e48cab77a40ba339',
346+
conclusion: 'success',
347+
output: {
348+
title: 'Tag Git v2.40.0-rc1.windows.1 @c8edb521bdabec14b07e9142e48cab77a40ba339',
349+
summary: 'Tag Git v2.40.0-rc1.windows.1 @c8edb521bdabec14b07e9142e48cab77a40ba339',
350+
text: 'For details, see [this run](https://github.com/git-for-windows/git-for-windows-automation/actions/runs/4322343196).\nTagged Git v2.40.0-rc1.windows.1\nDone!.'
351+
}
352+
},
353+
installation: {
354+
id: 123
355+
},
356+
repository: {
357+
name: 'git',
358+
owner: {
359+
login: 'git-for-windows'
360+
},
361+
full_name: 'git-for-windows/git'
362+
}
363+
}, {
364+
'x-github-event': 'check_run'
365+
})
366+
367+
try {
368+
expect(await index(context, context.req)).toBeUndefined()
369+
expect(context.res).toEqual({
370+
body: `git-artifacts-x86_64 run already exists at <url-to-existing-x86_64-run>.
371+
The \`git-artifacts-i686\` workflow run [was started](dispatched-workflow-git-artifacts.yml).
372+
`,
373+
headers: undefined,
374+
status: undefined
375+
})
376+
expect(mockGitHubApiRequest).toHaveBeenCalled()
377+
expect(mockGitHubApiRequest.mock.calls[0].slice(1)).toEqual([
378+
'installation-access-token',
379+
'POST',
380+
'/repos/git-for-windows/git-for-windows-automation/actions/workflows/git-artifacts.yml/dispatches', {
381+
ref: 'main',
382+
inputs: {
383+
architecture: 'i686',
384+
tag_git_workflow_run_id: 4322343196
385+
}
386+
}
387+
])
388+
} catch (e) {
389+
context.log.mock.calls.forEach(e => console.log(e[0]))
390+
throw e;
391+
}
392+
})

0 commit comments

Comments
 (0)