5d32a37258
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 4s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Extension CI / Lint & Validate (pull_request) Successful in 17s
Universal: Security Audit / Dependency Audit (pull_request) Successful in 10s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Release configuration (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
238 lines
8.0 KiB
Bash
238 lines
8.0 KiB
Bash
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
# FILE INFORMATION
|
|
# DEFGROUP: Automation.CI
|
|
# INGROUP: moko-platform.Automation
|
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
# PATH: /automation/ci-issue-reporter.sh
|
|
# VERSION: 09.23.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 ────────────────────────────────────────────────────────────────
|
|
GITEA_URL="${GITEA_URL:-https://git.mokoconsulting.tech}"
|
|
GITEA_TOKEN="${GITEA_TOKEN:-}"
|
|
REPO="${GITHUB_REPOSITORY:-}"
|
|
RUN_URL="${GITHUB_SERVER_URL:-${GITEA_URL}}/${REPO}/actions/runs/${GITHUB_RUN_ID:-0}"
|
|
LABEL_NAME="ci-auto"
|
|
LABEL_COLOR="#e11d48"
|
|
|
|
GATE=""
|
|
DETAILS=""
|
|
SEVERITY="error"
|
|
WORKFLOW=""
|
|
|
|
# ── Parse arguments ─────────────────────────────────────────────────────────
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: ci-issue-reporter.sh --gate NAME --details TEXT [OPTIONS]
|
|
|
|
Required:
|
|
--gate CI gate name (e.g. "Code Quality", "Self-Health")
|
|
--details Human-readable failure description
|
|
|
|
Optional:
|
|
--severity "error" (default) or "warning"
|
|
--workflow Workflow name for the issue title
|
|
--repo owner/repo (default: \$GITHUB_REPOSITORY)
|
|
--run-url URL to the CI run (auto-detected from env)
|
|
--token Gitea API token (default: \$GITEA_TOKEN)
|
|
--url Gitea base URL (default: \$GITEA_URL)
|
|
EOF
|
|
exit 1
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--gate) GATE="$2"; shift 2 ;;
|
|
--details) DETAILS="$2"; shift 2 ;;
|
|
--severity) SEVERITY="$2"; shift 2 ;;
|
|
--workflow) WORKFLOW="$2"; shift 2 ;;
|
|
--repo) REPO="$2"; shift 2 ;;
|
|
--run-url) RUN_URL="$2"; shift 2 ;;
|
|
--token) GITEA_TOKEN="$2"; shift 2 ;;
|
|
--url) GITEA_URL="$2"; shift 2 ;;
|
|
-h|--help) usage ;;
|
|
*) echo "Unknown option: $1"; usage ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$GATE" ]] && { echo "ERROR: --gate is required"; usage; }
|
|
[[ -z "$DETAILS" ]] && { echo "ERROR: --details is required"; usage; }
|
|
[[ -z "$GITEA_TOKEN" ]] && { echo "ERROR: GITEA_TOKEN not set"; exit 1; }
|
|
[[ -z "$REPO" ]] && { echo "ERROR: GITHUB_REPOSITORY not set"; exit 1; }
|
|
|
|
API="${GITEA_URL}/api/v1/repos/${REPO}"
|
|
|
|
# ── Build title ─────────────────────────────────────────────────────────────
|
|
if [[ -n "$WORKFLOW" ]]; then
|
|
TITLE="[CI] ${WORKFLOW}: ${GATE} failed"
|
|
else
|
|
TITLE="[CI] ${GATE} failed"
|
|
fi
|
|
|
|
# ── Ensure label exists ─────────────────────────────────────────────────────
|
|
ensure_label() {
|
|
local exists
|
|
exists=$(curl -sf -o /dev/null -w '%{http_code}' \
|
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
"${API}/labels" 2>/dev/null || echo "000")
|
|
|
|
if [[ "$exists" == "200" ]]; then
|
|
# Check if label already exists
|
|
local found
|
|
found=$(curl -sf \
|
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
"${API}/labels" 2>/dev/null \
|
|
| grep -o "\"name\":\"${LABEL_NAME}\"" || true)
|
|
|
|
if [[ -z "$found" ]]; then
|
|
curl -sf -X POST \
|
|
-H "Authorization: token ${GITEA_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() {
|
|
# URL-encode the gate name for the query
|
|
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 ${GITEA_TOKEN}" \
|
|
"${API}/issues?type=issues&state=open&labels=${LABEL_NAME}&q=${query}&limit=5" \
|
|
2>/dev/null || echo "[]")
|
|
|
|
# Extract the first matching issue number
|
|
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 <<BODY
|
|
## CI Gate Failure: ${GATE}
|
|
|
|
${severity_badge}
|
|
**Workflow:** ${WORKFLOW:-unknown}
|
|
**Branch:** ${GITHUB_REF_NAME:-unknown}
|
|
**Commit:** \`${GITHUB_SHA:0:8}\`
|
|
**Run:** [View CI run](${RUN_URL})
|
|
|
|
### Details
|
|
|
|
${DETAILS}
|
|
|
|
### Resolution
|
|
|
|
Fix the issue described above and push a new commit. This issue will be closed automatically when the gate passes, or can be closed manually.
|
|
|
|
---
|
|
*Auto-created by [ci-issue-reporter](${GITEA_URL}/${REPO}/src/branch/main/automation/ci-issue-reporter.sh)*
|
|
BODY
|
|
}
|
|
|
|
# ── Build comment body (for existing issues) ────────────────────────────────
|
|
build_comment() {
|
|
cat <<COMMENT
|
|
### CI failure recurrence
|
|
|
|
**Branch:** ${GITHUB_REF_NAME:-unknown}
|
|
**Commit:** \`${GITHUB_SHA:0:8}\`
|
|
**Run:** [View CI run](${RUN_URL})
|
|
|
|
${DETAILS}
|
|
COMMENT
|
|
}
|
|
|
|
# ── Main ────────────────────────────────────────────────────────────────────
|
|
ensure_label
|
|
|
|
EXISTING=$(find_existing_issue)
|
|
|
|
if [[ -n "$EXISTING" ]]; then
|
|
# Append comment to existing issue
|
|
COMMENT_BODY=$(build_comment)
|
|
COMMENT_JSON=$(printf '%s' "$COMMENT_BODY" | python3 -c "
|
|
import sys, json
|
|
print(json.dumps({'body': sys.stdin.read()}))" 2>/dev/null)
|
|
|
|
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
|
|
-H "Authorization: token ${GITEA_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
|
|
# Create new issue
|
|
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)
|
|
|
|
# Create the issue
|
|
RESPONSE=$(curl -sf -X POST \
|
|
-H "Authorization: token ${GITEA_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
|
|
# Apply label (separate call — more reliable across Gitea versions)
|
|
LABEL_ID=$(curl -sf \
|
|
-H "Authorization: token ${GITEA_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 ${GITEA_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
|