Skip to content

Commit f400ef9

Browse files
ci: Clean unused documentation preview
Added a functionality to take care of obsolete PR-previews removal to ensure that in the new PRs, there are no redundant ones. Since we have this mechanism, we do not need post-pr clean workflow because all the obsolete workflows will be deleted once a new PR is created or after 7 days. Signed-off-by: Arkadiusz Balys <arkadiusz.balys@nordicsemi.no>
1 parent 828b1eb commit f400ef9

4 files changed

Lines changed: 158 additions & 166 deletions

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Cleanup site state: remove pr-preview directories for closed PRs.
2+
# Keeps only directories for currently open PRs so saved state matches reality.
3+
# This action keeps the existing pages only for the actually opened PRs to avoid restoring
4+
# the pages of closed PRs.
5+
6+
name: 'Cleanup site state'
7+
description: 'Remove PR preview directories for closed PRs; keep only open PRs in site state'
8+
9+
inputs:
10+
site_dir:
11+
description: 'Path to the site directory (e.g. _site) containing pr-preview subdirs'
12+
required: true
13+
14+
runs:
15+
using: 'composite'
16+
steps:
17+
- name: Collect list of open PRs
18+
id: open-prs
19+
shell: bash
20+
env:
21+
GH_TOKEN: ${{ github.token }}
22+
run: |
23+
set -e
24+
25+
# Build JSON array of open PR numbers for use in next step.
26+
if ! OPEN_PRS_JSON=$(gh pr list --state open --json number -q '.[].number' --limit 100 | jq -R -s -c '[split("\n")[] | select(length > 0) | tonumber]'); then
27+
echo "Error: Failed to list open PRs" >&2
28+
exit 1
29+
fi
30+
31+
# Validate the output is valid JSON array
32+
if ! echo "$OPEN_PRS_JSON" | jq -e 'type == "array"' >/dev/null 2>&1; then
33+
echo "Error: Invalid JSON output from PR list" >&2
34+
exit 1
35+
fi
36+
37+
echo "open_prs_json=$OPEN_PRS_JSON" >> "$GITHUB_OUTPUT"
38+
echo "Found $(echo "$OPEN_PRS_JSON" | jq 'length') open PR(s)"
39+
40+
- name: Remove closed PR preview directories
41+
shell: bash
42+
env:
43+
SITE_DIR: ${{ inputs.site_dir }}
44+
OPEN_PRS_JSON: ${{ steps.open-prs.outputs.open_prs_json }}
45+
run: |
46+
set -e
47+
48+
# Validate site_dir input
49+
if [ -z "$SITE_DIR" ]; then
50+
echo "Error: site_dir is empty" >&2
51+
exit 1
52+
fi
53+
54+
# Reject paths with path traversal, absolute paths, or control characters
55+
if [[ "$SITE_DIR" == *".."* ]] || \
56+
[[ "$SITE_DIR" == /* ]] || \
57+
[[ "$SITE_DIR" == *$'\n'* ]] || \
58+
[[ "$SITE_DIR" == *$'\r'* ]] || \
59+
[[ "$SITE_DIR" == *$'\t'* ]]; then
60+
echo "Error: Invalid site_dir (path traversal or absolute path)" >&2
61+
exit 1
62+
fi
63+
64+
# Get absolute path of workspace root for boundary checking
65+
WORKSPACE_ROOT=$(cd "$GITHUB_WORKSPACE" && pwd)
66+
67+
# Resolve and validate site_dir path
68+
if [ ! -d "$SITE_DIR" ]; then
69+
echo "Info: site_dir does not exist: $SITE_DIR (first run or no previous state)" >&2
70+
exit 0
71+
fi
72+
73+
RESOLVED_SITE_DIR=$(cd "$SITE_DIR" && pwd)
74+
75+
# Ensure resolved path is within workspace
76+
if [[ "$RESOLVED_SITE_DIR" != "$WORKSPACE_ROOT"* ]]; then
77+
echo "Error: site_dir resolves outside workspace" >&2
78+
exit 1
79+
fi
80+
81+
PR_PREVIEW="${RESOLVED_SITE_DIR}/pr-preview"
82+
83+
# Validate PR_PREVIEW path is still within workspace
84+
if [[ "$PR_PREVIEW" != "$WORKSPACE_ROOT"* ]]; then
85+
echo "Error: PR_PREVIEW path escapes workspace" >&2
86+
exit 1
87+
fi
88+
89+
# If there is no previews, just exit
90+
if [ ! -d "$PR_PREVIEW" ]; then
91+
exit 0
92+
fi
93+
94+
# Process each pr-* directory
95+
for dir in "$PR_PREVIEW"/pr-*; do
96+
RESOLVED_DIR=$(cd "$dir" && pwd)
97+
98+
# Skip if glob didn't match anything, files, symlinks, etc.
99+
if [ ! -e "$dir" ] || [ ! -d "$dir" ] || [[ "$RESOLVED_DIR" != "$WORKSPACE_ROOT"* ]] || [[ "$RESOLVED_DIR" != "$PR_PREVIEW"* ]]; then
100+
continue
101+
fi
102+
name=$(basename "$dir")
103+
104+
# Validate directory name matches expected pattern (pr- followed by digits only)
105+
if [[ "$name" =~ ^pr-([0-9]+)$ ]]; then
106+
pr_num="${BASH_REMATCH[1]}"
107+
108+
# Validate pr_num is numeric (extra safety)
109+
if ! [[ "$pr_num" =~ ^[0-9]+$ ]]; then
110+
echo "Warning: Invalid PR number extracted: $pr_num" >&2
111+
continue
112+
fi
113+
114+
# Validate OPEN_PRS_JSON is valid before using it
115+
if ! echo "$OPEN_PRS_JSON" | jq -e 'type == "array"' >/dev/null 2>&1; then
116+
echo "Error: Invalid OPEN_PRS_JSON format" >&2
117+
exit 1
118+
fi
119+
120+
# Check if PR is still open
121+
if ! echo "$OPEN_PRS_JSON" | jq -e --argjson n "$pr_num" 'index($n) != null' >/dev/null 2>&1; then
122+
rm -rf "$dir"
123+
fi
124+
fi
125+
done
126+
127+
# Clean up empty pr-preview directory
128+
if [ -d "$PR_PREVIEW" ] && [ -z "$(ls -A "$PR_PREVIEW" 2>/dev/null)" ]; then
129+
rm -rf "$PR_PREVIEW"
130+
fi

.github/workflows/docpreview.yml

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,40 +30,51 @@ jobs:
3030
- name: Checkout repository
3131
uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4
3232

33-
- name: Download site state from cleanup
34-
id: download-cleanup
35-
uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6
36-
with:
37-
workflow: docremove.yml
38-
name: site-state
39-
path: _existing_pages
40-
workflow_conclusion: success
41-
search_artifacts: true
42-
if_no_artifact_found: warn
43-
continue-on-error: true
44-
4533
- name: Download site state from deploy
4634
id: download-deploy
47-
if: steps.download-cleanup.outcome == 'failure'
4835
uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6
4936
with:
5037
workflow: docpreview.yml
5138
name: site-state
52-
path: _existing_pages
39+
path: ${{ runner.temp }}/existing_pages
5340
workflow_conclusion: success
5441
search_artifacts: true
5542
if_no_artifact_found: warn
56-
continue-on-error: true
43+
continue-on-error: false
5744

5845
- name: Remove symlinks from downloaded artifacts
5946
run: |
60-
find _existing_pages -type l -delete 2>/dev/null || true
47+
if [ -d "${{ runner.temp }}/existing_pages" ]; then
48+
find "${{ runner.temp }}/existing_pages" -type l -delete 2>/dev/null || true
49+
rm -rf _existing_pages
50+
mkdir -p _existing_pages
51+
cp -a "${{ runner.temp }}/existing_pages/." _existing_pages/ || true
52+
fi
6153
6254
- name: Download build artifacts
6355
uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6
6456
with:
6557
workflow: docbuild.yml
6658
run_id: ${{ github.event.workflow_run.id }}
59+
path: ${{ runner.temp }}
60+
61+
- name: Move doc-preview from temp
62+
run: |
63+
if [ -d "doc-preview" ]; then
64+
rm -rf doc-preview
65+
fi
66+
if [ -d "${{ runner.temp }}/doc-preview" ]; then
67+
mv "${{ runner.temp }}/doc-preview" ./doc-preview
68+
else
69+
echo "Expected doc-preview directory not found in runner.temp" >&2
70+
exit 1
71+
fi
72+
73+
- name: Cleanup site state
74+
id: download-site-state
75+
uses: ./.github/actions/cleanup-site
76+
with:
77+
site_dir: _existing_pages
6778

6879
- name: Extract version and unzip docs
6980
working-directory: doc-preview
@@ -123,7 +134,7 @@ jobs:
123134
with:
124135
name: site-state
125136
path: _site
126-
retention-days: 14 # 2 weeks
137+
retention-days: 7 # 1 week, every rebase updates the artifacts
127138
overwrite: true
128139

129140
- name: Find existing comment

.github/workflows/docremove.yml

Lines changed: 0 additions & 123 deletions
This file was deleted.

.github/workflows/post_pr.yml

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)