workflow/labels: fix scheduled runs (#417250)

authored by Wolfgang Walther and committed by GitHub e1a3aac6 0ed1811d

+85 -78
+85 -78
.github/workflows/labels.yml
··· 69 69 // Normally a scheduled run, but could be workflow_dispatch, see above. Go back as far 70 70 // as the last successful run of this workflow to make sure we are not leaving anyone 71 71 // behind on GHA failures. 72 + // Defaults to go back 1 hour on the first run. 72 73 return (await github.rest.actions.listWorkflowRuns({ 73 74 ...context.repo, 74 75 workflow_id: 'labels.yml', 75 76 event: 'schedule', 76 77 status: 'success', 77 78 exclude_pull_requests: true 78 - })).data.workflow_runs[0]?.created_at 79 + })).data.workflow_runs[0]?.created_at ?? new Date().getTime() - 1 * 60 * 60 * 1000 79 80 })()) 80 81 core.info('cutoff timestamp: ' + cutoff.toISOString()) 81 82 ··· 98 99 direction: 'desc', 99 100 ...prEventCondition 100 101 }, 101 - async (response, done) => await Promise.all(response.data.map(async (pull_request) => { 102 - const log = (k,v) => core.info(`PR #${pull_request.number} - ${k}: ${v}`) 102 + async (response, done) => (await Promise.allSettled(response.data.map(async (pull_request) => { 103 + try { 104 + const log = (k,v) => core.info(`PR #${pull_request.number} - ${k}: ${v}`) 103 105 104 - log('Last updated at', pull_request.updated_at) 105 - if (new Date(pull_request.updated_at) < cutoff) return done() 106 + log('Last updated at', pull_request.updated_at) 107 + if (new Date(pull_request.updated_at) < cutoff) return done() 106 108 107 - const run_id = (await github.rest.actions.listWorkflowRuns({ 108 - ...context.repo, 109 - workflow_id: 'eval.yml', 110 - event: 'pull_request_target', 111 - // For PR events, the workflow run is still in progress with this job itself. 112 - status: prEventCondition ? 'in_progress' : 'success', 113 - exclude_pull_requests: true, 114 - head_sha: pull_request.head.sha 115 - })).data.workflow_runs[0]?.id 109 + const run_id = (await github.rest.actions.listWorkflowRuns({ 110 + ...context.repo, 111 + workflow_id: 'eval.yml', 112 + event: 'pull_request_target', 113 + // For PR events, the workflow run is still in progress with this job itself. 114 + status: prEventCondition ? 'in_progress' : 'success', 115 + exclude_pull_requests: true, 116 + head_sha: pull_request.head.sha 117 + })).data.workflow_runs[0]?.id 116 118 117 - // Newer PRs might not have run Eval to completion, yet. We can skip them, because this 118 - // job will be run as part of that Eval run anyway. 119 - log('Last eval run', run_id) 120 - if (!run_id) return; 119 + // Newer PRs might not have run Eval to completion, yet. We can skip them, because this 120 + // job will be run as part of that Eval run anyway. 121 + log('Last eval run', run_id) 122 + if (!run_id) return; 121 123 122 - const artifact = (await github.rest.actions.listWorkflowRunArtifacts({ 123 - ...context.repo, 124 - run_id, 125 - name: 'comparison' 126 - })).data.artifacts[0] 124 + const artifact = (await github.rest.actions.listWorkflowRunArtifacts({ 125 + ...context.repo, 126 + run_id, 127 + name: 'comparison' 128 + })).data.artifacts[0] 129 + 130 + // Instead of checking the boolean artifact.expired, we will give us a minute to 131 + // actually download the artifact in the next step and avoid that race condition. 132 + log('Artifact expires at', artifact.expires_at) 133 + if (new Date(artifact.expires_at) < new Date(new Date().getTime() + 60 * 1000)) return; 127 134 128 - // Instead of checking the boolean artifact.expired, we will give us a minute to 129 - // actually download the artifact in the next step and avoid that race condition. 130 - log('Artifact expires at', artifact.expires_at) 131 - if (new Date(artifact.expires_at) < new Date(new Date().getTime() + 60 * 1000)) return; 135 + await artifactClient.downloadArtifact(artifact.id, { 136 + findBy: { 137 + repositoryName: context.repo.repo, 138 + repositoryOwner: context.repo.owner, 139 + token: core.getInput('github-token') 140 + }, 141 + path: path.resolve(pull_request.number.toString()), 142 + expectedHash: artifact.digest 143 + }) 132 144 133 - await artifactClient.downloadArtifact(artifact.id, { 134 - findBy: { 135 - repositoryName: context.repo.repo, 136 - repositoryOwner: context.repo.owner, 137 - token: core.getInput('github-token') 138 - }, 139 - path: path.resolve('comparison'), 140 - expectedHash: artifact.digest 141 - }) 145 + // Get all currently set labels that we manage 146 + const before = 147 + pull_request.labels.map(({ name }) => name) 148 + .filter(name => 149 + name.startsWith('10.rebuild') || 150 + name == '11.by: package-maintainer' || 151 + name.startsWith('12.approvals:') || 152 + name == '12.approved-by: package-maintainer' 153 + ) 142 154 143 - // Get all currently set labels that we manage 144 - const before = 145 - pull_request.labels.map(({ name }) => name) 146 - .filter(name => 147 - name.startsWith('10.rebuild') || 148 - name == '11.by: package-maintainer' || 149 - name.startsWith('12.approvals:') || 150 - name == '12.approved-by: package-maintainer' 155 + const approvals = new Set( 156 + (await github.paginate(github.rest.pulls.listReviews, { 157 + ...context.repo, 158 + pull_number: pull_request.number 159 + })) 160 + .filter(review => review.state == 'APPROVED') 161 + .map(review => review.user.id) 151 162 ) 152 163 153 - const approvals = new Set( 154 - (await github.paginate(github.rest.pulls.listReviews, { 155 - ...context.repo, 156 - pull_number: pull_request.number 157 - })) 158 - .filter(review => review.state == 'APPROVED') 159 - .map(review => review.user.id) 160 - ) 164 + const maintainers = new Set(Object.keys( 165 + JSON.parse(await readFile(`${pull_request.number}/maintainers.json`, 'utf-8')) 166 + )) 161 167 162 - const maintainers = new Set(Object.keys( 163 - JSON.parse(await readFile('comparison/maintainers.json', 'utf-8')) 164 - )) 168 + // And the labels that should be there 169 + const after = JSON.parse(await readFile(`${pull_request.number}/changed-paths.json`, 'utf-8')).labels 170 + if (approvals.size > 0) after.push(`12.approvals: ${approvals.size > 2 ? '3+' : approvals.size}`) 171 + if (Array.from(maintainers).some(m => approvals.has(m))) after.push('12.approved-by: package-maintainer') 165 172 166 - // And the labels that should be there 167 - const after = JSON.parse(await readFile('comparison/changed-paths.json', 'utf-8')).labels 168 - if (approvals.size > 0) after.push(`12.approvals: ${approvals.size > 2 ? '3+' : approvals.size}`) 169 - if (Array.from(maintainers).some(m => approvals.has(m))) after.push('12.approved-by: package-maintainer') 170 - 171 - // Remove the ones not needed anymore 172 - await Promise.all( 173 - before.filter(name => !after.includes(name)) 174 - .map(name => github.rest.issues.removeLabel({ 175 - ...context.repo, 176 - issue_number: pull_request.number, 177 - name 178 - })) 179 - ) 173 + // Remove the ones not needed anymore 174 + await Promise.all( 175 + before.filter(name => !after.includes(name)) 176 + .map(name => github.rest.issues.removeLabel({ 177 + ...context.repo, 178 + issue_number: pull_request.number, 179 + name 180 + })) 181 + ) 180 182 181 - // And add the ones that aren't set already 182 - const added = after.filter(name => !before.includes(name)) 183 - if (added.length > 0) { 184 - await github.rest.issues.addLabels({ 185 - ...context.repo, 186 - issue_number: pull_request.number, 187 - labels: added 188 - }) 183 + // And add the ones that aren't set already 184 + const added = after.filter(name => !before.includes(name)) 185 + if (added.length > 0) { 186 + await github.rest.issues.addLabels({ 187 + ...context.repo, 188 + issue_number: pull_request.number, 189 + labels: added 190 + }) 191 + } 192 + } catch (cause) { 193 + throw new Error(`Labeling PR #${pull_request.number} failed.`, { cause }) 189 194 } 190 - })) 195 + }))) 196 + .filter(({ status }) => status == 'rejected') 197 + .map(({ reason }) => core.setFailed(`${reason.message}\n${reason.cause.stack}`)) 191 198 ) 192 199 193 200 - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0