176 lines
5.2 KiB
Plaintext
176 lines
5.2 KiB
Plaintext
pipeline {
|
|
agent {
|
|
kubernetes {
|
|
defaultContainer 'python'
|
|
yaml """
|
|
apiVersion: v1
|
|
kind: Pod
|
|
spec:
|
|
nodeSelector:
|
|
hardware: rpi5
|
|
kubernetes.io/arch: arm64
|
|
node-role.kubernetes.io/worker: "true"
|
|
containers:
|
|
- name: python
|
|
image: python:3.12-slim
|
|
command:
|
|
- cat
|
|
tty: true
|
|
"""
|
|
}
|
|
}
|
|
environment {
|
|
PIP_DISABLE_PIP_VERSION_CHECK = '1'
|
|
PYTHONUNBUFFERED = '1'
|
|
SUITE_NAME = 'titan-iac'
|
|
PUSHGATEWAY_URL = 'http://platform-quality-gateway.monitoring.svc.cluster.local:9091'
|
|
}
|
|
stages {
|
|
stage('Checkout') {
|
|
steps {
|
|
checkout scm
|
|
}
|
|
}
|
|
stage('Install deps') {
|
|
steps {
|
|
sh 'pip install --no-cache-dir -r ci/requirements.txt'
|
|
}
|
|
}
|
|
stage('Glue tests') {
|
|
steps {
|
|
sh '''
|
|
set -eu
|
|
mkdir -p build
|
|
pytest -q ci/tests/glue --junitxml=build/junit-glue.xml
|
|
'''
|
|
}
|
|
}
|
|
stage('Resolve Flux branch') {
|
|
steps {
|
|
script {
|
|
env.FLUX_BRANCH = sh(
|
|
returnStdout: true,
|
|
script: "awk '/branch:/{print $2; exit}' clusters/atlas/flux-system/gotk-sync.yaml"
|
|
).trim()
|
|
if (!env.FLUX_BRANCH) {
|
|
error('Flux branch not found in gotk-sync.yaml')
|
|
}
|
|
echo "Flux branch: ${env.FLUX_BRANCH}"
|
|
}
|
|
}
|
|
}
|
|
stage('Promote') {
|
|
when {
|
|
expression {
|
|
def branch = env.BRANCH_NAME ?: (env.GIT_BRANCH ?: '').replaceFirst('origin/', '')
|
|
return env.FLUX_BRANCH && branch == env.FLUX_BRANCH
|
|
}
|
|
}
|
|
steps {
|
|
withCredentials([usernamePassword(credentialsId: 'gitea-pat', usernameVariable: 'GIT_USER', passwordVariable: 'GIT_TOKEN')]) {
|
|
sh '''
|
|
set +x
|
|
git config user.email "jenkins@bstein.dev"
|
|
git config user.name "jenkins"
|
|
git remote set-url origin https://${GIT_USER}:${GIT_TOKEN}@scm.bstein.dev/bstein/titan-iac.git
|
|
git push origin HEAD:${FLUX_BRANCH}
|
|
'''
|
|
}
|
|
}
|
|
}
|
|
}
|
|
post {
|
|
always {
|
|
script {
|
|
env.QUALITY_STATUS = currentBuild.currentResult == 'SUCCESS' ? 'ok' : 'failed'
|
|
}
|
|
sh '''
|
|
set -eu
|
|
python - <<'PY'
|
|
import os
|
|
import urllib.request
|
|
import xml.etree.ElementTree as ET
|
|
from pathlib import Path
|
|
|
|
suite = os.getenv("SUITE_NAME", "titan-iac")
|
|
status = os.getenv("QUALITY_STATUS", "failed")
|
|
gateway = os.getenv("PUSHGATEWAY_URL", "http://platform-quality-gateway.monitoring.svc.cluster.local:9091").rstrip("/")
|
|
junit_path = Path("build/junit-glue.xml")
|
|
|
|
totals = {"tests": 0, "failures": 0, "errors": 0, "skipped": 0}
|
|
if junit_path.exists():
|
|
root = ET.parse(junit_path).getroot()
|
|
suites = [root] if root.tag == "testsuite" else list(root.findall("testsuite")) if root.tag == "testsuites" else []
|
|
for node in suites:
|
|
for key in totals:
|
|
raw = node.attrib.get(key) or "0"
|
|
try:
|
|
totals[key] += int(float(raw))
|
|
except ValueError:
|
|
pass
|
|
|
|
passed = max(totals["tests"] - totals["failures"] - totals["errors"] - totals["skipped"], 0)
|
|
|
|
def read_metrics() -> str:
|
|
try:
|
|
with urllib.request.urlopen(f"{gateway}/metrics", timeout=10) as resp:
|
|
return resp.read().decode("utf-8", errors="replace")
|
|
except Exception:
|
|
return ""
|
|
|
|
def read_counter(text: str, counter_status: str) -> float:
|
|
for line in text.splitlines():
|
|
if not line.startswith("platform_quality_gate_runs_total{"):
|
|
continue
|
|
if 'job="platform-quality-ci"' not in line:
|
|
continue
|
|
if f'suite="{suite}"' not in line:
|
|
continue
|
|
if f'status="{counter_status}"' not in line:
|
|
continue
|
|
parts = line.split()
|
|
if len(parts) < 2:
|
|
continue
|
|
try:
|
|
return float(parts[1])
|
|
except ValueError:
|
|
return 0.0
|
|
return 0.0
|
|
|
|
metrics = read_metrics()
|
|
ok_count = read_counter(metrics, "ok")
|
|
failed_count = read_counter(metrics, "failed")
|
|
if status == "ok":
|
|
ok_count += 1
|
|
else:
|
|
failed_count += 1
|
|
|
|
payload = "\n".join(
|
|
[
|
|
"# TYPE platform_quality_gate_runs_total counter",
|
|
f'platform_quality_gate_runs_total{{suite="{suite}",status="ok"}} {ok_count:.0f}',
|
|
f'platform_quality_gate_runs_total{{suite="{suite}",status="failed"}} {failed_count:.0f}',
|
|
"# TYPE titan_iac_quality_gate_tests_total gauge",
|
|
f'titan_iac_quality_gate_tests_total{{suite="{suite}",result="passed"}} {passed}',
|
|
f'titan_iac_quality_gate_tests_total{{suite="{suite}",result="failed"}} {totals["failures"]}',
|
|
f'titan_iac_quality_gate_tests_total{{suite="{suite}",result="error"}} {totals["errors"]}',
|
|
f'titan_iac_quality_gate_tests_total{{suite="{suite}",result="skipped"}} {totals["skipped"]}',
|
|
]
|
|
) + "\n"
|
|
|
|
req = urllib.request.Request(
|
|
f"{gateway}/metrics/job/platform-quality-ci/suite/{suite}",
|
|
data=payload.encode("utf-8"),
|
|
method="POST",
|
|
headers={"Content-Type": "text/plain"},
|
|
)
|
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
if resp.status >= 400:
|
|
raise RuntimeError(f"push failed: {resp.status}")
|
|
PY
|
|
'''
|
|
archiveArtifacts artifacts: 'build/junit-glue.xml', allowEmptyArchive: true
|
|
}
|
|
}
|
|
}
|