Uninstall + resiliency test
This commit is contained in:
parent
c6954816be
commit
b1cb57ab48
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user