|
1 | 1 | """Tests for cloud-init template parsing and rendering.""" |
2 | 2 |
|
| 3 | +import json |
| 4 | +import urllib.request |
| 5 | + |
| 6 | +import pytest |
| 7 | +import yaml |
3 | 8 | from jinja2 import Template, TemplateSyntaxError |
| 9 | +from jsonschema import Draft4Validator |
4 | 10 |
|
5 | 11 | from dropkit.config import Config |
6 | 12 |
|
| 13 | +CLOUD_CONFIG_SCHEMA_URL = ( |
| 14 | + "https://raw.githubusercontent.com/canonical/cloud-init/main/" |
| 15 | + "cloudinit/config/schemas/schema-cloud-config-v1.json" |
| 16 | +) |
| 17 | + |
| 18 | + |
| 19 | +@pytest.fixture(scope="module") |
| 20 | +def cloud_config_schema(): |
| 21 | + """Fetch the official cloud-init JSON schema (skip if offline).""" |
| 22 | + try: |
| 23 | + with urllib.request.urlopen(CLOUD_CONFIG_SCHEMA_URL, timeout=10) as resp: # noqa: S310 |
| 24 | + return json.loads(resp.read()) |
| 25 | + except (urllib.error.URLError, TimeoutError): |
| 26 | + pytest.skip("Could not fetch cloud-init schema (offline?)") |
| 27 | + |
7 | 28 |
|
8 | 29 | def _load_default_template() -> str: |
9 | 30 | """Load the default cloud-init template content.""" |
@@ -74,3 +95,36 @@ def test_docker_install_uses_distro_detection(): |
74 | 95 |
|
75 | 96 | # Architecture detected dynamically, not hardcoded amd64 |
76 | 97 | assert "dpkg --print-architecture" in rendered |
| 98 | + |
| 99 | + |
| 100 | +def _render_template(tailscale_enabled: bool = True) -> str: |
| 101 | + """Render the default template with sample variables.""" |
| 102 | + content = _load_default_template() |
| 103 | + template = Template(content) |
| 104 | + return template.render( |
| 105 | + username="testuser", |
| 106 | + full_name="Test User", |
| 107 | + email="test@example.com", |
| 108 | + ssh_keys=["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5 test@host"], |
| 109 | + tailscale_enabled=tailscale_enabled, |
| 110 | + ) |
| 111 | + |
| 112 | + |
| 113 | +def test_rendered_template_valid_cloud_config_schema(cloud_config_schema): |
| 114 | + """Verify the rendered template passes cloud-init schema validation.""" |
| 115 | + rendered = _render_template(tailscale_enabled=True) |
| 116 | + doc = yaml.safe_load(rendered) |
| 117 | + validator = Draft4Validator(cloud_config_schema) |
| 118 | + errors = list(validator.iter_errors(doc)) |
| 119 | + messages = [f" - {e.message}" for e in errors] |
| 120 | + assert not errors, "Cloud-init schema errors:\n" + "\n".join(messages) |
| 121 | + |
| 122 | + |
| 123 | +def test_rendered_template_no_tailscale_valid_schema(cloud_config_schema): |
| 124 | + """Verify the rendered template without Tailscale also passes schema validation.""" |
| 125 | + rendered = _render_template(tailscale_enabled=False) |
| 126 | + doc = yaml.safe_load(rendered) |
| 127 | + validator = Draft4Validator(cloud_config_schema) |
| 128 | + errors = list(validator.iter_errors(doc)) |
| 129 | + messages = [f" - {e.message}" for e in errors] |
| 130 | + assert not errors, "Cloud-init schema errors:\n" + "\n".join(messages) |
0 commit comments