forked from clan/clan-core
Merge pull request 'api/machines: better input/output validation' (#472) from DavHau-dave into main
This commit is contained in:
commit
231f1fe322
@ -2,7 +2,7 @@ import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from pydantic import AnyUrl, BaseModel, validator
|
||||
from pydantic import AnyUrl, BaseModel, Extra, validator
|
||||
|
||||
from ..dirs import clan_data_dir, clan_flakes_dir
|
||||
from ..flakes.create import DEFAULT_URL
|
||||
@ -29,3 +29,12 @@ class ClanFlakePath(BaseModel):
|
||||
|
||||
class FlakeCreateInput(ClanFlakePath):
|
||||
url: AnyUrl = DEFAULT_URL
|
||||
|
||||
|
||||
class MachineConfig(BaseModel):
|
||||
clanImports: list[str] = [] # noqa: N815
|
||||
clan: dict = {}
|
||||
|
||||
# allow extra fields to cover the full spectrum of a nixos config
|
||||
class Config:
|
||||
extra = Extra.allow
|
||||
|
@ -1,7 +1,7 @@
|
||||
from enum import Enum
|
||||
from typing import Dict, List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Extra, Field
|
||||
|
||||
from ..async_cmd import CmdOut
|
||||
from ..task_manager import TaskStatus
|
||||
@ -36,7 +36,12 @@ class MachineResponse(BaseModel):
|
||||
|
||||
|
||||
class ConfigResponse(BaseModel):
|
||||
config: dict
|
||||
clanImports: list[str] = [] # noqa: N815
|
||||
clan: dict = {}
|
||||
|
||||
# allow extra fields to cover the full spectrum of a nixos config
|
||||
class Config:
|
||||
extra = Extra.allow
|
||||
|
||||
|
||||
class SchemaResponse(BaseModel):
|
||||
|
@ -4,6 +4,8 @@ from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Body
|
||||
|
||||
from clan_cli.webui.api_inputs import MachineConfig
|
||||
|
||||
from ...config.machine import (
|
||||
config_for_machine,
|
||||
schema_for_machine,
|
||||
@ -55,15 +57,15 @@ async def get_machine(flake_name: FlakeName, name: str) -> MachineResponse:
|
||||
@router.get("/api/{flake_name}/machines/{name}/config", tags=[Tags.machine])
|
||||
async def get_machine_config(flake_name: FlakeName, name: str) -> ConfigResponse:
|
||||
config = config_for_machine(flake_name, name)
|
||||
return ConfigResponse(config=config)
|
||||
return ConfigResponse(**config)
|
||||
|
||||
|
||||
@router.put("/api/{flake_name}/machines/{name}/config", tags=[Tags.machine])
|
||||
async def set_machine_config(
|
||||
flake_name: FlakeName, name: str, config: Annotated[dict, Body()]
|
||||
) -> ConfigResponse:
|
||||
set_config_for_machine(flake_name, name, config)
|
||||
return ConfigResponse(config=config)
|
||||
flake_name: FlakeName, name: str, config: Annotated[MachineConfig, Body()]
|
||||
) -> None:
|
||||
conf = dict(config)
|
||||
set_config_for_machine(flake_name, name, conf)
|
||||
|
||||
|
||||
@router.get("/api/{flake_name}/machines/{name}/schema", tags=[Tags.machine])
|
||||
|
@ -37,7 +37,10 @@ def test_configure_machine(api: TestClient, test_flake: FlakeForTest) -> None:
|
||||
# ensure an empty config is returned by default for a new machine
|
||||
response = api.get(f"/api/{test_flake.name}/machines/machine1/config")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"config": {}}
|
||||
assert response.json() == {
|
||||
"clanImports": [],
|
||||
"clan": {},
|
||||
}
|
||||
|
||||
# get jsonschema for machine
|
||||
response = api.get(f"/api/{test_flake.name}/machines/machine1/schema")
|
||||
@ -75,7 +78,7 @@ def test_configure_machine(api: TestClient, test_flake: FlakeForTest) -> None:
|
||||
# ensure the config has actually been updated
|
||||
response = api.get(f"/api/{test_flake.name}/machines/machine1/config")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"config": invalid_config}
|
||||
assert response.json() == dict(clanImports=[], **invalid_config)
|
||||
|
||||
# the part of the config that makes the evaluation pass
|
||||
fs_config = dict(
|
||||
@ -109,12 +112,18 @@ def test_configure_machine(api: TestClient, test_flake: FlakeForTest) -> None:
|
||||
json=config2,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"config": config2}
|
||||
|
||||
# ensure the config has been applied
|
||||
response = api.get(
|
||||
f"/api/{test_flake.name}/machines/machine1/config",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == dict(clanImports=[], **config2)
|
||||
|
||||
# get the config again
|
||||
response = api.get(f"/api/{test_flake.name}/machines/machine1/config")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"config": config2}
|
||||
assert response.json() == {"clanImports": [], **config2}
|
||||
|
||||
# ensure PUT on the config is idempotent by passing the config again
|
||||
# For example, this should not result in the boot.loader.grub.devices being
|
||||
@ -124,7 +133,13 @@ def test_configure_machine(api: TestClient, test_flake: FlakeForTest) -> None:
|
||||
json=config2,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"config": config2}
|
||||
|
||||
# ensure the config has been applied
|
||||
response = api.get(
|
||||
f"/api/{test_flake.name}/machines/machine1/config",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == dict(clanImports=[], **config2)
|
||||
|
||||
# verify the machine config evaluates
|
||||
response = api.get(f"/api/{test_flake.name}/machines/machine1/verify")
|
||||
@ -163,16 +178,20 @@ def test_configure_machine(api: TestClient, test_flake: FlakeForTest) -> None:
|
||||
json=config_with_imports,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# ensure the config has been applied
|
||||
response = api.get(
|
||||
f"/api/{test_flake.name}/machines/machine1/config",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"config": {
|
||||
"clanImports": ["fake-module"],
|
||||
"clan": {
|
||||
"fake-module": {
|
||||
"fake-flag": True,
|
||||
},
|
||||
"clanImports": ["fake-module"],
|
||||
"clan": {
|
||||
"fake-module": {
|
||||
"fake-flag": True,
|
||||
},
|
||||
**fs_config,
|
||||
}
|
||||
},
|
||||
**fs_config,
|
||||
}
|
||||
|
||||
# remove the import from the config
|
||||
@ -185,4 +204,14 @@ def test_configure_machine(api: TestClient, test_flake: FlakeForTest) -> None:
|
||||
json=config_with_empty_imports,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"config": config_with_empty_imports}
|
||||
|
||||
# ensure the config has been applied
|
||||
response = api.get(
|
||||
f"/api/{test_flake.name}/machines/machine1/config",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"clanImports": ["fake-module"],
|
||||
"clan": {},
|
||||
**config_with_empty_imports,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user