mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
408052adc8
Fix the main-branch promotion gate to evaluate required status contexts from branch protection instead of all check runs, preventing self-blocking while the validator job is still in progress.
114 lines
3.9 KiB
YAML
114 lines
3.9 KiB
YAML
name: Validate Main Green CI
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
branch:
|
|
description: "Branch to validate"
|
|
required: false
|
|
default: main
|
|
type: string
|
|
workflow_call:
|
|
inputs:
|
|
branch:
|
|
required: false
|
|
default: main
|
|
type: string
|
|
|
|
jobs:
|
|
validate:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
checks: read
|
|
statuses: read
|
|
steps:
|
|
- name: validate branch checks are green
|
|
uses: actions/github-script@v7
|
|
env:
|
|
TARGET_BRANCH: ${{ inputs.branch || github.event.inputs.branch || 'main' }}
|
|
with:
|
|
script: |
|
|
const owner = context.repo.owner;
|
|
const repo = context.repo.repo;
|
|
const branch = process.env.TARGET_BRANCH || "main";
|
|
|
|
const branchResp = await github.rest.repos.getBranch({ owner, repo, branch });
|
|
const sha = branchResp.data.commit.sha;
|
|
core.info(`Validating checks for ${branch} @ ${sha}`);
|
|
|
|
let requiredContexts = [];
|
|
try {
|
|
const protection = await github.rest.repos.getBranchProtection({
|
|
owner,
|
|
repo,
|
|
branch,
|
|
});
|
|
requiredContexts = protection.data.required_status_checks?.contexts || [];
|
|
} catch (e) {
|
|
core.warning(`Could not read branch protection for ${branch}: ${e.message}`);
|
|
}
|
|
|
|
const combined = await github.rest.repos.getCombinedStatusForRef({
|
|
owner,
|
|
repo,
|
|
ref: sha,
|
|
});
|
|
const statusState = combined.data.state;
|
|
const statusCount = combined.data.statuses.length;
|
|
core.info(`Combined status state: ${statusState} (${statusCount} status contexts)`);
|
|
|
|
const checks = await github.paginate(github.rest.checks.listForRef, {
|
|
owner,
|
|
repo,
|
|
ref: sha,
|
|
per_page: 100,
|
|
});
|
|
|
|
if (checks.length === 0 && statusCount === 0) {
|
|
core.setFailed(`No checks/statuses found for ${branch}. Refusing promotion.`);
|
|
return;
|
|
}
|
|
|
|
if (requiredContexts.length > 0) {
|
|
core.info(`Required contexts from branch protection: ${requiredContexts.join(", ")}`);
|
|
const statusByContext = new Map(combined.data.statuses.map((s) => [s.context, s.state]));
|
|
const failures = [];
|
|
for (const ctx of requiredContexts) {
|
|
const state = statusByContext.get(ctx);
|
|
if (!state) {
|
|
failures.push(`${ctx}: missing`);
|
|
continue;
|
|
}
|
|
if (state !== "success") {
|
|
failures.push(`${ctx}: ${state}`);
|
|
}
|
|
}
|
|
if (failures.length > 0) {
|
|
core.setFailed(`Required checks for ${branch} are not green:\n${failures.join("\n")}`);
|
|
return;
|
|
}
|
|
core.info(`All required checks for ${branch} are green.`);
|
|
return;
|
|
}
|
|
|
|
core.warning("No required status contexts configured; using fallback all-check validation.");
|
|
const badChecks = checks.filter((c) => {
|
|
if (c.status !== "completed") return true;
|
|
return !["success", "neutral", "skipped"].includes(c.conclusion || "");
|
|
});
|
|
|
|
if (badChecks.length > 0) {
|
|
const details = badChecks.map((c) => `${c.name}: status=${c.status}, conclusion=${c.conclusion}`).join("\n");
|
|
core.setFailed(`Branch ${branch} is not green.\n${details}`);
|
|
return;
|
|
}
|
|
|
|
if (!["success", "neutral"].includes(statusState)) {
|
|
core.setFailed(`Combined status is ${statusState} for ${branch}.`);
|
|
return;
|
|
}
|
|
|
|
core.info(`Branch ${branch} is green.`);
|