Merge pull request 'inventoryStore: align class names and methods' (#5551) from fix-deletions into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5551
This commit is contained in:
@@ -7,7 +7,7 @@ from clan_lib.machines.actions import FieldSchema
|
||||
from clan_lib.nix_models.clan import InventoryMeta
|
||||
from clan_lib.persist.introspection import retrieve_typed_field_names
|
||||
from clan_lib.persist.inventory_store import InventoryStore
|
||||
from clan_lib.persist.write_rules import is_writeable_key
|
||||
from clan_lib.persist.write_rules import is_readonly_key
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -51,13 +51,13 @@ def get_clan_details_schema(flake: Flake) -> dict[str, FieldSchema]:
|
||||
|
||||
"""
|
||||
inventory_store = InventoryStore(flake)
|
||||
write_info = inventory_store.get_write_map()
|
||||
attribute_props = inventory_store.get_attribute_props()
|
||||
|
||||
field_names = retrieve_typed_field_names(InventoryMeta)
|
||||
|
||||
return {
|
||||
field: {
|
||||
"readonly": not is_writeable_key(f"meta.{field}", write_info),
|
||||
"readonly": is_readonly_key(f"meta.{field}", attribute_props),
|
||||
# TODO: Provide a meaningful reason
|
||||
"reason": None,
|
||||
"readonly_members": [],
|
||||
|
||||
@@ -18,7 +18,7 @@ from clan_lib.persist.path_utils import (
|
||||
list_difference,
|
||||
set_value_by_path,
|
||||
)
|
||||
from clan_lib.persist.write_rules import is_writeable_key
|
||||
from clan_lib.persist.write_rules import is_readonly_key
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -170,7 +170,7 @@ def get_machine_fields_schema(machine: Machine) -> dict[str, FieldSchema]:
|
||||
|
||||
"""
|
||||
inventory_store = InventoryStore(machine.flake)
|
||||
write_info = inventory_store.get_write_map()
|
||||
attribute_props = inventory_store.get_attribute_props()
|
||||
|
||||
field_names = retrieve_typed_field_names(InventoryMachine)
|
||||
|
||||
@@ -195,9 +195,9 @@ def get_machine_fields_schema(machine: Machine) -> dict[str, FieldSchema]:
|
||||
"readonly": (
|
||||
True
|
||||
if field in protected_fields
|
||||
else not is_writeable_key(
|
||||
else is_readonly_key(
|
||||
f"machines.{machine.name}.{field}",
|
||||
write_info,
|
||||
attribute_props,
|
||||
)
|
||||
),
|
||||
# TODO: Provide a meaningful reason
|
||||
|
||||
@@ -249,13 +249,15 @@ def test_get_machine_writeability(clan_flake: Callable[..., Flake]) -> None:
|
||||
persisted = inventory_store._get_persisted()
|
||||
assert get_value_by_path(persisted, "machines.jon.tags", []) == new_tags
|
||||
|
||||
write_info = get_machine_fields_schema(Machine("jon", flake))
|
||||
attribute_props = 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}}
|
||||
writeable_fields = {
|
||||
field for field, info in write_info.items() if not info["readonly"]
|
||||
field for field, info in attribute_props.items() if not info["readonly"]
|
||||
}
|
||||
read_only_fields = {
|
||||
field for field, info in attribute_props.items() if info["readonly"]
|
||||
}
|
||||
read_only_fields = {field for field, info in write_info.items() if info["readonly"]}
|
||||
|
||||
assert writeable_fields == {
|
||||
"tags",
|
||||
@@ -267,7 +269,7 @@ def test_get_machine_writeability(clan_flake: Callable[..., Flake]) -> None:
|
||||
}
|
||||
assert read_only_fields == {"machineClass", "name"}
|
||||
|
||||
assert write_info["tags"]["readonly_members"] == ["nix1", "all", "nixos"]
|
||||
assert attribute_props["tags"]["readonly_members"] == ["nix1", "all", "nixos"]
|
||||
|
||||
|
||||
@pytest.mark.with_core
|
||||
|
||||
@@ -77,8 +77,8 @@ def sanitize(data: Any, whitelist_paths: list[str], current_path: list[str]) ->
|
||||
|
||||
|
||||
@dataclass
|
||||
class WriteInfo:
|
||||
writeables: AttributeMap
|
||||
class PersistenceInfo:
|
||||
attribute_props: AttributeMap
|
||||
data_eval: "InventorySnapshot"
|
||||
data_disk: "InventorySnapshot"
|
||||
|
||||
@@ -184,7 +184,7 @@ class InventoryStore:
|
||||
|
||||
return inventory
|
||||
|
||||
def _get_inventory_current_priority(self) -> dict:
|
||||
def _get_introspection(self) -> dict:
|
||||
"""Returns the current priority of the inventory values
|
||||
|
||||
machines = {
|
||||
@@ -203,33 +203,33 @@ class InventoryStore:
|
||||
"""
|
||||
return self._flake.select("clanInternals.inventoryClass.introspection")
|
||||
|
||||
def _write_map(self) -> WriteInfo:
|
||||
def _get_persistence_info(self) -> PersistenceInfo:
|
||||
"""Get the paths of the writeable keys in the inventory
|
||||
|
||||
Load the inventory and determine the writeable keys
|
||||
Performs 2 nix evaluations to get the current priority and the inventory
|
||||
"""
|
||||
current_priority = self._get_inventory_current_priority()
|
||||
current_priority = self._get_introspection()
|
||||
|
||||
data_eval: InventorySnapshot = self._load_merged_inventory()
|
||||
data_disk: InventorySnapshot = self._get_persisted()
|
||||
|
||||
write_map = compute_attribute_persistence(
|
||||
attribute_props = compute_attribute_persistence(
|
||||
current_priority,
|
||||
dict(data_eval),
|
||||
dict(data_disk),
|
||||
inventory_file_name=self.inventory_file.name,
|
||||
)
|
||||
|
||||
return WriteInfo(write_map, data_eval, data_disk)
|
||||
return PersistenceInfo(attribute_props, data_eval, data_disk)
|
||||
|
||||
def get_write_map(self) -> Any:
|
||||
def get_attribute_props(self) -> Any:
|
||||
"""Get the writeability of the inventory
|
||||
|
||||
:return: A dictionary with the writeability of all paths
|
||||
"""
|
||||
write_info = self._write_map()
|
||||
return write_info.writeables
|
||||
persistence_info = self._get_persistence_info()
|
||||
return persistence_info.attribute_props
|
||||
|
||||
def read(self) -> InventorySnapshot:
|
||||
"""Accessor to the merged inventory
|
||||
@@ -265,15 +265,15 @@ class InventoryStore:
|
||||
"""Write the inventory to the flake directory
|
||||
and commit it to git with the given message
|
||||
"""
|
||||
write_info = self._write_map()
|
||||
persistence_info = self._get_persistence_info()
|
||||
patchset, delete_set = calc_patches(
|
||||
dict(write_info.data_disk),
|
||||
dict(persistence_info.data_disk),
|
||||
dict(update),
|
||||
dict(write_info.data_eval),
|
||||
write_info.writeables,
|
||||
dict(persistence_info.data_eval),
|
||||
persistence_info.attribute_props,
|
||||
)
|
||||
|
||||
persisted = dict(write_info.data_disk)
|
||||
persisted = dict(persistence_info.data_disk)
|
||||
for patch_path, data in patchset.items():
|
||||
set_value_by_path_tuple(persisted, patch_path, data)
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ def test_simple_deferred(setup_test_files: Path) -> None:
|
||||
_keys={"foo"}, # disable toplevel filtering
|
||||
)
|
||||
|
||||
attribute_props = store._write_map().writeables
|
||||
attribute_props = store._get_persistence_info().attribute_props
|
||||
assert attribute_props == {
|
||||
("foo",): {PersistenceAttribute.WRITE},
|
||||
("foo", "a"): {PersistenceAttribute.WRITE, PersistenceAttribute.DELETE},
|
||||
|
||||
@@ -12,9 +12,9 @@ from clan_lib.persist.path_utils import (
|
||||
from clan_lib.persist.validate import (
|
||||
validate_list_uniqueness,
|
||||
validate_no_static_deletion,
|
||||
validate_not_readonly,
|
||||
validate_patch_conflicts,
|
||||
validate_type_compatibility,
|
||||
validate_writeability,
|
||||
)
|
||||
from clan_lib.persist.write_rules import AttributeMap, PersistenceAttribute
|
||||
|
||||
@@ -131,7 +131,7 @@ Please report this issue at https://git.clan.lol/clan/clan-core/issues
|
||||
continue
|
||||
|
||||
# Validate the change is allowed
|
||||
validate_writeability(path, attribute_props)
|
||||
validate_not_readonly(path, attribute_props)
|
||||
validate_type_compatibility(path, old_value, new_value)
|
||||
validate_list_uniqueness(path, new_value)
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ def test_update_add_empty_dict() -> None:
|
||||
|
||||
data_disk: dict = {}
|
||||
|
||||
writeables = compute_attribute_persistence(prios, data_eval, data_disk)
|
||||
attribute_props = compute_attribute_persistence(prios, data_eval, data_disk)
|
||||
|
||||
update = deepcopy(data_eval)
|
||||
|
||||
@@ -79,7 +79,7 @@ def test_update_add_empty_dict() -> None:
|
||||
data_disk,
|
||||
update,
|
||||
all_values=data_eval,
|
||||
attribute_props=writeables,
|
||||
attribute_props=attribute_props,
|
||||
)
|
||||
|
||||
assert patchset == {("foo", "mimi"): {}} # this is what gets persisted
|
||||
|
||||
@@ -7,7 +7,7 @@ from clan_lib.persist.path_utils import (
|
||||
path_starts_with,
|
||||
path_to_string,
|
||||
)
|
||||
from clan_lib.persist.write_rules import AttributeMap, is_writeable_path
|
||||
from clan_lib.persist.write_rules import AttributeMap, is_readonly_path
|
||||
|
||||
|
||||
def validate_no_static_deletion(
|
||||
@@ -20,9 +20,9 @@ def validate_no_static_deletion(
|
||||
raise ClanError(msg)
|
||||
|
||||
|
||||
def validate_writeability(path: PathTuple, writeables: AttributeMap) -> None:
|
||||
def validate_not_readonly(path: PathTuple, writeables: AttributeMap) -> None:
|
||||
"""Validate that a path is writeable."""
|
||||
if not is_writeable_path(path, writeables):
|
||||
if is_readonly_path(path, writeables):
|
||||
msg = f"Path '{path_to_string(path)}' is readonly. - It seems its value is statically defined in nix."
|
||||
raise ClanError(msg)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class PersistenceAttribute(Enum):
|
||||
type AttributeMap = dict[PathTuple, set[PersistenceAttribute]]
|
||||
|
||||
|
||||
def is_writeable_path(
|
||||
def is_readonly_path(
|
||||
key: PathTuple,
|
||||
attributes: AttributeMap,
|
||||
) -> bool:
|
||||
@@ -30,9 +30,9 @@ def is_writeable_path(
|
||||
current_path = remaining
|
||||
if current_path in attributes:
|
||||
if PersistenceAttribute.WRITE in attributes[current_path]:
|
||||
return True
|
||||
if PersistenceAttribute.READONLY in attributes[current_path]:
|
||||
return False
|
||||
if PersistenceAttribute.READONLY in attributes[current_path]:
|
||||
return True
|
||||
# Check the parent path
|
||||
remaining = remaining[:-1]
|
||||
|
||||
@@ -40,7 +40,7 @@ def is_writeable_path(
|
||||
raise ClanError(msg)
|
||||
|
||||
|
||||
def is_writeable_key(
|
||||
def is_readonly_key(
|
||||
key: str,
|
||||
attributes: AttributeMap,
|
||||
) -> bool:
|
||||
@@ -51,7 +51,7 @@ def is_writeable_key(
|
||||
In case of ambiguity use is_writeable_path with tuple keys.
|
||||
"""
|
||||
items = key.split(".")
|
||||
return is_writeable_path(tuple(items), attributes)
|
||||
return is_readonly_path(tuple(items), attributes)
|
||||
|
||||
|
||||
def get_priority(value: Any) -> int | None:
|
||||
|
||||
Reference in New Issue
Block a user