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 shutil
import hashlib import hashlib
import json import json
import logging
from .base_deploy import BaseDeploy from .base_deploy import BaseDeploy
# Annotation imports # Annotation imports
@ -79,8 +80,29 @@ class AppDeploy(BaseDeploy):
self.venv_args = config.get('venv_args', None) self.venv_args = config.get('venv_args', None)
self.info_tags: List[str] = config.getlist("info_tags", []) 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 # We need to fetch all potential options for an Application. Not
# all options apply to each subtype, however we can't limit the # all options apply to each subtype, however we can't limit the
# options in children if we want to switch between channels and # options in children if we want to switch between channels and
@ -167,25 +189,30 @@ class AppDeploy(BaseDeploy):
raise NotImplementedError raise NotImplementedError
async def restart_service(self): async def restart_service(self):
if not self.is_service: if not self.managed_services:
self.notify_status(
"Application not configured as service, skipping restart")
return return
if self.name == "moonraker": is_full = self.cmd_helper.is_full_update()
# Launch restart async so the request can return for svc in self.managed_services:
# before the server restarts if is_full and svc != self.name:
event_loop = self.server.get_event_loop() self.notify_status(f"Service {svc} restart postponed...")
event_loop.delay_callback(.1, self._do_restart) self.cmd_helper.add_pending_restart(svc)
else: continue
await self._do_restart() 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: async def _do_restart(self, svc_name: str) -> None:
self.notify_status("Restarting Service...")
machine: Machine = self.server.lookup_component("machine") machine: Machine = self.server.lookup_component("machine")
try: try:
await machine.do_service_action("restart", self.name) await machine.do_service_action("restart", svc_name)
except Exception: except Exception:
if self.name == "moonraker": if svc_name == "moonraker":
# We will always get an error when restarting moonraker # We will always get an error when restarting moonraker
# from within the child process, so ignore it # from within the child process, so ignore it
return return

View File

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