Merge pull request 'api/machines: add tests for tags readOnly' (#4694) from readonly-tags into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4694
This commit is contained in:
@@ -9,10 +9,11 @@ from clan_lib.nix_models.clan import (
|
|||||||
)
|
)
|
||||||
from clan_lib.persist.inventory_store import InventoryStore
|
from clan_lib.persist.inventory_store import InventoryStore
|
||||||
from clan_lib.persist.util import (
|
from clan_lib.persist.util import (
|
||||||
|
get_value_by_path,
|
||||||
is_writeable_key,
|
is_writeable_key,
|
||||||
|
list_difference,
|
||||||
retrieve_typed_field_names,
|
retrieve_typed_field_names,
|
||||||
set_value_by_path,
|
set_value_by_path,
|
||||||
unmerge_lists,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -132,11 +133,11 @@ def get_machine_fields_schema(machine: Machine) -> dict[str, FieldSchema]:
|
|||||||
# TODO: handle this more generically. I.e via json schema
|
# TODO: handle this more generically. I.e via json schema
|
||||||
persisted_data = inventory_store._get_persisted() # noqa: SLF001
|
persisted_data = inventory_store._get_persisted() # noqa: SLF001
|
||||||
inventory = inventory_store.read() #
|
inventory = inventory_store.read() #
|
||||||
all_tags = inventory.get("machines", {}).get(machine.name, {}).get("tags", [])
|
all_tags = get_value_by_path(inventory, f"machines.{machine.name}.tags", [])
|
||||||
persisted_tags = (
|
persisted_tags = get_value_by_path(
|
||||||
persisted_data.get("machines", {}).get(machine.name, {}).get("tags", [])
|
persisted_data, f"machines.{machine.name}.tags", []
|
||||||
)
|
)
|
||||||
nix_tags = unmerge_lists(all_tags, persisted_tags)
|
nix_tags = list_difference(all_tags, persisted_tags)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
field: {
|
field: {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ from clan_lib.flake import Flake
|
|||||||
from clan_lib.machines import actions as actions_module
|
from clan_lib.machines import actions as actions_module
|
||||||
from clan_lib.machines.machines import Machine
|
from clan_lib.machines.machines import Machine
|
||||||
from clan_lib.nix_models.clan import Clan, InventoryMachine, Unknown
|
from clan_lib.nix_models.clan import Clan, InventoryMachine, Unknown
|
||||||
|
from clan_lib.persist.inventory_store import InventoryStore
|
||||||
|
from clan_lib.persist.util import get_value_by_path, set_value_by_path
|
||||||
|
|
||||||
from .actions import get_machine, get_machine_fields_schema, list_machines, set_machine
|
from .actions import get_machine, get_machine_fields_schema, list_machines, set_machine
|
||||||
|
|
||||||
@@ -191,6 +193,18 @@ def test_get_machine_writeability(clan_flake: Callable[..., Flake]) -> None:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Move this into the api
|
||||||
|
inventory_store = InventoryStore(flake=flake)
|
||||||
|
inventory = inventory_store.read()
|
||||||
|
curr_tags = get_value_by_path(inventory, "machines.jon.tags", [])
|
||||||
|
new_tags = ["managed1", "managed2"]
|
||||||
|
set_value_by_path(inventory, "machines.jon.tags", [*curr_tags, *new_tags])
|
||||||
|
inventory_store.write(inventory, message="Test writeability")
|
||||||
|
|
||||||
|
# Check that the tags were updated
|
||||||
|
persisted = inventory_store._get_persisted() # noqa: SLF001
|
||||||
|
assert get_value_by_path(persisted, "machines.jon.tags", []) == new_tags
|
||||||
|
|
||||||
write_info = get_machine_fields_schema(Machine("jon", flake))
|
write_info = get_machine_fields_schema(Machine("jon", flake))
|
||||||
|
|
||||||
# {'tags': {'writable': True, 'reason': None}, 'machineClass': {'writable': False, 'reason': None}, 'name': {'writable': False, 'reason': None}, 'description': {'writable': True, 'reason': None}, 'deploy.buildHost': {'writable': True, 'reason': None}, 'icon': {'writable': True, 'reason': None}, 'deploy.targetHost': {'writable': True, 'reason': None}}
|
# {'tags': {'writable': True, 'reason': None}, 'machineClass': {'writable': False, 'reason': None}, 'name': {'writable': False, 'reason': None}, 'description': {'writable': True, 'reason': None}, 'deploy.buildHost': {'writable': True, 'reason': None}, 'icon': {'writable': True, 'reason': None}, 'deploy.targetHost': {'writable': True, 'reason': None}}
|
||||||
@@ -207,3 +221,5 @@ def test_get_machine_writeability(clan_flake: Callable[..., Flake]) -> None:
|
|||||||
"icon",
|
"icon",
|
||||||
}
|
}
|
||||||
assert read_only_fields == {"machineClass", "name"}
|
assert read_only_fields == {"machineClass", "name"}
|
||||||
|
|
||||||
|
assert write_info["tags"]["readonly_members"] == ["nix1", "all", "nixos"]
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ def flatten_data(data: dict, parent_key: str = "", separator: str = ".") -> dict
|
|||||||
return flattened
|
return flattened
|
||||||
|
|
||||||
|
|
||||||
def unmerge_lists(all_items: list, filter_items: list) -> list:
|
def list_difference(all_items: list, filter_items: list) -> list:
|
||||||
"""
|
"""
|
||||||
Unmerge the current list. Given a previous list.
|
Unmerge the current list. Given a previous list.
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ After: {new}
|
|||||||
persisted_data = data_dyn.get(key, [])
|
persisted_data = data_dyn.get(key, [])
|
||||||
# List including nix values
|
# List including nix values
|
||||||
all_list = data_all.get(key, [])
|
all_list = data_all.get(key, [])
|
||||||
nix_list = unmerge_lists(all_list, persisted_data)
|
nix_list = list_difference(all_list, persisted_data)
|
||||||
|
|
||||||
# every item in nix_list MUST be in new
|
# every item in nix_list MUST be in new
|
||||||
nix_items_to_remove = list(
|
nix_items_to_remove = list(
|
||||||
@@ -307,7 +307,7 @@ After: {new}
|
|||||||
raise ClanError(msg)
|
raise ClanError(msg)
|
||||||
|
|
||||||
if new != all_list:
|
if new != all_list:
|
||||||
patchset[key] = unmerge_lists(new, nix_list)
|
patchset[key] = list_difference(new, nix_list)
|
||||||
else:
|
else:
|
||||||
patchset[key] = new
|
patchset[key] = new
|
||||||
|
|
||||||
@@ -441,6 +441,26 @@ def delete_by_path(d: dict[str, Any], path: str) -> Any:
|
|||||||
type DictLike = dict[str, Any] | Any
|
type DictLike = dict[str, Any] | Any
|
||||||
|
|
||||||
|
|
||||||
|
def get_value_by_path(d: DictLike, path: str, fallback: Any = None) -> Any:
|
||||||
|
"""
|
||||||
|
Get the value at a specific dot-separated path in a nested dictionary.
|
||||||
|
|
||||||
|
If the path does not exist, it returns fallback.
|
||||||
|
|
||||||
|
:param d: The dictionary to get from.
|
||||||
|
:param path: The dot-separated path to the key (e.g., 'foo.bar').
|
||||||
|
"""
|
||||||
|
keys = path.split(".")
|
||||||
|
current = d
|
||||||
|
for key in keys[:-1]:
|
||||||
|
current = current.setdefault(key, {})
|
||||||
|
|
||||||
|
if isinstance(current, dict):
|
||||||
|
return current.get(keys[-1], fallback)
|
||||||
|
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
|
||||||
def set_value_by_path(d: DictLike, path: str, content: Any) -> None:
|
def set_value_by_path(d: DictLike, path: str, content: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Update the value at a specific dot-separated path in a nested dictionary.
|
Update the value at a specific dot-separated path in a nested dictionary.
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ from clan_lib.persist.util import (
|
|||||||
calc_patches,
|
calc_patches,
|
||||||
delete_by_path,
|
delete_by_path,
|
||||||
determine_writeability,
|
determine_writeability,
|
||||||
|
list_difference,
|
||||||
merge_objects,
|
merge_objects,
|
||||||
path_match,
|
path_match,
|
||||||
set_value_by_path,
|
set_value_by_path,
|
||||||
unmerge_lists,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ def test_list_unmerge() -> None:
|
|||||||
all_machines = ["machineA", "machineB"]
|
all_machines = ["machineA", "machineB"]
|
||||||
inventory = ["machineB"]
|
inventory = ["machineB"]
|
||||||
|
|
||||||
nix_machines = unmerge_lists(all_machines, inventory)
|
nix_machines = list_difference(all_machines, inventory)
|
||||||
assert nix_machines == ["machineA"]
|
assert nix_machines == ["machineA"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user