-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrelease-please-bump-lockfile.yml
More file actions
200 lines (187 loc) Β· 7.88 KB
/
Copy pathrelease-please-bump-lockfile.yml
File metadata and controls
200 lines (187 loc) Β· 7.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
name: Push Cargo.lock to release-please PR
# Privileged half of the release-please-PR Cargo.lock pipeline.
#
# Fires after the unprivileged `Build Cargo.lock for release-please
# PR` workflow finishes. Downloads the Cargo.lock artifact it
# produced, validates it, then pushes the file to the PR branch via
# the GitHub Git Data API.
#
# **No `actions/checkout` of the PR branch.** **No `cargo` /
# `npm` / `bun` invocation.** The only code that runs in this
# privileged context is the artifact unzip + a github-script step
# that talks to the API. This is what closes CodeQL's
# `actions/untrusted-checkout/critical` rule: a privileged
# workflow_run job must not execute arbitrary code from a non-
# default ref.
"on":
workflow_run:
workflows: ["Build Cargo.lock for release-please PR"]
types: [completed]
permissions:
contents: write
actions: read
pull-requests: read
jobs:
push:
if: >-
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- name: Download artifact from upstream workflow run
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
const match = artifacts.data.artifacts.find(
(a) => a.name === 'release-please-lockfile',
);
if (!match) {
core.setFailed('No release-please-lockfile artifact found on upstream run');
return;
}
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: match.id,
archive_format: 'zip',
});
const fs = require('fs');
fs.writeFileSync('artifact.zip', Buffer.from(download.data));
- name: Unzip + validate artifact
# Bail fast (and loudly) on anything unexpected β a privileged
# workflow consuming an artifact from an unprivileged run must
# treat that artifact as untrusted input. We accept it only
# when every expected file is present, the Cargo.lock starts
# with cargo's canonical header, and the file size is within
# a sane envelope (1 KB-5 MB).
run: |
mkdir -p artifact
unzip -q artifact.zip -d artifact
for f in Cargo.lock pr_number pr_branch pr_head_sha; do
test -f "artifact/$f" || { echo "::error::missing $f"; exit 1; }
done
head -1 artifact/Cargo.lock | grep -qE '^# This file is automatically @generated by Cargo' || {
echo "::error::Cargo.lock does not start with the cargo header"
exit 1
}
size=$(stat -c%s artifact/Cargo.lock)
if [ "$size" -lt 1000 ] || [ "$size" -gt 5000000 ]; then
echo "::error::Cargo.lock size suspicious ($size bytes)"
exit 1
fi
- name: Validate PR identity + push Cargo.lock via API
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const fs = require('fs');
const prNumber = parseInt(
fs.readFileSync('artifact/pr_number', 'utf8').trim(),
10,
);
const branch = fs.readFileSync('artifact/pr_branch', 'utf8').trim();
const artifactHeadSha = fs
.readFileSync('artifact/pr_head_sha', 'utf8')
.trim();
// Defensive parse β the artifact came from an unprivileged
// build that ran on attacker-controllable code paths.
if (!Number.isInteger(prNumber) || prNumber <= 0) {
core.setFailed(`invalid PR number: ${prNumber}`);
return;
}
if (!/^release-please--[A-Za-z0-9._\/-]+$/.test(branch)) {
core.setFailed(`branch name does not match release-please pattern: ${branch}`);
return;
}
if (!/^[0-9a-f]{40}$/.test(artifactHeadSha)) {
core.setFailed(`invalid head SHA: ${artifactHeadSha}`);
return;
}
// Verify the PR is still open, still authored by the
// release-please bot, and its head ref still matches the
// artifact's claim.
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
if (pr.data.state !== 'open') {
core.info(`PR #${prNumber} is ${pr.data.state} β nothing to do`);
return;
}
if (pr.data.user.login !== 'github-actions[bot]') {
core.setFailed(
`PR author is ${pr.data.user.login}, expected github-actions[bot]`,
);
return;
}
if (pr.data.head.ref !== branch) {
core.setFailed(
`PR head ref ${pr.data.head.ref} does not match artifact branch ${branch}`,
);
return;
}
// If the PR branch has moved since the artifact was built,
// the Cargo.lock we have was generated against a now-stale
// Cargo.toml. Bail and let the next build-workflow run
// produce a fresh artifact.
const currentHead = pr.data.head.sha;
if (currentHead !== artifactHeadSha) {
core.info(
`PR head moved (${artifactHeadSha} -> ${currentHead}); skipping push, the next build will pick this up`,
);
return;
}
// Create a blob with the new Cargo.lock content.
const content = fs.readFileSync('artifact/Cargo.lock');
const blob = await github.rest.git.createBlob({
owner: context.repo.owner,
repo: context.repo.repo,
content: content.toString('base64'),
encoding: 'base64',
});
// Build a new tree that swaps src-tauri/Cargo.lock for our
// blob, leaving the rest of the parent tree untouched.
const parent = await github.rest.git.getCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: currentHead,
});
const tree = await github.rest.git.createTree({
owner: context.repo.owner,
repo: context.repo.repo,
base_tree: parent.data.tree.sha,
tree: [
{
path: 'src-tauri/Cargo.lock',
mode: '100644',
type: 'blob',
sha: blob.data.sha,
},
],
});
// Idempotent: if Cargo.lock is already in sync, the new
// tree SHA equals the parent's and we bail.
if (tree.data.sha === parent.data.tree.sha) {
core.info('Cargo.lock already in sync with Cargo.toml β nothing to push');
return;
}
// Commit + fast-forward the branch ref.
const commit = await github.rest.git.createCommit({
owner: context.repo.owner,
repo: context.repo.repo,
message: 'chore: bump Cargo.lock',
tree: tree.data.sha,
parents: [currentHead],
});
await github.rest.git.updateRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${branch}`,
sha: commit.data.sha,
});
core.info(`Pushed Cargo.lock commit ${commit.data.sha} to ${branch}`);