Files
net-kingdom/tools/playbook-capability-contract/tests/test_playbook_contract_validator.py

199 lines
7.1 KiB
Python

import importlib.util
import sys
from pathlib import Path
import yaml
TOOL_PATH = Path(__file__).resolve().parents[1] / "playbook_contract_validator.py"
SPEC = importlib.util.spec_from_file_location("playbook_contract_validator", TOOL_PATH)
validator = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
sys.modules[SPEC.name] = validator
SPEC.loader.exec_module(validator)
def valid_declaration() -> dict:
return {
"apiVersion": validator.API_VERSION,
"kind": validator.KIND,
"metadata": {
"id": "railiance-infra.bootstrap-host",
"name": "Railiance S1 host bootstrap",
"owner": "railiance-infra",
"repo": "railiance-infra",
"domain": "railiance",
"contract_version": "0.1",
},
"spec": {
"playbook": {
"path": "ansible/playbooks/bootstrap.yaml",
"type": "ansible",
"invocation": "make converge",
"description": "Converges the S1 host baseline.",
},
"capabilities": [
{
"id": "s1.os-baseline",
"tier": "S1",
"resource_kinds": ["infrastructure_resources", "secrets_credentials"],
"description": "OS baseline and bootstrap secret handling.",
}
],
"parameters": [
{
"name": "target_hosts",
"type": "array",
"required": True,
"constraints": {"min_items": 1},
"sensitivity": "operational",
"tuning_authority": "netkingdom_tunable",
"description": "Inventory hosts to converge.",
},
{
"name": "swapfile_size_mb",
"type": "integer",
"required": False,
"default": 4096,
"constraints": {"minimum": 0, "maximum": 65536},
"sensitivity": "operational",
"tuning_authority": "netkingdom_tunable",
"description": "Swap file size.",
},
{
"name": "wireguard_enabled",
"type": "boolean",
"required": False,
"default": False,
"sensitivity": "security_sensitive",
"tuning_authority": "platform_only",
"description": "Enable WireGuard role.",
},
],
"responsibilities": [
{
"resource_kind": "infrastructure_resources",
"owner": "railiance-infra",
"resources": ["server:target_hosts", "os-baseline"],
"repo_owns": "Ansible convergence mechanics.",
"netkingdom_orchestrates": "Whether S1 is selected for the scenario.",
}
],
"trust": {
"requires": [],
"satisfies": [
{
"state": "bare_host_trust",
"readiness_checks": [
{
"id": "os-baseline-converged",
"description": "Ansible baseline converged.",
"evidence": "bootstrap playbook completed",
}
],
}
],
},
"catalog": {
"publish": "capabilities/playbooks/railiance-infra.bootstrap-host.yaml",
"maturity": "reference",
"consumers": ["netkingdom-meta-orchestration"],
},
},
}
def declaration_from(data: dict, tmp_path: Path) -> validator.Declaration:
path = tmp_path / "declaration.yaml"
path.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8")
return validator.Declaration(path=path, data=data)
def error_messages(issues):
return [item.message for item in issues if item.level == "ERROR"]
def test_valid_declaration_passes(tmp_path):
declaration = declaration_from(valid_declaration(), tmp_path)
issues = validator.validate_declaration(declaration)
assert error_messages(issues) == []
def test_unknown_capability_fails(tmp_path):
data = valid_declaration()
data["spec"]["capabilities"][0]["id"] = "s9.magic"
declaration = declaration_from(data, tmp_path)
issues = validator.validate_declaration(declaration)
assert any("unknown capability id" in msg for msg in error_messages(issues))
def test_tenant_tunable_secret_reference_fails(tmp_path):
data = valid_declaration()
data["spec"]["parameters"][2]["sensitivity"] = "secret_reference"
data["spec"]["parameters"][2]["tuning_authority"] = "tenant_tunable"
declaration = declaration_from(data, tmp_path)
issues = validator.validate_declaration(declaration)
assert any("security-sensitive parameters cannot be tenant_tunable" in msg for msg in error_messages(issues))
def test_scenario_composition_selects_and_overrides(tmp_path):
declaration = declaration_from(valid_declaration(), tmp_path)
scenario = {
"id": "scenario:s1-host-bootstrap-reference",
"authority": "platform",
"requires": {"capabilities": ["s1.os-baseline"]},
"parameter_overrides": {
"railiance-infra.bootstrap-host": {
"target_hosts": ["railiance01"],
"swapfile_size_mb": 8192,
}
},
}
issues, composed = validator.compose_scenario([declaration], scenario)
assert error_messages(issues) == []
selected = composed["selected_declarations"][0]
assert selected["id"] == "railiance-infra.bootstrap-host"
assert selected["parameters"]["target_hosts"]["source"] == "override"
assert selected["parameters"]["swapfile_size_mb"]["value"] == 8192
def test_tenant_authority_cannot_override_platform_only(tmp_path):
declaration = declaration_from(valid_declaration(), tmp_path)
scenario = {
"id": "scenario:bad-tenant-override",
"authority": "tenant",
"requires": {"capabilities": ["s1.os-baseline"]},
"parameter_overrides": {
"railiance-infra.bootstrap-host": {
"target_hosts": ["tenant-host"],
"wireguard_enabled": True,
}
},
}
issues, _ = validator.compose_scenario([declaration], scenario)
assert any("tenant authority cannot override platform_only" in msg for msg in error_messages(issues))
def test_required_parameter_without_override_fails(tmp_path):
declaration = declaration_from(valid_declaration(), tmp_path)
scenario = {
"id": "scenario:missing-required-parameter",
"authority": "platform",
"requires": {"capabilities": ["s1.os-baseline"]},
"parameter_overrides": {},
}
issues, _ = validator.compose_scenario([declaration], scenario)
assert any("required parameter has no default or override" in msg for msg in error_messages(issues))