#!/usr/bin/env bash # ============================================================================ # Copyright (C) 2026 Moko Consulting # # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION # DEFGROUP: Automation.CI # INGROUP: mokocli.CLI # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI # PATH: /cli/ci_issue_reporter.sh # VERSION: 10.00.00 # BRIEF: Creates or updates a Gitea issue when a CI gate fails. # Deduplicates by searching open issues with the "ci-auto" label # whose title matches the gate. If a matching issue exists, a comment # is appended instead of opening a duplicate. # ============================================================================ set -euo pipefail # ── Defaults ──────────────────────────────────────────────────────────────── MOKOGITEA_URL="${MOKOGITEA_URL:-https://git.mokoconsulting.tech}" MOKOGITEA_TOKEN="${MOKOGITEA_TOKEN:-}" REPO="${GITHUB_REPOSITORY:-}" RUN_URL="${GITHUB_SERVER_URL:-${MOKOGITEA_URL}}/${REPO}/actions/runs/${GITHUB_RUN_ID:-0}" LABEL_NAME="ci-auto" LABEL_COLOR="#e11d48" GATE="" DETAILS="" SEVERITY="error" WORKFLOW="" # ── Parse arguments ───────────────────────────────────────────────────────── usage() { cat </dev/null || echo "000") if [[ "$exists" == "200" ]]; then local found found=$(curl -sf \ -H "Authorization: token ${MOKOGITEA_TOKEN}" \ "${API}/labels" 2>/dev/null \ | grep -o "\"name\":\"${LABEL_NAME}\"" || true) if [[ -z "$found" ]]; then curl -sf -X POST \ -H "Authorization: token ${MOKOGITEA_TOKEN}" \ -H "Content-Type: application/json" \ "${API}/labels" \ -d "{\"name\":\"${LABEL_NAME}\",\"color\":\"${LABEL_COLOR}\",\"description\":\"Auto-created by CI issue reporter\"}" \ > /dev/null 2>&1 || true fi fi } # ── Search for existing open issue ────────────────────────────────────────── find_existing_issue() { local query query=$(printf '%s' "[CI] ${GATE}" | sed 's/ /%20/g; s/\[/%5B/g; s/\]/%5D/g') local response response=$(curl -sf \ -H "Authorization: token ${MOKOGITEA_TOKEN}" \ "${API}/issues?type=issues&state=open&labels=${LABEL_NAME}&q=${query}&limit=5" \ 2>/dev/null || echo "[]") echo "$response" \ | grep -oP '"number":\s*\K[0-9]+' \ | head -1 } # ── Build issue body ──────────────────────────────────────────────────────── build_body() { local severity_badge if [[ "$SEVERITY" == "error" ]]; then severity_badge="**Severity:** Error" else severity_badge="**Severity:** Warning" fi cat </dev/null) HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \ -H "Authorization: token ${MOKOGITEA_TOKEN}" \ -H "Content-Type: application/json" \ "${API}/issues/${EXISTING}/comments" \ -d "${COMMENT_JSON}" 2>/dev/null || echo "000") if [[ "$HTTP" == "201" ]]; then echo "Commented on existing issue #${EXISTING}" else echo "WARNING: Failed to comment on issue #${EXISTING} (HTTP ${HTTP})" fi else ISSUE_BODY=$(build_body) ISSUE_JSON=$(python3 -c " import sys, json body = sys.stdin.read() print(json.dumps({ 'title': sys.argv[1], 'body': body, 'labels': [] }))" "$TITLE" <<< "$ISSUE_BODY" 2>/dev/null) RESPONSE=$(curl -sf -X POST \ -H "Authorization: token ${MOKOGITEA_TOKEN}" \ -H "Content-Type: application/json" \ "${API}/issues" \ -d "${ISSUE_JSON}" 2>/dev/null || echo "{}") ISSUE_NUM=$(echo "$RESPONSE" | grep -oP '"number":\s*\K[0-9]+' | head -1) if [[ -n "$ISSUE_NUM" ]]; then LABEL_ID=$(curl -sf \ -H "Authorization: token ${MOKOGITEA_TOKEN}" \ "${API}/labels" 2>/dev/null \ | grep -oP "\"id\":\s*\K[0-9]+(?=[^}]*\"name\":\s*\"${LABEL_NAME}\")" \ | head -1 || true) if [[ -n "$LABEL_ID" ]]; then curl -sf -X POST \ -H "Authorization: token ${MOKOGITEA_TOKEN}" \ -H "Content-Type: application/json" \ "${API}/issues/${ISSUE_NUM}/labels" \ -d "{\"labels\":[${LABEL_ID}]}" \ > /dev/null 2>&1 || true fi echo "Created issue #${ISSUE_NUM}: ${TITLE}" else echo "WARNING: Failed to create issue" echo "Response: ${RESPONSE}" fi fi