test(ci): validate link checker with temporary broken-link fixtures #702
Workflow file for this run
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
| name: Link Checker | |
| on: [push, pull_request] | |
| permissions: | |
| contents: read | |
| checks: write # required to post the neutral "Link Redirects" check-run | |
| jobs: | |
| linkchecker: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6 | |
| # Blocking: fail the build only on genuinely broken links (4xx/5xx, | |
| # DNS failures, timeouts). Redirects are accepted here so a moved link | |
| # does not break CI on its own -- the pass below surfaces those. | |
| - name: Check for broken links | |
| id: lychee-broken | |
| uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0 | |
| with: | |
| args: --accept=200,206,301,302,307,308,403,429 --max-retries=3 --retry-wait-time=10 "**/*.html" "**/*.md" "**/*.txt" "**/*.json" --exclude "https://opensearch-domain.region.com/" --exclude "https://github.com/\[your*" --exclude "https://localhost:9200" --exclude "https://codecov.io/gh/opensearch-project/opensearch-go/branch/main/graph/badge.svg" | |
| fail: true | |
| jobSummary: true | |
| env: | |
| GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} | |
| # Non-blocking: surface permanent redirects (301/308) so their links can | |
| # be updated to point at the final destination. --max-redirects=0 makes | |
| # lychee report the redirect status instead of following it; 302/307 stay | |
| # accepted because temporary redirects are not a signal to rewrite a link. | |
| # LICENSE.txt/NOTICE.txt are excluded here: their URLs are canonical | |
| # license text and standard attribution wording, not links to rewrite. | |
| # fail:false + if:always() so this always reports without breaking CI. | |
| - name: Check for permanently-redirecting links | |
| id: lychee-redirects | |
| if: always() | |
| uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0 | |
| with: | |
| args: --accept=200,206,302,307,403,429 --max-redirects=0 --max-retries=3 --retry-wait-time=10 --exclude-path LICENSE.txt --exclude-path NOTICE.txt "**/*.html" "**/*.md" "**/*.txt" "**/*.json" --exclude "https://opensearch-domain.region.com/" --exclude "https://github.com/\[your*" --exclude "https://localhost:9200" --exclude "https://codecov.io/gh/opensearch-project/opensearch-go/branch/main/graph/badge.svg" | |
| fail: false | |
| jobSummary: true | |
| output: lychee/redirects.md | |
| env: | |
| GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} | |
| # Turn each redirect the pass above found into a PR annotation, so a moved | |
| # link shows up inline on the PR (Files changed + checks summary) instead | |
| # of only in the run's job summary. Works on fork PRs too. Never fails. | |
| - name: Annotate redirecting links | |
| if: always() | |
| run: | | |
| report="lychee/redirects.md" | |
| [ -f "$report" ] || { echo "no redirect report found; nothing to annotate"; exit 0; } | |
| awk ' | |
| /^### Errors in / { file=$0; sub(/^### Errors in /, "", file); next } | |
| /^\* \[30[0-9]\]/ { | |
| url=$0; sub(/^[^<]*</, "", url); sub(/>.*/, "", url) | |
| code=$0; sub(/^\* \[/, "", code); sub(/\].*/, "", code) | |
| line=""; col=""; tmp=$0 | |
| if (sub(/^.*\(at /, "", tmp) && match(tmp, /^[0-9]+:[0-9]+/)) { | |
| pos=substr(tmp, 1, RLENGTH); split(pos, a, ":"); line=a[1]; col=a[2] | |
| } | |
| printf "::warning file=%s,line=%s,col=%s,title=Redirecting link::%s redirects (%s) -- update to its final destination\n", file, line, col, url, code | |
| } | |
| ' "$report" | |
| # Publish a distinct "Link Redirects" check-run with a neutral conclusion | |
| # when permanent redirects are found, so a moved link shows up as its own | |
| # yellow (neutral) badge in the PR checks list -- not just an annotation | |
| # on the linkchecker job. Neutral never blocks merge. | |
| # | |
| # Creating a check-run needs a checks:write token. Fork PRs get a | |
| # read-only GITHUB_TOKEN, so the API call 403s there; the step swallows | |
| # that (|| true) and the annotation step above remains the fork-PR signal. | |
| - name: Publish redirect check-run (neutral) | |
| if: always() | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| # PR events: annotate the PR head, not the synthetic merge commit. | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| report="lychee/redirects.md" | |
| # Build a markdown list of the redirecting links from the report. | |
| links="$(awk ' | |
| /^### Errors in / { file=$0; sub(/^### Errors in /, "", file); next } | |
| /^\* \[30[0-9]\]/ { | |
| url=$0; sub(/^[^<]*</, "", url); sub(/>.*/, "", url) | |
| code=$0; sub(/^\* \[/, "", code); sub(/\].*/, "", code) | |
| printf "- `%s:%s` [%s] %s\n", file, "", code, url | |
| } | |
| ' "$report" 2>/dev/null || true)" | |
| if [ -z "$links" ]; then | |
| conclusion="success" | |
| title="No redirecting links" | |
| summary="All checked links resolve directly (no permanent redirects)." | |
| else | |
| conclusion="neutral" | |
| count="$(printf '%s\n' "$links" | grep -c '^- ')" | |
| title="$count redirecting link(s) should be updated" | |
| summary=$(printf 'These links return a permanent redirect (301/308). Update them to their final destination.\n\n%s' "$links") | |
| fi | |
| # Post the check-run. checks:write is unavailable on fork PRs, where | |
| # this 403s; do not fail the job over it. | |
| gh api "repos/$REPO/check-runs" \ | |
| -f name='Link Redirects' \ | |
| -f head_sha="$HEAD_SHA" \ | |
| -f status='completed' \ | |
| -f conclusion="$conclusion" \ | |
| -f "output[title]=$title" \ | |
| -f "output[summary]=$summary" \ | |
| -f "details_url=$RUN_URL" \ | |
| >/dev/null \ | |
| && echo "posted 'Link Redirects' check-run: $conclusion" \ | |
| || echo "could not post check-run (expected on fork PRs with a read-only token); the annotation step already surfaced redirects" |