master
sh 100 lines 3.69 KB
Raw
1 #!/usr/bin/env bash
2 # Update Coverity Scan triage for one CID (low-level — usually called by finalize-defect.sh).
3 #
4 # Usage:
5 # update-triage.sh <cid> <classification> <severity> <action> <comment-file>
6 #
7 # Coverity attribute IDs (not names):
8 # classification (attr 3): 20=Unclassified 21=Pending 22=FalsePositive 23=Intentional 24=Bug
9 # severity (attr 1): 10=Unspecified 11=Major 12=Moderate 13=Minor
10 # action (attr 2): 1=Undecided 2=FixRequired 3=FixSubmitted 4=ModelingRequired 5=Ignore
11 # external ref (attr 4): always sent null here
12 #
13 # Comment is read from <comment-file>. MUST be ASCII (Cloudflare blocks em-dashes
14 # and smart quotes with a 403 Cloudflare challenge — see SKILL.md).
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 CID="${1:?usage: $0 <cid> <classification> <severity> <action> <comment-file>}"
24 CLASS_ID="${2:?usage}"
25 SEV_ID="${3:?usage}"
26 ACT_ID="${4:?usage}"
27 COMMENT_FILE="${5:?usage}"
28
29 cov_require_numeric_cid "${CID}"
30 for v in CLASS_ID SEV_ID ACT_ID; do
31 if [[ ! "${!v}" =~ ^[1-9][0-9]*$ ]]; then
32 echo -e "${COV_RED}[ERROR]${COV_NC} ${v} must be a positive integer, got: '${!v}'" >&2
33 exit 1
34 fi
35 done
36
37 if [[ ! -f "${COMMENT_FILE}" || ! -r "${COMMENT_FILE}" ]]; then
38 echo -e "${COV_RED}Comment file not readable: ${COMMENT_FILE}${COV_NC}" >&2
39 exit 1
40 fi
41
42 # ASCII-only check on the comment body. `tr -d '\000-\177'` is portable across
43 # GNU and BSD/macOS (`grep -P` is GNU-only).
44 if LC_ALL=C tr -d '\000-\177' < "${COMMENT_FILE}" | grep -q .; then
45 echo -e "${COV_RED}[ERROR]${COV_NC} ${COMMENT_FILE} contains non-ASCII bytes. Cloudflare will block. Replace em-dashes (--) and smart quotes." >&2
46 exit 1
47 fi
48
49 # cid + project are numeric (validated above); attribute values must be
50 # strings per the API.
51 payload="$(jq -n \
52 --argjson cid "${CID}" \
53 --arg class "${CLASS_ID}" \
54 --arg sev "${SEV_ID}" \
55 --arg act "${ACT_ID}" \
56 --argjson project "${COVERITY_PROJECT_ID}" \
57 --rawfile comment "${COMMENT_FILE}" \
58 '{
59 triageValues: [
60 {attributeId: 3, attributeValue: $class},
61 {attributeId: 1, attributeValue: $sev},
62 {attributeId: 2, attributeValue: $act},
63 {attributeId: 4, attributeValue: null}
64 ],
65 comment: $comment,
66 mergedDefectIds: [$cid],
67 ownerId: -1,
68 projectId: $project,
69 triageStoreIds: [],
70 type: "apply"
71 }')"
72
73 echo -e "${COV_GRAY}Posting triage update for CID ${CID} (class=${CLASS_ID} sev=${SEV_ID} act=${ACT_ID})...${COV_NC}" >&2
74
75 response_file="$(mktemp "${TMPDIR:-/tmp}/cov-triage-XXXXXX.json")"
76 trap 'rm -f "${response_file}"' EXIT
77
78 http=$(curl -sS -X POST -o "${response_file}" -w '%{http_code}' \
79 -H "accept: application/json, text/plain, */*" \
80 -H "content-type: application/json" \
81 -H "origin: ${COVERITY_HOST}" \
82 -H "referer: ${COVERITY_HOST}/" \
83 -H "user-agent: ${COVERITY_USER_AGENT}" \
84 -H "x-xsrf-token: ${COVERITY_XSRF}" \
85 -b "${COVERITY_COOKIE}" \
86 --data-raw "${payload}" \
87 "${COVERITY_HOST}/sourcebrowser/updatedefecttriage.json")
88
89 if [[ "${http}" != "200" ]]; then
90 echo -e "${COV_RED}HTTP ${http} -- update failed${COV_NC}" >&2
91 head -c 500 "${response_file}" >&2; echo >&2
92 if [[ "${http}" == "401" || "${http}" == "403" || "${http}" == "302" ]]; then
93 echo -e "${COV_RED}Session likely expired. Recapture cookie from browser and update .env.${COV_NC}" >&2
94 fi
95 exit 2
96 fi
97
98 echo -e "${COV_GREEN}OK -- triage updated for CID ${CID}.${COV_NC}" >&2
99 jq -c '{defectStatus, lastTriaged, updatedValuesByCid}' "${response_file}" 2>/dev/null || head -c 300 "${response_file}"
100 echo