Skip to content

Commit ca538ee

Browse files
committed
feat(workflow): split CI poller into ci-pending.yml and ci-poller.yml
ci-pending.yml: triggered on issues:opened, adds ci-pending label and sets CI_POLLER_HAS_PENDING repo variable to "true". ci-poller.yml: cron (*/5 min) with job-level if that checks vars.CI_POLLER_HAS_PENDING — when "false", no runner is provisioned (zero cost). When "true", checks CI status for all ci-pending issues and flips to ci-ready when green. Resets variable to "false" when all pending issues are resolved. Requires creating a CI_POLLER_HAS_PENDING repo variable (initial value: "false") before deploying.
1 parent a81365e commit ca538ee

File tree

3 files changed

+182
-104
lines changed

3 files changed

+182
-104
lines changed

.github/workflows/ci-pending.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Mark new publish issues as CI-pending
2+
3+
on:
4+
issues:
5+
types: [opened]
6+
7+
permissions:
8+
contents: read
9+
issues: read
10+
actions: write
11+
12+
jobs:
13+
mark-pending:
14+
runs-on: ubuntu-latest
15+
if: "startsWith(github.event.issue.title, 'publish: ')"
16+
steps:
17+
- name: Get auth token
18+
id: token
19+
uses: actions/create-github-app-token@v2.2.1
20+
with:
21+
app-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
22+
private-key: ${{ secrets.SENTRY_INTERNAL_APP_PRIVATE_KEY }}
23+
24+
- name: Add ci-pending label and enable poller
25+
env:
26+
GH_TOKEN: ${{ steps.token.outputs.token }}
27+
run: |
28+
gh issue edit "${{ github.event.issue.number }}" \
29+
-R "$GITHUB_REPOSITORY" \
30+
--add-label "ci-pending"
31+
32+
# Enable the cron poller (ci-poller.yml) so it starts checking
33+
gh variable set CI_POLLER_HAS_PENDING -R "$GITHUB_REPOSITORY" -b "true"

.github/workflows/ci-poller.yml

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
name: CI Status Poller
2+
3+
on:
4+
schedule:
5+
- cron: "*/5 * * * *"
6+
7+
permissions:
8+
contents: read
9+
issues: read
10+
actions: write
11+
12+
jobs:
13+
check-ci:
14+
runs-on: ubuntu-latest
15+
# Skip entirely (no runner provisioned) when there's nothing to check.
16+
# The CI_POLLER_HAS_PENDING variable is set to "true" by ci-pending.yml
17+
# and reset to "false" here when all pending issues are resolved.
18+
if: vars.CI_POLLER_HAS_PENDING == 'true'
19+
concurrency:
20+
group: ci-status-poller
21+
cancel-in-progress: false
22+
steps:
23+
- name: Get auth token
24+
id: token
25+
uses: actions/create-github-app-token@v2.2.1
26+
with:
27+
app-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
28+
private-key: ${{ secrets.SENTRY_INTERNAL_APP_PRIVATE_KEY }}
29+
30+
- name: Check CI status for ci-pending issues
31+
env:
32+
# Use the app token so label changes trigger publish.yml
33+
# (GITHUB_TOKEN events are suppressed by GitHub).
34+
GH_TOKEN: ${{ steps.token.outputs.token }}
35+
run: |
36+
# Find all open issues with ci-pending label (include body to extract commit SHA)
37+
issues=$(gh issue list -R "$GITHUB_REPOSITORY" \
38+
--state open \
39+
--label ci-pending \
40+
--limit 200 \
41+
--json number,title,labels,body)
42+
43+
count=$(echo "$issues" | jq length)
44+
if [[ "$count" == "0" ]]; then
45+
echo "No ci-pending issues found. Disabling poller."
46+
gh variable set CI_POLLER_HAS_PENDING -R "$GITHUB_REPOSITORY" -b "false"
47+
exit 0
48+
fi
49+
echo "Found ${count} ci-pending issue(s)."
50+
51+
# Check each issue's CI status
52+
echo "$issues" | jq -c '.[]' | while read -r issue; do
53+
number=$(echo "$issue" | jq -r '.number')
54+
title=$(echo "$issue" | jq -r '.title')
55+
body=$(echo "$issue" | jq -r '.body')
56+
has_accepted=$(echo "$issue" | jq '[.labels[].name] | any(. == "accepted")')
57+
58+
# Parse repo and version from title: "publish: owner/repo@version"
59+
repo=$(echo "$title" | sed -n 's/^publish: \(.*\)@.*/\1/p')
60+
version=$(echo "$title" | sed -n 's/^publish: .*@\(.*\)/\1/p')
61+
62+
if [[ -z "$repo" || -z "$version" ]]; then
63+
echo "::warning::Could not parse repo/version from issue #${number}: ${title}"
64+
continue
65+
fi
66+
67+
# Extract the commit SHA from the "View check runs" link in the issue body.
68+
# The link format is: https://github.com/{owner}/{repo}/commit/{SHA}/checks/
69+
# This avoids hard-coding the release branch name, which repos can customize.
70+
sha=$(echo "$body" | grep -oP '(?<=commit/)[0-9a-f]{40}(?=/checks)' || true)
71+
72+
if [[ -z "$sha" ]]; then
73+
echo "::warning::Could not extract commit SHA from issue #${number} body, skipping."
74+
continue
75+
fi
76+
77+
echo "Checking CI for ${repo}@${version} commit ${sha:0:8} (issue #${number})..."
78+
79+
# Check combined commit status ("pending" means statuses exist but
80+
# some haven't resolved yet; it does NOT mean "no statuses reported")
81+
commit_status=$(gh api "repos/${repo}/commits/${sha}/status" \
82+
--jq '.state' 2>/dev/null || echo "error")
83+
total_statuses=$(gh api "repos/${repo}/commits/${sha}/status" \
84+
--jq '.total_count' 2>/dev/null || echo "0")
85+
86+
if [[ "$commit_status" == "error" ]]; then
87+
echo " Could not fetch commit status for ${repo}@${sha:0:8}, skipping."
88+
continue
89+
fi
90+
91+
# Fetch all check runs (paginate to handle repos with >30 checks).
92+
# --paginate --jq applies the filter per-page, so we flatten with
93+
# '.check_runs[]' and count with a second jq pass.
94+
all_checks=$(gh api --paginate "repos/${repo}/commits/${sha}/check-runs" \
95+
--jq '.check_runs[]' 2>/dev/null || true)
96+
97+
total_checks=$(echo "$all_checks" | jq -s 'length')
98+
pending_checks=$(echo "$all_checks" | jq -s '[.[] | select(.status != "completed")] | length')
99+
failed_checks=$(echo "$all_checks" | jq -s '[.[] | select(.conclusion == "failure")] | length')
100+
101+
echo " commit_status=${commit_status} (${total_statuses} statuses) pending_checks=${pending_checks} failed_checks=${failed_checks} total_checks=${total_checks}"
102+
103+
# Require at least one check run or one commit status to exist —
104+
# otherwise CI hasn't started yet and all counts would be 0.
105+
if [[ "$total_checks" == "0" && "$total_statuses" == "0" ]]; then
106+
echo " No check runs or commit statuses found — CI may not have started yet."
107+
continue
108+
fi
109+
110+
# CI is ready when:
111+
# - commit status is "success" or no statuses were reported (some
112+
# repos use only check runs, not commit statuses)
113+
# - all check runs are completed (none pending)
114+
# - no check runs have failed
115+
status_ok=false
116+
if [[ "$commit_status" == "success" ]]; then
117+
status_ok=true
118+
elif [[ "$total_statuses" == "0" ]]; then
119+
# No commit statuses reported — repo uses only check runs
120+
status_ok=true
121+
fi
122+
123+
if [[ "$status_ok" == "true" \
124+
&& "$pending_checks" == "0" && "$failed_checks" == "0" ]]; then
125+
126+
echo " CI passed! Adding ci-ready label."
127+
gh issue edit "$number" -R "$GITHUB_REPOSITORY" \
128+
--remove-label "ci-pending" \
129+
--add-label "ci-ready"
130+
131+
if [[ "$has_accepted" == "true" ]]; then
132+
comment="CI checks passed for ${repo}@${version}. Publishing is starting now."
133+
else
134+
comment="CI checks passed for ${repo}@${version}. Publishing will start once the **accepted** label is also present."
135+
fi
136+
gh issue comment "$number" -R "$GITHUB_REPOSITORY" --body "$comment"
137+
fi
138+
done
139+
140+
# Re-check if there are still pending issues; disable poller if none remain
141+
remaining=$(gh issue list -R "$GITHUB_REPOSITORY" \
142+
--state open \
143+
--label ci-pending \
144+
--limit 1 \
145+
--json number -q 'length')
146+
if [[ "$remaining" == "0" ]]; then
147+
echo "All ci-pending issues resolved. Disabling poller."
148+
gh variable set CI_POLLER_HAS_PENDING -R "$GITHUB_REPOSITORY" -b "false"
149+
fi

.github/workflows/ci-ready.yml

Lines changed: 0 additions & 104 deletions
This file was deleted.

0 commit comments

Comments
 (0)