update_manager: refactor AppDeloy class

Don't require the `app_params` argument, instead dynamically
generate the configuration from a dict.  This simiplifies AppDeploy
initialization as the internally generated configurations can be
read in the same way as those supplied in moonraker.conf.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2022-03-27 08:07:32 -04:00
parent 54081612ca
commit e9e86c3042
No known key found for this signature in database
GPG Key ID: 7027245FBBDDF59A
6 changed files with 99 additions and 109 deletions

View File

@ -8,7 +8,6 @@ from __future__ import annotations
import pathlib import pathlib
import shutil import shutil
import hashlib import hashlib
import json
import logging import logging
from .base_deploy import BaseDeploy from .base_deploy import BaseDeploy
@ -28,7 +27,7 @@ if TYPE_CHECKING:
CHANNEL_TO_TYPE = { CHANNEL_TO_TYPE = {
"stable": "zip", "stable": "zip",
"beta": "zip_beta", "beta": "git_repo",
"dev": "git_repo" "dev": "git_repo"
} }
TYPE_TO_CHANNEL = { TYPE_TO_CHANNEL = {
@ -38,29 +37,17 @@ TYPE_TO_CHANNEL = {
} }
class AppDeploy(BaseDeploy): class AppDeploy(BaseDeploy):
def __init__(self, def __init__(self, config: ConfigHelper, cmd_helper: CommandHelper) -> None:
config: ConfigHelper, super().__init__(config, cmd_helper, prefix="Application")
cmd_helper: CommandHelper,
app_params: Optional[Dict[str, Any]]
) -> None:
self.config = config self.config = config
self.app_params = app_params
cfg_hash = self._calc_config_hash()
super().__init__(config, cmd_helper, prefix="Application",
cfg_hash=cfg_hash)
self.debug = self.cmd_helper.is_debug_enabled() self.debug = self.cmd_helper.is_debug_enabled()
if app_params is not None: self.type = config.get('type')
self.channel: str = app_params['channel'] self.channel = config.get(
self.path: pathlib.Path = pathlib.Path( "channel", TYPE_TO_CHANNEL[self.type]
app_params['path']).expanduser().resolve() )
executable: Optional[str] = app_params['executable'] self.path = pathlib.Path(
self.type = CHANNEL_TO_TYPE[self.channel] config.get('path')).expanduser().resolve()
else: executable = config.get('env', None)
self.type = config.get('type')
self.channel = TYPE_TO_CHANNEL[self.type]
self.path = pathlib.Path(
config.get('path')).expanduser().resolve()
executable = config.get('env', None)
if self.channel not in CHANNEL_TO_TYPE.keys(): if self.channel not in CHANNEL_TO_TYPE.keys():
raise config.error( raise config.error(
f"Invalid Channel '{self.channel}' for config " f"Invalid Channel '{self.channel}' for config "
@ -136,15 +123,6 @@ class AppDeploy(BaseDeploy):
self._is_valid = storage.get('is_valid', False) self._is_valid = storage.get('is_valid', False)
return storage return storage
def _calc_config_hash(self) -> str:
cfg_hash = self.config.get_hash()
if self.app_params is None:
return cfg_hash.hexdigest()
else:
app_bytes = json.dumps(self.app_params).encode()
cfg_hash.update(app_bytes)
return cfg_hash.hexdigest()
def _verify_path(self, def _verify_path(self,
config: ConfigHelper, config: ConfigHelper,
option: str, option: str,

View File

@ -0,0 +1,62 @@
# Moonraker/Klipper update configuration
#
# Copyright (C) 2022 Eric Callahan <arksine.code@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
from __future__ import annotations
import os
import sys
import copy
from typing import (
TYPE_CHECKING,
Dict
)
if TYPE_CHECKING:
from confighelper import ConfigHelper
from components.database import MoonrakerDatabase
MOONRAKER_PATH = os.path.normpath(os.path.join(
os.path.dirname(__file__), "../../.."))
KLIPPER_DEFAULT_PATH = os.path.expanduser("~/klipper")
KLIPPER_DEFAULT_EXEC = os.path.expanduser("~/klippy-env/bin/python")
BASE_CONFIG: Dict[str, Dict[str, str]] = {
"moonraker": {
"origin": "https://github.com/arksine/moonraker.git",
"requirements": "scripts/moonraker-requirements.txt",
"venv_args": "-p python3",
"install_script": "scripts/install-moonraker.sh",
"host_repo": "arksine/moonraker",
"env": sys.executable,
"path": MOONRAKER_PATH,
"managed_services": "moonraker"
},
"klipper": {
"moved_origin": "https://github.com/kevinoconnor/klipper.git",
"origin": "https://github.com/Klipper3d/klipper.git",
"requirements": "scripts/klippy-requirements.txt",
"venv_args": "-p python2",
"install_script": "scripts/install-octopi.sh",
"host_repo": "arksine/moonraker",
"managed_services": "klipper"
}
}
def get_base_configuration(config: ConfigHelper, channel: str) -> ConfigHelper:
server = config.get_server()
base_cfg = copy.deepcopy(BASE_CONFIG)
app_type = "zip" if channel == "stable" else "git_repo"
base_cfg["moonraker"]["channel"] = channel
base_cfg["moonraker"]["type"] = app_type
base_cfg["klipper"]["channel"] = channel
base_cfg["klipper"]["type"] = app_type
db: MoonrakerDatabase = server.lookup_component('database')
base_cfg["klipper"]["path"] = db.get_item(
"moonraker", "update_manager.klipper_path", KLIPPER_DEFAULT_PATH
).result()
base_cfg["klipper"]["env"] = db.get_item(
"moonraker", "update_manager.klipper_exec", KLIPPER_DEFAULT_EXEC
).result()
return config.read_supplemental_dict(base_cfg)

View File

@ -28,12 +28,8 @@ if TYPE_CHECKING:
from .update_manager import CommandHelper from .update_manager import CommandHelper
class GitDeploy(AppDeploy): class GitDeploy(AppDeploy):
def __init__(self, def __init__(self, config: ConfigHelper, cmd_helper: CommandHelper) -> None:
config: ConfigHelper, super().__init__(config, cmd_helper)
cmd_helper: CommandHelper,
app_params: Optional[Dict[str, Any]] = None
) -> None:
super().__init__(config, cmd_helper, app_params)
self.repo = GitRepo(cmd_helper, self.path, self.name, self.repo = GitRepo(cmd_helper, self.path, self.name,
self.origin, self.moved_origin) self.origin, self.moved_origin)
if self.type != 'git_repo': if self.type != 'git_repo':
@ -41,7 +37,7 @@ class GitDeploy(AppDeploy):
@staticmethod @staticmethod
async def from_application(app: AppDeploy) -> GitDeploy: async def from_application(app: AppDeploy) -> GitDeploy:
new_app = GitDeploy(app.config, app.cmd_helper, app.app_params) new_app = GitDeploy(app.config, app.cmd_helper)
await new_app.reinstall() await new_app.reinstall()
return new_app return new_app

View File

@ -1,17 +0,0 @@
# Supplemental configuration for Moonraker's Update Manager
# component. This file should not be modified.
[update_manager moonraker]
origin: https://github.com/arksine/moonraker.git
requirements: scripts/moonraker-requirements.txt
venv_args: -p python3
install_script: scripts/install-moonraker.sh
host_repo: arksine/moonraker
[update_manager klipper]
moved_origin: https://github.com/kevinoconnor/klipper.git
origin: https://github.com/Klipper3d/klipper.git
requirements: scripts/klippy-requirements.txt
venv_args: -p python2
install_script: scripts/install-octopi.sh
host_repo: arksine/moonraker

View File

@ -9,13 +9,13 @@ import asyncio
import os import os
import pathlib import pathlib
import logging import logging
import sys
import shutil import shutil
import zipfile import zipfile
import time import time
import tempfile import tempfile
import re import re
from thirdparty.packagekit import enums as PkEnum from thirdparty.packagekit import enums as PkEnum
from . import base_config
from .base_deploy import BaseDeploy from .base_deploy import BaseDeploy
from .app_deploy import AppDeploy from .app_deploy import AppDeploy
from .git_deploy import GitDeploy from .git_deploy import GitDeploy
@ -51,13 +51,6 @@ if TYPE_CHECKING:
from dbus_next.aio import ProxyInterface from dbus_next.aio import ProxyInterface
JsonType = Union[List[Any], Dict[str, Any]] JsonType = Union[List[Any], Dict[str, Any]]
MOONRAKER_PATH = os.path.normpath(os.path.join(
os.path.dirname(__file__), "../../.."))
SUPPLEMENTAL_CFG_PATH = os.path.join(
os.path.dirname(__file__), "update_manager.conf")
KLIPPER_DEFAULT_PATH = os.path.expanduser("~/klipper")
KLIPPER_DEFAULT_EXEC = os.path.expanduser("~/klippy-env/bin/python")
# Check To see if Updates are necessary each hour # Check To see if Updates are necessary each hour
UPDATE_REFRESH_INTERVAL = 3600. UPDATE_REFRESH_INTERVAL = 3600.
# Perform auto refresh no later than 4am # Perform auto refresh no later than 4am
@ -73,46 +66,30 @@ class UpdateManager:
def __init__(self, config: ConfigHelper) -> None: def __init__(self, config: ConfigHelper) -> None:
self.server = config.get_server() self.server = config.get_server()
self.event_loop = self.server.get_event_loop() self.event_loop = self.server.get_event_loop()
self.app_config = config.read_supplemental_config(
SUPPLEMENTAL_CFG_PATH)
auto_refresh_enabled = config.getboolean('enable_auto_refresh', False)
self.channel = config.get('channel', "dev") self.channel = config.get('channel', "dev")
if self.channel not in ["dev", "beta"]: if self.channel not in ["dev", "beta"]:
raise config.error( raise config.error(
f"Unsupported channel '{self.channel}' in section" f"Unsupported channel '{self.channel}' in section"
" [update_manager]") " [update_manager]")
self.app_config = base_config.get_base_configuration(
config, self.channel
)
auto_refresh_enabled = config.getboolean('enable_auto_refresh', False)
self.cmd_helper = CommandHelper(config) self.cmd_helper = CommandHelper(config)
self.updaters: Dict[str, BaseDeploy] = {} self.updaters: Dict[str, BaseDeploy] = {}
if config.getboolean('enable_system_updates', True): if config.getboolean('enable_system_updates', True):
self.updaters['system'] = PackageDeploy(config, self.cmd_helper) self.updaters['system'] = PackageDeploy(config, self.cmd_helper)
db: DBComp = self.server.lookup_component('database') mcfg = self.app_config["moonraker"]
kpath = db.get_item("moonraker", "update_manager.klipper_path", kcfg = self.app_config["klipper"]
KLIPPER_DEFAULT_PATH).result() mclass = get_deploy_class(mcfg.get("path"))
kenv_path = db.get_item("moonraker", "update_manager.klipper_exec", self.updaters['moonraker'] = mclass(mcfg, self.cmd_helper)
KLIPPER_DEFAULT_EXEC).result() kclass = BaseDeploy
self.updaters['moonraker'] = get_deploy_class(MOONRAKER_PATH)(
self.app_config[f"update_manager moonraker"], self.cmd_helper,
{
'channel': self.channel,
'path': MOONRAKER_PATH,
'executable': sys.executable
}
)
if ( if (
os.path.exists(kpath) and os.path.exists(kcfg.get("path")) and
os.path.exists(kenv_path) os.path.exists(kcfg.get("env"))
): ):
self.updaters['klipper'] = get_deploy_class(kpath)( kclass = get_deploy_class(kcfg.get("path"))
self.app_config[f"update_manager klipper"], self.cmd_helper, self.updaters['klipper'] = kclass(kcfg, self.cmd_helper)
{
'channel': self.channel,
'path': kpath,
'executable': kenv_path
}
)
else:
self.updaters['klipper'] = BaseDeploy(
self.app_config[f"update_manager klipper"], self.cmd_helper)
# TODO: The below check may be removed when invalid config options # TODO: The below check may be removed when invalid config options
# raise a config error. # raise a config error.
@ -135,8 +112,8 @@ class UpdateManager:
self.updaters[name] = WebClientDeploy(cfg, self.cmd_helper) self.updaters[name] = WebClientDeploy(cfg, self.cmd_helper)
elif client_type in ["git_repo", "zip", "zip_beta"]: elif client_type in ["git_repo", "zip", "zip_beta"]:
path = os.path.expanduser(cfg.get('path')) path = os.path.expanduser(cfg.get('path'))
self.updaters[name] = get_deploy_class(path)( dclass = get_deploy_class(path)
cfg, self.cmd_helper) self.updaters[name] = dclass(cfg, self.cmd_helper)
else: else:
raise config.error( raise config.error(
f"Invalid type '{client_type}' for section [{section}]") f"Invalid type '{client_type}' for section [{section}]")
@ -215,14 +192,12 @@ class UpdateManager:
db: DBComp = self.server.lookup_component('database') db: DBComp = self.server.lookup_component('database')
db.insert_item("moonraker", "update_manager.klipper_path", kpath) db.insert_item("moonraker", "update_manager.klipper_path", kpath)
db.insert_item("moonraker", "update_manager.klipper_exec", executable) db.insert_item("moonraker", "update_manager.klipper_exec", executable)
kcfg = self.app_config["klipper"]
kcfg.set_option("path", kpath)
kcfg.set_option("env", executable)
need_notification = not isinstance(kupdater, AppDeploy) need_notification = not isinstance(kupdater, AppDeploy)
self.updaters['klipper'] = get_deploy_class(kpath)( kclass = get_deploy_class(kpath)
self.app_config[f"update_manager klipper"], self.cmd_helper, self.updaters['klipper'] = kclass(kcfg, self.cmd_helper)
{
'channel': self.channel,
'path': kpath,
'executable': executable
})
async with self.cmd_request_lock: async with self.cmd_request_lock:
umdb = self.cmd_helper.get_umdb() umdb = self.cmd_helper.get_umdb()
await umdb.pop('klipper', None) await umdb.pop('klipper', None)

View File

@ -35,12 +35,8 @@ RINFO_KEYS = [
] ]
class ZipDeploy(AppDeploy): class ZipDeploy(AppDeploy):
def __init__(self, def __init__(self, config: ConfigHelper, cmd_helper: CommandHelper) -> None:
config: ConfigHelper, super().__init__(config, cmd_helper)
cmd_helper: CommandHelper,
app_params: Optional[Dict[str, Any]] = None
) -> None:
super().__init__(config, cmd_helper, app_params)
self.official_repo: str = "?" self.official_repo: str = "?"
self.owner: str = "?" self.owner: str = "?"
# Extract repo from origin for validation # Extract repo from origin for validation
@ -60,7 +56,7 @@ class ZipDeploy(AppDeploy):
@staticmethod @staticmethod
async def from_application(app: AppDeploy) -> ZipDeploy: async def from_application(app: AppDeploy) -> ZipDeploy:
new_app = ZipDeploy(app.config, app.cmd_helper, app.app_params) new_app = ZipDeploy(app.config, app.cmd_helper)
await new_app.reinstall() await new_app.reinstall()
return new_app return new_app