PR #1301 · internal CI dispatch #1372
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
|
Check warning on line 1 in .github/workflows/internal-ci-bridge.yml
|
||
| # SPDX-License-Identifier: Apache-2.0 | ||
| name: TensorRT-Model-Connect Internal CI Bridge | ||
| run-name: >- | ||
| PR #${{ github.event.pull_request.number || inputs.pr_number }} · internal CI dispatch | ||
| on: | ||
| pull_request_target: | ||
| branches: | ||
| - main | ||
| types: [labeled] | ||
| workflow_dispatch: | ||
| inputs: | ||
| pr_number: | ||
| description: Open pull request number to test | ||
| required: true | ||
| type: string | ||
| permissions: {} | ||
| concurrency: | ||
| group: >- | ||
| trtmc-private-dispatch-${{ github.event.pull_request.number || inputs.pr_number }} | ||
| cancel-in-progress: false | ||
| jobs: | ||
| authorize: | ||
| name: Authorize trusted trigger | ||
| if: >- | ||
| github.repository == 'NVIDIA/TensorRT-Model-Connect' && | ||
| ( | ||
| github.event_name == 'workflow_dispatch' || | ||
| ( | ||
| github.event_name == 'pull_request_target' && | ||
| github.ref == 'refs/heads/main' && | ||
| github.event.label.name == 'run-internal-ci' | ||
| ) | ||
| ) | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 5 | ||
| permissions: | ||
| actions: read | ||
| contents: read | ||
| pull-requests: write | ||
| outputs: | ||
| pr_number: ${{ steps.snapshot.outputs.pr_number }} | ||
| head_sha: ${{ steps.snapshot.outputs.head_sha }} | ||
| base_sha: ${{ steps.snapshot.outputs.base_sha }} | ||
| steps: | ||
| # This trusted workflow reads PR metadata only. It never checks out or | ||
| # executes the pull-request head. | ||
| - name: Capture the exact pull-request snapshot | ||
| id: snapshot | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| ACTOR: ${{ github.actor }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }} | ||
| EVENT_NAME: ${{ github.event_name }} | ||
| EVENT_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] | ||
| actor_role="$( | ||
| gh api --method GET \ | ||
| "/repos/$GITHUB_REPOSITORY/collaborators/$ACTOR/permission" \ | ||
| --jq '.role_name' | ||
| )" | ||
| case "$actor_role" in | ||
| maintain|admin) ;; | ||
| *) | ||
| echo "::error::Only actors with maintain or admin access may dispatch CI." | ||
| exit 1 | ||
| ;; | ||
| esac | ||
| echo "trigger_authorized=true" >> "$GITHUB_OUTPUT" | ||
| if [ "$EVENT_NAME" = "pull_request_target" ]; then | ||
| [[ "$EVENT_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]] | ||
| fi | ||
| pull="$(gh api --method GET "/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")" | ||
| state="$(jq -er '.state' <<<"$pull")" | ||
| base_repo="$(jq -er '.base.repo.full_name' <<<"$pull")" | ||
| base_ref="$(jq -er '.base.ref' <<<"$pull")" | ||
| head_sha="$(jq -er '.head.sha' <<<"$pull")" | ||
| merge_sha="$(jq -r '.merge_commit_sha // empty' <<<"$pull")" | ||
| test "$state" = "open" | ||
| test "$base_repo" = "$GITHUB_REPOSITORY" | ||
| test "$base_ref" = "main" | ||
| [[ "$head_sha" =~ ^[0-9a-f]{40}$ ]] | ||
| [[ "$merge_sha" =~ ^[0-9a-f]{40}$ ]] || { | ||
| echo "::error::Pull request #$PR_NUMBER has no testable merge commit." | ||
| exit 1 | ||
| } | ||
| if [ "$EVENT_NAME" = "pull_request_target" ] \ | ||
| && [ "$head_sha" != "$EVENT_HEAD_SHA" ]; then | ||
| echo "::error::The CI trigger was superseded by a newer PR head." | ||
| exit 1 | ||
| fi | ||
| merge="$(gh api --method GET "/repos/$GITHUB_REPOSITORY/git/commits/$merge_sha")" | ||
| resolved_merge="$(jq -er '.sha' <<<"$merge")" | ||
| parent_count="$(jq -er '.parents | length' <<<"$merge")" | ||
| base_sha="$(jq -er '.parents[0].sha' <<<"$merge")" | ||
| merge_head="$(jq -er '.parents[1].sha' <<<"$merge")" | ||
| merge_tree="$(jq -er '.tree.sha' <<<"$merge")" | ||
| test "$resolved_merge" = "$merge_sha" | ||
| test "$parent_count" = "2" | ||
| test "$merge_head" = "$head_sha" | ||
| [[ "$base_sha" =~ ^[0-9a-f]{40}$ ]] | ||
| [[ "$merge_tree" =~ ^[0-9a-f]{40}$ ]] | ||
| # CPU success belongs to the exact PR head. Main may advance after | ||
| # CPU finishes; accept an older tested base in the authorized base's | ||
| # history. Internal CI resolves and pins its own merge snapshot. | ||
| if ! community_ci_runs="$( | ||
| gh api --method GET \ | ||
| "/repos/$GITHUB_REPOSITORY/actions/workflows/community-ci.yml/runs?event=workflow_dispatch&branch=main&per_page=100" | ||
| )"; then | ||
| echo "::error::Unable to query Community CI runs for PR head $head_sha." | ||
| exit 1 | ||
| fi | ||
| community_ci_title_prefix="Stable Community CI · PR #$PR_NUMBER · head $head_sha · merge " | ||
| community_ci_run="" | ||
| community_cpu_job="" | ||
| cpu_reason="No eligible Community CI run was found." | ||
| if [ -n "$community_ci_runs" ]; then | ||
| community_ci_candidates="$( | ||
| jq -r \ | ||
| --arg title_prefix "$community_ci_title_prefix" \ | ||
| '.workflow_runs | ||
| | map( | ||
| select(.event == "workflow_dispatch" and .head_branch == "main") | ||
| | select(.display_title | startswith($title_prefix)) | ||
| | { | ||
| id, | ||
| updated_at, | ||
| merge_sha: (.display_title | ltrimstr($title_prefix)) | ||
| } | ||
| ) | ||
| | sort_by(.updated_at) | ||
| | reverse | ||
| | .[] | ||
| | [.id, .merge_sha] | ||
| | @tsv' \ | ||
| <<<"$community_ci_runs" | ||
| )" | ||
| while IFS=$'\t' read -r candidate_run candidate_merge_sha; do | ||
| [[ "$candidate_run" =~ ^[1-9][0-9]*$ ]] || continue | ||
| [[ "$candidate_merge_sha" =~ ^[0-9a-f]{40}$ ]] || continue | ||
| if ! candidate_merge="$( | ||
| gh api --method GET \ | ||
| "/repos/$GITHUB_REPOSITORY/git/commits/$candidate_merge_sha" | ||
| )"; then | ||
| echo "::error::Unable to read the merge snapshot for Community CI run $candidate_run." | ||
| exit 1 | ||
| fi | ||
| candidate_resolved="$(jq -r '.sha // empty' <<<"$candidate_merge")" | ||
| candidate_parent_count="$(jq -r '.parents | length' <<<"$candidate_merge")" | ||
| candidate_base="$(jq -r '.parents[0].sha // empty' <<<"$candidate_merge")" | ||
| candidate_head="$(jq -r '.parents[1].sha // empty' <<<"$candidate_merge")" | ||
| candidate_tree="$(jq -r '.tree.sha // empty' <<<"$candidate_merge")" | ||
| if [ "$candidate_resolved" != "$candidate_merge_sha" ] \ | ||
| || [ "$candidate_parent_count" != "2" ] \ | ||
| || [ "$candidate_head" != "$head_sha" ]; then | ||
| continue | ||
| fi | ||
| [[ "$candidate_base" =~ ^[0-9a-f]{40}$ ]] || continue | ||
| [[ "$candidate_tree" =~ ^[0-9a-f]{40}$ ]] || continue | ||
| if [ "$candidate_base" = "$base_sha" ]; then | ||
| # Regenerating a merge with the same parents must preserve its tree. | ||
| [ "$candidate_tree" = "$merge_tree" ] || continue | ||
| else | ||
| if ! comparison="$( | ||
| gh api --method GET \ | ||
| "/repos/$GITHUB_REPOSITORY/compare/$candidate_base...$base_sha?per_page=1" | ||
| )"; then | ||
| echo "::error::Unable to verify base ancestry for Community CI run $candidate_run." | ||
| exit 1 | ||
| fi | ||
| if ! jq -e --arg base "$candidate_base" \ | ||
| '.status == "ahead" and .merge_base_commit.sha == $base' \ | ||
| <<<"$comparison" > /dev/null; then | ||
| continue | ||
| fi | ||
| fi | ||
| if ! cpu_job="$( | ||
| gh api --method GET \ | ||
| "/repos/$GITHUB_REPOSITORY/actions/runs/$candidate_run/jobs?filter=latest&per_page=100" \ | ||
| --jq '.jobs | map(select(.name == "Community CPU / Required")) | last // {}' | ||
| )"; then | ||
| echo "::error::Unable to query the CPU gate for Community CI run $candidate_run." | ||
| exit 1 | ||
| fi | ||
| cpu_status="$(jq -r '.status // "missing"' <<<"$cpu_job")" | ||
| cpu_conclusion="$(jq -r '.conclusion // "none"' <<<"$cpu_job")" | ||
| candidate_cpu_job="$(jq -r '.id // empty' <<<"$cpu_job")" | ||
| cpu_reason="Run $candidate_run: CPU gate status=$cpu_status, conclusion=$cpu_conclusion." | ||
| if [ "$cpu_status" = "completed" ] && [ "$cpu_conclusion" = "success" ] \ | ||
| && [[ "$candidate_cpu_job" =~ ^[1-9][0-9]*$ ]]; then | ||
| community_ci_run="$candidate_run" | ||
| community_cpu_job="$candidate_cpu_job" | ||
| echo "Accepted CPU run $candidate_run for head $head_sha: tested base $candidate_base, authorized base $base_sha." | ||
| break | ||
| fi | ||
| done <<<"$community_ci_candidates" | ||
| fi | ||
| if ! [[ "$community_cpu_job" =~ ^[1-9][0-9]*$ ]]; then | ||
| echo "::error::Community CPU / Required must pass on PR head $head_sha before internal CI can run. $cpu_reason" | ||
| exit 1 | ||
| fi | ||
| { | ||
| echo "pr_number=$PR_NUMBER" | ||
| echo "head_sha=$head_sha" | ||
| echo "base_sha=$base_sha" | ||
| } >> "$GITHUB_OUTPUT" | ||
| - name: Consume the trusted trigger label | ||
| if: ${{ always() && github.event_name == 'pull_request_target' && steps.snapshot.outputs.trigger_authorized == 'true' }} | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| run: | | ||
| set -euo pipefail | ||
| gh api --silent --method DELETE \ | ||
| "/repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/labels/run-internal-ci" | ||
| announce: | ||
| name: Publish pending result | ||
| needs: authorize | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 5 | ||
| permissions: | ||
| pull-requests: write | ||
| statuses: write | ||
| steps: | ||
| - name: Publish the pending automated status | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| HEAD_SHA: ${{ needs.authorize.outputs.head_sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| gh api --silent --method POST \ | ||
| "/repos/$GITHUB_REPOSITORY/statuses/$HEAD_SHA" \ | ||
| -f state=pending \ | ||
| -f context="TRTMC Internal CI / Automated premerge gate" \ | ||
| -f description="Automated internal CI is running" \ | ||
| -f target_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" | ||
| - name: Remove the previous automated failure comment | ||
| continue-on-error: true | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| PR_NUMBER: ${{ needs.authorize.outputs.pr_number }} | ||
| run: | | ||
| set -euo pipefail | ||
| gh api --paginate \ | ||
| "/repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments?per_page=100" \ | ||
| --jq '.[] | select(.user.login == "github-actions[bot]") | select(.body | contains("<!-- trtmc-internal-ci-result -->")) | .id' \ | ||
| | while read -r comment_id; do | ||
| [[ "$comment_id" =~ ^[1-9][0-9]*$ ]] || continue | ||
| gh api --silent --method DELETE \ | ||
| "/repos/$GITHUB_REPOSITORY/issues/comments/$comment_id" || true | ||
| done | ||
| dispatch: | ||
| name: Internal CI dispatch | ||
| needs: | ||
| - authorize | ||
| - announce | ||
| if: ${{ always() && needs.authorize.result == 'success' }} | ||
| runs-on: ubuntu-24.04 | ||
| environment: | ||
| name: ci-dispatch | ||
| deployment: false | ||
| timeout-minutes: 360 | ||
| permissions: | ||
| contents: read | ||
| outputs: | ||
| private_conclusion: ${{ steps.private_result.outputs.conclusion }} | ||
| steps: | ||
| - name: Dispatch internal premerge CI | ||
| id: private_run | ||
| env: | ||
| GH_TOKEN: ${{ secrets.TRTMC_CI_DISPATCH_TOKEN }} | ||
| PRIVATE_CI_OWNER: ${{ secrets.TRTMC_PRIVATE_CI_OWNER }} | ||
| PRIVATE_CI_REPOSITORY: ${{ secrets.TRTMC_PRIVATE_CI_REPOSITORY }} | ||
| PR_NUMBER: ${{ needs.authorize.outputs.pr_number }} | ||
| HEAD_SHA: ${{ needs.authorize.outputs.head_sha }} | ||
| BASE_SHA: ${{ needs.authorize.outputs.base_sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| [[ "$PRIVATE_CI_OWNER" =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,37}[A-Za-z0-9])?$ ]] | ||
| [[ "$PRIVATE_CI_REPOSITORY" =~ ^[A-Za-z0-9]([A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$ ]] | ||
| umask 077 | ||
| dispatch_nonce="$(openssl rand -hex 16)" | ||
| [[ "$dispatch_nonce" =~ ^[0-9a-f]{32}$ ]] | ||
| echo "::add-mask::$dispatch_nonce" | ||
| payload="$RUNNER_TEMP/internal-ci-dispatch.json" | ||
| trap 'rm -f "$payload"' EXIT | ||
| jq -n \ | ||
| --arg pr_number "$PR_NUMBER" \ | ||
| --arg head_sha "$HEAD_SHA" \ | ||
| --arg base_sha "$BASE_SHA" \ | ||
| --arg dispatch_nonce "$dispatch_nonce" \ | ||
| '{ | ||
| ref: "main", | ||
| inputs: { | ||
| pr_number: $pr_number, | ||
| head_sha: $head_sha, | ||
| base_sha: $base_sha, | ||
| dispatch_nonce: $dispatch_nonce | ||
| } | ||
| }' > "$payload" | ||
| gh api --silent --method POST \ | ||
| "/repos/$PRIVATE_CI_OWNER/$PRIVATE_CI_REPOSITORY/actions/workflows/premerge.yml/dispatches" \ | ||
| --input "$payload" | ||
| expected_title="Source PR #$PR_NUMBER · $HEAD_SHA · dispatch $dispatch_nonce" | ||
| run_id="" | ||
| for _ in $(seq 1 20); do | ||
| runs="$( | ||
| gh api --method GET \ | ||
| "/repos/$PRIVATE_CI_OWNER/$PRIVATE_CI_REPOSITORY/actions/workflows/premerge.yml/runs?event=workflow_dispatch&per_page=100" | ||
| )" | ||
| run_id="$( | ||
| jq -r \ | ||
| --arg title "$expected_title" \ | ||
| '[ | ||
| .workflow_runs[] | ||
| | select(.display_title == $title) | ||
| ] | ||
| | sort_by(.created_at) | ||
| | last | ||
| | .id // empty' <<<"$runs" | ||
| )" | ||
| if [[ "$run_id" =~ ^[1-9][0-9]*$ ]]; then | ||
| break | ||
| fi | ||
| sleep 3 | ||
| done | ||
| if ! [[ "$run_id" =~ ^[1-9][0-9]*$ ]]; then | ||
| echo "::error::The exact dispatched Internal CI run was not found." | ||
| exit 1 | ||
| fi | ||
| echo "::add-mask::$run_id" | ||
| { | ||
| echo "run_id=$run_id" | ||
| echo "dispatch_nonce=$dispatch_nonce" | ||
| } >> "$GITHUB_OUTPUT" | ||
| - name: Wait for the exact Internal CI result | ||
| id: private_result | ||
| env: | ||
| GH_TOKEN: ${{ secrets.TRTMC_CI_DISPATCH_TOKEN }} | ||
| PRIVATE_CI_OWNER: ${{ secrets.TRTMC_PRIVATE_CI_OWNER }} | ||
| PRIVATE_CI_REPOSITORY: ${{ secrets.TRTMC_PRIVATE_CI_REPOSITORY }} | ||
| RUN_ID: ${{ steps.private_run.outputs.run_id }} | ||
| run: | | ||
| set -euo pipefail | ||
| [[ "$RUN_ID" =~ ^[1-9][0-9]*$ ]] | ||
| # The exact private run can queue behind GPU work. Let the job-level | ||
| # timeout bound this wait instead of misreporting queue delay as a | ||
| # terminal Internal CI failure. | ||
| while true; do | ||
| run="$( | ||
| gh api --method GET \ | ||
| "/repos/$PRIVATE_CI_OWNER/$PRIVATE_CI_REPOSITORY/actions/runs/$RUN_ID" | ||
| )" | ||
| status="$(jq -er '.status' <<<"$run")" | ||
| if [ "$status" = "completed" ]; then | ||
| conclusion="$(jq -r '.conclusion // "unknown"' <<<"$run")" | ||
| case "$conclusion" in | ||
| success|failure|cancelled|timed_out|skipped|neutral|action_required) ;; | ||
| *) conclusion=unknown ;; | ||
| esac | ||
| echo "conclusion=$conclusion" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
| sleep 15 | ||
| done | ||
| publish: | ||
| name: Publish automated result | ||
| needs: | ||
| - authorize | ||
| - announce | ||
| - dispatch | ||
| if: ${{ always() && needs.authorize.result == 'success' }} | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 10 | ||
| permissions: | ||
| actions: read | ||
| pull-requests: write | ||
| statuses: write | ||
| steps: | ||
| - name: Resolve the contributor-visible result | ||
| id: result | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| PR_NUMBER: ${{ needs.authorize.outputs.pr_number }} | ||
| HEAD_SHA: ${{ needs.authorize.outputs.head_sha }} | ||
| DISPATCH_RESULT: ${{ needs.dispatch.result }} | ||
| PRIVATE_CONCLUSION: ${{ needs.dispatch.outputs.private_conclusion }} | ||
| run: | | ||
| set -euo pipefail | ||
| state=failure | ||
| description="Automated internal CI did not complete; details withheld" | ||
| publish_comment=true | ||
| # Main may advance while this exact PR head is in CI. pull.base.sha | ||
| # and the synthetic merge's first parent refresh independently and | ||
| # are not comparable snapshot identities. Authorization already | ||
| # binds the downstream run to the tested merge, so publication is | ||
| # invalidated only when the PR itself is superseded. | ||
| snapshot_current=false | ||
| if pull="$(gh api --method GET "/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")"; then | ||
| current_state="$(jq -r '.state // ""' <<<"$pull")" | ||
| current_repo="$(jq -r '.base.repo.full_name // ""' <<<"$pull")" | ||
| current_ref="$(jq -r '.base.ref // ""' <<<"$pull")" | ||
| current_head="$(jq -r '.head.sha // ""' <<<"$pull")" | ||
| if [ "$current_state" = "open" ] \ | ||
| && [ "$current_repo" = "$GITHUB_REPOSITORY" ] \ | ||
| && [ "$current_ref" = "main" ] \ | ||
| && [ "$current_head" = "$HEAD_SHA" ]; then | ||
| snapshot_current=true | ||
| elif [ "$current_head" != "$HEAD_SHA" ]; then | ||
| description="Automated internal CI result was superseded by a newer PR head" | ||
| publish_comment=false | ||
| fi | ||
| fi | ||
| if [ "$snapshot_current" = "true" ] && [ "$DISPATCH_RESULT" = "success" ]; then | ||
| case "$PRIVATE_CONCLUSION" in | ||
| success) | ||
| state=success | ||
| description="Automated internal CI passed" | ||
| publish_comment=false | ||
| ;; | ||
| failure) | ||
| description="Automated internal CI failed; details withheld" | ||
| ;; | ||
| cancelled|timed_out|skipped|neutral|action_required|unknown|"") | ||
| description="Automated internal CI did not complete; details withheld" | ||
| ;; | ||
| esac | ||
| fi | ||
| { | ||
| echo "state=$state" | ||
| echo "description=$description" | ||
| echo "publish_comment=$publish_comment" | ||
| } >> "$GITHUB_OUTPUT" | ||
| - name: Prepare the contributor-visible log and comment | ||
| if: ${{ steps.result.outputs.state != 'success' }} | ||
| env: | ||
| PUBLIC_FAILURE_LOG: ${{ runner.temp }}/contributor-result/public-failure.log | ||
| COMMENT_BODY: ${{ runner.temp }}/internal-ci-comment.md | ||
| DESCRIPTION: ${{ steps.result.outputs.description }} | ||
| HEAD_SHA: ${{ needs.authorize.outputs.head_sha }} | ||
| PR_NUMBER: ${{ needs.authorize.outputs.pr_number }} | ||
| run: | | ||
| set -euo pipefail | ||
| mkdir -p "$(dirname "$PUBLIC_FAILURE_LOG")" | ||
| { | ||
| echo "TRTMC Protected CI result" | ||
| echo "=========================" | ||
| echo | ||
| echo "Status: FAILED" | ||
| echo "Pull request: #$PR_NUMBER" | ||
| echo "Head commit: $HEAD_SHA" | ||
| echo "Reason: $DESCRIPTION" | ||
| echo | ||
| echo "Protected failure details are not transferred to the public repository." | ||
| } > "$PUBLIC_FAILURE_LOG" | ||
| { | ||
| echo '<!-- trtmc-internal-ci-result -->' | ||
| echo 'This is an automated Internal CI result; no review from an individual maintainer is requested.' | ||
| echo | ||
| echo '```text' | ||
| sed -n '1,240p' "$PUBLIC_FAILURE_LOG" | ||
| echo '```' | ||
| echo | ||
| echo 'Open the public Source Actions run from the automated status link above.' | ||
| } > "$COMMENT_BODY" | ||
| [ "$(wc -c < "$COMMENT_BODY")" -le 60000 ] | ||
| - name: Publish the terminal automated status | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| HEAD_SHA: ${{ needs.authorize.outputs.head_sha }} | ||
| STATE: ${{ steps.result.outputs.state }} | ||
| DESCRIPTION: ${{ steps.result.outputs.description }} | ||
| run: | | ||
| set -euo pipefail | ||
| gh api --silent --method POST \ | ||
| "/repos/$GITHUB_REPOSITORY/statuses/$HEAD_SHA" \ | ||
| -f state="$STATE" \ | ||
| -f context="TRTMC Internal CI / Automated premerge gate" \ | ||
| -f description="$DESCRIPTION" \ | ||
| -f target_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" | ||
| - name: Print public-failure.log | ||
| if: ${{ steps.result.outputs.state != 'success' }} | ||
| run: cat "${{ runner.temp }}/contributor-result/public-failure.log" | ||
| - name: Upload public failure summary | ||
| if: ${{ steps.result.outputs.state != 'success' }} | ||
| continue-on-error: true | ||
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | ||
| with: | ||
| name: public-failure-log | ||
| path: ${{ runner.temp }}/contributor-result/public-failure.log | ||
| if-no-files-found: error | ||
| retention-days: 7 | ||
| - name: Upsert the automated failure comment | ||
| if: ${{ steps.result.outputs.state != 'success' && steps.result.outputs.publish_comment == 'true' }} | ||
| continue-on-error: true | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| PR_NUMBER: ${{ needs.authorize.outputs.pr_number }} | ||
| COMMENT_BODY: ${{ runner.temp }}/internal-ci-comment.md | ||
| run: | | ||
| set -euo pipefail | ||
| mapfile -t comment_ids < <( | ||
| gh api --paginate \ | ||
| "/repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments?per_page=100" \ | ||
| --jq '.[] | select(.user.login == "github-actions[bot]") | select(.body | contains("<!-- trtmc-internal-ci-result -->")) | .id' | ||
| ) | ||
| payload="$RUNNER_TEMP/internal-ci-comment.json" | ||
| jq -n --rawfile body "$COMMENT_BODY" '{body: $body}' > "$payload" | ||
| if [ "${#comment_ids[@]}" -eq 0 ]; then | ||
| gh api --silent --method POST \ | ||
| "/repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \ | ||
| --input "$payload" | ||
| else | ||
| gh api --silent --method PATCH \ | ||
| "/repos/$GITHUB_REPOSITORY/issues/comments/${comment_ids[0]}" \ | ||
| --input "$payload" | ||
| for comment_id in "${comment_ids[@]:1}"; do | ||
| gh api --silent --method DELETE \ | ||
| "/repos/$GITHUB_REPOSITORY/issues/comments/$comment_id" || true | ||
| done | ||
| fi | ||
| - name: Preserve the failed automated verdict | ||
| if: ${{ steps.result.outputs.state != 'success' }} | ||
| run: exit 1 | ||