api/machines: split off config validation into separate endpoint

- This speeds up PUT /machines{name}/config as it doesn't do the expensive check anymore
- instead use PUT /machines/{name}/verify which allows a dry-run evaluation of a config which is passed without writing it to disk
This commit is contained in:
DavHau
2023-10-25 17:48:30 +01:00
parent ed87aefbad
commit 0e5c7d2d13
3 changed files with 32 additions and 18 deletions

View File

@@ -64,16 +64,13 @@ def config_for_machine(machine_name: str) -> dict:
return json.load(f) return json.load(f)
def set_config_for_machine(machine_name: str, config: dict) -> Optional[str]: def set_config_for_machine(machine_name: str, config: dict) -> None:
# write the config to a json file located at {flake}/machines/{machine_name}/settings.json # write the config to a json file located at {flake}/machines/{machine_name}/settings.json
if not machine_folder(machine_name).exists(): if not machine_folder(machine_name).exists():
raise HTTPException( raise HTTPException(
status_code=404, status_code=404,
detail=f"Machine {machine_name} not found. Create the machine first`", detail=f"Machine {machine_name} not found. Create the machine first`",
) )
error = verify_machine_config(machine_name, config)
if error is not None:
return error
settings_path = machine_settings_file(machine_name) settings_path = machine_settings_file(machine_name)
settings_path.parent.mkdir(parents=True, exist_ok=True) settings_path.parent.mkdir(parents=True, exist_ok=True)
with open(settings_path, "w") as f: with open(settings_path, "w") as f:
@@ -82,7 +79,6 @@ def set_config_for_machine(machine_name: str, config: dict) -> Optional[str]:
if repo_dir is not None: if repo_dir is not None:
commit_file(settings_path, repo_dir) commit_file(settings_path, repo_dir)
return None
def schema_for_machine( def schema_for_machine(

View File

@@ -2,7 +2,7 @@
import logging import logging
from typing import Annotated from typing import Annotated
from fastapi import APIRouter, Body, HTTPException from fastapi import APIRouter, Body
from ...config.machine import ( from ...config.machine import (
config_for_machine, config_for_machine,
@@ -57,9 +57,7 @@ async def get_machine_config(name: str) -> ConfigResponse:
async def set_machine_config( async def set_machine_config(
name: str, config: Annotated[dict, Body()] name: str, config: Annotated[dict, Body()]
) -> ConfigResponse: ) -> ConfigResponse:
error = set_config_for_machine(name, config) set_config_for_machine(name, config)
if error is not None:
raise HTTPException(status_code=400, detail=error)
return ConfigResponse(config=config) return ConfigResponse(config=config)
@@ -78,7 +76,17 @@ async def set_machine_schema(
@router.get("/api/machines/{name}/verify") @router.get("/api/machines/{name}/verify")
async def put_verify_machine_config(name: str) -> VerifyMachineResponse: async def get_verify_machine_config(name: str) -> VerifyMachineResponse:
error = verify_machine_config(name) error = verify_machine_config(name)
success = error is None success = error is None
return VerifyMachineResponse(success=success, error=error) return VerifyMachineResponse(success=success, error=error)
@router.put("/api/machines/{name}/verify")
async def put_verify_machine_config(
name: str,
config: Annotated[dict, Body()],
) -> VerifyMachineResponse:
error = verify_machine_config(name, config)
success = error is None
return VerifyMachineResponse(success=success, error=error)

View File

@@ -45,29 +45,39 @@ def test_configure_machine(api: TestClient, test_flake: Path) -> None:
json_response = response.json() json_response = response.json()
assert "schema" in json_response and "properties" in json_response["schema"] assert "schema" in json_response and "properties" in json_response["schema"]
# set come invalid config (fileSystems missing) # an invalid config missing the fileSystems
config = dict( invalid_config = dict(
clan=dict( clan=dict(
jitsi=dict( jitsi=dict(
enable=True, enable=True,
), ),
), ),
) )
# verify an invalid config (fileSystems missing) fails
response = api.put( response = api.put(
"/api/machines/machine1/config", "/api/machines/machine1/verify",
json=config, json=invalid_config,
) )
assert response.status_code == 400 assert response.status_code == 200
assert ( assert (
"The fileSystems option does not specify your root" "The fileSystems option does not specify your root"
in response.json()["detail"] in response.json()["error"]
) )
# ensure config is still empty after the invalid attempt # set come invalid config (fileSystems missing)
response = api.put(
"/api/machines/machine1/config",
json=invalid_config,
)
assert response.status_code == 200
# ensure the config has actually been updated
response = api.get("/api/machines/machine1/config") response = api.get("/api/machines/machine1/config")
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == {"config": {}} assert response.json() == {"config": invalid_config}
# the part of the config that makes the evaluation pass
fs_config = dict( fs_config = dict(
fileSystems={ fileSystems={
"/": dict( "/": dict(