diff --git a/usr/lib/python3.8/vmmgr/actionqueue.py b/usr/lib/python3.8/vmmgr/actionqueue.py index f3b18dd..484cd56 100644 --- a/usr/lib/python3.8/vmmgr/actionqueue.py +++ b/usr/lib/python3.8/vmmgr/actionqueue.py @@ -3,7 +3,7 @@ from enum import Enum from collections import deque from threading import Lock -from spoc.config import LOCK_FILE +from spoc import config as spoc_config from spoc.flock import locked class ActionItemType(Enum): @@ -19,20 +19,15 @@ class ActionItemType(Enum): APP_STOP = 10 class ActionItem: - def __init__(self, action_type, key, action, show_progress=True): + def __init__(self, action_type, key, action): self.type = action_type self.key = key self.action = action - self.show_progress = show_progress - self.units_total = 1 + self.units_total = 0 self.units_done = 0 def run(self): - if self.show_progress: - self.action(self) - else: - self.action() - self.units_done = 1 + self.action(self) class ActionAppQueue: def __init__(self, action): @@ -46,28 +41,28 @@ class ActionAppQueue: self.queue.append(ActionItem(ActionItemType.IMAGE_UNPACK, image.name, image.unpack_downloaded)) def delete_image(self, image): - self.queue.append(ActionItem(ActionItemType.IMAGE_DELETE, image.name, image.delete, False)) + self.queue.append(ActionItem(ActionItemType.IMAGE_DELETE, image.name, image.delete)) def install_app(self, app): self.queue.append(ActionItem(ActionItemType.APP_DOWNLOAD, app.name, app.download)) self.queue.append(ActionItem(ActionItemType.APP_UNPACK, app.name, app.unpack_downloaded)) - self.queue.append(ActionItem(ActionItemType.APP_INSTALL, app.name, app.install, False)) + self.queue.append(ActionItem(ActionItemType.APP_INSTALL, app.name, app.install)) def update_app(self, app): - self.queue.append(ActionItem(ActionItemType.APP_STOP, app.name, app.stop, False)) + self.queue.append(ActionItem(ActionItemType.APP_STOP, app.name, app.stop)) self.queue.append(ActionItem(ActionItemType.APP_DOWNLOAD, app.name, app.download)) self.queue.append(ActionItem(ActionItemType.APP_UNPACK, app.name, app.unpack_downloaded)) - self.queue.append(ActionItem(ActionItemType.APP_UPDATE, app.name, app.update, False)) + self.queue.append(ActionItem(ActionItemType.APP_UPDATE, app.name, app.update)) def uninstall_app(self, app): - self.queue.append(ActionItem(ActionItemType.APP_STOP, app.name, app.stop, False)) - self.queue.append(ActionItem(ActionItemType.APP_UNINSTALL, app.name, app.uninstall, False)) + self.queue.append(ActionItem(ActionItemType.APP_STOP, app.name, app.stop)) + self.queue.append(ActionItem(ActionItemType.APP_UNINSTALL, app.name, app.uninstall)) def start_app(self, app): - self.queue.append(ActionItem(ActionItemType.APP_START, app.name, app.start, False)) + self.queue.append(ActionItem(ActionItemType.APP_START, app.name, app.start)) def stop_app(self, app): - self.queue.append(ActionItem(ActionItemType.APP_STOP, app.name, app.stop, False)) + self.queue.append(ActionItem(ActionItemType.APP_STOP, app.name, app.stop)) def process(self): for item in self.queue: @@ -96,13 +91,16 @@ class ActionQueue: self.actions[app_name] = ActionAppQueue(action) self.queue.append(app_name) - @locked(LOCK_FILE) - def process_actions(self): + def process(self): # Main method for deferred queue processing called by WSGI close handler with self.lock: # If the queue is being processesd by another thread, allow this thread to be terminated if self.is_running: return + self.process_actions() + + @locked(spoc_config.LOCK_FILE) + def process_actions(self): while True: with self.lock: # Try to get an item from queue diff --git a/usr/lib/python3.8/vmmgr/vmmgr.py b/usr/lib/python3.8/vmmgr/vmmgr.py index 012c804..a98b95b 100644 --- a/usr/lib/python3.8/vmmgr/vmmgr.py +++ b/usr/lib/python3.8/vmmgr/vmmgr.py @@ -1,14 +1,16 @@ # -*- coding: utf-8 -*- import configparser +import importlib import os import shutil import subprocess import urllib +from spoc import config as spoc_config, repo_local, repo_online from spoc.app import App -from spoc.config import ONLINE_BASE_URL from spoc.container import Container, ContainerState from spoc.depsolver import DepSolver +from spoc.image import Image from . import config, crypto, net, templates from .paths import ACME_CRON, ACME_DIR, ISSUE_FILE, MOTD_FILE, NGINX_DIR @@ -145,7 +147,7 @@ def update_app_autostart(app_name, enabled): def is_app_started(app_name): # Assume that the main container has always the same name as app - return Container(app_name).get_status() == ContainerState.RUNNING + return Container(app_name).get_state() == ContainerState.RUNNING def is_app_autostarted(app_name): # Check OpenRC service enablement @@ -169,7 +171,7 @@ def uninstall_app(app_name, queue): queue.uninstall_app(app) # Remove unused layers removed_containers = [container.name for container in app.containers] - retained_containers = [definition for name,definition in repo_local.get_containers() if name not in removed_containers] + retained_containers = [definition for name,definition in repo_local.get_containers().items() if name not in removed_containers] remove_unused_layers(retained_containers, queue) def update_app(app_name, queue): @@ -186,7 +188,7 @@ def update_app(app_name, queue): queue.update_app(app) # Remove unused layers removed_containers = [container.name for container in app.containers] - retained_containers = [definition for name,definition in repo_local.get_containers() if name not in removed_containers] + new_containers + retained_containers = [definition for name,definition in repo_local.get_containers().items() if name not in removed_containers] + new_containers remove_unused_layers(retained_containers, queue) def remove_unused_layers(retained_containers, queue): @@ -205,18 +207,22 @@ def remove_unused_layers(retained_containers, queue): def update_repo_conf(url, username, password): # Include credentials in the repo URL and save to SPOC config - spoc_config = configparser.ConfigParser() - spoc_config.read('/etc/spoc/spoc.conf') parts = urllib.parse.urlsplit(url) - netloc = f'{username}:{password}@{url}' if username or password else url + netloc = f'{username}:{password}@{parts.netloc}' if username or password else parts.netloc url = urllib.parse.urlunsplit((parts.scheme, netloc, parts.path, parts.query, parts.fragment)) - spoc_config['repo']['url'] = ONLINE_BASE_URL = url - with open('/etc/spoc/spoc.conf', 'w') as f: - config.write(f) + repo_config = configparser.ConfigParser() + repo_config.read(spoc_config.CONFIG_FILE) + repo_config['repo']['url'] = url + with open(spoc_config.CONFIG_FILE, 'w') as f: + repo_config.write(f) + # Reimport the config module, reloading the defined URL values + importlib.reload(spoc_config) + repo_online.data = None def get_repo_conf(): # Parse the SPOC config repo URL and return as tuple - parts = urllib.parse.urlsplit(ONLINE_BASE_URL) + parts = urllib.parse.urlsplit(spoc_config.ONLINE_BASE_URL) netloc = parts.netloc.split('@', 1)[1] if parts.username or parts.password else parts.netloc url = urllib.parse.urlunsplit((parts.scheme, netloc, parts.path, parts.query, parts.fragment)) - return {'url':url, 'username':parts.username} + username = parts.username if parts.username else '' + return {'url':url, 'username':username} diff --git a/usr/lib/python3.8/vmmgr/wsgiapp.py b/usr/lib/python3.8/vmmgr/wsgiapp.py index 610e522..eea0fdf 100644 --- a/usr/lib/python3.8/vmmgr/wsgiapp.py +++ b/usr/lib/python3.8/vmmgr/wsgiapp.py @@ -8,7 +8,7 @@ from math import floor from pkg_resources import parse_version from spoc import repo_online, repo_local from werkzeug.exceptions import HTTPException, NotFound, Unauthorized -from werkzeug.routing import Map, Rule +from werkzeug.routing import Map, Rule, RequestRedirect from werkzeug.utils import redirect from werkzeug.wrappers import Request, Response @@ -161,8 +161,8 @@ class WSGIApp: # Application manager view. repo_error = None try: - # Populate online_repo cache or fail early when the repo can't be reached - repo_online.get_apps() + # Repopulate online_repo cache or fail early when the repo can't be reached + repo_online.load(True) except InvalidSignature: repo_error = request.session.lang.invalid_packages_signature() except Unauthorized: @@ -177,7 +177,11 @@ class WSGIApp: def render_setup_apps_table(self, request): lang = request.session.lang local_apps = repo_local.get_apps() - online_apps = repo_online.get_apps() + try: + online_apps = repo_online.get_apps() + except: + online_apps = {} + apps_config = config.get_apps() actionable_apps = sorted(set(online_apps) | set(local_apps)) pending_actions = self.queue.get_actions() app_data = {} @@ -185,12 +189,12 @@ class WSGIApp: installed = app in local_apps title = local_apps[app]['meta']['title'] if installed else online_apps[app]['meta']['title'] try: - visible = local_apps[app]['visible'] - except: + visible = apps_config[app]['visible'] + except KeyError: visible = False try: autostarted = local_apps[app]['autostart'] - except: + except KeyError: autostarted = False if app in pending_actions: # Display queued or currently processed actions @@ -231,8 +235,9 @@ class WSGIApp: if app_queue.action not in (vmmgr.start_app, vmmgr.stop_app): # For tasks other than start/stop which have only a single subtask, display also index of the subtask in queue status = f'[{app_queue.index}/{len(app_queue.queue)}] {status}' - if action_item.show_progress: - status = f'{status} ({floor(current_action.units_done/current_action.units_total*100)} %)' + if action_item.units_total: + # Show progress for tasks which have measurable progress + status = f'{status} ({floor(action_item.units_done/action_item.units_total*100)} %)' actions = '
' else: # Display queued (pending, not started) task @@ -260,8 +265,11 @@ class WSGIApp: else: status = f'{lang.status_stopped()}' actions = f'{lang.action_start()}, {lang.action_uninstall()}' - if parse_version(online_apps[app]['version']) > parse_version(app.version): - actions = f'{actions}, {lang.action_update()}' + try: + if parse_version(online_apps[app]['version']) > parse_version(local_apps[app]['version']): + actions = f'{actions}, {lang.action_update()}' + except KeyError: + pass app_data[app] = {'title': title, 'visible': visible, 'installed': installed, 'autostarted': autostarted, 'status': status, 'actions': actions} return self.render_template('setup-apps-table.html', request, app_data=app_data) @@ -369,7 +377,7 @@ class WSGIApp: app = request.form['app'] self.queue.enqueue_action(app, action) response = self.render_json({'ok': self.render_setup_apps_table(request)}) - response.call_on_close(self.queue.process_actions) + response.call_on_close(self.queue.process) return response def start_app_action(self, request): diff --git a/usr/lib/python3.8/vmmgr/wsgilang.py b/usr/lib/python3.8/vmmgr/wsgilang.py index 8d5d26c..cf1ef1c 100644 --- a/usr/lib/python3.8/vmmgr/wsgilang.py +++ b/usr/lib/python3.8/vmmgr/wsgilang.py @@ -44,6 +44,7 @@ class WSGILang: 'status_installing': 'Instaluje se', 'status_updating': 'Aktualizuje se', 'status_uninstalling': 'Odinstalovává se', + 'status_deleting': 'Maže se {}', 'status_not_installed': 'Není nainstalována', 'action_start': 'Spustit', 'action_stop': 'Zastavit',