-
Notifications
You must be signed in to change notification settings - Fork 223
298 lines (255 loc) · 12.9 KB
/
Copy pathpr-podman-push.yaml
File metadata and controls
298 lines (255 loc) · 12.9 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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
name: Podman PR Push
on:
workflow_run:
workflows:
- 'PR Build Image (Hermetic)'
types:
- completed
jobs:
podman-push:
name: Push Podman Image to Registry
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_run' }}
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Get PR number from workflow run
id: get-pr-info
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_TARGET_REPO: ${{ github.repository }}
# If the PR is from a fork, prefix it with `<owner-login>:`, otherwise only the PR branch name is relevant:
PR_BRANCH: |-
${{
(github.event.workflow_run.head_repository.owner.login != github.event.workflow_run.repository.owner.login)
&& format('{0}:{1}', github.event.workflow_run.head_repository.owner.login, github.event.workflow_run.head_branch)
|| github.event.workflow_run.head_branch
}}
FULL_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
SHORT_SHA=$(echo "${{ env.FULL_SHA }}" | cut -c1-8)
# Need to use gh cli instead of `events.workflow_run.pull_requests` because the latter doesn't work for PRs from forks
# Refer to https://github.com/orgs/community/discussions/25220
PR_NUMBER=$(gh pr view --repo "${PR_TARGET_REPO}" "${PR_BRANCH}" --json number --jq '.number')
if [ -n "$PR_NUMBER" ]; then
echo "Found PR number: $PR_NUMBER"
echo "PR branch: $PR_BRANCH"
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "short-sha=$SHORT_SHA" >> $GITHUB_OUTPUT
else
echo "::error::Failed to determine PR number for branch: $PR_BRANCH"
echo "## Failed to determine PR number" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Could not find a PR for branch: \`$PR_BRANCH\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "No PR comment could be posted for this workflow run." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Comment build failure
if: ${{ github.event.workflow_run.conclusion != 'success' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
PR_NUMBER: ${{ steps.get-pr-info.outputs.pr_number }}
BUILD_WORKFLOW_URL: ${{ github.event.workflow_run.html_url }}
BUILD_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
with:
script: |
const prNumber = process.env.PR_NUMBER;
const buildUrl = process.env.BUILD_WORKFLOW_URL;
const conclusion = process.env.BUILD_CONCLUSION;
const body = `The container image [build workflow](${buildUrl}) finished with status: \`${conclusion}\`.\n\n`
github.rest.issues.createComment({
issue_number: parseInt(prNumber),
owner: context.repo.owner,
repo: context.repo.repo,
body: body
})
- name: Determine artifact name
if: ${{ github.event.workflow_run.conclusion == 'success' }}
id: get-artifact-name
env:
PR_NUMBER: ${{ steps.get-pr-info.outputs.pr_number }}
WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
# For workflow_run, extract from the event context
SHORT_SHA=$(echo "${{ env.WORKFLOW_HEAD_SHA }}" | cut -c1-8)
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
ARTIFACT_NAME="podman-image-${{ env.PR_NUMBER }}-${SHORT_SHA}"
SKIP_ARTIFACT_NAME="pr-${{ env.PR_NUMBER }}-${SHORT_SHA}-isSkipped"
echo "Using artifact name: $ARTIFACT_NAME"
echo "Using skip artifact name: $SKIP_ARTIFACT_NAME"
echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
echo "skip_artifact_name=$SKIP_ARTIFACT_NAME" >> $GITHUB_OUTPUT
- name: Download Skip Status Artifact
if: ${{ github.event.workflow_run.conclusion == 'success' }}
id: download-skip-status
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
env:
SKIP_ARTIFACT_NAME: ${{ steps.get-artifact-name.outputs.skip_artifact_name }}
with:
name: ${{ env.SKIP_ARTIFACT_NAME }}
path: ./rhdh-skip-artifacts
run-id: ${{ github.event.workflow_run.id || github.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true
- name: Check Skip Status
if: ${{ github.event.workflow_run.conclusion == 'success' }}
id: check-skip
run: |
if [ -f "./rhdh-skip-artifacts/isSkipped.txt" ]; then
IS_SKIPPED_RAW=$(cat ./rhdh-skip-artifacts/isSkipped.txt)
# Sanitize: Allow only 'true' or 'false', default to false if unknown to skip artifact download in case of injection
if [[ "$IS_SKIPPED_RAW" == "false" ]]; then
IS_SKIPPED="false"
else
IS_SKIPPED="true"
fi
echo "Found skip status: $IS_SKIPPED_RAW"
echo "Sanitized skip status: $IS_SKIPPED"
echo "is_skipped=$IS_SKIPPED" >> $GITHUB_OUTPUT
else
echo "Skip status artifact not found, skipping push"
echo "is_skipped=true" >> $GITHUB_OUTPUT
fi
- name: Download Image Artifacts
if: ${{ github.event.workflow_run.conclusion == 'success' && steps.check-skip.outputs.is_skipped != 'true' }}
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
env:
ARTIFACT_NAME: ${{ steps.get-artifact-name.outputs.artifact_name }}
with:
name: ${{ env.ARTIFACT_NAME }}
path: ./rhdh-podman-artifacts
run-id: ${{ github.event.workflow_run.id || github.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Load and prepare image
if: ${{ github.event.workflow_run.conclusion == 'success' && steps.check-skip.outputs.is_skipped != 'true' }}
id: prepare
env:
PR_NUMBER: ${{ steps.get-pr-info.outputs.pr_number }}
run: |
# Check if artifacts exist
if [ ! -f "./rhdh-podman-artifacts/image.tar" ]; then
echo "Error: image.tar not found in artifacts"
echo "This may make sense if the build was skipped"
exit 1
fi
# Load the image from tar file (contains all tags)
podman load -i ./rhdh-podman-artifacts/image.tar
# Read metadata
TAGS_LIST=$(cat ./rhdh-podman-artifacts/tags.txt)
echo "Loaded images:"
podman images
echo "Full tags from metadata:"
printf '%s\n' "$TAGS_LIST"
# SECURITY: tags.txt is produced by the untrusted pull_request job (the
# fork author controls ./.github/actions/docker-build). Reject anything
# that is not exactly quay.io/rhdh-community/rhdh:pr-<this PR number>[-suffix]
# so a poisoned artifact cannot overwrite :next / :latest / :release-* tags.
ALLOWED_RE="^quay\.io/rhdh-community/rhdh:pr-${PR_NUMBER}(-[A-Za-z0-9._-]+)?$"
while IFS= read -r tag; do
[ -z "$tag" ] && continue
if ! [[ "$tag" =~ $ALLOWED_RE ]]; then
echo "::error::Refusing to push disallowed tag from untrusted artifact: $tag"
exit 1
fi
done <<< "$TAGS_LIST"
# SECURITY: Use a random delimiter to prevent output injection from artifact poisoning
DELIMITER=$(openssl rand -hex 16)
echo "tags<<$DELIMITER" >> $GITHUB_OUTPUT
printf '%s\n' "$TAGS_LIST" >> $GITHUB_OUTPUT
echo "$DELIMITER" >> $GITHUB_OUTPUT
- name: Push Images
if: ${{ github.event.workflow_run.conclusion == 'success' && steps.check-skip.outputs.is_skipped != 'true' }}
uses: redhat-actions/push-to-registry@5ed88d269cf581ea9ef6dd6806d01562096bee9c # v2.8
env:
PUSHED_TAGS: ${{ steps.prepare.outputs.tags }}
with:
tags: ${{ env.PUSHED_TAGS }}
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_TOKEN }}
- name: Comment skip status
if: ${{ github.event.workflow_run.conclusion == 'success' && steps.check-skip.outputs.is_skipped == 'true' }}
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
env:
PR_NUMBER: ${{ steps.get-pr-info.outputs.pr_number }}
BUILD_WORKFLOW_URL: ${{ github.event.workflow_run.html_url }}
PUBLISH_WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
script: |
const prNumber = process.env.PR_NUMBER;
const buildUrl = process.env.BUILD_WORKFLOW_URL;
const publishUrl = process.env.PUBLISH_WORKFLOW_URL;
const body = `The container image [build](${buildUrl}) and [publish](${publishUrl}) workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).\n`;
github.rest.issues.createComment({
issue_number: parseInt(prNumber),
owner: context.repo.owner,
repo: context.repo.repo,
body: body
})
- name: Comment the image pull link
if: ${{ github.event.workflow_run.conclusion == 'success' && steps.check-skip.outputs.is_skipped != 'true' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
PUSHED_TAGS: ${{ steps.prepare.outputs.tags }}
PR_NUMBER: ${{ steps.get-pr-info.outputs.pr_number }}
BUILD_WORKFLOW_URL: ${{ github.event.workflow_run.html_url }}
PUBLISH_WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
script: |
const prNumber = process.env.PR_NUMBER;
const pushedTags = process.env.PUSHED_TAGS;
const buildUrl = process.env.BUILD_WORKFLOW_URL;
const publishUrl = process.env.PUBLISH_WORKFLOW_URL;
// This shouldn't happen since Push Images would've failed, but just in case a mistake is made in the workflow logic or logic changes
const noTagsError = (reason) => {
const body = `The image was [built](${buildUrl}) and [published](${publishUrl}) but no tags were found: ${reason}.\n`;
github.rest.issues.createComment({
issue_number: parseInt(prNumber),
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
core.setFailed(reason);
};
if (!pushedTags) {
noTagsError('No pushed tags output found');
return;
}
const tags = pushedTags.trim().split('\n').filter(tag => tag.trim());
if (tags.length === 0) {
noTagsError('Pushed tags output was empty');
return;
}
console.log(`Found ${tags.length} tags:`, tags);
const tagLinks = tags.map(fullTag => {
return `* [\`${fullTag}\`](https://${fullTag})`;
}).join('\n');
const body = `Image was [built](${buildUrl}) and [published](${publishUrl}) successfully. It is available at:\n\n${tagLinks}\n`;
console.log(`Creating comment for PR ${prNumber} with body:\n ${body}`);
github.rest.issues.createComment({
issue_number: parseInt(prNumber),
owner: context.repo.owner,
repo: context.repo.repo,
body: body
})
- name: Comment publish failure
if: ${{ failure() && github.event.workflow_run.conclusion == 'success' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
PR_NUMBER: ${{ steps.get-pr-info.outputs.pr_number }}
BUILD_WORKFLOW_URL: ${{ github.event.workflow_run.html_url }}
PUBLISH_WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
script: |
const prNumber = process.env.PR_NUMBER;
const buildUrl = process.env.BUILD_WORKFLOW_URL;
const publishUrl = process.env.PUBLISH_WORKFLOW_URL;
const body = `The container image was [built](${buildUrl}) successfully but failed to [publish](${publishUrl}) to the registry.\n`;
github.rest.issues.createComment({
issue_number: parseInt(prNumber),
owner: context.repo.owner,
repo: context.repo.repo,
body: body
})