From 8a6239e08d3059350a64d8000591f1da6849a8a3 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Tue, 12 Nov 2024 14:57:06 +0100 Subject: [PATCH 1/3] pkgs/cli: Add tagging support to `machines list` Add the `--tags` flag to `clan machines list` This now supports the machine tagging system from the inventory. Multiple tags are the intersection of the tags of a specific machine. Example two machines with overlapping tags: ``` server: ["intel"] laptop: ["intel", "graphical"] ``` - `clan machines list --tags intel` will output: ``` server laptop ``` - `clan machines list --tags intel graphical` will output: ``` laptop ``` - `clan machines list --tags graphical` will output: ``` laptop ``` --- pkgs/clan-cli/clan_cli/machines/list.py | 10 +++++ pkgs/clan-cli/clan_cli/tags.py | 58 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 pkgs/clan-cli/clan_cli/tags.py diff --git a/pkgs/clan-cli/clan_cli/machines/list.py b/pkgs/clan-cli/clan_cli/machines/list.py index 63c5bf847..f2e243d72 100644 --- a/pkgs/clan-cli/clan_cli/machines/list.py +++ b/pkgs/clan-cli/clan_cli/machines/list.py @@ -10,6 +10,7 @@ from clan_cli.cmd import run_no_stdout from clan_cli.errors import ClanCmdError, ClanError from clan_cli.inventory import Machine, load_inventory_eval, set_inventory from clan_cli.nix import nix_eval, nix_shell +from clan_cli.tags import list_nixos_machines_by_tags log = logging.getLogger(__name__) @@ -132,9 +133,18 @@ def check_machine_online( def list_command(args: argparse.Namespace) -> None: flake_path = args.flake.path + if args.tags: + list_nixos_machines_by_tags(flake_path, args.tags) + return for name in list_nixos_machines(flake_path): print(name) def register_list_parser(parser: argparse.ArgumentParser) -> None: + tag_parser = parser.add_argument( + "--tags", + nargs="+", + default=[], + help="Tags that machines should be queried for. Multiple tags will intersect.", + ) parser.set_defaults(func=list_command) diff --git a/pkgs/clan-cli/clan_cli/tags.py b/pkgs/clan-cli/clan_cli/tags.py new file mode 100644 index 000000000..4f84a792e --- /dev/null +++ b/pkgs/clan-cli/clan_cli/tags.py @@ -0,0 +1,58 @@ +import json +from pathlib import Path +from typing import Any + +from clan_cli.cmd import run_no_stdout +from clan_cli.errors import ClanError +from clan_cli.nix import nix_eval + + +def list_tagged_machines(flake_url: str | Path) -> dict[str, Any]: + """ + Query machines from the inventory with their meta information intact. + The meta information includes tags. + """ + cmd = nix_eval( + [ + f"{flake_url}#clanInternals.inventory.machines", + "--json", + ] + ) + proc = run_no_stdout(cmd) + + try: + res = proc.stdout.strip() + data = json.loads(res) + except json.JSONDecodeError as e: + msg = f"Error decoding tagged inventory machines from flake: {e}" + raise ClanError(msg) from e + else: + return data + + +def query_machines_by_tags( + flake_path: str | Path, tags: list[str] | None = None +) -> list[str]: + """ + Query machines by their respective tags, if multiple tags are specified + then only machines that have those respective tags specified will be listed. + It is an intersection of the tags and machines. + """ + machines = list_tagged_machines(flake_path) + + if not tags: + return list(machines.keys()) + + filtered_machines = [] + for machine_id, machine_values in machines.items(): + if all(tag in machine_values["tags"] for tag in tags): + filtered_machines.append(machine_id) + + return filtered_machines + + +def list_nixos_machines_by_tags( + flake_path: str | Path, tags: list[str] | None = None +) -> None: + for name in query_machines_by_tags(flake_path, tags): + print(name) From f160de27227bebb1c633abc72fe7310bfd8a3118 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Tue, 12 Nov 2024 14:57:29 +0100 Subject: [PATCH 2/3] pkgs/clan: `machines list --tags` add dynamic completer --- pkgs/clan-cli/clan_cli/machines/list.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/clan-cli/clan_cli/machines/list.py b/pkgs/clan-cli/clan_cli/machines/list.py index f2e243d72..163acf385 100644 --- a/pkgs/clan-cli/clan_cli/machines/list.py +++ b/pkgs/clan-cli/clan_cli/machines/list.py @@ -7,6 +7,7 @@ from typing import Literal from clan_cli.api import API from clan_cli.cmd import run_no_stdout +from clan_cli.completions import add_dynamic_completer, complete_tags from clan_cli.errors import ClanCmdError, ClanError from clan_cli.inventory import Machine, load_inventory_eval, set_inventory from clan_cli.nix import nix_eval, nix_shell @@ -147,4 +148,5 @@ def register_list_parser(parser: argparse.ArgumentParser) -> None: default=[], help="Tags that machines should be queried for. Multiple tags will intersect.", ) + add_dynamic_completer(tag_parser, complete_tags) parser.set_defaults(func=list_command) From fd4ba6d86fd7e2c010b645f5c3db031cb437bdb5 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Tue, 12 Nov 2024 14:57:44 +0100 Subject: [PATCH 3/3] pkgs/cli: Document `clan machines list --tags` --- pkgs/clan-cli/clan_cli/machines/cli.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkgs/clan-cli/clan_cli/machines/cli.py b/pkgs/clan-cli/clan_cli/machines/cli.py index 812a27969..ac91199d2 100644 --- a/pkgs/clan-cli/clan_cli/machines/cli.py +++ b/pkgs/clan-cli/clan_cli/machines/cli.py @@ -57,6 +57,13 @@ Examples: $ clan machines list Lists all the machines and their descriptions. + + $ clan machines list --tags [TAGS..] + Lists all the machines that have the specified tags associated through the inventory. + If multiple tags are specified machines are matched against both tags. + + $ clan machines list --tags vm + Lists all machines that are associated with the "vm" tag through the inventory. """ ), formatter_class=argparse.RawTextHelpFormatter,