Fix bugs and missing pieces, add SPOC config reload
This commit is contained in:
parent
31372ac3e1
commit
be054ed17b
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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 = '<div class="loader"></div>'
|
||||
else:
|
||||
# Display queued (pending, not started) task
|
||||
@ -260,8 +265,11 @@ class WSGIApp:
|
||||
else:
|
||||
status = f'<span class="error">{lang.status_stopped()}</span>'
|
||||
actions = f'<a href="#" class="app-start">{lang.action_start()}</a>, <a href="#" class="app-uninstall">{lang.action_uninstall()}</a>'
|
||||
if parse_version(online_apps[app]['version']) > parse_version(app.version):
|
||||
actions = f'{actions}, <a href="#" class="app-update">{lang.action_update()}</a>'
|
||||
try:
|
||||
if parse_version(online_apps[app]['version']) > parse_version(local_apps[app]['version']):
|
||||
actions = f'{actions}, <a href="#" class="app-update">{lang.action_update()}</a>'
|
||||
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):
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user