clan-vm-manager: Fix regression
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
# Remove working directory from sys.path
|
||||||
|
if "" in sys.path:
|
||||||
|
sys.path.remove("")
|
||||||
|
|
||||||
from clan_cli.profiler import profile
|
from clan_cli.profiler import profile
|
||||||
|
|
||||||
from clan_app.app import MainApplication
|
from clan_app.app import MainApplication
|
||||||
|
|||||||
@@ -44,14 +44,14 @@ except ImportError:
|
|||||||
def flake_path(arg: str) -> FlakeId:
|
def flake_path(arg: str) -> FlakeId:
|
||||||
flake_dir = Path(arg).resolve()
|
flake_dir = Path(arg).resolve()
|
||||||
if flake_dir.exists() and flake_dir.is_dir():
|
if flake_dir.exists() and flake_dir.is_dir():
|
||||||
return FlakeId(flake_dir)
|
return FlakeId(str(flake_dir))
|
||||||
return FlakeId(arg)
|
return FlakeId(arg)
|
||||||
|
|
||||||
|
|
||||||
def default_flake() -> FlakeId | None:
|
def default_flake() -> FlakeId | None:
|
||||||
val = get_clan_flake_toplevel_or_env()
|
val = get_clan_flake_toplevel_or_env()
|
||||||
if val:
|
if val:
|
||||||
return FlakeId(val)
|
return FlakeId(str(val))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ class FlakeConfig:
|
|||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if isinstance(self.vm, dict):
|
if isinstance(self.vm, dict):
|
||||||
self.vm = VmConfig(**self.vm)
|
self.vm = VmConfig(**self.vm)
|
||||||
|
if isinstance(self.flake_url, dict):
|
||||||
|
self.flake_url = FlakeId(**self.flake_url)
|
||||||
|
|
||||||
|
|
||||||
def run_cmd(cmd: list[str]) -> str:
|
def run_cmd(cmd: list[str]) -> str:
|
||||||
@@ -46,7 +48,7 @@ def inspect_flake(flake_url: str | Path, machine_name: str) -> FlakeConfig:
|
|||||||
f"Machine {machine_name} not found in {flake_url}. Available machines: {', '.join(machines)}"
|
f"Machine {machine_name} not found in {flake_url}. Available machines: {', '.join(machines)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
machine = Machine(machine_name, FlakeId(flake_url))
|
machine = Machine(machine_name, FlakeId(str(flake_url)))
|
||||||
vm = inspect_vm(machine)
|
vm = inspect_vm(machine)
|
||||||
|
|
||||||
# Make symlink to gcroots from vm.machine_icon
|
# Make symlink to gcroots from vm.machine_icon
|
||||||
@@ -89,7 +91,7 @@ def inspect_flake(flake_url: str | Path, machine_name: str) -> FlakeConfig:
|
|||||||
meta = nix_metadata(flake_url)
|
meta = nix_metadata(flake_url)
|
||||||
return FlakeConfig(
|
return FlakeConfig(
|
||||||
vm=vm,
|
vm=vm,
|
||||||
flake_url=FlakeId(flake_url),
|
flake_url=FlakeId(str(flake_url)),
|
||||||
clan_name=clan_name,
|
clan_name=clan_name,
|
||||||
flake_attr=machine_name,
|
flake_attr=machine_name,
|
||||||
nar_hash=meta["locked"]["narHash"],
|
nar_hash=meta["locked"]["narHash"],
|
||||||
@@ -109,7 +111,7 @@ class InspectOptions:
|
|||||||
def inspect_command(args: argparse.Namespace) -> None:
|
def inspect_command(args: argparse.Namespace) -> None:
|
||||||
inspect_options = InspectOptions(
|
inspect_options = InspectOptions(
|
||||||
machine=args.machine,
|
machine=args.machine,
|
||||||
flake=args.flake or FlakeId(Path.cwd()),
|
flake=args.flake or FlakeId(str(Path.cwd())),
|
||||||
)
|
)
|
||||||
res = inspect_flake(
|
res = inspect_flake(
|
||||||
flake_url=str(inspect_options.flake), machine_name=inspect_options.machine
|
flake_url=str(inspect_options.flake), machine_name=inspect_options.machine
|
||||||
|
|||||||
@@ -9,16 +9,19 @@ from .errors import ClanError
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FlakeId:
|
class FlakeId:
|
||||||
loc: str | Path
|
loc: str
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
assert isinstance(
|
assert isinstance(
|
||||||
self.loc, str | Path
|
self.loc, str
|
||||||
), f"Flake {self.loc} has an invalid format: {type(self.loc)}"
|
), f"Flake {self.loc} has an invalid format: {type(self.loc)}"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return str(self.loc)
|
return str(self.loc)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(str(self.loc))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> Path:
|
def path(self) -> Path:
|
||||||
assert self.is_local(), f"Flake {self.loc} is not a local path"
|
assert self.is_local(), f"Flake {self.loc} is not a local path"
|
||||||
@@ -87,20 +90,11 @@ class ClanURI:
|
|||||||
self.machine_name = components.fragment
|
self.machine_name = components.fragment
|
||||||
|
|
||||||
def _parse_url(self, comps: urllib.parse.ParseResult) -> FlakeId:
|
def _parse_url(self, comps: urllib.parse.ParseResult) -> FlakeId:
|
||||||
comb = (
|
if comps.scheme == "" or "file" in comps.scheme:
|
||||||
comps.scheme,
|
res_p = Path(comps.path).expanduser().resolve()
|
||||||
comps.netloc,
|
flake_id = FlakeId(str(res_p))
|
||||||
comps.path,
|
else:
|
||||||
comps.params,
|
flake_id = FlakeId(comps.geturl())
|
||||||
comps.query,
|
|
||||||
comps.fragment,
|
|
||||||
)
|
|
||||||
match comb:
|
|
||||||
case ("file", "", path, "", "", _) | ("", "", path, "", "", _): # type: ignore
|
|
||||||
flake_id = FlakeId(Path(path).expanduser().resolve())
|
|
||||||
case _:
|
|
||||||
flake_id = FlakeId(comps.geturl())
|
|
||||||
|
|
||||||
return flake_id
|
return flake_id
|
||||||
|
|
||||||
def get_url(self) -> str:
|
def get_url(self) -> str:
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ def list_history() -> list[HistoryEntry]:
|
|||||||
|
|
||||||
def new_history_entry(url: str, machine: str) -> HistoryEntry:
|
def new_history_entry(url: str, machine: str) -> HistoryEntry:
|
||||||
flake = inspect_flake(url, machine)
|
flake = inspect_flake(url, machine)
|
||||||
flake.flake_url = flake.flake_url
|
|
||||||
return HistoryEntry(
|
return HistoryEntry(
|
||||||
flake=flake,
|
flake=flake,
|
||||||
last_used=datetime.datetime.now().isoformat(),
|
last_used=datetime.datetime.now().isoformat(),
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ class VmConfig:
|
|||||||
self.flake_url = FlakeId(self.flake_url)
|
self.flake_url = FlakeId(self.flake_url)
|
||||||
if isinstance(self.waypipe, dict):
|
if isinstance(self.waypipe, dict):
|
||||||
self.waypipe = WaypipeConfig(**self.waypipe)
|
self.waypipe = WaypipeConfig(**self.waypipe)
|
||||||
|
if isinstance(self.flake_url, dict):
|
||||||
|
self.flake_url = FlakeId(**self.flake_url)
|
||||||
|
|
||||||
|
|
||||||
def inspect_vm(machine: Machine) -> VmConfig:
|
def inspect_vm(machine: Machine) -> VmConfig:
|
||||||
@@ -48,7 +50,7 @@ class InspectOptions:
|
|||||||
def inspect_command(args: argparse.Namespace) -> None:
|
def inspect_command(args: argparse.Namespace) -> None:
|
||||||
inspect_options = InspectOptions(
|
inspect_options = InspectOptions(
|
||||||
machine=args.machine,
|
machine=args.machine,
|
||||||
flake=args.flake or FlakeId(Path.cwd()),
|
flake=args.flake or FlakeId(str(Path.cwd())),
|
||||||
)
|
)
|
||||||
|
|
||||||
machine = Machine(inspect_options.machine, inspect_options.flake)
|
machine = Machine(inspect_options.machine, inspect_options.flake)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ def test_history_add(
|
|||||||
history_file = user_history_file()
|
history_file = user_history_file()
|
||||||
assert history_file.exists()
|
assert history_file.exists()
|
||||||
history = [HistoryEntry(**entry) for entry in json.loads(open(history_file).read())]
|
history = [HistoryEntry(**entry) for entry in json.loads(open(history_file).read())]
|
||||||
assert str(history[0].flake.flake_url["loc"]) == str(test_flake_with_core.path)
|
assert str(history[0].flake.flake_url) == str(test_flake_with_core.path)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.impure
|
@pytest.mark.impure
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ def test_generate_secret(
|
|||||||
secrets_folder / "vm1-zerotier-identity-secret" / "machines" / "vm1"
|
secrets_folder / "vm1-zerotier-identity-secret" / "machines" / "vm1"
|
||||||
).exists()
|
).exists()
|
||||||
|
|
||||||
store2 = SecretStore(Machine(name="vm2", flake=FlakeId(test_flake_with_core.path)))
|
store2 = SecretStore(
|
||||||
|
Machine(name="vm2", flake=FlakeId(str(test_flake_with_core.path)))
|
||||||
|
)
|
||||||
|
|
||||||
assert store2.exists("", "password")
|
assert store2.exists("", "password")
|
||||||
assert store2.exists("", "password-hash")
|
assert store2.exists("", "password-hash")
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ def test_upload_secret(
|
|||||||
cli.run(["facts", "generate", "vm1"])
|
cli.run(["facts", "generate", "vm1"])
|
||||||
|
|
||||||
store = SecretStore(
|
store = SecretStore(
|
||||||
Machine(name="vm1", flake=FlakeId(test_flake_with_core_and_pass.path))
|
Machine(name="vm1", flake=FlakeId(str(test_flake_with_core_and_pass.path)))
|
||||||
)
|
)
|
||||||
|
|
||||||
network_id = machine_get_fact(
|
network_id = machine_get_fact(
|
||||||
|
|||||||
25
pkgs/clan-vm-manager/.vscode/lhebendanz.weaudit
vendored
25
pkgs/clan-vm-manager/.vscode/lhebendanz.weaudit
vendored
@@ -2,30 +2,7 @@
|
|||||||
"clientRemote": "",
|
"clientRemote": "",
|
||||||
"gitRemote": "",
|
"gitRemote": "",
|
||||||
"gitSha": "",
|
"gitSha": "",
|
||||||
"treeEntries": [
|
"treeEntries": [],
|
||||||
{
|
|
||||||
"label": "source of problem",
|
|
||||||
"entryType": 0,
|
|
||||||
"author": "lhebendanz",
|
|
||||||
"locations": [
|
|
||||||
{
|
|
||||||
"path": "../clan-cli/clan_cli/history/add.py",
|
|
||||||
"startLine": 45,
|
|
||||||
"endLine": 59,
|
|
||||||
"label": "",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"details": {
|
|
||||||
"severity": "",
|
|
||||||
"difficulty": "",
|
|
||||||
"type": "",
|
|
||||||
"description": "",
|
|
||||||
"exploit": "",
|
|
||||||
"recommendation": "Short term, \nLong term, \n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"auditedFiles": [],
|
"auditedFiles": [],
|
||||||
"resolvedEntries": []
|
"resolvedEntries": []
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ from typing import IO, ClassVar
|
|||||||
|
|
||||||
import gi
|
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, FlakeId
|
||||||
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.history.add import HistoryEntry
|
||||||
from clan_cli.machines.machines import Machine
|
from clan_cli.machines.machines import Machine
|
||||||
@@ -51,7 +51,7 @@ class VMObject(GObject.Object):
|
|||||||
|
|
||||||
# Store the data from the history entry
|
# Store the data from the history entry
|
||||||
self.data: HistoryEntry = data
|
self.data: HistoryEntry = data
|
||||||
|
assert isinstance(self.data.flake.vm.flake_url, FlakeId)
|
||||||
self.build_log_cb = build_log_cb
|
self.build_log_cb = build_log_cb
|
||||||
|
|
||||||
# Create a process object to store the VM process
|
# Create a process object to store the VM process
|
||||||
@@ -99,6 +99,7 @@ class VMObject(GObject.Object):
|
|||||||
return GLib.SOURCE_REMOVE
|
return GLib.SOURCE_REMOVE
|
||||||
|
|
||||||
def update(self, data: HistoryEntry) -> None:
|
def update(self, data: HistoryEntry) -> None:
|
||||||
|
assert isinstance(data.flake.flake_url, FlakeId)
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def _on_vm_status_changed(self, source: "VMObject") -> None:
|
def _on_vm_status_changed(self, source: "VMObject") -> None:
|
||||||
@@ -180,12 +181,14 @@ class VMObject(GObject.Object):
|
|||||||
return GLib.SOURCE_REMOVE
|
return GLib.SOURCE_REMOVE
|
||||||
|
|
||||||
def __start(self) -> None:
|
def __start(self) -> None:
|
||||||
|
assert isinstance(self.data.flake.vm.flake_url, FlakeId)
|
||||||
with self._create_machine() as machine:
|
with self._create_machine() as machine:
|
||||||
# Start building VM
|
# Start building VM
|
||||||
tstart = datetime.now()
|
tstart = datetime.now()
|
||||||
log.info(f"Building VM {self.get_id()}")
|
log.info(f"Building VM {self.get_id()}")
|
||||||
log_dir = Path(str(self.log_dir.name))
|
log_dir = Path(str(self.log_dir.name))
|
||||||
|
|
||||||
|
assert isinstance(self.data.flake.vm.flake_url, FlakeId)
|
||||||
# Start the build process
|
# Start the build process
|
||||||
self.build_process = spawn(
|
self.build_process = spawn(
|
||||||
on_except=None,
|
on_except=None,
|
||||||
@@ -272,6 +275,7 @@ class VMObject(GObject.Object):
|
|||||||
self.emit("vm_status_changed", self)
|
self.emit("vm_status_changed", self)
|
||||||
return
|
return
|
||||||
log.debug(f"VM state dir {self.log_dir.name}")
|
log.debug(f"VM state dir {self.log_dir.name}")
|
||||||
|
assert isinstance(self.data.flake.vm.flake_url, FlakeId)
|
||||||
self._start_thread = threading.Thread(target=self.__start)
|
self._start_thread = threading.Thread(target=self.__start)
|
||||||
self._start_thread.start()
|
self._start_thread.start()
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from pathlib import Path
|
|||||||
from typing import Any, ClassVar
|
from typing import Any, ClassVar
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
from clan_cli.clan_uri import ClanURI
|
from clan_cli.clan_uri import ClanURI, FlakeId
|
||||||
from clan_cli.history.add import HistoryEntry
|
from clan_cli.history.add import HistoryEntry
|
||||||
from clan_cli.machines.machines import Machine
|
from clan_cli.machines.machines import Machine
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ class Emitter(GObject.GObject):
|
|||||||
|
|
||||||
class ClanStore:
|
class ClanStore:
|
||||||
_instance: "None | ClanStore" = None
|
_instance: "None | ClanStore" = None
|
||||||
_clan_store: GKVStore[str, VMStore]
|
_clan_store: GKVStore[FlakeId, VMStore]
|
||||||
|
|
||||||
_emitter: Emitter
|
_emitter: Emitter
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@ class ClanStore:
|
|||||||
|
|
||||||
def set_logging_vm(self, ident: str) -> VMObject | None:
|
def set_logging_vm(self, ident: str) -> VMObject | None:
|
||||||
vm = self.get_vm(ClanURI(f"clan://{ident}"))
|
vm = self.get_vm(ClanURI(f"clan://{ident}"))
|
||||||
|
|
||||||
if vm is not None:
|
if vm is not None:
|
||||||
self._logging_vm = vm
|
self._logging_vm = vm
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ class ClanStore:
|
|||||||
self.clan_store.register_on_change(on_clanstore_change)
|
self.clan_store.register_on_change(on_clanstore_change)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def clan_store(self) -> GKVStore[str, VMStore]:
|
def clan_store(self) -> GKVStore[FlakeId, VMStore]:
|
||||||
return self._clan_store
|
return self._clan_store
|
||||||
|
|
||||||
def create_vm_task(self, vm: HistoryEntry) -> bool:
|
def create_vm_task(self, vm: HistoryEntry) -> bool:
|
||||||
@@ -109,8 +110,12 @@ class ClanStore:
|
|||||||
def log_details(gfile: Gio.File) -> None:
|
def log_details(gfile: Gio.File) -> None:
|
||||||
self.log_details(vm, gfile)
|
self.log_details(vm, gfile)
|
||||||
|
|
||||||
|
assert isinstance(entry.flake.flake_url, FlakeId)
|
||||||
|
|
||||||
vm = VMObject(icon=icon, data=entry, build_log_cb=log_details)
|
vm = VMObject(icon=icon, data=entry, build_log_cb=log_details)
|
||||||
|
assert isinstance(vm.data.flake.flake_url, FlakeId)
|
||||||
self.push(vm)
|
self.push(vm)
|
||||||
|
assert isinstance(vm.data.flake.flake_url, FlakeId)
|
||||||
|
|
||||||
def log_details(self, vm: VMObject, gfile: Gio.File) -> None:
|
def log_details(self, vm: VMObject, gfile: Gio.File) -> None:
|
||||||
views = ViewStack.use().view
|
views = ViewStack.use().view
|
||||||
@@ -136,7 +141,7 @@ class ClanStore:
|
|||||||
# we cannot check this type, python is not smart enough
|
# we cannot check this type, python is not smart enough
|
||||||
|
|
||||||
def push(self, vm: VMObject) -> None:
|
def push(self, vm: VMObject) -> None:
|
||||||
url = str(vm.data.flake.flake_url)
|
url = vm.data.flake.flake_url
|
||||||
|
|
||||||
# Only write to the store if the Clan is not already in it
|
# Only write to the store if the Clan is not already in it
|
||||||
# Every write to the KVStore rerenders bound widgets to the clan_store
|
# Every write to the KVStore rerenders bound widgets to the clan_store
|
||||||
@@ -160,11 +165,12 @@ class ClanStore:
|
|||||||
vm_store.append(vm)
|
vm_store.append(vm)
|
||||||
|
|
||||||
def remove(self, vm: VMObject) -> None:
|
def remove(self, vm: VMObject) -> None:
|
||||||
del self.clan_store[str(vm.data.flake.flake_url)][vm.data.flake.flake_attr]
|
del self.clan_store[vm.data.flake.flake_url][vm.data.flake.flake_attr]
|
||||||
|
|
||||||
def get_vm(self, uri: ClanURI) -> None | VMObject:
|
def get_vm(self, uri: ClanURI) -> None | VMObject:
|
||||||
machine = Machine(uri.machine_name, uri.flake)
|
machine = Machine(uri.machine_name, uri.flake)
|
||||||
vm_store = self.clan_store.get(str(machine.flake))
|
vm_store = self.clan_store.get(machine.flake)
|
||||||
|
|
||||||
if vm_store is None:
|
if vm_store is None:
|
||||||
return None
|
return None
|
||||||
vm = vm_store.get(str(machine.name), None)
|
vm = vm_store.get(str(machine.name), None)
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class ClanList(Gtk.Box):
|
|||||||
|
|
||||||
grp = Adw.PreferencesGroup()
|
grp = Adw.PreferencesGroup()
|
||||||
grp.set_title(vm.data.flake.clan_name)
|
grp.set_title(vm.data.flake.clan_name)
|
||||||
grp.set_description(vm.data.flake.flake_url)
|
grp.set_description(str(vm.data.flake.flake_url))
|
||||||
|
|
||||||
add_action = Gio.SimpleAction.new("add", GLib.VariantType.new("s"))
|
add_action = Gio.SimpleAction.new("add", GLib.VariantType.new("s"))
|
||||||
add_action.connect("activate", self.on_add)
|
add_action.connect("activate", self.on_add)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
from clan_cli.clan_uri import FlakeId
|
||||||
from clan_cli.history.list import list_history
|
from clan_cli.history.list import list_history
|
||||||
|
|
||||||
from clan_vm_manager.components.interfaces import ClanConfig
|
from clan_vm_manager.components.interfaces import ClanConfig
|
||||||
@@ -73,6 +74,7 @@ class MainWindow(Adw.ApplicationWindow):
|
|||||||
# Execute `clan flakes add <path>` to democlan for this to work
|
# Execute `clan flakes add <path>` to democlan for this to work
|
||||||
# TODO: Make list_history a generator function
|
# TODO: Make list_history a generator function
|
||||||
for entry in list_history():
|
for entry in list_history():
|
||||||
|
assert isinstance(entry.flake.flake_url, FlakeId)
|
||||||
GLib.idle_add(ClanStore.use().create_vm_task, entry)
|
GLib.idle_add(ClanStore.use().create_vm_task, entry)
|
||||||
|
|
||||||
GLib.idle_add(self._set_clan_store_ready)
|
GLib.idle_add(self._set_clan_store_ready)
|
||||||
|
|||||||
Reference in New Issue
Block a user