Merge pull request 'api/machines: move configuration data into subattribute' (#5048) from api-list-machines into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5048
This commit is contained in:
@@ -136,7 +136,7 @@ export const SidebarBody = (props: SidebarProps) => {
|
||||
<MachineRoute
|
||||
clanURI={clanURI}
|
||||
machineID={id}
|
||||
name={machine.name || id}
|
||||
name={machine.data.name || id}
|
||||
serviceCount={0}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -26,13 +26,19 @@ const mockFetcher: Fetcher = <K extends OperationNames>(
|
||||
const resultData: Partial<ResultDataMap> = {
|
||||
list_machines: {
|
||||
pandora: {
|
||||
name: "pandora",
|
||||
data: {
|
||||
name: "pandora",
|
||||
},
|
||||
},
|
||||
enceladus: {
|
||||
name: "enceladus",
|
||||
data: {
|
||||
name: "enceladus",
|
||||
},
|
||||
},
|
||||
dione: {
|
||||
name: "dione",
|
||||
data: {
|
||||
name: "dione",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -62,20 +62,28 @@ const mockFetcher: Fetcher = <K extends OperationNames>(
|
||||
},
|
||||
list_machines: {
|
||||
jon: {
|
||||
name: "jon",
|
||||
tags: ["all", "nixos", "tag1"],
|
||||
data: {
|
||||
name: "jon",
|
||||
tags: ["all", "nixos", "tag1"],
|
||||
},
|
||||
},
|
||||
sara: {
|
||||
name: "sara",
|
||||
tags: ["all", "darwin", "tag2"],
|
||||
data: {
|
||||
name: "sara",
|
||||
tags: ["all", "darwin", "tag2"],
|
||||
},
|
||||
},
|
||||
kyra: {
|
||||
name: "kyra",
|
||||
tags: ["all", "darwin", "tag2"],
|
||||
data: {
|
||||
name: "kyra",
|
||||
tags: ["all", "darwin", "tag2"],
|
||||
},
|
||||
},
|
||||
leila: {
|
||||
name: "leila",
|
||||
tags: ["all", "darwin", "tag2"],
|
||||
data: {
|
||||
name: "leila",
|
||||
tags: ["all", "darwin", "tag2"],
|
||||
},
|
||||
},
|
||||
},
|
||||
list_tags: {
|
||||
|
||||
@@ -120,7 +120,7 @@ const SelectService = () => {
|
||||
label: t,
|
||||
type: "tag" as const,
|
||||
members: Object.entries(machinesQuery.data || {})
|
||||
.filter(([_, m]) => m.tags?.includes(t))
|
||||
.filter(([_, m]) => m.data.tags?.includes(t))
|
||||
.map(([k]) => k),
|
||||
};
|
||||
});
|
||||
@@ -206,7 +206,7 @@ const useOptions = (tagsQuery: TagsQuery, machinesQuery: MachinesQuery) =>
|
||||
label: tag,
|
||||
value: "t_" + tag,
|
||||
members: Object.entries(machines)
|
||||
.filter(([_, v]) => v.tags?.includes(tag))
|
||||
.filter(([_, v]) => v.data.tags?.includes(tag))
|
||||
.map(([k]) => k),
|
||||
}));
|
||||
|
||||
|
||||
@@ -103,7 +103,9 @@ def get_machines_for_update(
|
||||
machines_to_update = list(
|
||||
filter(
|
||||
requires_explicit_update,
|
||||
instantiate_inventory_to_machines(flake, machines_with_tags).values(),
|
||||
instantiate_inventory_to_machines(
|
||||
flake, {name: m.data for name, m in machines_with_tags.items()}
|
||||
).values(),
|
||||
),
|
||||
)
|
||||
# all machines that are in the clan but not included in the update list
|
||||
@@ -128,13 +130,13 @@ def get_machines_for_update(
|
||||
machines_to_update = []
|
||||
valid_names = validate_machine_names(explicit_names, flake)
|
||||
for name in valid_names:
|
||||
inventory_machine = machines_with_tags.get(name)
|
||||
if not inventory_machine:
|
||||
machine = machines_with_tags.get(name)
|
||||
if not machine:
|
||||
msg = "This is an internal bug"
|
||||
raise ClanError(msg)
|
||||
|
||||
machines_to_update.append(
|
||||
Machine.from_inventory(name, flake, inventory_machine),
|
||||
Machine.from_inventory(name, flake, machine.data),
|
||||
)
|
||||
|
||||
return machines_to_update
|
||||
|
||||
@@ -7,6 +7,7 @@ from clan_lib.errors import ClanError
|
||||
from clan_lib.flake.flake import Flake
|
||||
from clan_lib.machines.machines import Machine
|
||||
from clan_lib.nix_models.clan import (
|
||||
InventoryInstance,
|
||||
InventoryMachine,
|
||||
)
|
||||
from clan_lib.persist.inventory_store import InventoryStore
|
||||
@@ -41,28 +42,68 @@ class MachineState(TypedDict):
|
||||
# add more info later when retrieving remote state
|
||||
|
||||
|
||||
@dataclass
|
||||
class MachineResponse:
|
||||
data: InventoryMachine
|
||||
# Reference the installed service instances
|
||||
instance_refs: set[str] = field(default_factory=set)
|
||||
|
||||
|
||||
def machine_instances(
|
||||
machine_name: str,
|
||||
instances: dict[str, InventoryInstance],
|
||||
tag_map: dict[str, set[str]],
|
||||
) -> set[str]:
|
||||
res: set[str] = set()
|
||||
for instance_name, instance in instances.items():
|
||||
for role in instance.get("roles", {}).values():
|
||||
if machine_name in role.get("machines", {}):
|
||||
res.add(instance_name)
|
||||
|
||||
for tag in role.get("tags", {}):
|
||||
if tag in tag_map and machine_name in tag_map[tag]:
|
||||
res.add(instance_name)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@API.register
|
||||
def list_machines(
|
||||
flake: Flake,
|
||||
opts: ListOptions | None = None,
|
||||
) -> dict[str, InventoryMachine]:
|
||||
) -> dict[str, MachineResponse]:
|
||||
"""List machines of a clan"""
|
||||
inventory_store = InventoryStore(flake=flake)
|
||||
inventory = inventory_store.read()
|
||||
|
||||
machines = inventory.get("machines", {})
|
||||
raw_machines = inventory.get("machines", {})
|
||||
|
||||
if opts and opts.filter.tags is not None:
|
||||
filtered_machines = {}
|
||||
tag_map: dict[str, set[str]] = {}
|
||||
|
||||
for machine_name, machine in machines.items():
|
||||
for machine_name, machine in raw_machines.items():
|
||||
for tag in machine.get("tags", []):
|
||||
if tag not in tag_map:
|
||||
tag_map[tag] = set()
|
||||
tag_map[tag].add(machine_name)
|
||||
|
||||
instances = inventory.get("instances", {})
|
||||
|
||||
res: dict[str, MachineResponse] = {}
|
||||
for machine_name, machine in raw_machines.items():
|
||||
m = MachineResponse(
|
||||
data=InventoryMachine(**machine),
|
||||
instance_refs=machine_instances(machine_name, instances, tag_map),
|
||||
)
|
||||
|
||||
# Check filters
|
||||
if opts and opts.filter.tags is not None:
|
||||
machine_tags = machine.get("tags", [])
|
||||
if all(ft in machine_tags for ft in opts.filter.tags):
|
||||
filtered_machines[machine_name] = machine
|
||||
if not all(ft in machine_tags for ft in opts.filter.tags):
|
||||
continue
|
||||
|
||||
return filtered_machines
|
||||
res[machine_name] = m
|
||||
|
||||
return machines
|
||||
return res
|
||||
|
||||
|
||||
@API.register
|
||||
|
||||
@@ -67,6 +67,33 @@ def test_list_inventory_machines(clan_flake: Callable[..., Flake]) -> None:
|
||||
assert list(machines.keys()) == ["jon", "sara", "vanessa"]
|
||||
|
||||
|
||||
@pytest.mark.with_core
|
||||
def test_list_machines_instance_refs(clan_flake: Callable[..., Flake]) -> None:
|
||||
flake = clan_flake(
|
||||
{
|
||||
"inventory": {
|
||||
"machines": {
|
||||
"jon": {},
|
||||
"sara": {},
|
||||
},
|
||||
"instances": {
|
||||
"admin": {
|
||||
"roles": {"default": {"machines": {"jon": {}}}},
|
||||
},
|
||||
"borgbackup": {
|
||||
"roles": {"default": {"tags": {"all": {}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
machines = list_machines(flake)
|
||||
|
||||
assert machines["sara"].instance_refs == set({"borgbackup"})
|
||||
assert machines["jon"].instance_refs == set({"admin", "borgbackup"})
|
||||
|
||||
|
||||
@pytest.mark.with_core
|
||||
def test_set_machine_no_op(clan_flake: Callable[..., Flake]) -> None:
|
||||
flake = clan_flake(
|
||||
|
||||
@@ -30,7 +30,9 @@ def list_full_machines(flake: Flake) -> dict[str, Machine]:
|
||||
"""Like `list_machines`, but returns a full 'machine' instance for each machine."""
|
||||
machines = list_machines(flake)
|
||||
|
||||
return instantiate_inventory_to_machines(flake, machines)
|
||||
return instantiate_inventory_to_machines(
|
||||
flake, {name: m.data for name, m in machines.items()}
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
Reference in New Issue
Block a user