diff --git a/pkgs/clan-cli/clan_cli/config/__init__.py b/pkgs/clan-cli/clan_cli/config/__init__.py index d20b66731..5182f2639 100644 --- a/pkgs/clan-cli/clan_cli/config/__init__.py +++ b/pkgs/clan-cli/clan_cli/config/__init__.py @@ -28,10 +28,7 @@ def map_type(type: str) -> Any: "16 bit unsigned integer; between 0 and 65535 (both inclusive)", ]: return int - elif type == "string": - return str - # lib.type.passwdEntry - elif type == "string, not containing newlines or colons": + elif type.startswith("string"): return str elif type.startswith("null or "): subtype = type.removeprefix("null or ") diff --git a/pkgs/clan-cli/clan_cli/config/machine.py b/pkgs/clan-cli/clan_cli/config/machine.py index c309b36f9..ba4e7fb3c 100644 --- a/pkgs/clan-cli/clan_cli/config/machine.py +++ b/pkgs/clan-cli/clan_cli/config/machine.py @@ -12,6 +12,32 @@ from clan_cli.machines.folders import machine_folder, machine_settings_file from clan_cli.nix import nix_eval +def verify_machine_config( + machine_name: str, flake: Optional[Path] = None +) -> tuple[bool, Optional[str]]: + """ + Verify that the machine evaluates successfully + Returns a tuple of (success, error_message) + """ + if flake is None: + flake = get_clan_flake_toplevel() + proc = subprocess.run( + nix_eval( + flags=[ + "--impure", + "--show-trace", + f".#nixosConfigurations.{machine_name}.config.system.build.toplevel.outPath", + ], + ), + capture_output=True, + text=True, + cwd=flake, + ) + if proc.returncode != 0: + return False, proc.stderr + return True, None + + def config_for_machine(machine_name: str) -> dict: # read the config from a json file located at {flake}/machines/{machine_name}/settings.json if not machine_folder(machine_name).exists(): diff --git a/pkgs/clan-cli/clan_cli/webui/routers/machines.py b/pkgs/clan-cli/clan_cli/webui/routers/machines.py index 1a19530a4..3eb815e77 100644 --- a/pkgs/clan-cli/clan_cli/webui/routers/machines.py +++ b/pkgs/clan-cli/clan_cli/webui/routers/machines.py @@ -4,6 +4,8 @@ from typing import Annotated from fastapi import APIRouter, Body +import clan_cli.config as config + from ...config.machine import ( config_for_machine, schema_for_machine, @@ -19,6 +21,7 @@ from ..schemas import ( MachinesResponse, SchemaResponse, Status, + VerifyMachineResponse, ) log = logging.getLogger(__name__) @@ -63,3 +66,9 @@ async def set_machine_config( async def get_machine_schema(name: str) -> SchemaResponse: schema = schema_for_machine(name) return SchemaResponse(schema=schema) + + +@router.get("/api/machines/{name}/verify") +async def verify_machine_config(name: str) -> VerifyMachineResponse: + success, error = config.machine.verify_machine_config(name) + return VerifyMachineResponse(success=success, error=error) diff --git a/pkgs/clan-cli/clan_cli/webui/schemas.py b/pkgs/clan-cli/clan_cli/webui/schemas.py index c53636c5a..43ac47a10 100644 --- a/pkgs/clan-cli/clan_cli/webui/schemas.py +++ b/pkgs/clan-cli/clan_cli/webui/schemas.py @@ -38,6 +38,11 @@ class SchemaResponse(BaseModel): schema_: dict = Field(alias="schema") +class VerifyMachineResponse(BaseModel): + success: bool + error: str | None + + class VmStatusResponse(BaseModel): error: str | None status: TaskStatus diff --git a/pkgs/clan-cli/tests/test_machines_api.py b/pkgs/clan-cli/tests/test_machines_api.py index 2fba07593..fd944ee9c 100644 --- a/pkgs/clan-cli/tests/test_machines_api.py +++ b/pkgs/clan-cli/tests/test_machines_api.py @@ -46,18 +46,40 @@ def test_configure_machine(api: TestClient, test_flake: Path) -> None: assert "schema" in json_response and "properties" in json_response["schema"] # set some config - response = api.put( - "/api/machines/machine1/config", - json=dict( - clan=dict( - jitsi=True, - ) + config = dict( + clan=dict( + jitsi=dict( + enable=True, + ), + ), + fileSystems={ + "/": dict( + device="/dev/fake_disk", + fsType="ext4", + ), + }, + # set boot.loader.grub.devices + boot=dict( + loader=dict( + grub=dict( + devices=["/dev/fake_disk"], + ), + ), ), ) + response = api.put( + "/api/machines/machine1/config", + json=config, + ) assert response.status_code == 200 - assert response.json() == {"config": {"clan": {"jitsi": True}}} + assert response.json() == {"config": config} + + # verify the machine config + response = api.get("/api/machines/machine1/verify") + assert response.status_code == 200 + assert response.json() == {"success": True, "error": None} # get the config again response = api.get("/api/machines/machine1/config") assert response.status_code == 200 - assert response.json() == {"config": {"clan": {"jitsi": True}}} + assert response.json() == {"config": config}