From f4792109ec8c4cdb8fd1437b508f0552a16791ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 16 Apr 2025 13:49:47 +0200 Subject: [PATCH 1/3] Remove unused `clan history update` subcommand --- pkgs/clan-cli/clan_cli/history/__init__.py | 3 -- pkgs/clan-cli/clan_cli/history/update.py | 50 ---------------------- 2 files changed, 53 deletions(-) delete mode 100644 pkgs/clan-cli/clan_cli/history/update.py diff --git a/pkgs/clan-cli/clan_cli/history/__init__.py b/pkgs/clan-cli/clan_cli/history/__init__.py index 5edb8d0e2..bb9cefe0b 100644 --- a/pkgs/clan-cli/clan_cli/history/__init__.py +++ b/pkgs/clan-cli/clan_cli/history/__init__.py @@ -3,7 +3,6 @@ import argparse from .add import register_add_parser from .list import register_list_parser -from .update import register_update_parser # takes a (sub)parser and configures it @@ -18,5 +17,3 @@ def register_parser(parser: argparse.ArgumentParser) -> None: register_add_parser(add_parser) list_parser = subparser.add_parser("list", help="List recently used flakes") register_list_parser(list_parser) - update_parser = subparser.add_parser("update", help="Update a clan flake") - register_update_parser(update_parser) diff --git a/pkgs/clan-cli/clan_cli/history/update.py b/pkgs/clan-cli/clan_cli/history/update.py deleted file mode 100644 index 5bdd65556..000000000 --- a/pkgs/clan-cli/clan_cli/history/update.py +++ /dev/null @@ -1,50 +0,0 @@ -# !/usr/bin/env python3 -import argparse -import datetime - -from clan_cli.clan.inspect import inspect_flake -from clan_cli.clan_uri import ClanURI -from clan_cli.errors import ClanCmdError -from clan_cli.locked_open import write_history_file -from clan_cli.nix import nix_metadata - -from .add import HistoryEntry, list_history - - -def update_history() -> list[HistoryEntry]: - logs = list_history() - - for entry in logs: - try: - meta = nix_metadata(str(entry.flake.flake_url)) - except ClanCmdError as e: - print(f"Failed to update {entry.flake.flake_url}: {e}") - continue - - new_hash = meta["locked"]["narHash"] - if new_hash != entry.flake.nar_hash: - print( - f"Updating {entry.flake.flake_url} from {entry.flake.nar_hash} to {new_hash}" - ) - uri = ClanURI.from_str( - url=str(entry.flake.flake_url), - machine_name=entry.flake.flake_attr, - ) - flake = inspect_flake(uri.get_url(), uri.machine_name) - flake.flake_url = flake.flake_url - entry = HistoryEntry( - flake=flake, - last_used=datetime.datetime.now(tz=datetime.UTC).isoformat(), - ) - - write_history_file(logs) - return logs - - -def add_update_command(args: argparse.Namespace) -> None: - update_history() - - -# takes a (sub)parser and configures it -def register_update_parser(parser: argparse.ArgumentParser) -> None: - parser.set_defaults(func=add_update_command) From 0b4e896af3811b99445cdd23c5f97f886887c7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 16 Apr 2025 14:22:45 +0200 Subject: [PATCH 2/3] migrate clan history to clan-vm-manager this functionality is not really useful or used in clan-vm-manager and therefore should live in the clan-vm-manager. Not porting the test for now because we probably get rid of the clan-vm-manager soon in favour of the UI. --- docs/mkdocs.yml | 1 - pkgs/clan-cli/clan_cli/__init__.py | 7 --- pkgs/clan-cli/clan_cli/history/__init__.py | 19 -------- pkgs/clan-cli/clan_cli/history/list.py | 26 ---------- .../clan_cli/tests/test_history_cli.py | 47 ------------------- .../clan_vm_manager/components/vmobj.py | 2 +- .../clan_vm_manager/history.py} | 45 +++++++++++++++--- .../clan_vm_manager/singletons/use_join.py | 2 +- .../clan_vm_manager/singletons/use_vms.py | 2 +- .../clan_vm_manager/windows/main_window.py | 2 +- pkgs/clan-vm-manager/pyproject.toml | 2 +- pkgs/clan-vm-manager/tests/conftest.py | 1 + pkgs/clan-vm-manager/tests/stdout.py | 34 ++++++++++++++ 13 files changed, 79 insertions(+), 111 deletions(-) delete mode 100644 pkgs/clan-cli/clan_cli/history/__init__.py delete mode 100644 pkgs/clan-cli/clan_cli/history/list.py delete mode 100644 pkgs/clan-cli/clan_cli/tests/test_history_cli.py rename pkgs/{clan-cli/clan_cli/history/add.py => clan-vm-manager/clan_vm_manager/history.py} (74%) create mode 100644 pkgs/clan-vm-manager/tests/stdout.py diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 2d2afce8c..25429a974 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -125,7 +125,6 @@ nav: - reference/cli/facts.md - reference/cli/flakes.md - reference/cli/flash.md - - reference/cli/history.md - reference/cli/machines.md - reference/cli/select.md - reference/cli/secrets.md diff --git a/pkgs/clan-cli/clan_cli/__init__.py b/pkgs/clan-cli/clan_cli/__init__.py index 98e85191a..cedf7e430 100644 --- a/pkgs/clan-cli/clan_cli/__init__.py +++ b/pkgs/clan-cli/clan_cli/__init__.py @@ -16,7 +16,6 @@ __all__ = ["admin", "directory", "disk", "iwd", "mdns_discovery", "modules", "up from . import ( backups, clan, - history, secrets, select, state, @@ -372,12 +371,6 @@ For more detailed information, visit: {help_hyperlink("deploy", "https://docs.cl ) vms.register_parser(parser_vms) - parser_history = subparsers.add_parser( - "history", - description="manage history", - ) - history.register_parser(parser_history) - parser_select = subparsers.add_parser( "select", aliases=["se"], diff --git a/pkgs/clan-cli/clan_cli/history/__init__.py b/pkgs/clan-cli/clan_cli/history/__init__.py deleted file mode 100644 index bb9cefe0b..000000000 --- a/pkgs/clan-cli/clan_cli/history/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# !/usr/bin/env python3 -import argparse - -from .add import register_add_parser -from .list import register_list_parser - - -# takes a (sub)parser and configures it -def register_parser(parser: argparse.ArgumentParser) -> None: - subparser = parser.add_subparsers( - title="command", - description="the command to run", - help="the command to run", - required=True, - ) - add_parser = subparser.add_parser("add", help="Add a clan flake") - register_add_parser(add_parser) - list_parser = subparser.add_parser("list", help="List recently used flakes") - register_list_parser(list_parser) diff --git a/pkgs/clan-cli/clan_cli/history/list.py b/pkgs/clan-cli/clan_cli/history/list.py deleted file mode 100644 index 778a6c311..000000000 --- a/pkgs/clan-cli/clan_cli/history/list.py +++ /dev/null @@ -1,26 +0,0 @@ -# !/usr/bin/env python3 -import argparse -from datetime import datetime - -from .add import HistoryEntry, list_history - - -def list_history_command(args: argparse.Namespace) -> None: - res: dict[str, list[HistoryEntry]] = {} - for history_entry in list_history(): - url = str(history_entry.flake.flake_url) - if res.get(url) is None: - res[url] = [] - res[url].append(history_entry) - - for flake_url, entries in res.items(): - print(flake_url) - for entry in entries: - d = datetime.fromisoformat(entry.last_used) - last_used = d.strftime("%d/%m/%Y %H:%M:%S") - print(f" {entry.flake.flake_attr} ({last_used})") - - -# takes a (sub)parser and configures it -def register_list_parser(parser: argparse.ArgumentParser) -> None: - parser.set_defaults(func=list_history_command) diff --git a/pkgs/clan-cli/clan_cli/tests/test_history_cli.py b/pkgs/clan-cli/clan_cli/tests/test_history_cli.py deleted file mode 100644 index 2377fd70d..000000000 --- a/pkgs/clan-cli/clan_cli/tests/test_history_cli.py +++ /dev/null @@ -1,47 +0,0 @@ -import json -from typing import TYPE_CHECKING - -import pytest -from clan_cli.dirs import user_history_file -from clan_cli.history.add import HistoryEntry -from clan_cli.tests.fixtures_flakes import FlakeForTest -from clan_cli.tests.helpers import cli -from clan_cli.tests.stdout import CaptureOutput - -if TYPE_CHECKING: - pass - - -@pytest.mark.impure -def test_history_add( - test_flake_with_core: FlakeForTest, -) -> None: - cmd = [ - "history", - "add", - f"clan://{test_flake_with_core.path}#vm1", - ] - cli.run(cmd) - - history_file = user_history_file() - assert history_file.exists() - history = [ - HistoryEntry.from_json(entry) for entry in json.loads(history_file.read_text()) - ] - assert str(history[0].flake.flake_url) == str(test_flake_with_core.path) - - -@pytest.mark.impure -def test_history_list( - capture_output: CaptureOutput, - test_flake_with_core: FlakeForTest, -) -> None: - with capture_output as output: - cli.run(["history", "list"]) - assert str(test_flake_with_core.path) not in output.out - - cli.run(["history", "add", f"clan://{test_flake_with_core.path}#vm1"]) - - with capture_output as output: - cli.run(["history", "list"]) - assert str(test_flake_with_core.path) in output.out diff --git a/pkgs/clan-vm-manager/clan_vm_manager/components/vmobj.py b/pkgs/clan-vm-manager/clan_vm_manager/components/vmobj.py index f83945ea7..741d0fcb3 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/components/vmobj.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/components/vmobj.py @@ -15,12 +15,12 @@ import gi from clan_cli import vms from clan_cli.clan_uri import ClanURI from clan_cli.dirs import vm_state_dir -from clan_cli.history.add import HistoryEntry from clan_cli.machines.machines import Machine from clan_cli.vms.inspect import inspect_vm from clan_cli.vms.qemu import QMPWrapper from clan_vm_manager.components.executor import MPProcess, spawn +from clan_vm_manager.history import HistoryEntry from clan_vm_manager.singletons.toast import ( InfoToast, SuccessToast, diff --git a/pkgs/clan-cli/clan_cli/history/add.py b/pkgs/clan-vm-manager/clan_vm_manager/history.py similarity index 74% rename from pkgs/clan-cli/clan_cli/history/add.py rename to pkgs/clan-vm-manager/clan_vm_manager/history.py index 38aa743a8..566cd2a44 100644 --- a/pkgs/clan-cli/clan_cli/history/add.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/history.py @@ -1,4 +1,3 @@ -# !/usr/bin/env python3 import argparse import dataclasses import datetime @@ -113,12 +112,46 @@ def add_history_command(args: argparse.Namespace) -> None: add_history(args.uri) -# takes a (sub)parser and configures it -def register_add_parser(parser: argparse.ArgumentParser) -> None: - parser.add_argument( +def list_history_command(args: argparse.Namespace) -> None: + res: dict[str, list[HistoryEntry]] = {} + for history_entry in list_history(): + url = str(history_entry.flake.flake_url) + if res.get(url) is None: + res[url] = [] + res[url].append(history_entry) + + for flake_url, entries in res.items(): + print(flake_url) + for entry in entries: + d = datetime.datetime.fromisoformat(entry.last_used) + last_used = d.strftime("%d/%m/%Y %H:%M:%S") + print(f" {entry.flake.flake_attr} ({last_used})") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + prog="clan history", + description="Manage clan history", + ) + subparser = parser.add_subparsers( + title="command", + description="the command to run", + help="the command to run", + required=True, + ) + add_parser = subparser.add_parser("add", help="Add a clan flake") + add_parser.add_argument( "uri", type=ClanURI.from_str, help="Path to the flake", default="." ) - parser.add_argument( + add_parser.add_argument( "--all", help="Add all machines", default=False, action="store_true" ) - parser.set_defaults(func=add_history_command) + add_parser.set_defaults(func=add_history_command) + list_parser = subparser.add_parser("list", help="List recently used flakes") + list_parser.set_defaults(func=list_history_command) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + args.func(args) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_join.py b/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_join.py index 1d720f978..d8fc062da 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_join.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_join.py @@ -5,10 +5,10 @@ from typing import Any, ClassVar, cast import gi from clan_cli.clan_uri import ClanURI -from clan_cli.history.add import HistoryEntry, add_history from clan_cli.machines.machines import Machine from clan_vm_manager.components.gkvstore import GKVStore +from clan_vm_manager.history import HistoryEntry, add_history from clan_vm_manager.singletons.use_vms import ClanStore gi.require_version("Gtk", "4.0") diff --git a/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py b/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py index 83418c084..5725ffc30 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py @@ -6,12 +6,12 @@ from typing import Any, ClassVar import gi from clan_cli.clan_uri import ClanURI from clan_cli.flake import Flake -from clan_cli.history.add import HistoryEntry from clan_cli.machines.machines import Machine from clan_vm_manager import assets from clan_vm_manager.components.gkvstore import GKVStore from clan_vm_manager.components.vmobj import VMObject +from clan_vm_manager.history import HistoryEntry from clan_vm_manager.singletons.use_views import ViewStack from clan_vm_manager.views.logs import Logs diff --git a/pkgs/clan-vm-manager/clan_vm_manager/windows/main_window.py b/pkgs/clan-vm-manager/clan_vm_manager/windows/main_window.py index 8d875cb74..f7fd2948e 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/windows/main_window.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/windows/main_window.py @@ -2,9 +2,9 @@ import logging import threading import gi -from clan_cli.history.list import list_history from clan_vm_manager.components.interfaces import ClanConfig +from clan_vm_manager.history import list_history from clan_vm_manager.singletons.toast import ToastOverlay from clan_vm_manager.singletons.use_views import ViewStack from clan_vm_manager.singletons.use_vms import ClanStore diff --git a/pkgs/clan-vm-manager/pyproject.toml b/pkgs/clan-vm-manager/pyproject.toml index 0762eb1a3..3196965c7 100644 --- a/pkgs/clan-vm-manager/pyproject.toml +++ b/pkgs/clan-vm-manager/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" name = "clan-vm-manager" description = "clan vm manager" dynamic = ["version"] -scripts = { clan-vm-manager = "clan_vm_manager:main" } +scripts = { clan-vm-manager = "clan_vm_manager:main", clan-vm-manager-history = "clan_vm_manager.history:main" } [project.urls] Homepage = "https://clan.lol/" diff --git a/pkgs/clan-vm-manager/tests/conftest.py b/pkgs/clan-vm-manager/tests/conftest.py index 1841dc81e..4db37f1ca 100644 --- a/pkgs/clan-vm-manager/tests/conftest.py +++ b/pkgs/clan-vm-manager/tests/conftest.py @@ -16,6 +16,7 @@ pytest_plugins = [ "root", "command", "wayland", + "stdout", ] diff --git a/pkgs/clan-vm-manager/tests/stdout.py b/pkgs/clan-vm-manager/tests/stdout.py new file mode 100644 index 000000000..8d460c9c0 --- /dev/null +++ b/pkgs/clan-vm-manager/tests/stdout.py @@ -0,0 +1,34 @@ +import types + +import pytest + + +class CaptureOutput: + def __init__(self, capsys: pytest.CaptureFixture) -> None: + self.capsys = capsys + self.capsys_disabled = capsys.disabled() + self.capsys_disabled.__enter__() + + def __enter__(self) -> "CaptureOutput": + self.capsys_disabled.__exit__(None, None, None) + self.capsys.readouterr() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: + res = self.capsys.readouterr() + self.out = res.out + self.err = res.err + + # Disable capsys again + self.capsys_disabled = self.capsys.disabled() + self.capsys_disabled.__enter__() + + +@pytest.fixture +def capture_output(capsys: pytest.CaptureFixture) -> CaptureOutput: + return CaptureOutput(capsys) From ec738fac30887c9727a14d5a753861fb96e9e77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 16 Apr 2025 14:34:21 +0200 Subject: [PATCH 3/3] impure-checks: limit number of workers to number of tests --- checks/impure/flake-module.nix | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/checks/impure/flake-module.nix b/checks/impure/flake-module.nix index 93609cbae..b0736fe9b 100644 --- a/checks/impure/flake-module.nix +++ b/checks/impure/flake-module.nix @@ -19,6 +19,7 @@ [ pkgs.gitMinimal pkgs.nix + pkgs.coreutils pkgs.rsync # needed to have rsync installed on the dummy ssh server ] ++ self'.packages.clan-cli-full.runtimeDependencies @@ -30,7 +31,12 @@ # this disables dynamic dependency loading in clan-cli export CLAN_NO_DYNAMIC_DEPS=1 - nix develop "$ROOT#clan-cli" -c bash -c "TMPDIR=/tmp python -m pytest -m impure ./clan_cli $@" + jobs=$(nproc) + # Spawning worker in pytest is relatively slow, so we limit the number of jobs to 13 + # (current number of impure tests) + jobs="$((jobs > 13 ? 13 : jobs))" + + nix develop "$ROOT#clan-cli" -c bash -c "TMPDIR=/tmp python -m pytest -n $jobs -m impure ./clan_cli $@" ''; }; }