Onboard tenant-engine to the staged-promotion contract
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 50s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 50s
TEN-WP-0008. railiance/app.toml declares criticality=high, empty secrets, isolated canary, and the live PostgreSQL digest as previous_stable. Manifests render through kustomize (deploy/ and deploy/canary/). Stage 1 passed. Stage 2/3 Helm-only CLI gap requested as RAIL-BS-IN-0001 rather than a dummy chart. Assistant: grok Assistant-Session: 01a04cea-e5e8-7081-a0fc-808ebbc35fa9
This commit is contained in:
parent
f9f8e0c54f
commit
6644ad8402
19 changed files with 1053 additions and 18 deletions
67
tests/test_staged_promotion.py
Normal file
67
tests/test_staged_promotion.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
"""TEN-WP-0008: app.toml contract and kustomize overlays."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
STABLE = (
|
||||
"sha256:a8e8086ffc5b772c1391b166f5e1884b90f7d327b152c205eceae129df555c24"
|
||||
)
|
||||
|
||||
|
||||
def test_app_toml_validates_against_railiance_app_v1() -> None:
|
||||
pytest.importorskip("jsonschema")
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(ROOT / "tests" / "validate_app_toml.py")],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, result.stderr + result.stdout
|
||||
|
||||
|
||||
def test_previous_stable_matches_the_manifest_pin() -> None:
|
||||
contract = tomllib.loads((ROOT / "railiance" / "app.toml").read_text())
|
||||
assert contract["stages"]["stage3"]["previous_stable"].endswith(STABLE)
|
||||
manifest = (ROOT / "deploy" / "base" / "tenant-engine.yaml").read_text()
|
||||
assert STABLE in manifest
|
||||
assert contract["secrets"]["references"] == []
|
||||
assert contract["app"]["criticality"] == "high"
|
||||
assert contract["stages"]["stage2"]["canary_mode"] == "isolated"
|
||||
|
||||
|
||||
def _kustomize(path: str) -> str:
|
||||
if subprocess.run(["kubectl", "version", "--client"], capture_output=True).returncode != 0:
|
||||
pytest.skip("kubectl is required to render kustomize overlays")
|
||||
result = subprocess.run(
|
||||
["kubectl", "kustomize", path],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, result.stderr
|
||||
return result.stdout
|
||||
|
||||
|
||||
def test_production_kustomize_keeps_the_stable_name() -> None:
|
||||
rendered = _kustomize("deploy")
|
||||
assert "name: tenant-engine-canary" not in rendered
|
||||
assert STABLE in rendered
|
||||
assert "kind: Deployment" in rendered
|
||||
|
||||
|
||||
def test_canary_kustomize_is_isolated_from_production_selector() -> None:
|
||||
rendered = _kustomize("deploy/canary")
|
||||
docs = [doc for doc in rendered.split("---") if doc.strip()]
|
||||
service = next(doc for doc in docs if "kind: Service" in doc and "tenant-engine-canary" in doc)
|
||||
assert "app.kubernetes.io/name: tenant-engine-canary" in service
|
||||
deploy = next(doc for doc in docs if "kind: Deployment" in doc)
|
||||
assert "name: tenant-engine-canary" in deploy
|
||||
assert "app.kubernetes.io/name: tenant-engine-canary" in deploy
|
||||
assert STABLE in deploy
|
||||
41
tests/validate_app_toml.py
Normal file
41
tests/validate_app_toml.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Validate railiance/app.toml against the railiance.app.v1 schema."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CONTRACT = ROOT / "railiance" / "app.toml"
|
||||
SCHEMA_CANDIDATES = [
|
||||
Path.home() / "railiance-bootstrap" / "schemas" / "railiance-app.schema.json",
|
||||
Path.home() / "railiance-cluster" / "schemas" / "railiance-app.schema.json",
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
schema_path = next((path for path in SCHEMA_CANDIDATES if path.exists()), None)
|
||||
if schema_path is None:
|
||||
print(
|
||||
"railiance-app.schema.json not found in railiance-bootstrap or railiance-cluster",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
try:
|
||||
import jsonschema
|
||||
except ImportError:
|
||||
print("jsonschema is required to validate railiance/app.toml", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
document = tomllib.loads(CONTRACT.read_text())
|
||||
schema = json.loads(schema_path.read_text())
|
||||
jsonschema.validate(document, schema)
|
||||
print(f"valid {CONTRACT.relative_to(ROOT)} against {schema_path}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue