master
sh 107 lines 4.32 KB
Raw
1 #!/usr/bin/env bash
2 # Fetch all pages of a Coverity Scan view table.
3 #
4 # Usage:
5 # fetch-table.sh <viewId> <pages> <output-prefix>
6 # Example:
7 # fetch-table.sh 41549 7 .local/audits/coverity/raw/outstanding
8 #
9 # Coverity's UI uses two steps per page:
10 # 1) POST /views/table.json {projectId, viewId, pageNum} -- updates server view state
11 # 2) GET /reports/table.json?projectId=X&viewId=Y -- fetches rows for current state
12 #
13 # The script combines all pages into <prefix>-all.json (a flat array).
14 # Pages already on disk are skipped (idempotent).
15
16 set -euo pipefail
17
18 # shellcheck source=./_lib.sh
19 # shellcheck disable=SC1091
20 source "$(dirname "$0")/_lib.sh"
21 cov_load_env
22
23 VIEW_ID="${1:?usage: $0 <viewId> <pages> <output-prefix>}"
24 PAGES="${2:?usage: $0 <viewId> <pages> <output-prefix>}"
25 PREFIX="${3:?usage: $0 <viewId> <pages> <output-prefix>}"
26
27 # View IDs and page counts are positive integers. Reject anything else
28 # before the loop -- otherwise non-numeric PAGES would make `seq` produce
29 # no output, and the final jq merge would block waiting on stdin.
30 if [[ ! "${VIEW_ID}" =~ ^[1-9][0-9]*$ ]]; then
31 echo -e "${COV_RED}[ERROR]${COV_NC} viewId must be a positive integer, got: '${VIEW_ID}'" >&2
32 exit 1
33 fi
34 if [[ ! "${PAGES}" =~ ^[1-9][0-9]*$ ]]; then
35 echo -e "${COV_RED}[ERROR]${COV_NC} pages must be a positive integer, got: '${PAGES}'" >&2
36 exit 1
37 fi
38
39 mkdir -p "$(dirname "${PREFIX}")"
40
41 for page in $(seq 1 "${PAGES}"); do
42 out="${PREFIX}-page${page}.json"
43 if [[ -s "${out}" ]]; then
44 echo -e "${COV_GRAY}[page ${page}/${PAGES}] cached ${out}${COV_NC}" >&2
45 continue
46 fi
47
48 # Step 1: set server-side page state.
49 post_body="{\"projectId\":${COVERITY_PROJECT_ID},\"viewId\":${VIEW_ID},\"pageNum\":${page}}"
50 post_http=$(curl -sS -o /dev/null -w '%{http_code}' -X POST \
51 -H "accept: application/json, text/plain, */*" \
52 -H "content-type: application/json" \
53 -H "origin: ${COVERITY_HOST}" \
54 -H "referer: ${COVERITY_HOST}/" \
55 -H "user-agent: ${COVERITY_USER_AGENT}" \
56 -H "x-xsrf-token: ${COVERITY_XSRF}" \
57 -b "${COVERITY_COOKIE}" \
58 --data-raw "${post_body}" \
59 "${COVERITY_HOST}/views/table.json")
60 if [[ "${post_http}" != "200" ]]; then
61 echo -e "${COV_RED}[page ${page}] POST failed HTTP=${post_http}${COV_NC}" >&2
62 exit 2
63 fi
64
65 # Step 2: fetch the now-current page.
66 # Capture exit status of curl explicitly so a transport failure (timeout,
67 # connection reset, DNS) doesn't leave a non-empty partial file behind
68 # that the next run would treat as cached.
69 get_http=$(curl -sS -o "${out}" -w '%{http_code}' \
70 -H "accept: application/json, text/plain, */*" \
71 -H "referer: ${COVERITY_HOST}/" \
72 -H "user-agent: ${COVERITY_USER_AGENT}" \
73 -b "${COVERITY_COOKIE}" \
74 "${COVERITY_HOST}/reports/table.json?projectId=${COVERITY_PROJECT_ID}&viewId=${VIEW_ID}" \
75 || echo "000")
76 if [[ "${get_http}" != "200" ]]; then
77 echo -e "${COV_RED}[page ${page}] GET failed HTTP=${get_http}${COV_NC}" >&2
78 rm -f "${out}"
79 exit 3
80 fi
81 # Validate response is JSON with the expected shape -- a Cloudflare
82 # challenge or a 200-with-HTML-body would otherwise be cached as
83 # 'valid' and confuse subsequent runs.
84 if ! jq -e '.resultSet.results | type == "array"' "${out}" >/dev/null 2>&1; then
85 echo -e "${COV_RED}[page ${page}] response not in expected shape; first 200 chars:${COV_NC}" >&2
86 head -c 200 "${out}" >&2; echo >&2
87 rm -f "${out}"
88 exit 3
89 fi
90
91 count=$(jq '.resultSet.results | length' "${out}")
92 total=$(jq '.resultSet.totalCount' "${out}")
93 echo -e "${COV_GRAY}[page ${page}/${PAGES}] fetched ${count} rows (total=${total})${COV_NC}" >&2
94 sleep 0.3
95 done
96
97 combined="${PREFIX}-all.json"
98 # Build the explicit page-file list. A glob (`-page*.json`) would pick up
99 # stale page files from a previous larger fetch (e.g. running with PAGES=5
100 # after a prior run with PAGES=7 would merge 7 pages into "all"). Enumerate
101 # only the pages we asked for in this invocation.
102 page_files=()
103 for page in $(seq 1 "${PAGES}"); do
104 page_files+=("${PREFIX}-page${page}.json")
105 done
106 jq -s '[.[].resultSet.results[]]' "${page_files[@]}" > "${combined}"
107 echo -e "${COV_GREEN}Combined $(jq 'length' "${combined}") defects into ${combined}${COV_NC}" >&2