Uninstall + resiliency test

This commit is contained in:
Disassembler 2018-10-26 14:12:12 +02:00
parent c6954816be
commit b1cb57ab48
No known key found for this signature in database
GPG Key ID: 524BD33A0EE29499
5 changed files with 104 additions and 67 deletions

View File

@ -36,13 +36,20 @@ class PackageManager:
self.online_packages = json.loads(packages) self.online_packages = json.loads(packages)
def install_package(self, name): def install_package(self, name):
# Main installation function. Wrapper for download, registration and setup # Main installation function. Wrapper for download, install script and registration
self.fetch_online_packages() self.fetch_online_packages()
for dep in self.get_deps(name): for dep in self.get_deps(name):
if dep not in self.conf['packages']: if dep not in self.conf['packages']:
self.download_package(dep) self.download_package(dep)
self.run_install_script(dep)
self.register_package(dep) self.register_package(dep)
self.setup_package()
def uninstall_package(self, name):
# Main uninstallation function. Wrapper for uninstall script, filesystem purge and unregistration
# TODO: Get dependencies which can be uninstalled
self.run_uninstall_script(name)
self.purge_package(name)
self.unregister_package(name)
def download_package(self, name): def download_package(self, name):
# Downloads, verifies, unpacks and sets up a package # Downloads, verifies, unpacks and sets up a package
@ -56,14 +63,39 @@ class PackageManager:
if self.online_packages[name]['sha512'] != hash_file(tmp_archive): if self.online_packages[name]['sha512'] != hash_file(tmp_archive):
raise InvalidSignature(name) raise InvalidSignature(name)
# Unpack # Unpack
subprocess.run(['tar', 'xJf', tmp_archive], cwd=LXC_ROOT) subprocess.run(['tar', 'xJf', tmp_archive], cwd='/')
os.unlink(tmp_archive) os.unlink(tmp_archive)
def purge_package(self, name):
# Removes package and shared data from filesystem
shutil.rmtree(os.path.join(LXC_ROOT, self.conf['packages'][name]['lxcpath']))
srv_dir = os.path.join('/srv/', name)
if os.path.exsit(srv_dir):
shutil.rmtree(srv_dir)
def run_install_script(self, name):
# Runs install.sh for a package, if the script is present
install_dir = os.path.join('/srv/', name, 'install')
install_script = os.path.join('/srv/', name, 'install.sh')
if os.path.exists(install_script):
subprocess.run(install_script)
os.unlink(install_script)
if os.path.exists(install_dir):
shutil.rmtree(install_dir)
def run_uninstall_script(self, name):
# Runs uninstall.sh for a package, if the script is present
uninstall_script = os.path.join('/srv/', name, 'uninstall.sh')
if os.path.exists(uninstall_script):
subprocess.run(uninstall_script)
def register_package(self, name): def register_package(self, name):
# Registers a package in local configuration # Registers a package in local configuration
metadata = self.online_packages[name] metadata = self.online_packages[name]
self.conf['packages'][name] = { self.conf['packages'][name] = {
'version': metadata['version'], 'deps': metadata['deps'],
'lxcpath': metadata['lxcpath'],
'version': metadata['version']
} }
# If host definition is present, register the package as application # If host definition is present, register the package as application
if 'host' in metadata: if 'host' in metadata:
@ -76,15 +108,12 @@ class PackageManager:
} }
self.conf.save() self.conf.save()
def setup_package(self): def unregister_package(self, name):
# Runs setup.sh for a package, if the script is present # Removes a package from local configuration
setup_dir = os.path.join(LXC_ROOT, 'setup') del self.conf['packages'][name]
setup_script = os.path.join(LXC_ROOT, 'setup.sh') if name in self.conf['apps']:
if os.path.exists(setup_script): del self.conf['apps'][name]
subprocess.run(setup_script) self.conf.save()
os.unlink(setup_script)
if os.path.exists(setup_dir):
shutil.rmtree(setup_dir)
def get_deps(self, name): def get_deps(self, name):
# Flatten dependency tree for a package # Flatten dependency tree for a package

View File

@ -78,6 +78,7 @@ class WSGIApp(object):
Rule('/start-app', endpoint='start_app_action'), Rule('/start-app', endpoint='start_app_action'),
Rule('/stop-app', endpoint='stop_app_action'), Rule('/stop-app', endpoint='stop_app_action'),
Rule('/install-app', endpoint='install_app_action'), Rule('/install-app', endpoint='install_app_action'),
Rule('/uninstall-app', endpoint='uninstall_app_action'),
Rule('/update-password', endpoint='update_password_action'), Rule('/update-password', endpoint='update_password_action'),
Rule('/shutdown-vm', endpoint='shutdown_vm_action'), Rule('/shutdown-vm', endpoint='shutdown_vm_action'),
Rule('/reboot-vm', endpoint='reboot_vm_action'), Rule('/reboot-vm', endpoint='reboot_vm_action'),
@ -286,9 +287,27 @@ class WSGIApp(object):
return self.render_json({'error': request.session.lang.malformed_request()}) return self.render_json({'error': request.session.lang.malformed_request()})
except: except:
return self.render_json({'error': request.session.lang.package_manager_error()}) return self.render_json({'error': request.session.lang.package_manager_error()})
# Reload config and get fresh data
self.vmmgr.conf.load()
app_title = self.vmmgr.conf['apps'][app]['title'] app_title = self.vmmgr.conf['apps'][app]['title']
return self.render_json({'ok': self.render_setup_apps_row(app, app_title)}) return self.render_json({'ok': self.render_setup_apps_row(app, app_title)})
def uninstall_app_action(self, request):
# Uninstalls application
try:
app = request.form['app']
self.vmmgr.stop_app(app)
pkgmgr = PackageManager()
pkgmgr.uninstall_package(app)
except (BadRequest, InvalidValueException):
return self.render_json({'error': request.session.lang.malformed_request()})
except:
return self.render_json({'error': request.session.lang.package_manager_error()})
# Get title from old data, then reload config
app_title = self.vmmgr.conf['apps'][app]['title']
self.vmmgr.conf.load()
return self.render_json({'ok': self.render_setup_apps_row(app, app_title)})
def update_password_action(self, request): def update_password_action(self, request):
# Updates password for both HDD encryption (LUKS-on-LVM) and web interface admin account # Updates password for both HDD encryption (LUKS-on-LVM) and web interface admin account
try: try:

View File

@ -11,7 +11,8 @@ $(function() {
$('#app-manager') $('#app-manager')
.on('click', '.app-start', start_app) .on('click', '.app-start', start_app)
.on('click', '.app-stop', stop_app) .on('click', '.app-stop', stop_app)
.on('click', '.app-install', install_app); .on('click', '.app-install', install_app)
.on('click', '.app-uninstall', install_app);
$('#update-password').on('submit', update_password); $('#update-password').on('submit', update_password);
$('#reboot-vm').on('click', reboot_vm); $('#reboot-vm').on('click', reboot_vm);
$('#shutdown-vm').on('click', shutdown_vm); $('#shutdown-vm').on('click', shutdown_vm);
@ -116,11 +117,11 @@ function update_common() {
return false; return false;
} }
function update_app_visibility(ev) { function _update_app(item, ev) {
var el = $(ev.target); var el = $(ev.target);
var app = el.closest('tr').data('app'); var app = el.closest('tr').data('app');
var value = el.is(':checked') ? 'true' : ''; var value = el.is(':checked') ? 'true' : '';
$.post('/update-app-visibility', {'app': app, 'value': value}, function(data) { $.post('/update-app-'+item, {'app': app, 'value': value}, function(data) {
if (data.error) { if (data.error) {
el.prop('checked', !value); el.prop('checked', !value);
alert(data.error); alert(data.error);
@ -128,60 +129,45 @@ function update_app_visibility(ev) {
}); });
} }
function update_app_visibility(ev) {
return _update_app('visibility', ev);
}
function update_app_autostart(ev) { function update_app_autostart(ev) {
return _update_app('autostart', ev);
}
function _do_app(action, ev) {
var el = $(ev.target); var el = $(ev.target);
var app = el.closest('tr').data('app'); var tr = el.closest('tr');
var value = el.is(':checked') ? 'true' : ''; var td = el.closest('td');
$.post('/update-app-autostart', {'app': app, 'value': value}, function(data) { td.html('<div class="loader"></div>');
$.post('/'+action+'-app', {'app': tr.data('app')}, function(data) {
if (data.error) { if (data.error) {
el.prop('checked', !value); td.attr('class','error').html(data.error);
alert(data.error); } else {
tr.replaceWith(data.ok);
} }
}); });
return false;
} }
function start_app(ev) { function start_app(ev) {
var el = $(ev.target); return _do_app('start', ev);
var tr = el.closest('tr');
var td = el.closest('td');
td.html('<div class="loader"></div>');
$.post('/start-app', {'app': tr.data('app')}, function(data) {
if (data.error) {
td.attr('class','error').html(data.error);
} else {
tr.replaceWith(data.ok);
}
});
return false;
} }
function stop_app(ev) { function stop_app(ev) {
var el = $(ev.target); return _do_app('stop', ev);
var tr = el.closest('tr');
var td = el.closest('td');
td.html('<div class="loader"></div>');
$.post('/stop-app', {'app': tr.data('app')}, function(data) {
if (data.error) {
td.attr('class','error').html(data.error);
} else {
tr.replaceWith(data.ok);
}
});
return false;
} }
function install_app(ev) { function install_app(ev) {
var el = $(ev.target); return _do_app('install', ev);
var tr = el.closest('tr'); }
var td = el.closest('td');
td.html('<div class="loader"></div>'); function uninstall_app(ev) {
$.post('/install-app', {'app': tr.data('app')}, function(data) { if (confirm('Do you really want to uninstall this applition?')) {
if (data.error) { return _do_app('uninstall', ev);
td.attr('class','error').html(data.error);
} else {
tr.replaceWith(data.ok);
} }
});
return false; return false;
} }
@ -201,20 +187,22 @@ function update_password() {
return false; return false;
} }
function reboot_vm() { function _do_vm(action) {
if (confirm('Do you really want to reboot VM?')) { $.get('/'+action+'-vm', function(data) {
$.get('/reboot-vm', function(data) {
$('#vm-message').attr('class','info').html(data.ok).show(); $('#vm-message').attr('class','info').html(data.ok).show();
}); });
} }
function reboot_vm() {
if (confirm('Do you really want to reboot VM?')) {
_do_vm('reboot');
}
return false; return false;
} }
function shutdown_vm() { function shutdown_vm() {
if (confirm('Do you really want to shutdown VM?')) { if (confirm('Do you really want to shutdown VM?')) {
$.get('/shutdown-vm', function(data) { _do_vm('shutdown');
$('#vm-message').attr('class','info').html(data.ok).show();
});
} }
return false; return false;
} }

View File

@ -2,6 +2,6 @@
<td>{{ app_title }}</td> <td>{{ app_title }}</td>
<td class="center"><input type="checkbox" class="app-visible"{% if app not in conf['apps'] %} disabled{% elif conf['apps'][app]['visible'] %} checked{% endif %}></td> <td class="center"><input type="checkbox" class="app-visible"{% if app not in conf['apps'] %} disabled{% elif conf['apps'][app]['visible'] %} checked{% endif %}></td>
<td class="center"><input type="checkbox" class="app-autostart"{% if app not in conf['apps'] %} disabled{% elif is_service_autostarted(app) %} checked{% endif %}></td> <td class="center"><input type="checkbox" class="app-autostart"{% if app not in conf['apps'] %} disabled{% elif is_service_autostarted(app) %} checked{% endif %}></td>
<td>{% if app not in conf['apps'] %} Není nainstalována{% elif is_service_started(app) %}<span class="info">Spuštěna</span>{% else %}<span class="error">Zastavena</span>{% endif %}</td> <td>{% if app not in conf['apps'] %} N/A{% elif is_service_started(app) %}<span class="info">Spuštěna</span>{% else %}<span class="error">Zastavena</span>{% endif %}</td>
<td>{% if app not in conf['apps'] %}<a href="#" class="app-install">Instalovat</a>{% else %}{% if is_service_started(app) %}<a href="#" class="app-stop">Zastavit</a>{% else %}<a href="#" class="app-start">Spustit</a>{% endif %}, <a href="#" class="app-uninstall">Odinstalovat</a>{% endif %}</td> <td>{% if app not in conf['apps'] %}<a href="#" class="app-install">Instalovat</a>{% else %}{% if is_service_started(app) %}<a href="#" class="app-stop">Zastavit</a>{% else %}<a href="#" class="app-start">Spustit</a>{% endif %}, <a href="#" class="app-uninstall">Odinstalovat</a>{% endif %}</td>
</tr> </tr>

View File

@ -39,12 +39,13 @@ def pack(pkg_file):
# Create archive # Create archive
print('Archiving', meta['lxcpath']) print('Archiving', meta['lxcpath'])
subprocess.run(['tar', 'cp', '--xattrs', '-f', tar_path, meta['lxcpath']], cwd=LXC_ROOT) subprocess.run(['tar', '--xattrs', '-cpf', tar_path, os.path.join(LXC_ROOT, meta['lxcpath'])], cwd='/')
if '/' not in meta['lxcpath']: if '/' not in meta['lxcpath']:
print('Archiving setup files') # If lxcpath doesn't point to layer but is a full-featured container, pack also scripts
print('Archiving install/upgrade/uninstall scripts and related files')
cwd = os.path.dirname(os.path.abspath(pkg_file)) cwd = os.path.dirname(os.path.abspath(pkg_file))
subprocess.run(['tar', 'rpf', tar_path, 'setup', 'setup.sh'], cwd=cwd) subprocess.run(['tar', '--transform' 's,^\.,/srv/{},'.format(pkg_name), '-rpf', tar_path, 'install', 'install.sh', 'upgrade', 'upgrade.sh', 'uninstall', 'uninstall.sh'], cwd=cwd)
print('Compressing', tar_path) print('Compressing', tar_path, '({} MB)'.format(os.path.getsize(tar_path)/1048576))
subprocess.run(['xz', '-9', tar_path]) subprocess.run(['xz', '-9', tar_path])
# Register package # Register package