update_manager: add support for extensions

While use of "unofficial" klippy extras an moonraker components
is not officially supported, there is no harm in facilitating
updates for these extensions in the update manager.  This adds
configuration which will restart either moonraker or klipper
after an extension is updated.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2022-03-26 15:21:02 -04:00
parent 16737d086d
commit c8042a5700
No known key found for this signature in database
GPG Key ID: 7027245FBBDDF59A
2 changed files with 67 additions and 20 deletions

View File

@ -9,6 +9,7 @@ import pathlib
import shutil
import hashlib
import json
import logging
from .base_deploy import BaseDeploy
# Annotation imports
@ -79,8 +80,29 @@ class AppDeploy(BaseDeploy):
self.venv_args = config.get('venv_args', None)
self.info_tags: List[str] = config.getlist("info_tags", [])
self.is_service = config.getboolean("is_system_service", True)
self.managed_services: List[str] = []
svc_default = []
if config.getboolean("is_system_service", True):
svc_default.append(self.name)
svc_choices = [self.name, "klipper", "moonraker"]
services: List[str] = config.getlist(
"managed_services", svc_default, separator=None
)
for svc in services:
if svc not in svc_choices:
raw = " ".join(services)
self.server.add_warning(
f"[{config.get_name()}]: Option 'restart_action: {raw}' "
f"contains an invalid value '{svc}'. All values must be "
f"one of the following choices: {svc_choices}"
)
break
for svc in svc_choices:
if svc in services and svc not in self.managed_services:
self.managed_services.append(svc)
logging.debug(
f"Extension {self.name} managed services: {self.managed_services}"
)
# We need to fetch all potential options for an Application. Not
# all options apply to each subtype, however we can't limit the
# options in children if we want to switch between channels and
@ -167,25 +189,30 @@ class AppDeploy(BaseDeploy):
raise NotImplementedError
async def restart_service(self):
if not self.is_service:
self.notify_status(
"Application not configured as service, skipping restart")
if not self.managed_services:
return
if self.name == "moonraker":
# Launch restart async so the request can return
# before the server restarts
event_loop = self.server.get_event_loop()
event_loop.delay_callback(.1, self._do_restart)
else:
await self._do_restart()
is_full = self.cmd_helper.is_full_update()
for svc in self.managed_services:
if is_full and svc != self.name:
self.notify_status(f"Service {svc} restart postponed...")
self.cmd_helper.add_pending_restart(svc)
continue
self.cmd_helper.remove_pending_restart(svc)
self.notify_status(f"Restarting service {svc}...")
if svc == "moonraker":
# Launch restart async so the request can return
# before the server restarts
event_loop = self.server.get_event_loop()
event_loop.delay_callback(.1, self._do_restart, svc)
else:
await self._do_restart(svc)
async def _do_restart(self) -> None:
self.notify_status("Restarting Service...")
async def _do_restart(self, svc_name: str) -> None:
machine: Machine = self.server.lookup_component("machine")
try:
await machine.do_service_action("restart", self.name)
await machine.do_service_action("restart", svc_name)
except Exception:
if self.name == "moonraker":
if svc_name == "moonraker":
# We will always get an error when restarting moonraker
# from within the child process, so ignore it
return

View File

@ -27,6 +27,7 @@ from typing import (
Any,
Awaitable,
Optional,
Set,
Tuple,
Type,
Union,
@ -324,10 +325,13 @@ class UpdateManager:
kupdater = self.updaters.get('klipper')
if isinstance(kupdater, AppDeploy):
self.klippy_identified_evt = asyncio.Event()
klippy_updated = True
check_restart = True
if not await self._check_need_reinstall(app_name):
klippy_updated = await kupdater.update()
if klippy_updated:
check_restart = await kupdater.update()
if self.cmd_helper.needs_service_restart(app_name):
await kupdater.restart_service()
check_restart = True
if check_restart:
self.cmd_helper.notify_update_response(
"Waiting for Klippy to reconnect (this may take"
" up to 2 minutes)...")
@ -344,8 +348,11 @@ class UpdateManager:
# Update Moonraker
app_name = 'moonraker'
moon_updater = cast(AppDeploy, self.updaters["moonraker"])
if not await self._check_need_reinstall(app_name):
await self.updaters['moonraker'].update()
await moon_updater.update()
if self.cmd_helper.needs_service_restart(app_name):
await moon_updater.restart_service()
self.cmd_helper.set_full_complete(True)
self.cmd_helper.notify_update_response(
"Full Update Complete", is_complete=True)
@ -490,6 +497,7 @@ class CommandHelper:
self.cur_update_id: Optional[int] = None
self.full_update: bool = False
self.full_complete: bool = False
self.pending_service_restarts: Set[str] = set()
def get_server(self) -> Server:
return self.server
@ -511,10 +519,18 @@ class CommandHelper:
self.cur_update_id = uid
self.full_update = app == "full"
self.full_complete = not self.full_update
self.pending_service_restarts.clear()
def is_full_update(self) -> bool:
return self.full_update
def add_pending_restart(self, svc_name: str) -> None:
self.pending_service_restarts.add(svc_name)
def remove_pending_restart(self, svc_name: str) -> None:
if svc_name in self.pending_service_restarts:
self.pending_service_restarts.remove(svc_name)
def set_full_complete(self, complete: bool = False):
self.full_complete = complete
@ -522,6 +538,10 @@ class CommandHelper:
self.cur_update_app = self.cur_update_id = None
self.full_update = False
self.full_complete = False
self.pending_service_restarts.clear()
def needs_service_restart(self, svc_name: str) -> bool:
return svc_name in self.pending_service_restarts
def is_app_updating(self, app_name: str) -> bool:
return self.cur_update_app == app_name