Merge pull request 'API: init delete instance' (#5641) from instance-delete into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5641
This commit is contained in:
hsjobeki
2025-10-23 12:46:24 +00:00
3 changed files with 105 additions and 2 deletions

View File

@@ -67,7 +67,7 @@ def set_value_by_path_tuple(d: DictLike, path: PathTuple, content: Any) -> None:
current[keys[-1]] = content current[keys[-1]] = content
def delete_by_path_tuple(d: dict[str, Any], path: PathTuple) -> Any: def delete_by_path_tuple(d: DictLike, path: PathTuple) -> Any:
"""Deletes the nested entry specified by a dot-separated path from the dictionary using pop(). """Deletes the nested entry specified by a dot-separated path from the dictionary using pop().
:param data: The dictionary to modify. :param data: The dictionary to modify.
@@ -126,6 +126,7 @@ def delete_by_path(d: dict[str, Any], path: str) -> Any:
V = TypeVar("V") V = TypeVar("V")
# TODO: Use PathTuple
def get_value_by_path( def get_value_by_path(
d: DictLike, d: DictLike,
path: str, path: str,

View File

@@ -15,7 +15,11 @@ from clan_lib.nix_models.clan import (
InventoryInstancesType, InventoryInstancesType,
) )
from clan_lib.persist.inventory_store import InventoryStore from clan_lib.persist.inventory_store import InventoryStore
from clan_lib.persist.path_utils import get_value_by_path, set_value_by_path from clan_lib.persist.path_utils import (
delete_by_path_tuple,
get_value_by_path,
set_value_by_path,
)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -492,6 +496,37 @@ def list_service_instances(flake: Flake) -> dict[str, InventoryInstanceInfo]:
return res return res
@API.register
def delete_service_instance(
flake: Flake,
instance_ref: str,
) -> None:
"""Deletes an instance
:param instance_ref: The name of the instance to update
:raises ClanError: If the instance_ref is invalid or cannot be deleted
"""
inventory_store = InventoryStore(flake)
inventory = inventory_store.read()
instance: InventoryInstance | None = get_value_by_path(
inventory, f"instances.{instance_ref}", None
)
if instance is None:
msg = f"Instance '{instance_ref}' not found"
raise ClanError(msg)
delete_by_path_tuple(inventory, ("instances", f"{instance_ref}"))
# TODO: improve error message
# "Cannot delete path 'instances.static"
inventory_store.write(
inventory, message=f"Delete service instance '{instance_ref}'"
)
@API.register @API.register
def set_service_instance( def set_service_instance(
flake: Flake, instance_ref: str, roles: InventoryInstanceRolesType flake: Flake, instance_ref: str, roles: InventoryInstanceRolesType
@@ -540,6 +575,8 @@ def set_service_instance(
) )
# override settings, machines only if passed # override settings, machines only if passed
# TODO: refactor after extraModules removal
# in https://git.clan.lol/clan/clan-core/pulls/5634
merged = { merged = {
**static, **static,
**role_cfg, **role_cfg,

View File

@@ -6,6 +6,7 @@ from clan_cli.tests.fixtures_flakes import nested_dict
from clan_lib.errors import ClanError from clan_lib.errors import ClanError
from clan_lib.flake.flake import Flake from clan_lib.flake.flake import Flake
from clan_lib.services.modules import ( from clan_lib.services.modules import (
delete_service_instance,
get_service_readmes, get_service_readmes,
list_service_instances, list_service_instances,
list_service_modules, list_service_modules,
@@ -251,3 +252,67 @@ def test_update_service_instance(
assert updated_machines == { assert updated_machines == {
"sara": {"settings": {"greeting": "sara"}}, "sara": {"settings": {"greeting": "sara"}},
} }
@pytest.mark.with_core
def test_delete_service_instance(
clan_flake: Callable[..., Flake],
) -> None:
# Data that can be mutated via API calls
mutable_inventory_json: Inventory = {
"instances": {
"to-remain": {"module": {"name": "admin"}},
"to-delete": {"module": {"name": "admin"}},
}
}
flake = clan_flake({}, mutable_inventory_json=mutable_inventory_json)
# Ensure preconditions
instances = list_service_instances(flake)
assert set(instances.keys()) == {"to-delete", "to-remain"}
# Raises for non-existing instance
with pytest.raises(ClanError) as excinfo:
delete_service_instance(flake, "non-existing-instance")
assert "Instance 'non-existing-instance' not found" in str(excinfo.value)
# Deletes instance
delete_service_instance(flake, "to-delete")
updated_instances = list_service_instances(flake)
assert set(updated_instances.keys()) == {"to-remain"}
@pytest.mark.with_core
def test_delete_static_service_instance(
clan_flake: Callable[..., Flake],
) -> None:
# Data that can be mutated via API calls
mutable_inventory_json: Inventory = {
"instances": {
"static": {"module": {"name": "admin"}},
}
}
flake = clan_flake(
{
"inventory": {
"instances": {
"static": {"roles": {"default": {}}},
}
}
},
mutable_inventory_json=mutable_inventory_json,
)
# Ensure preconditions
instances = list_service_instances(flake)
assert set(instances.keys()) == {"static"}
# Raises for non-existing instance
with pytest.raises(ClanError) as excinfo:
delete_service_instance(flake, "static")
# TODO: improve error message
assert "Cannot delete path 'instances.static" in str(excinfo.value)