Uninstall + resiliency test
This commit is contained in:
parent
c6954816be
commit
b1cb57ab48
@ -36,13 +36,20 @@ class PackageManager:
|
||||
self.online_packages = json.loads(packages)
|
||||
|
||||
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()
|
||||
for dep in self.get_deps(name):
|
||||
if dep not in self.conf['packages']:
|
||||
self.download_package(dep)
|
||||
self.run_install_script(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):
|
||||
# Downloads, verifies, unpacks and sets up a package
|
||||
@ -56,14 +63,39 @@ class PackageManager:
|
||||
if self.online_packages[name]['sha512'] != hash_file(tmp_archive):
|
||||
raise InvalidSignature(name)
|
||||
# Unpack
|
||||
subprocess.run(['tar', 'xJf', tmp_archive], cwd=LXC_ROOT)
|
||||
subprocess.run(['tar', 'xJf', tmp_archive], cwd='/')
|
||||
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):
|
||||
# Registers a package in local configuration
|
||||
metadata = self.online_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' in metadata:
|
||||
@ -76,15 +108,12 @@ class PackageManager:
|
||||
}
|
||||
self.conf.save()
|
||||
|
||||
def setup_package(self):
|
||||
# Runs setup.sh for a package, if the script is present
|
||||
setup_dir = os.path.join(LXC_ROOT, 'setup')
|
||||
setup_script = os.path.join(LXC_ROOT, 'setup.sh')
|
||||
if os.path.exists(setup_script):
|
||||
subprocess.run(setup_script)
|
||||
os.unlink(setup_script)
|
||||
if os.path.exists(setup_dir):
|
||||
shutil.rmtree(setup_dir)
|
||||
def unregister_package(self, name):
|
||||
# Removes a package from local configuration
|
||||
del self.conf['packages'][name]
|
||||
if name in self.conf['apps']:
|
||||
del self.conf['apps'][name]
|
||||
self.conf.save()
|
||||
|
||||
def get_deps(self, name):
|
||||
# Flatten dependency tree for a package
|
||||
|
@ -78,6 +78,7 @@ class WSGIApp(object):
|
||||
Rule('/start-app', endpoint='start_app_action'),
|
||||
Rule('/stop-app', endpoint='stop_app_action'),
|
||||
Rule('/install-app', endpoint='install_app_action'),
|
||||
Rule('/uninstall-app', endpoint='uninstall_app_action'),
|
||||
Rule('/update-password', endpoint='update_password_action'),
|
||||
Rule('/shutdown-vm', endpoint='shutdown_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()})
|
||||
except:
|
||||
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']
|
||||
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):
|
||||
# Updates password for both HDD encryption (LUKS-on-LVM) and web interface admin account
|
||||
try:
|
||||
|
@ -11,7 +11,8 @@ $(function() {
|
||||
$('#app-manager')
|
||||
.on('click', '.app-start', start_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);
|
||||
$('#reboot-vm').on('click', reboot_vm);
|
||||
$('#shutdown-vm').on('click', shutdown_vm);
|
||||
@ -116,11 +117,11 @@ function update_common() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function update_app_visibility(ev) {
|
||||
function _update_app(item, ev) {
|
||||
var el = $(ev.target);
|
||||
var app = el.closest('tr').data('app');
|
||||
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) {
|
||||
el.prop('checked', !value);
|
||||
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) {
|
||||
return _update_app('autostart', ev);
|
||||
}
|
||||
|
||||
function _do_app(action, ev) {
|
||||
var el = $(ev.target);
|
||||
var app = el.closest('tr').data('app');
|
||||
var value = el.is(':checked') ? 'true' : '';
|
||||
$.post('/update-app-autostart', {'app': app, 'value': value}, function(data) {
|
||||
var tr = el.closest('tr');
|
||||
var td = el.closest('td');
|
||||
td.html('<div class="loader"></div>');
|
||||
$.post('/'+action+'-app', {'app': tr.data('app')}, function(data) {
|
||||
if (data.error) {
|
||||
el.prop('checked', !value);
|
||||
alert(data.error);
|
||||
td.attr('class','error').html(data.error);
|
||||
} else {
|
||||
tr.replaceWith(data.ok);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function start_app(ev) {
|
||||
var el = $(ev.target);
|
||||
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;
|
||||
return _do_app('start', ev);
|
||||
}
|
||||
|
||||
function stop_app(ev) {
|
||||
var el = $(ev.target);
|
||||
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;
|
||||
return _do_app('stop', ev);
|
||||
}
|
||||
|
||||
function install_app(ev) {
|
||||
var el = $(ev.target);
|
||||
var tr = el.closest('tr');
|
||||
var td = el.closest('td');
|
||||
td.html('<div class="loader"></div>');
|
||||
$.post('/install-app', {'app': tr.data('app')}, function(data) {
|
||||
if (data.error) {
|
||||
td.attr('class','error').html(data.error);
|
||||
} else {
|
||||
tr.replaceWith(data.ok);
|
||||
return _do_app('install', ev);
|
||||
}
|
||||
|
||||
function uninstall_app(ev) {
|
||||
if (confirm('Do you really want to uninstall this applition?')) {
|
||||
return _do_app('uninstall', ev);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -201,20 +187,22 @@ function update_password() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function reboot_vm() {
|
||||
if (confirm('Do you really want to reboot VM?')) {
|
||||
$.get('/reboot-vm', function(data) {
|
||||
function _do_vm(action) {
|
||||
$.get('/'+action+'-vm', function(data) {
|
||||
$('#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;
|
||||
}
|
||||
|
||||
function shutdown_vm() {
|
||||
if (confirm('Do you really want to shutdown VM?')) {
|
||||
$.get('/shutdown-vm', function(data) {
|
||||
$('#vm-message').attr('class','info').html(data.ok).show();
|
||||
});
|
||||
_do_vm('shutdown');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -2,6 +2,6 @@
|
||||
<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-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>
|
||||
</tr>
|
||||
|
@ -39,12 +39,13 @@ def pack(pkg_file):
|
||||
|
||||
# Create archive
|
||||
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']:
|
||||
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))
|
||||
subprocess.run(['tar', 'rpf', tar_path, 'setup', 'setup.sh'], cwd=cwd)
|
||||
print('Compressing', tar_path)
|
||||
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, '({} MB)'.format(os.path.getsize(tar_path)/1048576))
|
||||
subprocess.run(['xz', '-9', tar_path])
|
||||
|
||||
# Register package
|
||||
|
Loading…
Reference in New Issue
Block a user