refactor: Separate runtime from build. Enhance configuration and registry management, migrate to registry-based component handling
This commit is contained in:
@@ -18,10 +18,12 @@ def castle_root(tmp_path: Path) -> Generator[Path, None, None]:
|
||||
"components": {
|
||||
"test-svc": {
|
||||
"description": "Test service",
|
||||
"source": "test-svc",
|
||||
"run": {
|
||||
"runner": "python_uv_tool",
|
||||
"tool": "test-svc",
|
||||
"working_dir": "test-svc",
|
||||
},
|
||||
"defaults": {
|
||||
"env": {"TEST_SVC_DATA_DIR": str(tmp_path / "data" / "test-svc")},
|
||||
},
|
||||
"expose": {
|
||||
|
||||
@@ -62,7 +62,7 @@ class TestLoadConfig:
|
||||
svc = config.components["test-svc"]
|
||||
assert svc.run.runner == "python_uv_tool"
|
||||
assert svc.run.tool == "test-svc"
|
||||
assert svc.run.working_dir == "test-svc"
|
||||
assert svc.source == "test-svc"
|
||||
|
||||
def test_tool_no_run(self, castle_root: Path) -> None:
|
||||
"""Tool without run block has no run spec."""
|
||||
|
||||
@@ -33,9 +33,7 @@ class TestRoleDerivation:
|
||||
m = ComponentManifest(
|
||||
id="svc",
|
||||
run=RunPythonUvTool(runner="python_uv_tool", tool="svc"),
|
||||
expose=ExposeSpec(
|
||||
http=HttpExposeSpec(internal=HttpInternal(port=8000))
|
||||
),
|
||||
expose=ExposeSpec(http=HttpExposeSpec(internal=HttpInternal(port=8000))),
|
||||
)
|
||||
assert Role.SERVICE in m.roles
|
||||
|
||||
@@ -117,9 +115,7 @@ class TestRoleDerivation:
|
||||
m = ComponentManifest(
|
||||
id="multi",
|
||||
run=RunPythonUvTool(runner="python_uv_tool", tool="multi"),
|
||||
expose=ExposeSpec(
|
||||
http=HttpExposeSpec(internal=HttpInternal(port=8000))
|
||||
),
|
||||
expose=ExposeSpec(http=HttpExposeSpec(internal=HttpInternal(port=8000))),
|
||||
install=InstallSpec(path=PathInstallSpec(alias="multi")),
|
||||
)
|
||||
assert Role.SERVICE in m.roles
|
||||
@@ -130,9 +126,7 @@ class TestRoleDerivation:
|
||||
m = ComponentManifest(
|
||||
id="svc",
|
||||
run=RunPythonUvTool(runner="python_uv_tool", tool="svc"),
|
||||
expose=ExposeSpec(
|
||||
http=HttpExposeSpec(internal=HttpInternal(port=8000))
|
||||
),
|
||||
expose=ExposeSpec(http=HttpExposeSpec(internal=HttpInternal(port=8000))),
|
||||
manage=ManageSpec(systemd=SystemdSpec()),
|
||||
)
|
||||
assert Role.SERVICE in m.roles
|
||||
@@ -144,7 +138,9 @@ class TestConsistencyValidation:
|
||||
|
||||
def test_remote_with_systemd_raises(self) -> None:
|
||||
"""Remote runner + systemd management is invalid."""
|
||||
with pytest.raises(ValueError, match="manage.systemd cannot be enabled for runner=remote"):
|
||||
with pytest.raises(
|
||||
ValueError, match="manage.systemd cannot be enabled for runner=remote"
|
||||
):
|
||||
ComponentManifest(
|
||||
id="bad",
|
||||
run=RunRemote(runner="remote", base_url="http://example.com"),
|
||||
|
||||
@@ -5,7 +5,12 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
from castle_core.config import load_config
|
||||
from castle_core.generators.systemd import generate_unit, unit_name
|
||||
from castle_core.generators.systemd import (
|
||||
generate_unit,
|
||||
generate_unit_from_deployed,
|
||||
unit_name,
|
||||
)
|
||||
from castle_core.registry import DeployedComponent
|
||||
|
||||
|
||||
class TestUnitName:
|
||||
@@ -27,15 +32,15 @@ class TestUnitGeneration:
|
||||
unit = generate_unit(config, "test-svc", manifest)
|
||||
assert "Description=Castle: Test service" in unit
|
||||
|
||||
def test_contains_working_dir(self, castle_root: Path) -> None:
|
||||
"""Unit file has correct working directory."""
|
||||
def test_no_working_directory(self, castle_root: Path) -> None:
|
||||
"""Unit file has no WorkingDirectory (source/runtime separation)."""
|
||||
config = load_config(castle_root)
|
||||
manifest = config.components["test-svc"]
|
||||
unit = generate_unit(config, "test-svc", manifest)
|
||||
assert f"WorkingDirectory={castle_root / 'test-svc'}" in unit
|
||||
assert "WorkingDirectory" not in unit
|
||||
|
||||
def test_contains_environment(self, castle_root: Path) -> None:
|
||||
"""Unit file has environment variables."""
|
||||
"""Unit file has environment variables from defaults.env."""
|
||||
config = load_config(castle_root)
|
||||
manifest = config.components["test-svc"]
|
||||
unit = generate_unit(config, "test-svc", manifest)
|
||||
@@ -55,3 +60,47 @@ class TestUnitGeneration:
|
||||
manifest = config.components["test-svc"]
|
||||
unit = generate_unit(config, "test-svc", manifest)
|
||||
assert "run test-svc" in unit
|
||||
|
||||
|
||||
class TestUnitFromDeployed:
|
||||
"""Tests for registry-based systemd unit generation."""
|
||||
|
||||
def test_basic_service(self) -> None:
|
||||
"""Generate a unit from a deployed component."""
|
||||
deployed = DeployedComponent(
|
||||
runner="python_uv_tool",
|
||||
run_cmd=["/home/user/.local/bin/uv", "run", "my-svc"],
|
||||
env={"MY_SVC_PORT": "9001", "MY_SVC_DATA_DIR": "/data/castle/my-svc"},
|
||||
description="My service",
|
||||
)
|
||||
unit = generate_unit_from_deployed("my-svc", deployed)
|
||||
assert "Description=Castle: My service" in unit
|
||||
assert "ExecStart=/home/user/.local/bin/uv run my-svc" in unit
|
||||
assert "Environment=MY_SVC_PORT=9001" in unit
|
||||
assert "Environment=MY_SVC_DATA_DIR=/data/castle/my-svc" in unit
|
||||
assert "WorkingDirectory" not in unit
|
||||
assert "Restart=on-failure" in unit
|
||||
|
||||
def test_scheduled_job(self) -> None:
|
||||
"""Scheduled component generates oneshot unit."""
|
||||
deployed = DeployedComponent(
|
||||
runner="command",
|
||||
run_cmd=["/home/user/.local/bin/my-job"],
|
||||
env={},
|
||||
description="Nightly job",
|
||||
schedule="0 2 * * *",
|
||||
)
|
||||
unit = generate_unit_from_deployed("my-job", deployed)
|
||||
assert "Type=oneshot" in unit
|
||||
assert "Restart=" not in unit
|
||||
|
||||
def test_no_repo_paths(self) -> None:
|
||||
"""Generated units must not reference repo paths."""
|
||||
deployed = DeployedComponent(
|
||||
runner="python_uv_tool",
|
||||
run_cmd=["/home/user/.local/bin/uv", "run", "my-svc"],
|
||||
env={"DATA_DIR": "/data/castle/my-svc"},
|
||||
description="Test",
|
||||
)
|
||||
unit = generate_unit_from_deployed("my-svc", deployed)
|
||||
assert "/data/repos/" not in unit
|
||||
|
||||
Reference in New Issue
Block a user