Skip to content

Commit 6fdd7c1

Browse files
lidel2color
andauthored
fix: support workflow_run events for github status and pr comments (#37)
* fix: support workflow_run events for github status and pr comments adds proper detection of workflow_run events to ensure commit statuses and pr comments are posted to the correct sha when the action is triggered from a workflow_run context (e.g., secure fork pr workflows) - detect workflow_run event and use workflow_run.head_sha for status - find pr number from workflow_run context for comments - update both status and comment sha references * docs: add dual workflow pattern for fork PRs * fix: improve PR matching for workflow_run events - match PRs by SHA instead of taking the first one - add warning when multiple PRs exist but none match SHA - add warning when no PRs are found for the branch --------- Co-authored-by: Daniel Norman <1992255+2color@users.noreply.github.com>
1 parent b8094b7 commit 6fdd7c1

File tree

3 files changed

+175
-14
lines changed

3 files changed

+175
-14
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Fixed
11+
12+
- Add support for using ipfs-deploy-action in workflows triggered by `workflow_run` events to allow secure usage in PRs from forks.
13+
1014
## [1.6.0] - 2025-05-16
1115

1216
### Added

README.md

Lines changed: 120 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@ The [composite action](https://docs.github.com/en/actions/sharing-automations/cr
1111

1212
![PR comment with CID and preview links](./screenshot-pr-comment.png)
1313

14+
## Table of Contents
15+
16+
- [Features](#features)
17+
- [How does this compare to the other IPFS actions?](#how-does-this-compare-to-the-other-ipfs-actions)
18+
- [Storacha configuration](#storacha-configuration)
19+
- [Inputs](#inputs)
20+
- [Required Inputs](#required-inputs)
21+
- [Optional Inputs](#optional-inputs)
22+
- [Outputs](#outputs)
23+
- [Usage](#usage)
24+
- [Simple Workflow (No Fork PRs)](#simple-workflow-no-fork-prs)
25+
- [Dual Workflows (With Fork PRs)](#dual-workflows-with-fork-prs)
26+
- [FAQ](#faq)
27+
1428
## Features
1529

1630
- 📦 Merkleizes your static site into a CAR file
@@ -98,9 +112,9 @@ The signing key and proof will be used as [inputs](#inputs) to the action.
98112

99113
## Usage
100114

101-
See the [IPNS Inspector](https://github.com/ipfs/ipns-inspector/blob/main/.github/workflows/build.yml) for a real-world example of this action in use.
115+
### Simple Workflow (No Fork PRs)
102116

103-
Here's a basic example of how to use this action in your workflow:
117+
For repositories that don't accept PRs from forks, you can use a single workflow:
104118

105119
```yaml
106120
name: Build and Deploy to IPFS
@@ -109,6 +123,7 @@ permissions:
109123
contents: read
110124
pull-requests: write
111125
statuses: write
126+
112127
on:
113128
push:
114129
branches:
@@ -118,7 +133,7 @@ on:
118133
jobs:
119134
build-and-deploy:
120135
runs-on: ubuntu-latest
121-
outputs: # This exposes the CID output of the action to the rest of the workflow
136+
outputs:
122137
cid: ${{ steps.deploy.outputs.cid }}
123138
steps:
124139
- name: Checkout code
@@ -136,8 +151,8 @@ jobs:
136151
- name: Build project
137152
run: npm run build
138153

139-
- uses: ipfs/ipfs-deploy-action@v1
140-
name: Deploy to IPFS
154+
- name: Deploy to IPFS
155+
uses: ipfs/ipfs-deploy-action@v1
141156
id: deploy
142157
with:
143158
path-to-deploy: out
@@ -146,8 +161,108 @@ jobs:
146161
github-token: ${{ github.token }}
147162
```
148163
164+
### Dual Workflows (With Fork PRs)
165+
166+
For secure deployments of PRs from forks, use two separate workflows that pass artifacts between them:
167+
168+
**`.github/workflows/build.yml`** - Builds without secrets access:
169+
```yaml
170+
name: Build
171+
172+
permissions:
173+
contents: read
174+
175+
on:
176+
push:
177+
branches:
178+
- main
179+
pull_request:
180+
branches:
181+
- main
182+
183+
env:
184+
BUILD_PATH: 'out' # Update this to your build output directory
185+
186+
jobs:
187+
build:
188+
runs-on: ubuntu-latest
189+
steps:
190+
- name: Checkout code
191+
uses: actions/checkout@v4
192+
with:
193+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
194+
195+
196+
- name: Setup Node.js
197+
uses: actions/setup-node@v4
198+
with:
199+
node-version: '20'
200+
cache: 'npm'
201+
202+
- name: Install dependencies
203+
run: npm ci
204+
205+
- name: Build project
206+
run: npm run build
207+
208+
- name: Upload build artifact
209+
uses: actions/upload-artifact@v4
210+
with:
211+
name: website-build-${{ github.run_id }}
212+
path: ${{ env.BUILD_PATH }}
213+
retention-days: 1
214+
```
215+
216+
**`.github/workflows/deploy.yml`** - Deploys with secrets access:
217+
```yaml
218+
name: Deploy
219+
220+
permissions:
221+
contents: read
222+
pull-requests: write
223+
statuses: write
224+
225+
on:
226+
workflow_run:
227+
workflows: ["Build"]
228+
types: [completed]
229+
230+
env:
231+
BUILD_PATH: 'website-build' # Directory where artifact from build.yml will be unpacked
232+
233+
jobs:
234+
deploy-ipfs:
235+
if: github.event.workflow_run.conclusion == 'success'
236+
runs-on: ubuntu-latest
237+
outputs:
238+
cid: ${{ steps.deploy.outputs.cid }}
239+
steps:
240+
- name: Download build artifact
241+
uses: actions/download-artifact@v4
242+
with:
243+
name: website-build-${{ github.event.workflow_run.id }}
244+
path: ${{ env.BUILD_PATH }}
245+
run-id: ${{ github.event.workflow_run.id }}
246+
github-token: ${{ github.token }}
247+
248+
- name: Deploy to IPFS
249+
uses: ipfs/ipfs-deploy-action@v1
250+
id: deploy
251+
with:
252+
path-to-deploy: ${{ env.BUILD_PATH }}
253+
storacha-key: ${{ secrets.STORACHA_KEY }}
254+
storacha-proof: ${{ secrets.STORACHA_PROOF }}
255+
github-token: ${{ github.token }}
256+
```
257+
258+
See real-world examples:
259+
- [IPFS Specs](https://github.com/ipfs/specs/tree/main/.github/workflows) - Uses the secure two-workflow pattern
260+
- [IPFS Docs](https://github.com/ipfs/ipfs-docs/tree/main/.github/workflows) - Uses the secure two-workflow pattern
261+
149262
## FAQ
150263

264+
- How can I safely build on PRs from forks?
265+
- Use the two-workflow pattern shown above. The build workflow runs on untrusted fork code without secrets access, while the deploy workflow only runs after a successful build and has access to secrets but never executes untrusted code. This pattern uses GitHub's `workflow_run` event to securely pass artifacts between workflows.
151266
- What's the difference between uploading a CAR and using the Pinning API?
152267
- Since the CAR is like a tarball of the full build with some additional metadata (merkle proofs), the upload will be as big as the build output. Pinning with the [Pinning API](https://github.com/ipfs/pinning-services-api-spec) in contrast is just a request to instruct the pinning service to retrieve and pin the data. At the time this action is first released, CAR uploads is supported by Kubo, Storacha, and Filebase, but not Pinata.
153268
- How can I update DNSLink?

action.yml

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -321,10 +321,18 @@ runs:
321321
script: |
322322
const cid = '${{ steps.merkleize.outputs.cid }}';
323323
324-
// For PR events, we need to use the head SHA
325-
const sha = (context.eventName === 'pull_request' || context.eventName === 'pull_request_target')
326-
? context.payload.pull_request.head.sha
327-
: context.sha;
324+
// Determine the correct SHA based on the event type
325+
let sha;
326+
if (context.eventName === 'workflow_run') {
327+
// For workflow_run events triggered by PRs, use the PR's head SHA
328+
sha = context.payload.workflow_run.head_sha;
329+
} else if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
330+
// For PR events, use the head SHA
331+
sha = context.payload.pull_request.head.sha;
332+
} else {
333+
// For push events, use the commit SHA
334+
sha = context.sha;
335+
}
328336
329337
await github.rest.repos.createCommitStatus({
330338
owner: context.repo.owner,
@@ -336,26 +344,60 @@ runs:
336344
context: 'IPFS'
337345
});
338346
347+
- name: Get PR number for workflow_run
348+
if: ${{ inputs.set-pr-comment == 'true' && github.event_name == 'workflow_run' }}
349+
id: pr-number
350+
uses: actions/github-script@v7
351+
with:
352+
github-token: ${{ inputs.github-token }}
353+
script: |
354+
// For workflow_run events, we need to find the PR number from the workflow run
355+
if (context.payload.workflow_run.event === 'pull_request') {
356+
const pulls = await github.rest.pulls.list({
357+
owner: context.repo.owner,
358+
repo: context.repo.repo,
359+
head: `${context.payload.workflow_run.head_repository.owner.login}:${context.payload.workflow_run.head_branch}`,
360+
state: 'open'
361+
});
362+
363+
if (pulls.data.length > 0) {
364+
// Try to find the PR whose head SHA matches the workflow run's head_sha
365+
const matchingPr = pulls.data.find(pr => pr.head.sha === context.payload.workflow_run.head_sha);
366+
367+
if (matchingPr) {
368+
core.setOutput('number', matchingPr.number);
369+
core.setOutput('sha', matchingPr.head.sha);
370+
} else {
371+
// Fallback: use the first PR, but warn in the summary
372+
core.setOutput('number', pulls.data[0].number);
373+
core.setOutput('sha', context.payload.workflow_run.head_sha);
374+
core.warning(`Multiple PRs found for branch ${context.payload.workflow_run.head_branch}, but none matched head_sha. Using the first PR (#${pulls.data[0].number}).`);
375+
}
376+
} else {
377+
core.warning(`No open PRs found for branch ${context.payload.workflow_run.head_branch} from ${context.payload.workflow_run.head_repository.owner.login}`);
378+
}
379+
}
380+
339381
- name: Find Comment to update
340-
if: ${{ inputs.set-pr-comment == 'true' && (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') }}
382+
if: ${{ inputs.set-pr-comment == 'true' && (github.event_name == 'pull_request' || github.event_name == 'pull_request_target' || (github.event_name == 'workflow_run' && steps.pr-number.outputs.number)) }}
341383
uses: peter-evans/find-comment@v3
342384
id: fc
343385
with:
344-
issue-number: ${{ github.event.pull_request.number }}
386+
issue-number: ${{ github.event.pull_request.number || steps.pr-number.outputs.number }}
345387
comment-author: 'github-actions[bot]'
346388
body-includes: '🚀 Build'
347389
token: ${{ inputs.github-token }}
348390

349391
- name: Create or update comment
350-
if: ${{ inputs.set-pr-comment == 'true' && (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') }}
392+
if: ${{ inputs.set-pr-comment == 'true' && (github.event_name == 'pull_request' || github.event_name == 'pull_request_target' || (github.event_name == 'workflow_run' && steps.pr-number.outputs.number)) }}
351393
uses: peter-evans/create-or-update-comment@v4
352394
with:
353395
token: ${{ inputs.github-token }}
354396
comment-id: ${{ steps.fc.outputs.comment-id }}
355-
issue-number: ${{ github.event.pull_request.number }}
397+
issue-number: ${{ github.event.pull_request.number || steps.pr-number.outputs.number }}
356398
body: |
357399
### 🚀 Build Preview on IPFS ready
358-
- 🔎 Commit: ${{ github.event.pull_request.head.sha || github.sha }}
400+
- 🔎 Commit: ${{ github.event.pull_request.head.sha || steps.pr-number.outputs.sha }}
359401
- 🔏 CID `${{ steps.merkleize.outputs.cid }}`
360402
- 📦 Preview:
361403
- [dweb.link](https://dweb.link/ipfs/${{ steps.merkleize.outputs.cid }})

0 commit comments

Comments
 (0)