Skip to content

Commit a99ba08

Browse files
committed
chore: workflow success checks
1 parent 367b773 commit a99ba08

6 files changed

Lines changed: 307 additions & 4 deletions

File tree

.github/workflows/build-actions-runner.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,18 @@ jobs:
5252
push: ${{ !github.event.pull_request.head.repo.fork && steps.metadata.outputs.tags != '' }}
5353
tags: ${{ steps.metadata.outputs.tags }}
5454
labels: ${{ steps.metadata.outputs.labels }}
55+
56+
workflow-success:
57+
name: Build Actions Runner Workflow Success
58+
runs-on: ubuntu-latest
59+
needs: [build_and_push]
60+
if: always()
61+
steps:
62+
- name: Checkout repository
63+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
64+
with:
65+
persist-credentials: false
66+
- name: Check Workflow Status
67+
uses: immich-app/devtools/actions/success-check@6b81b1572e466f7f48ba3c823159ce3f4a4d66a6
68+
with:
69+
needs: ${{ toJson(needs) }}

.github/workflows/flux-diff.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,27 @@ jobs:
3939
```diff
4040
${{ steps.diff.outputs.diff }}
4141
```
42+
43+
workflow-success:
44+
name: Flux Diff Workflow Success
45+
runs-on: ubuntu-latest
46+
needs: [flux-diff]
47+
if: always()
48+
steps:
49+
- name: Checkout repository
50+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
51+
with:
52+
persist-credentials: false
53+
- name: Check Workflow Status
54+
uses: immich-app/devtools/actions/success-check@6b81b1572e466f7f48ba3c823159ce3f4a4d66a6
55+
with:
56+
needs: ${{ toJson(needs) }}
57+
- name: Create Check
58+
uses: LouisBrunner/checks-action@v2.0.0
59+
if: always()
60+
with:
61+
token: ${{ secrets.GITHUB_TOKEN }}
62+
name: Test XYZ
63+
conclusion: ${{ job.status }}
64+
output: |
65+
{"summary":"${{ job.status }}"}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
name: Overall Pipeline Status Check
2+
3+
on:
4+
# This workflow can be triggered manually or on a schedule if desired,
5+
# but its primary design here is to be triggered by workflow_dispatch
6+
# or potentially by a push to main if you want to check status after merges.
7+
# workflow_dispatch: # Allows manual triggering
8+
workflow_run:
9+
workflows: [Zizmor, Terragrunt, Scripts]
10+
branches-ignore:
11+
- "main"
12+
types:
13+
- completed
14+
# push:
15+
# branches: [main]
16+
# # paths: # Consider path filtering if you only want to run this for specific changes
17+
# # - '.github/workflows/**' # Example: run if any workflow changes
18+
# pull_request:
19+
# branches:
20+
# - main
21+
permissions:
22+
checks: read # Required to read check runs
23+
actions: read # Required to list workflow runs (fallback or additional info)
24+
contents: read # Required to checkout the repository
25+
26+
jobs:
27+
check_all_workflow_success_jobs:
28+
runs-on: ubuntu-latest
29+
outputs:
30+
overall_status_message: ${{ steps.evaluate_checks.outputs.overall_status_message }}
31+
all_checks_successful: ${{ steps.evaluate_checks.outputs.all_checks_successful }}
32+
steps:
33+
- name: Checkout Repository
34+
uses: actions/checkout@v4 # Use a more recent version
35+
with:
36+
# No need to persist credentials for this read-only operation
37+
persist-credentials: false
38+
39+
- name: Verify Workflow Success Checks via GitHub Checks API
40+
id: evaluate_checks
41+
uses: actions/github-script@v7 # Use a more recent version
42+
env:
43+
COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }} # Handle both PR and push events
44+
EXPECTED_CHECK_NAMES_JSON: |
45+
[
46+
"Zizmor Workflow Success",
47+
"Scripts Workflow Success",
48+
"Flux Diff Workflow Success",
49+
"Terragrunt Workflow Success",
50+
"Build Actions Runner Workflow Success"
51+
]
52+
POLLING_INTERVAL_SECONDS: 8 # Interval in seconds to poll the Checks API
53+
POLLING_TIMEOUT_MINUTES: 15 # Timeout in minutes to wait for all checks
54+
with:
55+
github-token: ${{ secrets.GITHUB_TOKEN }}
56+
script: |
57+
const commit_sha = process.env.COMMIT_SHA;
58+
const expected_check_names = JSON.parse(process.env.EXPECTED_CHECK_NAMES_JSON);
59+
const polling_interval_ms = parseInt(process.env.POLLING_INTERVAL_SECONDS) * 1000;
60+
const polling_timeout_ms = parseInt(process.env.POLLING_TIMEOUT_MINUTES) * 60 * 1000;
61+
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
62+
63+
let all_successful = true;
64+
let status_summary = `Overall status for commit ${commit_sha}:\n`;
65+
let found_checks_count = 0;
66+
const relevant_checks = {};
67+
const startTime = Date.now();
68+
69+
core.info(`Starting to poll for ${expected_check_names.length} expected check runs for SHA: ${commit_sha}`);
70+
core.info(`Polling interval: ${process.env.POLLING_INTERVAL_SECONDS}s, Timeout: ${process.env.POLLING_TIMEOUT_MINUTES}m`);
71+
72+
async function getChecks() {
73+
const { data: checks } = await github.rest.checks.listForRef({
74+
owner: context.repo.owner,
75+
repo: context.repo.repo,
76+
ref: commit_sha,
77+
per_page: 100
78+
});
79+
return checks.check_runs;
80+
}
81+
82+
try {
83+
while (Date.now() - startTime < polling_timeout_ms) {
84+
const check_runs = await getChecks();
85+
let current_found_expected_checks = 0;
86+
87+
for (const check_run of check_runs) {
88+
if (expected_check_names.includes(check_run.name)) {
89+
// Store the latest completed status for each expected check name
90+
if (!relevant_checks[check_run.name] ||
91+
new Date(check_run.completed_at) > new Date(relevant_checks[check_run.name].completed_at)) {
92+
// Only consider it "found" for polling purposes if it's completed,
93+
// or if we decide to track pending ones too.
94+
// For now, let's assume we are waiting for them to appear, possibly completed.
95+
relevant_checks[check_run.name] = {
96+
conclusion: check_run.conclusion,
97+
status: check_run.status,
98+
html_url: check_run.html_url,
99+
completed_at: check_run.completed_at,
100+
name: check_run.name
101+
};
102+
}
103+
}
104+
}
105+
106+
// Count how many of the *expected* checks we have found so far
107+
current_found_expected_checks = expected_check_names.filter(name => relevant_checks[name]).length;
108+
core.info(`Poll: Found ${current_found_expected_checks}/${expected_check_names.length} expected checks so far.`);
109+
110+
if (current_found_expected_checks === expected_check_names.length) {
111+
core.info("All expected checks have been found.");
112+
break; // Exit polling loop
113+
}
114+
115+
if (Date.now() - startTime + polling_interval_ms > polling_timeout_ms) {
116+
core.warning("Polling timeout nearing, last attempt.");
117+
}
118+
await new Promise(resolve => setTimeout(resolve, polling_interval_ms));
119+
}
120+
121+
if (Object.keys(relevant_checks).length < expected_check_names.length) {
122+
status_summary += `TIMEOUT: Not all expected checks were found within ${process.env.POLLING_TIMEOUT_MINUTES} minutes.\n`;
123+
core.error(`Timeout: Expected ${expected_check_names.length} checks, but only found ${Object.keys(relevant_checks).length}.`);
124+
all_successful = false;
125+
// List missing checks
126+
for (const check_name of expected_check_names) {
127+
if (!relevant_checks[check_name]) {
128+
status_summary += ` - ${check_name}: MISSING (Not found after timeout)\n`;
129+
core.warning(`Missing expected check: ${check_name}`);
130+
}
131+
}
132+
}
133+
134+
core.info("Proceeding to evaluate conclusions of found checks.");
135+
// Evaluate conclusions of all *expected* checks, whether found or timed out
136+
for (const check_name of expected_check_names) {
137+
const check = relevant_checks[check_name];
138+
if (check) {
139+
if (check.name === check_name) { // Ensure we are evaluating the correct check from our map
140+
found_checks_count++; // This counts checks that were found and are being evaluated
141+
status_summary += ` - ${check.name}: ${check.status} - ${check.conclusion || 'pending/unknown'} (${check.html_url})\n`;
142+
if (check.status !== 'completed') {
143+
core.warning(`Check '${check.name}' is not completed (Status: ${check.status}). Treating as not successful.`);
144+
all_successful = false;
145+
} else if (check.conclusion !== 'success') {
146+
if (check.conclusion === 'skipped' || check.conclusion === 'neutral') {
147+
core.info(`Check '${check.name}' was ${check.conclusion}, considering it acceptable.`);
148+
} else {
149+
all_successful = false;
150+
core.warning(`Check '${check.name}' did not succeed (Conclusion: ${check.conclusion}).`);
151+
}
152+
}
153+
}
154+
} else if (all_successful) {
155+
// If it wasn't found due to timeout, it's already handled and all_successful is false.
156+
// This part is more of a safeguard if a check was expected but somehow not in relevant_checks after polling completed.
157+
status_summary += ` - ${check_name}: Not found (Logic error or not reported by API)\n`;
158+
all_successful = false;
159+
core.warning(`Expected check '${check_name}' was not in the final list for evaluation.`);
160+
}
161+
}
162+
163+
if (found_checks_count < expected_check_names.length && all_successful) {
164+
// This case should ideally be covered by the timeout logic making all_successful false.
165+
status_summary += `Warning: Some expected checks were not evaluated (${found_checks_count}/${expected_check_names.length}).\n`;
166+
all_successful = false;
167+
}
168+
169+
} catch (error) {
170+
core.error(`Error during polling or processing check runs: ${error}`);
171+
status_summary += ` - Error during script execution: ${error.message}\n`;
172+
all_successful = false;
173+
}
174+
175+
core.setOutput('overall_status_message', status_summary);
176+
core.setOutput('all_checks_successful', all_successful.toString());
177+
178+
core.summary.addRaw(status_summary);
179+
await core.summary.write();
180+
181+
if (!all_successful) {
182+
core.setFailed('One or more required workflow success checks have not succeeded, are missing, or timed out.');
183+
}
184+
185+
report_final_status:
186+
runs-on: ubuntu-latest
187+
needs: check_all_workflow_success_jobs
188+
if: always() # Always run to report the outcome
189+
steps:
190+
- name: Report Overall Pipeline Status
191+
run: |
192+
echo "Overall Pipeline Evaluation Complete."
193+
echo "Summary from previous job:"
194+
echo "${{ needs.check_all_workflow_success_jobs.outputs.overall_status_message }}"
195+
echo "All checks successful: ${{ needs.check_all_workflow_success_jobs.outputs.all_checks_successful }}"
196+
if [[ "${{ needs.check_all_workflow_success_jobs.outputs.all_checks_successful }}" != "true" ]]; then
197+
echo "Pipeline has issues or one or more checks did not succeed."
198+
# exit 1 # Optionally fail this job too, check_all_workflow_success_jobs already fails
199+
else
200+
echo "All checks reported success! Pipeline is green."
201+
fi

.github/workflows/scripts.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,27 @@ jobs:
7373
- name: Run tsc
7474
run: npm run check
7575
if: ${{ !cancelled() }}
76+
77+
workflow-success:
78+
name: Scripts Workflow Success
79+
runs-on: ubuntu-latest
80+
needs: [pre-job, scripts-unit-tests]
81+
if: always()
82+
steps:
83+
- name: Checkout repository
84+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
85+
with:
86+
persist-credentials: false
87+
- name: Check Workflow Status
88+
uses: immich-app/devtools/actions/success-check@6b81b1572e466f7f48ba3c823159ce3f4a4d66a6
89+
with:
90+
needs: ${{ toJson(needs) }}
91+
- name: Create Check
92+
uses: LouisBrunner/checks-action@v2.0.0
93+
if: always()
94+
with:
95+
token: ${{ secrets.GITHUB_TOKEN }}
96+
name: Test XYZ
97+
conclusion: ${{ job.status }}
98+
output: |
99+
{"summary":"${{ job.status }}"}

.github/workflows/terragrunt.yaml

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ on:
33
workflow_dispatch:
44
pull_request:
55
branches: [ "main" ]
6-
paths:
7-
- "tf/**"
8-
- ".github/workflows/terragrunt.yaml"
9-
- ".mise/config.toml"
6+
# paths:
7+
# - "tf/**"
8+
# - ".github/workflows/terragrunt.yaml"
9+
# - ".mise/config.toml"
1010
push:
1111
branches: [ "main" ]
1212
paths:
@@ -131,3 +131,27 @@ jobs:
131131
run: |
132132
mise run tf init -reconfigure
133133
mise run tf apply --terragrunt-non-interactive
134+
135+
workflow-success:
136+
name: Terragrunt Workflow Success
137+
runs-on: ubuntu-latest
138+
needs: [plan, deploy]
139+
if: always()
140+
steps:
141+
- name: Checkout repository
142+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
143+
with:
144+
persist-credentials: false
145+
- name: Check Workflow Status
146+
uses: immich-app/devtools/actions/success-check@6b81b1572e466f7f48ba3c823159ce3f4a4d66a6
147+
with:
148+
needs: ${{ toJson(needs) }}
149+
- name: Create Check
150+
uses: LouisBrunner/checks-action@v2.0.0
151+
if: always()
152+
with:
153+
token: ${{ secrets.GITHUB_TOKEN }}
154+
name: Test XYZ
155+
conclusion: ${{ job.status }}
156+
output: |
157+
{"summary":"${{ steps.test.outputs.summary }}"}

.github/workflows/zizmor.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,18 @@ jobs:
3737
with:
3838
sarif_file: results.sarif
3939
category: zizmor
40+
41+
workflow-success:
42+
name: Zizmor Workflow Success
43+
runs-on: ubuntu-latest
44+
needs: [zizmor]
45+
if: always()
46+
steps:
47+
- name: Checkout repository
48+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
49+
with:
50+
persist-credentials: false
51+
- name: Check Workflow Status
52+
uses: immich-app/devtools/actions/success-check@6b81b1572e466f7f48ba3c823159ce3f4a4d66a6
53+
with:
54+
needs: ${{ toJson(needs) }}

0 commit comments

Comments
 (0)