-
Notifications
You must be signed in to change notification settings - Fork 157
c.com Link checker fix #2846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
petesfrench
wants to merge
12
commits into
main
Choose a base branch
from
link-checker-fix
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
c.com Link checker fix #2846
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
0bba553
fix: failing links from linkchecker
petesfrench 41f2229
feat: add retry mechanism to linkchecker
petesfrench d975ea6
Adjust live links job naming
petesfrench 8265998
refactor(live-links): retry mechanism to handle runtime errors gracef…
petesfrench 375da48
Merge branch 'link-checker-fix' of github.com:canonical/canonical.com…
petesfrench 6077baa
Merge branch 'main' into link-checker-fix
petesfrench 35cf769
refactor: break out live-links custom script into checklinks.sh and c…
petesfrench e11a4b3
Merge branch 'link-checker-fix' of github.com:canonical/canonical.com…
petesfrench 99be647
chore: add shell scripts tests
muhammad-ali-pk 3d50a15
Merge branch 'main' into link-checker-fix
muhammad-ali-pk 8879f3d
fix: don't persist credential on live-links job
petesfrench bb7a468
fix: update sitemap link for 12-factor documentation
petesfrench File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| [checking] | ||
| maxrequestspersecond=5 | ||
| recursionlevel=2 | ||
| timeout=60 | ||
| sslverify=0 | ||
|
|
||
| [filtering] | ||
| checkextern=1 | ||
| ignore= | ||
| https://res.cloudinary.com | ||
| q_auto | ||
| fl_sanitize | ||
| c_fill | ||
| e_sharpen | ||
| w_[0-9]* | ||
| h_[0-9]* | ||
| https://canonical.com/blog | ||
| https://ubuntu.com/blog | ||
| https://canonical.com/static/css/* | ||
| https://www.xilinx.com/* | ||
| https://player.vimeo.com/video/* | ||
| http://atea.com | ||
| https://linuxpolska.com/* | ||
| https://www.packet.net | ||
| https://www.amd.com/ | ||
| https://start.microsemi.com | ||
| http://www.omnicore-it.com/ | ||
| http://www.pgp.com/ | ||
| https://www.hcl.com/ | ||
| http://www.randrinc.com/ | ||
| https://www.storagemadeeasy.com/ | ||
|
|
||
| [output] | ||
| status=0 | ||
| warnings=0 | ||
| ignoreerrors= | ||
| ^https?://.* ^.*(471|500|503|504|400|403|401) | ||
| ^https?://.* Connection aborted | ||
| ^https?://.* Connection reset by peer | ||
| ^https?://.* RemoteDisconnected | ||
| ^https?://.* Read timed out |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| #!/usr/bin/env bash | ||
| # check-links.sh — run LinkChecker with retry logic for transient failures. | ||
| # | ||
| # Usage: | ||
| # ./scripts/check-links.sh [URL] | ||
| # | ||
| # Defaults to https://canonical.com when no URL is given. | ||
| # Uses .linkchecker/linkcheckerrc from the repo root for config. | ||
| # | ||
| # Examples: | ||
| # ./scripts/check-links.sh # check live site | ||
| # ./scripts/check-links.sh http://localhost:8002 # check local dev server | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" | ||
| TARGET_URL="${1:-https://canonical.com}" | ||
|
|
||
| runtime_error() { | ||
| echo "LinkChecker failed unexpectedly. See the output above for details." >&2 | ||
| exit 2 | ||
| } | ||
|
|
||
| # Print the unique failed URLs (valid=False) from a linkchecker CSV. | ||
| extract_failed() { | ||
| python3 - "$1" <<'PY' | ||
| import csv | ||
| import sys | ||
|
|
||
| seen = set() | ||
| with open(sys.argv[1], newline="") as f: | ||
| rows = (line for line in f if not line.startswith("#")) | ||
| for row in csv.DictReader(rows, delimiter=";"): | ||
| if row.get("valid", "").strip().lower() == "false": | ||
| url = row.get("urlname") | ||
| if url and url not in seen: | ||
| seen.add(url) | ||
| print(url) | ||
| PY | ||
| } | ||
|
|
||
| WORK_DIR="$(mktemp -d)" | ||
| trap 'rm -rf "$WORK_DIR"' EXIT | ||
|
|
||
| if linkchecker \ | ||
| --config "${REPO_ROOT}/.linkchecker/linkcheckerrc" \ | ||
| --no-warning \ | ||
| -F "csv/${WORK_DIR}/failed-links.csv" \ | ||
| "${TARGET_URL}"; then | ||
| echo "No broken links found." | ||
| exit 0 | ||
| else | ||
| checker_status=$? | ||
| fi | ||
|
|
||
| [ "$checker_status" -eq 1 ] || runtime_error | ||
| extract_failed "${WORK_DIR}/failed-links.csv" > "${WORK_DIR}/current-urls.txt" || runtime_error | ||
| [ -s "${WORK_DIR}/current-urls.txt" ] || runtime_error | ||
|
|
||
| attempt=1 | ||
| max_attempts=3 | ||
| delay=60 | ||
|
|
||
| # Re-check only the previously failed links | ||
| while [ "$attempt" -le "$max_attempts" ] && [ -s "${WORK_DIR}/current-urls.txt" ]; do | ||
| echo "Attempt $attempt/$max_attempts: rechecking $(wc -l < "${WORK_DIR}/current-urls.txt") link(s) after ${delay}s" | ||
| sleep "$delay" | ||
|
|
||
| mapfile -t urls < "${WORK_DIR}/current-urls.txt" | ||
| retry_csv="${WORK_DIR}/retry-${attempt}.csv" | ||
| if linkchecker \ | ||
| --config "${REPO_ROOT}/.linkchecker/linkcheckerrc" \ | ||
| --no-warning \ | ||
| --recursion-level=0 \ | ||
| --timeout=30 \ | ||
| -F "csv/${retry_csv}" \ | ||
| "${urls[@]}"; then | ||
| echo "All remaining links resolved on attempt $attempt." | ||
| exit 0 | ||
| else | ||
| checker_status=$? | ||
| fi | ||
|
|
||
| [ "$checker_status" -eq 1 ] || runtime_error | ||
| extract_failed "$retry_csv" > "${WORK_DIR}/next-urls.txt" || runtime_error | ||
| [ -s "${WORK_DIR}/next-urls.txt" ] || runtime_error | ||
| mv "${WORK_DIR}/next-urls.txt" "${WORK_DIR}/current-urls.txt" | ||
| attempt=$((attempt + 1)) | ||
| delay=$((delay * 2)) | ||
| done | ||
|
|
||
| echo "Links still broken after $max_attempts attempts:" | ||
| cat "${WORK_DIR}/current-urls.txt" | ||
| exit 1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| #!/usr/bin/env bash | ||
| # Smoke test for scripts/check-links.sh. | ||
| # | ||
| # Puts a fake `linkchecker` and a no-op `sleep` on PATH, so the real network and | ||
| # the retry backoff are never hit. Run with: bash tests/shell/test-check-links.sh | ||
|
|
||
| set -uo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" | ||
| TARGET="${REPO_ROOT}/scripts/check-links.sh" | ||
|
|
||
| STUB_DIR="$(mktemp -d)" | ||
| trap 'rm -rf "$STUB_DIR"' EXIT | ||
|
|
||
| # STUB_EXITS: space-separated exit code per invocation. | ||
| # STUB_FAILS: space-separated group per invocation; comma-separated failing | ||
| # URLs within a group, or "-" for none. | ||
| cat > "${STUB_DIR}/linkchecker" <<'STUB' | ||
| #!/usr/bin/env bash | ||
| out="" | ||
| prev="" | ||
| for arg in "$@"; do | ||
| [ "$prev" = "-F" ] && out="${arg#csv/}" | ||
| prev="$arg" | ||
| done | ||
|
|
||
| n=$(( $(cat "$STUB_STATE") + 1 )) | ||
| echo "$n" > "$STUB_STATE" | ||
| printf '%s\n' "$*" >> "$STUB_LOG" | ||
|
|
||
| read -ra exits <<< "$STUB_EXITS" | ||
| read -ra groups <<< "$STUB_FAILS" | ||
| code="${exits[$((n - 1))]:-0}" | ||
| fails="${groups[$((n - 1))]:--}" | ||
|
|
||
| if [ -n "$out" ]; then | ||
| { | ||
| echo "# generated by fake linkchecker" | ||
| echo "urlname;parentname;valid" | ||
| if [ "$fails" != "-" ]; then | ||
| IFS=',' read -ra urls <<< "$fails" | ||
| for u in "${urls[@]}"; do echo "${u};https://example.com;False"; done | ||
| fi | ||
| echo "https://example.com/ok;https://example.com;True" | ||
| } > "$out" | ||
| fi | ||
|
|
||
| exit "$code" | ||
| STUB | ||
| chmod +x "${STUB_DIR}/linkchecker" | ||
|
|
||
| printf '#!/usr/bin/env bash\nexit 0\n' > "${STUB_DIR}/sleep" | ||
| chmod +x "${STUB_DIR}/sleep" | ||
|
|
||
| export STUB_STATE="${STUB_DIR}/count" | ||
| export STUB_LOG="${STUB_DIR}/log" | ||
|
|
||
| failures=0 | ||
| status=0 | ||
| output="" | ||
|
|
||
| run_case() { | ||
| export STUB_EXITS="$1" | ||
| export STUB_FAILS="$2" | ||
| echo 0 > "$STUB_STATE" | ||
| : > "$STUB_LOG" | ||
| output="$(PATH="${STUB_DIR}:${PATH}" bash "$TARGET" https://example.com 2>&1)" | ||
| status=$? | ||
| } | ||
|
|
||
| check() { | ||
| local name="$1" expected="$2" actual="$3" | ||
| if [ "$expected" = "$actual" ]; then | ||
| echo "ok - ${name}" | ||
| else | ||
| echo "FAIL - ${name}: expected '${expected}', got '${actual}'" | ||
| printf '%s\n' "$output" | sed 's/^/ | /' | ||
| failures=$((failures + 1)) | ||
| fi | ||
| } | ||
|
|
||
| contains() { | ||
| local name="$1" needle="$2" | ||
| case "$output" in | ||
| *"$needle"*) echo "ok - ${name}" ;; | ||
| *) | ||
| echo "FAIL - ${name}: output missing '${needle}'" | ||
| printf '%s\n' "$output" | sed 's/^/ | /' | ||
| failures=$((failures + 1)) | ||
| ;; | ||
| esac | ||
| } | ||
|
|
||
| # 1. Clean run. | ||
| run_case "0" "-" | ||
| check "clean run exits 0" 0 "$status" | ||
| contains "clean run reports success" "No broken links found." | ||
|
|
||
| # 2. Transient failure that clears on the first retry. | ||
| run_case "1 0" "https://example.com/flaky -" | ||
| check "transient failure exits 0" 0 "$status" | ||
| contains "transient failure reports recovery" "resolved on attempt 1" | ||
|
|
||
| # 3. Persistent failure: initial run + 3 retries, then give up. | ||
| run_case "1 1 1 1" "https://example.com/dead https://example.com/dead https://example.com/dead https://example.com/dead" | ||
| check "persistent failure exits 1" 1 "$status" | ||
| contains "persistent failure lists the URL" "https://example.com/dead" | ||
| check "persistent failure uses all attempts" 4 "$(cat "$STUB_STATE")" | ||
|
|
||
| # 4. Unexpected linkchecker exit code is not treated as a broken link. | ||
| run_case "3" "-" | ||
| check "runtime error exits 2" 2 "$status" | ||
| contains "runtime error is reported" "LinkChecker failed unexpectedly" | ||
|
|
||
| # 5. Duplicate failures are de-duplicated before the recheck. | ||
| run_case "1 0" "https://example.com/a,https://example.com/a,https://example.com/b -" | ||
| check "dedup run exits 0" 0 "$status" | ||
| check "recheck receives 1 copy of /a" 1 "$(grep -c -o 'https://example.com/a' <<< "$(sed -n 2p "$STUB_LOG")")" | ||
| contains "recheck reports 2 links" "rechecking 2 link(s)" | ||
|
|
||
| echo | ||
| if [ "$failures" -eq 0 ]; then | ||
| echo "All check-links.sh tests passed." | ||
| else | ||
| echo "${failures} test(s) failed." | ||
| fi | ||
| exit $(( failures > 0 )) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.