master
sh 115 lines 4.18 KB
Raw
1 #!/usr/bin/env bash
2 # Search SonarCloud findings for the configured project.
3 #
4 # Usage:
5 # sonar-search.sh issues [--rule RULE_ID] [--resolved=false|true]
6 # sonar-search.sh hotspots [--status=TO_REVIEW|REVIEWED]
7 # sonar-search.sh summary # rule + count for open issues + hotspots
8 #
9 # Output: a single merged JSON object on stdout (.issues / .hotspots is the
10 # concatenation of all pages). Always paginated to .paging.total -- a `--ps`
11 # arg is no longer accepted because it was a footgun (only the first page
12 # was ever returned).
13 #
14 # This is a READ-ONLY script -- it does not mutate Sonar state. Safe to
15 # run anytime to inspect what's outstanding.
16
17 set -euo pipefail
18
19 # shellcheck source=./_lib.sh
20 # shellcheck disable=SC1091
21 source "$(dirname "$0")/_lib.sh"
22 sq_load_env
23
24 cmd="${1:-summary}"; shift || true
25
26 # Whitelist common URL-param values to avoid raw user input ending up in
27 # the URL. Sonar would reject malformed params anyway, but the error
28 # messages are confusing -- fail-fast locally instead.
29 _validate_resolved() {
30 local v="$1"
31 case "${v}" in true|false) ;; *)
32 echo -e "${SQ_RED}[ERROR]${SQ_NC} --resolved must be 'true' or 'false', got: '${v}'" >&2
33 return 1 ;;
34 esac
35 }
36 _validate_status() {
37 local v="$1"
38 case "${v}" in TO_REVIEW|REVIEWED) ;; *)
39 echo -e "${SQ_RED}[ERROR]${SQ_NC} --status must be 'TO_REVIEW' or 'REVIEWED', got: '${v}'" >&2
40 return 1 ;;
41 esac
42 }
43
44 case "${cmd}" in
45 issues)
46 rule=""
47 resolved="false"
48 while [[ $# -gt 0 ]]; do
49 arg="$1"
50 case "${arg}" in
51 --rule)
52 if [[ $# -lt 2 ]]; then
53 echo -e "${SQ_RED}[ERROR]${SQ_NC} --rule requires a value (e.g. --rule c:S2245)" >&2
54 exit 2
55 fi
56 rule="$2"
57 shift 2
58 ;;
59 --resolved=*) resolved="${arg#*=}"; shift ;;
60 *) echo "Unknown arg: ${arg}" >&2; exit 2 ;;
61 esac
62 done
63 _validate_resolved "${resolved}"
64 path="/api/issues/search?componentKeys=${SONAR_PROJECT}&resolved=${resolved}"
65 # URL-encode the rule id so values containing `:` (always),
66 # spaces, or other reserved chars don't inject extra params.
67 [[ -n "${rule}" ]] && path="${path}&rules=$(sq_url_encode "${rule}")"
68 sq_paginate "${path}" \
69 | jq -s '{paging: .[0].paging, issues: [.[].issues[]]}'
70 ;;
71
72 hotspots)
73 status="TO_REVIEW"
74 while [[ $# -gt 0 ]]; do
75 arg="$1"
76 case "${arg}" in
77 --status=*) status="${arg#*=}"; shift ;;
78 *) echo "Unknown arg: ${arg}" >&2; exit 2 ;;
79 esac
80 done
81 _validate_status "${status}"
82 path="/api/hotspots/search?projectKey=${SONAR_PROJECT}&status=${status}"
83 sq_paginate "${path}" \
84 | jq -s '{paging: .[0].paging, hotspots: [.[].hotspots[]]}'
85 ;;
86
87 summary)
88 echo "=== Open issues by rule ===" >&2
89 # Issue facets are computed server-side and returned on every page;
90 # the first page's facet totals reflect ALL matching issues, so a
91 # single fetch is correct here.
92 sq_run_read curl --fail --silent --show-error -u "${SONAR_TOKEN}:" \
93 "${SONAR_HOST_URL}/api/issues/search?componentKeys=${SONAR_PROJECT}&resolved=false&ps=1&facets=rules" \
94 | jq -r '.facets[] | select(.property=="rules") | .values[] | " \(.val) (\(.count))"' \
95 | sort -k2 -t'(' -nr | head -30
96
97 echo >&2
98 echo "=== Open hotspots by rule ===" >&2
99 # Hotspot search has no facets, so we have to walk every page.
100 sq_paginate "/api/hotspots/search?projectKey=${SONAR_PROJECT}&status=TO_REVIEW" \
101 | jq -s '[.[].hotspots[].ruleKey]
102 | group_by(.) | map({rule: .[0], count: length})
103 | sort_by(-.count) | .[] | " \(.rule) (\(.count))"' -r \
104 | head -30
105 ;;
106
107 "")
108 echo "usage: $0 issues|hotspots|summary [args...]" >&2
109 exit 2
110 ;;
111 *)
112 echo "Unknown command: ${cmd}" >&2
113 exit 2
114 ;;
115 esac