diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index c4da1c7eb5..df44416b5e 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -19,7 +19,6 @@ jobs: pr-check: runs-on: ubuntu-latest permissions: - pull-requests: write contents: read steps: - uses: actions/checkout@v4 @@ -66,5 +65,17 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }} + COMMENT_OUT_FILE: pr-comment.txt run: | + echo "$PR_NUMBER" > pr-number.txt ci/target/release/pr-check ${{ steps.changed.outputs.files }} + + - name: Upload PR check comment artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: pr-check-comment + path: | + pr-comment.txt + pr-number.txt + if-no-files-found: ignore diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml new file mode 100644 index 0000000000..e1964a12d1 --- /dev/null +++ b/.github/workflows/pr-comment.yml @@ -0,0 +1,55 @@ +name: Post PR Comment + +# This workflow implements the "Split Workflow" pattern to securely post comments +# on Pull Requests coming from forks. Since fork PRs run with a read-only token, +# the main 'PR Check' workflow uploads the comment as an artifact, and this +# trusted workflow downloads it and posts it to the PR. +on: + workflow_run: + workflows: ["PR Check"] + types: + - completed + +jobs: + post-comment: + runs-on: ubuntu-latest + permissions: + pull-requests: write + actions: read + steps: + - name: Download PR check artifact + uses: actions/download-artifact@v4 + with: + name: pr-check-comment + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + continue-on-error: true + + - name: Post or update comment + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + run: | + if [ ! -f pr-number.txt ] || [ ! -f pr-comment.txt ]; then + echo "Artifacts not found. The PR Check workflow likely skipped or failed early." + exit 0 + fi + + PR=$(cat pr-number.txt | tr -d '\n') + + # Find the ID of an existing comment with the bot's marker + COMMENT_ID=$(gh api "repos/$REPO/issues/$PR/comments" \ + -q '.[] | select(.body | contains("")) | .id' \ + | head -n 1) + + if [ -n "$COMMENT_ID" ]; then + echo "Updating existing comment $COMMENT_ID on PR #$PR" + gh api -X PATCH "repos/$REPO/issues/comments/$COMMENT_ID" \ + -f body=@pr-comment.txt \ + --silent + else + echo "Creating new comment on PR #$PR" + gh api "repos/$REPO/issues/$PR/comments" \ + -f body=@pr-comment.txt \ + --silent + fi \ No newline at end of file diff --git a/ci/pr-check/src/main.rs b/ci/pr-check/src/main.rs index 480f80c81a..4e10cc4081 100644 --- a/ci/pr-check/src/main.rs +++ b/ci/pr-check/src/main.rs @@ -441,6 +441,7 @@ async fn main() -> Result<()> { let gh_repo = env::var("GITHUB_REPOSITORY").context("GITHUB_REPOSITORY not set")?; let pr_number_str = env::var("PR_NUMBER").context("PR_NUMBER not set")?; let pr_number = parse_pr_number(&pr_number_str)?; + let out_file = env::var("COMMENT_OUT_FILE").ok(); // Remaining CLI arguments are the paths to check. // Usage: pr-check data/tools/foo.yml data/tools/bar.yml @@ -470,7 +471,15 @@ async fn main() -> Result<()> { let comment_body = render_comment(&reports)?; - upsert_comment(&client, &gh_repo, pr_number, &comment_body).await?; + if let Some(path) = out_file { + std::fs::write(&path, &comment_body) + .with_context(|| format!("Failed to write comment to {}", path))?; + eprintln!("Wrote comment to {}", path); + } else { + if let Err(e) = upsert_comment(&client, &gh_repo, pr_number, &comment_body).await { + eprintln!("Warning: Failed to post PR comment: {e}"); + } + } let any_failures = reports.iter().any(|r| r.any_fail()); if any_failures {