ci: harden quality metrics freshness

This commit is contained in:
codex 2026-05-11 13:22:22 -03:00
parent f6ef942cd1
commit 65d5212072
3 changed files with 82 additions and 18 deletions

View File

@ -76,6 +76,7 @@ def _load_quality_report(path: Path) -> tuple[float, int, dict[str, str]]:
except Exception: except Exception:
checks["sonarqube"] = "failed" checks["sonarqube"] = "failed"
ironbank_report = Path(os.getenv("QUALITY_GATE_IRONBANK_REPORT", "build/ironbank-compliance.json")) ironbank_report = Path(os.getenv("QUALITY_GATE_IRONBANK_REPORT", "build/ironbank-compliance.json"))
ironbank_required = os.getenv("QUALITY_GATE_IRONBANK_REQUIRED", "1").strip().lower() in {"1", "true", "yes", "on"}
if ironbank_report.exists(): if ironbank_report.exists():
try: try:
ironbank_payload = json.loads(ironbank_report.read_text(encoding="utf-8")) ironbank_payload = json.loads(ironbank_report.read_text(encoding="utf-8"))
@ -85,11 +86,15 @@ def _load_quality_report(path: Path) -> tuple[float, int, dict[str, str]]:
else: else:
status = ironbank_payload.get("status") or ironbank_payload.get("result") status = ironbank_payload.get("status") or ironbank_payload.get("result")
if isinstance(status, str): if isinstance(status, str):
checks["supply_chain"] = ( normalized = status.strip().lower()
"ok" if status.strip().lower() in {"ok", "pass", "passed", "success", "compliant"} else "failed" if normalized in {"ok", "pass", "passed", "success", "compliant"}:
) checks["supply_chain"] = "ok"
elif normalized in {"n/a", "na", "not_applicable", "not-applicable", "skipped", "skip"}:
checks["supply_chain"] = "failed" if ironbank_required else "not_applicable"
else:
checks["supply_chain"] = "failed" if ironbank_required else "not_applicable"
except Exception: except Exception:
checks["supply_chain"] = "failed" checks["supply_chain"] = "failed" if ironbank_required else "not_applicable"
return float(coverage), int(source_lines), checks return float(coverage), int(source_lines), checks

View File

@ -3,7 +3,6 @@ from __future__ import annotations
"""Parse test results and format Pushgateway-friendly metrics payloads.""" """Parse test results and format Pushgateway-friendly metrics payloads."""
from dataclasses import dataclass from dataclasses import dataclass
import re
import urllib.request import urllib.request
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from pathlib import Path from pathlib import Path
@ -89,17 +88,35 @@ def read_pushgateway_counters(text: str, *, suite: str, job: str) -> dict[str, f
"""Read the current quality-gate counters for a suite from Pushgateway text.""" """Read the current quality-gate counters for a suite from Pushgateway text."""
counters: dict[str, float] = {"ok": 0.0, "failed": 0.0} counters: dict[str, float] = {"ok": 0.0, "failed": 0.0}
for status in counters: for line in text.splitlines():
pattern = re.compile( if not line.startswith("platform_quality_gate_runs_total{"):
rf'^platform_quality_gate_runs_total\{{[^}}]*job="{re.escape(job)}"[^}}]*suite="{re.escape(suite)}"[^}}]*status="{status}"[^}}]*\}}\s+([0-9]+(?:\.[0-9]+)?)$', continue
re.M, if f'job="{job}"' not in line or f'suite="{suite}"' not in line:
) continue
match = pattern.search(text) parts = line.split()
if match: if len(parts) < 2:
counters[status] = float(match.group(1)) continue
for status in counters:
if f'status="{status}"' not in line:
continue
try:
counters[status] = float(parts[1])
except ValueError:
counters[status] = 0.0
return counters return counters
def pushgateway_series_exists(text: str, *, metric: str, labels: dict[str, str]) -> bool:
"""Return whether a labeled series already exists in Pushgateway text."""
for line in text.splitlines():
if not line.startswith(metric + "{"):
continue
if all(f'{key}="{value}"' in line for key, value in labels.items()):
return True
return False
def render_payload( def render_payload(
*, *,
suite: str, suite: str,
@ -178,10 +195,22 @@ def publish_quality_metrics(
gateway = gateway.rstrip("/") gateway = gateway.rstrip("/")
text = urllib.request.urlopen(f"{gateway}/metrics", timeout=10).read().decode("utf-8", errors="replace") text = urllib.request.urlopen(f"{gateway}/metrics", timeout=10).read().decode("utf-8", errors="replace")
counters = read_pushgateway_counters(text, suite=suite, job=job) counters = read_pushgateway_counters(text, suite=suite, job=job)
if status == "ok": already_recorded = bool(build_number) and pushgateway_series_exists(
counters["ok"] += 1 text,
else: metric="platform_quality_gate_build_info",
counters["failed"] += 1 labels={
"job": job,
"suite": suite,
"branch": branch or "unknown",
"build_number": build_number or "unknown",
"jenkins_job": jenkins_job or suite,
},
)
if not already_recorded:
if status == "ok":
counters["ok"] += 1
else:
counters["failed"] += 1
payload = render_payload( payload = render_payload(
suite=suite, suite=suite,

View File

@ -2,7 +2,14 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
from testing.ci.summary import RunSummary, load_junit_cases, load_junit_summary, render_payload from testing.ci.summary import (
RunSummary,
load_junit_cases,
load_junit_summary,
pushgateway_series_exists,
read_pushgateway_counters,
render_payload,
)
def test_load_junit_summary_combines_suites(tmp_path: Path) -> None: def test_load_junit_summary_combines_suites(tmp_path: Path) -> None:
@ -46,3 +53,26 @@ def test_load_junit_cases_and_render_test_case_metrics(tmp_path: Path) -> None:
) )
assert 'platform_quality_gate_test_case_result{suite="bstein_home"' in payload assert 'platform_quality_gate_test_case_result{suite="bstein_home"' in payload
assert 'test="app.health::test_fail",status="failed"} 1' in payload assert 'test="app.health::test_fail",status="failed"} 1' in payload
def test_pushgateway_counter_parser_is_label_order_insensitive() -> None:
text = "\n".join(
[
'platform_quality_gate_runs_total{suite="bstein_home",status="ok",job="platform-quality-ci"} 10',
'platform_quality_gate_runs_total{status="failed",job="platform-quality-ci",suite="bstein_home"} 2',
'platform_quality_gate_build_info{suite="bstein_home",branch="master",build_number="274",jenkins_job="bstein-dev-home",job="platform-quality-ci"} 1',
]
)
assert read_pushgateway_counters(text, suite="bstein_home", job="platform-quality-ci") == {"ok": 10.0, "failed": 2.0}
assert pushgateway_series_exists(
text,
metric="platform_quality_gate_build_info",
labels={
"job": "platform-quality-ci",
"suite": "bstein_home",
"branch": "master",
"build_number": "274",
"jenkins_job": "bstein-dev-home",
},
)