Skip to content

Commit 6cdb2ae

Browse files
committed
Block release if checks are failing
1 parent 9d6df30 commit 6cdb2ae

1 file changed

Lines changed: 81 additions & 3 deletions

File tree

.github/workflows/release.yaml

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ on:
88
- main
99

1010
permissions:
11+
checks: read
1112
contents: write
13+
statuses: read
1214

1315
jobs:
1416
generate-tag:
@@ -29,6 +31,82 @@ jobs:
2931
git tag "$GIT_TAG"
3032
git push origin "$GIT_TAG"
3133
34+
validate-production-tag:
35+
name: Validate production tag checks
36+
runs-on: ubuntu-latest
37+
needs: [generate-tag]
38+
permissions:
39+
checks: read
40+
contents: read
41+
statuses: read
42+
steps:
43+
- name: Fail if the tagged commit has failing or pending checks
44+
if: github.ref_type == 'tag' && !contains(needs.generate-tag.outputs.tag, '-')
45+
uses: actions/github-script@v9
46+
with:
47+
script: |
48+
const failingConclusions = new Set([
49+
"action_required",
50+
"cancelled",
51+
"failure",
52+
"stale",
53+
"startup_failure",
54+
"timed_out",
55+
]);
56+
57+
const checkRuns = await github.paginate(
58+
github.rest.checks.listForRef,
59+
{
60+
...context.repo,
61+
ref: context.sha,
62+
filter: "all",
63+
per_page: 100,
64+
},
65+
);
66+
const currentRunPath = `/actions/runs/${context.runId}/`;
67+
const latestExternalChecks = new Map();
68+
for (const check of checkRuns) {
69+
// A tag workflow can create newer checks with the same names as
70+
// the branch checks. Exclude this run before selecting the latest.
71+
if (check.details_url?.includes(currentRunPath)) {
72+
continue;
73+
}
74+
const key = `${check.app?.id}:${check.name}`;
75+
const latest = latestExternalChecks.get(key);
76+
if (!latest || check.id > latest.id) {
77+
latestExternalChecks.set(key, check);
78+
}
79+
}
80+
const blockingChecks = [...latestExternalChecks.values()].filter(
81+
(check) =>
82+
check.status !== "completed" ||
83+
failingConclusions.has(check.conclusion),
84+
);
85+
86+
const { data: combinedStatus } =
87+
await github.rest.repos.getCombinedStatusForRef({
88+
...context.repo,
89+
ref: context.sha,
90+
});
91+
const blockingStatuses = combinedStatus.statuses.filter(
92+
(status) => status.state !== "success",
93+
);
94+
95+
if (blockingChecks.length || blockingStatuses.length) {
96+
const blockers = [
97+
...blockingChecks.map(
98+
(check) =>
99+
`check run: ${check.name} (${check.conclusion ?? check.status})`,
100+
),
101+
...blockingStatuses.map(
102+
(status) => `commit status: ${status.context} (${status.state})`,
103+
),
104+
];
105+
core.setFailed(
106+
`Production release blocked because ${context.sha} has failing or pending checks:\n${blockers.join("\n")}`,
107+
);
108+
}
109+
32110
build-melange-packages:
33111
needs: [generate-tag]
34112
if: contains(needs.generate-tag.outputs.tag, '-')
@@ -48,7 +126,7 @@ jobs:
48126
arch: ${{ matrix.runner.arch }}
49127

50128
build-securebuild:
51-
needs: [generate-tag]
129+
needs: [generate-tag, validate-production-tag]
52130
if: github.ref_type == 'tag' && !contains(needs.generate-tag.outputs.tag, '-')
53131
uses: ./.github/workflows/publish-securebuild.yml
54132
with:
@@ -205,7 +283,7 @@ jobs:
205283
goreleaser:
206284
runs-on: ubuntu-latest
207285
if: github.ref_type != 'branch'
208-
needs: [generate-tag, build-web]
286+
needs: [generate-tag, validate-production-tag, build-web]
209287
steps:
210288
- name: Checkout
211289
uses: actions/checkout@v7
@@ -384,7 +462,7 @@ jobs:
384462

385463
generate-kots-release-notes-pr:
386464
runs-on: ubuntu-latest
387-
needs: [generate-tag]
465+
needs: [generate-tag, validate-production-tag]
388466
if: github.ref_type != 'branch'
389467
steps:
390468
- name: Setup Depot CLI

0 commit comments

Comments
 (0)