Repository navigation
245 lines (210 loc) · 9.26 KB
/
Copy pathdeploy-docs-preview.yml
File metadata and controls
245 lines (210 loc) · 9.26 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
name: Docs Preview Post-Build
on:
workflow_run:
workflows: ["Build Docs Preview"]
types: [completed]
permissions:
contents: write
pull-requests: write
actions: read
concurrency:
group: docs-preview-post-build-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: false
env:
PREVIEW_RETENTION_LIMIT: 6
jobs:
deploy-preview:
runs-on: ubuntu-24.04
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: true
- name: Download Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: docs-preview-build
path: temp-artifact
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- name: Read Metadata
id: meta
run: |
ACTION=$(head -n1 temp-artifact/pr/action 2>/dev/null | tr -d '[:space:]')
case "$ACTION" in
opened|reopened|synchronize) ;;
*) echo "::error::invalid or missing action: '$ACTION'"; exit 1 ;;
esac
PR_NUM=$(head -n1 temp-artifact/pr/number 2>/dev/null | tr -d '[:space:]')
case "$PR_NUM" in
''|*[!0-9]*) echo "::error::invalid or missing PR number: '$PR_NUM'"; exit 1 ;;
esac
SHA=$(head -n1 temp-artifact/pr/sha 2>/dev/null | tr -d '[:space:]')
case "$SHA" in
''|*[!a-f0-9]*) echo "::error::invalid or missing commit SHA: '$SHA'"; exit 1 ;;
esac
echo "action=$ACTION" >> "$GITHUB_OUTPUT"
echo "pr_number=$PR_NUM" >> "$GITHUB_OUTPUT"
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
- name: Deploy PR preview
uses: rossjrw/pr-preview-action@ffa7509e91a3ec8dfc2e5536c4d5c1acdf7a6de9 # v1.8.1
with:
source-dir: ./temp-artifact/public
preview-branch: gh-pages
umbrella-dir: pr-preview
pr-number: ${{ steps.meta.outputs.pr_number }}
pages-base-url: docs.layer5.io
action: deploy
wait-for-pages-deployment: false
comment: false
- name: Comment PR with Preview URL
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
with:
header: pr-preview
number: ${{ steps.meta.outputs.pr_number }}
message: |
🚀 Preview deployment: https://docs.layer5.io/pr-preview/pr-${{ steps.meta.outputs.pr_number }}/
> *Note: Preview may take a moment (GitHub Pages deployment in progress). Please wait and refresh. Track deployment [here](https://github.com/${{ github.repository }}/actions/workflows/pages/pages-build-deployment)*
prune:
# Both jobs push to gh-pages, but only prune retries a lost race, so deploy
# must land first.
needs: deploy-preview
runs-on: ubuntu-24.04
outputs:
removed_prs: ${{ steps.prune-previews.outputs.removed_prs }}
removed_prs_json: ${{ steps.prune-previews.outputs.removed_prs_json }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout gh-pages for preview retention
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: gh-pages
fetch-depth: 0
persist-credentials: true # required: the prune step pushes to gh-pages
sparse-checkout: |
pr-preview
path: gh-pages-maintenance
- name: Prune old PR previews
id: prune-previews
# Pruning is best-effort housekeeping on the shared gh-pages branch.
# Many preview runs write to gh-pages at once, so it must never be
# the reason a preview is reported as failed: the push is retried against
# the freshest state, and the step is continue-on-error as a backstop.
continue-on-error: true
run: |
cd gh-pages-maintenance
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Write safe defaults first. $GITHUB_OUTPUT is last-write-wins, so even
# if this step is interrupted, the downstream "Comment on pruned
# previews" step sees a valid empty list rather than an empty string.
{
echo "removed_prs="
echo "removed_prs_json=[]"
} >> "$GITHUB_OUTPUT"
# A naive push to the shared gh-pages branch loses the race with other
# concurrent preview runs ("! [rejected] (fetch first)"). Re-derive the
# prune from the freshest remote state on every attempt and retry with
# backoff so a lost race self-heals instead of failing the workflow.
removed_prs=()
outcome=""
attempts=5
for attempt in $(seq 1 "$attempts"); do
git fetch --quiet origin gh-pages
git reset --quiet --hard FETCH_HEAD
mkdir -p pr-preview
mapfile -t previews < <(
while IFS= read -r preview; do
timestamp="$(git log -1 --format=%ct -- "pr-preview/$preview" 2>/dev/null || echo 0)"
printf '%s %s\n' "$timestamp" "$preview"
done < <(find pr-preview -mindepth 1 -maxdepth 1 -type d -name 'pr-*' -printf '%f\n') \
| sort -nr \
| awk '{print $2}'
)
removed_prs=()
if (( ${#previews[@]} > PREVIEW_RETENTION_LIMIT )); then
for preview in "${previews[@]:PREVIEW_RETENTION_LIMIT}"; do
rm -rf "pr-preview/$preview"
removed_prs+=("${preview#pr-}")
done
fi
# Within the retention limit, or another run already pruned: done.
if git diff --quiet -- pr-preview; then
outcome="noop"
break
fi
git add pr-preview
git commit --quiet -m "Prune old PR previews"
if git push origin HEAD:gh-pages; then
outcome="pushed"
break
fi
echo "Prune push lost the race (attempt ${attempt}/${attempts}); re-syncing gh-pages and retrying..."
sleep "$(( attempt * 5 + RANDOM % 5 ))"
done
if [ -z "$outcome" ]; then
echo "::warning::Could not prune old PR previews after ${attempts} attempts due to concurrent gh-pages updates; a later run will retry. Not failing the preview."
removed_prs=()
fi
if [ "${#removed_prs[@]}" -eq 0 ]; then
{
echo "removed_prs="
echo "removed_prs_json=[]"
} >> "$GITHUB_OUTPUT"
else
{
echo "removed_prs=$(IFS=,; echo "${removed_prs[*]}")"
echo "removed_prs_json=$(printf '%s\n' "${removed_prs[@]}" | jq -R . | jq -sc .)"
} >> "$GITHUB_OUTPUT"
fi
- name: Comment on pruned previews
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
REMOVED_PRS_JSON: ${{ steps.prune-previews.outputs.removed_prs_json }}
PREVIEW_RETENTION_LIMIT: ${{ env.PREVIEW_RETENTION_LIMIT }}
with:
script: |
const removedPrs = JSON.parse(process.env.REMOVED_PRS_JSON);
const retentionLimit = process.env.PREVIEW_RETENTION_LIMIT;
const header = "pr-preview";
const marker = `<!-- Sticky Pull Request Comment${header} -->`;
for (const prNumber of removedPrs) {
const body =
`Preview deployment for PR #${prNumber} removed.\n\n` +
`This PR preview was automatically pruned because we keep only the ${retentionLimit} most recently updated previews on GitHub Pages to stay within deployment size limits.\n\n` +
`If needed, push a new commit to this PR to generate a fresh preview.\n` +
`${marker}`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(prNumber),
per_page: 100,
});
const existingComment = [...comments].reverse().find((comment) =>
comment.user?.login === "github-actions[bot]" &&
comment.body?.includes(marker)
);
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body,
});
continue;
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(prNumber),
body,
});
}