Skip to content

Commit a36d42a

Browse files
committed
ci: prevent stale branch builds and image promotion
1 parent 89cb3fa commit a36d42a

2 files changed

Lines changed: 133 additions & 4 deletions

File tree

‎.github/workflows/build-dockerhub.yml‎

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,54 @@ permissions:
3131

3232
concurrency:
3333
group: dockerhub-${{ inputs.overwrite_existing_release && inputs.release_tag || github.ref_name }}
34-
cancel-in-progress: true
34+
cancel-in-progress: false
3535

3636
jobs:
37+
source-head:
38+
name: "🧭 Verify Current Source Head"
39+
runs-on: ubuntu-latest
40+
outputs:
41+
current: ${{ steps.verify.outputs.current }}
42+
steps:
43+
- name: Skip stale branch events
44+
id: verify
45+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
46+
with:
47+
script: |
48+
const isBranch = context.ref.startsWith('refs/heads/');
49+
const checksBranchHead =
50+
isBranch &&
51+
(context.eventName === 'push' || context.eventName === 'workflow_dispatch');
52+
53+
if (!checksBranchHead) {
54+
core.setOutput('current', 'true');
55+
return;
56+
}
57+
58+
const branch = context.ref.slice('refs/heads/'.length);
59+
const { data } = await github.rest.repos.getBranch({
60+
owner: context.repo.owner,
61+
repo: context.repo.repo,
62+
branch,
63+
});
64+
const currentSha = data.commit.sha;
65+
const isCurrent = currentSha === context.sha;
66+
67+
core.setOutput('current', String(isCurrent));
68+
if (isCurrent) {
69+
core.info(`${context.sha} is still the current head of ${branch}.`);
70+
} else {
71+
core.notice(
72+
`Skipping stale ${context.eventName} event for ${context.sha}; ` +
73+
`${branch} now points to ${currentSha}.`
74+
);
75+
}
76+
3777
prepare:
3878
name: "🔧 Prepare Metadata"
3979
runs-on: ubuntu-latest
40-
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')
80+
needs: source-head
81+
if: needs.source-head.outputs.current == 'true' && github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')
4182
outputs:
4283
sha_short: ${{ steps.vars.outputs.sha_short }}
4384
version: ${{ steps.mode.outputs.version }}
@@ -578,6 +619,53 @@ jobs:
578619
needs: [prepare, build-linux]
579620
if: github.event_name != 'pull_request' && needs.prepare.outputs.mode != 'master'
580621
steps:
622+
- name: Prevent stale dev image promotion
623+
if: needs.prepare.outputs.mode == 'dev'
624+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
625+
env:
626+
SOURCE_SHA: ${{ github.sha }}
627+
with:
628+
script: |
629+
const branch = context.ref.replace('refs/heads/', '');
630+
const sourceSha = process.env.SOURCE_SHA;
631+
const { data: branchData } = await github.rest.repos.getBranch({
632+
owner: context.repo.owner,
633+
repo: context.repo.repo,
634+
branch,
635+
});
636+
const currentSha = branchData.commit.sha;
637+
638+
if (currentSha === sourceSha) {
639+
core.info(`${sourceSha} is still the current head of ${branch}.`);
640+
return;
641+
}
642+
643+
const { data: currentCommit } = await github.rest.repos.getCommit({
644+
owner: context.repo.owner,
645+
repo: context.repo.repo,
646+
ref: currentSha,
647+
});
648+
const generatedCommitTitle =
649+
'chore: update generated development inputs after build [skip ci]';
650+
const isExpectedGeneratedChild =
651+
currentCommit.parents.length === 1 &&
652+
currentCommit.parents[0].sha === sourceSha &&
653+
currentCommit.commit.message.split('\n', 1)[0] === generatedCommitTitle &&
654+
currentCommit.committer?.login === 'github-actions[bot]';
655+
656+
if (isExpectedGeneratedChild) {
657+
core.info(
658+
`${currentSha} is the expected generated child of ${sourceSha}; ` +
659+
'promotion remains valid.'
660+
);
661+
return;
662+
}
663+
664+
core.setFailed(
665+
`Refusing to promote stale image for ${sourceSha}; ` +
666+
`${branch} now points to ${currentSha}.`
667+
);
668+
581669
- name: Set up Docker Buildx
582670
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
583671

‎.github/workflows/codeql.yml‎

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,54 @@ on:
99

1010
concurrency:
1111
group: ${{ github.workflow }}-${{ github.ref }}
12-
cancel-in-progress: true
12+
cancel-in-progress: false
1313

1414
jobs:
15+
source-head:
16+
name: Verify Current Source Head
17+
runs-on: ubuntu-latest
18+
outputs:
19+
current: ${{ steps.verify.outputs.current }}
20+
steps:
21+
- name: Skip stale branch events
22+
id: verify
23+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
24+
with:
25+
script: |
26+
const isBranch = context.ref.startsWith('refs/heads/');
27+
const checksBranchHead =
28+
isBranch &&
29+
(context.eventName === 'push' || context.eventName === 'workflow_dispatch');
30+
31+
if (!checksBranchHead) {
32+
core.setOutput('current', 'true');
33+
return;
34+
}
35+
36+
const branch = context.ref.slice('refs/heads/'.length);
37+
const { data } = await github.rest.repos.getBranch({
38+
owner: context.repo.owner,
39+
repo: context.repo.repo,
40+
branch,
41+
});
42+
const currentSha = data.commit.sha;
43+
const isCurrent = currentSha === context.sha;
44+
45+
core.setOutput('current', String(isCurrent));
46+
if (isCurrent) {
47+
core.info(`${context.sha} is still the current head of ${branch}.`);
48+
} else {
49+
core.notice(
50+
`Skipping stale ${context.eventName} event for ${context.sha}; ` +
51+
`${branch} now points to ${currentSha}.`
52+
);
53+
}
54+
1555
analyze:
1656
name: Analyze (${{ matrix.language }})
1757
runs-on: ubuntu-latest
18-
if: github.actor != 'dependabot[bot]'
58+
needs: source-head
59+
if: needs.source-head.outputs.current == 'true' && github.actor != 'dependabot[bot]'
1960
permissions:
2061
actions: read
2162
contents: read

0 commit comments

Comments
 (0)