Source code for univention.appcenter.actions.install

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Univention App Center
#  univention-app module for installing an app
#
# Copyright 2015-2022 Univention GmbH
#
# https://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <https://www.gnu.org/licenses/>.
#

from univention.admindiary.client import write_event
from univention.admindiary.events import APP_INSTALL_START, APP_INSTALL_SUCCESS, APP_INSTALL_FAILURE

from univention.appcenter.app_cache import Apps
from univention.appcenter.actions import get_action
from univention.appcenter.exceptions import Abort, InstallMasterPackagesPasswordError, InstallMasterPackagesNoninteractiveError, InstallFailed, InstallNonDockerVersionError, InstallWithoutPermissionError
from univention.appcenter.actions.install_base import InstallRemoveUpgrade
from univention.appcenter.ucr import ucr_get, ucr_save
from univention.appcenter.utils import find_hosts_for_master_packages
from univention.appcenter.packages import update_packages, install_packages_dry_run, install_packages, dist_upgrade_dry_run


[docs]class ControlScriptException(Exception): pass
[docs]class Install(InstallRemoveUpgrade): '''Installs an application from the Univention App Center.''' help = 'Install an app' prescript_ext = 'preinst' pre_readme = 'readme_install' post_readme = 'readme_post_install'
[docs] def setup_parser(self, parser): super(Install, self).setup_parser(parser) parser.add_argument('--do-not-revert', action='store_false', dest='revert', help='Do not revert the installation when it fails. May leave the system in an undesired state') parser.add_argument('--only-master-packages', action='store_true', help='Install only Primary Node packages') parser.add_argument('--do-not-install-master-packages-remotely', action='store_false', dest='install_master_packages_remotely', help='Do not install Primary Node packages on Primary or Backup Directory Node systems')
[docs] def main(self, args): apps = args.app real_apps = [] for app in apps: _apps = Apps().get_all_apps_with_id(app.id) if app._docker_prudence_is_true(): _apps = [_app for _app in _apps if not _app.docker] if _apps: app = sorted(_apps)[-1] self.warn('Using %s instead of %s because docker is to be ignored' % (app, args.app)) else: raise InstallNonDockerVersionError(args.app) if not app.install_permissions_exist(): _apps = [_app for _app in _apps if not _app.install_permissions] if _apps: app = sorted(_apps)[-1] self.warn('Using %s instead of %s because of lacking install permissions' % (app, args.app)) else: raise InstallWithoutPermissionError() real_apps.append(app) args.app = real_apps return self.do_it(args)
def _write_start_event(self, app, args): return write_event(APP_INSTALL_START, {'name': app.name, 'version': app.version}, username=self._get_username(args)) def _write_success_event(self, app, context_id, args): return write_event(APP_INSTALL_SUCCESS, {'name': app.name, 'version': app.version}, username=self._get_username(args), context_id=context_id) def _write_fail_event(self, app, context_id, status, args): return write_event(APP_INSTALL_FAILURE, {'name': app.name, 'version': app.version, 'error_code': str(status)}, username=self._get_username(args), context_id=context_id) def _install_only_master_packages(self, args): return args.only_master_packages def _call_action_hooks(self, directory): super(Install, self)._run_parts(directory) def _do_it(self, app, args): if self._install_only_master_packages(args): self._install_master_packages(app, unregister_if_uninstalled=True) else: self._register_files(app) self.percentage = 5 self._register_app(app, args) self.percentage = 10 self._register_database(app) self.percentage = 15 if not hasattr(args, 'register_attributes') or args.register_attributes: self._register_attributes(app, args) self.percentage = 25 if self._install_app(app, args): self._configure(app, args) self._update_certificates(app, args) self.percentage = 80 self._call_join_script(app, args) self._register_listener(app) ucr_save({'appcenter/prudence/docker/%s' % app.id: 'yes'}) else: raise InstallFailed() def _install_packages(self, packages): return install_packages(packages) def _install_master_packages(self, app, unregister_if_uninstalled=False): old_app = Apps().find(app.id) was_installed = old_app.is_installed() if self._register_component(app): update_packages() ret = self._install_packages(app.default_packages_master) if was_installed: if old_app != app: self.log('Re-registering component for %s' % old_app) if self._register_component(old_app): update_packages() elif unregister_if_uninstalled: self.log('Unregistering component for %s' % app) if self._unregister_component(app): update_packages() return ret def _install_only_master_packages_remotely(self, app, host, is_master, args): if args.install_master_packages_remotely: self.log('Installing some packages of %s on %s' % (app.id, host)) else: self.warn('Not installing packages on %s. Please make sure that these packages are installed by calling "univention-app install "%s=%s" --only-master-packages" on the host' % (host, app.id, app.version)) return username = 'root@%s' % host try: if args.noninteractive: raise InstallMasterPackagesNoninteractiveError() password = self._get_password_for(username) with self._get_password_file(password=password) as password_file: if not password_file: raise InstallMasterPackagesPasswordError() # TODO: fallback if univention-app is not installed process = self._subprocess(['/usr/sbin/univention-ssh', password_file, username, 'univention-app', 'install', '%s=%s' % (app.id, app.version), '--only-master-packages', '--noninteractive', '--do-not-send-info']) if process.returncode != 0: self.warn('Installing Primary Node packages for %s on %s failed!' % (app.id, host)) except Abort: if is_master: self.fatal('This is the Primary Directory Node. Cannot continue!') raise else: self.warn('This is a Backup Directory Node. Continuing anyway, please rerun univention-app install %s --only-master-packages there later!' % (app.id)) def _install_app(self, app, args): if self._register_component(app): update_packages() if app.default_packages_master: if ucr_get('server/role') == 'domaincontroller_master': self._install_master_packages(app) self.percentage = 30 for host, is_master in find_hosts_for_master_packages(): self._install_only_master_packages_remotely(app, host, is_master, args) if ucr_get('server/role') == 'domaincontroller_backup': self._install_master_packages(app) self.percentage = 30 ret = self._install_packages(app.get_packages()) self.percentage = 80 return ret def _revert(self, app, args): if not args.revert: return try: password = self._get_password(args, ask=False) remove = get_action('remove') remove.call(app=[app], noninteractive=args.noninteractive, username=args.username, password=password, send_info=False, skip_checks=[], backup=False) except Exception: pass def _dry_run(self, app, args): return self._install_packages_dry_run(app, args, with_dist_upgrade=False)
[docs] def do_it_once(self, app, args): try: return super(Install, self).do_it_once(app, args) finally: if app.is_installed() and app.id in args.autoinstalled: ucr_save({app.ucr_autoinstalled_key: 'yes'})
def _install_packages_dry_run(self, app, args, with_dist_upgrade): original_app = Apps().find(app.id) if original_app.is_installed(): was_installed = True else: was_installed = False self.log('Dry run for %s' % app) if self._register_component(app): self.debug('Updating packages') update_packages() self.debug('Component %s registered' % app.component_id) pkgs = self._get_packages_for_dry_run(app, args) self.debug('Dry running with %r' % pkgs) ret = install_packages_dry_run(pkgs) if with_dist_upgrade: upgrade_ret = dist_upgrade_dry_run() ret['install'] = sorted(set(ret['install']).union(set(upgrade_ret['install']))) ret['remove'] = sorted(set(ret['remove']).union(set(upgrade_ret['remove']))) ret['broken'] = sorted(set(ret['broken']).union(set(upgrade_ret['broken']))) if args.install_master_packages_remotely: # TODO: should test remotely self.log('Not testing package changes of remote packages!') pass if args.dry_run or ret['broken']: if was_installed: if self._register_component(original_app): self.debug('Updating packages') update_packages() self.debug('Component %s reregistered' % original_app.component_id) else: if self._unregister_component(app): self.debug('Updating packages') update_packages() self.debug('Component %s unregistered' % app.component_id) return ret def _get_packages_for_dry_run(self, app, args): if args.only_master_packages: return app.default_packages_master else: return app.get_packages(additional=True)