From a364b5ebf3309df661c453b9818aecce99b2a8d8 Mon Sep 17 00:00:00 2001 From: DavHau Date: Fri, 29 Aug 2025 13:14:19 +0000 Subject: [PATCH] API/list_service_instances: add module metadata (#5023) @hsjobeki Co-authored-by: Johannes Kirschbauer Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5023 --- .../ui/src/workflows/Service/Service.tsx | 5 ++- pkgs/clan-cli/clan_lib/services/modules.py | 37 ++++++++++++++++--- .../clan_lib/services/modules_test.py | 35 ++++++++++++++++++ templates/clan/flake-parts-minimal/flake.nix | 6 +-- 4 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 pkgs/clan-cli/clan_lib/services/modules_test.py diff --git a/pkgs/clan-app/ui/src/workflows/Service/Service.tsx b/pkgs/clan-app/ui/src/workflows/Service/Service.tsx index 21e6f720e..e5e0ca3e7 100644 --- a/pkgs/clan-app/ui/src/workflows/Service/Service.tsx +++ b/pkgs/clan-app/ui/src/workflows/Service/Service.tsx @@ -78,8 +78,9 @@ const SelectService = () => { instances: Object.entries(serviceInstancesQuery.data) .filter( ([name, i]) => - i.module?.name === m.module.name && - (!i.module?.input || i.module?.input === m.module.input), + i.module.module.name === m.module.name && + (!i.module.module.input || + i.module.module.input === m.module.input), ) .map(([name, _]) => name), })), diff --git a/pkgs/clan-cli/clan_lib/services/modules.py b/pkgs/clan-cli/clan_lib/services/modules.py index 6ddaf99fc..33a7e8902 100644 --- a/pkgs/clan-cli/clan_lib/services/modules.py +++ b/pkgs/clan-cli/clan_lib/services/modules.py @@ -162,6 +162,10 @@ def list_service_modules(flake: Flake) -> list[Module]: """Show information about a module""" modules = flake.select("clanInternals.inventoryClass.modulesPerSource") + if "clan-core" not in modules: + msg = "Cannot find 'clan-core' input in the flake. Make sure your clan-core input is named 'clan-core'" + raise ClanError(msg) + res: list[Module] = [] for input_name, module_set in modules.items(): for module_name, module_info in module_set.items(): @@ -331,11 +335,34 @@ def create_service_instance( ) +class InventoryInstanceInfo(TypedDict): + module: Module + roles: InventoryInstanceRolesType + + @API.register -def list_service_instances( - flake: Flake, -) -> dict[str, InventoryInstance]: - """Show information about a module""" +def list_service_instances(flake: Flake) -> dict[str, InventoryInstanceInfo]: + """Returns all currently present service instances including their full configuration""" inventory_store = InventoryStore(flake) inventory = inventory_store.read() - return inventory.get("instances", {}) + service_modules = { + (mod["module"]["name"], mod["module"].get("input", "clan-core")): mod + for mod in list_service_modules(flake) + } + instances = inventory.get("instances", {}) + res: dict[str, InventoryInstanceInfo] = {} + for instance_name, instance in instances.items(): + module_key = ( + instance.get("module", {})["name"], + instance.get("module", {}).get("input") + or "clan-core", # Replace None (or falsey) with "clan-core" + ) + module = service_modules.get(module_key) + if module is None: + msg = f"Module '{module_key}' for instance '{instance_name}' not found" + raise ClanError(msg) + res[instance_name] = InventoryInstanceInfo( + module=module, + roles=instance.get("roles", {}), + ) + return res diff --git a/pkgs/clan-cli/clan_lib/services/modules_test.py b/pkgs/clan-cli/clan_lib/services/modules_test.py new file mode 100644 index 000000000..bf157a42a --- /dev/null +++ b/pkgs/clan-cli/clan_lib/services/modules_test.py @@ -0,0 +1,35 @@ +from collections.abc import Callable + +import pytest +from clan_cli.tests.fixtures_flakes import nested_dict +from clan_lib.flake.flake import Flake +from clan_lib.services.modules import list_service_instances + + +@pytest.mark.with_core +def test_list_service_instances( + clan_flake: Callable[..., Flake], +) -> None: + config = nested_dict() + config["inventory"]["machines"]["alice"] = {} + config["inventory"]["machines"]["bob"] = {} + # implicit module selection (defaults to clan-core/admin) + config["inventory"]["instances"]["admin"]["roles"]["default"]["tags"]["all"] = {} + # explicit module selection + config["inventory"]["instances"]["my-sshd"]["module"]["input"] = "clan-core" + config["inventory"]["instances"]["my-sshd"]["module"]["name"] = "sshd" + # input = null + config["inventory"]["instances"]["my-sshd-2"]["module"]["input"] = None + config["inventory"]["instances"]["my-sshd-2"]["module"]["name"] = "sshd" + # external input + flake = clan_flake(config) + + instances = list_service_instances(flake) + + assert list(instances.keys()) == ["admin", "my-sshd", "my-sshd-2"] + assert instances["admin"]["module"]["module"].get("input") == "clan-core" + assert instances["admin"]["module"]["module"].get("name") == "admin" + assert instances["my-sshd"]["module"]["module"].get("input") == "clan-core" + assert instances["my-sshd"]["module"]["module"].get("name") == "sshd" + assert instances["my-sshd-2"]["module"]["module"].get("input") == "clan-core" + assert instances["my-sshd-2"]["module"]["module"].get("name") == "sshd" diff --git a/templates/clan/flake-parts-minimal/flake.nix b/templates/clan/flake-parts-minimal/flake.nix index 63afbb405..baead908c 100644 --- a/templates/clan/flake-parts-minimal/flake.nix +++ b/templates/clan/flake-parts-minimal/flake.nix @@ -1,11 +1,11 @@ { inputs = { - clan.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"; - nixpkgs.follows = "clan/nixpkgs"; + clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"; + nixpkgs.follows = "clan-core/nixpkgs"; flake-parts.url = "github:hercules-ci/flake-parts"; - flake-parts.inputs.nixpkgs-lib.follows = "clan/nixpkgs"; + flake-parts.inputs.nixpkgs-lib.follows = "clan-core/nixpkgs"; }; outputs =