-
Notifications
You must be signed in to change notification settings - Fork 5
Support snapshot builds #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
977332b
f9d770e
e4f4be9
9bc6f17
2c788f9
5597f02
f4eee78
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -133,12 +133,314 @@ const cascadingRuns = async (context, req) => { | |
|
||
return comment | ||
} | ||
if (checkRunOwner === 'git-for-windows' | ||
&& checkRunRepo === 'git' | ||
&& name.startsWith('git-artifacts-')) { | ||
const output = req.body.check_run.output | ||
const match = output.summary.match( | ||
/Build Git (\S+) artifacts from commit (\S+) \(tag-git run #(\d+)\)/ | ||
) | ||
if (!match) throw new Error( | ||
`Could not parse 'summary' attribute of check-run ${req.body.check_run.id}: ${output.summary}` | ||
) | ||
const [, ver, commit, tagGitWorkflowRunID] = match | ||
const snapshotTag = `prerelease-${ver.replace(/^v/, '')}` | ||
|
||
// First, verify that the snapshot has not been uploaded yet | ||
const gitSnapshotsToken = await getToken(context, checkRunOwner, 'git-snapshots') | ||
const githubApiRequest = require('./github-api-request') | ||
try { | ||
const releasePath = `${checkRunOwner}/git-snapshots/releases/tags/${snapshotTag}` | ||
await githubApiRequest( | ||
context, | ||
gitSnapshotsToken, | ||
'GET', | ||
`/repos/${releasePath}`, | ||
) | ||
return `Ignoring ${name} check-run because the snapshot for ${commit} was already uploaded` | ||
+ ` to https://github.com/${releasePath}` | ||
} catch(e) { | ||
if (e?.statusCode !== 404) throw e | ||
// The snapshot does not exist yet | ||
} | ||
|
||
// Next, check that the commit is on the `main` branch | ||
const gitToken = await getToken(context, checkRunOwner, checkRunRepo) | ||
const { behind_by } = await githubApiRequest( | ||
context, | ||
gitToken, | ||
'GET', | ||
`/repos/${checkRunOwner}/${checkRunRepo}/compare/HEAD...${commit}`, | ||
) | ||
if (behind_by > 0) { | ||
return `Ignoring ${name} check-run because its corresponding commit ${commit} is not on the main branch` | ||
} | ||
|
||
const workFlowRunIDs = {} | ||
const { listCheckRunsForCommit, queueCheckRun } = require('./check-runs') | ||
for (const architecture of ['x86_64', 'i686', 'aarch64']) { | ||
const workflowName = `git-artifacts-${architecture}` | ||
const runs = name === workflowName ? [req.body.check_run] : await listCheckRunsForCommit( | ||
context, | ||
gitToken, | ||
checkRunOwner, | ||
checkRunRepo, | ||
commit, | ||
workflowName | ||
) | ||
const needle = | ||
`Build Git ${ver} artifacts from commit ${commit} (tag-git run #${tagGitWorkflowRunID})` | ||
const latest = runs | ||
.filter(run => run.output.summary === needle) | ||
Comment on lines
+191
to
+194
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This filtering feels fragile. Is this the best (only, I suspect 😢) way to filter runs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, there is no other way, really. I use the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the record: The |
||
.sort((a, b) => a.id - b.id) | ||
.pop() | ||
if (latest) { | ||
if (latest.status !== 'completed') { | ||
return `The '${workflowName}' run at ${latest.html_url} did not complete yet.` | ||
} | ||
if (latest.conclusion !== 'success') { | ||
throw new Error(`The '${workflowName}' run at ${latest.html_url} did not succeed.`) | ||
} | ||
|
||
const match = latest.output.text.match( | ||
/For details, see \[this run\]\(https:\/\/github.com\/([^/]+)\/([^/]+)\/actions\/runs\/(\d+)\)/ | ||
) | ||
if (!match) throw new Error(`Unhandled 'text' attribute of git-artifacts run ${latest.id}: ${latest.url}`) | ||
const owner = match[1] | ||
const repo = match[2] | ||
workFlowRunIDs[architecture] = match[3] | ||
if (owner !== 'git-for-windows' || repo !== 'git-for-windows-automation') { | ||
throw new Error(`Unexpected repository ${owner}/${repo} for git-artifacts run ${latest.id}: ${latest.url}`) | ||
} | ||
} else { | ||
return `Won't trigger 'upload-snapshot' in reaction to ${name} because the '${workflowName}' run does not exist.` | ||
} | ||
} | ||
|
||
const checkRunTitle = `Upload snapshot ${snapshotTag}` | ||
await queueCheckRun( | ||
context, | ||
gitToken, | ||
'git-for-windows', | ||
'git', | ||
commit, | ||
'upload-snapshot', | ||
checkRunTitle, | ||
checkRunTitle | ||
) | ||
|
||
const gitForWindowsAutomationToken = | ||
await getToken(context, checkRunOwner, 'git-for-windows-automation') | ||
const triggerWorkflowDispatch = require('./trigger-workflow-dispatch') | ||
const answer = await triggerWorkflowDispatch( | ||
context, | ||
gitForWindowsAutomationToken, | ||
'git-for-windows', | ||
'git-for-windows-automation', | ||
'upload-snapshot.yml', | ||
'main', { | ||
git_artifacts_x86_64_workflow_run_id: workFlowRunIDs['x86_64'], | ||
git_artifacts_i686_workflow_run_id: workFlowRunIDs['i686'], | ||
git_artifacts_aarch64_workflow_run_id: workFlowRunIDs['aarch64'], | ||
} | ||
) | ||
|
||
return `The 'upload-snapshot' workflow run was started at ${answer.html_url}` | ||
} | ||
return `Not a cascading run: ${name}; Doing nothing.` | ||
} | ||
return `Unhandled action: ${action}` | ||
} | ||
|
||
const handlePush = async (context, req) => { | ||
const pushOwner = req.body.repository.owner.login | ||
const pushRepo = req.body.repository.name | ||
const ref = req.body.ref | ||
const commit = req.body.after | ||
|
||
if (pushOwner !== 'git-for-windows' || pushRepo !== 'git') { | ||
throw new Error(`Refusing to handle push to ${pushOwner}/${pushRepo}`) | ||
} | ||
|
||
if (ref !== 'refs/heads/main') return `Ignoring push to ${ref}` | ||
|
||
// See whether there was are already a `tag-git` check-run for this commit | ||
const { listCheckRunsForCommit, queueCheckRun, updateCheckRun } = require('./check-runs') | ||
const gitToken = await getToken(context, pushOwner, pushRepo) | ||
const runs = await listCheckRunsForCommit( | ||
context, | ||
gitToken, | ||
pushOwner, | ||
pushRepo, | ||
commit, | ||
'tag-git' | ||
) | ||
|
||
const latest = runs | ||
.sort((a, b) => a.id - b.id) | ||
.pop() | ||
|
||
if (latest && latest.status !== 'completed') throw new Error(`The 'tag-git' run at ${latest.html_url} did not complete yet before ${commit} was pushed to ${ref}!`) | ||
|
||
const gitForWindowsAutomationToken = | ||
await getToken(context, pushOwner, 'git-for-windows-automation') | ||
const triggerWorkflowDispatch = require('./trigger-workflow-dispatch') | ||
if (!latest) { | ||
// There is no `tag-git` workflow run; Trigger it to build a new snapshot | ||
const tagGitCheckRunTitle = `Tag snapshot Git @${commit}` | ||
const tagGitCheckRunId = await queueCheckRun( | ||
context, | ||
gitForWindowsAutomationToken, | ||
pushOwner, | ||
pushRepo, | ||
commit, | ||
'tag-git', | ||
tagGitCheckRunTitle, | ||
tagGitCheckRunTitle | ||
) | ||
|
||
try { | ||
const answer = await triggerWorkflowDispatch( | ||
context, | ||
gitForWindowsAutomationToken, | ||
pushOwner, | ||
'git-for-windows-automation', | ||
'tag-git.yml', | ||
'main', { | ||
rev: commit, | ||
owner: pushOwner, | ||
repo: pushRepo, | ||
snapshot: 'true' | ||
} | ||
) | ||
return `The 'tag-git' workflow run was started at ${answer.html_url}` | ||
} catch (e) { | ||
await updateCheckRun( | ||
context, | ||
gitForWindowsAutomationToken, | ||
pushOwner, | ||
pushRepo, | ||
tagGitCheckRunId, { | ||
status: 'completed', | ||
conclusion: 'failure', | ||
output: { | ||
title: tagGitCheckRunTitle, | ||
summary: tagGitCheckRunTitle, | ||
text: e.message || JSON.stringify(e, null, 2) | ||
} | ||
} | ||
) | ||
throw e | ||
} | ||
} | ||
|
||
if (latest.conclusion !== 'success') throw new Error( | ||
`The 'tag-git' run at ${latest.html_url} did not succeed (conclusion = ${latest.conclusion}).` | ||
) | ||
|
||
// There is already a `tag-git` workflow run; Is there already an `upload-snapshot` run? | ||
const latestUploadSnapshotRun = (await listCheckRunsForCommit( | ||
context, | ||
gitToken, | ||
pushOwner, | ||
pushRepo, | ||
commit, | ||
'upload-snapshot' | ||
)).pop() | ||
if (latestUploadSnapshotRun) return `The 'upload-snapshot' check-run already exists for ${commit}: ${latestUploadSnapshotRun.html_url}` | ||
|
||
// Trigger the `upload-snapshot` run directly | ||
const tagGitCheckRunTitle = `Upload snapshot Git @${commit}` | ||
const tagGitCheckRunId = await queueCheckRun( | ||
context, | ||
await getToken(), | ||
pushOwner, | ||
pushRepo, | ||
commit, | ||
'tag-git', | ||
tagGitCheckRunTitle, | ||
tagGitCheckRunTitle | ||
) | ||
|
||
const match = latest.output.summary.match(/^Tag Git (\S+) @([0-9a-f]+)$/) | ||
if (!match) throw new Error(`Unexpected summary '${latest.output.summary}' of tag-git run: ${latest.html_url}`) | ||
if (!match[2] === commit) throw new Error(`Unexpected revision ${match[2]} '${latest.output.summary}' of tag-git run: ${latest.html_url}`) | ||
const ver = match[1] | ||
|
||
try { | ||
const workFlowRunIDs = {} | ||
for (const architecture of ['x86_64', 'i686', 'aarch64']) { | ||
const workflowName = `git-artifacts-${architecture}` | ||
const runs = await listCheckRunsForCommit( | ||
context, | ||
gitToken, | ||
pushOwner, | ||
pushRepo, | ||
commit, | ||
workflowName | ||
) | ||
const needle = | ||
`Build Git ${ver} artifacts from commit ${commit} (tag-git run #${latest.id})` | ||
const latest2 = runs | ||
.filter(run => run.output.summary === needle) | ||
.sort((a, b) => a.id - b.id) | ||
.pop() | ||
if (latest2) { | ||
if (latest2.status !== 'completed' || latest2.conclusion !== 'success') { | ||
throw new Error(`The '${workflowName}' run at ${latest2.html_url} did not succeed.`) | ||
} | ||
|
||
const match = latest2.output.text.match( | ||
/For details, see \[this run\]\(https:\/\/github.com\/([^/]+)\/([^/]+)\/actions\/runs\/(\d+)\)/ | ||
) | ||
if (!match) throw new Error(`Unhandled 'text' attribute of git-artifacts run ${latest2.id}: ${latest2.url}`) | ||
const owner = match[1] | ||
const repo = match[2] | ||
workFlowRunIDs[architecture] = match[3] | ||
if (owner !== 'git-for-windows' || repo !== 'git-for-windows-automation') { | ||
throw new Error(`Unexpected repository ${owner}/${repo} for git-artifacts run ${latest2.id}: ${latest2.url}`) | ||
} | ||
} else { | ||
return `Won't trigger 'upload-snapshot' on pushing ${commit} because the '${workflowName}' run does not exist.` | ||
} | ||
} | ||
|
||
const answer = await triggerWorkflowDispatch( | ||
context, | ||
gitForWindowsAutomationToken, | ||
pushRepo, | ||
'git-for-windows-automation', | ||
'upload-snapshot.yml', | ||
'main', { | ||
git_artifacts_x86_64_workflow_run_id: workFlowRunIDs['x86_64'], | ||
git_artifacts_i686_workflow_run_id: workFlowRunIDs['i686'], | ||
git_artifacts_aarch64_workflow_run_id: workFlowRunIDs['aarch64'], | ||
} | ||
) | ||
|
||
return `The 'upload-snapshot' workflow run was started at ${answer.html_url}` | ||
} catch (e) { | ||
await updateCheckRun( | ||
context, | ||
gitForWindowsAutomationToken, | ||
pushOwner, | ||
pushRepo, | ||
tagGitCheckRunId, { | ||
status: 'completed', | ||
conclusion: 'failure', | ||
output: { | ||
title: tagGitCheckRunTitle, | ||
summary: tagGitCheckRunTitle, | ||
text: e.message || JSON.stringify(e, null, 2) | ||
} | ||
} | ||
) | ||
throw e | ||
} | ||
} | ||
|
||
module.exports = { | ||
triggerGitArtifactsRuns, | ||
cascadingRuns | ||
cascadingRuns, | ||
handlePush | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've never seen this syntax before (the leading empty
,
).. is it intentional?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this skips the
match[0]
, which would be the entire matched string (I only need the capturing groups).