Merge pull request 'Move clan history subcommand to clan-vm-manager' (#3336) from fix-update into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3336
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
[
|
[
|
||||||
pkgs.gitMinimal
|
pkgs.gitMinimal
|
||||||
pkgs.nix
|
pkgs.nix
|
||||||
|
pkgs.coreutils
|
||||||
pkgs.rsync # needed to have rsync installed on the dummy ssh server
|
pkgs.rsync # needed to have rsync installed on the dummy ssh server
|
||||||
]
|
]
|
||||||
++ self'.packages.clan-cli-full.runtimeDependencies
|
++ self'.packages.clan-cli-full.runtimeDependencies
|
||||||
@@ -30,7 +31,12 @@
|
|||||||
# this disables dynamic dependency loading in clan-cli
|
# this disables dynamic dependency loading in clan-cli
|
||||||
export CLAN_NO_DYNAMIC_DEPS=1
|
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 $@"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,6 @@ nav:
|
|||||||
- reference/cli/facts.md
|
- reference/cli/facts.md
|
||||||
- reference/cli/flakes.md
|
- reference/cli/flakes.md
|
||||||
- reference/cli/flash.md
|
- reference/cli/flash.md
|
||||||
- reference/cli/history.md
|
|
||||||
- reference/cli/machines.md
|
- reference/cli/machines.md
|
||||||
- reference/cli/select.md
|
- reference/cli/select.md
|
||||||
- reference/cli/secrets.md
|
- reference/cli/secrets.md
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ __all__ = ["admin", "directory", "disk", "iwd", "mdns_discovery", "modules", "up
|
|||||||
from . import (
|
from . import (
|
||||||
backups,
|
backups,
|
||||||
clan,
|
clan,
|
||||||
history,
|
|
||||||
secrets,
|
secrets,
|
||||||
select,
|
select,
|
||||||
state,
|
state,
|
||||||
@@ -372,12 +371,6 @@ For more detailed information, visit: {help_hyperlink("deploy", "https://docs.cl
|
|||||||
)
|
)
|
||||||
vms.register_parser(parser_vms)
|
vms.register_parser(parser_vms)
|
||||||
|
|
||||||
parser_history = subparsers.add_parser(
|
|
||||||
"history",
|
|
||||||
description="manage history",
|
|
||||||
)
|
|
||||||
history.register_parser(parser_history)
|
|
||||||
|
|
||||||
parser_select = subparsers.add_parser(
|
parser_select = subparsers.add_parser(
|
||||||
"select",
|
"select",
|
||||||
aliases=["se"],
|
aliases=["se"],
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
# !/usr/bin/env python3
|
|
||||||
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
|
|
||||||
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)
|
|
||||||
update_parser = subparser.add_parser("update", help="Update a clan flake")
|
|
||||||
register_update_parser(update_parser)
|
|
||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
@@ -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
|
|
||||||
@@ -15,12 +15,12 @@ import gi
|
|||||||
from clan_cli import vms
|
from clan_cli import vms
|
||||||
from clan_cli.clan_uri import ClanURI
|
from clan_cli.clan_uri import ClanURI
|
||||||
from clan_cli.dirs import vm_state_dir
|
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.machines.machines import Machine
|
||||||
from clan_cli.vms.inspect import inspect_vm
|
from clan_cli.vms.inspect import inspect_vm
|
||||||
from clan_cli.vms.qemu import QMPWrapper
|
from clan_cli.vms.qemu import QMPWrapper
|
||||||
|
|
||||||
from clan_vm_manager.components.executor import MPProcess, spawn
|
from clan_vm_manager.components.executor import MPProcess, spawn
|
||||||
|
from clan_vm_manager.history import HistoryEntry
|
||||||
from clan_vm_manager.singletons.toast import (
|
from clan_vm_manager.singletons.toast import (
|
||||||
InfoToast,
|
InfoToast,
|
||||||
SuccessToast,
|
SuccessToast,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
# !/usr/bin/env python3
|
|
||||||
import argparse
|
import argparse
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import datetime
|
import datetime
|
||||||
@@ -113,12 +112,46 @@ def add_history_command(args: argparse.Namespace) -> None:
|
|||||||
add_history(args.uri)
|
add_history(args.uri)
|
||||||
|
|
||||||
|
|
||||||
# takes a (sub)parser and configures it
|
def list_history_command(args: argparse.Namespace) -> None:
|
||||||
def register_add_parser(parser: argparse.ArgumentParser) -> None:
|
res: dict[str, list[HistoryEntry]] = {}
|
||||||
parser.add_argument(
|
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="."
|
"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"
|
"--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)
|
||||||
@@ -5,10 +5,10 @@ from typing import Any, ClassVar, cast
|
|||||||
|
|
||||||
import gi
|
import gi
|
||||||
from clan_cli.clan_uri import ClanURI
|
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_cli.machines.machines import Machine
|
||||||
|
|
||||||
from clan_vm_manager.components.gkvstore import GKVStore
|
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
|
from clan_vm_manager.singletons.use_vms import ClanStore
|
||||||
|
|
||||||
gi.require_version("Gtk", "4.0")
|
gi.require_version("Gtk", "4.0")
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ from typing import Any, ClassVar
|
|||||||
import gi
|
import gi
|
||||||
from clan_cli.clan_uri import ClanURI
|
from clan_cli.clan_uri import ClanURI
|
||||||
from clan_cli.flake import Flake
|
from clan_cli.flake import Flake
|
||||||
from clan_cli.history.add import HistoryEntry
|
|
||||||
from clan_cli.machines.machines import Machine
|
from clan_cli.machines.machines import Machine
|
||||||
|
|
||||||
from clan_vm_manager import assets
|
from clan_vm_manager import assets
|
||||||
from clan_vm_manager.components.gkvstore import GKVStore
|
from clan_vm_manager.components.gkvstore import GKVStore
|
||||||
from clan_vm_manager.components.vmobj import VMObject
|
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.singletons.use_views import ViewStack
|
||||||
from clan_vm_manager.views.logs import Logs
|
from clan_vm_manager.views.logs import Logs
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
from clan_cli.history.list import list_history
|
|
||||||
|
|
||||||
from clan_vm_manager.components.interfaces import ClanConfig
|
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.toast import ToastOverlay
|
||||||
from clan_vm_manager.singletons.use_views import ViewStack
|
from clan_vm_manager.singletons.use_views import ViewStack
|
||||||
from clan_vm_manager.singletons.use_vms import ClanStore
|
from clan_vm_manager.singletons.use_vms import ClanStore
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
name = "clan-vm-manager"
|
name = "clan-vm-manager"
|
||||||
description = "clan vm manager"
|
description = "clan vm manager"
|
||||||
dynamic = ["version"]
|
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]
|
[project.urls]
|
||||||
Homepage = "https://clan.lol/"
|
Homepage = "https://clan.lol/"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pytest_plugins = [
|
|||||||
"root",
|
"root",
|
||||||
"command",
|
"command",
|
||||||
"wayland",
|
"wayland",
|
||||||
|
"stdout",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
34
pkgs/clan-vm-manager/tests/stdout.py
Normal file
34
pkgs/clan-vm-manager/tests/stdout.py
Normal file
@@ -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)
|
||||||
Reference in New Issue
Block a user