Secret mgmt problem
This commit is contained in:
parent
65a2fc9b14
commit
155a1ee98c
@ -19,7 +19,7 @@
|
|||||||
"**/.mypy_cache": true,
|
"**/.mypy_cache": true,
|
||||||
"**/.reports": true,
|
"**/.reports": true,
|
||||||
"**/.ruff_cache": true,
|
"**/.ruff_cache": true,
|
||||||
"**/result": true,
|
"**/result/**": true,
|
||||||
"/nix/store/**": true
|
"/nix/store/**": true
|
||||||
},
|
},
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
@ -29,6 +29,7 @@
|
|||||||
"**/.mypy_cache": true,
|
"**/.mypy_cache": true,
|
||||||
"**/.reports": true,
|
"**/.reports": true,
|
||||||
"**/.ruff_cache": true,
|
"**/.ruff_cache": true,
|
||||||
|
"**/result/": true,
|
||||||
"/nix/store/**": true
|
"/nix/store/**": true
|
||||||
},
|
},
|
||||||
"files.autoSave": "off"
|
"files.autoSave": "off"
|
||||||
|
@ -18,7 +18,6 @@ from collections.abc import Callable
|
|||||||
# Kill the new process and all its children by sending a SIGTERM signal to the process group
|
# Kill the new process and all its children by sending a SIGTERM signal to the process group
|
||||||
def _kill_group(proc: mp.Process) -> None:
|
def _kill_group(proc: mp.Process) -> None:
|
||||||
pid = proc.pid
|
pid = proc.pid
|
||||||
assert pid is not None
|
|
||||||
if proc.is_alive():
|
if proc.is_alive():
|
||||||
os.killpg(pid, signal.SIGTERM)
|
os.killpg(pid, signal.SIGTERM)
|
||||||
else:
|
else:
|
||||||
|
@ -21,13 +21,17 @@ gi.require_version("Gtk", "4.0")
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from gi.repository import Gio, GLib, GObject
|
from gi.repository import Gio, GLib, GObject
|
||||||
|
import logging
|
||||||
|
import multiprocessing as mp
|
||||||
|
from clan_cli.machines.machines import Machine
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class VM(GObject.Object):
|
class VM(GObject.Object):
|
||||||
# Define a custom signal with the name "vm_stopped" and a string argument for the message
|
# Define a custom signal with the name "vm_stopped" and a string argument for the message
|
||||||
__gsignals__: ClassVar = {
|
__gsignals__: ClassVar = {
|
||||||
"vm_started": (GObject.SignalFlags.RUN_FIRST, None, [GObject.Object]),
|
"vm_status_changed": (GObject.SignalFlags.RUN_FIRST, None, [GObject.Object]),
|
||||||
"vm_stopped": (GObject.SignalFlags.RUN_FIRST, None, [GObject.Object]),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -35,23 +39,28 @@ class VM(GObject.Object):
|
|||||||
icon: Path,
|
icon: Path,
|
||||||
status: VMStatus,
|
status: VMStatus,
|
||||||
data: HistoryEntry,
|
data: HistoryEntry,
|
||||||
process: MPProcess | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.data = data
|
self.data = data
|
||||||
self.process = process
|
self.process = MPProcess("dummy", mp.Process(), Path("./dummy"))
|
||||||
|
self._watcher_id: int = 0
|
||||||
self.status = status
|
self.status = status
|
||||||
|
self._last_liveness: bool = False
|
||||||
self.log_dir = tempfile.TemporaryDirectory(
|
self.log_dir = tempfile.TemporaryDirectory(
|
||||||
prefix="clan_vm-", suffix=f"-{self.data.flake.flake_attr}"
|
prefix="clan_vm-", suffix=f"-{self.data.flake.flake_attr}"
|
||||||
)
|
)
|
||||||
self._finalizer = weakref.finalize(self, self.stop)
|
self._finalizer = weakref.finalize(self, self.stop)
|
||||||
|
|
||||||
def start(self) -> None:
|
def __start(self) -> None:
|
||||||
if self.process is not None:
|
if self.is_running():
|
||||||
show_error_dialog(ClanError("VM is already running"))
|
log.warn("VM is already running")
|
||||||
return
|
return
|
||||||
|
machine = Machine(
|
||||||
|
name=self.data.flake.flake_attr,
|
||||||
|
flake=self.data.flake.flake_url,
|
||||||
|
)
|
||||||
vm = vms.run.inspect_vm(
|
vm = vms.run.inspect_vm(
|
||||||
flake_url=self.data.flake.flake_url, flake_attr=self.data.flake.flake_attr
|
machine
|
||||||
)
|
)
|
||||||
self.process = spawn(
|
self.process = spawn(
|
||||||
on_except=None,
|
on_except=None,
|
||||||
@ -59,39 +68,51 @@ class VM(GObject.Object):
|
|||||||
func=vms.run.run_vm,
|
func=vms.run.run_vm,
|
||||||
vm=vm,
|
vm=vm,
|
||||||
)
|
)
|
||||||
self.emit("vm_started", self)
|
|
||||||
GLib.timeout_add(50, self.vm_stopped_task)
|
|
||||||
|
|
||||||
def start_async(self) -> None:
|
def start(self) -> None:
|
||||||
threading.Thread(target=self.start).start()
|
if self.is_running():
|
||||||
|
log.warn("VM is already running")
|
||||||
|
return
|
||||||
|
|
||||||
|
threading.Thread(target=self.__start).start()
|
||||||
|
|
||||||
|
if self._watcher_id == 0:
|
||||||
|
# Every 50ms check if the VM is still running
|
||||||
|
self._watcher_id = GLib.timeout_add(50, self._vm_watcher_task)
|
||||||
|
|
||||||
|
if self._watcher_id == 0:
|
||||||
|
log.error("Failed to add watcher")
|
||||||
|
raise ClanError("Failed to add watcher")
|
||||||
|
|
||||||
|
def _vm_watcher_task(self) -> bool:
|
||||||
|
if self.is_running() != self._last_liveness:
|
||||||
|
self.emit("vm_status_changed", self)
|
||||||
|
prev_liveness = self._last_liveness
|
||||||
|
self._last_liveness = self.is_running()
|
||||||
|
|
||||||
|
# If the VM was running and now it is not, remove the watcher
|
||||||
|
if prev_liveness == True and not self.is_running():
|
||||||
|
return GLib.SOURCE_REMOVE
|
||||||
|
|
||||||
def vm_stopped_task(self) -> bool:
|
|
||||||
if not self.is_running():
|
|
||||||
self.emit("vm_stopped", self)
|
|
||||||
return GLib.SOURCE_REMOVE
|
|
||||||
return GLib.SOURCE_CONTINUE
|
return GLib.SOURCE_CONTINUE
|
||||||
|
|
||||||
def is_running(self) -> bool:
|
def is_running(self) -> bool:
|
||||||
if self.process is not None:
|
return self.process.proc.is_alive()
|
||||||
return self.process.proc.is_alive()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_id(self) -> str:
|
def get_id(self) -> str:
|
||||||
return f"{self.data.flake.flake_url}#{self.data.flake.flake_attr}"
|
return f"{self.data.flake.flake_url}#{self.data.flake.flake_attr}"
|
||||||
|
|
||||||
def stop_async(self) -> None:
|
|
||||||
threading.Thread(target=self.stop).start()
|
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
if self.process is None:
|
log.info("Stopping VM")
|
||||||
print("VM is already stopped", file=sys.stderr)
|
if not self.is_running():
|
||||||
|
log.error("VM already stopped")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.process.kill_group()
|
self.process.kill_group()
|
||||||
self.process = None
|
|
||||||
|
|
||||||
def read_log(self) -> str:
|
def read_log(self) -> str:
|
||||||
if self.process is None:
|
if not self.process.out_file.exists():
|
||||||
|
log.error(f"Log file {self.process.out_file} does not exist")
|
||||||
return ""
|
return ""
|
||||||
return self.process.out_file.read_text()
|
return self.process.out_file.read_text()
|
||||||
|
|
||||||
@ -137,14 +158,6 @@ class VMS:
|
|||||||
else:
|
else:
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
def handle_vm_stopped(self, func: Callable[[VM, VM], None]) -> None:
|
|
||||||
for vm in self.list_store:
|
|
||||||
vm.connect("vm_stopped", func)
|
|
||||||
|
|
||||||
def handle_vm_started(self, func: Callable[[VM, VM], None]) -> None:
|
|
||||||
for vm in self.list_store:
|
|
||||||
vm.connect("vm_started", func)
|
|
||||||
|
|
||||||
def get_running_vms(self) -> list[VM]:
|
def get_running_vms(self) -> list[VM]:
|
||||||
return list(filter(lambda vm: vm.is_running(), self.list_store))
|
return list(filter(lambda vm: vm.is_running(), self.list_store))
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
from clan_cli.history.add import HistoryEntry
|
from clan_cli.history.add import HistoryEntry
|
||||||
|
|
||||||
@ -45,10 +44,6 @@ class ClanList(Gtk.Box):
|
|||||||
vms = VMS.use()
|
vms = VMS.use()
|
||||||
join = Join.use()
|
join = Join.use()
|
||||||
|
|
||||||
# TODO: Move this up to create_widget and connect every VM signal to its corresponding switch
|
|
||||||
vms.handle_vm_stopped(self.stopped_vm)
|
|
||||||
vms.handle_vm_started(self.started_vm)
|
|
||||||
|
|
||||||
self.join_boxed_list = create_boxed_list(
|
self.join_boxed_list = create_boxed_list(
|
||||||
model=join.list_store, render_row=self.render_join_row
|
model=join.list_store, render_row=self.render_join_row
|
||||||
)
|
)
|
||||||
@ -77,10 +72,10 @@ class ClanList(Gtk.Box):
|
|||||||
if not VMS.use().list_store.get_n_items():
|
if not VMS.use().list_store.get_n_items():
|
||||||
self.vm_boxed_list.add_css_class("no-shadow")
|
self.vm_boxed_list.add_css_class("no-shadow")
|
||||||
|
|
||||||
def render_vm_row(self, boxed_list: Gtk.ListBox, item: VM) -> Gtk.Widget:
|
def render_vm_row(self, boxed_list: Gtk.ListBox, vm: VM) -> Gtk.Widget:
|
||||||
if boxed_list.has_css_class("no-shadow"):
|
if boxed_list.has_css_class("no-shadow"):
|
||||||
boxed_list.remove_css_class("no-shadow")
|
boxed_list.remove_css_class("no-shadow")
|
||||||
flake = item.data.flake
|
flake = vm.data.flake
|
||||||
row = Adw.ActionRow()
|
row = Adw.ActionRow()
|
||||||
|
|
||||||
# Title
|
# Title
|
||||||
@ -90,16 +85,16 @@ class ClanList(Gtk.Box):
|
|||||||
row.set_title_selectable(True)
|
row.set_title_selectable(True)
|
||||||
|
|
||||||
# Subtitle
|
# Subtitle
|
||||||
row.set_subtitle(item.get_id())
|
row.set_subtitle(vm.get_id())
|
||||||
row.set_subtitle_lines(1)
|
row.set_subtitle_lines(1)
|
||||||
|
|
||||||
# Avatar
|
# # Avatar
|
||||||
avatar = Adw.Avatar()
|
# avatar = Adw.Avatar()
|
||||||
avatar.set_custom_image(Gdk.Texture.new_from_filename(flake.icon))
|
# avatar.set_custom_image(Gdk.Texture.new_from_filename(flake.icon))
|
||||||
avatar.set_text(flake.clan_name + " " + flake.flake_attr)
|
# avatar.set_text(flake.clan_name + " " + flake.flake_attr)
|
||||||
avatar.set_show_initials(True)
|
# avatar.set_show_initials(True)
|
||||||
avatar.set_size(50)
|
# avatar.set_size(50)
|
||||||
row.add_prefix(avatar)
|
# row.add_prefix(avatar)
|
||||||
|
|
||||||
# Switch
|
# Switch
|
||||||
switch = Gtk.Switch()
|
switch = Gtk.Switch()
|
||||||
@ -107,7 +102,8 @@ class ClanList(Gtk.Box):
|
|||||||
box.set_valign(Gtk.Align.CENTER)
|
box.set_valign(Gtk.Align.CENTER)
|
||||||
box.append(switch)
|
box.append(switch)
|
||||||
|
|
||||||
switch.connect("notify::active", partial(self.on_row_toggle, item))
|
switch.connect("notify::active", partial(self.on_row_toggle, vm))
|
||||||
|
vm.connect("vm_status_changed", partial(self.vm_status_changed, switch))
|
||||||
row.add_suffix(box)
|
row.add_suffix(box)
|
||||||
|
|
||||||
return row
|
return row
|
||||||
@ -148,22 +144,21 @@ class ClanList(Gtk.Box):
|
|||||||
|
|
||||||
return row
|
return row
|
||||||
|
|
||||||
def started_vm(self, vm: VM, _vm: VM) -> None:
|
|
||||||
print("VM started", vm.data.flake.flake_attr)
|
|
||||||
|
|
||||||
def stopped_vm(self, vm: VM, _vm: VM) -> None:
|
|
||||||
print("VM stopped", vm.data.flake.flake_attr)
|
|
||||||
|
|
||||||
def show_error_dialog(self, error: str) -> None:
|
def show_error_dialog(self, error: str) -> None:
|
||||||
dialog = Gtk.MessageDialog(
|
p = Views.use().main_window
|
||||||
parent=self.get_toplevel(),
|
|
||||||
modal=True,
|
# app = Gio.Application.get_default()
|
||||||
message_type=Gtk.MessageType.ERROR,
|
# p = Gtk.Application.get_active_window(app)
|
||||||
buttons=Gtk.ButtonsType.OK,
|
|
||||||
text=error,
|
dialog = Adw.MessageDialog(
|
||||||
|
heading="Error"
|
||||||
)
|
)
|
||||||
dialog.run()
|
dialog.add_response("ok", "ok")
|
||||||
dialog.destroy()
|
dialog.set_body(error)
|
||||||
|
dialog.set_transient_for(p) # set the parent window of the dialog
|
||||||
|
dialog.choose()
|
||||||
|
|
||||||
|
|
||||||
def on_trust_clicked(self, item: JoinValue, widget: Gtk.Widget) -> None:
|
def on_trust_clicked(self, item: JoinValue, widget: Gtk.Widget) -> None:
|
||||||
def on_join(_history: list[HistoryEntry]) -> None:
|
def on_join(_history: list[HistoryEntry]) -> None:
|
||||||
@ -187,7 +182,21 @@ class ClanList(Gtk.Box):
|
|||||||
print("Toggled", vm.data.flake.flake_attr, "active:", row.get_active())
|
print("Toggled", vm.data.flake.flake_attr, "active:", row.get_active())
|
||||||
|
|
||||||
if row.get_active():
|
if row.get_active():
|
||||||
vm.start_async()
|
row.set_state(False)
|
||||||
|
vm.start()
|
||||||
|
|
||||||
if not row.get_active():
|
if not row.get_active():
|
||||||
vm.stop_async()
|
row.set_state(True)
|
||||||
|
vm.stop()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def vm_status_changed(self, switch: Gtk.Switch, vm: VM, _vm: VM) -> None:
|
||||||
|
switch.set_active(vm.is_running())
|
||||||
|
switch.set_state(vm.is_running())
|
||||||
|
|
||||||
|
if vm.process and vm.process.proc.exitcode != 0:
|
||||||
|
print(f"====== {vm.is_running()}")
|
||||||
|
self.show_error_dialog(vm.read_log())
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user