| 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 |