refactor(KIAUH): big refactor of instance handling

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
This commit is contained in:
dw-0
2023-12-26 23:37:35 +01:00
parent 1b4c76d080
commit 9dedf38079
9 changed files with 179 additions and 164 deletions

View File

@@ -11,7 +11,7 @@
from abc import abstractmethod, ABC from abc import abstractmethod, ABC
from pathlib import Path from pathlib import Path
from typing import List, Union, Optional, Type, TypeVar from typing import List, Type, TypeVar
from kiauh.utils.constants import SYSTEMD, CURRENT_USER from kiauh.utils.constants import SYSTEMD, CURRENT_USER
@@ -25,7 +25,7 @@ class BaseInstance(ABC):
def __init__( def __init__(
self, self,
suffix: Optional[str], suffix: str,
instance_type: B = B, instance_type: B = B,
): ):
self._instance_type = instance_type self._instance_type = instance_type
@@ -52,7 +52,7 @@ class BaseInstance(ABC):
return self._suffix return self._suffix
@suffix.setter @suffix.setter
def suffix(self, value: Union[str, None]) -> None: def suffix(self, value: str) -> None:
self._suffix = value self._suffix = value
@property @property
@@ -144,7 +144,7 @@ class BaseInstance(ABC):
def get_service_file_name(self, extension: bool = False) -> str: def get_service_file_name(self, extension: bool = False) -> str:
name = f"{self.__class__.__name__.lower()}" name = f"{self.__class__.__name__.lower()}"
if self.suffix is not None: if self.suffix != "":
name += f"-{self.suffix}" name += f"-{self.suffix}"
return name if not extension else f"{name}.service" return name if not extension else f"{name}.service"
@@ -153,7 +153,7 @@ class BaseInstance(ABC):
return SYSTEMD.joinpath(self.get_service_file_name(extension=True)) return SYSTEMD.joinpath(self.get_service_file_name(extension=True))
def get_data_dir_name_from_suffix(self) -> str: def get_data_dir_name_from_suffix(self) -> str:
if self._suffix is None: if self._suffix == "":
return "printer" return "printer"
elif self._suffix.isdigit(): elif self._suffix.isdigit():
return f"printer_{self._suffix}" return f"printer_{self._suffix}"

View File

@@ -207,10 +207,8 @@ class InstanceManager:
return instance_list return instance_list
def _get_instance_suffix(self, file_path: Path) -> Union[str, None]: def _get_instance_suffix(self, file_path: Path) -> str:
full_name = file_path.name.split(".")[0] return file_path.stem.split("-")[-1] if "-" in file_path.stem else ""
return full_name.split("-")[-1] if "-" in full_name else None
def _sort_instance_list(self, s: Union[int, str, None]): def _sort_instance_list(self, s: Union[int, str, None]):
if s is None: if s is None:

View File

@@ -0,0 +1,8 @@
from enum import unique, Enum
@unique
class NameScheme(Enum):
SINGLE = "SINGLE"
INDEX = "INDEX"
CUSTOM = "CUSTOM"

View File

@@ -9,10 +9,9 @@
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
import shutil
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from typing import List, Union from typing import List
from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.core.instance_manager.base_instance import BaseInstance
from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR, MODULE_PATH from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR, MODULE_PATH
@@ -26,7 +25,7 @@ class Klipper(BaseInstance):
def blacklist(cls) -> List[str]: def blacklist(cls) -> List[str]:
return ["None", "mcu"] return ["None", "mcu"]
def __init__(self, suffix: str = None): def __init__(self, suffix: str = ""):
super().__init__(instance_type=self, suffix=suffix) super().__init__(instance_type=self, suffix=suffix)
self.klipper_dir: Path = KLIPPER_DIR self.klipper_dir: Path = KLIPPER_DIR
self.env_dir: Path = KLIPPER_ENV_DIR self.env_dir: Path = KLIPPER_ENV_DIR

View File

@@ -10,12 +10,13 @@
# ======================================================================= # # ======================================================================= #
from pathlib import Path from pathlib import Path
from typing import List, Union from typing import List
from kiauh import KIAUH_CFG from kiauh import KIAUH_CFG
from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.core.backup_manager.backup_manager import BackupManager
from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.config_manager.config_manager import ConfigManager
from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.core.instance_manager.instance_manager import InstanceManager
from kiauh.core.instance_manager.name_scheme import NameScheme
from kiauh.modules.klipper import ( from kiauh.modules.klipper import (
EXIT_KLIPPER_SETUP, EXIT_KLIPPER_SETUP,
DEFAULT_KLIPPER_REPO_URL, DEFAULT_KLIPPER_REPO_URL,
@@ -25,20 +26,21 @@ from kiauh.modules.klipper import (
) )
from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.klipper.klipper import Klipper
from kiauh.modules.klipper.klipper_dialogs import ( from kiauh.modules.klipper.klipper_dialogs import (
print_instance_overview,
print_select_instance_count_dialog,
print_update_warn_dialog, print_update_warn_dialog,
print_select_custom_name_dialog,
) )
from kiauh.modules.klipper.klipper_utils import ( from kiauh.modules.klipper.klipper_utils import (
handle_convert_single_to_multi_instance_names,
handle_new_multi_instance_names,
handle_existing_multi_instance_names,
handle_disruptive_system_packages, handle_disruptive_system_packages,
check_user_groups, check_user_groups,
handle_single_to_multi_conversion, handle_to_multi_instance_conversion,
create_example_printer_cfg, create_example_printer_cfg,
detect_name_scheme,
add_to_existing,
get_install_count,
assign_custom_name,
) )
from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.core.repo_manager.repo_manager import RepoManager
from kiauh.modules.moonraker.moonraker import Moonraker
from kiauh.utils.input_utils import get_confirm, get_number_input from kiauh.utils.input_utils import get_confirm, get_number_input
from kiauh.utils.logger import Logger from kiauh.utils.logger import Logger
from kiauh.utils.system_utils import ( from kiauh.utils.system_utils import (
@@ -50,50 +52,81 @@ from kiauh.utils.system_utils import (
) )
# TODO: this method needs refactoring! (but it works for now)
def install_klipper() -> None: def install_klipper() -> None:
im = InstanceManager(Klipper) im = InstanceManager(Klipper)
kl_instances: List[Klipper] = im.instances
add_additional = handle_existing_instances(im.instances) # ask to add new instances, if there are existing ones
if len(im.instances) > 0 and not add_additional: if kl_instances and not add_to_existing():
Logger.print_status(EXIT_KLIPPER_SETUP) Logger.print_status(EXIT_KLIPPER_SETUP)
return return
print_select_instance_count_dialog() install_count = get_install_count()
question = f"Number of{' additional' if len(im.instances) > 0 else ''} Klipper instances to set up" # install_count = None -> user entered "b" to go back
install_count = get_number_input(question, 1, default=1, allow_go_back=True)
if install_count is None: if install_count is None:
Logger.print_status(EXIT_KLIPPER_SETUP) Logger.print_status(EXIT_KLIPPER_SETUP)
return return
instance_names = set_instance_suffix(im.instances, install_count) # create a dict of the size of the existing instances + install count
if instance_names is None: name_scheme = NameScheme.SINGLE
Logger.print_status(EXIT_KLIPPER_SETUP) single_to_multi = len(kl_instances) == 1 and kl_instances[0].suffix == ""
return name_dict = {c: "" for c in range(len(kl_instances) + install_count)}
if (not kl_instances and install_count > 1) or single_to_multi:
print_select_custom_name_dialog()
if get_confirm("Assign custom names?", False, allow_go_back=True):
name_scheme = NameScheme.CUSTOM
else:
name_scheme = NameScheme.INDEX
# if there are more moonraker instances installed than klipper, we
# load their names into the name_dict, as we will detect and enforce that naming scheme
mr_instances: List[Moonraker] = InstanceManager(Moonraker).instances
if len(mr_instances) > len(kl_instances):
for k, v in enumerate(mr_instances):
name_dict[k] = v.suffix
name_scheme = detect_name_scheme(mr_instances)
elif len(kl_instances) > 1:
for k, v in enumerate(kl_instances):
name_dict[k] = v.suffix
name_scheme = detect_name_scheme(kl_instances)
# set instance names if multiple instances will be created
if name_scheme != NameScheme.SINGLE:
for k in name_dict:
if name_dict[k] == "" and name_scheme == NameScheme.INDEX:
name_dict[k] = str(k + 1)
elif name_dict[k] == "" and name_scheme == NameScheme.CUSTOM:
assign_custom_name(k, name_dict)
create_example_cfg = get_confirm("Create example printer.cfg?") create_example_cfg = get_confirm("Create example printer.cfg?")
if len(im.instances) < 1: if not kl_instances:
setup_klipper_prerequesites() setup_klipper_prerequesites()
convert_single_to_multi = ( count = 0
len(im.instances) == 1 and im.instances[0].suffix is None and install_count >= 1 for name in name_dict:
) if name_dict[name] in [n.suffix for n in kl_instances]:
continue
for name in instance_names:
if convert_single_to_multi:
current_instance = handle_single_to_multi_conversion(im, name)
convert_single_to_multi = False
else: else:
current_instance = Klipper(suffix=name) count += 1
im.current_instance = current_instance if single_to_multi:
im.create_instance() handle_to_multi_instance_conversion(name_dict[name])
im.enable_instance() single_to_multi = False
count -= 1
else:
new_instance = Klipper(suffix=name_dict[name])
im.current_instance = new_instance
im.create_instance()
im.enable_instance()
if create_example_cfg:
create_example_printer_cfg(new_instance)
im.start_instance()
if create_example_cfg: if count == install_count:
create_example_printer_cfg(current_instance) break
im.start_instance()
im.reload_daemon() im.reload_daemon()
@@ -137,41 +170,6 @@ def install_klipper_packages(klipper_dir: Path) -> None:
install_system_packages(packages) install_system_packages(packages)
def handle_existing_instances(instance_list: List[Klipper]) -> bool:
instance_count = len(instance_list)
if instance_count > 0:
print_instance_overview(instance_list)
if not get_confirm("Add new instances?", allow_go_back=True):
return False
return True
def set_instance_suffix(
instance_list: List[Klipper], install_count: int
) -> List[Union[str, None]]:
instance_count = len(instance_list)
# new single instance install
if instance_count == 0 and install_count == 1:
return [None]
# convert single instance install to multi install
elif instance_count == 1 and install_count >= 1 and instance_list[0].suffix is None:
return handle_convert_single_to_multi_instance_names(install_count)
# new multi instance install
elif instance_count == 0 and install_count > 1:
return handle_new_multi_instance_names(instance_count, install_count)
# existing multi instance install
elif instance_count > 1:
return handle_existing_multi_instance_names(
instance_count, install_count, instance_list
)
def update_klipper() -> None: def update_klipper() -> None:
print_update_warn_dialog() print_update_warn_dialog()
if not get_confirm("Update Klipper now?"): if not get_confirm("Update Klipper now?"):

View File

@@ -20,16 +20,20 @@ from pathlib import Path
from typing import List, Union, Literal, Dict from typing import List, Union, Literal, Dict
from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.config_manager.config_manager import ConfigManager
from kiauh.core.instance_manager.base_instance import BaseInstance
from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.core.instance_manager.instance_manager import InstanceManager
from kiauh.core.instance_manager.name_scheme import NameScheme
from kiauh.modules.klipper import MODULE_PATH, KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.modules.klipper import MODULE_PATH, KLIPPER_DIR, KLIPPER_ENV_DIR
from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.klipper.klipper import Klipper
from kiauh.modules.klipper.klipper_dialogs import ( from kiauh.modules.klipper.klipper_dialogs import (
print_missing_usergroup_dialog, print_missing_usergroup_dialog,
print_select_custom_name_dialog, print_instance_overview,
print_select_instance_count_dialog,
) )
from kiauh.modules.moonraker.moonraker_utils import moonraker_to_multi_conversion
from kiauh.utils.common import get_install_status_common, get_repo_name from kiauh.utils.common import get_install_status_common, get_repo_name
from kiauh.utils.constants import CURRENT_USER from kiauh.utils.constants import CURRENT_USER
from kiauh.utils.input_utils import get_confirm, get_string_input from kiauh.utils.input_utils import get_confirm, get_string_input, get_number_input
from kiauh.utils.logger import Logger from kiauh.utils.logger import Logger
from kiauh.utils.system_utils import mask_system_service from kiauh.utils.system_utils import mask_system_service
@@ -41,84 +45,57 @@ def get_klipper_status() -> Dict[Literal["status", "repo"], str]:
} }
def assign_custom_names( def add_to_existing() -> bool:
instance_count: int, install_count: int, instance_list: List[Klipper] = None kl_instances = InstanceManager(Klipper).instances
) -> List[str]: print_instance_overview(kl_instances)
instance_names = [] return get_confirm("Add new instances?", allow_go_back=True)
exclude = Klipper.blacklist()
# if an instance_list is provided, exclude all existing instance suffixes
if instance_list is not None:
for instance in instance_list:
exclude.append(instance.suffix)
for i in range(instance_count + install_count):
question = f"Enter name for instance {i + 1}"
name = get_string_input(question, exclude=exclude)
instance_names.append(name)
exclude.append(name)
return instance_names
def handle_convert_single_to_multi_instance_names( def get_install_count() -> Union[int, None]:
install_count: int, kl_instances = InstanceManager(Klipper).instances
) -> Union[List[str], None]: print_select_instance_count_dialog()
print_select_custom_name_dialog() question = f"Number of{' additional' if len(kl_instances) > 0 else ''} Klipper instances to set up"
choice = get_confirm("Assign custom names?", False, allow_go_back=True) return get_number_input(question, 1, default=1, allow_go_back=True)
if choice is True:
# instance_count = 0 and install_count + 1 as we want to assign a new name to the existing single install
return assign_custom_names(0, install_count + 1)
elif choice is False:
# "install_count + 2" as we need to account for the existing single install
_range = range(1, install_count + 2)
return [str(i) for i in _range]
return None
def handle_new_multi_instance_names( def assign_custom_name(key: int, name_dict: Dict[int, str]) -> None:
instance_count: int, install_count: int existing_names = []
) -> Union[List[str], None]: existing_names.extend(Klipper.blacklist())
print_select_custom_name_dialog() existing_names.extend(name_dict[n] for n in name_dict)
choice = get_confirm("Assign custom names?", False, allow_go_back=True) question = f"Enter name for instance {key + 1}"
if choice is True: name_dict[key] = get_string_input(question, exclude=existing_names)
return assign_custom_names(instance_count, install_count)
elif choice is False:
_range = range(1, install_count + 1)
return [str(i) for i in _range]
return None
def handle_existing_multi_instance_names( def handle_to_multi_instance_conversion(new_name: str) -> None:
instance_count: int, install_count: int, instance_list: List[Klipper] Logger.print_status("Converting single instance to multi instances ...")
) -> List[str]: klipper_to_multi_conversion(new_name)
if has_custom_names(instance_list): moonraker_to_multi_conversion(new_name)
return assign_custom_names(instance_count, install_count, instance_list)
def klipper_to_multi_conversion(new_name: str) -> None:
Logger.print_status("Convert Klipper single to multi instance ...")
im = InstanceManager(Klipper)
im.current_instance = im.instances[0]
# temporarily store the data dir path
old_data_dir = im.instances[0].data_dir
# remove the old single instance
im.stop_instance()
im.disable_instance()
im.delete_instance()
# create a new klipper instance with the new name
im.current_instance = Klipper(suffix=new_name)
new_data_dir: Path = im.current_instance.data_dir
# rename the old data dir and use it for the new instance
Logger.print_status(f"Rename '{old_data_dir}' to '{new_data_dir}' ...")
if not new_data_dir.is_dir():
old_data_dir.rename(new_data_dir)
else: else:
start = get_highest_index(instance_list) + 1 Logger.print_info(f"'{new_data_dir}' already exist. Skipped ...")
_range = range(start, start + install_count)
return [str(i) for i in _range]
im.create_instance()
def handle_single_to_multi_conversion( im.enable_instance()
instance_manager: InstanceManager, name: str im.start_instance()
) -> Klipper:
instance_list = instance_manager.instances
instance_manager.current_instance = instance_list[0]
old_data_dir_name = instance_manager.instances[0].data_dir
instance_manager.stop_instance()
instance_manager.disable_instance()
instance_manager.delete_instance()
instance_manager.current_instance = Klipper(suffix=name)
new_data_dir_name = instance_manager.current_instance.data_dir
try:
Path(old_data_dir_name).rename(new_data_dir_name)
return instance_manager.current_instance
except OSError as e:
log = f"Cannot rename {old_data_dir_name} to {new_data_dir_name}:\n{e}"
Logger.print_error(log)
def check_user_groups(): def check_user_groups():
@@ -189,13 +166,13 @@ def handle_disruptive_system_packages() -> None:
Logger.print_warn(warn_msg) Logger.print_warn(warn_msg)
def has_custom_names(instance_list: List[Klipper]) -> bool: def detect_name_scheme(instance_list: List[BaseInstance]) -> NameScheme:
pattern = re.compile("^\d+$") pattern = re.compile("^\d+$")
for instance in instance_list: for instance in instance_list:
if not pattern.match(instance.suffix): if not pattern.match(instance.suffix):
return True return NameScheme.CUSTOM
return False return NameScheme.INDEX
def get_highest_index(instance_list: List[Klipper]) -> int: def get_highest_index(instance_list: List[Klipper]) -> int:

View File

@@ -9,7 +9,6 @@
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
import shutil
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from typing import List, Union from typing import List, Union
@@ -27,7 +26,7 @@ class Moonraker(BaseInstance):
def blacklist(cls) -> List[str]: def blacklist(cls) -> List[str]:
return ["None", "mcu"] return ["None", "mcu"]
def __init__(self, suffix: str = None): def __init__(self, suffix: str = ""):
super().__init__(instance_type=self, suffix=suffix) super().__init__(instance_type=self, suffix=suffix)
self.moonraker_dir: Path = MOONRAKER_DIR self.moonraker_dir: Path = MOONRAKER_DIR
self.env_dir: Path = MOONRAKER_ENV_DIR self.env_dir: Path = MOONRAKER_ENV_DIR

View File

@@ -54,21 +54,23 @@ from kiauh.utils.system_utils import (
def check_moonraker_install_requirements() -> bool: def check_moonraker_install_requirements() -> bool:
kl_im = InstanceManager(Klipper)
kl_instance_list = kl_im.instances
kl_instance_count = len(kl_instance_list)
if not (sys.version_info.major >= 3 and sys.version_info.minor >= 7): if not (sys.version_info.major >= 3 and sys.version_info.minor >= 7):
Logger.print_error("Versioncheck failed!") Logger.print_error("Versioncheck failed!")
Logger.print_error("Python 3.7 or newer required to run Moonraker.") Logger.print_error("Python 3.7 or newer required to run Moonraker.")
return False return False
is_klipper_installed = kl_instance_count > 0 kl_instance_count = len(InstanceManager(Klipper).instances)
if not is_klipper_installed: if kl_instance_count < 1:
Logger.print_warn("Klipper not installed!") Logger.print_warn("Klipper not installed!")
Logger.print_warn("Moonraker cannot be installed! Install Klipper first.") Logger.print_warn("Moonraker cannot be installed! Install Klipper first.")
return False return False
mr_instance_count = len(InstanceManager(Moonraker).instances)
if mr_instance_count >= kl_instance_count:
Logger.print_warn("Unable to install more Moonraker instances!")
Logger.print_warn("More Klipper instances required.")
return False
return True return True

View File

@@ -10,9 +10,10 @@
# ======================================================================= # # ======================================================================= #
import shutil import shutil
from typing import Dict, Literal from typing import Dict, Literal, List
from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.config_manager.config_manager import ConfigManager
from kiauh.core.instance_manager.instance_manager import InstanceManager
from kiauh.modules.moonraker import ( from kiauh.modules.moonraker import (
DEFAULT_MOONRAKER_PORT, DEFAULT_MOONRAKER_PORT,
MODULE_PATH, MODULE_PATH,
@@ -83,3 +84,36 @@ def create_example_moonraker_conf(
cm.write_config() cm.write_config()
Logger.print_ok(f"Example moonraker.conf created in '{instance.cfg_dir}'") Logger.print_ok(f"Example moonraker.conf created in '{instance.cfg_dir}'")
def moonraker_to_multi_conversion(new_name: str) -> None:
"""
Converts the first instance in the List of Moonraker instances to an instance
with a new name. This method will be called when converting from a single Klipper
instance install to a multi instance install when Moonraker is also already
installed with a single instance.
:param new_name: new name the previous single instance is renamed to
:return: None
"""
im = InstanceManager(Moonraker)
instances: List[Moonraker] = im.instances
if not instances:
return
# in case there are multiple Moonraker instances, we don't want to do anything
if len(instances) > 1:
Logger.print_info("More than a single Moonraker instance found. Skipped ...")
return
Logger.print_status("Convert Moonraker single to multi instance ...")
# remove the old single instance
im.current_instance = im.instances[0]
im.stop_instance()
im.disable_instance()
im.delete_instance()
# create a new klipper instance with the new name
im.current_instance = Moonraker(suffix=new_name)
# create, enable and start the new moonraker instance
im.create_instance()
im.enable_instance()
im.start_instance()