|
| 1 | +name: Required CI Gates |
| 2 | + |
| 3 | +on: |
| 4 | + pull_request_target: |
| 5 | + types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] |
| 6 | + workflow_run: |
| 7 | + workflows: |
| 8 | + - Branch Checks |
| 9 | + - Branch E2E Checks |
| 10 | + - GPU Test |
| 11 | + - Branch Kubernetes E2E |
| 12 | + - Helm Lint |
| 13 | + types: [completed] |
| 14 | + |
| 15 | +permissions: |
| 16 | + actions: read |
| 17 | + contents: read |
| 18 | + pull-requests: read |
| 19 | + statuses: write |
| 20 | + |
| 21 | +concurrency: |
| 22 | + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.workflow_run.head_sha || github.run_id }} |
| 23 | + cancel-in-progress: true |
| 24 | + |
| 25 | +jobs: |
| 26 | + publish: |
| 27 | + name: Publish required CI gate statuses |
| 28 | + runs-on: ubuntu-latest |
| 29 | + steps: |
| 30 | + - name: Evaluate required CI gates |
| 31 | + env: |
| 32 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 33 | + GH_REPO: ${{ github.repository }} |
| 34 | + EVENT_NAME: ${{ github.event_name }} |
| 35 | + PR_NUMBER_FROM_EVENT: ${{ github.event.pull_request.number }} |
| 36 | + PR_HEAD_SHA_FROM_EVENT: ${{ github.event.pull_request.head.sha }} |
| 37 | + PR_LABELS_FROM_EVENT: ${{ toJSON(github.event.pull_request.labels.*.name) }} |
| 38 | + WORKFLOW_RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} |
| 39 | + WORKFLOW_RUN_EVENT: ${{ github.event.workflow_run.event }} |
| 40 | + shell: bash |
| 41 | + run: | |
| 42 | + set -euo pipefail |
| 43 | +
|
| 44 | + post_status() { |
| 45 | + local context="$1" |
| 46 | + local state="$2" |
| 47 | + local description="$3" |
| 48 | + local target_url="${4:-}" |
| 49 | +
|
| 50 | + args=( |
| 51 | + --method POST |
| 52 | + "repos/$GH_REPO/statuses/$HEAD_SHA" |
| 53 | + -f "state=$state" |
| 54 | + -f "context=$context" |
| 55 | + -f "description=$description" |
| 56 | + ) |
| 57 | + if [ -n "$target_url" ]; then |
| 58 | + args+=(-f "target_url=$target_url") |
| 59 | + fi |
| 60 | +
|
| 61 | + echo "$context: $state - $description" |
| 62 | + gh api "${args[@]}" >/dev/null |
| 63 | + } |
| 64 | +
|
| 65 | + has_label() { |
| 66 | + local label="$1" |
| 67 | + jq -e --arg label "$label" 'index($label) != null' <<< "$LABELS_JSON" >/dev/null |
| 68 | + } |
| 69 | +
|
| 70 | + resolve_pull_request_event() { |
| 71 | + PR_NUMBER="$PR_NUMBER_FROM_EVENT" |
| 72 | + HEAD_SHA="$PR_HEAD_SHA_FROM_EVENT" |
| 73 | + LABELS_JSON=$(jq -c . <<< "$PR_LABELS_FROM_EVENT") |
| 74 | + } |
| 75 | +
|
| 76 | + resolve_workflow_run_event() { |
| 77 | + if [ "$WORKFLOW_RUN_EVENT" != "push" ]; then |
| 78 | + echo "Ignoring workflow_run from event '$WORKFLOW_RUN_EVENT'." |
| 79 | + exit 0 |
| 80 | + fi |
| 81 | +
|
| 82 | + local associated_prs pr |
| 83 | + associated_prs=$(gh api "repos/$GH_REPO/commits/$WORKFLOW_RUN_HEAD_SHA/pulls") |
| 84 | + pr=$(jq -c 'map(select(.state == "open"))[0] // empty' <<< "$associated_prs") |
| 85 | + if [ -z "$pr" ]; then |
| 86 | + echo "No open PR associated with $WORKFLOW_RUN_HEAD_SHA; nothing to publish." |
| 87 | + exit 0 |
| 88 | + fi |
| 89 | +
|
| 90 | + PR_NUMBER=$(jq -r '.number' <<< "$pr") |
| 91 | + pr=$(gh api "repos/$GH_REPO/pulls/$PR_NUMBER") |
| 92 | + HEAD_SHA=$(jq -r '.head.sha' <<< "$pr") |
| 93 | + LABELS_JSON=$(gh api "repos/$GH_REPO/issues/$PR_NUMBER" --jq '[.labels[].name]') |
| 94 | + } |
| 95 | +
|
| 96 | + resolve_context() { |
| 97 | + if [ "$EVENT_NAME" = "pull_request_target" ]; then |
| 98 | + resolve_pull_request_event |
| 99 | + elif [ "$EVENT_NAME" = "workflow_run" ]; then |
| 100 | + resolve_workflow_run_event |
| 101 | + else |
| 102 | + echo "Unsupported event '$EVENT_NAME'." |
| 103 | + exit 1 |
| 104 | + fi |
| 105 | +
|
| 106 | + PR_URL="https://github.com/$GH_REPO/pull/$PR_NUMBER" |
| 107 | + MIRROR_REF="pull-request/$PR_NUMBER" |
| 108 | + } |
| 109 | +
|
| 110 | + verify_mirror() { |
| 111 | + local context="$1" |
| 112 | + local mirror_sha |
| 113 | +
|
| 114 | + mirror_sha=$(gh api "repos/$GH_REPO/branches/$MIRROR_REF" --jq '.commit.sha' 2>/dev/null || true) |
| 115 | + if [ -z "$mirror_sha" ]; then |
| 116 | + post_status "$context" pending "Waiting for /ok to test mirror" "$PR_URL" |
| 117 | + return 1 |
| 118 | + fi |
| 119 | +
|
| 120 | + if [ "$mirror_sha" != "$HEAD_SHA" ]; then |
| 121 | + post_status "$context" pending "Waiting for /ok to test mirror" "$PR_URL" |
| 122 | + return 1 |
| 123 | + fi |
| 124 | +
|
| 125 | + return 0 |
| 126 | + } |
| 127 | +
|
| 128 | + evaluate_workflow() { |
| 129 | + local context="$1" |
| 130 | + local workflow_file="$2" |
| 131 | + local workflow_name="$3" |
| 132 | + local required_label="${4:-}" |
| 133 | + local workflow_url="https://github.com/$GH_REPO/actions/workflows/$workflow_file" |
| 134 | +
|
| 135 | + if [ -n "$required_label" ] && ! has_label "$required_label"; then |
| 136 | + post_status "$context" success "$required_label not applied" "$PR_URL" |
| 137 | + return 0 |
| 138 | + fi |
| 139 | +
|
| 140 | + if ! verify_mirror "$context"; then |
| 141 | + return 0 |
| 142 | + fi |
| 143 | +
|
| 144 | + local runs latest run_id status conclusion run_url real_success |
| 145 | + runs=$(gh api "repos/$GH_REPO/actions/workflows/$workflow_file/runs?head_sha=$HEAD_SHA&event=push" --jq '.workflow_runs') |
| 146 | + latest=$(jq -c --arg branch "$MIRROR_REF" '[.[] | select(.head_branch == $branch)] | sort_by(.created_at) | reverse | .[0] // empty' <<< "$runs") |
| 147 | +
|
| 148 | + if [ -z "$latest" ]; then |
| 149 | + post_status "$context" pending "Waiting for $workflow_name" "$workflow_url" |
| 150 | + return 0 |
| 151 | + fi |
| 152 | +
|
| 153 | + run_id=$(jq -r '.id' <<< "$latest") |
| 154 | + status=$(jq -r '.status' <<< "$latest") |
| 155 | + conclusion=$(jq -r '.conclusion' <<< "$latest") |
| 156 | + run_url=$(jq -r '.html_url' <<< "$latest") |
| 157 | +
|
| 158 | + if [ "$status" != "completed" ]; then |
| 159 | + post_status "$context" pending "$workflow_name is $status" "$run_url" |
| 160 | + return 0 |
| 161 | + fi |
| 162 | +
|
| 163 | + if [ "$conclusion" != "success" ]; then |
| 164 | + post_status "$context" failure "$workflow_name concluded $conclusion" "$run_url" |
| 165 | + return 0 |
| 166 | + fi |
| 167 | +
|
| 168 | + real_success=$(gh api "repos/$GH_REPO/actions/runs/$run_id/jobs?per_page=100" \ |
| 169 | + --jq '[.jobs[] | select(.conclusion == "success" and .name != "Resolve PR metadata")] | length') |
| 170 | +
|
| 171 | + if [ "$real_success" -lt 1 ]; then |
| 172 | + post_status "$context" failure "No real CI jobs ran" "$run_url" |
| 173 | + return 0 |
| 174 | + fi |
| 175 | +
|
| 176 | + post_status "$context" success "$workflow_name passed" "$run_url" |
| 177 | + } |
| 178 | +
|
| 179 | + resolve_context |
| 180 | +
|
| 181 | + evaluate_workflow "OpenShell / Branch Checks" "branch-checks.yml" "Branch Checks" |
| 182 | + evaluate_workflow "OpenShell / E2E" "branch-e2e.yml" "Branch E2E Checks" "test:e2e" |
| 183 | + evaluate_workflow "OpenShell / GPU E2E" "test-gpu.yml" "GPU Test" "test:e2e-gpu" |
| 184 | + evaluate_workflow "OpenShell / Kubernetes E2E" "branch-kubernetes-e2e.yml" "Branch Kubernetes E2E" "test:e2e-kubernetes" |
| 185 | + evaluate_workflow "OpenShell / Helm Lint" "helm-lint.yml" "Helm Lint" |
0 commit comments