Merge pull request 'feat(api): add list_inventory_tags' (#4692) from feat/machine-tags-writeability into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4692
This commit is contained in:
@@ -1,12 +0,0 @@
|
|||||||
"""
|
|
||||||
DEPRECATED:
|
|
||||||
|
|
||||||
Don't use this module anymore
|
|
||||||
|
|
||||||
Instead use:
|
|
||||||
'clan_lib.persist.inventoryStore'
|
|
||||||
|
|
||||||
Which is an abstraction over the inventory
|
|
||||||
|
|
||||||
Interacting with 'clan_lib.inventory' is NOT recommended and will be removed
|
|
||||||
"""
|
|
||||||
@@ -12,6 +12,7 @@ from clan_lib.nix_models.clan import (
|
|||||||
InventoryMachinesType,
|
InventoryMachinesType,
|
||||||
InventoryMetaType,
|
InventoryMetaType,
|
||||||
InventoryServicesType,
|
InventoryServicesType,
|
||||||
|
InventoryTagsType,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .util import (
|
from .util import (
|
||||||
@@ -106,6 +107,7 @@ class InventorySnapshot(TypedDict):
|
|||||||
instances: NotRequired[InventoryInstancesType]
|
instances: NotRequired[InventoryInstancesType]
|
||||||
meta: NotRequired[InventoryMetaType]
|
meta: NotRequired[InventoryMetaType]
|
||||||
services: NotRequired[InventoryServicesType]
|
services: NotRequired[InventoryServicesType]
|
||||||
|
tags: NotRequired[InventoryTagsType]
|
||||||
|
|
||||||
|
|
||||||
class InventoryStore:
|
class InventoryStore:
|
||||||
|
|||||||
0
pkgs/clan-cli/clan_lib/tags/__init__.py
Normal file
0
pkgs/clan-cli/clan_lib/tags/__init__.py
Normal file
54
pkgs/clan-cli/clan_lib/tags/list.py
Normal file
54
pkgs/clan-cli/clan_lib/tags/list.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from clan_lib.api import API
|
||||||
|
from clan_lib.flake import Flake
|
||||||
|
from clan_lib.persist.inventory_store import InventoryStore
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TagList:
|
||||||
|
options: set[str]
|
||||||
|
special: set[str]
|
||||||
|
|
||||||
|
|
||||||
|
@API.register
|
||||||
|
def list_tags(flake: Flake) -> TagList:
|
||||||
|
"""
|
||||||
|
List all tags of a clan.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- 'options' - Existing Tags that can be added to machines
|
||||||
|
- 'special' - Prefined Tags that are special and cannot be added to machines, they can be used in roles and refer to a fixed set of machines.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
inventory_store = InventoryStore(flake=flake)
|
||||||
|
inventory = inventory_store.read()
|
||||||
|
|
||||||
|
machines = inventory.get("machines", {})
|
||||||
|
|
||||||
|
tags: set[str] = set()
|
||||||
|
|
||||||
|
for machine in machines.values():
|
||||||
|
machine_tags = machine.get("tags", [])
|
||||||
|
for tag in machine_tags:
|
||||||
|
tags.add(tag)
|
||||||
|
|
||||||
|
instances = inventory.get("instances", {})
|
||||||
|
for instance in instances.values():
|
||||||
|
roles: dict[str, Any] = instance.get("roles", {})
|
||||||
|
for role in roles.values():
|
||||||
|
role_tags = role.get("tags", {})
|
||||||
|
for tag in role_tags:
|
||||||
|
tags.add(tag)
|
||||||
|
|
||||||
|
global_tags = inventory.get("tags", {})
|
||||||
|
|
||||||
|
for tag in global_tags:
|
||||||
|
if tag not in tags:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tags.remove(tag)
|
||||||
|
|
||||||
|
return TagList(options=tags, special=set(global_tags.keys()))
|
||||||
84
pkgs/clan-cli/clan_lib/tags/list_test.py
Normal file
84
pkgs/clan-cli/clan_lib/tags/list_test.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from clan_lib.flake import Flake
|
||||||
|
from clan_lib.persist.inventory_store import InventoryStore
|
||||||
|
from clan_lib.persist.util import get_value_by_path, set_value_by_path
|
||||||
|
from clan_lib.tags.list import list_tags
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.with_core
|
||||||
|
def test_list_inventory_tags(clan_flake: Callable[..., Flake]) -> None:
|
||||||
|
flake = clan_flake(
|
||||||
|
{
|
||||||
|
"inventory": {
|
||||||
|
"machines": {
|
||||||
|
"jon": {
|
||||||
|
"tags": ["foo", "bar"],
|
||||||
|
},
|
||||||
|
"sara": {
|
||||||
|
"tags": ["foo", "baz", "fizz"],
|
||||||
|
},
|
||||||
|
"bob": {
|
||||||
|
"tags": ["foo", "bar"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"instances": {
|
||||||
|
"instance1": {
|
||||||
|
"roles": {
|
||||||
|
"role1": {"tags": {"predefined": {}, "maybe": {}}},
|
||||||
|
"role2": {"tags": {"predefined2": {}, "maybe2": {}}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
raw=r"""
|
||||||
|
{
|
||||||
|
inventory.tags = {
|
||||||
|
"global" = [ "future_machine" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
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 add tags via API")
|
||||||
|
|
||||||
|
# Check that the tags were updated
|
||||||
|
persisted = inventory_store._get_persisted() # noqa: SLF001
|
||||||
|
assert get_value_by_path(persisted, "machines.jon.tags", []) == new_tags
|
||||||
|
|
||||||
|
tags = list_tags(flake)
|
||||||
|
|
||||||
|
assert tags.options == set(
|
||||||
|
{
|
||||||
|
# Tags defined in nix
|
||||||
|
"bar",
|
||||||
|
"baz",
|
||||||
|
"fizz",
|
||||||
|
"foo",
|
||||||
|
"predefined",
|
||||||
|
"predefined2",
|
||||||
|
"maybe",
|
||||||
|
"maybe2",
|
||||||
|
# Tags managed by the UI
|
||||||
|
"managed1",
|
||||||
|
"managed2",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert tags.special == set(
|
||||||
|
{
|
||||||
|
# Predefined tags
|
||||||
|
"all",
|
||||||
|
"global",
|
||||||
|
"darwin",
|
||||||
|
"nixos",
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -36,6 +36,7 @@ COMMON_VERBS = {
|
|||||||
# If you need a new top-level resource, create an issue to discuss it first.
|
# If you need a new top-level resource, create an issue to discuss it first.
|
||||||
TOP_LEVEL_RESOURCES = {
|
TOP_LEVEL_RESOURCES = {
|
||||||
"clan", # clan management
|
"clan", # clan management
|
||||||
|
"tag", # Tags
|
||||||
"machine", # machine management
|
"machine", # machine management
|
||||||
"task", # task management
|
"task", # task management
|
||||||
"secret", # sops & key operations
|
"secret", # sops & key operations
|
||||||
|
|||||||
Reference in New Issue
Block a user