From 3168ffe0277d2aa74eebeffefe06e6bab408b979 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Wed, 22 Apr 2026 01:57:19 -0300 Subject: [PATCH] ci(titan-iac): feed coverage into sonar gate --- Jenkinsfile | 44 +++++++++++++++++++++++++++++++++++++++- ci/Jenkinsfile.titan-iac | 44 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 24e65607..246ce466 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -69,6 +69,19 @@ spec: ''' } } + stage('Prepare local quality evidence') { + steps { + sh ''' + set -eu + mkdir -p build + set +e + python3 -m testing.quality_gate --profile local --build-dir build + local_quality_rc=$? + set -e + printf '%s\n' "${local_quality_rc}" > build/local-quality-gate.rc + ''' + } + } stage('Collect SonarQube evidence') { steps { container('quality-tools') { @@ -84,7 +97,7 @@ spec: "-Dsonar.exclusions=**/.git/**,**/build/**,**/dist/**,**/node_modules/**,**/.venv/**,**/__pycache__/**,**/coverage/**,**/test-results/**,**/playwright-report/**,services/monitoring/dashboards/**,services/monitoring/grafana-dashboard-*.yaml" "-Dsonar.test.inclusions=**/tests/**,**/testing/**,**/*_test.go,**/*.test.ts,**/*.test.tsx,**/*.spec.ts,**/*.spec.tsx" ) - [ -f build/coverage.xml ] && args+=("-Dsonar.python.coverage.reportPaths=build/coverage.xml") + [ -f build/coverage-unit.xml ] && args+=("-Dsonar.python.coverage.reportPaths=build/coverage-unit.xml") set +e sonar-scanner "${args[@]}" | tee build/sonar-scanner.log rc=${PIPESTATUS[0]} @@ -99,8 +112,10 @@ spec: import base64 import json import os +import time import urllib.parse import urllib.request +from pathlib import Path host = os.getenv('SONARQUBE_HOST_URL', '').strip().rstrip('/') project_key = os.getenv('SONARQUBE_PROJECT_KEY', '').strip() @@ -112,6 +127,33 @@ payload = { "note": "missing SONARQUBE_HOST_URL and/or SONARQUBE_PROJECT_KEY", } if host and project_key: + task_file = Path('.scannerwork/report-task.txt') + task_id = '' + if task_file.exists(): + for line in task_file.read_text(encoding='utf-8').splitlines(): + key, _, value = line.partition('=') + if key == 'ceTaskId': + task_id = value.strip() + break + if task_id: + ce_query = urllib.parse.urlencode({"id": task_id}) + deadline = time.monotonic() + 180 + while time.monotonic() < deadline: + ce_request = urllib.request.Request(f"{host}/api/ce/task?{ce_query}", method="GET") + if token: + encoded = base64.b64encode(f"{token}:".encode("utf-8")).decode("utf-8") + ce_request.add_header("Authorization", f"Basic {encoded}") + try: + with urllib.request.urlopen(ce_request, timeout=12) as response: + ce_payload = json.loads(response.read().decode("utf-8")) + except Exception: + time.sleep(3) + continue + status = str(ce_payload.get("task", {}).get("status", "")).upper() + if status in {"SUCCESS", "FAILED", "CANCELED"}: + break + time.sleep(3) + query = urllib.parse.urlencode({"projectKey": project_key}) request = urllib.request.Request( f"{host}/api/qualitygates/project_status?{query}", diff --git a/ci/Jenkinsfile.titan-iac b/ci/Jenkinsfile.titan-iac index 70539e1d..b363ddc4 100644 --- a/ci/Jenkinsfile.titan-iac +++ b/ci/Jenkinsfile.titan-iac @@ -68,6 +68,19 @@ spec: ''' } } + stage('Prepare local quality evidence') { + steps { + sh ''' + set -eu + mkdir -p build + set +e + python3 -m testing.quality_gate --profile local --build-dir build + local_quality_rc=$? + set -e + printf '%s\n' "${local_quality_rc}" > build/local-quality-gate.rc + ''' + } + } stage('Collect SonarQube evidence') { steps { container('quality-tools') { @@ -83,7 +96,7 @@ spec: "-Dsonar.exclusions=**/.git/**,**/build/**,**/dist/**,**/node_modules/**,**/.venv/**,**/__pycache__/**,**/coverage/**,**/test-results/**,**/playwright-report/**,services/monitoring/dashboards/**,services/monitoring/grafana-dashboard-*.yaml" "-Dsonar.test.inclusions=**/tests/**,**/testing/**,**/*_test.go,**/*.test.ts,**/*.test.tsx,**/*.spec.ts,**/*.spec.tsx" ) - [ -f build/coverage.xml ] && args+=("-Dsonar.python.coverage.reportPaths=build/coverage.xml") + [ -f build/coverage-unit.xml ] && args+=("-Dsonar.python.coverage.reportPaths=build/coverage-unit.xml") set +e sonar-scanner "${args[@]}" | tee build/sonar-scanner.log rc=${PIPESTATUS[0]} @@ -98,8 +111,10 @@ spec: import base64 import json import os +import time import urllib.parse import urllib.request +from pathlib import Path host = os.getenv('SONARQUBE_HOST_URL', '').strip().rstrip('/') project_key = os.getenv('SONARQUBE_PROJECT_KEY', '').strip() @@ -111,6 +126,33 @@ payload = { "note": "missing SONARQUBE_HOST_URL and/or SONARQUBE_PROJECT_KEY", } if host and project_key: + task_file = Path('.scannerwork/report-task.txt') + task_id = '' + if task_file.exists(): + for line in task_file.read_text(encoding='utf-8').splitlines(): + key, _, value = line.partition('=') + if key == 'ceTaskId': + task_id = value.strip() + break + if task_id: + ce_query = urllib.parse.urlencode({"id": task_id}) + deadline = time.monotonic() + 180 + while time.monotonic() < deadline: + ce_request = urllib.request.Request(f"{host}/api/ce/task?{ce_query}", method="GET") + if token: + encoded = base64.b64encode(f"{token}:".encode("utf-8")).decode("utf-8") + ce_request.add_header("Authorization", f"Basic {encoded}") + try: + with urllib.request.urlopen(ce_request, timeout=12) as response: + ce_payload = json.loads(response.read().decode("utf-8")) + except Exception: + time.sleep(3) + continue + status = str(ce_payload.get("task", {}).get("status", "")).upper() + if status in {"SUCCESS", "FAILED", "CANCELED"}: + break + time.sleep(3) + query = urllib.parse.urlencode({"projectKey": project_key}) request = urllib.request.Request( f"{host}/api/qualitygates/project_status?{query}",