|
| 1 | +# 🚀 GitHub Actions: Securely Commenting Test Results on All PRs |
| 2 | + |
| 3 | +## 📌 Overview |
| 4 | + |
| 5 | +This tutorial explains how to set up a GitHub Actions workflow that runs tests and comments on the results of pull requests (PRs), including those from forks. Using two workflows ensures security while allowing test reports to be posted on all types of PRs. |
| 6 | + |
| 7 | +The setup consists of two workflows: |
| 8 | + |
| 9 | +1. **`workflowA`** - Runs tests and uploads the test results as artifacts. |
| 10 | +2. **`workflowB`** - Retrieves test results and comments on the corresponding pull request. |
| 11 | + |
| 12 | +This method is applicable to any project using GitHub Actions for CI/CD, ensuring a secure and efficient way to handle test reporting. |
| 13 | + |
| 14 | +--- |
| 15 | + |
| 16 | +## ⚠️ Important Note |
| 17 | + |
| 18 | +These workflows should be implemented on the default branch of the repository (either `master` or `main` in newer repositories) to ensure proper execution and integration. Running workflows on other branches may lead to unexpected behavior, security issues, or failure to post comments on pull requests. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## 🔐 Why Use Two Workflows? |
| 23 | + |
| 24 | +GitHub restricts workflows triggered by `pull_request` events from writing to the base repository when PRs originate from forks. This limitation prevents workflows from commenting on pull requests directly. |
| 25 | + |
| 26 | +Using `pull_request_target` instead of `pull_request` allows commenting on forked PRs, but it introduces a significant security risk: the workflow runs with write permissions on the base repository, making it vulnerable to malicious code execution. Attackers could potentially modify workflows to exfiltrate secrets, overwrite critical repository files, or introduce malicious changes that could be merged unnoticed. |
| 27 | + |
| 28 | +To mitigate this, we split the workflow into two: |
| 29 | + |
| 30 | +- **The first workflow (********`workflowA`********)** runs tests and uploads the results as artifacts. This workflow is triggered using `pull_request`, ensuring it runs whenever a new pull request is opened, updated, or reopened. |
| 31 | +- **The second workflow (********`workflowB`********)** is triggered by `workflow_run` when the first workflow completes. Since `workflow_run` does not inherit permissions from the pull request, it eliminates security issues while allowing it to post a comment securely. |
| 32 | + |
| 33 | +This method ensures that test results are always accessible while maintaining security. |
| 34 | + |
| 35 | +--- |
| 36 | + |
| 37 | +## 🔄 Step-by-Step Workflow Execution |
| 38 | + |
| 39 | +### **1️⃣ workflowA: Running Tests and Uploading Artifacts** |
| 40 | + |
| 41 | +This workflow is triggered when a pull request is opened, synchronized, or reopened on any branch. It performs the following steps: |
| 42 | + |
| 43 | +- **Check out PR code**: |
| 44 | + |
| 45 | +```yaml |
| 46 | +- name: Check out PR code |
| 47 | + uses: actions/checkout@v4 |
| 48 | +``` |
| 49 | +
|
| 50 | +- **Run Tests**: |
| 51 | +
|
| 52 | +```yaml |
| 53 | +- name: Run Tests |
| 54 | + run: | |
| 55 | + ./run-tests.sh # Replace with your actual test command |
| 56 | +``` |
| 57 | +
|
| 58 | +- **Convert Test Results to a Compatible Format**: |
| 59 | +
|
| 60 | +```yaml |
| 61 | +- name: Convert Test Results |
| 62 | + run: | |
| 63 | + npx test-result-converter ./testReport.xml -o ./results/test-report.json # Modify based on your test framework |
| 64 | +``` |
| 65 | +
|
| 66 | +- **Upload Test Report Artifact**: |
| 67 | +
|
| 68 | +```yaml |
| 69 | +- name: Upload Test Report Artifact |
| 70 | + uses: actions/upload-artifact@v4 |
| 71 | + with: |
| 72 | + name: testReport |
| 73 | + path: ./results/test-report.json |
| 74 | +``` |
| 75 | +
|
| 76 | +- **Save PR Number and Upload as an Artifact**: |
| 77 | +
|
| 78 | +To ensure that `workflowB` can correctly comment on the corresponding pull request, we save the PR number as an artifact in `workflowA`. Since `workflowB` is triggered by `workflowA` using `workflow_run`, it does not have direct access to the PR metadata. Uploading the PR number as an artifact allows `workflowB` to retrieve and use it for posting test results in the correct pull request. |
| 79 | + |
| 80 | +```yaml |
| 81 | +- name: Save PR Number |
| 82 | + run: echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV |
| 83 | +
|
| 84 | +- name: Upload PR Number as Artifact |
| 85 | + run: echo $PR_NUMBER > pr_number.txt |
| 86 | + shell: bash |
| 87 | +
|
| 88 | +- name: Upload PR Number Artifact |
| 89 | + uses: actions/upload-artifact@v4 |
| 90 | + with: |
| 91 | + name: pr_number |
| 92 | + path: pr_number.txt |
| 93 | +``` |
| 94 | + |
| 95 | +Since this workflow only requires read permissions, it avoids potential security risks when dealing with external contributions from forked repositories. The second workflow, which has the necessary permissions to write, is responsible for retrieving and posting the results, ensuring a secure and controlled execution process. |
| 96 | + |
| 97 | +--- |
| 98 | + |
| 99 | +### **2️⃣ workflowB: Downloading Artifacts and Posting Results** |
| 100 | + |
| 101 | +This workflow is triggered when `workflowA` completes successfully. Since GitHub Actions does not allow direct artifact downloads across workflows using `actions/download-artifact`. |
| 102 | + |
| 103 | +- **Download Test Report Artifact:** Since GitHub Actions does not allow direct artifact downloads across workflows using `actions/download-artifact`, we use [`dawidd6/action-download-artifact@v8`](https://github.com/dawidd6/action-download-artifact) instead. This repository enables downloading artifacts from a previous workflow run by specifying the `run_id`, which is essential when handling artifacts between separate workflows. It follows these steps: |
| 104 | +```yaml |
| 105 | +- name: Download Test Report Artifact |
| 106 | + uses: dawidd6/action-download-artifact@v8 |
| 107 | + with: |
| 108 | + name: testReport |
| 109 | + run_id: ${{ github.event.workflow_run.id }} |
| 110 | + path: artifacts |
| 111 | +``` |
| 112 | + |
| 113 | +- **Download PR Number Artifact**: |
| 114 | + |
| 115 | +```yaml |
| 116 | +- name: Download PR Number Artifact |
| 117 | + uses: dawidd6/action-download-artifact@v8 |
| 118 | + with: |
| 119 | + name: pr_number |
| 120 | + run_id: ${{ github.event.workflow_run.id }} |
| 121 | + path: pr_number |
| 122 | +``` |
| 123 | + |
| 124 | +- **Read PR Number**: |
| 125 | + |
| 126 | +```yaml |
| 127 | +- name: Read PR Number |
| 128 | + id: read_pr_number |
| 129 | + run: | |
| 130 | + PR_NUMBER=$(cat pr_number/pr_number.txt) |
| 131 | + echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV |
| 132 | +``` |
| 133 | + |
| 134 | +- **Publish Test Report**: |
| 135 | + |
| 136 | +```yaml |
| 137 | +- name: Publish Test Report |
| 138 | + uses: test-reporter/[email protected] |
| 139 | + with: |
| 140 | + report-path: 'artifacts/test-report.json' |
| 141 | + issue: ${{ env.PR_NUMBER }} |
| 142 | + env: |
| 143 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 144 | +``` |
| 145 | + |
| 146 | +This final step posts the test report as a comment on the pull request, making it easy for contributors and maintainers to review test results. |
| 147 | + |
| 148 | +--- |
| 149 | + |
| 150 | +## ✅ Conclusion |
| 151 | + |
| 152 | +By structuring the workflows this way, we achieve the following: |
| 153 | + |
| 154 | +- **Secure execution** without exposing repository write access to forked pull requests. |
| 155 | +- **Successful test execution** and result upload. |
| 156 | +- **Seamless commenting** on pull requests with test results while mitigating security risks. |
| 157 | + |
| 158 | +This method ensures that test results are reliably posted while maintaining a secure GitHub Actions setup. Additionally, this approach scales effectively for large repositories with many PRs, as the artifact-based workflow minimizes redundant computations and ensures efficient resource utilization. 🚀 |
| 159 | + |
0 commit comments