def get_package_manager(): if get_package_manager._package_manager is None: package_manager = PackageManager(lock=False) package_manager.set_finished( ) # currently not working. accepting new tasks package_manager.logger.parent = get_base_logger() log_filter = _PackageManagerLogHandler() package_manager.logger.addHandler(log_filter) get_package_manager._package_manager = package_manager return get_package_manager._package_manager
def init(self): os.umask( 0o022 ) # umc umask is too restrictive for app center as it creates a lot of files in docker containers self.ucr = ucr_instance() self.update_applications_done = False install_opener(self.ucr) self._is_working = False try: self.package_manager = PackageManager( info_handler=MODULE.process, step_handler=None, error_handler=MODULE.warn, lock=False, ) except SystemError as exc: MODULE.error(str(exc)) raise umcm.UMC_Error(str(exc), status=500) self.package_manager.set_finished( ) # currently not working. accepting new tasks get_package_manager._package_manager = self.package_manager # build cache _update_modules() get_action('list').get_apps() # not initialize here: error prone due to network errors and also kinda slow self._uu = None self._cm = None # in order to set the correct locale locale.setlocale(locale.LC_ALL, str(self.locale)) try: log_to_logfile() except IOError: pass # connect univention.appcenter.log to the progress-method handler = ProgressInfoHandler(self.package_manager) handler.setLevel(logging.INFO) get_base_logger().addHandler(handler) percentage = ProgressPercentageHandler(self.package_manager) percentage.setLevel(logging.DEBUG) get_base_logger().getChild('actions.install.progress').addHandler( percentage) get_base_logger().getChild('actions.upgrade.progress').addHandler( percentage) get_base_logger().getChild('actions.remove.progress').addHandler( percentage)
def up(self, *args, **kwargs): self.package_manager = PackageManager(always_noninteractive=False) handler = _PackageManagerLoggerHandlerWithoutProcess(self.message, self.step, self.error) self.package_manager.logger.addHandler(handler) self.roles_package_map = { 'domaincontroller_master': 'univention-server-master', 'domaincontroller_backup': 'univention-server-backup', 'domaincontroller_slave': 'univention-server-slave', 'memberserver': 'univention-server-member', } self.current_server_role = self.ucr.get('server/role') self.wanted_server_role = self.profile.get('server/role')
def init(self): self._finishedLock = threading.Lock() self._errors = [] self.progress_state = Progress() self._installation_started = False self.package_manager = PackageManager( info_handler=self.progress_state.info_handler, step_handler=self.progress_state.step_handler, error_handler=self.progress_state.error_handler, lock=False, always_noninteractive=True, ) self.package_manager.set_max_steps(100.0) self.original_certificate_file = None
def up(self, *args, **kwargs): self.package_manager = PackageManager( info_handler=self.message, step_handler=self.step, error_handler=self.error, handle_only_frontend_errors=True, # ignore pmerror status ) self.roles_package_map = { 'domaincontroller_master': 'univention-server-master', 'domaincontroller_backup': 'univention-server-backup', 'domaincontroller_slave': 'univention-server-slave', 'memberserver': 'univention-server-member', 'fatclient': 'univention-managed-client', 'mobileclient': 'univention-mobile-client', } self.current_server_role = self.get_ucr_var('server/role') self.wanted_server_role = self.get_profile_var('server/role')
def init(self): self.ucr = univention.config_registry.ConfigRegistry() self.ucr.load() util.install_opener(self.ucr) self.package_manager = PackageManager( info_handler=MODULE.process, step_handler=None, error_handler=MODULE.warn, lock=False, always_noninteractive=True, ) self.uu = UniventionUpdater(False) self.component_manager = util.ComponentManager(self.ucr, self.uu) # in order to set the correct locale for Application locale.setlocale(locale.LC_ALL, str(self.locale))
def init(self): os.umask(0o022) # umc umask is too restrictive for app center as it creates a lot of files in docker containers self.ucr = ucr_instance() self.update_applications_done = False util.install_opener(self.ucr) self._remote_progress = {} try: self.package_manager = PackageManager( info_handler=MODULE.process, step_handler=None, error_handler=MODULE.warn, lock=False, ) except SystemError as exc: MODULE.error(str(exc)) raise umcm.UMC_Error(str(exc), status=500) self.package_manager.set_finished() # currently not working. accepting new tasks self.uu = UniventionUpdater(False) self.component_manager = util.ComponentManager(self.ucr, self.uu) get_package_manager._package_manager = self.package_manager # in order to set the correct locale for Application locale.setlocale(locale.LC_ALL, str(self.locale)) try: log_to_logfile() except IOError: pass # connect univention.appcenter.log to the progress-method handler = ProgressInfoHandler(self.package_manager) handler.setLevel(logging.INFO) get_base_logger().addHandler(handler) percentage = ProgressPercentageHandler(self.package_manager) percentage.setLevel(logging.DEBUG) get_base_logger().getChild('actions.install.progress').addHandler(percentage) get_base_logger().getChild('actions.upgrade.progress').addHandler(percentage) get_base_logger().getChild('actions.remove.progress').addHandler(percentage)
def up(self, *args, **kwargs): self.package_manager = PackageManager( info_handler=self.message, step_handler=self.step, error_handler=self.error, handle_only_frontend_errors=True, # ignore pmerror status ) self.roles_package_map = { 'domaincontroller_master' : 'univention-server-master', 'domaincontroller_backup' : 'univention-server-backup', 'domaincontroller_slave' : 'univention-server-slave', 'memberserver' : 'univention-server-member', 'fatclient' : 'univention-managed-client', 'mobileclient' : 'univention-mobile-client', } self.current_server_role = self.get_ucr_var('server/role') self.wanted_server_role = self.get_profile_var('server/role')
class Instance(umcm.Base, ProgressMixin): def init(self): os.umask(0o022) # umc umask is too restrictive for app center as it creates a lot of files in docker containers self.ucr = ucr_instance() self.update_applications_done = False util.install_opener(self.ucr) self._remote_progress = {} try: self.package_manager = PackageManager( info_handler=MODULE.process, step_handler=None, error_handler=MODULE.warn, lock=False, ) except SystemError as exc: MODULE.error(str(exc)) raise umcm.UMC_Error(str(exc), status=500) self.package_manager.set_finished() # currently not working. accepting new tasks self.uu = UniventionUpdater(False) self.component_manager = util.ComponentManager(self.ucr, self.uu) get_package_manager._package_manager = self.package_manager # in order to set the correct locale for Application locale.setlocale(locale.LC_ALL, str(self.locale)) try: log_to_logfile() except IOError: pass # connect univention.appcenter.log to the progress-method handler = ProgressInfoHandler(self.package_manager) handler.setLevel(logging.INFO) get_base_logger().addHandler(handler) percentage = ProgressPercentageHandler(self.package_manager) percentage.setLevel(logging.DEBUG) get_base_logger().getChild('actions.install.progress').addHandler(percentage) get_base_logger().getChild('actions.upgrade.progress').addHandler(percentage) get_base_logger().getChild('actions.remove.progress').addHandler(percentage) def error_handling(self, etype, exc, etraceback): error_handling(etype, exc, etraceback) return super(Instance, self).error_handling(exc, etype, etraceback) @simple_response def version(self): info = get_action('info') return info.get_compatibility() @simple_response def query(self, quick=False): if not quick: self.update_applications() self.ucr.load() reload_package_manager() list_apps = get_action('list') domain = get_action('domain') apps = list_apps.get_apps() if self.ucr.is_true('appcenter/docker', True): if not self._test_for_docker_service(): raise umcm.UMC_Error(_('The docker service is not running! The App Center will not work properly.') + ' ' + _('Make sure docker.io is installed, try starting the service with "service docker start".')) info = domain.to_dict(apps) if quick: ret = [] for app in info: if app is None: ret.append(None) else: short_info = {} for attr in ['id', 'name', 'vendor', 'maintainer', 'description', 'long_description', 'categories', 'end_of_life', 'update_available', 'logo_name', 'is_installed_anywhere', 'is_installed', 'installations']: short_info[attr] = app[attr] ret.append(short_info) return ret else: return info def update_applications(self): if self.ucr.is_true('appcenter/umc/update/always', True): update = get_action('update') try: update.call() except NetworkError as err: raise umcm.UMC_Error(str(err)) except Abort: pass Application._all_applications = None self.update_applications_done = True def _test_for_docker_service(self): if docker_bridge_network_conflict(): msg = _('A conflict between the system network settings and the docker bridge default network has been detected.') + '\n\n' msg += _('Please either configure a different network for the docker bridge by setting the UCR variable docker/daemon/default/opts/bip to a different network and restart the system,') + ' ' msg += _('or disable the docker support in the AppCenter by setting appcenter/docker to false.') raise umcm.UMC_Error(msg) if not docker_is_running(): MODULE.warn('Docker is not running! Trying to start it now...') call_process(['invoke-rc.d', 'docker', 'start']) if not docker_is_running(): return False return True @simple_response def enable_docker(self): if self._test_for_docker_service(): ucr_save({'appcenter/docker': 'enabled'}) else: raise umcm.UMC_Error(_('Unable to start the docker service!') + ' ' + _('Make sure docker.io is installed, try starting the service with "service docker start".')) @require_apps_update @require_password @simple_response(with_progress=True) def sync_ldap(self): register = get_action('register') register.call(username=self.username, password=self.password) # used in updater-umc @simple_response def get_by_component_id(self, component_id): domain = get_action('domain') if isinstance(component_id, list): requested_apps = [Apps().find_by_component_id(cid) for cid in component_id] return domain.to_dict(requested_apps) else: app = Apps().find_by_component_id(component_id) if app: return domain.to_dict([app])[0] else: raise umcm.UMC_Error(_('Could not find an application for %s') % component_id) # used in updater-umc @simple_response def app_updates(self): upgrade = get_action('upgrade') domain = get_action('domain') return domain.to_dict(list(upgrade.iter_upgradable_apps())) @sanitize(application=StringSanitizer(minimum=1, required=True)) @simple_response def get(self, application): domain = get_action('domain') app = Apps().find(application) if app is None: raise umcm.UMC_Error(_('Could not find an application for %s') % (application,)) return domain.to_dict([app])[0] @sanitize(app=AppSanitizer(required=True), values=DictSanitizer({})) @simple_response def configure(self, app, autostart, values): configure = get_action('configure') configure.call(app=app, set_vars=values, autostart=autostart) @sanitize(app=AppSanitizer(required=True), mode=ChoicesSanitizer(['start', 'stop'])) @simple_response def app_service(self, app, mode): service = get_action(mode) service.call(app=app) @sanitize(app=AppSanitizer(required=False), action=ChoicesSanitizer(['get', 'buy', 'search']), value=StringSanitizer()) @simple_response def track(self, app, action, value): send_information(action, app=app, value=value) def invoke_dry_run(self, request): request.options['only_dry_run'] = True self.invoke(request) @require_password @sanitize( host=StringSanitizer(required=True), function=ChoicesSanitizer(['install', 'update', 'uninstall'], required=True), app=StringSanitizer(required=True), force=BooleanSanitizer(), values=DictSanitizer({}) ) @simple_response(with_progress=True) def invoke_remote_docker(self, host, function, app, force, values, progress): options = {'function': function, 'app': app, 'force': force, 'values': values} client = Client(host, self.username, self.password) result = client.umc_command('appcenter/docker/invoke', options).result self._remote_progress[progress.id] = client, result['id'] @simple_response def remote_progress(self, progress_id): try: client, remote_progress_id = self._remote_progress[progress_id] except KeyError: # actually happens: before invoke_remote_docker is finished, remote_progress is already called return {} else: return client.umc_command('appcenter/docker/progress', {'progress_id': remote_progress_id}).result @require_apps_update @require_password @sanitize( function=MappingSanitizer({ 'install': 'install', 'update': 'upgrade', 'uninstall': 'remove', }, required=True), app=AppSanitizer(required=True), force=BooleanSanitizer(), values=DictSanitizer({}) ) @simple_response(with_progress=True) def invoke_docker(self, function, app, force, values, progress): if function == 'upgrade': app = Apps().find_candidate(app) serious_problems = False progress.title = _('%s: Running tests') % (app.name,) errors, warnings = app.check(function) can_continue = force # "dry_run" if errors: MODULE.process('Cannot %s %s: %r' % (function, app.id, errors)) serious_problems = True can_continue = False if warnings: MODULE.process('Warning trying to %s %s: %r' % (function, app.id, warnings)) result = { 'serious_problems': serious_problems, 'invokation_forbidden_details': errors, 'invokation_warning_details': warnings, 'can_continue': can_continue, 'software_changes_computed': False, } if can_continue: with self.locked(): kwargs = {'noninteractive': True, 'skip_checks': ['shall_have_enough_ram', 'shall_only_be_installed_in_ad_env_with_password_service', 'must_not_have_concurrent_operation']} if function == 'install': progress.title = _('Installing %s') % (app.name,) kwargs['set_vars'] = values elif function == 'uninstall': progress.title = _('Uninstalling %s') % (app.name,) elif function == 'upgrade': progress.title = _('Upgrading %s') % (app.name,) action = get_action(function) handler = UMCProgressHandler(progress) handler.setLevel(logging.INFO) action.logger.addHandler(handler) try: result['success'] = action.call(app=app, username=self.username, password=self.password, **kwargs) except AppCenterError as exc: raise umcm.UMC_Error(str(exc), result=dict( display_feedback=True, title='%s %s' % (exc.title, exc.info))) finally: action.logger.removeHandler(handler) return result @contextmanager def locked(self): try: with self.package_manager.locked(reset_status=True, set_finished=True): yield except LockError: raise umcm.UMC_Error(_('Another package operation is in progress')) @require_apps_update @require_password @sanitize( function=ChoicesSanitizer(['install', 'uninstall', 'update', 'install-schema', 'update-schema'], required=True), application=StringSanitizer(minimum=1, required=True), force=BooleanSanitizer(), host=StringSanitizer(), only_dry_run=BooleanSanitizer(), dont_remote_install=BooleanSanitizer(), values=DictSanitizer({}) ) def invoke(self, request): # ATTENTION!!!!!!! # this function has to stay compatible with the very first App Center installations (Dec 2012) # if you add new arguments that change the behaviour # you should add a new method (see invoke_dry_run) or add a function name (e.g. install-schema) # this is necessary because newer app center may talk remotely with older one # that does not understand new arguments and behaves the old way (in case of # dry_run: install application although they were asked to dry_run) host = request.options.get('host') function = request.options.get('function') send_as = function if function.startswith('install'): function = 'install' if function.startswith('update'): function = 'update' application_id = request.options.get('application') Application.all(only_local=True) # if not yet cached, cache. but use only local inis application = Application.find(application_id) if application is None: raise umcm.UMC_Error(_('Could not find an application for %s') % (application_id,)) force = request.options.get('force') only_dry_run = request.options.get('only_dry_run') dont_remote_install = request.options.get('dont_remote_install') only_master_packages = send_as.endswith('schema') MODULE.process('Try to %s (%s) %s on %s. Force? %r. Only master packages? %r. Prevent installation on other systems? %r. Only dry run? %r.' % (function, send_as, application_id, host, force, only_master_packages, dont_remote_install, only_dry_run)) # REMOTE invocation! if host and host != self.ucr.get('hostname'): try: client = Client(host, self.username, self.password) result = client.umc_command('appcenter/invoke', request.options).result except (ConnectionError, HTTPError) as exc: MODULE.error('Error during remote appcenter/invoke: %s' % (exc,)) result = { 'unreachable': [host], 'master_unreachable': True, 'serious_problems': True, 'software_changes_computed': True, # not really... } else: if result['can_continue']: def _thread_remote(_client, _package_manager): with _package_manager.locked(reset_status=True, set_finished=True): _package_manager.unlock() # not really locked locally, but busy, so "with locked()" is appropriate Application._query_remote_progress(_client, _package_manager) def _finished_remote(thread, result): if isinstance(result, BaseException): MODULE.warn('Exception during %s %s: %s' % (function, application_id, str(result))) thread = notifier.threads.Simple('invoke', notifier.Callback(_thread_remote, client, self.package_manager), _finished_remote) thread.run() self.finished(request.id, result) return # make sure that the application can be installed/updated can_continue = True delayed_can_continue = True serious_problems = False result = { 'install': [], 'remove': [], 'broken': [], 'unreachable': [], 'master_unreachable': False, 'serious_problems': False, 'hosts_info': {}, 'problems_with_hosts': False, 'serious_problems_with_hosts': False, 'invokation_forbidden_details': {}, 'invokation_warning_details': {}, 'software_changes_computed': False, } if not application: MODULE.process('Application not found: %s' % application_id) can_continue = False if can_continue and not only_master_packages: forbidden, warnings = application.check_invokation(function, self.package_manager) if forbidden: MODULE.process('Cannot %s %s: %r' % (function, application_id, forbidden)) result['invokation_forbidden_details'] = forbidden can_continue = False serious_problems = True if warnings: MODULE.process('Warning trying to %s %s: %r' % (function, application_id, forbidden)) result['invokation_warning_details'] = warnings if not force: # dont stop "immediately". # compute the package changes! delayed_can_continue = False result['serious_problems'] = serious_problems result['can_continue'] = can_continue try: if can_continue: if self._working(): # make it multi-tab safe (same session many buttons to be clicked) raise LockError() with self.package_manager.locked(reset_status=True): previously_registered_by_dry_run = False if can_continue and function in ('install', 'update'): remove_component = only_dry_run dry_run_result, previously_registered_by_dry_run = application.install_dry_run(self.package_manager, self.component_manager, remove_component=remove_component, username=self._username, password=self.password, only_master_packages=only_master_packages, dont_remote_install=dont_remote_install, function=function, force=force) result.update(dry_run_result) result['software_changes_computed'] = True serious_problems = bool(result['broken'] or result['master_unreachable'] or result['serious_problems_with_hosts']) if serious_problems or (not force and (result['unreachable'] or result['install'] or result['remove'] or result['problems_with_hosts'])): MODULE.process('Problems encountered or confirmation required. Removing component %s' % application.component_id) if not remove_component: # component was not removed automatically after dry_run if application.candidate: # operation on candidate failed. re-register original application application.register(self.component_manager, self.package_manager) else: # operation on self failed. unregister all application.unregister_all_and_register(None, self.component_manager, self.package_manager) can_continue = False elif can_continue and function in ('uninstall',) and not force: result['remove'] = application.uninstall_dry_run(self.package_manager) result['software_changes_computed'] = True can_continue = False can_continue = can_continue and delayed_can_continue and not only_dry_run result['serious_problems'] = serious_problems result['can_continue'] = can_continue if can_continue and not only_dry_run: def _thread(module, application, function): with module.package_manager.locked(set_finished=True): with module.package_manager.no_umc_restart(exclude_apache=True): if function in ('install', 'update'): # dont have to add component: already added during dry_run return application.install(module.package_manager, module.component_manager, add_component=only_master_packages, send_as=send_as, username=self._username, password=self.password, only_master_packages=only_master_packages, dont_remote_install=dont_remote_install, previously_registered_by_dry_run=previously_registered_by_dry_run) else: return application.uninstall(module.package_manager, module.component_manager, self._username, self.password) def _finished(thread, result): if isinstance(result, BaseException): MODULE.warn('Exception during %s %s: %s' % (function, application_id, str(result))) thread = notifier.threads.Simple('invoke', notifier.Callback(_thread, self, application, function), _finished) thread.run() else: self.package_manager.set_finished() # nothing to do, ready to take new commands self.finished(request.id, result) except LockError: # make it thread safe: another process started a package manager # this module instance already has a running package manager raise umcm.UMC_Error(_('Another package operation is in progress')) def keep_alive(self, request): ''' Fix for Bug #30611: UMC kills appcenter module if no request is sent for $(ucr get umc/module/timeout). this happens if a user logs out during a very long installation. this function will be run by the frontend to always have one connection open to prevent killing the module. ''' def _thread(): while not self.package_manager.progress_state._finished: time.sleep(1) def _finished(thread, result): success = not isinstance(result, BaseException) if not success: MODULE.warn('Exception during keep_alive: %s' % result) self.finished(request.id, success) thread = notifier.threads.Simple('keep_alive', notifier.Callback(_thread), _finished) thread.run() @simple_response def ping(self): return True @simple_response def buy(self, application): app = Apps().find(application) if not app or not app.shop_url: return None ret = {} ret['key_id'] = LICENSE.uuid ret['ucs_version'] = self.ucr.get('version/version') ret['app_id'] = app.id ret['app_version'] = app.version # ret['locale'] = locale.getlocale()[0] # done by frontend ret['user_count'] = None # FIXME: get users and computers from license ret['computer_count'] = None return ret @simple_response def enable_disable_app(self, application, enable=True): app = Apps().find(application) if not app: return stall = get_action('stall') stall.call(app=app, undo=enable) @simple_response def packages_sections(self): """ fills the 'sections' combobox in the search form """ sections = set() cache = apt.Cache() for package in cache: sections.add(package.section) return sorted(sections) @sanitize(pattern=PatternSanitizer(required=True)) @simple_response def packages_query(self, pattern, section='all', key='package'): """ Query to fill the grid. Structure is fixed here. """ result = [] for package in self.package_manager.packages(reopen=True): if section == 'all' or package.section == section: toshow = False if pattern.pattern == '^.*$': toshow = True elif key == 'package' and pattern.search(package.name): toshow = True elif key == 'description' and package.candidate and pattern.search(package.candidate.raw_description): toshow = True if toshow: result.append(self._package_to_dict(package, full=False)) return result @simple_response def packages_get(self, package): """ retrieves full properties of one package """ package = self.package_manager.get_package(package) if package is not None: return self._package_to_dict(package, full=True) else: # TODO: 404? return {} @sanitize( function=MappingSanitizer({ 'install': 'install', 'upgrade': 'install', 'uninstall': 'remove', }, required=True), packages=ListSanitizer(StringSanitizer(minimum=1), required=True), update=BooleanSanitizer() ) @simple_response def packages_invoke_dry_run(self, packages, function, update): if update: self.package_manager.update() packages = self.package_manager.get_packages(packages) kwargs = {'install': [], 'remove': [], 'dry_run': True} if function == 'install': kwargs['install'] = packages else: kwargs['remove'] = packages return dict(zip(['install', 'remove', 'broken'], self.package_manager.mark(**kwargs))) @sanitize( function=MappingSanitizer({ 'install': 'install', 'upgrade': 'install', 'uninstall': 'remove', }, required=True), packages=ListSanitizer(StringSanitizer(minimum=1), required=True) ) def packages_invoke(self, request): """ executes an installer action """ packages = request.options.get('packages') function = request.options.get('function') try: if self._working(): # make it multi-tab safe (same session many buttons to be clicked) raise LockError() with self.package_manager.locked(reset_status=True): not_found = [pkg_name for pkg_name in packages if self.package_manager.get_package(pkg_name) is None] self.finished(request.id, {'not_found': not_found}) if not not_found: def _thread(package_manager, function, packages): with package_manager.locked(set_finished=True): with package_manager.no_umc_restart(exclude_apache=True): if function == 'install': package_manager.install(*packages) else: package_manager.uninstall(*packages) def _finished(thread, result): if isinstance(result, BaseException): MODULE.warn('Exception during %s %s: %r' % (function, packages, str(result))) thread = notifier.threads.Simple('invoke', notifier.Callback(_thread, self.package_manager, function, packages), _finished) thread.run() else: self.package_manager.set_finished() # nothing to do, ready to take new commands except LockError: # make it thread safe: another process started a package manager # this module instance already has a running package manager raise umcm.UMC_Error(_('Another package operation is in progress')) def _working(self): return not self.package_manager.progress_state._finished @simple_response def working(self): # TODO: PackageManager needs is_idle() or something # preferably the package_manager can tell what is currently executed: # package_manager.is_working() => False or _('Installing UCC') return self._working() @simple_response def custom_progress(self): timeout = 5 return self.package_manager.poll(timeout) def _package_to_dict(self, package, full): """ Helper that extracts properties from a 'apt_pkg.Package' object and stores them into a dictionary. Depending on the 'full' switch, stores only limited (for grid display) or full (for detail view) set of properties. """ installed = package.installed # may be None found = True candidate = package.candidate found = candidate is not None if not found: candidate = NoneCandidate() result = { 'package': package.name, 'installed': package.is_installed, 'upgradable': package.is_upgradable and found, 'summary': candidate.summary, } # add (and translate) a combined status field # *** NOTE *** we translate it here: if we would use the Custom Formatter # of the grid then clicking on the sort header would not work. if package.is_installed: if package.is_upgradable: result['status'] = _('upgradable') else: result['status'] = _('installed') else: result['status'] = _('not installed') # additional fields needed for detail view if full: # Some fields differ depending on whether the package is installed or not: if package.is_installed: result['section'] = installed.section result['priority'] = installed.priority or '' result['summary'] = installed.summary # take the current one result['description'] = installed.description result['installed_version'] = installed.version result['size'] = installed.installed_size if package.is_upgradable: result['candidate_version'] = candidate.version else: del result['upgradable'] # not installed: don't show 'upgradable' at all result['section'] = candidate.section result['priority'] = candidate.priority or '' result['description'] = candidate.description result['size'] = candidate.installed_size result['candidate_version'] = candidate.version # format size to handle bytes size = result['size'] byte_mods = ['B', 'kB', 'MB'] for byte_mod in byte_mods: if size < 10000: break size = float(size) / 1000 # MB, not MiB else: size = size * 1000 # once too often if size == int(size): format_string = '%d %s' else: format_string = '%.2f %s' result['size'] = format_string % (size, byte_mod) return result @simple_response def components_query(self): """ Returns components list for the grid in the ComponentsPage. """ # be as current as possible. self.uu.ucr_reinit() self.ucr.load() result = [] for comp in self.uu.get_all_components(): result.append(self.component_manager.component(comp)) return result @sanitize_list(StringSanitizer()) @multi_response(single_values=True) def components_get(self, iterator, component_id): # be as current as possible. self.uu.ucr_reinit() self.ucr.load() for component_id in iterator: yield self.component_manager.component(component_id) @sanitize_list(DictSanitizer({'object': advanced_components_sanitizer})) @multi_response def components_put(self, iterator, object): """Writes back one or more component definitions. """ # umc.widgets.Form wraps the real data into an array: # # [ # { # 'object' : { ... a dict with the real data .. }, # 'options': None # }, # ... more such entries ... # ] # # Current approach is to return a similarly structured array, # filled with elements, each one corresponding to one array # element of the request: # # [ # { # 'status' : a number where 0 stands for success, anything else # is an error code # 'message' : a result message # 'object' : a dict of field -> error message mapping, allows # the form to show detailed error information # }, # ... more such entries ... # ] with util.set_save_commit_load(self.ucr) as super_ucr: for object, in iterator: yield self.component_manager.put(object, super_ucr) self.package_manager.update() # do the same as components_put (update) # but dont allow adding an already existing entry components_add = sanitize_list(DictSanitizer({'object': add_components_sanitizer}))(components_put) components_add.__name__ = 'components_add' @sanitize_list(StringSanitizer()) @multi_response(single_values=True) def components_del(self, iterator, component_id): for component_id in iterator: yield self.component_manager.remove(component_id) self.package_manager.update() @multi_response def settings_get(self, iterator): # *** IMPORTANT *** Our UCR copy must always be current. This is not only # to catch up changes made via other channels (ucr command line etc), # but also to reflect the changes we have made ourselves! self.ucr.load() for _ in iterator: yield { 'unmaintained': self.ucr.is_true('repository/online/unmaintained', False), 'server': self.ucr.get('repository/online/server', ''), 'prefix': self.ucr.get('repository/online/prefix', ''), } @sanitize_list( DictSanitizer({'object': basic_components_sanitizer}), min_elements=1, max_elements=1 # moduleStore with one element... ) @multi_response def settings_put(self, iterator, object): # FIXME: returns values although it should yield (multi_response) changed = False # Set values into our UCR copy. try: with util.set_save_commit_load(self.ucr) as super_ucr: for object, in iterator: for key, value in object.iteritems(): MODULE.info(" ++ Setting new value for '%s' to '%s'" % (key, value)) super_ucr.set_registry_var('%s/%s' % (constants.ONLINE_BASE, key), value) changed = super_ucr.changed() except Exception as e: MODULE.warn(" !! Writing UCR failed: %s" % str(e)) return [{'message': str(e), 'status': constants.PUT_WRITE_ERROR}] self.package_manager.update() # Bug #24878: emit a warning if repository is not reachable try: updater = self.uu for line in updater.print_version_repositories().split('\n'): if line.strip(): break else: raise ConfigurationError() except ConfigurationError: msg = _("There is no repository at this server (or at least none for the current UCS version)") MODULE.warn(" !! Updater error: %s" % msg) response = {'message': msg, 'status': constants.PUT_UPDATER_ERROR} # if nothing was committed, we want a different type of error code, # just to appropriately inform the user if changed: response['status'] = constants.PUT_UPDATER_NOREPOS return [response] except: info = sys.exc_info() emsg = '%s: %s' % info[:2] MODULE.warn(" !! Updater error [%s]: %s" % (emsg)) return [{'message': str(info[1]), 'status': constants.PUT_UPDATER_ERROR}] return [{'status': constants.PUT_SUCCESS}]
class Instance(umcm.Base): def init(self): self.ucr = univention.config_registry.ConfigRegistry() self.ucr.load() util.install_opener(self.ucr) self.package_manager = PackageManager( info_handler=MODULE.process, step_handler=None, error_handler=MODULE.warn, lock=False, always_noninteractive=True, ) self.uu = UniventionUpdater(False) self.component_manager = util.ComponentManager(self.ucr, self.uu) # in order to set the correct locale for Application locale.setlocale(locale.LC_ALL, str(self.locale)) @sanitize(email=EmailSanitizer(required=True)) @simple_response def request_new_license(self, email): license = LICENSE.dump_data() if license is None: raise umcm.UMC_CommandError(_('Cannot parse License from LDAP')) data = {} data['email'] = email data['licence'] = license data = urllib.urlencode(data) url = 'https://license.univention.de/keyid/conversion/submit' request = urllib2.Request(url, data=data, headers={'User-agent': 'UMC/AppCenter'}) try: util.urlopen(request) except Exception as e: try: # try to parse an html error body = e.read() detail = re.search( '<span id="details">(?P<details>.*?)</span>', body).group(1) except: detail = str(e) raise umcm.UMC_CommandError( _('An error occurred while sending the request: %s') % detail) else: return True @sanitize(pattern=PatternSanitizer(default='.*')) @simple_response def query(self, pattern): LICENSE.reload() try: applications = Application.all(force_reread=True) except (urllib2.HTTPError, urllib2.URLError) as e: raise umcm.UMC_CommandError( _('Could not query App Center: %s') % e) result = [] self.package_manager.reopen_cache() for application in applications: if pattern.search(application.name): props = application.to_dict(self.package_manager) # delete larger entries for ikey in ('readmeupdate', 'licenseagreement'): if ikey in props: del props[ikey] result.append(props) return result @sanitize(application=StringSanitizer(minimum=1, required=True)) @simple_response def get(self, application): LICENSE.reload() application = Application.find(application) self.package_manager.reopen_cache() return application.to_dict(self.package_manager) @sanitize(function=ChoicesSanitizer(['install', 'uninstall', 'update'], required=True), application=StringSanitizer(minimum=1, required=True), force=BooleanSanitizer()) def invoke(self, request): function = request.options.get('function') application_id = request.options.get('application') application = Application.find(application_id) force = request.options.get('force') try: # make sure that the application cane be installed/updated can_continue = True result = { 'install': [], 'remove': [], 'broken': [], } if not application: MODULE.info('Application not found: %s' % application_id) can_continue = False elif function == 'install' and not application.can_be_installed( self.package_manager): MODULE.info('Application cannot be installed: %s' % application_id) can_continue = False elif function == 'update' and not application.can_be_updated(): MODULE.info('Application cannot be updated: %s' % application_id) can_continue = False if can_continue and function in ('install', 'update'): result = application.install_dry_run(self.package_manager, self.component_manager, remove_component=False) if result['broken'] or (result['remove'] and not force): MODULE.info('Remove component: %s' % application_id) self.component_manager.remove_app(application) self.package_manager.update() can_continue = False elif can_continue and function in ('uninstall', ) and not force: result['remove'] = application.uninstall_dry_run( self.package_manager) can_continue = False result['can_continue'] = can_continue self.finished(request.id, result) if can_continue: def _thread(module, application, function): with module.package_manager.locked(reset_status=True, set_finished=True): with module.package_manager.no_umc_restart(): if function in ('install', 'update'): # dont have to add component: already added during dry_run return application.install( module.package_manager, module.component_manager, add_component=False) else: return application.uninstall( module.package_manager, module.component_manager) def _finished(thread, result): if isinstance(result, BaseException): MODULE.warn('Exception during %s %s: %s' % (function, application_id, str(result))) thread = notifier.threads.Simple( 'invoke', notifier.Callback(_thread, self, application, function), _finished) thread.run() except LockError: # make it thread safe: another process started a package manager # this module instance already has a running package manager raise umcm.UMC_CommandError( _('Another package operation is in progress')) @simple_response def app_center_app_license(self, application): application = Application.find(application) if not application or not application.get('licensefile'): raise umcm.UMC_CommandError( _('No license file available for application: %s') % (application.id)) # open the license file and replace line breaks with BR-tags fp = util.urlopen(application.get('licensefile')) txt = ''.join(fp.readlines()).strip() txt = txt.replace('\n\n\n', '\n<br>\n<br>\n<br>\n') txt = txt.replace('\n\n', '\n<br>\n<br>\n') return txt @simple_response def packages_sections(self): """ fills the 'sections' combobox in the search form """ sections = set() for package in self.package_manager.packages(): sections.add(package.section) return sorted(sections) @sanitize(pattern=PatternSanitizer(required=True)) @simple_response def packages_query(self, pattern, section='all', key='package'): """ Query to fill the grid. Structure is fixed here. """ result = [] for package in self.package_manager.packages(): if section == 'all' or package.section == section: toshow = False if pattern.pattern == '^.*$': toshow = True elif key == 'package' and pattern.search(package.name): toshow = True elif key == 'description' and pattern.search( package.candidate.raw_description): toshow = True if toshow: result.append(self._package_to_dict(package, full=False)) return result @simple_response def packages_get(self, package): """ retrieves full properties of one package """ package = self.package_manager.get_package(package) if package is not None: return self._package_to_dict(package, full=True) else: # TODO: 404? return {} @sanitize(function=MappingSanitizer( { 'install': 'install', 'upgrade': 'install', 'uninstall': 'remove', }, required=True), packages=ListSanitizer(StringSanitizer(minimum=1), required=True)) @simple_response def packages_invoke_dry_run(self, packages, function): packages = self.package_manager.get_packages(packages) kwargs = {'install': [], 'remove': [], 'dry_run': True} if function == 'install': kwargs['install'] = packages else: kwargs['remove'] = packages return dict( zip(['install', 'remove', 'broken'], self.package_manager.mark(**kwargs))) @sanitize(function=MappingSanitizer( { 'install': 'install', 'upgrade': 'install', 'uninstall': 'remove', }, required=True), packages=ListSanitizer(StringSanitizer(minimum=1), required=True)) def packages_invoke(self, request): """ executes an installer action """ packages = request.options.get('packages') function = request.options.get('function') try: with self.package_manager.locked(reset_status=True): not_found = [ pkg_name for pkg_name in packages if self.package_manager.get_package(pkg_name) is None ] self.finished(request.id, {'not_found': not_found}) if not not_found: def _thread(package_manager, function, packages): with package_manager.locked(set_finished=True): with package_manager.no_umc_restart(): if function == 'install': package_manager.install(*packages) else: package_manager.uninstall(*packages) def _finished(thread, result): if isinstance(result, BaseException): MODULE.warn('Exception during %s %s: %r' % (function, packages, str(result))) thread = notifier.threads.Simple( 'invoke', notifier.Callback(_thread, self.package_manager, function, packages), _finished) thread.run() except LockError: # make it thread safe: another process started a package manager # this module instance already has a running package manager raise umcm.UMC_CommandError( _('Another package operation is in progress')) @simple_response def progress(self): timeout = 5 return self.package_manager.poll(timeout) def _package_to_dict(self, package, full): """ Helper that extracts properties from a 'apt_pkg.Package' object and stores them into a dictionary. Depending on the 'full' switch, stores only limited (for grid display) or full (for detail view) set of properties. """ installed = package.installed # may be None candidate = package.candidate result = { 'package': package.name, 'installed': package.is_installed, 'upgradable': package.is_upgradable, 'summary': candidate.summary, } # add (and translate) a combined status field # *** NOTE *** we translate it here: if we would use the Custom Formatter # of the grid then clicking on the sort header would not work. if package.is_installed: if package.is_upgradable: result['status'] = _('upgradable') else: result['status'] = _('installed') else: result['status'] = _('not installed') # additional fields needed for detail view if full: result['section'] = package.section result['priority'] = package.priority # Some fields differ depending on whether the package is installed or not: if package.is_installed: result['summary'] = installed.summary # take the current one result['description'] = installed.description result['installed_version'] = installed.version result['size'] = installed.installed_size if package.is_upgradable: result['candidate_version'] = candidate.version else: del result[ 'upgradable'] # not installed: don't show 'upgradable' at all result['description'] = candidate.description result['size'] = candidate.installed_size result['candidate_version'] = candidate.version # format size to handle bytes size = result['size'] byte_mods = ['B', 'kB', 'MB'] for byte_mod in byte_mods: if size < 10000: break size = float(size) / 1000 # MB, not MiB else: size = size * 1000 # once too often if size == int(size): format_string = '%d %s' else: format_string = '%.2f %s' result['size'] = format_string % (size, byte_mod) return result @simple_response def components_query(self): """ Returns components list for the grid in the ComponentsPage. """ # be as current as possible. self.uu.ucr_reinit() self.ucr.load() result = [] for comp in self.uu.get_all_components(): result.append(self.component_manager.component(comp)) return result @sanitize_list(StringSanitizer()) @multi_response(single_values=True) def components_get(self, iterator, component_id): # be as current as possible. self.uu.ucr_reinit() self.ucr.load() for component_id in iterator: yield self.component_manager.component(component_id) @sanitize_list(DictSanitizer({'object': advanced_components_sanitizer})) @multi_response def components_put(self, iterator, object): """Writes back one or more component definitions. """ # umc.widgets.Form wraps the real data into an array: # # [ # { # 'object' : { ... a dict with the real data .. }, # 'options': None # }, # ... more such entries ... # ] # # Current approach is to return a similarly structured array, # filled with elements, each one corresponding to one array # element of the request: # # [ # { # 'status' : a number where 0 stands for success, anything else # is an error code # 'message' : a result message # 'object' : a dict of field -> error message mapping, allows # the form to show detailed error information # }, # ... more such entries ... # ] with util.set_save_commit_load(self.ucr) as super_ucr: for object, in iterator: yield self.component_manager.put(object, super_ucr) self.package_manager.update() # do the same as components_put (update) # but dont allow adding an already existing entry components_add = sanitize_list( DictSanitizer({'object': add_components_sanitizer}))(components_put) components_add.__name__ = 'components_add' @sanitize_list(StringSanitizer()) @multi_response(single_values=True) def components_del(self, iterator, component_id): for component_id in iterator: yield self.component_manager.remove(component_id) self.package_manager.update() @multi_response def settings_get(self, iterator): # *** IMPORTANT *** Our UCR copy must always be current. This is not only # to catch up changes made via other channels (ucr command line etc), # but also to reflect the changes we have made ourselves! self.ucr.load() for _ in iterator: yield { 'maintained': self.ucr.is_true('repository/online/maintained', False), 'unmaintained': self.ucr.is_true('repository/online/unmaintained', False), 'server': self.ucr.get('repository/online/server', ''), 'prefix': self.ucr.get('repository/online/prefix', ''), } @sanitize_list( DictSanitizer({'object': basic_components_sanitizer}), min_elements=1, max_elements=1 # moduleStore with one element... ) @multi_response def settings_put(self, iterator, object): # FIXME: returns values although it should yield (multi_response) changed = False # Set values into our UCR copy. try: with util.set_save_commit_load(self.ucr) as super_ucr: for object, in iterator: for key, value in object.iteritems(): MODULE.info( " ++ Setting new value for '%s' to '%s'" % (key, value)) super_ucr.set_registry_var( '%s/%s' % (constants.ONLINE_BASE, key), value) changed = super_ucr.changed() except Exception as e: MODULE.warn(" !! Writing UCR failed: %s" % str(e)) return [{'message': str(e), 'status': constants.PUT_WRITE_ERROR}] self.package_manager.update() # Bug #24878: emit a warning if repository is not reachable try: updater = self.uu for line in updater.print_version_repositories().split('\n'): if line.strip(): break else: raise ConfigurationError() except ConfigurationError: msg = _( "There is no repository at this server (or at least none for the current UCS version)" ) MODULE.warn(" !! Updater error: %s" % msg) response = {'message': msg, 'status': constants.PUT_UPDATER_ERROR} # if nothing was committed, we want a different type of error code, # just to appropriately inform the user if changed: response['status'] = constants.PUT_UPDATER_NOREPOS return [response] except: info = sys.exc_info() emsg = '%s: %s' % info[:2] MODULE.warn(" !! Updater error [%s]: %s" % (emsg)) return [{ 'message': str(info[1]), 'status': constants.PUT_UPDATER_ERROR }] return [{'status': constants.PUT_SUCCESS}]
class Instance(umcm.Base, ProgressMixin): def init(self): os.umask( 0o022 ) # umc umask is too restrictive for app center as it creates a lot of files in docker containers self.ucr = ucr_instance() self.update_applications_done = False install_opener(self.ucr) self._is_working = False try: self.package_manager = PackageManager( info_handler=MODULE.process, step_handler=None, error_handler=MODULE.warn, lock=False, ) except SystemError as exc: MODULE.error(str(exc)) raise umcm.UMC_Error(str(exc), status=500) self.package_manager.set_finished( ) # currently not working. accepting new tasks get_package_manager._package_manager = self.package_manager # build cache _update_modules() get_action('list').get_apps() # not initialize here: error prone due to network errors and also kinda slow self._uu = None self._cm = None # in order to set the correct locale locale.setlocale(locale.LC_ALL, str(self.locale)) try: log_to_logfile() except IOError: pass # connect univention.appcenter.log to the progress-method handler = ProgressInfoHandler(self.package_manager) handler.setLevel(logging.INFO) get_base_logger().addHandler(handler) percentage = ProgressPercentageHandler(self.package_manager) percentage.setLevel(logging.DEBUG) get_base_logger().getChild('actions.install.progress').addHandler( percentage) get_base_logger().getChild('actions.upgrade.progress').addHandler( percentage) get_base_logger().getChild('actions.remove.progress').addHandler( percentage) def get_updater(self): if self._uu is None: self._uu = UniventionUpdater(False) return self._uu def get_component_manager(self): if self._cm is None: self._cm = ComponentManager(self.ucr, self.get_updater()) return self._cm def error_handling(self, etype, exc, etraceback): error_handling(etype, exc, etraceback) return super(Instance, self).error_handling(exc, etype, etraceback) @simple_response def version(self, version=None): info = get_action('info') ret = info.get_compatibility() if not info.is_compatible(version): raise umcm.UMC_Error( 'The App Center version of the requesting host is not compatible with the version of %s (%s)' % (get_local_fqdn(), ret)) return ret @sanitize( version=StringSanitizer(required=True), function=StringSanitizer(required=False), ) @simple_response def version2(self, version, function=None): info = get_action('info') return { 'compatible': info.is_compatible(version, function=function), 'version': info.get_ucs_version() } def _remote_appcenter(self, host, function=None): if host is None: raise ValueError('Cannot connect to None') if not host.endswith('.%s' % self.ucr.get('domainname')): raise ValueError('Only connect to FQDNs within the domain') info = get_action('info') opts = {'version': info.get_ucs_version()} if function is not None: opts['function'] = function try: client = Client(host, self.username, self.password) response = client.umc_command('appcenter/version2', opts) except (HTTPError) as exc: raise umcm.UMC_Error( _('Problems connecting to {0} ({1}). Please update {0}!'). format(host, exc.message)) except (ConnectionError, Exception) as exc: raise umcm.UMC_Error( _('Problems connecting to {} ({}).').format(host, str(exc))) err_msg = _( 'The App Center version of the this host ({}) is not compatible with the version of {} ({})' ).format(opts['version'], host, response.result.get('version')) # i guess this is kind of bad if response.status != 200: raise umcm.UMC_Error(err_msg) # remote says he is not compatible if response.result.get('compatible', True) is False: raise umcm.UMC_Error(err_msg) # i'm not compatible if not info.is_compatible(response.result.get('version')): raise umcm.UMC_Error(err_msg) return client @sanitize(apps=ListSanitizer(AppSanitizer(), required=True), action=ChoicesSanitizer(['install', 'upgrade', 'remove'], required=True)) @simple_response def resolve(self, apps, action): ret = {} ret['apps'] = resolve_dependencies(apps, action) ret['auto_installed'] = [ app.id for app in ret['apps'] if app.id not in [a.id for a in apps] ] apps = ret['apps'] ret['errors'], ret['warnings'] = check(apps, action) domain = get_action('domain') ret['apps'] = domain.to_dict(apps) ret['settings'] = {} self.ucr.load() for app in apps: ret['settings'][app.id] = self._get_config(app, action.title()) return ret @require_apps_update @require_password @sanitize( apps=ListSanitizer(AppSanitizer(), required=True), auto_installed=ListSanitizer(required=True), action=ChoicesSanitizer(['install', 'upgrade', 'remove'], required=True), hosts=DictSanitizer({}, required=True), settings=DictSanitizer({}, required=True), dry_run=BooleanSanitizer(), ) @simple_response(with_progress=True) def run(self, progress, apps, auto_installed, action, hosts, settings, dry_run): localhost = get_local_fqdn() ret = {} if dry_run: for host in hosts: _apps = [ next(app for app in apps if app.id == _app) for _app in hosts[host] ] if host == localhost: ret[host] = self._run_local_dry_run( _apps, action, {}, progress) else: try: ret[host] = self._run_remote_dry_run( host, _apps, action, auto_installed, {}, progress) except umcm.UMC_Error: ret[host] = {'unreachable': [app.id for app in _apps]} else: for app in apps: for host in hosts: if app.id not in hosts[host]: continue host_result = ret.get(host, {}) ret[host] = host_result _settings = {app.id: settings[app.id]} if host == localhost: host_result[app.id] = self._run_local( app, action, _settings, auto_installed, progress) else: host_result[app.id] = self._run_remote( host, app, action, auto_installed, _settings, progress)[app.id] if not host_result[app.id]['success']: break return ret def _run_local_dry_run(self, apps, action, settings, progress): if action == 'upgrade': apps = [Apps().find_candidate(app) or app for app in apps] if len(apps) == 1: progress.title = _('%s: Running tests') % apps[0].name else: progress.title = _('%d Apps: Running tests') % len(apps) ret = {} ret['errors'], ret['warnings'] = check(apps, action) ret['errors'].pop('must_have_no_unmet_dependencies', None) # has to be resolved prior to this call! action = get_action(action)() ret['packages'] = {} for app in apps: args = action._build_namespace( app=[app], dry_run=True, install_master_packages_remotely=False, only_master_packages=False) result = action.dry_run(app, args) if result is not None: ret['packages'][app.id] = result return ret def _run_local(self, app, action, settings, auto_installed, progress): kwargs = { 'noninteractive': True, 'auto_installed': auto_installed, 'skip_checks': [ 'shall_have_enough_ram', 'shall_only_be_installed_in_ad_env_with_password_service', 'must_not_have_concurrent_operation' ], } if settings.get(app.id): kwargs['set_vars'] = settings[app.id] if action == 'install': progress.title = _('Installing %s') % (app.name, ) elif action == 'remove': progress.title = _('Uninstalling %s') % (app.name, ) elif action == 'upgrade': progress.title = _('Upgrading %s') % (app.name, ) action = get_action(action) handler = UMCProgressHandler(progress) handler.setLevel(logging.INFO) action.logger.addHandler(handler) try: package_manager = get_package_manager() with package_manager.no_umc_restart(exclude_apache=True): success = action.call(app=[app], username=self.username, password=self.password, **kwargs) return {'success': success} except AppCenterError as exc: raise umcm.UMC_Error(str(exc), result=dict(display_feedback=True, title='%s %s' % (exc.title, exc.info))) finally: action.logger.removeHandler(handler) def _run_remote_dry_run(self, host, apps, action, auto_installed, settings, progress): return self._run_remote_logic(host, apps, action, auto_installed, settings, progress, dry_run=True) def _run_remote(self, host, app, action, auto_installed, settings, progress): return self._run_remote_logic(host, [app], action, auto_installed, settings, progress, dry_run=False) def _run_remote_logic(self, host, apps, action, auto_installed, settings, progress, dry_run): if len(apps) == 1: progress.title = _('%s: Connecting to %s') % (apps[0].name, host) else: progress.title = _('%d Apps: Connecting to %s') % (len(apps), host) client = self._remote_appcenter(host, function='appcenter/run') opts = { 'apps': [str(app) for app in apps], 'auto_installed': auto_installed, 'action': action, 'hosts': { host: [app.id for app in apps] }, 'settings': settings, 'dry_run': dry_run } progress_id = client.umc_command('appcenter/run', opts).result['id'] while True: result = client.umc_command('appcenter/progress', { 'progress_id': progress_id }).result if result['finished']: return result['result'][host] progress.title = result['title'] progress.intermediate.extend(result['intermediate']) progress.message = result['message'] time.sleep(result['retry_after'] / 1000.0) @simple_response def query(self, quick=False): query_cache_file = '/var/cache/univention-appcenter/umc-query.json' if quick: try: with open(query_cache_file) as fd: return json.load(fd) except (EnvironmentError, ValueError) as exc: MODULE.error('Error returning cached query: %s' % exc) return [] self.update_applications() self.ucr.load() reload_package_manager() list_apps = get_action('list') domain = get_action('domain') apps = list_apps.get_apps() if self.ucr.is_true('appcenter/docker', True): if not self._test_for_docker_service(): raise umcm.UMC_Error( _('The docker service is not running! The App Center will not work properly.' ) + ' ' + _('Make sure docker.io is installed, try starting the service with "service docker start".' )) info = domain.to_dict(apps) with open(query_cache_file, 'w') as fd: json.dump(info, fd) return info def update_applications(self): if self.ucr.is_true('appcenter/umc/update/always', True): update = get_action('update') try: update.call() except NetworkError as err: raise umcm.UMC_Error(str(err)) except Abort: pass self.update_applications_done = True def _test_for_docker_service(self): if docker_bridge_network_conflict(): msg = _( 'A conflict between the system network settings and the docker bridge default network has been detected.' ) + '\n\n' msg += _( 'Please either configure a different network for the docker bridge by setting the UCR variable docker/daemon/default/opts/bip to a different network and restart the system,' ) + ' ' msg += _( 'or disable the docker support in the AppCenter by setting appcenter/docker to false.' ) raise umcm.UMC_Error(msg) if not docker_is_running(): MODULE.warn('Docker is not running! Trying to start it now...') call_process(['invoke-rc.d', 'docker', 'start']) if not docker_is_running(): return False return True @simple_response def suggestions(self, version): try: cache = AppCenterCache.build(server=default_server()) cache_file = cache.get_cache_file('.suggestions.json') with open(cache_file) as fd: json = load(fd) except (EnvironmentError, ValueError): raise umcm.UMC_Error(_('Could not load suggestions.')) else: try: return json[version] except (KeyError, AttributeError): raise umcm.UMC_Error(_('Unexpected suggestions data.')) @simple_response def enable_docker(self): if self._test_for_docker_service(): ucr_save({'appcenter/docker': 'enabled'}) else: raise umcm.UMC_Error( _('Unable to start the docker service!') + ' ' + _('Make sure docker.io is installed, try starting the service with "service docker start".' )) @require_apps_update @require_password @simple_response(with_progress=True) def sync_ldap(self): register = get_action('register') register.call(username=self.username, password=self.password) # used in updater-umc @simple_response def get_by_component_id(self, component_id): domain = get_action('domain') if isinstance(component_id, list): requested_apps = [ Apps().find_by_component_id(cid) for cid in component_id ] return domain.to_dict(requested_apps) else: app = Apps().find_by_component_id(component_id) if app: return domain.to_dict([app])[0] else: raise umcm.UMC_Error( _('Could not find an application for %s') % component_id) # used in updater-umc @simple_response def app_updates(self): upgrade = get_action('upgrade') domain = get_action('domain') return domain.to_dict(list(upgrade.iter_upgradable_apps())) @sanitize(application=StringSanitizer(minimum=1, required=True)) @simple_response def get(self, application): list_apps = get_action('list') domain = get_action('domain') apps = list_apps.get_apps() for app in apps: if app.id == application: break else: app = None if app is None: raise umcm.UMC_Error( _('Could not find an application for %s') % (application, )) return domain.to_dict([app])[0] @sanitize(app=AppSanitizer(required=True)) @simple_response def config(self, app, phase): self.ucr.load() return self._get_config(app, phase) def _get_config(self, app, phase): autostart = self.ucr.get('%s/autostart' % app.id, 'yes') is_running = app_is_running(app) values = {} for setting in app.get_settings(): if phase in setting.show or phase in setting.show_read_only: value = setting.get_value(app, phase) if isinstance(setting, FileSetting) and not isinstance( setting, PasswordFileSetting): if value: value = b64encode( value.encode('utf-8')).decode('ascii') values[setting.name] = value return { 'autostart': autostart, 'is_running': is_running, 'values': values, } @sanitize(app=AppSanitizer(required=True), values=DictSanitizer({})) @simple_response(with_progress=True) def configure(self, progress, app, values, autostart=None): for setting in app.get_settings(): if isinstance(setting, FileSetting) and not isinstance( setting, PasswordFileSetting): if values.get(setting.name): values[setting.name] = b64decode( values[setting.name]).decode('utf-8') configure = get_action('configure') handler = UMCProgressHandler(progress) handler.setLevel(logging.INFO) configure.logger.addHandler(handler) try: return configure.call(app=app, set_vars=values, autostart=autostart) finally: configure.logger.removeHandler(handler) @sanitize(app=AppSanitizer(required=True), mode=ChoicesSanitizer(['start', 'stop'])) @simple_response def app_service(self, app, mode): service = get_action(mode) service.call(app=app) @sanitize(app=AppSanitizer(required=False), action=ChoicesSanitizer(['get', 'buy', 'search', 'vote']), value=StringSanitizer()) @simple_response def track(self, app, action, value): send_information(action, app=app, value=value) @contextmanager def locked(self): try: if self._working(): raise LockError() with package_lock(): yield except LockError: raise umcm.UMC_Error(_('Another package operation is in progress')) def _install_master_packages_on_hosts(self, app, function): if function.startswith('upgrade'): remote_function = 'update-schema' else: remote_function = 'install-schema' master_packages = app.default_packages_master if not master_packages: return hosts = find_hosts_for_master_packages() all_hosts_count = len(hosts) package_manager = get_package_manager() package_manager.set_max_steps( all_hosts_count * 200) # up to 50% if all hosts are installed # maybe we already installed local packages (on master) if self.ucr.get('server/role') == 'domaincontroller_master': # TODO: set_max_steps should reset _start_steps. need function like set_start_steps() package_manager.progress_state._start_steps = all_hosts_count * 100 for host, host_is_master in hosts: package_manager.progress_state.info( _('Installing LDAP packages on %s') % host) try: if not self._install_master_packages_on_host( app, remote_function, host): error_message = 'Unable to install %r on %s. Check /var/log/univention/management-console-module-appcenter.log on the host and this server. All errata updates have been installed on %s?' % ( master_packages, host, host) raise Exception(error_message) except Exception as e: MODULE.error('%s: %s' % (host, e)) if host_is_master: role = 'Primary Directory Node' else: role = 'Backup Directory Node' # ATTENTION: This message is not localised. It is parsed by the frontend to markup this message! If you change this message, be sure to do the same in AppCenterPage.js package_manager.progress_state.error( 'Installing extension of LDAP schema for %s seems to have failed on %s %s' % (app.component_id, role, host)) if host_is_master: raise # only if host_is_master! finally: package_manager.add_hundred_percent() def _install_master_packages_on_host(self, app, function, host): client = Client(host, self.username, self.password) result = client.umc_command( 'appcenter/invoke', { 'function': function, 'application': app.id, 'force': True, 'dont_remote_install': True }).result if result['can_continue']: all_errors = self._query_remote_progress(client) return len(all_errors) == 0 else: MODULE.warn('%r' % result) return False def _install_dry_run_remote(self, app, function, dont_remote_install, force): MODULE.process('Invoke install_dry_run_remote') self.ucr.load() if function.startswith('upgrade'): remote_function = 'update-schema' else: remote_function = 'install-schema' master_packages = app.default_packages_master # connect to Primary/Backup Nodes unreachable = [] hosts_info = {} remote_info = { 'master_unreachable': False, 'problems_with_hosts': False, 'serious_problems_with_hosts': False, } dry_run_threads = [] info = get_action('info') if master_packages and not dont_remote_install: hosts = find_hosts_for_master_packages() # checking remote host is I/O heavy, so use threads # "global" variables: unreachable, hosts_info, remote_info def _check_remote_host(app_id, host, host_is_master, username, password, force, remote_function): MODULE.process('Starting dry_run for %s on %s' % (app_id, host)) MODULE.process('%s: Connecting...' % host) try: client = Client(host, username, password) except (ConnectionError, HTTPError) as exc: MODULE.warn('_check_remote_host: %s: %s' % (host, exc)) unreachable.append(host) if host_is_master: remote_info['master_unreachable'] = True else: MODULE.process('%s: ... done' % host) host_info = {} MODULE.process('%s: Getting version...' % host) try: host_version = client.umc_command( 'appcenter/version', { 'version': info.get_compatibility() }).result except Forbidden: # command is not yet known (older app center) MODULE.process('%s: ... forbidden!' % host) host_version = None except (ConnectionError, HTTPError) as exc: MODULE.warn('%s: Could not get appcenter/version: %s' % (host, exc)) raise except Exception as exc: MODULE.error('%s: Exception: %s' % (host, exc)) raise MODULE.process('%s: ... done' % host) host_info['compatible_version'] = info.is_compatible( host_version) MODULE.process('%s: Invoking %s ...' % (host, remote_function)) try: host_info['result'] = client.umc_command( 'appcenter/invoke_dry_run', { 'function': remote_function, 'application': app_id, 'force': force, 'dont_remote_install': True, }).result except Forbidden: # command is not yet known (older app center) MODULE.process('%s: ... forbidden!' % host) host_info['result'] = { 'can_continue': False, 'serious_problems': False } except (ConnectionError, HTTPError) as exc: MODULE.warn('Could not get appcenter/version: %s' % (exc, )) raise MODULE.process('%s: ... done' % host) if not host_info['compatible_version'] or not host_info[ 'result']['can_continue']: remote_info['problems_with_hosts'] = True if host_info['result'][ 'serious_problems'] or not host_info[ 'compatible_version']: remote_info['serious_problems_with_hosts'] = True hosts_info[host] = host_info MODULE.process('Finished dry_run for %s on %s' % (app_id, host)) for host, host_is_master in hosts: thread = Thread(target=_check_remote_host, args=(app.id, host, host_is_master, self.username, self.password, force, remote_function)) thread.start() dry_run_threads.append(thread) result = {} for thread in dry_run_threads: thread.join() MODULE.process('All %d threads finished' % (len(dry_run_threads))) result['unreachable'] = unreachable result['hosts_info'] = hosts_info result.update(remote_info) return result def _query_remote_progress(self, client): all_errors = set() number_failures = 0 number_failures_max = 20 host = client.hostname while True: try: result = client.umc_command('appcenter/progress').result except (ConnectionError, HTTPError) as exc: MODULE.warn('%s: appcenter/progress returned an error: %s' % (host, exc)) number_failures += 1 if number_failures >= number_failures_max: MODULE.error( '%s: Remote App Center cannot be contacted for more than %d seconds. Maybe just a long Apache Restart? Presume failure! Check logs on remote machine, maybe installation was successful.' % number_failures_max) return False time.sleep(1) continue else: # everything okay. reset "timeout" number_failures = 0 MODULE.info('Result from %s: %r' % (host, result)) info = result['info'] steps = result['steps'] errors = ['%s: %s' % (host, error) for error in result['errors']] if info: self.package_manager.progress_state.info(info) if steps: steps = float( steps ) # bug in package_manager in 3.1-0: int will result in 0 because of division and steps < max_steps self.package_manager.progress_state.percentage(steps) for error in errors: if error not in all_errors: self.package_manager.progress_state.error(error) all_errors.add(error) if result['finished'] is True: break time.sleep(0.1) return all_errors def keep_alive(self, request): ''' Fix for Bug #30611: UMC kills appcenter module if no request is sent for $(ucr get umc/module/timeout). this happens if a user logs out during a very long installation. this function will be run by the frontend to always have one connection open to prevent killing the module. ''' def _thread(): while self._working(): time.sleep(1) def _finished(thread, result): success = not isinstance(result, BaseException) if not success: MODULE.warn('Exception during keep_alive: %s' % result) self.finished(request.id, success) thread = notifier.threads.Simple('keep_alive', notifier.Callback(_thread), _finished) thread.run() @simple_response def ping(self): return True @simple_response def buy(self, application): app = Apps().find(application) if not app or not app.shop_url: return None ret = {} ret['key_id'] = self.ucr.get('license/uuid') ret['ucs_version'] = self.ucr.get('version/version') ret['app_id'] = app.id ret['app_version'] = app.version # ret['locale'] = locale.getlocale()[0] # done by frontend ret['user_count'] = None # FIXME: get users and computers from license ret['computer_count'] = None return ret @simple_response def enable_disable_app(self, application, enable=True): app = Apps().find(application) if not app: return stall = get_action('stall') stall.call(app=app, undo=enable) @simple_response def packages_sections(self): """ fills the 'sections' combobox in the search form """ sections = set() cache = apt.Cache() for package in cache: sections.add(package.section) return sorted(sections) @sanitize(pattern=PatternSanitizer(required=True)) @simple_response def packages_query(self, pattern, section='all', key='package'): """ Query to fill the grid. Structure is fixed here. """ result = [] for package in self.package_manager.packages(reopen=True): if section == 'all' or package.section == section: toshow = False if pattern.pattern == '^.*$': toshow = True elif key == 'package' and pattern.search(package.name): toshow = True elif key == 'description' and package.candidate and pattern.search( package.candidate.raw_description): toshow = True if toshow: result.append(self._package_to_dict(package, full=False)) return result @simple_response def packages_get(self, package): """ retrieves full properties of one package """ package = self.package_manager.get_package(package) if package is not None: return self._package_to_dict(package, full=True) else: # TODO: 404? return {} @sanitize(function=MappingSanitizer( { 'install': 'install', 'upgrade': 'install', 'uninstall': 'remove', }, required=True), packages=ListSanitizer(StringSanitizer(minimum=1), required=True), update=BooleanSanitizer()) @simple_response def packages_invoke_dry_run(self, packages, function, update): if update: self.package_manager.update() packages = self.package_manager.get_packages(packages) kwargs = {'install': [], 'remove': [], 'dry_run': True} if function == 'install': kwargs['install'] = packages else: kwargs['remove'] = packages return dict( zip(['install', 'remove', 'broken'], self.package_manager.mark(**kwargs))) @sanitize(function=MappingSanitizer( { 'install': 'install', 'upgrade': 'install', 'uninstall': 'remove', }, required=True), packages=ListSanitizer(StringSanitizer(minimum=1), required=True)) def packages_invoke(self, request): """ executes an installer action """ packages = request.options.get('packages') function = request.options.get('function') try: if self._working(): # make it multi-tab safe (same session many buttons to be clicked) raise LockError() with self.package_manager.locked(reset_status=True): not_found = [ pkg_name for pkg_name in packages if self.package_manager.get_package(pkg_name) is None ] self.finished(request.id, {'not_found': not_found}) if not not_found: def _thread(package_manager, function, packages): with package_manager.locked(set_finished=True): with package_manager.no_umc_restart( exclude_apache=True): if function == 'install': package_manager.install(*packages) else: package_manager.uninstall(*packages) def _finished(thread, result): if isinstance(result, BaseException): MODULE.warn('Exception during %s %s: %r' % (function, packages, str(result))) thread = notifier.threads.Simple( 'invoke', notifier.Callback(_thread, self.package_manager, function, packages), _finished) thread.run() else: self.package_manager.set_finished( ) # nothing to do, ready to take new commands except LockError: # make it thread safe: another process started a package manager # this module instance already has a running package manager raise umcm.UMC_Error(_('Another package operation is in progress')) @contextmanager def is_working(self): self._is_working = True yield self._is_working = False def _working(self): return self._is_working or os.path.exists( LOCK_FILE) or not self.package_manager.progress_state._finished @simple_response def working(self): # TODO: PackageManager needs is_idle() or something # preferably the package_manager can tell what is currently executed: # package_manager.is_working() => False or _('Installing PKG') return self._working() @simple_response def custom_progress(self): timeout = 5 ret = self.package_manager.poll(timeout) ret['finished'] = not self._working() return ret def _package_to_dict(self, package, full): """ Helper that extracts properties from a 'apt_pkg.Package' object and stores them into a dictionary. Depending on the 'full' switch, stores only limited (for grid display) or full (for detail view) set of properties. """ installed = package.installed # may be None found = True candidate = package.candidate found = candidate is not None if not found: candidate = NoneCandidate() result = { 'package': package.name, 'installed': package.is_installed, 'upgradable': package.is_upgradable and found, 'summary': candidate.summary, } # add (and translate) a combined status field # *** NOTE *** we translate it here: if we would use the Custom Formatter # of the grid then clicking on the sort header would not work. if package.is_installed: if package.is_upgradable: result['status'] = _('upgradable') else: result['status'] = _('installed') else: result['status'] = _('not installed') # additional fields needed for detail view if full: # Some fields differ depending on whether the package is installed or not: if package.is_installed: result['section'] = installed.section result['priority'] = installed.priority or '' result['summary'] = installed.summary # take the current one result['description'] = installed.description result['installed_version'] = installed.version result['size'] = installed.installed_size if package.is_upgradable: result['candidate_version'] = candidate.version else: del result[ 'upgradable'] # not installed: don't show 'upgradable' at all result['section'] = candidate.section result['priority'] = candidate.priority or '' result['description'] = candidate.description result['size'] = candidate.installed_size result['candidate_version'] = candidate.version # format size to handle bytes size = result['size'] byte_mods = ['B', 'kB', 'MB'] for byte_mod in byte_mods: if size < 10000: break size = float(size) / 1000 # MB, not MiB else: size = size * 1000 # once too often if size == int(size): format_string = '%d %s' else: format_string = '%.2f %s' result['size'] = format_string % (size, byte_mod) return result @simple_response def components_query(self): """ Returns components list for the grid in the ComponentsPage. """ # be as current as possible. self.get_updater().ucr_reinit() self.ucr.load() return [ self.get_component_manager().component(comp.name) for comp in self.get_updater().get_components(all=True) ] @sanitize_list(StringSanitizer()) @multi_response(single_values=True) def components_get(self, iterator, component_id): # be as current as possible. self.get_updater().ucr_reinit() self.ucr.load() for component_id in iterator: yield self.get_component_manager().component(component_id) @sanitize_list(DictSanitizer({'object': advanced_components_sanitizer})) @multi_response def components_put(self, iterator, object): """Writes back one or more component definitions. """ # umc.widgets.Form wraps the real data into an array: # # [ # { # 'object' : { ... a dict with the real data .. }, # 'options': None # }, # ... more such entries ... # ] # # Current approach is to return a similarly structured array, # filled with elements, each one corresponding to one array # element of the request: # # [ # { # 'status' : a number where 0 stands for success, anything else # is an error code # 'message' : a result message # 'object' : a dict of field -> error message mapping, allows # the form to show detailed error information # }, # ... more such entries ... # ] with set_save_commit_load(self.ucr) as super_ucr: for object, in iterator: yield self.get_component_manager().put(object, super_ucr) self.package_manager.update() # do the same as components_put (update) # but don't allow adding an already existing entry components_add = sanitize_list( DictSanitizer({'object': add_components_sanitizer}))(components_put) components_add.__name__ = 'components_add' @sanitize_list(StringSanitizer()) @multi_response(single_values=True) def components_del(self, iterator, component_id): for component_id in iterator: yield self.get_component_manager().remove(component_id) self.package_manager.update() @multi_response def settings_get(self, iterator): # *** IMPORTANT *** Our UCR copy must always be current. This is not only # to catch up changes made via other channels (ucr command line etc), # but also to reflect the changes we have made ourselves! self.ucr.load() for _ in iterator: yield { 'unmaintained': self.ucr.is_true('repository/online/unmaintained', False), 'server': self.ucr.get('repository/online/server', ''), 'prefix': self.ucr.get('repository/online/prefix', ''), } @sanitize_list( DictSanitizer({'object': basic_components_sanitizer}), min_elements=1, max_elements=1 # moduleStore with one element... ) @multi_response def settings_put(self, iterator, object): # FIXME: returns values although it should yield (multi_response) changed = False # Set values into our UCR copy. try: with set_save_commit_load(self.ucr) as super_ucr: for object, in iterator: for key, value in object.items(): MODULE.info( " ++ Setting new value for '%s' to '%s'" % (key, value)) super_ucr.set_registry_var( '%s/%s' % (ONLINE_BASE, key), value) changed = super_ucr.changed() except Exception as e: MODULE.warn(" !! Writing UCR failed: %s" % str(e)) return [{'message': str(e), 'status': PUT_WRITE_ERROR}] self.package_manager.update() # Bug #24878: emit a warning if repository is not reachable try: updater = self.get_updater() for line in updater.print_version_repositories().split('\n'): if line.strip(): break else: raise ConfigurationError() except ConfigurationError: msg = _( "There is no repository at this server (or at least none for the current UCS version)" ) MODULE.warn(" !! Updater error: %s" % msg) response = {'message': msg, 'status': PUT_UPDATER_ERROR} # if nothing was committed, we want a different type of error code, # just to appropriately inform the user if changed: response['status'] = PUT_UPDATER_NOREPOS return [response] except BaseException as ex: MODULE.warn(" !! Updater error: %s" % (ex, )) return [{'message': str(ex), 'status': PUT_UPDATER_ERROR}] return [{'status': PUT_SUCCESS}]
class AptScript(SetupScript): '''More or less just a wrapper around univention.lib.package_manager.PackageManager with SetupScript capabilities. ''' brutal_apt_options = True def up(self, *args, **kwargs): self.package_manager = PackageManager(always_noninteractive=False) handler = _PackageManagerLoggerHandlerWithoutProcess( self.message, self.step, self.error) self.package_manager.logger.addHandler(handler) self.roles_package_map = { 'domaincontroller_master': 'univention-server-master', 'domaincontroller_backup': 'univention-server-backup', 'domaincontroller_slave': 'univention-server-slave', 'memberserver': 'univention-server-member', 'basesystem': 'univention-basesystem', } self.current_server_role = self.ucr.get('server/role') self.wanted_server_role = self.profile.get('server/role') def set_always_install(self, *packages): self.package_manager.always_install(packages) @contextmanager def noninteractive(self): if self.brutal_apt_options: with self.package_manager.brutal_noninteractive(): yield else: with self.package_manager.noninteractive(): yield def update(self): with self.noninteractive(): return self.package_manager.update() def get_package(self, pkg_name): return self.package_manager.get_package(pkg_name) def finish_task(self, *log_msgs): '''Task is finished. Increment counter and inform the progress parser. Reopen the cache (maybe unneeded but does not slow us down too much). ''' # don't log msgs for now self.package_manager.add_hundred_percent() self.reopen_cache() def reopen_cache(self): self.package_manager.reopen_cache() def mark_auto(self, auto, *pkgs): self.package_manager.mark_auto(auto, *pkgs) def commit(self, install=None, remove=None, msg_if_failed=''): with self.noninteractive(): self.package_manager.commit(install, remove, msg_if_failed=msg_if_failed) def install(self, *pkg_names): with self.noninteractive(): self.package_manager.install(*pkg_names) def uninstall(self, *pkg_names): with self.noninteractive(): self.package_manager.uninstall(*pkg_names) def get_package_for_role(self, role_name): '''Searches for the meta-package that belongs to the given role_name ''' try: # get "real" package for server/role pkg_name = self.roles_package_map[role_name] return self.package_manager.cache[pkg_name] except KeyError: self.error(_('Failed to get package for Role %s') % role_name) return None def autoremove(self): with self.noninteractive(): self.package_manager.autoremove() def down(self): self.package_manager.unlock()
class Instance(Base): def init(self): self._finishedLock = threading.Lock() self._errors = [] self.progress_state = Progress() self._installation_started = False self.package_manager = PackageManager( info_handler=self.progress_state.info_handler, step_handler=self.progress_state.step_handler, error_handler=self.progress_state.error_handler, lock=False, always_noninteractive=True, ) self.package_manager.set_max_steps(100.0) self.original_certificate_file = None def error_handling(self, etype, exc, etraceback): if isinstance(exc, SchoolInstallerError): # restore the original certificate... this is done at any error before the system join self.restore_original_certificate() def get_samba_version(self): '''Returns 3 or 4 for Samba4 or Samba3 installation, respectively, and returns None otherwise.''' if self.package_manager.is_installed('univention-samba4'): return 4 elif self.package_manager.is_installed('univention-samba'): return 3 return None def get_school_environment(self): '''Returns 'singlemaster', 'multiserver', or None''' if self.package_manager.is_installed('ucs-school-singlemaster'): return 'singlemaster' elif self.package_manager.is_installed('ucs-school-slave') or self.package_manager.is_installed('ucs-school-nonedu-slave') or self.package_manager.is_installed('ucs-school-master'): return 'multiserver' return None def get_school_version(self): return ucr.get('appcenter/apps/ucsschool/version') @simple_response def query(self, **kwargs): """Returns a dictionary of initial values for the form.""" ucr.load() return { 'server_role': ucr.get('server/role'), 'joined': os.path.exists('/var/univention-join/joined'), 'samba': self.get_samba_version(), 'school_environment': self.get_school_environment(), 'guessed_master': get_master_dns_lookup(), 'hostname': ucr.get('hostname'), } @simple_response def progress(self): if not self._installation_started: if self._installation_started is False: self.progress_state.finish() self.progress_state.error_handler('Critical: There is no current installation running. Maybe the previous process died?') self._installation_started = False return self.progress_state.poll() @simple_response def get_metainfo(self): """Queries the specified DC Master for metainformation about the UCS@school environment""" master = ucr.get('ldap/master') or get_master_dns_lookup() if not master: return return self._umc_master(self.username, self.password, master, 'schoolinstaller/get/metainfo/master') @sanitize( master=HostSanitizer(required=True, regex_pattern=RE_HOSTNAME), # TODO: add regex error message; Bug #42955 username=StringSanitizer(required=True, minimum=1), password=StringSanitizer(required=True, minimum=1), school=StringSanitizer(required=True, regex_pattern=RE_OU), # TODO: add regex error message ) @simple_response def get_schoolinfo(self, username, password, master, school): """Queries the specified DC Master for information about the specified school""" return self._umc_master(username, password, master, 'schoolinstaller/get/schoolinfo/master', {'school': school}) @sanitize( school=StringSanitizer(required=True, regex_pattern=RE_OU), # TODO: add regex error message ) @simple_response def get_schoolinfo_master(self, school): """ Fetches LDAP information from master about specified OU. This function assumes that the given arguments have already been validated! """ school_name = school try: lo, po = get_machine_connection(write=True) school = School.from_dn(School(name=school_name).dn, None, lo) except noObject: exists = False class_share_server = None home_share_server = None educational_slaves = [] administrative_slaves = [] except ldap.SERVER_DOWN: raise # handled via UMC except ldap.LDAPError as exc: MODULE.warn('LDAP error during receiving school info: %s' % (exc,)) raise UMC_Error(_('The LDAP connection to the master system failed.')) else: exists = True class_share_server = school.class_share_file_server home_share_server = school.home_share_file_server educational_slaves = [SchoolDCSlave.from_dn(dn, None, lo).name for dn in school.educational_servers] administrative_slaves = [SchoolDCSlave.from_dn(dn, None, lo).name for dn in school.administrative_servers] return { 'exists': exists, 'school': school_name, 'classShareServer': class_share_server, 'homeShareServer': home_share_server, 'educational_slaves': educational_slaves, 'administrative_slaves': administrative_slaves, } @simple_response def get_metainfo_master(self): """Returns information about the UCS@school Installation on the DC Master.""" return { 'samba': self.get_samba_version(), 'school_environment': self.get_school_environment(), 'school_version': self.get_school_version(), } def _umc_master(self, username, password, master, uri, data=None): try: return umc(username, password, master, uri, data).result except Forbidden: raise SchoolInstallerError(_('Make sure ucs-school-umc-installer is installed on the DC Master and all join scripts are executed.')) except (ConnectionError, HTTPError) as exc: raise SchoolInstallerError(_('Could not connect to the DC Master %s: %s') % (master, exc)) # TODO: set status, message, result @sanitize( username=StringSanitizer(required=True), password=StringSanitizer(required=True), master=HostSanitizer(required=True, regex_pattern=RE_HOSTNAME_OR_EMPTY), schoolOU=StringSanitizer(required=True, regex_pattern=RE_OU_OR_EMPTY), setup=ChoicesSanitizer(['multiserver', 'singlemaster']), server_type=ChoicesSanitizer(['educational', 'administrative']), nameEduServer=StringSanitizer(regex_pattern=RE_HOSTNAME_OR_EMPTY), # javascript wizard page always passes value to backend, even if empty ) def install(self, request): # get all arguments username = request.options.get('username') password = request.options.get('password') master = request.options.get('master') school_ou = request.options.get('schoolOU') educational_slave = request.options.get('nameEduServer') ou_display_name = request.options.get('OUdisplayname', school_ou) # use school OU name as fallback server_type = request.options.get('server_type') setup = request.options.get('setup') server_role = ucr.get('server/role') joined = os.path.exists('/var/univention-join/joined') if self._installation_started: raise ValueError('The installation was started twice. This should not have happened.') if server_role != 'domaincontroller_slave': # use the credentials of the currently authenticated user on a master/backup system self.require_password() username = self.username password = self.password master = '%s.%s' % (ucr.get('hostname'), ucr.get('domainname')) if server_role == 'domaincontroller_backup': master = ucr.get('ldap/master') self.original_certificate_file = None # check for valid school OU if ((setup == 'singlemaster' and server_role == 'domaincontroller_master') or server_role == 'domaincontroller_slave') and not RE_OU.match(school_ou): raise SchoolInstallerError(_('The specified school OU is not valid.')) # check for valid server role if server_role not in ('domaincontroller_master', 'domaincontroller_backup', 'domaincontroller_slave'): raise SchoolInstallerError(_('Invalid server role! UCS@school can only be installed on the system roles master domain controller, backup domain controller, or slave domain controller.')) if server_role == 'domaincontroller_slave' and not server_type: raise SchoolInstallerError(_('Server type has to be set for domain controller slave')) if server_role == 'domaincontroller_slave' and server_type == 'administrative' and not educational_slave: raise SchoolInstallerError(_('The name of an educational server has to be specified if the system shall be configured as administrative server.')) if server_role == 'domaincontroller_slave' and server_type == 'administrative' and educational_slave.lower() == ucr.get('hostname').lower(): raise SchoolInstallerError(_('The name of the educational server may not be equal to the name of the administrative slave.')) if server_role == 'domaincontroller_slave': # on slave systems, download the certificate from the master in order # to be able to build up secure connections self.original_certificate_file = self.retrieve_root_certificate(master) if server_role != 'domaincontroller_master': # check for a compatible environment on the DC master masterinfo = self._umc_master(username, password, master, 'schoolinstaller/get/metainfo') school_environment = masterinfo['school_environment'] master_samba_version = masterinfo['samba'] if not school_environment: raise SchoolInstallerError(_('Please install UCS@school on the master domain controller system. Cannot proceed installation on this system.')) if master_samba_version == 3: raise SchoolInstallerError(_('This UCS domain uses Samba 3 which is no longer supported by UCS@school. Please update all domain systems to samba 4 to be able to continue.')) if server_role == 'domaincontroller_slave' and school_environment != 'multiserver': raise SchoolInstallerError(_('The master domain controller is not configured for a UCS@school multi server environment. Cannot proceed installation on this system.')) if server_role == 'domaincontroller_backup' and school_environment != setup: raise SchoolInstallerError(_('The UCS@school master domain controller needs to be configured similarly to this backup system. Please choose the correct environment type for this system.')) if server_role == 'domaincontroller_backup' and not joined: raise SchoolInstallerError(_('In order to install UCS@school on a backup domain controller, the system needs to be joined first.')) # everything ok, try to acquire the lock for the package installation lock_aquired = self.package_manager.lock(raise_on_fail=False) if not lock_aquired: MODULE.warn('Could not aquire lock for package manager') raise SchoolInstallerError(_('Cannot get lock for installation process. Another package manager seems to block the operation.')) # see which packages we need to install MODULE.process('performing UCS@school installation') packages_to_install = [] installed_samba_version = self.get_samba_version() if installed_samba_version == 3: raise SchoolInstallerError(_('This UCS domain uses Samba 3 which is no longer supported by UCS@school. Please update all domain systems to samba 4 to be able to continue.')) if server_role == 'domaincontroller_slave': # slave packages_to_install.extend(['univention-samba4', 'univention-s4-connector']) if server_type == 'educational': packages_to_install.append('ucs-school-slave') else: packages_to_install.append('ucs-school-nonedu-slave') else: # master or backup if setup == 'singlemaster': if installed_samba_version: pass # do not install samba a second time else: # otherwise install samba4 packages_to_install.extend(['univention-samba4', 'univention-s4-connector']) packages_to_install.append('ucs-school-singlemaster') elif setup == 'multiserver': packages_to_install.append('ucs-school-master') else: raise SchoolInstallerError(_('Invalid UCS@school configuration.')) MODULE.info('Packages to be installed: %s' % ', '.join(packages_to_install)) # reset the current installation progress steps = 100 # installation -> 100 if server_role != 'domaincontroller_backup' and not (server_role == 'domaincontroller_master' and setup == 'multiserver'): steps += 10 # create_ou -> 10 if server_role == 'domaincontroller_slave': steps += 10 # move_slave_into_ou -> 10 steps += 100 # system_join -> 100 steps self._installation_started = True progress_state = self.progress_state progress_state.reset(steps) progress_state.component = _('Installation of UCS@school packages') self.package_manager.reset_status() def _thread(_self, packages): MODULE.process('Starting package installation') with _self.package_manager.locked(reset_status=True, set_finished=True): with _self.package_manager.no_umc_restart(exclude_apache=True): _self.package_manager.update() if not _self.package_manager.install(*packages): raise SchoolInstallerError(_('Failed to install packages.')) if server_role != 'domaincontroller_backup' and not (server_role == 'domaincontroller_master' and setup == 'multiserver'): # create the school OU (not on backup and not on master w/multi server environment) MODULE.info('Starting creation of LDAP school OU structure...') progress_state.component = _('Creation of LDAP school structure') progress_state.info = '' try: if server_role == 'domaincontroller_slave': _educational_slave = ucr.get('hostname') if server_type == 'educational' else educational_slave administrative_slave = None if server_type == 'educational' else ucr.get('hostname') create_ou_remote(master, username, password, school_ou, ou_display_name, _educational_slave, administrative_slave) elif server_role == 'domaincontroller_master': create_ou_local(school_ou, ou_display_name) except SchoolInstallerError as exc: MODULE.error(str(exc)) raise SchoolInstallerError(_( 'The UCS@school software packages have been installed, however, a school OU could not be created and consequently a re-join of the system has not been performed. ' 'Please create a new school OU structure using the UMC module "Add school" on the master and perform a domain join on this machine via the UMC module "Domain join".' )) progress_state.add_steps(10) if server_role == 'domaincontroller_slave': # make sure that the slave is correctly moved below its OU MODULE.info('Trying to move the slave entry in the right OU structure...') result = umc(username, password, master, 'schoolwizards/schools/move_dc', {'schooldc': ucr.get('hostname'), 'schoolou': school_ou}, 'schoolwizards/schools').result if not result.get('success'): MODULE.warn('Could not successfully move the slave DC into its correct OU structure:\n%s' % result.get('message')) raise SchoolInstallerError(_('Validating the LDAP school OU structure failed. It seems that the current slave system has already been assigned to a different school or that the specified school OU name is already in use.')) # system join on a slave system progress_state.component = _('Domain join') if server_role == 'domaincontroller_slave': progress_state.info = _('Preparing domain join...') MODULE.process('Starting system join...') else: # run join scripts on DC backup/master progress_state.info = _('Executing join scripts...') MODULE.process('Running join scripts...') system_join( username, password, info_handler=self.progress_state.info_handler, step_handler=self.progress_state.add_steps, error_handler=self.progress_state.error_handler, ) def _finished(thread, result): MODULE.info('Finished installation') progress_state.finish() progress_state.info = _('finished...') self._installation_started = None if isinstance(result, SchoolInstallerError): MODULE.warn('Error during installation: %s' % (result,)) self.restore_original_certificate() progress_state.error_handler(str(result)) elif isinstance(result, BaseException): self.restore_original_certificate() msg = ''.join(traceback.format_exception(*thread.exc_info)) MODULE.error('Exception during installation: %s' % msg) progress_state.error_handler(_('An unexpected error occurred during installation: %s') % result) thread = notifier.threads.Simple('ucsschool-install', notifier.Callback(_thread, self, packages_to_install), notifier.Callback(_finished)) thread.run() self.finished(request.id, None) def retrieve_root_certificate(self, master): '''On a slave system, download the root certificate from the specified master and install it on the system. In this way it can be ensured that secure connections can be performed even though the system has not been joined yet. Returns the renamed original file if it has been renamed. Otherwise None is returned.''' if ucr.get('server/role') != 'domaincontroller_slave': # only do this on a slave system return # make sure the directory exists if not os.path.exists(os.path.dirname(CERTIFICATE_PATH)): os.makedirs(os.path.dirname(CERTIFICATE_PATH)) original_certificate_file = None # download the certificate from the DC master certificate_uri = 'http://%s/ucs-root-ca.crt' % (master,) MODULE.info('Downloading root certificate from: %s' % (master,)) try: certificate_file, headers = urllib.urlretrieve(certificate_uri) if not filecmp.cmp(CERTIFICATE_PATH, certificate_file): # we need to update the certificate file... # save the original file first and make sure we do not override any existing file count = 1 original_certificate_file = CERTIFICATE_PATH + '.orig' while os.path.exists(original_certificate_file): count += 1 original_certificate_file = CERTIFICATE_PATH + '.orig%s' % count os.rename(CERTIFICATE_PATH, original_certificate_file) MODULE.info('Backing up old root certificate as: %s' % original_certificate_file) # place the downloaded certificate at the original position os.rename(certificate_file, CERTIFICATE_PATH) os.chmod(CERTIFICATE_PATH, 0o644) except EnvironmentError as exc: # print warning and ignore error MODULE.warn('Could not download root certificate [%s], error ignored: %s' % (certificate_uri, exc)) self.original_certificate_file = original_certificate_file self.restore_original_certificate() return original_certificate_file def restore_original_certificate(self): # try to restore the original certificate file if self.original_certificate_file and os.path.exists(self.original_certificate_file): try: MODULE.info('Restoring original root certificate.') os.rename(self.original_certificate_file, CERTIFICATE_PATH) except EnvironmentError as exc: MODULE.warn('Could not restore original root certificate: %s' % (exc,)) self.original_certificate_file = None
def simple_handler(f): def _simple_handler(msg): msg = '%s\n\r' % msg.strip() f.write(msg) return _simple_handler parser = optparse.OptionParser() parser.add_option('-a', '--app-id', help='app id to deinstall', metavar='APP_ID') (options, args) = parser.parse_args() if not options.app_id: raise Exception('app id missing') ucr = ConfigRegistry() ucr.load() updater = UniventionUpdater(False) package_manager = PackageManager(info_handler=simple_handler(sys.stdout), error_handler=simple_handler(sys.stderr)) component_manager = ComponentManager(ucr, updater) app = Application.find(options.app_id) if app: if not app.uninstall(package_manager, component_manager): raise Exception('deinstallation of app %s failed' % options.app_id)
class AptScript(SetupScript): '''More or less just a wrapper around univention.lib.package_manager.PackageManager with SetupScript capabilities. ''' brutal_apt_options = True def up(self, *args, **kwargs): self.package_manager = PackageManager( info_handler=self.message, step_handler=self.step, error_handler=self.error, handle_only_frontend_errors=True, # ignore pmerror status ) self.roles_package_map = { 'domaincontroller_master' : 'univention-server-master', 'domaincontroller_backup' : 'univention-server-backup', 'domaincontroller_slave' : 'univention-server-slave', 'memberserver' : 'univention-server-member', 'fatclient' : 'univention-managed-client', 'mobileclient' : 'univention-mobile-client', } self.current_server_role = self.get_ucr_var('server/role') self.wanted_server_role = self.get_profile_var('server/role') def set_always_install(self, *packages): self.package_manager.always_install(packages) @contextmanager def noninteractive(self): if self.brutal_apt_options: with self.package_manager.brutal_noninteractive(): yield else: with self.package_manager.noninteractive(): yield def update(self): with self.noninteractive(): return self.package_manager.update() def get_package(self, pkg_name): return self.package_manager.get_package(pkg_name) def finish_task(self, *log_msgs): '''Task is finished. Increment counter and inform the progress parser. Reopen the cache (maybe unneeded but does not slow us down too much). ''' # dont log msgs for now self.package_manager.add_hundred_percent() self.reopen_cache() def reopen_cache(self): self.package_manager.reopen_cache() def mark_auto(self, auto, *pkgs): self.package_manager.mark_auto(auto, *pkgs) def commit(self, install=None, remove=None, msg_if_failed=''): with self.noninteractive(): self.package_manager.commit(install, remove, msg_if_failed=msg_if_failed) def install(self, *pkg_names): with self.noninteractive(): self.package_manager.install(*pkg_names) def uninstall(self, *pkg_names): with self.noninteractive(): self.package_manager.uninstall(*pkg_names) def get_package_for_role(self, role_name): '''Searches for the meta-package that belongs to the given role_name ''' try: # get "real" package for server/role pkg_name = self.roles_package_map[role_name] return self.package_manager.cache[pkg_name] except KeyError: self.error(_('Failed to get package for Role %s') % role_name) return None def autoremove(self): with self.noninteractive(): self.package_manager.autoremove() def down(self): self.package_manager.unlock()
class Instance(umcm.Base): def init(self): self.ucr = univention.config_registry.ConfigRegistry() self.ucr.load() util.install_opener(self.ucr) self.package_manager = PackageManager( info_handler=MODULE.process, step_handler=None, error_handler=MODULE.warn, lock=False, always_noninteractive=True, ) self.uu = UniventionUpdater(False) self.component_manager = util.ComponentManager(self.ucr, self.uu) # in order to set the correct locale for Application locale.setlocale(locale.LC_ALL, str(self.locale)) @sanitize(email=EmailSanitizer(required=True)) @simple_response def request_new_license(self, email): license = LICENSE.dump_data() if license is None: raise umcm.UMC_CommandError(_("Cannot parse License from LDAP")) data = {} data["email"] = email data["licence"] = license data = urllib.urlencode(data) url = "https://license.univention.de/keyid/conversion/submit" request = urllib2.Request(url, data=data, headers={"User-agent": "UMC/AppCenter"}) try: util.urlopen(request) except Exception as e: try: # try to parse an html error body = e.read() detail = re.search('<span id="details">(?P<details>.*?)</span>', body).group(1) except: detail = str(e) raise umcm.UMC_CommandError(_("An error occurred while sending the request: %s") % detail) else: return True @sanitize(pattern=PatternSanitizer(default=".*")) @simple_response def query(self, pattern): LICENSE.reload() try: applications = Application.all(force_reread=True) except (urllib2.HTTPError, urllib2.URLError) as e: raise umcm.UMC_CommandError(_("Could not query App Center: %s") % e) result = [] self.package_manager.reopen_cache() for application in applications: if pattern.search(application.name): props = application.to_dict(self.package_manager) # delete larger entries for ikey in ("readmeupdate", "licenseagreement"): if ikey in props: del props[ikey] result.append(props) return result @sanitize(application=StringSanitizer(minimum=1, required=True)) @simple_response def get(self, application): LICENSE.reload() application = Application.find(application) self.package_manager.reopen_cache() return application.to_dict(self.package_manager) @sanitize( function=ChoicesSanitizer(["install", "uninstall", "update"], required=True), application=StringSanitizer(minimum=1, required=True), force=BooleanSanitizer(), ) def invoke(self, request): function = request.options.get("function") application_id = request.options.get("application") application = Application.find(application_id) force = request.options.get("force") try: # make sure that the application cane be installed/updated can_continue = True result = {"install": [], "remove": [], "broken": []} if not application: MODULE.info("Application not found: %s" % application_id) can_continue = False elif function == "install" and not application.can_be_installed(self.package_manager): MODULE.info("Application cannot be installed: %s" % application_id) can_continue = False elif function == "update" and not application.can_be_updated(): MODULE.info("Application cannot be updated: %s" % application_id) can_continue = False if can_continue and function in ("install", "update"): result = application.install_dry_run( self.package_manager, self.component_manager, remove_component=False ) if result["broken"] or (result["remove"] and not force): MODULE.info("Remove component: %s" % application_id) self.component_manager.remove_app(application) self.package_manager.update() can_continue = False elif can_continue and function in ("uninstall",) and not force: result["remove"] = application.uninstall_dry_run(self.package_manager) can_continue = False result["can_continue"] = can_continue self.finished(request.id, result) if can_continue: def _thread(module, application, function): with module.package_manager.locked(reset_status=True, set_finished=True): with module.package_manager.no_umc_restart(): if function in ("install", "update"): # dont have to add component: already added during dry_run return application.install( module.package_manager, module.component_manager, add_component=False ) else: return application.uninstall(module.package_manager, module.component_manager) def _finished(thread, result): if isinstance(result, BaseException): MODULE.warn("Exception during %s %s: %s" % (function, application_id, str(result))) thread = notifier.threads.Simple( "invoke", notifier.Callback(_thread, self, application, function), _finished ) thread.run() except LockError: # make it thread safe: another process started a package manager # this module instance already has a running package manager raise umcm.UMC_CommandError(_("Another package operation is in progress")) @simple_response def app_center_app_license(self, application): application = Application.find(application) if not application or not application.get("licensefile"): raise umcm.UMC_CommandError(_("No license file available for application: %s") % (application.id)) # open the license file and replace line breaks with BR-tags fp = util.urlopen(application.get("licensefile")) txt = "".join(fp.readlines()).strip() txt = txt.replace("\n\n\n", "\n<br>\n<br>\n<br>\n") txt = txt.replace("\n\n", "\n<br>\n<br>\n") return txt @simple_response def packages_sections(self): """ fills the 'sections' combobox in the search form """ sections = set() for package in self.package_manager.packages(): sections.add(package.section) return sorted(sections) @sanitize(pattern=PatternSanitizer(required=True)) @simple_response def packages_query(self, pattern, section="all", key="package"): """ Query to fill the grid. Structure is fixed here. """ result = [] for package in self.package_manager.packages(): if section == "all" or package.section == section: toshow = False if pattern.pattern == "^.*$": toshow = True elif key == "package" and pattern.search(package.name): toshow = True elif key == "description" and pattern.search(package.candidate.raw_description): toshow = True if toshow: result.append(self._package_to_dict(package, full=False)) return result @simple_response def packages_get(self, package): """ retrieves full properties of one package """ package = self.package_manager.get_package(package) if package is not None: return self._package_to_dict(package, full=True) else: # TODO: 404? return {} @sanitize( function=MappingSanitizer({"install": "install", "upgrade": "install", "uninstall": "remove"}, required=True), packages=ListSanitizer(StringSanitizer(minimum=1), required=True), ) @simple_response def packages_invoke_dry_run(self, packages, function): packages = self.package_manager.get_packages(packages) kwargs = {"install": [], "remove": [], "dry_run": True} if function == "install": kwargs["install"] = packages else: kwargs["remove"] = packages return dict(zip(["install", "remove", "broken"], self.package_manager.mark(**kwargs))) @sanitize( function=MappingSanitizer({"install": "install", "upgrade": "install", "uninstall": "remove"}, required=True), packages=ListSanitizer(StringSanitizer(minimum=1), required=True), ) def packages_invoke(self, request): """ executes an installer action """ packages = request.options.get("packages") function = request.options.get("function") try: with self.package_manager.locked(reset_status=True): not_found = [pkg_name for pkg_name in packages if self.package_manager.get_package(pkg_name) is None] self.finished(request.id, {"not_found": not_found}) if not not_found: def _thread(package_manager, function, packages): with package_manager.locked(set_finished=True): with package_manager.no_umc_restart(): if function == "install": package_manager.install(*packages) else: package_manager.uninstall(*packages) def _finished(thread, result): if isinstance(result, BaseException): MODULE.warn("Exception during %s %s: %r" % (function, packages, str(result))) thread = notifier.threads.Simple( "invoke", notifier.Callback(_thread, self.package_manager, function, packages), _finished ) thread.run() except LockError: # make it thread safe: another process started a package manager # this module instance already has a running package manager raise umcm.UMC_CommandError(_("Another package operation is in progress")) @simple_response def progress(self): timeout = 5 return self.package_manager.poll(timeout) def _package_to_dict(self, package, full): """ Helper that extracts properties from a 'apt_pkg.Package' object and stores them into a dictionary. Depending on the 'full' switch, stores only limited (for grid display) or full (for detail view) set of properties. """ installed = package.installed # may be None candidate = package.candidate result = { "package": package.name, "installed": package.is_installed, "upgradable": package.is_upgradable, "summary": candidate.summary, } # add (and translate) a combined status field # *** NOTE *** we translate it here: if we would use the Custom Formatter # of the grid then clicking on the sort header would not work. if package.is_installed: if package.is_upgradable: result["status"] = _("upgradable") else: result["status"] = _("installed") else: result["status"] = _("not installed") # additional fields needed for detail view if full: result["section"] = package.section result["priority"] = package.priority # Some fields differ depending on whether the package is installed or not: if package.is_installed: result["summary"] = installed.summary # take the current one result["description"] = installed.description result["installed_version"] = installed.version result["size"] = installed.installed_size if package.is_upgradable: result["candidate_version"] = candidate.version else: del result["upgradable"] # not installed: don't show 'upgradable' at all result["description"] = candidate.description result["size"] = candidate.installed_size result["candidate_version"] = candidate.version # format size to handle bytes size = result["size"] byte_mods = ["B", "kB", "MB"] for byte_mod in byte_mods: if size < 10000: break size = float(size) / 1000 # MB, not MiB else: size = size * 1000 # once too often if size == int(size): format_string = "%d %s" else: format_string = "%.2f %s" result["size"] = format_string % (size, byte_mod) return result @simple_response def components_query(self): """ Returns components list for the grid in the ComponentsPage. """ # be as current as possible. self.uu.ucr_reinit() self.ucr.load() result = [] for comp in self.uu.get_all_components(): result.append(self.component_manager.component(comp)) return result @sanitize_list(StringSanitizer()) @multi_response(single_values=True) def components_get(self, iterator, component_id): # be as current as possible. self.uu.ucr_reinit() self.ucr.load() for component_id in iterator: yield self.component_manager.component(component_id) @sanitize_list(DictSanitizer({"object": advanced_components_sanitizer})) @multi_response def components_put(self, iterator, object): """Writes back one or more component definitions. """ # umc.widgets.Form wraps the real data into an array: # # [ # { # 'object' : { ... a dict with the real data .. }, # 'options': None # }, # ... more such entries ... # ] # # Current approach is to return a similarly structured array, # filled with elements, each one corresponding to one array # element of the request: # # [ # { # 'status' : a number where 0 stands for success, anything else # is an error code # 'message' : a result message # 'object' : a dict of field -> error message mapping, allows # the form to show detailed error information # }, # ... more such entries ... # ] with util.set_save_commit_load(self.ucr) as super_ucr: for (object,) in iterator: yield self.component_manager.put(object, super_ucr) self.package_manager.update() # do the same as components_put (update) # but dont allow adding an already existing entry components_add = sanitize_list(DictSanitizer({"object": add_components_sanitizer}))(components_put) components_add.__name__ = "components_add" @sanitize_list(StringSanitizer()) @multi_response(single_values=True) def components_del(self, iterator, component_id): for component_id in iterator: yield self.component_manager.remove(component_id) self.package_manager.update() @multi_response def settings_get(self, iterator): # *** IMPORTANT *** Our UCR copy must always be current. This is not only # to catch up changes made via other channels (ucr command line etc), # but also to reflect the changes we have made ourselves! self.ucr.load() for _ in iterator: yield { "maintained": self.ucr.is_true("repository/online/maintained", False), "unmaintained": self.ucr.is_true("repository/online/unmaintained", False), "server": self.ucr.get("repository/online/server", ""), "prefix": self.ucr.get("repository/online/prefix", ""), } @sanitize_list( DictSanitizer({"object": basic_components_sanitizer}), min_elements=1, max_elements=1, # moduleStore with one element... ) @multi_response def settings_put(self, iterator, object): # FIXME: returns values although it should yield (multi_response) changed = False # Set values into our UCR copy. try: with util.set_save_commit_load(self.ucr) as super_ucr: for (object,) in iterator: for key, value in object.iteritems(): MODULE.info(" ++ Setting new value for '%s' to '%s'" % (key, value)) super_ucr.set_registry_var("%s/%s" % (constants.ONLINE_BASE, key), value) changed = super_ucr.changed() except Exception as e: MODULE.warn(" !! Writing UCR failed: %s" % str(e)) return [{"message": str(e), "status": constants.PUT_WRITE_ERROR}] self.package_manager.update() # Bug #24878: emit a warning if repository is not reachable try: updater = self.uu for line in updater.print_version_repositories().split("\n"): if line.strip(): break else: raise ConfigurationError() except ConfigurationError: msg = _("There is no repository at this server (or at least none for the current UCS version)") MODULE.warn(" !! Updater error: %s" % msg) response = {"message": msg, "status": constants.PUT_UPDATER_ERROR} # if nothing was committed, we want a different type of error code, # just to appropriately inform the user if changed: response["status"] = constants.PUT_UPDATER_NOREPOS return [response] except: info = sys.exc_info() emsg = "%s: %s" % info[:2] MODULE.warn(" !! Updater error [%s]: %s" % (emsg)) return [{"message": str(info[1]), "status": constants.PUT_UPDATER_ERROR}] return [{"status": constants.PUT_SUCCESS}]