From 7f6eb54c6ebedf839de7b1cd1ee72194decde8de Mon Sep 17 00:00:00 2001 From: idziubenko Date: Thu, 1 Jul 2021 16:46:31 +0300 Subject: [PATCH] fixed colortext, fixed cl-update-checker --- bin/{cl-console-gui_py3 => cl-console-gui} | 0 ...l-update-checker_py3 => cl-update-checker} | 26 +- libs_crutch/core/backup.py | 724 +++++++ libs_crutch/core/core_main.py | 68 + libs_crutch/core/result_viewer.py | 692 +++++++ libs_crutch/core/result_viewer_gui.py | 129 ++ libs_crutch/core/set_vars.py | 113 ++ libs_crutch/core/setup_cache.py | 118 ++ libs_crutch/core/setup_package.py | 414 ++++ libs_crutch/core/utils/cl_backup.py | 90 + libs_crutch/core/utils/cl_backup_restore.py | 104 + libs_crutch/core/utils/cl_config.py | 49 + libs_crutch/core/utils/cl_core_custom.py | 63 + libs_crutch/core/utils/cl_core_dispatch.py | 42 + libs_crutch/core/utils/cl_core_group.py | 92 + libs_crutch/core/utils/cl_core_patch.py | 46 + libs_crutch/core/utils/cl_core_request.py | 75 + libs_crutch/core/utils/cl_core_restart.py | 53 + libs_crutch/core/utils/cl_core_setup.py | 77 + libs_crutch/core/utils/cl_core_variables.py | 62 + libs_crutch/core/utils/cl_core_view_cert.py | 42 + libs_crutch/core/wsdl_core.py | 795 ++++++++ libs_crutch/lib/datavars.py | 1 + libs_crutch/lib/utils/binhosts.py | 2 +- libs_crutch/update/emerge_parser.py | 853 ++++++++ libs_crutch/update/profile.py | 268 +++ libs_crutch/update/update.py | 1764 +++++++++++++++++ libs_crutch/update/update_info.py | 39 +- libs_crutch/update/update_tasks.py | 28 + libs_crutch/update/utils/__init__.py | 0 libs_crutch/update/utils/cl_setup_update.py | 47 + libs_crutch/update/utils/cl_update.py | 457 +++++ libs_crutch/update/utils/cl_update_profile.py | 147 ++ libs_crutch/update/variables/__init__.py | 4 +- libs_crutch/update/variables/update.py | 2 +- libs_crutch/update/wsdl_update.py | 183 ++ pym/consolegui/application/more.py | 9 +- pym/consolegui/application/user_update.py | 122 ++ pym/consolegui/application/utils.py | 5 +- 39 files changed, 7745 insertions(+), 60 deletions(-) rename bin/{cl-console-gui_py3 => cl-console-gui} (100%) rename bin/{cl-update-checker_py3 => cl-update-checker} (94%) create mode 100644 libs_crutch/core/backup.py create mode 100644 libs_crutch/core/core_main.py create mode 100644 libs_crutch/core/result_viewer.py create mode 100644 libs_crutch/core/result_viewer_gui.py create mode 100644 libs_crutch/core/set_vars.py create mode 100644 libs_crutch/core/setup_cache.py create mode 100644 libs_crutch/core/setup_package.py create mode 100644 libs_crutch/core/utils/cl_backup.py create mode 100644 libs_crutch/core/utils/cl_backup_restore.py create mode 100644 libs_crutch/core/utils/cl_config.py create mode 100644 libs_crutch/core/utils/cl_core_custom.py create mode 100644 libs_crutch/core/utils/cl_core_dispatch.py create mode 100644 libs_crutch/core/utils/cl_core_group.py create mode 100644 libs_crutch/core/utils/cl_core_patch.py create mode 100644 libs_crutch/core/utils/cl_core_request.py create mode 100644 libs_crutch/core/utils/cl_core_restart.py create mode 100644 libs_crutch/core/utils/cl_core_setup.py create mode 100644 libs_crutch/core/utils/cl_core_variables.py create mode 100644 libs_crutch/core/utils/cl_core_view_cert.py create mode 100644 libs_crutch/core/wsdl_core.py create mode 100644 libs_crutch/update/emerge_parser.py create mode 100644 libs_crutch/update/profile.py create mode 100644 libs_crutch/update/update.py create mode 100644 libs_crutch/update/update_tasks.py create mode 100644 libs_crutch/update/utils/__init__.py create mode 100644 libs_crutch/update/utils/cl_setup_update.py create mode 100644 libs_crutch/update/utils/cl_update.py create mode 100644 libs_crutch/update/utils/cl_update_profile.py create mode 100644 libs_crutch/update/wsdl_update.py create mode 100644 pym/consolegui/application/user_update.py diff --git a/bin/cl-console-gui_py3 b/bin/cl-console-gui similarity index 100% rename from bin/cl-console-gui_py3 rename to bin/cl-console-gui diff --git a/bin/cl-update-checker_py3 b/bin/cl-update-checker similarity index 94% rename from bin/cl-update-checker_py3 rename to bin/cl-update-checker index 64166e0..06803fa 100644 --- a/bin/cl-update-checker_py3 +++ b/bin/cl-update-checker @@ -15,15 +15,16 @@ # See the License for the specific language governing permissions and # limitations under the License. - -#TODO: this is untested, and probably broken +import logging +# logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG) +logger = logging.getLogger(__name__) import os from os import path from PyQt5 import QtCore, QtGui, QtWidgets import time -from calculate.core.client.cert_info import user_can_run_update +from calculate.consolegui.application.user_update import user_can_run_update from calculate.lib.datavars import DataVars import dbus import dbus.service @@ -131,6 +132,7 @@ class DBusChecker(dbus.service.Object): self.parent = parent # export this object to dbus dbus.service.Object.__init__(self, name, session) + self.parent.systray.setVisible(True) @dbus.service.method(DBUS_NAME_UPDATER, in_signature='', out_signature='') def hide_systray(self): @@ -159,7 +161,7 @@ class CheckThread(QtWidgets.QMainWindow, UpdateInfo): def __init__(self, bus): super(CheckThread, self).__init__() UpdateInfo.__init__(self) - + self.bus = bus self.wm = pyinotify.WatchManager() @@ -170,11 +172,11 @@ class CheckThread(QtWidgets.QMainWindow, UpdateInfo): self.already_timer = QtCore.QTimer(self) self.already_timer.timeout.connect(self.notify_step) - self.already_timer.start(1000) + self.already_timer.start(1) self.check_timer = QtCore.QTimer(self) self.check_timer.timeout.connect(self.step) - self.check_timer.start(self.interval * 1000) + self.check_timer.start(self.interval * 1) self.systray = SysTray(self) self.gui_runned = False @@ -222,10 +224,8 @@ class CheckThread(QtWidgets.QMainWindow, UpdateInfo): if __name__ == '__main__': import sys - if os.fork(): sys.exit(1) - if not user_can_run_update(True): sys.stderr.write(_("User can not to perform the system update") + "\n") sys.exit(1) @@ -236,23 +236,25 @@ if __name__ == '__main__': sys.exit(1) app = QtWidgets.QApplication(sys.argv) - for i in [0.5, 1, 2, 5]: if QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): break time.sleep(i) - + # Enable glib main loop support dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) # Get the session bus try: bus = dbus.SessionBus() - except dbus.exceptions.DBusException, e: + except dbus.exceptions.DBusException as e: + print(e) sys.exit(1) - + try: remote_object = bus.get_object(DBUS_NAME_UPDATER, DBUS_APP_UPDATER) g = dbus.Interface(remote_object, DBUS_NAME_UPDATER) + props = g.getProperties() + print(props) sys.exit(1) except Exception as e: pass diff --git a/libs_crutch/core/backup.py b/libs_crutch/core/backup.py new file mode 100644 index 0000000..554f063 --- /dev/null +++ b/libs_crutch/core/backup.py @@ -0,0 +1,724 @@ +# -*- coding: utf-8 -*- + +# Copyright 2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from __future__ import absolute_import +import sys +import os +import stat +import re +from os import path +from calculate.core.server.core_interfaces import MethodsInterface +from calculate.lib.utils.files import (makeDirectory, removeDir, tar_directory, + FilePermission, find, listDirectory, + FilesError, process, readFileEx, + readFile, pathJoin, FindFileType) +from calculate.lib.utils.content import FileOwnersRestricted, ContentsStorage +from calculate.lib.utils.portage import getInstalledAtom, makeCfgName +from calculate.lib.configparser import ConfigParserCaseSens +from calculate.lib.cl_template import Template +from calculate.lib.utils.accounts import Passwd, Group, Shadow + +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from .variables.action import Actions +import tarfile +import shutil +import glob +from itertools import chain + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) + +__ = getLazyLocalTranslate(_) + + +class BackupError(Exception): + """ + Исключение вызванное во время резервного копирования настроек + """ + + +class Backup(MethodsInterface): + """ + Выполнение резервного копирования настроек + """ + + def init(self): + self.apply_files = set() + self.unlink_autorun = set() + self.uid_map = {} + self.gid_map = {} + + def prepare_backup(self, dn, rootname): + makeDirectory(path.join(dn, rootname)) + return True + + def remove_directory(self, dn): + removeDir(dn) + return True + + def backup_user_changed(self, owner, dn_root): + """ + Сохранить конфигурационные файлы изменённые пользователем + :param owner: + :param dn_root: + :return: + """ + for fn in owner.get_md5_failed(lambda x: x.startswith('/etc')): + self.backup_file(fn, dn_root) + return True + + def prepare_contents(self, dn, contents_file, rootname): + dn_root = path.join(dn, rootname) + fo = FileOwnersRestricted( + "/", ["/%s" % x for x in find(path.join(dn, rootname), + fullpath=False)] + ["/etc"]) + self.backup_user_changed(fo, dn_root) + cs = ContentsStorage(contents_file, fo) + cs.keep(dn_root, dn_root) + return True + + def create_archive(self, dn, archfile): + arch_dn = path.dirname(archfile) + if not path.exists(arch_dn): + makeDirectory(arch_dn) + os.chmod(arch_dn, FilePermission.UserAll) + tar_directory(dn, archfile) + return True + + def open_archive(self, dn, archfile): + makeDirectory(dn) + with tarfile.open(archfile, 'r:bz2') as f: + f.extractall(dn) + return True + + def restore_configs(self, archfile, dn, contents_name, root_name): + """ + Восстановить все файлы настроек + :param archfile: + :param dn: + :return: + """ + dirs_data = {} + used_dirs = set() + _gid = lambda x: self.gid_map.get(x, x) + _uid = lambda x: self.uid_map.get(x, x) + with tarfile.open(archfile, 'r:bz2') as f: + try: + # исключить из переноса файлы, которые принадлежат пакетам, + # которые не установлены в системе + contents = f.extractfile(f.getmember(contents_name)) + pkg_file = [x.split()[:3:2] for x in contents] + not_installed_files = [ + x for x in pkg_file + if not any(getInstalledAtom(x[0].partition(":")[0]))] + skip_packages = sorted(list( + set([x[0] for x in not_installed_files]))) + if skip_packages: + self.printWARNING( + _("Settings ignored for following packages: %s") % + ", ".join(x.partition(":")[0] for x in skip_packages)) + not_installed_files = [x[1] for x in not_installed_files] + except KeyError: + raise BackupError(_("CONTENTS file not found")) + for ti in (x for x in f if x.name.startswith("%s/" % root_name)): + if ti.name[4:] in not_installed_files: + continue + if ti.issym() and not path.exists(ti.linkpath): + continue + if ti.name[5:]: + fn_system = path.join(dn, ti.path[5:]) + if ti.isdir(): + dirs_data[fn_system] = (ti.mode, _uid(ti.uid), + _gid(ti.gid)) + continue + dirs_list = fn_system.split('/') + for i in range(2, len(dirs_list)): + used_dirs.add("/".join(dirs_list[:i])) + if path.lexists(fn_system): + stat_system = os.lstat(fn_system) + if ti.issym(): + if stat.S_ISLNK(stat_system.st_mode): + system_link = os.readlink(fn_system) + if system_link == ti.linkpath: + continue + else: + if (stat.S_IMODE(stat_system.st_mode) == ti.mode and + stat_system.st_uid == _uid(ti.uid) and + stat_system.st_gid == _gid(ti.gid)): + data_system = readFile(fn_system) + extr_file = f.extractfile(ti) + if extr_file: + data_ti = extr_file.read() + if self.is_equal_files(data_system, + data_ti): + continue + ti.name = ti.name[5:] + f.extract(ti, dn) + os.chown(fn_system, _uid(ti.uid), _gid(ti.gid)) + if ti.isfile() or ti.issym(): + # если симлинк в списке предварительного удаления + # то исключаем его из списка изменённых файлов + if fn_system in self.unlink_autorun: + self.unlink_autorun.remove(fn_system) + else: + self.apply_files.add(fn_system) + # восстановление прав у каталогов, конфиги в которых должны были + # восстанавливаться + for dn_name in sorted(used_dirs): + if dn_name in dirs_data: + dn_mode, dn_uid, dn_gid = dirs_data[dn_name] + if path.lexists(dn_name): + stat_system = os.lstat(dn_name) + if (stat.S_IMODE(stat_system.st_mode) != dn_mode or + stat_system.st_uid != dn_uid or + stat_system.st_gid != dn_gid): + os.chmod(dn_name, dn_mode) + os.chown(dn_name, dn_uid, dn_gid) + + return True + + def sava_ini(self, section, key, val): + ini = ConfigParserCaseSens(strict=False) + ini.read(self.clVars.Get('cl_backup_ini_env'), encoding="utf-8") + if not ini.has_section(section): + ini.add_section(section) + ini[section][key] = str(val) + + ini["backup"]["version"] = self.clVars.Get('cl_ver') + with open(self.clVars.Get('cl_backup_ini_env'), 'w') as f: + ini.write(f) + return True + + def load_ini(self, section, key): + ini = ConfigParserCaseSens(strict=False) + ini.read(self.clVars.Get('cl_backup_ini_env'), encoding="utf-8") + return ini.get(section, key, fallback="") + + def save_initd(self, dn, root_name): + """ + Сохранить список init.d + :param dn: + :param root_name: + :return: + """ + self.sava_ini("backup", "init", + ','.join(listDirectory('/etc/init.d', fullPath=False))) + dn_root = path.join(dn, root_name) + for dn in ('/etc/runlevels/sysinit', + '/etc/runlevels/default', + '/etc/runlevels/boot'): + try: + dn_backup = pathJoin(dn_root, dn) + if not path.exists(dn_backup): + makeDirectory(dn_backup) + for fn in listDirectory(dn, fullPath=True): + if path.islink(fn): + link = os.readlink(fn) + symname = pathJoin(dn_root, fn) + if not path.lexists(symname): + os.symlink(link, symname) + except (OSError, IOError) as e: + raise BackupError(_("Failed to enable service at startup") + + (_(": %s") % (str(e)))) + return True + + def make_directory_sync(self, base_dn, dn, prefix="/"): + """ + Создать директорию и сохранить права из prefix + :param dn: + :param prefix: + :return: + """ + if not path.exists(dn): + self.make_directory_sync(base_dn, path.dirname(dn), prefix) + rel_dn = path.relpath(dn, base_dn) + system_dn = path.join(prefix, rel_dn) + system_dn_stat = os.lstat(system_dn) + if not makeDirectory(dn): + raise BackupError(_("Failed to create directory %s") % dn) + os.chown(dn, system_dn_stat.st_uid, system_dn_stat.st_gid) + os.chmod(dn, stat.S_IMODE(system_dn_stat.st_mode)) + + def backup_file(self, source_fn, target_dn, prefix="/"): + """ + Сделать резервную копию указанного файла + :param source_fn: + :param target_dn: + :return: + """ + target_fn = path.join(target_dn, path.relpath(source_fn, prefix)) + source_stat = os.lstat(source_fn) + target_base_dn = path.dirname(target_fn) + self.make_directory_sync(target_dn, target_base_dn, prefix=prefix) + if stat.S_ISLNK(source_stat.st_mode): + source_link = os.readlink(source_fn) + os.symlink(source_link, target_fn) + elif stat.S_ISREG(source_stat.st_mode): + shutil.copy2(source_fn, target_fn) + os.chown(target_fn, source_stat.st_uid, source_stat.st_gid) + return True + + def backup_marked(self, source_dn, target_dn, subdn, root_name): + """ + Сохранить файлы из указанного каталога, отмеченного комментариями + выполнения шаблонов + :return: + """ + source_etc_dn = path.join(source_dn, subdn) + root_dn = path.join(target_dn, root_name) + reCfg = re.compile('._cfg\d{4}_') + try: + for fn in find(source_etc_dn, filetype=FindFileType.RegularFile): + if (not reCfg.search(fn) and + " Modified Calculate" in readFileEx(fn, + headbyte=300)): + self.backup_file(fn, root_dn, prefix=source_dn) + except (OSError, IOError) as e: + raise BackupError(_("Failed to backup configuration files that " + "were modified by templates") + + (_(": %s") % (str(e)))) + return True + + def clear_autorun(self): + """ + Удалить все файлы из автозапуска, которые ссылаются на файлы из списка + init.d + :return: + """ + files = ["/etc/init.d/%s" % x.strip() + for x in self.load_ini("backup", "init").split(',')] + for dn in ('/etc/runlevels/sysinit', + '/etc/runlevels/default', + '/etc/runlevels/boot'): + for fn in listDirectory(dn, fullPath=True): + if path.islink(fn) and os.readlink(fn) in files: + os.unlink(fn) + self.unlink_autorun.add(fn) + return True + + def restore_contents(self, contentsfile, dn): + cs = ContentsStorage(contentsfile) + cs.restore(dn, files=self.apply_files) + return True + + def set_service_action(self): + self.clVars.Set('core.cl_backup_action', Actions.Service, force=True) + return True + + nm_name = "NetworkManager" + nm_config = "/etc/NetworkManager" + nm_connections = path.join(nm_config, "system-connections") + + def _do_service(self, service, action): + """ + Выполнить действие с сервисом (restart, start, stop, zap) + :param service: + :param action: + :return: + """ + actions = { + 'restart': _("Failed to restart {name} service"), + 'start': _("Failed to start {name} service"), + 'stop': _("Failed to stop {name} service"), + 'zap': _("Failed to zap {name} service"), + 'status': _("Failed to get status of {name} service") + } + try: + p = process(service, action) + if p.failed(): + data = p.readerr().strip() + if ("has started, but is inactive" not in data and + "will start when" not in data): + for line in data.split('\n'): + self.printERROR(line) + raise BackupError(actions.get(action, action).format( + name=path.basename(service))) + except FilesError: + self.printERROR(actions.get(action, action).format( + name=path.basename(service))) + raise + return True + + def stop_net_services(self): + """ + Остановить все сетевые службы (и NM и openrc) + :return: + """ + self._do_service("/etc/init.d/netmount", "zap") + for fn in chain(["/etc/init.d/%s" % self.nm_name], + glob.glob('/etc/init.d/net.*')): + if fn.endswith('.lo') or not path.exists(fn): + continue + self._do_service(fn, "stop") + return True + + def unlink_openrc_net_services(self, files): + """ + Удалить сетевые сервисы openrc сервисы openrc + :return: + """ + for fn in glob.glob('/etc/init.d/net.*'): + if fn.endswith('.lo') or not path.exists(fn) or fn in files: + continue + try: + os.unlink(fn) + self.apply_files.add(fn) + except OSError as e: + self.printERROR(_("Failed to remove %s service"), + path.basename(fn)) + self.printERROR(str(e)) + return True + + def is_networkmanager_backup(self, backup_path): + """ + Проверить сетевой менеджер в резервной копии + :param backup_path: + :return: + """ + return path.lexists(path.join( + backup_path, "root/etc/runlevels/default/%s" % self.nm_name)) + + def is_networkmanager_system(self): + """ + Проверить сетевой менеджер в текущей системе + :return: + """ + return path.lexists("/etc/runlevels/default/%s" % self.nm_name) + + def restore_openrc_net_initd(self, files): + """ + Восстановить сервисы net.* и запустить их + :return: + """ + for fn in files: + if not path.exists(fn): + os.symlink("/etc/init.d/net.lo", fn) + self.apply_files.add(fn) + self._do_service(fn, "start") + return True + + def restore_files(self, backup_path, files, notapply=False): + """ + Восстановить указанные файлы из backup/root + :param backup_path: + :param files: список файлов (поддерживаются глобальные символы) + :return: + """ + len_source_prefix = len(path.join(backup_path, "root")) + _gid = lambda x: self.gid_map.get(x, x) + _uid = lambda x: self.uid_map.get(x, x) + for source in chain(*[glob.glob(pathJoin(backup_path, "root", x)) + for x in files]): + dest = source[len_source_prefix:] + if path.lexists(source): + dn = path.dirname(dest) + if not path.exists(dn): + makeDirectory(dn) + if path.lexists(dest): + if self.is_equal_system_backup(dest, source): + continue + + shutil.copy2(source, dest) + fn_stat = os.lstat(source) + os.chown(dest, _uid(fn_stat.st_uid), _gid(fn_stat.st_gid)) + if not notapply: + self.apply_files.add(dest) + return True + + def clear_nm_connections(self, backup_path): + """ + Удалить доступные соединения для NetworkManager + :return: + """ + base_dir = pathJoin(backup_path, "root") + for fn in listDirectory(self.nm_connections, fullPath=True): + try: + if not path.exists(pathJoin(base_dir, fn)): + os.unlink(fn) + self.apply_files.add(fn) + except OSError as e: + raise BackupError(str(e)) + + def is_equal_files(self, text1, text2): + """ + Сравнить два файла отбросив комментарии и пробельные символы в начале и + в конце + :param text1: + :param text2: + :return: + """ + text1 = Template.removeComment(text1).strip() + text2 = Template.removeComment(text2).strip() + return text1 == text2 + + def is_equal_system_backup(self, system_fn, backup_fn): + """ + Проверить одинаковый ли файл в резервной копии и системе + :param system_fn: + :param backup_fn: + :return: + """ + _gid = lambda x: self.gid_map.get(x, x) + _uid = lambda x: self.uid_map.get(x, x) + if path.islink(system_fn) != path.islink(backup_fn): + return False + if path.islink(system_fn): + return os.readlink(system_fn) == os.readlink(backup_fn) + if path.isfile(system_fn) != path.isfile(backup_fn): + return False + data_system = readFile(system_fn) + data_backup = readFile(backup_fn) + if not self.is_equal_files(data_system, data_backup): + return False + stat_system = os.lstat(system_fn) + stat_backup = os.lstat(backup_fn) + if (stat.S_IMODE(stat_system.st_mode) != + stat.S_IMODE(stat_backup.st_mode) or + stat_system.st_uid != _uid(stat_backup.st_uid) or + stat_system.st_gid != _gid(stat_backup.st_gid)): + return False + return True + + def check_backup_for_network(self, backup_path, files): + """ + Проверить конфигурационные файлы настройки сети на соответствие текущим + :param backup_path: + :return: + """ + backup_nm = self.is_networkmanager_backup(backup_path) + system_nm = self.is_networkmanager_system() + # проверить совпадает ли сетевой менеджер + if backup_nm != system_nm: + if backup_nm and not any( + getInstalledAtom("net-misc/networkmanager")): + return True + return False + # если nm проверить совпадение system-connections + if backup_nm: + connection_files = set(path.basename(x) for x in chain( + glob.glob("%s/*" % self.nm_connections), + glob.glob("%s/*" % (pathJoin(backup_path, "root", + self.nm_connections))) + )) + for fn in connection_files: + system_fn = pathJoin(self.nm_connections, fn) + backup_fn = pathJoin(backup_path, "root", + self.nm_connections, fn) + if not self.is_equal_system_backup(system_fn, backup_fn): + return False + # если openrc проверить conf.d/net и соответствие net.* + else: + system_fn = "/etc/conf.d/net" + backup_fn = pathJoin(backup_path, "root", system_fn) + if not self.is_equal_system_backup(system_fn, backup_fn): + return False + system_net_set = (set(path.basename(x) + for x in glob.glob('/etc/init.d/net.*')) - + {"net.lo"}) + backup_net_set = set(path.basename(x) for x in files) + if system_net_set != backup_net_set: + return False + return True + + def restore_network(self, backup_path): + """ + Восстановить сеть из backup + :param backup_path: + :return: + """ + files = ["/etc/init.d/%s" % x.strip() + for x in self.load_ini("backup", "init").split(',') + if x.startswith("net.") and x != "net.lo"] + if self.check_backup_for_network(backup_path, files): + self.endTask("skip") + return True + self.stop_net_services() + self.unlink_openrc_net_services(files) + self.clear_nm_connections(backup_path) + self.restore_files(backup_path, ["/etc/conf.d/hostname", + "/etc/resolv.conf", + "/etc/hosts"]) + self._do_service("/etc/init.d/hostname", "restart") + if self.is_networkmanager_backup(backup_path): + self.unlink_openrc_net_services([]) + self.restore_files(backup_path, [ + "/etc/NetworkManager/system-connections/*", + "/etc/NetworkManager/dispatcher.d/*", + ]) + self._do_service("/etc/init.d/NetworkManager", "start") + else: + self.restore_files(backup_path, ["/etc/conf.d/net"]) + self.restore_openrc_net_initd(files) + self._do_service("/etc/init.d/netmount", "start") + return True + + def special_backup(self, backup_path): + """ + Выполнить специализирование резервное копирование модулей + :param backup_path: + :return: + """ + for backup_obj in self.iterate_modules(): + backup_obj.backup(backup_path) + return True + + def special_restore(self, backup_path): + """ + Выполнить специализирование восстановление из резервной копии + :param backup_path: + :return: + """ + for backup_obj in self.iterate_modules(): + backup_obj.restore(backup_path) + return True + + def iterate_modules(self): + """ + Перебрать все модули backup + :return: + """ + site_packages = [path.join(x, "calculate") + for x in sys.path + if (x.endswith('site-packages') and + x.startswith('/usr/lib'))] + ret_list = [] + for module, modDir in chain( + *map(lambda x: map(lambda y: (path.basename(y), y), + listDirectory(x, True, True)), + site_packages)): + if path.exists(path.join(modDir, "backup_%s.py" % module)): + if not "calculate-%s" % module in ret_list: + ret_list.append("calculate-%s" % module) + cl_backup = ret_list + for pack in cl_backup: + if pack: + module_name = '%s.backup_%s' % (pack.replace("-", "."), + pack.rpartition("-")[2]) + import importlib + + try: + backup_module = importlib.import_module(module_name) + backup_obj = backup_module.Backup(self, self.clVars) + yield backup_obj + except ImportError: + sys.stderr.write(_("Unable to import %s") % module_name) + + def display_changed_configs(self): + """ + Отобразить список восстановленных файлов + :return: + """ + t = Template(self.clVars, printWARNING=self.printWARNING, + printERROR=self.printERROR, printSUCCESS=self.printSUCCESS) + t.verboseOutput(sorted(list(self.apply_files | self.unlink_autorun))) + return True + + def display_backup_configs(self, archfile): + """ + Отобразить список помещённых в резервную копию файлов + :return: + """ + with tarfile.open(archfile, 'r:bz2') as f: + self.printWARNING(_("Calculate Utilities have backuped files") + + _(":")) + for fn in sorted("/%s" % x.path.partition('/')[2] for x in + f.getmembers() if (not x.isdir() and ( + x.path.startswith("root") or + x.path.startswith("ldap")))): + self.printSUCCESS(" " * 5 + fn) + return True + + def run_openrc(self, command): + p = process("/sbin/openrc", "default") + p.success() + return True + + passwd_fn = '/etc/passwd' + group_fn = '/etc/group' + shadow_fn = '/etc/shadow' + + def save_accounts(self, backup_path): + accounts_path = path.join(backup_path, "accounts") + for source_fn in (self.passwd_fn, self.group_fn, self.shadow_fn): + self.backup_file(source_fn, accounts_path, prefix="/etc") + return True + + def restore_accounts(self, backup_path): + accounts_path = path.join(backup_path, "accounts") + backup_passwd_fn = pathJoin(accounts_path, + path.basename(self.passwd_fn)) + backup_group_fn = pathJoin(accounts_path, path.basename(self.group_fn)) + backup_shadow_fn = pathJoin(accounts_path, + path.basename(self.shadow_fn)) + + if any(not path.exists(x) for x in (backup_passwd_fn, + backup_group_fn, + backup_shadow_fn)): + return "skip" + # пользователи + passwd = Passwd(readFile(self.passwd_fn)) + backup_passwd = Passwd(readFile(backup_passwd_fn)) + added_users = [x.name for x in passwd.new_users(backup_passwd)] + keep_users = [x.name for x in backup_passwd.new_users(passwd)] + if self.clVars.GetBool('cl_backup_verbose_set') and added_users: + self.printSUCCESS( + _("Restored users:") + " " + ", ".join(added_users)) + self.uid_map = backup_passwd.get_uid_map(passwd) + passwd.join(backup_passwd) + with open(makeCfgName(self.passwd_fn), 'w') as f: + passwd.write(f) + os.chown(self.passwd_fn, 0, 0) + os.chmod(self.passwd_fn, + FilePermission.OtherRead | + FilePermission.GroupRead | + FilePermission.UserRead | + FilePermission.UserWrite) + + # группы + groups = Group(readFile(self.group_fn)) + backup_groups = Group(readFile(backup_group_fn)) + added_groups = [x.name for x in groups.new_groups(backup_groups)] + if self.clVars.GetBool('cl_backup_verbose_set') and added_groups: + self.printSUCCESS(_("Restored groups:") + " " + + ", ".join(added_groups)) + self.gid_map = backup_groups.get_gid_map(groups) + groups.join(backup_groups, keep_users=keep_users) + with open(makeCfgName(self.group_fn), 'w') as f: + groups.write(f) + os.chown(self.group_fn, 0, 0) + os.chmod(self.group_fn, + FilePermission.OtherRead | + FilePermission.GroupRead | + FilePermission.UserRead | + FilePermission.UserWrite) + + # пароли + shadow = Shadow(readFile(self.shadow_fn)) + backup_shadow = Shadow(readFile(backup_shadow_fn)) + changed_shadow = [x.name + for x in shadow.changed_passwords(backup_shadow)] + if self.clVars.GetBool('cl_backup_verbose_set') and changed_shadow: + self.printSUCCESS(_("Restored user passwords:") + " " + + ", ".join(changed_shadow)) + shadow.join(backup_shadow) + with open(makeCfgName(self.shadow_fn), 'w') as f: + shadow.write(f) + os.chown(self.shadow_fn, 0, 0) + os.chmod(self.shadow_fn, + FilePermission.UserRead | + FilePermission.UserWrite) + return True diff --git a/libs_crutch/core/core_main.py b/libs_crutch/core/core_main.py new file mode 100644 index 0000000..ffed785 --- /dev/null +++ b/libs_crutch/core/core_main.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# Copyright 2012-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# from server.cl_server import main +# print(main) + +from __future__ import print_function +from __future__ import absolute_import +def core_main(): + import sys + + if hasattr(sys, "setdefaultencoding"): + sys.setdefaultencoding("utf-8") + from calculate.lib.cl_lang import setLocalTranslate + + _ = lambda x: x + setLocalTranslate('cl_core', sys.modules[__name__]) + from traceback import print_exc + from os import path + + if not path.exists('/dev/urandom'): + sys.stderr.write("/dev/urandom not found\n") + sys.exit(1) + + try: + from .server.cl_server import main + + reload(sys) + from calculate.lib.datavars import CriticalError, DataVarsError + + try: + sys.exit(main()) + except (CriticalError, DataVarsError) as e: + sys.stderr.write("%s\n" % str(e)) + sys.exit(1) + except ImportError as e: + print_exc() + cannot_import = 'cannot import name ' + no_module = 'No module named ' + if e.message.startswith(cannot_import): + print (_('Failed to import %s') + % e.message.rpartition(cannot_import)[2]) + elif e.message.startswith(no_module): + print (_('No module named %s') % + e.message.rpartition(no_module)[2]) + else: + print(e.message) + sys.exit(1) + except KeyboardInterrupt: + print() + print(_("Task interrupted")) + + +if (__name__ == "__main__"): + core_main() \ No newline at end of file diff --git a/libs_crutch/core/result_viewer.py b/libs_crutch/core/result_viewer.py new file mode 100644 index 0000000..3b994d5 --- /dev/null +++ b/libs_crutch/core/result_viewer.py @@ -0,0 +1,692 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function +from itertools import cycle +from calculate.lib.utils.colortext import get_terminal_print, Terminal, \ + TextState, convert_xml_to_terminal, Print +from calculate.lib.cl_progressbar import get_progress_bar +import sys +from calculate.lib.utils.files import getch, set_active_tty, get_active_tty +from calculate.lib.utils.text import tableReport +import threading +from calculate.lib.utils.tools import classificate + +Colors = TextState.Colors +from calculate.lib.cl_lang import setLocalTranslate + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) + + +class Spinner(threading.Thread): + def __init__(self, *args, **kwargs): + self.__halt = threading.Event() + self.__main_thread = threading.currentThread() + threading.Thread.__init__(self, *args, **kwargs) + self.start() + + def run(self): + Terminal().cursor = False + try: + sys.stdout.write(" |") + for c in cycle('/-\|'): + sys.stdout.write('\b' + c) + sys.stdout.flush() + self.__halt.wait(0.2) + sys.stdout.flush() + if self.__halt.is_set(): + sys.stdout.write('\b\b \b\b') + return + if not self.__main_thread.is_alive(): + return + finally: + Terminal().cursor = True + + def stop(self): + self.__halt.set() + self.join() + + +class Table(tableReport): + def __init__(self, *args, **kwargs): + self.res = [] + tableReport.__init__(self, *args, **kwargs) + + def printFunc(self, s): + self.res.append(s) + + def printTable(self): + self.setAutosize() + self.printReport(printRows=False) + return "".join(self.res) + + +def printTable(data, header=None): + try: + if any(data): + return Table(None, header, data, colSpan=0).printTable() + else: + return "" + except Exception: + # print str(e) + raise + + +def echo_on(f): + def wrapper(self, *args, **kw): + oldecho = self.parent.terminal_info.echo + self.parent.terminal_info.echo = True + try: + return f(self, *args, **kw) + finally: + self.parent.terminal_info.echo = oldecho + + return wrapper + + +class TaskState(object): + """ + Текущее состояние вывода сообщений + """ + + def __init__(self, parent): + self.parent = parent + + @property + def state(self): + return self.parent.task_state + + def process_tags(self, s): + """ + Выполнить текстовое преобразование + """ + s = s or "" + return convert_xml_to_terminal(s).replace(" ", " ") + + def display_asterisk(self, color): + """ + Отобразить маркер + """ + self.parent.printer(" ") + self.parent.printer.foreground(color).bold("*") + self.parent.printer(" ") + + def _right_indent(self, indent, width=-1): + """ + Выполнить выравнивание от правого края + """ + if width > 0: + self.parent.printer('\r') + self.parent.printer.right(width - indent) + else: + self.parent.printer(" ") + + def _change_asterisk(self, color, width=-1): + if width > 0: + self.parent.printer('\r') + self.display_asterisk(color) + + def dotting(self): + if self.parent.spinner: + self.parent.spinner.stop() + self.parent.printer(" ...") + self.parent.printer.flush() + + def _print_result(self, text, color): + width = self.parent.terminal_info.width + self._change_asterisk(color, width) + self._right_indent(len(text) + 4, width) + self.parent.printer.bold.foreground(TextState.Colors.BLUE)("[ ") + self.parent.printer.bold.foreground(color)(text) + self.parent.printer.bold.foreground(TextState.Colors.BLUE)(" ]") + self.parent.printer("\n") + + def _print_ok(self): + self._print_result("ok", TextState.Colors.GREEN) + + def _print_failed(self): + self._print_result("!!", TextState.Colors.RED) + + def _print_skip(self): + self._print_result("skip", TextState.Colors.YELLOW) + + def display_result(self, result): + func_map = {"skip": self._print_skip, + False: self._print_failed} + func_map.get(result, self._print_ok)() + self.parent.printer.flush() + + def startTask(self, message, progress, num): + pass + + def endTask(self, result, progress_message=None): + pass + + def breakTask(self): + pass + + def printMessage(self, color, message): + for i, line in classificate(self.process_tags(message).split('\n')): + self.display_asterisk(color) + self.parent.printer(line) + if not i.last: + self.parent.printer('\n') + try: + self.parent.printer.flush() + except IOError: + pass + + def printERROR(self, message): + self.printMessage(Colors.RED, message) + + def printSUCCESS(self, message): + self.printMessage(Colors.GREEN, message) + + def printWARNING(self, message): + self.printMessage(Colors.YELLOW, message) + + def startGroup(self, message): + self.parent.printer.foreground(Colors.WHITE)(self.process_tags(message)) + self.parent.printer('\n') + + def endGroup(self): + pass + + def beginFrame(self, message): + self.parent.terminal_info.echo = False + + def endFrame(self): + self.parent.terminal_info.echo = True + + def addProgress(self, message): + pass + + def setProgress(self, percent, short_message, long_message): + pass + + @echo_on + def askConfirm(self, message, default): + self.parent.printer("\n") + while True: + try: + _print = Print(output=self.parent.printer.output) + if default in "yes": + yes_color, no_color = Colors.GREEN, Colors.LIGHT_RED + else: + yes_color, no_color = Colors.LIGHT_RED, Colors.GREEN + yes = _print.foreground(yes_color)("Yes") + no = _print.foreground(no_color)("No") + white_message = _print.foreground(Colors.WHITE)(message) + ask = raw_input(white_message + ' (%s/%s): ' % (yes, no)) + except (EOFError, KeyboardInterrupt): + ask = 'no' + print() + if ask.lower() in ['n', 'no']: + return "no" + if ask.lower() in ['y', 'yes']: + return "yes" + if ask == '': + return default + + def printPre(self, message): + self.parent.printer(self.process_tags(message)) + self.parent.printer('\n') + + def printDefault(self, message): + self.parent.printer(self.process_tags(message)) + self.parent.printer('\n') + + @echo_on + def askChoice(self, message, answers): + self.parent.printer("\n") + Colors = TextState.Colors + printer = self.parent.printer + _print = Print(output=printer.output) + # ability answer by first letter + firstletter = 0 + i_value, i_comment = 0, 1 + answerByChar = map(lambda x: x[i_value][firstletter], answers) + + if filter(lambda x: answerByChar.count(x) > 1, answerByChar): + use_getch = False + sa = slice(0, None) + else: + use_getch = True + sa = slice(1) + message = _print.foreground(Colors.WHITE)(message) + full_message = (message + + ' (%s): ' % ("/".join(map( + lambda x: "%s[%s]" % (x[i_comment], x[i_value][sa]), + answers)))) + while True: + CTRC_C = chr(3) + if use_getch: + printer(full_message) + ask = getch() + printer("\n") + if ask in (CTRC_C, ""): + raise KeyboardInterrupt + else: + try: + ask = raw_input(full_message) + except (EOFError, KeyboardInterrupt): + printer("\n") + raise KeyboardInterrupt + ask = ask.lower() + like_answers = filter(lambda x: x[i_value].startswith(ask), + answers) + if not like_answers: + self.state.printERROR(_('The answer is uncertain')) + continue + if len(like_answers) == 1: + return like_answers[i_value][firstletter] + else: + self.state.printERROR(_('Ambiguous answer:') + + ",".join(map(lambda x: x[i_comment], + like_answers))) + + @echo_on + def askQuestion(self, message): + self.parent.printer("\n") + return raw_input(message + _(":")) + + def askPassword(self, message, twice): + from calculate.lib.utils.common import getpass + + old_tty = None + try: + if self.parent.terminal_info.is_boot_console(): + old_tty = get_active_tty() + set_active_tty(1) + + text1 = _("%s: ") % message + if not twice: + return getpass.getpass(text1) + text2 = _('Repeat: ') + pass1 = 'password' + pass2 = 'repeat' + try: + while pass1 != pass2: + pass1 = getpass.getpass(text1) + pass2 = getpass.getpass(text2) + if pass1 != pass2: + self.state.printERROR(_('Passwords do not match')) + except KeyboardInterrupt: + return None + passwd = pass1 if (pass1 and pass1 == pass2) else None + return passwd + finally: + if old_tty and old_tty.isdigit(): + set_active_tty(int(old_tty)) + + def printTable(self, table_name, head, body): + self.state.printSUCCESS(message=table_name) + self.parent.printer(printTable(body, head)) + + +class CleanState(TaskState): + """ + Ожидается вывод + """ + + def startTask(self, message, progress, num): + self.printMessage(Colors.GREEN, message) + self.parent.spinner = Spinner() + self.parent.set_state('start') + if progress: + self.parent.addProgress() + + def printERROR(self, message): + super(CleanState, self).printERROR(message) + self.parent.printer('\n') + + def printSUCCESS(self, message): + super(CleanState, self).printSUCCESS(message) + self.parent.printer('\n') + + def printWARNING(self, message): + super(CleanState, self).printWARNING(message) + self.parent.printer('\n') + + +class CleanStateNoProgress(CleanState): + """ + ... без отображения прогрессов + """ + + def startTask(self, message, progress, num): + self.display_asterisk(Colors.GREEN) + self.parent.printer(message) + self.dotting() + self.parent.set_state('start') + + +class StartState(TaskState): + """ + Выполняется задача (отображается spinner) + """ + + def startTask(self, message, progress, num): + self.parent.endTask(True) + self.parent.startTask(message, progress, num) + + def endTask(self, result, progress_message=None): + self.dotting() + self.parent.set_state('clean') + self.display_result(result) + + def breakTask(self): + self.dotting() + self.parent.set_state('clean') + self.parent.printer('\n') + + def printERROR(self, message): + self.dotting() + self.parent.printer('\n') + self.parent.set_state('clean') + self.state.printERROR(message) + + def printSUCCESS(self, message): + self.dotting() + self.parent.set_state('breaked') + self.state.printSUCCESS(message) + + def printWARNING(self, message): + self.dotting() + self.parent.set_state('breaked') + self.state.printWARNING(message) + + def startGroup(self, message): + self.state.endTask(True) + self.state.startGroup(message) + + def endGroup(self): + self.state.endTask(True) + self.state.endGroup() + + def beginFrame(self, message): + self.state.endTask(True) + self.state.beginFrame(message) + + def endFrame(self): + self.state.endTask(True) + self.state.endFrame() + + def addProgress(self, message): + self.parent.set_state("pre-progress") + self.state.addProgress(message) + + def printPre(self, message): + self.parent.endTask(True) + self.state.printPre(message) + + def printDefault(self, message): + self.state.endTask(True) + self.state.printDefault(message) + + def askChoice(self, message, answers): + self.breakTask() + return self.state.askChoice(message, answers) + + def askQuestion(self, message): + self.breakTask() + return self.state.askQuestion(message) + + def askPassword(self, message, twice): + self.breakTask() + return self.state.askPassword(message, twice) + + def askConfirm(self, message, default): + self.breakTask() + return self.state.askConfirm(message, default) + + def printTable(self, table_name, head, body): + self.breakTask() + self.state.printTable(table_name, head, body) + + +class StartStateNoProgress(StartState): + """ + ... без прогресса + """ + + def startTask(self, message, progress, num): + self.parent.endTask(True) + self.parent.startTask(message, progress, num) + + def endTask(self, result, progress_message=None): + self.parent.set_state('clean') + self.display_result(result) + + def breakTask(self): + self.parent.printer('\n') + + def printERROR(self, message): + self.breakTask() + self.parent.set_state('clean') + self.state.printERROR(message) + + def printSUCCESS(self, message): + self.breakTask() + self.parent.set_state('clean') + self.state.printSUCCESS(message) + + def printWARNING(self, message): + self.breakTask() + self.parent.set_state('clean') + self.state.printWARNING(message) + + def addProgress(self, message): + pass + + +class BreakedState(StartState): + """ + Во время выполнения задачи выведено сообщение + """ + + def stop_spinner_newline(self): + self.parent.spinner.stop() + self.parent.printer('\n') + + def startTask(self, message, progress, num): + self.state.endTask(True) + self.state.startTask(message, progress, num) + + def breakTask(self): + self.stop_spinner_newline() + self.parent.set_state('clean') + + def endTask(self, result, progress_message=None): + self.breakTask() + + def printERROR(self, message): + self.parent.endTask(True) + self.state.printERROR(message) + + def printSUCCESS(self, message): + self.stop_spinner_newline() + TaskState.printSUCCESS(self, message) + self.parent.spinner = Spinner() + + def printWARNING(self, message): + self.stop_spinner_newline() + TaskState.printWARNING(self, message) + self.parent.spinner = Spinner() + + +class PreProgressState(StartState): + """ + Задача запрошена как с прогрессом но проценты еще не обрабатывались + """ + + def addProgress(self, message): + pass + + def setProgress(self, percent, short_message, long_message): + self.parent.set_state("progress") + self.dotting() + self.parent.printer("\n") + self.parent.add_progressbar() + self.parent.terminal_info.cursor = False + self.state.setProgress(percent, short_message, long_message) + + +class ProgressState(StartState): + """ + Отображается progressbar + """ + + def finish_and_clean(self): + self.parent.printer('\r') + self.parent.printer.flush() + self.parent.progress.finish() + self.parent.terminal_info.cursor = True + self.parent.set_progressbar(None) + self.parent.printer.up(1).clear_line("") + self.parent.printer.up(1)("") + + def setProgress(self, percent, short_message, long_message): + if not 0 <= percent <= 100: + self.breakTask() + else: + self.parent.progress.update(percent) + + def breakTask(self): + self.finish_and_clean() + self.parent.set_state('clean') + self.parent.printer('\n') + + def endTask(self, result, progress_message=None): + self.finish_and_clean() + self.parent.set_state('clean') + self.display_result(result) + + def printERROR(self, message): + self.finish_and_clean() + self.parent.printer.down(1)("") + self.parent.set_state('clean') + self.state.printERROR(message) + + def printSUCCESS(self, message): + self.finish_and_clean() + self.parent.set_state('breaked') + self.state.printSUCCESS(message) + + def printWARNING(self, message): + self.finish_and_clean() + self.parent.set_state('breaked') + self.state.printWARNING(message) + + +class ResultViewer(object): + """ + Просмотрщик результатов + """ + + def __init__(self): + self.printer = \ + get_terminal_print(sys.stdout) + self.terminal_info = Terminal() + self.states = {'clean': CleanState(self), + 'breaked': BreakedState(self), + 'pre-progress': PreProgressState(self), + 'progress': ProgressState(self), + 'start': StartState(self)} + self.task_state = self.states['clean'] + self.spinner = None + self.progress = None + self.no_questions = False + + def set_no_progress(self): + self.states = {'clean': CleanStateNoProgress(self), + 'start': StartStateNoProgress(self)} + self.set_state('clean') + + def set_no_questions(self): + self.no_questions = True + + def set_state(self, state): + self.task_state = self.states[state] + + def add_progressbar(self): + self.set_progressbar(get_progress_bar()) + + def set_progressbar(self, pb): + self.progress = pb + + def endTask(self, result=None, progress_message=None): + self.task_state.endTask(result, progress_message) + + def startTask(self, message, progress=False, num=1): + self.task_state.startTask(message, progress, num) + + def printERROR(self, message, onlyShow=None): + if onlyShow != 'gui': + self.task_state.printERROR(message) + + def printSUCCESS(self, message, onlyShow=None): + if onlyShow != 'gui': + self.task_state.printSUCCESS(message) + + def printWARNING(self, message, onlyShow=None): + if onlyShow != 'gui': + self.task_state.printWARNING(message) + + def startGroup(self, message): + self.task_state.startGroup(message) + + def endGroup(self): + self.task_state.endGroup() + + def beginFrame(self, message=None): + self.task_state.beginFrame(message) + + def endFrame(self): + self.task_state.endFrame() + + def addProgress(self, message=None): + self.task_state.addProgress(message) + + def setProgress(self, percent, short_message=None, long_message=None): + self.task_state.setProgress(percent, short_message, long_message) + + def printPre(self, message, onlyShow=None): + if onlyShow != 'gui': + self.task_state.printPre(message) + + def printDefault(self, message='', onlyShow=None): + if onlyShow != 'gui': + self.task_state.printDefault(message) + + def askConfirm(self, message, default="yes"): + if self.no_questions: + return default + return self.task_state.askConfirm(message, default) + + def askChoice(self, message, answers=(("yes", "Yes"), ("no", "No"))): + return self.task_state.askChoice(message, answers) + + def askPassword(self, message, twice=False): + return self.task_state.askPassword(message, twice) + + def askQuestion(self, message): + return self.task_state.askQuestion(message) + + def printTable(self, table_name, head, body, fields=None, + onClick=None, addAction=None, step=None, records=None): + self.task_state.printTable(table_name, head, body) diff --git a/libs_crutch/core/result_viewer_gui.py b/libs_crutch/core/result_viewer_gui.py new file mode 100644 index 0000000..e88067c --- /dev/null +++ b/libs_crutch/core/result_viewer_gui.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from calculate.lib.cl_progressbar import get_progress_bar, get_message_box +from calculate.lib.utils.colortext import TextState +import sys +from .result_viewer import PreProgressState, ProgressState + +Colors = TextState.Colors +from calculate.lib.cl_lang import setLocalTranslate + +setLocalTranslate('cl_core3', sys.modules[__name__]) + + +class PreProgressStateGui(PreProgressState): + """ + Задача запрошена как с прогрессом но проценты еще не обрабатывались + """ + + def addProgress(self, message): + self.dotting() + self.parent.printer("\n") + self.parent.add_progressbar() + self.parent.set_state("progress") + + +class ProgressStateGui(ProgressState): + """ + Отображение для gui прогресса + """ + + def finish_and_clean(self): + self.parent.progress.finish() + self.parent.printer.up(1)("") + self.parent.set_progressbar(None) + + +class ResultViewerDecorator(object): + def __init__(self, rv): + self.rv = rv + for v in self.rv.states.values(): + v.parent = self + + def __getattr__(self, item): + return getattr(self.rv, item) + + +class ProgressGui(ResultViewerDecorator): + """ + Отображение прогресса в Qt диалогах + """ + + def __init__(self, rv): + super(ProgressGui, self).__init__(rv) + self.rv.states['pre-progress'] = PreProgressStateGui(self) + self.rv.states['progress'] = ProgressStateGui(self) + self.progress_title = "" + + def add_progressbar(self): + self.set_progressbar(get_progress_bar("gui", self.progress_title)) + + def startTask(self, message, progress=False, num=1): + self.rv.startTask(message, progress, num) + self.progress_title = message + + +class ErrorGui(ResultViewerDecorator): + """ + Отображение ошибок через gui + """ + + def __init__(self, rv): + super(ErrorGui, self).__init__(rv) + self.messages = [] + + def show_messages(self): + get_message_box().critical("\n".join(self.messages).decode('utf-8')) + + def printERROR(self, message, onlyShow=None): + self.rv.printERROR(message, onlyShow) + if onlyShow != 'gui': + if message: + self.messages.append(message) + + def endFrame(self): + self.rv.task_state.endFrame() + if self.messages: + self.show_messages() + + +class WarningGui(ResultViewerDecorator): + """ + Отображение предупреждений через gui + """ + + def __init__(self, rv): + super(WarningGui, self).__init__(rv) + self.warnings = [] + + def show_messages(self): + get_message_box().warning("\n".join(self.warnings).decode('utf-8')) + + def printWARNING(self, message, onlyShow=None): + self.rv.printWARNING(message, onlyShow) + if onlyShow != 'gui': + if message: + self.warnings.append(message) + + def endFrame(self): + self.rv.task_state.endFrame() + if not self.messages and self.warnings: + self.show_messages() + elif self.messages: + self.messages.extend(self.warnings) + self.rv.show_messages() diff --git a/libs_crutch/core/set_vars.py b/libs_crutch/core/set_vars.py new file mode 100644 index 0000000..b2ccbe9 --- /dev/null +++ b/libs_crutch/core/set_vars.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import sys +import re +from calculate.core.server.core_interfaces import MethodsInterface + +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + +VARIABLE, MODE, LOCATION, VALUE = 0, 1, 2, 3 + + +class Variables(MethodsInterface): + def writeVariables(self, vardata): + """ + Write variable to env files, or delete from env files + """ + dv = self.clVars + data = filter(lambda x: x[LOCATION] or + not x[LOCATION] and + dv.isFromIni(x[VARIABLE]), + vardata) + if data: + head = [_("Variable"), _("Mode"), _("Location"), _("Value")] + self.printTable(_("List of variables"), head, data) + for varname, mode, location, value in data: + if location: + value = dv.unserialize(dv.getInfo(varname).type, str(value)) + section, op, varname = varname.rpartition('.') + if not location: + for env_location in dv.Get('main.cl_env_location'): + if not dv.Delete(varname, env_location, header=section): + self.printWARNING( + _("Failed to delete variable {var} from " + "{location}").format( + var=varname, location=env_location)) + else: + if varname in dv.iniCache: + oldValue = dv.unserialize( + dv.getInfo(varname).type, + str(dv.iniCache[varname]['value'])) + else: + oldValue = None + if value != oldValue: + dv.Write(varname, value, location=location, + header=section) + else: + self.printSUCCESS("Nothing to set") + return True + + def showVariables(self, showVal, filterVal, vardata): + """ + Show variables by cl_variable_filter + """ + dv = self.clVars + removeQuotes = lambda x: x if x != "''" else "" + reIndex = re.compile("((?:\w+\.)?(\w+))(?:\[(\d+)\])") + if showVal: + index = reIndex.search(showVal) + if index: + varname = index.group(1) + index = int(index.group(3)) + else: + varname = showVal + prevVal = str(dv.Select('cl_variable_value', + where='cl_variable_fullname', + eq=varname, limit=1)) + if index is not None: + typeVar = dv.getInfo(varname).type + val = dv.unserialize(typeVar, prevVal) + if index < len(val): + self.printDefault(removeQuotes(val[index])) + else: + self.printDefault("") + else: + self.printDefault(removeQuotes(prevVal)) + return True + filter_names = {'all': None, + 'userset': lambda x: x[LOCATION], + 'writable': lambda x: x[MODE].startswith("w"), + 'system': lambda x: x[LOCATION] == "system", + 'local': lambda x: x[LOCATION] == "local", + 'remote': lambda x: x[LOCATION] == "remote"} + filterFunc = filter_names.get(filterVal, + lambda x: filterVal in x[VARIABLE]) + body = filter(filterFunc, vardata) + dv.close() + if body: + head = [_("Variable"), _("Mode"), + _("Location"), _("Value")] + self.printTable(_("List of variables"), head, body) + return True + else: + self.printWARNING(_("No such variables")) + return True diff --git a/libs_crutch/core/setup_cache.py b/libs_crutch/core/setup_cache.py new file mode 100644 index 0000000..6b95a64 --- /dev/null +++ b/libs_crutch/core/setup_cache.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- + +# Copyright 2015-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +import os +from os import path +from itertools import chain, groupby +import time + +from calculate.lib.datavars import DataVars +from calculate.lib.cl_template import templateFunction +from calculate.lib.utils.files import readFile, writeFile + +get_pkgname_by_filename = templateFunction.get_pkgname_by_filename + + +class Cache(object): + reMerge = re.compile("(merge|mergepkg)\(([-\w/]*)(?:\[[^\]]\])?\)[-!=<>]") + rePatch = re.compile("^#\s*Calculate.*ac_install_patch==on") + + PATCH_TYPE = "patch" + MERGE_TYPE = "merge" + DIRECTORY_TEMPLATE = ".calculate_directory" + CLT_SUFFIX = ".clt" + + def __init__(self, dv=None): + if dv is None: + dv = DataVars() + dv.importData() + dv.flIniFile() + self.dv = dv + self.base_dn = "/var/lib/calculate/calculate-core/cache" + self.fn_mtime = path.join(self.base_dn, "merge.mtime") + self.fn_patch = path.join(self.base_dn, "merge-patch.list") + self.fn_setup = path.join(self.base_dn, "merge-setup.list") + + def search_merge(self, dn): + """ + Сканировать директорию с шаблонами + """ + patch_dirs = [] + for root, dirs, files in os.walk(dn): + for fn in (path.join(root, x) for x in files): + data = readFile(fn) + if self.rePatch.search(data): + if path.basename(fn) == self.DIRECTORY_TEMPLATE: + patch_dirs.append(path.dirname(fn)) + patch_template = True + else: + if any(fn.startswith(x) for x in patch_dirs): + patch_template = True + else: + patch_template = False + for fname, pkg in self.reMerge.findall(data): + pkg = pkg or get_pkgname_by_filename(fn) + yield (self.PATCH_TYPE if patch_template + else self.MERGE_TYPE, pkg) + + def search_merge_clt(self, dn): + """ + Сканировать clt шаблоны + """ + for root, dirs, files in os.walk(dn): + for fn in (path.join(root, x) for x in files + if x.endswith(self.CLT_SUFFIX)): + data = readFile(fn) + for fname, pkg in self.reMerge.findall(data): + pkg = pkg or get_pkgname_by_filename(fn) + yield (self.MERGE_TYPE, pkg) + + @staticmethod + def check_new_that(mtime_fn, dirs, fn_filter=None): + """ + Проверить появились ли новые файлы после последней проверки + """ + if not path.exists(mtime_fn): + return True + check_mtime = os.stat(mtime_fn).st_mtime + for dn in dirs: + for root, dirs, files in os.walk(dn): + for fn in (path.join(root, x) for x in files + if fn_filter is None or fn_filter(x)): + if os.stat(fn).st_mtime > check_mtime: + return True + return False + + def update(self, force=False): + template_path = self.dv.Get('main.cl_template_path') + if (force or + self.check_new_that( + self.fn_mtime, ['/etc'], + fn_filter=lambda x: x.endswith(self.CLT_SUFFIX)) or + self.check_new_that(self.fn_mtime, template_path)): + all_packages = chain(self.search_merge_clt('/etc'), + *[self.search_merge(x) for x in template_path]) + for _type, pkgs in groupby(sorted(all_packages), lambda x: x[0]): + list_packages = sorted(set(y for x, y in pkgs)) + if _type == self.MERGE_TYPE: + with writeFile(self.fn_setup) as f: + f.write("\n".join(list_packages)) + if _type == self.PATCH_TYPE: + with writeFile(self.fn_patch) as f: + f.write("\n".join(list_packages)) + with writeFile(self.fn_mtime) as f: + f.write(str(time.time())) diff --git a/libs_crutch/core/setup_package.py b/libs_crutch/core/setup_package.py new file mode 100644 index 0000000..5a28078 --- /dev/null +++ b/libs_crutch/core/setup_package.py @@ -0,0 +1,414 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import sys +import os +from os import path + +from calculate.core.datavars import DataVars +from calculate.core.server.core_interfaces import MethodsInterface +from calculate.core.server.gen_pid import get_pid_info, ProcessMode +from calculate.lib.datavars import Variable +from calculate.lib.cl_log import log +from calculate.lib.utils.common import getPasswdUsers +from calculate.lib.utils.portage import isPkgInstalled, reVerSplitToPV +from calculate.lib.utils.colortext import get_color_print +import pwd +import glob +import calculate.lib.cl_template as cl_template + +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) + +__ = getLazyLocalTranslate(_) + + +class SetupPackageError(Exception): + """ + Исключение вызванное во время настройки пакета + """ + + +class UpdateLogger(MethodsInterface): + """ + Логгер для обновления настроек системы + """ + + logger = log("apply-templates", + filename="/var/log/calculate/update_config.log", + formatter="%(asctime)s - %(levelname)s - %(message)s") + + def ERROR(self, *arg, **argv): + """ + Вывести ошибку в лог и на экран + """ + self.logger.error(arg[0]) + self.printERROR(*arg, **argv) + + def SUCCESS(self, *arg, **argv): + """ + Вывести сообщение в лог и на экран + """ + self.logger.info(arg[0]) + self.printSUCCESS(*arg, **argv) + + def WARNING(self, *arg, **argv): + """ + Вывести предупреждение в лог и на экран + """ + self.logger.warn(arg[0]) + self.printWARNING(*arg, **argv) + + +class ChainProgressTemplate(cl_template.ProgressTemplate): + """ + Наложение шаблонов с определением перенастройки зависимых пакетов + """ + + def __init__(self, startTask, endTask, *args, **kwargs): + self.startTask = startTask + self.endTask = endTask + cl_template.ProgressTemplate.__init__(self, *args, **kwargs) + + def changeMergePackage(self, packages): + """ + Изменился настраиваемый пакет (по зависимостям) + """ + self.endTask() + packages = filter(isPkgInstalled, + packages) + self.startTask(_("Configuring dependencies: %s") % + ",".join(packages)) + return True + + +class StubVariable(Variable): + """ + Переменная-заглушка используется при обновлении настроек пакета + в emerge. Если переменная не найдена, то будет возвращена пустая + строка. + """ + value = "" + + +class UpdateConfigs(UpdateLogger): + """ + Обновить настройки пакета в пользовательских профилях + """ + + def init(self): + self.color_print = get_color_print() + + def getXUsers(self): + """ + Получить пользователей в X сессии + """ + return list( + self.clVars.Get('desktop.cl_desktop_online_user')) + ["root"] + + def getConfiguredPasswdUsers(self): + """ + Получить пользоватлей, которые есть в /etc/passwd (UID>=1000) + и при этом у них есть настройка профиля (.calculate/ini.env) + """ + user, dn = 0, 1 + iniEnv = ".calculate/ini.env" + return map(lambda x: x[user], + filter(lambda x: path.exists(path.join(x[dn], iniEnv)), + map(lambda x: (x, pwd.getpwnam(x).pw_dir), + getPasswdUsers()))) + + def _setClMergePkg(self, clVars, category, nameProgram, slot=None): + """ + Установить переменную cl_merge_pkg в зависимости от category и + nameProgram + """ + # выбрана перенастройка всех пакетов, установленных в системе + if nameProgram == "all": + clVars.Set("cl_merge_pkg", + map(lambda x: "{CATEGORY}/{PN}".format(**x), + filter(None, + map(reVerSplitToPV, + glob.glob('/var/db/pkg/*/*')))), + True) + else: + if slot: + clVars.Set("cl_merge_pkg", ["%s/%s:%s" % (category, + nameProgram, slot)], True) + else: + clVars.Set("cl_merge_pkg", ["%s/%s" % (category, nameProgram)], + True) + clVars.Set("cl_merge_set", "on", True) + + def updateDesktopConfig(self, nameProgram, version, slot, category, + configPath, + rootSet, verbose, dispatchConf, templates_locate, + ebuildPhase, useClt, arch_machine): + """ + Настроить пакеты в профилях пользователей + """ + # настраиватся будут пользователи из активных X сессии + # и сконфигурированные + xUsers = filter(lambda x: not "(unknown)" in x, + self.getXUsers()) + if not xUsers: + self.logger.info(_("Package %s") % nameProgram) + self.logger.warn(_("X session users not found")) + return True + self.logger.info(_("Package %s") % nameProgram) + self.logger.info(_("Updating user configuration files")) + firstValue = True + + clVars = DataVars() + try: + clVars.importData() + clVars.flIniFile() + setupable_users = set(xUsers + self.getConfiguredPasswdUsers()) + for userName in list(setupable_users): + clVars.Set("cl_root_path", '/', True) + clVars.Set("ur_login", userName, True) + clVars.Set("cl_action", "desktop", True) + clVars.Set("cl_verbose_set", verbose, True) + clVars.Set("cl_protect_use_set", "off", True) + clVars.Set("cl_template_path_use", templates_locate, True) + clVars.Set("install.os_install_arch_machine", arch_machine, + True) + + self._setClMergePkg(clVars, category, nameProgram) + + clTempl = ChainProgressTemplate(self.startTask, + self.endTask, + self.setProgress, + clVars, cltObj=False, + printSUCCESS=self.printSUCCESS, + printERROR=self.printERROR, + askConfirm=self.askConfirm, + printWARNING=self.printWARNING, + printWarning=False) + clTempl.onFirstValue = lambda *args: \ + self.startTask( + _("User configuring the {nameProgram} package by " + "Calculate Utilities").format( + nameProgram=nameProgram)) + clTempl.firstValue = firstValue + clTempl.applyTemplates() + firstValue = clTempl.firstValue + nofastlogin_users = set(getPasswdUsers()) - setupable_users + fastlogin_path = self.clVars.Get( + 'desktop.cl_desktop_fastlogin_path') + for user in nofastlogin_users: + fastlogin_user = path.join(fastlogin_path, user) + if path.exists(fastlogin_user): + try: + os.unlink(fastlogin_user) + except OSError: + pass + finally: + clVars.close() + self.endTask() + return True + + def updateSystemConfig(self, nameProgram, version, slot, category, + configPath, + rootSet, verbose, dispatchConf, templates_locate, + ebuildPhase, useClt, arch_machine): + """ + Обновить конфигурационные файлы системы + """ + self.logger.info(_("Package %s") % nameProgram) + self.logger.info(_("Updating system cofiguration files")) + if not os.path.exists(configPath): + self.ERROR(_("Path '%s' does not exist") % configPath) + return False + + clVars = DataVars() + try: + clVars.importData() + clVars.flIniFile() + clVars.Set("cl_root_path", configPath, True) + + # если конфигурирование пакета происходит не в корне + if rootSet: + # остальные пакеты настраиваются в корень + clVars.Set("cl_root_path_next", '/', True) + + if self.clVars.Get('core.cl_core_pkg_slot_opt') != "all": + self._setClMergePkg(clVars, category, nameProgram, slot) + else: + self._setClMergePkg(clVars, category, nameProgram) + clVars.Set("cl_action", 'merge', True) + clVars.Set("cl_verbose_set", verbose, True) + clVars.Set("cl_dispatch_conf", dispatchConf, True) + clVars.Set("cl_template_path_use", templates_locate, True) + clVars.Set("core.cl_core_pkg_slot", slot, True) + clVars.Set("install.os_install_arch_machine", arch_machine, True) + useClt = useClt in (True, "on") + + dictVer = {slot: version} + cl_template.templateFunction.installProg.update( + {"%s/%s" % (category, nameProgram): dictVer, + "%s" % nameProgram: dictVer}) + + # используем объект шаблонов + # с clt шаблонами, clt фильтром, без использования postDispatchConf + clTempl = ChainProgressTemplate( + self.startTask, + self.endTask, + self.setProgress, + clVars, cltObj=useClt, + cltFilter=True, + printSUCCESS=self.printSUCCESS, + printERROR=self.printERROR, + printWARNING=self.printWARNING, + askConfirm=self.askConfirm, + dispatchConf=self.dispatchConf + if not ebuildPhase and self.isInteractive() else None, + printWarning=False) + # выводим сообщение о настройке пакета только если действительно + # менялись файлы + clTempl.onFirstValue = lambda *args: \ + self.startTask(_("System configuring for {nameProgram} " + "package by Calculate Utilities").format( + nameProgram=nameProgram)) + clTempl.applyTemplates() + finally: + clVars.close() + self.endTask() + return True + + def patchPackage(self, configPath, nameProgram, arch_machine): + """ + Наложить патчи на пакет + """ + self.clVars.Set("cl_root_path", configPath, True) + self.clVars.Set("install.os_install_arch_machine", arch_machine, True) + clTempl = ChainProgressTemplate(self.startTask, + self.endTask, + self.setProgress, + self.clVars, cltObj=False, + printSUCCESS=self.printSUCCESS, + printERROR=self.printERROR, + askConfirm=self.askConfirm, + printWARNING=self.printWARNING, + printWarning=False) + clTempl.onFirstValue = lambda *args: self.startTask( + _("Using patches for the {nameProgram} package by " + "Calculate Utilities").format( + nameProgram=nameProgram), + progress=True) + clTempl.applyTemplates() + return True + + def checkRunning(self): + """ + Проверить наличие запущенных процессов в cl-core + """ + from calculate.core.server.loaded_methods import LoadedMethods + + cur_pid = os.getpid() + pid_list = [pid for pid in get_pid_info(self.clVars) + if (pid.get("mode", '') == ProcessMode.CoreDaemon and + pid.get("os_pid", '') != cur_pid)] + if pid_list: + _print = self.color_print + method_names = {value[0]: value[2] for key, value in + LoadedMethods.conMethods.items()} + self.printSUCCESS( + _("Calculate core is executing the following tasks")) + mult = _print.bold("*") + for pid in pid_list: + name = pid['name'] + method_name = method_names.get(name, name) + self.printDefault( + " {mult} {title} ({name})".format(mult=mult, + title=method_name, + name=name)) + answer = self.askConfirm( + _("Would you like to terminate these tasks?"), "no") + if answer == "no": + raise KeyboardInterrupt + return True + + def restartService(self, service_name): + """ + Перезапустить указанный сервис + """ + import time + + time.sleep(1) + os.system('/etc/init.d/%s restart &>/dev/null &' % service_name) + return True + + def processConfig(self, nameProgram, version, slot, category, + verbose, dispatchConf, templates_locate, + ebuildPhase, useClt, arch_machine): + """ + Обновить конфигурационные файлы системы + """ + self.logger.info(_("Package %s") % nameProgram) + self.logger.info(_("Updating system cofiguration files")) + + clVars = DataVars() + try: + clVars.importData() + clVars.flIniFile() + clVars.Set("cl_root_path", "/", True) + + if self.clVars.Get('core.cl_core_pkg_slot_opt') != "all": + self._setClMergePkg(clVars, category, nameProgram, slot) + else: + self._setClMergePkg(clVars, category, nameProgram) + clVars.Set("cl_action", 'config', True) + clVars.Set("cl_verbose_set", verbose, True) + clVars.Set("cl_dispatch_conf", dispatchConf, True) + clVars.Set("cl_template_path_use", templates_locate, True) + clVars.Set("core.cl_core_pkg_slot", slot, True) + useClt = False + + #dictVer = {slot: version} + #cl_template.templateFunction.installProg.update( + # {"%s/%s" % (category, nameProgram): dictVer, + # "%s" % nameProgram: dictVer}) + + # используем объект шаблонов + # с clt шаблонами, clt фильтром, без использования postDispatchConf + clTempl = ChainProgressTemplate( + self.startTask, + self.endTask, + self.setProgress, + clVars, cltObj=useClt, + cltFilter=True, + printSUCCESS=self.printSUCCESS, + printERROR=self.printERROR, + printWARNING=self.printWARNING, + askConfirm=self.askConfirm, + dispatchConf=self.dispatchConf + if not ebuildPhase and self.isInteractive() else None, + printWarning=False) + # выводим сообщение о настройке пакета только если действительно + # менялись файлы + clTempl.onFirstValue = lambda *args: \ + self.startTask(_("System configuring for {nameProgram} " + "package by Calculate Utilities").format( + nameProgram=nameProgram)) + clTempl.applyTemplates() + finally: + clVars.close() + self.endTask() + return True diff --git a/libs_crutch/core/utils/cl_backup.py b/libs_crutch/core/utils/cl_backup.py new file mode 100644 index 0000000..e795fb6 --- /dev/null +++ b/libs_crutch/core/utils/cl_backup.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +# Copyright 2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action, Tasks +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.utils.files import FilesError +from calculate.lib.datavars import VariableError, DataVarsError +from calculate.core.backup import BackupError +from calculate.lib.cl_template import TemplatesError + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClBackupAction(Action): + """ + Создание резервной копии настроек + """ + # ошибки, которые отображаются без подробностей + native_error = (BackupError, FilesError, DataVarsError, VariableError, + TemplatesError) + successMessage = __("Backup successfully completed!") + failedMessage = __("Failed to perform backup!") + interruptMessage = __("Backup manually interrupted") + + tasks = [ + {'name': 'prepare_dir', + 'method': 'Backup.prepare_backup(core.cl_backup_path,' + 'core.cl_backup_root_name)' + }, + {'name': 'backup_marked', + 'message': __("Backing up files configured by templates"), + 'method': 'Backup.backup_marked("/",core.cl_backup_path,' + '"etc",core.cl_backup_root_name)' + }, + {'name': 'templates', + 'message': __("Templates preparing for backup"), + 'method': 'Backup.applyTemplates(install.cl_source,' + 'False,True,None,True,True)', + }, + {'name': 'special_backup', + 'method': 'Backup.special_backup(core.cl_backup_path)' + }, + {'name': 'accounts_backup', + 'message': __("Backing up accounts info"), + 'method': 'Backup.save_accounts(core.cl_backup_path)' + }, + {'name': 'prepare_content', + 'message': __("Calculating checksums"), + 'method': 'Backup.prepare_contents(core.cl_backup_path,' + 'core.cl_backup_file_contents,core.cl_backup_root_name)', + }, + {'name': 'save_initd', + 'method': 'Backup.save_initd(core.cl_backup_path,' + 'core.cl_backup_root_name)', + }, + {'name': 'pack_backup', + 'message': __("Packing backup"), + 'method': 'Backup.create_archive(core.cl_backup_path,' + 'core.cl_backup_file)' + }, + {'name': 'remove_dir', + 'message': __("Clearing temporary files"), + 'method': 'Backup.remove_directory(core.cl_backup_path)', + 'depend': Tasks.success_one_of("prepare_dir") + }, + {'name': 'display_verbose', + 'method': 'Backup.display_backup_configs(core.cl_backup_file)', + 'condition': lambda Get: Get('core.cl_backup_verbose_set') == 'on' + }, + {'name': 'display_arch', + 'message': __("Archive created: {core.cl_backup_file}") + } + ] + diff --git a/libs_crutch/core/utils/cl_backup_restore.py b/libs_crutch/core/utils/cl_backup_restore.py new file mode 100644 index 0000000..16e62b0 --- /dev/null +++ b/libs_crutch/core/utils/cl_backup_restore.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +# Copyright 2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action, Tasks +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.utils.files import FilesError +from calculate.lib.datavars import VariableError, DataVarsError +from calculate.core.backup import BackupError +from calculate.lib.cl_template import TemplatesError +from tarfile import ReadError + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClBackupRestoreAction(Action): + """ + Восстановление настроек из резервной копии + """ + # ошибки, которые отображаются без подробностей + native_error = (BackupError, FilesError, DataVarsError, VariableError, + TemplatesError, ReadError) + successMessage = __("Files successfully restored from backup!") + failedMessage = __("Failed to restore from backup!") + interruptMessage = __("Restoration from backup manually interrupted") + + restore_tasks = [] + + tasks = [ + {'name': 'unpack_backup', + 'message': __("Unpacking backup"), + 'method': 'Backup.open_archive(core.cl_backup_path,' + 'core.cl_backup_file)' + }, + {'name': 'restore_accounts', + 'message': __("Restoring user accounts"), + 'method': 'Backup.restore_accounts(core.cl_backup_path)' + }, + {'name': 'restore_network', + 'message': __("Restoring network"), + 'method': 'Backup.restore_network(core.cl_backup_path)' + }, + {'name': 'special_restore', + 'method': 'Backup.special_restore(core.cl_backup_path)' + }, + {'name': 'templates', + 'message': __("Restoring services"), + 'method': 'Backup.applyTemplates(install.cl_source,' + 'False,True,None,True,True)', + }, + {'name': 'clear_autorun', + 'method': 'Backup.clear_autorun()', + }, + {'name': 'restore_configs', + 'message': __("Unpacking configuration files"), + 'method': 'Backup.restore_configs(core.cl_backup_file,"/",' + 'core.cl_backup_contents_name,core.cl_backup_root_name)' + }, + {'name': 'restore_content', + 'message': __("Restoring file owners"), + 'method': 'Backup.restore_contents(core.cl_backup_file_contents,' + '"/")', + }, + {'name': 'display_verbose', + 'method': 'Backup.display_changed_configs()', + 'condition': lambda Get: Get('core.cl_backup_verbose_set') == 'on' + }, + {'name': 'set_service_mode', + 'method': 'Backup.set_service_action()', + }, + {'name': 'templates_service', + 'message': __("Configuration after restoring from backup"), + 'method': 'Backup.applyTemplates(install.cl_source,' + 'False,True,None,True,True)', + }, + {'name': 'remove_dir', + 'method': 'Backup.remove_directory(core.cl_backup_path)', + 'depend': Tasks.success_one_of("unpack_backup") + }, + {'name': 'dispatch_conf', + 'message': __("Updating configuration files"), + 'method': 'Backup.dispatchConf()', + 'condition': lambda Get: Get('cl_dispatch_conf') != 'skip' + }, + {'name': 'openrc_default', + 'message': __("Running stopped services"), + 'method': 'Backup.run_openrc("default")', + } + ] diff --git a/libs_crutch/core/utils/cl_config.py b/libs_crutch/core/utils/cl_config.py new file mode 100644 index 0000000..5150bcb --- /dev/null +++ b/libs_crutch/core/utils/cl_config.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.utils.files import FilesError +from calculate.lib.datavars import VariableError, DataVarsError +from calculate.lib.cl_template import TemplatesError + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClConfigAction(Action): + """ + Действие настройка пакета для пользователя и системы + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError, TemplatesError, VariableError, DataVarsError) + templateTaskMessage = __("The system is being configured") + successMessage = None + failedMessage = __("Failed to configure the system!") + interruptMessage = __("Configuration manually interrupted") + + tasks = [ + {'name': 'process_config', + # наложить шаблоны настройки пакета + 'method': 'UpdateConfigs.processConfig(cl_core_pkg_name,' + 'cl_core_pkg_version,cl_core_pkg_slot,' + 'cl_core_pkg_category,cl_verbose_set,cl_dispatch_conf,' + 'cl_template_path_use,cl_ebuild_phase,' + 'cl_template_clt_set,cl_core_arch_machine)', + }, + ] diff --git a/libs_crutch/core/utils/cl_core_custom.py b/libs_crutch/core/utils/cl_core_custom.py new file mode 100644 index 0000000..6a63c50 --- /dev/null +++ b/libs_crutch/core/utils/cl_core_custom.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +# Copyright 2013-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.datavars import VariableError +from calculate.lib.cl_template import TemplatesError +from calculate.lib.utils.files import FilesError + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClCoreCustomAction(Action): + """ + Действие для настройки параметров видео + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError, TemplatesError, VariableError) + + successMessage = __("Action successfully completed!") + failedMessage = __("Failed to perform action!") + interruptMessage = __("Action manually interrupted") + + def __init__(self): + # список задач для действия + self.tasks = [ + {'name': 'set_vars', + 'method': ( + 'UpdateConfigs.setVariable("install.os_install_arch_machine",' + 'cl_core_arch_machine, True)') + }, + {'name': 'apply_templates', + # наложить шаблоны на текущий дистрибутив, включая clt шаблоны + # без использования фильтров по clt шаблонам + 'method': 'UpdateConfigs.applyTemplates(None,False,' + 'None,None)', + }, + {'name': 'failed_action', + 'error': __("Action {ac_custom_name} not found"), + 'condition': lambda Get: not filter( + lambda x: (x and x[0] == 'ac_custom_name' and + x[1] == Get('ac_custom_name')), + Get('cl_used_action')) + } + ] + + Action.__init__(self) diff --git a/libs_crutch/core/utils/cl_core_dispatch.py b/libs_crutch/core/utils/cl_core_dispatch.py new file mode 100644 index 0000000..60e9e36 --- /dev/null +++ b/libs_crutch/core/utils/cl_core_dispatch.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.utils.files import FilesError + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClCoreDispatchAction(Action): + """ + Действие обновление конфигурационных файлов + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError,) + + successMessage = __("Dispatch complete!") + failedMessage = __("Failed to dispatch configuration files!") + interruptMessage = __("Dispatching manually interrupted") + + # список задач для действия + tasks = [ + {'name': 'dispatch', + 'method': 'UpdateConfigs.dispatchConf()', + }] diff --git a/libs_crutch/core/utils/cl_core_group.py b/libs_crutch/core/utils/cl_core_group.py new file mode 100644 index 0000000..c008191 --- /dev/null +++ b/libs_crutch/core/utils/cl_core_group.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.utils.files import FilesError + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClCoreGroupShow(Action): + """ + Отображение групп + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError,) + successMessage = None + failedMessage = None + interruptMessage = __("Viewing manually interrupted") + + tasks = [ + {'name': 'view_group', + 'method': 'Groups.show_groups_meth(cl_page_count,cl_page_offset)' + }] + + +class ClCoreGroupMod(Action): + """ + Изменение группы + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError,) + successMessage = __("Group {cl_core_group} changed") + failedMessage = __("Failed to change {cl_core_group} group") + interruptMessage = __("Modifying manually interrupted") + + tasks = [ + {'name': 'mod_group', + 'method': ( + 'Groups.change_group_meth(cl_core_group,cl_core_group_rights,' + 'cl_core_group_rights_path)') + }] + + +class ClCoreGroupAdd(Action): + """ + Добавление группы + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError,) + successMessage = __("Group {cl_core_group} added") + failedMessage = __("Failed to add {cl_core_group} group") + interruptMessage = __("Adding manually interrupted") + + tasks = [ + {'name': 'add_group', + 'method': 'Groups.add_group_meth(cl_core_group,cl_core_group_rights,' + 'cl_core_group_rights_path)' + }] + + +class ClCoreGroupDel(Action): + """ + Удаление группы + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError,) + successMessage = __("Group {cl_core_group} deleted") + failedMessage = __("Failed to delete {cl_core_group} group") + interruptMessage = __("Deleting manually interrupted") + + tasks = [ + {'name': 'del_group', + 'method': 'Groups.del_group_meth(cl_core_group,' + 'cl_core_group_rights_path)' + }] diff --git a/libs_crutch/core/utils/cl_core_patch.py b/libs_crutch/core/utils/cl_core_patch.py new file mode 100644 index 0000000..2dbe807 --- /dev/null +++ b/libs_crutch/core/utils/cl_core_patch.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.utils.files import FilesError +from calculate.lib.datavars import VariableError, DataVarsError +from calculate.lib.cl_template import TemplatesError + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClCorePatchAction(Action): + """ + Действие наложния патчей + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError, TemplatesError, VariableError, DataVarsError) + templateTaskMessage = __("Appling patches to package sources") + successMessage = None + failedMessage = __("Failed to patch package sources!") + interruptMessage = __("Patching manually interrupted") + + tasks = [ + {'name': 'patch_package', + # наложить патчи для пакета + 'method': 'UpdateConfigs.patchPackage(cl_core_pkg_path,' + 'cl_core_pkg_name,cl_core_arch_machine)' + } + ] diff --git a/libs_crutch/core/utils/cl_core_request.py b/libs_crutch/core/utils/cl_core_request.py new file mode 100644 index 0000000..a7f7487 --- /dev/null +++ b/libs_crutch/core/utils/cl_core_request.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.utils.files import FilesError + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClCoreRequestShow(Action): + """ + Отображение запросов + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError,) + successMessage = None + failedMessage = None + interruptMessage = __("Viewing manually interrupted") + + tasks = [ + {'name': 'view_req', + 'method': 'Request.show_request_meth(cl_page_count,cl_page_offset)' + }] + + +class ClCoreRequestConfirm(Action): + """ + Подтверждение запроса + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError,) + successMessage = __("Certificate {cl_req_crt_path} is signed") + failedMessage = __("Failed to sign {cl_req_csr_path} request") + interruptMessage = __("Signing manually interrupted") + + tasks = [ + {'name': 'mod_req', + 'method': 'Request.confirm_request_meth(cl_core_client_certs_path,' + 'cl_core_cert_path,cl_req_csr_path,cl_req_crt_path,' + 'cl_req_group)' + }] + + +class ClCoreRequestDel(Action): + """ + Удаление запроса + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError,) + successMessage = None + failedMessage = __("Failed to delete the request with ID={cl_req_id}") + interruptMessage = __("Deleting manually interrupted") + + tasks = [ + {'name': 'del_req', + 'method': 'Request.del_request_meth(cl_core_database,cl_req_csr_path,' + 'cl_req_crt_path,cl_req_id)' + }] diff --git a/libs_crutch/core/utils/cl_core_restart.py b/libs_crutch/core/utils/cl_core_restart.py new file mode 100644 index 0000000..e620312 --- /dev/null +++ b/libs_crutch/core/utils/cl_core_restart.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# Copyright 2013-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.datavars import VariableError +from calculate.lib.cl_template import TemplatesError +from calculate.lib.utils.files import FilesError + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClCoreRestartAction(Action): + """ + Действие для настройки параметров видео + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError, TemplatesError, VariableError) + + successMessage = __("Action successfully completed!") + failedMessage = __("Failed to perform action!") + interruptMessage = __("Action manually interrupted") + + def __init__(self): + # список задач для действия + self.tasks = [ + {'name': 'check_running', + # проверить запущенные процессы + 'method': 'UpdateConfigs.checkRunning()', + }, + {'name': 'restart', + # перезапустить calculate-core + 'message': _("Restarting calculate-core"), + 'method': 'UpdateConfigs.restartService("calculate-core")', + }] + + Action.__init__(self) diff --git a/libs_crutch/core/utils/cl_core_setup.py b/libs_crutch/core/utils/cl_core_setup.py new file mode 100644 index 0000000..119b022 --- /dev/null +++ b/libs_crutch/core/utils/cl_core_setup.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.utils.files import FilesError +from calculate.lib.datavars import VariableError, DataVarsError +from calculate.lib.cl_template import TemplatesError + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClCoreSetupAction(Action): + """ + Действие настройка пакета для пользователя и системы + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError, TemplatesError, VariableError, DataVarsError) + templateTaskMessage = __("The system is being configured") + successMessage = None + failedMessage = __("Failed to configure the system!") + interruptMessage = __("Configuration manually interrupted") + + tasks = [ + {'name': 'system_rm_package', + # наложить шаблоны удаления пакета + 'method': 'UpdateConfigs.updateSystemConfig(cl_core_pkg_name,' + '"",cl_core_pkg_slot,cl_core_pkg_category,cl_core_pkg_path,' + 'cl_core_pkg_root_set,cl_verbose_set,cl_dispatch_conf,' + 'cl_template_path_use,cl_ebuild_phase,' + 'cl_template_clt_set,cl_core_arch_machine)', + 'condition': lambda dv: ( + dv.Get('cl_core_pkg_system_set') == 'on' and + dv.Get('cl_ebuild_phase') in ('prerm', 'postrm')) + }, + {'name': 'system_setup_package', + # наложить шаблоны настройки пакета + 'method': 'UpdateConfigs.updateSystemConfig(cl_core_pkg_name,' + 'cl_core_pkg_version,cl_core_pkg_slot,' + 'cl_core_pkg_category,cl_core_pkg_path,' + 'cl_core_pkg_root_set,cl_verbose_set,cl_dispatch_conf,' + 'cl_template_path_use,cl_ebuild_phase,' + 'cl_template_clt_set,cl_core_arch_machine)', + 'condition': lambda dv: ( + dv.Get('cl_core_pkg_system_set') == 'on' and + dv.Get('cl_ebuild_phase') not in ('prerm', 'postrm')) + }, + {'name': 'user_setup_package', + # наложить шаблоны настройки пакета + 'method': 'UpdateConfigs.updateDesktopConfig(cl_core_pkg_name,' + 'cl_core_pkg_version,cl_core_pkg_slot,' + 'cl_core_pkg_category,cl_core_pkg_path,' + 'cl_core_pkg_root_set,cl_verbose_set,cl_dispatch_conf,' + 'cl_template_path_use,cl_ebuild_phase,' + 'cl_template_clt_set,cl_core_arch_machine)', + 'condition': lambda dv: ( + dv.Get('cl_core_pkg_desktop_set') == 'on' and + dv.isModuleInstalled('desktop') and + dv.Get('cl_ebuild_phase') not in ("preinst", "prerm")) + } + ] diff --git a/libs_crutch/core/utils/cl_core_variables.py b/libs_crutch/core/utils/cl_core_variables.py new file mode 100644 index 0000000..019fc82 --- /dev/null +++ b/libs_crutch/core/utils/cl_core_variables.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.utils.files import FilesError +from calculate.lib.datavars import VariableError, DataVarsError + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClCoreVariables(Action): + """ + Отображение сертификатов + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError, DataVarsError, VariableError) + successMessage = None + failedMessage = None + interruptMessage = __("Modification of variables manually interrupted") + + tasks = [ + {'name': 'write_vars', + # записать переменные + 'method': 'Variables.writeVariables(cl_variable_data)' + } + ] + + +class ClCoreVariablesShow(Action): + """ + Отображение сертификатов + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError, DataVarsError, VariableError) + successMessage = None + failedMessage = None + interruptMessage = __("Viewing manually interrupted") + + tasks = [ + {'name': 'view_vars', + # отобразить переменные + 'method': 'Variables.showVariables(cl_variable_show,' + 'cl_variable_filter,cl_variable_data)' + } + ] diff --git a/libs_crutch/core/utils/cl_core_view_cert.py b/libs_crutch/core/utils/cl_core_view_cert.py new file mode 100644 index 0000000..6237aea --- /dev/null +++ b/libs_crutch/core/utils/cl_core_view_cert.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.utils.files import FilesError + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClCoreViewCert(Action): + """ + Отображение сертификатов + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError,) + successMessage = None + failedMessage = None + interruptMessage = __("Viewing manually interrupted") + + tasks = [ + {'name': 'view_cert', + # наложить патчи для пакета + 'method': 'Certificate.show_certs_meth(cl_page_count,cl_page_offset)' + } + ] diff --git a/libs_crutch/core/wsdl_core.py b/libs_crutch/core/wsdl_core.py new file mode 100644 index 0000000..1cb13af --- /dev/null +++ b/libs_crutch/core/wsdl_core.py @@ -0,0 +1,795 @@ +# -*- coding: utf-8 -*- + +# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +import sys + +from calculate.lib.datavars import VariableError, DataVarsError + +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate + +_ = lambda x: x +setLocalTranslate('cl_core3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + +from . import setup_package +from . import server.certificate as certificate +from . import server.groups as groups +from . import server.request as request +from . import set_vars +from .backup import Backup, BackupError +from calculate.core.utils.cl_backup import ClBackupAction +from calculate.core.utils.cl_backup_restore import ClBackupRestoreAction +from calculate.core.server.func import WsdlBase +from calculate.core.utils.cl_core_setup import ClCoreSetupAction +from calculate.core.utils.cl_core_patch import ClCorePatchAction +from calculate.core.utils.cl_config import ClConfigAction +from calculate.core.utils.cl_core_dispatch import ClCoreDispatchAction +from calculate.core.utils.cl_core_view_cert import ClCoreViewCert +from calculate.core.utils.cl_core_group import ( + ClCoreGroupShow, ClCoreGroupMod, ClCoreGroupAdd, ClCoreGroupDel) +from calculate.core.utils.cl_core_request import ( + ClCoreRequestShow, ClCoreRequestConfirm, ClCoreRequestDel) +from calculate.core.utils.cl_core_variables import (ClCoreVariables, + ClCoreVariablesShow) +from calculate.core.utils.cl_core_custom import ClCoreCustomAction +from calculate.core.utils.cl_core_restart import ClCoreRestartAction +from calculate.core.variables.action import Actions + + +class Wsdl(WsdlBase): + methods = [ + # + # Настройка пакета во время установки (cl-core-setup) + # + { + # идентификатор метода + 'method_name': "core_setup", + # категория метода + 'category': __('Configuration'), + # заголовок метода + 'title': __("Configure a Package"), + # иконка для графической консоли + 'image': 'calculate-core-setup,' + 'preferences-desktop-default-applications', + # метод присутствует в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-core-setup', + # права для запуска метода + 'rights': ['setup_package'], + # объект содержащий модули для действия + 'logic': {'UpdateConfigs': setup_package.UpdateConfigs}, + # описание действия + 'action': ClCoreSetupAction, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError, + setup_package.SetupPackageError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'merge', 'cl_verbose_set': "on"}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Configure a package"), + normal=('cl_core_pkg_name',), + expert=('cl_core_pkg_category', + 'cl_core_pkg_version_opt', + 'cl_core_pkg_slot_opt', + 'cl_core_pkg_path', + 'cl_core_arch_machine', + 'cl_templates_locate', + 'cl_core_pkg_system_set', + 'cl_core_pkg_desktop_set', + 'cl_core_pkg_root_set', + 'cl_verbose_set', + 'cl_dispatch_conf'), + next_label=_("Run"))]}, + # + # Патч исходников пакета (cl-core-patch) + # + { + # идентификатор метода + 'method_name': "core_patch", + # категория метода + 'category': __('Configuration'), + # заголовок метода + 'title': __("Patch"), + # иконка для графической консоли + 'image': None, + # метода нет в графической консоли + 'gui': False, + # консольная команда + 'command': 'cl-core-patch', + # права для запуска метода + 'rights': ['configure'], + # объект содержащий модули для действия + 'logic': {'UpdateConfigs': setup_package.UpdateConfigs}, + # описание действия + 'action': ClCorePatchAction, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError, + setup_package.SetupPackageError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'patch', + 'cl_protect_use_set!': 'off'}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Configure a package"), + normal=('cl_core_pkg_name',), + expert=('cl_core_pkg_category', + 'cl_core_pkg_version', + 'cl_core_pkg_slot', + 'cl_core_pkg_path', + 'cl_core_arch_machine', + 'cl_templates_locate', + 'cl_verbose_set'), + next_label=_("Run"))]}, + # + # Обновление конфигурационных файлов (cl-dispatch-conf) + # + { + # идентификатор метода + 'method_name': "core_dispatch", + # категория метода + 'category': __('Update '), + # заголовок метода + 'title': __("Update Settings"), + # иконка для графической консоли + 'image': 'calculate-core-dispatch,edit-find-replace,computer', + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-dispatch-conf', + # права для запуска метода + 'rights': ['configure'], + # объект содержащий модули для действия + 'logic': {'UpdateConfigs': setup_package.UpdateConfigs}, + # описание действия + 'action': ClCoreDispatchAction, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError, + setup_package.SetupPackageError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'dispatch'}, + # описание груп (список лямбда функций) + 'groups': []}, + # + # Отобразить сертификаты + # + { + # идентификатор метода + 'method_name': "core_view_cert", + # категория метода + 'category': __('Utilities'), + # заголовок метода + 'title': __("Show Certificates"), + # иконка для графической консоли + 'image': 'calculate-core-view-cert,certificate-server,' + 'application-certificate', + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-core-view-cert', + # права для запуска метода + 'rights': ['certificates'], + # объект содержащий модули для действия + 'logic': {'Certificate': certificate.Certificate}, + # описание действия + 'action': ClCoreViewCert, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': { + 'cl_page_max!': lambda dv: len(dv.Get('cl_list_cert_id'))}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Certificates"), + normal=('cl_page_count', 'cl_page_offset'), + next_label=_("Next"))]}, + # + # Отобразить детали сертификата + # + { + # идентификатор метода + 'method_name': "core_detail_view_cert", + # категория метода + # 'category':__('Utilities'), + # заголовок метода + 'title': __("Certificate Details"), + # иконка для графической консоли + 'image': None, + # метод в графической консоли + 'gui': True, + # права для запуска метода + 'rights': ['certificates'], + # объект содержащий модули для действия + 'logic': {}, + # описание действия + 'action': None, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Certificate details"), + normal=('cl_cert_id', 'cl_cert_groups', + 'cl_cert_perms'), + custom_buttons=[('but0', _("Back"), + "core_view_cert", + "button")])]}, + # + # Группы + # + { + # идентификатор метода + 'method_name': "core_group_show", + # категория метода + 'category': __('Utilities'), + # заголовок метода + 'title': __("Show Groups"), + # иконка для графической консоли + 'image': 'calculate-core-group-show,user-group-properties,' + 'view-certificate-import,application-certificate', + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-core-group-show', + # права для запуска метода + 'rights': ['core_group'], + # объект содержащий модули для действия + 'logic': {'Groups': groups.Groups}, + # описание действия + 'action': ClCoreGroupShow, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': { + 'cl_page_max!': lambda dv: len(dv.Choice('cl_core_group'))}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Groups"), + normal=('cl_page_count', 'cl_page_offset'), + next_label=_("Next"))]}, + # + # Отобразить детали группы + # + { + # идентификатор метода + 'method_name': "core_detail_group", + # категория метода + # 'category':__('Utilities'), + # заголовок метода + 'title': __("Group Details"), + # иконка для графической консоли + 'image': None, + # метод в графической консоли + 'gui': True, + # права для запуска метода + 'rights': ['core_group'], + # объект содержащий модули для действия + 'logic': {}, + # описание действия + 'action': None, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Group details"), + normal=( + 'cl_core_group', + 'cl_core_group_rights'), + custom_buttons=[('but0', _("Back"), + "core_group_show", + "button"), + ('but1', _("Change"), + "core_group_mod", + "button"), + ('but2', _("Delete"), + "core_group_del", + "button")])]}, + # + # Изменить группу + # + { + # идентификатор метода + 'method_name': "core_group_mod", + # категория метода + # 'category':__('Utilities'), + # заголовок метода + 'title': __("Modify Group"), + # иконка для графической консоли + 'image': None, + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-core-groupmod', + # права для запуска метода + 'rights': ['core_group'], + # объект содержащий модули для действия + 'logic': {'Groups': groups.Groups}, + # описание действия + 'action': ClCoreGroupMod, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'modify'}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Modify group"), + normal=( + 'cl_core_group', + 'cl_core_group_rights'), + next_label=_("Done"), + custom_buttons=[('but2', _("Confirm"), + 'core_change_group', + "button")])]}, + # + # Добавить группу + # + { + # идентификатор метода + 'method_name': "core_group_add", + # категория метода + # 'category':__('Utilities'), + # заголовок метода + 'title': __("Add a Group"), + # иконка для графической консоли + 'image': None, + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-core-groupadd', + # права для запуска метода + 'rights': ['core_group'], + # объект содержащий модули для действия + 'logic': {'Groups': groups.Groups}, + # описание действия + 'action': ClCoreGroupAdd, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'add'}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Add a group"), + normal=( + 'cl_core_group', + 'cl_core_group_rights'), + next_label=_("Add"))]}, + # + # Удалить группу + # + { + # идентификатор метода + 'method_name': "core_group_del", + # категория метода + # 'category':__('Utilities'), + # заголовок метода + 'title': __("Delete the Group"), + # иконка для графической консоли + 'image': None, + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-core-groupdel', + # права для запуска метода + 'rights': ['core_group'], + # объект содержащий модули для действия + 'logic': {'Groups': groups.Groups}, + # описание действия + 'action': ClCoreGroupDel, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'delete'}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Delete the group"), + normal=('cl_core_group',), + next_label=_("Delete"))]}, + # + # Запрос на сертификат + # + { + # идентификатор метода + 'method_name': "core_request_show", + # категория метода + 'category': __('Utilities'), + # заголовок метода + 'title': __("Show Requests"), + # иконка для графической консоли + 'image': 'calculate-core-request-show,view-certificate-import,' + 'application-certificate', + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-core-request-show', + # права для запуска метода + 'rights': ['request'], + # объект содержащий модули для действия + 'logic': {'Request': request.Request}, + # описание действия + 'action': ClCoreRequestShow, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': { + 'cl_page_max!': lambda dv: len(dv.Get('cl_list_req_id'))}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Show requests"), + normal=('cl_page_count', 'cl_page_offset'), + next_label=_("Next"))]}, + # + # Отобразить детали запроса + # + { + # идентификатор метода + 'method_name': "core_detail_request", + # категория метода + # 'category':__('Utilities'), + # заголовок метода + 'title': __("Request Details"), + # иконка для графической консоли + 'image': None, + # метод в графической консоли + 'gui': True, + # права для запуска метода + 'rights': ['request'], + # объект содержащий модули для действия + 'logic': {}, + # описание действия + 'action': None, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Group details"), + normal=('cl_req_id', 'cl_req_user_name', + 'cl_req_ip', + 'cl_req_mac', 'cl_req_date', + 'cl_req_location', + 'cl_req_group'), + custom_buttons=[('but0', _("Back"), + "core_request_show", + "button"), + ('but1', _("Confirm"), + "core_request_confirm", + "button"), + ('but2', _("Delete"), + "core_request_del", + "button")])]}, + # + # Удалить запрос + # + { + # идентификатор метода + 'method_name': "core_request_del", + # категория метода + # 'category':__('Utilities'), + # заголовок метода + 'title': __("Delete the Request"), + # иконка для графической консоли + 'image': None, + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-core-request-del', + # права для запуска метода + 'rights': ['request'], + # объект содержащий модули для действия + 'logic': {'Request': request.Request}, + # описание действия + 'action': ClCoreRequestDel, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'delete'}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Delete the request"), + normal=('cl_req_id',), + next_label=_("Delete"))]}, + # + # Подтвердить запрос + # + { + # идентификатор метода + 'method_name': "core_request_confirm", + # категория метода + # 'category':__('Utilities'), + # заголовок метода + 'title': __("Confirm the Request"), + # иконка для графической консоли + 'image': None, + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-core-request-confirm', + # права для запуска метода + 'rights': ['request'], + # объект содержащий модули для действия + 'logic': {'Request': request.Request}, + # описание действия + 'action': ClCoreRequestConfirm, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'confirm'}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Delete the request"), + normal=('cl_req_id', 'cl_req_group'), + next_label=_("Delete"))]}, + # + # установить переменные + # + { + # идентификатор метода + 'method_name': "core_variables", + # категория метода + 'category': __('Utilities'), + # заголовок метода + 'title': __("Setup Variables"), + # иконка для графической консоли + 'image': 'calculate-core-variables,applications-versioncontrol,' + 'text-x-preview,text-x-makefile', + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-core-variables', + # права для запуска метода + 'rights': ['setup_variables'], + # объект содержащий модули для действия + 'logic': {'Variables': set_vars.Variables}, + # описание действия + 'action': ClCoreVariables, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Setup variables"), + normal=('cl_variable_data',), + next_label=_("Save"))]}, + # + # отобразить переменные + # + { + # идентификатор метода + 'method_name': "core_variables_show", + # категория метода + 'category': __('Utilities'), + # заголовок метода + 'title': __("View Variables"), + # иконка для графической консоли + 'image': None, + # метод в графической консоли + 'gui': False, + # консольная команда + 'command': 'cl-core-variables-show', + # права для запуска метода + 'rights': ['configure'], + # объект содержащий модули для действия + 'logic': {'Variables': set_vars.Variables}, + # описание действия + 'action': ClCoreVariablesShow, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Setup variables"), + normal=( + 'cl_variable_filter', + 'cl_variable_show'), + next_label=_("Show"))]}, + # + # Выполнить настройку пакета (cl-config) + # + { + # идентификатор метода + 'method_name': "core_config", + # категория метода + 'category': __('Configuration'), + # заголовок метода + 'title': __("Config"), + # иконка для графической консоли + 'image': None, + # метода нет в графической консоли + 'gui': False, + # консольная команда + 'command': 'cl-config', + # права для запуска метода + 'rights': ['configure'], + # объект содержащий модули для действия + 'logic': {'UpdateConfigs': setup_package.UpdateConfigs}, + # описание действия + 'action': ClConfigAction, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError, + setup_package.SetupPackageError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'config', 'cl_verbose_set': "on"}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Configure a package"), + normal=('cl_core_pkg_name',), + expert=('cl_core_pkg_category', + 'cl_core_pkg_version_opt', + 'cl_core_pkg_slot_opt', + 'cl_templates_locate', + 'cl_verbose_set'), + next_label=_("Run"))]}, + # + # отобразить переменные + # + { + # идентификатор метода + 'method_name': "core_custom", + # категория метода + 'category': __('Utilities'), + # заголовок метода + 'title': __("Custom Action"), + # иконка для графической консоли + 'image': 'calculate-core-custom,gnome-desktop-config,desktop-config', + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-core-custom', + # права для запуска метода + 'rights': ['custom_configure'], + # объект содержащий модули для действия + 'logic': {'UpdateConfigs': setup_package.UpdateConfigs}, + # описание действия + 'action': ClCoreCustomAction, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_verbose_set': "on", 'cl_human_edit_set': "on"}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Custom action"), + normal=( + 'ac_custom_name', 'cl_human_edit_set', + 'cl_verbose_set'), + expert=( + 'ur_core_login', 'cl_core_arch_machine', + 'cl_templates_locate', + 'cl_dispatch_conf'), + next_label=_("Run"))]}, + # + # перезапустить сервис calculate core + # + { + # идентификатор метода + 'method_name': "core_restart", + # категория метода + 'category': __('Utilities'), + # заголовок метода + 'title': __("Restart calculate-core"), + # иконка для графической консоли + 'image': 'calculate-core-restart,view-refresh', + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-core-restart', + # права для запуска метода + 'rights': ['core_restart'], + # объект содержащий модули для действия + 'logic': {'UpdateConfigs': setup_package.UpdateConfigs}, + # описание действия + 'action': ClCoreRestartAction, + # объект переменных + 'datavars': "core", + 'native_error': (VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'restart'}, + # описание груп (список лямбда функций) + 'groups': []}, + # + # создание резервной копии настроек + # + { + # идентификатор метода + 'method_name': "backup", + # категория метода + 'category':__('Backup'), + # заголовок метода + 'title': __("Backup"), + # иконка для графической консоли + 'image': 'calculate-backup', + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-backup', + # права для запуска метода + 'rights': ['backup'], + # объект содержащий модули для действия + 'logic': {'Backup': Backup}, + # описание действия + 'action': ClBackupAction, + # объект переменных + 'datavars': "core", + 'native_error': (BackupError, VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': Actions.Backup}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("System backup"), + normal=('cl_backup_verbose_set',), + next_label=_("Run"))]}, + # + # восстановление настроек из резервной копии + # + { + # идентификатор метода + 'method_name': "backup_restore", + # категория метода + 'category':__('Backup'), + # заголовок метода + 'title': __("Restore"), + # иконка для графической консоли + 'image': 'calculate-backup-restore', + # метод в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-backup-restore', + # права для запуска метода + 'rights': ['backup'], + # объект содержащий модули для действия + 'logic': {'Backup': Backup}, + # описание действия + 'action': ClBackupRestoreAction, + # объект переменных + 'datavars': "core", + 'native_error': (BackupError, VariableError, DataVarsError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': Actions.BackupRestore, + 'core.cl_backup_action': Actions.Restore}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("System restore"), + normal=('cl_backup_verbose_set',), + brief=('cl_backup_file', 'cl_backup_time'), + next_label=_("Run"))], + 'brief': {'next': __("Run"), + 'name': __("System restore")}}, + ] diff --git a/libs_crutch/lib/datavars.py b/libs_crutch/lib/datavars.py index dcb53a6..4a18988 100644 --- a/libs_crutch/lib/datavars.py +++ b/libs_crutch/lib/datavars.py @@ -1051,6 +1051,7 @@ class SimpleDataVars(object): return getList()(value) if "table" in varType: return map(getList(':'), value.split(',')) + value = str(value, "UTF-8") return fixEmpty(value).strip("'").strip('"') def ZipVars(self, *argvVarNames, **kw): diff --git a/libs_crutch/lib/utils/binhosts.py b/libs_crutch/lib/utils/binhosts.py index c126f2f..06f28b8 100644 --- a/libs_crutch/lib/utils/binhosts.py +++ b/libs_crutch/lib/utils/binhosts.py @@ -15,7 +15,7 @@ # limitations under the License. import sys -import urllib2 +import urllib.request as urllib2 import re import time import os diff --git a/libs_crutch/update/emerge_parser.py b/libs_crutch/update/emerge_parser.py new file mode 100644 index 0000000..7033f5f --- /dev/null +++ b/libs_crutch/update/emerge_parser.py @@ -0,0 +1,853 @@ +# -*- coding: utf-8 -*- + +# Copyright 2015-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import hashlib + +import os +from os import path + +import re +import sys +from calculate.lib.utils.colortext.palette import TextState +from calculate.lib.utils.tools import ignore +from calculate.lib.utils.git import Git, GitError +from calculate.lib.utils.portage import (EmergePackage, PackageList, + EmergeUpdateInfo, + EmergeRemoveInfo) + +Colors = TextState.Colors +import pexpect +from calculate.lib.utils.files import (getProgPath, readLinesFile, + listDirectory, + writeFile, readFile) +from calculate.lib.utils.colortext.output import XmlOutput +from calculate.lib.utils.colortext.converter import (ConsoleCodes256Converter, + XmlConverter) +from calculate.lib.cl_log import log + +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate, _ + +setLocalTranslate('cl_update3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + +linux_term_env = {'TERM': 'linux'} + +class EmergeError(Exception): + """ + Ошибка при сборке пакетов + """ + + +class EmergeNeedRootError(EmergeError): + pass + + +class CommandExecutor(object): + """ + Запуск программы для объекта Emerge + """ + logfile = '/var/log/calculate/lastcommand.log' + + def __init__(self, cmd, params, env=None, cwd=None, logfile=None): + self.cwd = cwd + self.env = env or dict(os.environ) + self.env.update({'EINFO_QUIET': 'NO'}) + self.env.update(linux_term_env) + self.cmd = cmd + self.params = params + self.child = None + if logfile: + self.logfile = logfile + + def get_command(self): + return [self.cmd] + list(self.params) + + def execute(self): + if self.child is None: + command_data = self.get_command() + self.child = pexpect.spawn(command_data[0], command_data[1:], + logfile=open(self.logfile, 'w'), + env=self.env, cwd=self.cwd, timeout=None) + return self.child + + def close(self): + if self.child is not None: + self.child.close() + self.child = None + + def success(self): + if self.child: + self.child.read() + if self.child.isalive(): + self.child.wait() + return self.child.exitstatus == 0 + return False + + def failed(self): + return not self.success() + + def send(self, s): + if self.child: + self.child.send(s) + + +class EmergeCommand(CommandExecutor): + """ + Запуск emerge для последующего анализирования + """ + # параметры по умолчанию + default_params = ["-av", "--color=y", "--nospinner"] + emerge_cmd = getProgPath("/usr/bin/emerge") + + def __init__(self, packages, extra_params=None, env=None, cwd=None, + logfile=None, emerge_default_opts=None, env_update=None, + use=""): + extra_params = extra_params or [] + if env is None: + if emerge_default_opts is None: + env = {'CLEAN_DELAY': '0'} + else: + env = { + 'CLEAN_DELAY': '0', + 'EMERGE_DEFAULT_OPTS': re.sub( + r'(?:^|\s)(--columns)(?=\s|$)', '', + emerge_default_opts) + } + if use: + env["USE"] = use + env.update(os.environ) + if env_update is not None: + env.update(env_update) + + params = self.default_params + extra_params + packages + super(EmergeCommand, self).__init__(self.emerge_cmd, params=params, + env=env, cwd=cwd, logfile=logfile) + + +def Chroot(chroot_path, obj): + """ + Преобразовать команду (экземпляр объекта) в chroot + :param obj: экземпляр команды + :param chroot_path: путь для chroot + :return: + """ + old_get_command = obj.get_command + + def get_command(): + chrootCmd = '/usr/bin/chroot' + bashCmd = '/bin/bash' + bash_command = ( + "env-update &>/dev/null;" + "source /etc/profile &>/dev/null;" + "{cmd}".format(cmd=" ".join(old_get_command()))) + return [chrootCmd, chroot_path, bashCmd, "-c", bash_command] + obj.get_command = get_command + return obj + + +def Linux32(obj): + """ + Преобразовать команду (экземпляр объекта) в вызов под linux32 + :param obj: экземпляр команды + :return: + """ + old_get_command = obj.get_command + + def get_command(): + return ["/usr/bin/linux32"] + old_get_command() + obj.get_command = get_command + return obj + + +class InfoBlockInterface(object): + """ + Интерфейс для информационного блока + """ + action = None + token = None + result = None + text_converter = ConsoleCodes256Converter(XmlOutput()) + + def get_block(self, child): + pass + + def add_element(self, element): + """ + :type element: InfoBlockInterface + """ + pass + + +class EmergeInformationBlock(InfoBlockInterface): + _color_block = "(?:\033\[[^m]+?m)?" + _new_line = "(?:\r*\n)" + end_token = ["\n"] + re_block = None + re_match_type = type(re.match("", "")) + re_type = type(re.compile("")) + + def __init__(self, parent): + """ + :type parent: InfoBlockInterface + """ + self.result = None + self.text_converter = parent.text_converter + self.parent = parent + self.parent.add_element(self) + self.children = [] + + def add_element(self, element): + """ + :type element: InfoBlockInterface + """ + self.children.append(element) + + def __str__(self): + if type(self.result) == self.re_match_type: + return self.result.group() + else: + return self.result or "" + + def __nonzero__(self): + return bool(self.result) + + def __len__(self): + if self.result is None: + return 0 + else: + return len(self.result) + + def __contains__(self, item): + if self.result is None: + return False + else: + return item in str(self) + + def _get_text(self, result): + """ + Получить результат из регулярки и преобразовать его через self.converter + """ + if result: + return self.text_converter.transform(result.rstrip()) + return "" + + def get_block(self, child): + try: + token = child.match + if type(self.end_token) == self.re_type: + child.expect(self.end_token) + match = child.match.group() + else: + child.expect_exact(self.end_token) + match = child.match + self.get_data(self.re_block.search( + token + child.before + match)) + except pexpect.EOF: + child.buffer = "".join( + [x for x in (child.before, child.after, child.buffer) + if type(x) == str]) + + def get_data(self, match): + self.result = self._get_text(match.group(1)) + + +class InstallPackagesBlock(EmergeInformationBlock): + """ + Блок emerge содержащий список пакетов для установки + """ + list = PackageList([]) + remove_list = PackageList([]) + block_packages = False + _new_line = EmergeInformationBlock._new_line + _color_block = EmergeInformationBlock._color_block + token = "\n[" + end_token = ["\r\n\r", "\n\n"] + + re_block = re.compile(r"((?:^\[.*?{nl})+)".format(nl=_new_line), + re.MULTILINE) + re_blocks = re.compile(r"\[{c}blocks{c} {c}b".format(c=_color_block)) + + def get_data(self, match): + super(InstallPackagesBlock, self).get_data(match) + list_block = XmlConverter().transform(self.result).split('\n') + self.list = PackageList(map(EmergeUpdateInfo, list_block)) + self.remove_list = PackageList(map(EmergeRemoveInfo, list_block)) + self.block_packages = any(self.re_blocks.search(x) for x in list_block) + + +class UninstallPackagesBlock(EmergeInformationBlock): + """ + Блок emerge содержащий список удаляемых пакетов + """ + list = PackageList([]) + verbose_result = "" + _new_line = EmergeInformationBlock._new_line + _color_block = EmergeInformationBlock._color_block + token = ["Calculating removal order", + "These are the packages that would be unmerged"] + end_token = re.compile("All selected packages:.*\n") + re_block = re.compile( + r"(?:{token}).*?{nl}(.*){nl}All selected packages: (.*?){nl}". + format(token="|".join(token), + nl=_new_line, c=_color_block), re.DOTALL) + + def get_data(self, match): + re_clean = re.compile( + "^.*?({token}).*?{c}{nl}".format(token="|".join(self.token), + nl=self._new_line, + c=self._color_block), re.DOTALL) + verbose_result = re_clean.sub("", match.group(1)) + self.verbose_result = self._get_text(verbose_result) + self.result = self._get_text(match.group(2)) + list_block = XmlConverter().transform(self.result).split() + self.list = PackageList(map(EmergePackage, list_block)) + + +class GroupEmergeInformationBlock(EmergeInformationBlock): + """ + Группа блоков + """ + def get_block(self, child): + self.children_get_block(child) + self.result = True + + def children_get_block(self, child): + for block in self.children: + block.get_block(child) + + def children_action(self, child): + for block in (x for x in self.children if x.result and x.action): + if block.action(child) is False: + return False + + def action(self, child): + self.children_action(child) + return False + + +class FinishEmergeGroup(GroupEmergeInformationBlock): + """ + Блок завершения команды + """ + token = pexpect.EOF + # регуляреное выражение, определяющее содержит ли блок + # сообщения об ошибках + re_failed = re.compile( + r"Fetch instructions for \S+:|" + r"The following.*are necessary to proceed|" + r"!!! Multiple package .* slot have been pulled|" + r"no ebuilds to satisfy|" + r"Dependencies could not be completely resolved due to", + re.MULTILINE) + + def get_block(self, child): + if child.isalive(): + child.wait() + if child.exitstatus != 0 or self.re_failed.search(child.before): + self.children_get_block(child) + else: + self.result = True + + +class PrepareErrorBlock(EmergeInformationBlock): + """ + Блок информации с ошибками при получении списка устанавливаемых пакетов + """ + token = None + + re_drop = re.compile("news items need reading|" + "Use eselect news|" + "Calculating dependencies|" + "to read news items|" + "Local copy of remote index is up-to-date|" + "These are the packages that would be merged|" + "Process finished with exit code") + re_multi_empty_line = re.compile("(?:
){3,}", re.DOTALL) + re_strip_br = re.compile("^(?:
)+|(?:
)+$", re.DOTALL) + + def remove_needless_data(self, data): + return "\n".join([x for x in data.split('\n') + if not self.re_drop.search(x)]) + + def strip_br(self, data): + return self.re_strip_br.sub( + "", + self.re_multi_empty_line.sub("

", data)) + + def get_block(self, child): + self.result = self.strip_br( + self._get_text(self.remove_needless_data(child.before))) + + def action(self, child): + raise EmergeError(_("Emerge failed")) + + +class DownloadSizeBlock(EmergeInformationBlock): + """ + Размер скачиваемых обновлений + """ + token = "Size of downloads:" + + re_block = re.compile(r"Size of downloads:\s(\S+\s\S+)") + + def __str__(self): + if self.result: + return self.result + else: + return "0 kB" + +class SkippedPackagesBlock(EmergeInformationBlock): + """ + Размер скачиваемых обновлений + """ + token = "The following update has been skipped" + end_token = ["For more information, see the MASKED"] + + re_block = re.compile( + r"(The following update has.*?)(?=For more information)", re.S) + + def __str__(self): + if self.result: + return self.result + else: + return "" + +class QuestionGroup(GroupEmergeInformationBlock): + """ + Группа блоков разбора вопросов от emerge + """ + token = "Would you like" + end_token = ["]", "\n"] + _color_block = EmergeInformationBlock._color_block + re_block = re.compile( + "(Would you.*)\[{c}Yes{c}/{c}No{c}".format(c=_color_block)) + + def get_block(self, child): + try: + before = child.before + token = child.match + if type(self.end_token) == self.re_type: + child.expect(self.end_token) + match = child.match.group() + else: + child.expect_exact(self.end_token) + match = child.match + data = token + child.before + match + child.before = before + for block in self.children: + child.match = re.search(block.token, data) + block.get_block(child) + if block.result: + break + except pexpect.EOF: + child.buffer = "".join( + [x for x in (child.before, child.after, child.buffer) + if type(x) == str]) + +class QuestionChangeConfigBlock(GroupEmergeInformationBlock): + """ + Вопрос об изменении конфигурационных файлов + """ + token = "Would you like to add these changes to your config files" + + def get_block(self, child): + if child.match: + self.result = self.token + self.children_get_block(child) + + def action(self, child): + if self.result: + child.send("no\n") + if child.isalive(): + child.wait() + self.children_action(child) + + +class QuestionBlock(EmergeInformationBlock): + """ + Блок вопроса + """ + default_answer = "yes" + token = "Would you" + + def get_block(self, child): + if child.match: + self.result = self.token + + def action(self, child): + if self.result: + child.send("%s\n" % self.default_answer) + return False + + +class NeedRootBlock(EmergeInformationBlock): + """ + Пользователь не явеляется root + """ + token = "This action requires superuser access" + + def get_data(self, child): + self.result = True + + def action(self, child): + raise EmergeNeedRootError(_("This action requires superuser access")) + + +class NotifierInformationBlock(EmergeInformationBlock): + """ + Информационный блок поддерживающий observing + """ + def __init__(self, parent): + super(NotifierInformationBlock, self).__init__(parent) + self.observers = [] + + def get_data(self, match): + self.result = match + + def add_observer(self, f): + self.observers.append(f) + + def clear_observers(self): + self.observers = [] + + def remove_observer(self, f): + if f in self.observers: + self.observers.remove(f) + + def notify(self, observer, groups): + observer(groups) + + def action(self, child): + if self.result and self.observers: + groups = self.result.groups() + for observer in self.observers: + self.notify(observer, groups) + + +class EmergingPackage(NotifierInformationBlock): + """ + Запуск устанавливаемого пакета + + ObserverFunc: (package, num=number, max_num=number, binary=binary) + """ + _color_block = EmergeInformationBlock._color_block + token = ">>> Emerging " + re_block = re.compile( + "Emerging (binary )?\({c}(\d+){c} " + "of {c}(\d+){c}\) {c}([^\s\033]+){c}".format(c=_color_block)) + + def notify(self, observer, groups): + observer(EmergePackage(groups[3]), num=groups[1], max_num=groups[2], + binary=bool(groups[0])) + + +class UnemergingPackage(NotifierInformationBlock): + """ + Запуск устанавливаемого пакета + + ObserverFunc: (package, num=number, max_num=number) + """ + _color_block = EmergeInformationBlock._color_block + token = ">>> Unmerging" + re_block = re.compile( + r"Unmerging (?:\({c}(\d+){c} " + r"of {c}(\d+){c}\) )?(\S+)\.\.\.".format(c=_color_block)) + + def notify(self, observer, groups): + observer(EmergePackage(groups[2]), num=groups[0], max_num=groups[1]) + + +class FetchingTarball(NotifierInformationBlock): + """ + Происходит скачивание архивов + """ + token = "Saving to:" + re_block = re.compile("Saving to:\s*[‘'](\S+)?['’]") + + def notify(self, observer, groups): + observer(groups[0]) + + +class InstallingPackage(NotifierInformationBlock): + """ + Запуск устанавливаемого пакета + + ObserverFunc: (package, binary=binary) + """ + _color_block = EmergeInformationBlock._color_block + binary = None + + token = ">>> Installing " + re_block = re.compile( + "Installing \({c}(\d+){c} " + "of {c}(\d+){c}\) {c}([^\s\033]+){c}".format(c=_color_block)) + + def notify(self, observer, groups): + strpkg = str(EmergePackage(groups[2])) + binary = bool(self.binary and strpkg in self.binary) + observer(EmergePackage(groups[2]), binary=binary) + + def mark_binary(self, package): + if self.binary is None: + self.binary = [] + self.binary.append(str(package)) + + +class EmergeingErrorBlock(EmergeInformationBlock): + """ + Блок содержит информацию об ошибке во время сборки пакета + """ + token = ["* ERROR: ", " * \033[39;49;00mERROR: "] + end_token = "Working directory:" + re_block = re.compile("ERROR: (\S*) failed \([^)]+\).*?" + "The complete build log is located at '([^']+)", + re.DOTALL) + package = "" + + def get_data(self, match): + self.result = self._get_text(match.group(2).rstrip()) + self.package = match.group(1) + + @property + def log(self): + return self.text_converter.transform(readFile(self.result)) + + def action(self, child): + raise EmergeError(_("Failed to emerge %s") % self.package) + + +class RevdepPercentBlock(NotifierInformationBlock): + """ + Блок определния статуса revdep-rebuild + """ + token = "Collecting system binaries" + end_token = [re.compile("Assigning files to packages|" + "All prepared. Starting rebuild")] + re_block = re.compile("\[\s(\d+)%\s\]") + action = None + + def notify(self, observer, groups): + percent = int(groups[0]) + observer(percent) + + def get_block(self, child): + expect_result = [self.re_block]+self.end_token + try: + while True: + index = child.expect(expect_result) + if index == 0: + for observer in self.observers: + self.notify(observer, child.match.groups()) + else: + self.result = child.match + break + except pexpect.EOF: + self.result = "" + +class EmergeParser(InfoBlockInterface): + """ + Парсер вывода emerge + """ + + def __init__(self, command, run=False): + self.command = command + self.elements = {} + + self.install_packages = InstallPackagesBlock(self) + self.uninstall_packages = UninstallPackagesBlock(self) + self.question_group = QuestionGroup(self) + self.change_config_question = QuestionChangeConfigBlock( + self.question_group) + self.question = QuestionBlock(self.question_group) + self.finish_block = FinishEmergeGroup(self) + self.need_root = NeedRootBlock(self) + self.prepare_error = PrepareErrorBlock(self.finish_block) + self.change_config_question.add_element(self.prepare_error) + self.download_size = DownloadSizeBlock(self) + self.skipped_packages = SkippedPackagesBlock(self) + self.emerging_error = EmergeingErrorBlock(self) + + self.installing = InstallingPackage(self) + self.uninstalling = UnemergingPackage(self) + self.emerging = EmergingPackage(self) + self.fetching = FetchingTarball(self) + + self.emerging.add_observer(self.mark_binary) + self.emerging.add_observer(self.skip_fetching) + if run: + self.run() + + def mark_binary(self, package, binary=False, **kw): + if binary: + self.installing.mark_binary(package) + + def skip_fetching(self, *argv, **kw): + self.fetching.action = lambda child: None + + def add_element(self, element): + """ + :type element: InfoBlockInterface + """ + if element.token: + if type(element.token) == list: + for token in element.token: + self.elements[token] = element + else: + self.elements[element.token] = element + + def run(self): + """ + Запустить команду + """ + child = self.command.execute() + + while True: + index = child.expect_exact(self.elements.keys()) + element = self.elements.values()[index] + element.get_block(child) + if element.action: + if element.action(child) is False: + break + + def close(self): + self.command.close() + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.close() + + +class MtimeCheckvalue(object): + def __init__(self, *fname): + self.fname = fname + + def value_func(self, fn): + return str(int(os.stat(fn).st_mtime)) + + def get_check_values(self, file_list): + for fn in file_list: + if path.exists(fn) and not path.isdir(fn): + yield fn, self.value_func(fn) + else: + for k, v in self.get_check_values( + listDirectory(fn, fullPath=True)): + yield k, v + + def checkvalues(self): + return self.get_check_values(self.fname) + + +class Md5Checkvalue(MtimeCheckvalue): + def value_func(self, fn): + return hashlib.md5(readFile(fn)).hexdigest() + + +class GitCheckvalue(object): + def __init__(self, git, rpath): + self.rpath = rpath + self.git = git + + def checkvalues(self): + with ignore(GitError): + if self.git.is_git(self.rpath): + yield self.rpath, self.git.getCurrentCommit(self.rpath) + + +class EmergeCache(object): + """ + Кэш пакетов + """ + cache_file = '/var/lib/calculate/calculate-update/world.cache' + # список файлов проверяемый по mtime на изменения + check_list = [MtimeCheckvalue('/etc/make.conf', + '/etc/portage', + '/etc/make.profile'), + Md5Checkvalue('/var/lib/portage/world', + '/var/lib/portage/world_sets')] + logger = log("emerge-cache", + filename="/var/log/calculate/emerge-cache.log", + formatter="%(asctime)s - %(levelname)s - %(message)s") + + def __init__(self): + self.files_control_values = {} + self.pkg_list = PackageList([]) + + def set_cache(self, package_list): + """ + Установить в кэш список пакетов + """ + with writeFile(self.cache_file) as f: + for fn, val in self.get_control_values().items(): + f.write("{fn}={val}\n".format(fn=fn, val=val)) + f.write('\n') + for pkg in package_list: + f.write("%s\n" % str(pkg)) + self.logger.info("Setting cache (%d packages)" % len(package_list)) + + def drop_cache(self, reason=None): + if path.exists(self.cache_file): + with ignore(OSError): + os.unlink(self.cache_file) + self.logger.info("Droping cache. Reason: %s" % reason) + else: + self.logger.info("Droping empty cache. Reason: %s" % reason) + + def get_cached_package_list(self): + self.read_cache() + if not path.exists(self.cache_file): + self.logger.info("Requesting empty cache") + if self.check_actuality(): + return self.pkg_list + return None + + def check_actuality(self): + """ + Кэш считается актуальным если ни один из файлов не менялся + """ + if self.get_control_values() == self.files_control_values: + self.logger.info( + "Getting actuality cache (%d packages)" % len(self.pkg_list)) + return True + else: + reason = "Unknown" + for k, v in self.get_control_values().items(): + if k in self.files_control_values: + if v != self.files_control_values[k]: + reason = "%s was modified" % k + else: + reason = "Checksum of file %s is not exist" % k + self.logger.info("Failed to get cache. Reason: %s" % reason) + return False + + def read_cache(self): + self.files_control_values = {} + cache_file_lines = readLinesFile(self.cache_file) + for line in cache_file_lines: + if "=" not in line: + break + k, v = line.split('=') + self.files_control_values[k] = v.strip() + self.pkg_list = PackageList(cache_file_lines) + + def get_control_values(self): + def generate(): + for obj in self.check_list: + for check_value in obj.checkvalues(): + yield check_value + + return dict(generate()) diff --git a/libs_crutch/update/profile.py b/libs_crutch/update/profile.py new file mode 100644 index 0000000..b0f028f --- /dev/null +++ b/libs_crutch/update/profile.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- + +# Copyright 2014-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import os +from os import path +import shutil +from calculate.lib.utils.files import (listDirectory, readFile, readLinesFile, + makeDirectory, removeDir) +from calculate.lib.utils.git import Git +from .update import UpdateError + +from calculate.lib.cl_lang import setLocalTranslate, _ +setLocalTranslate('cl_update3', sys.modules[__name__]) + +DEFAULT_BRANCH = Git.Reference.Master + +class RepositoryStorageInterface(object): + def __iter__(self): + raise StopIteration + + def get_profiles(self, url, branch=DEFAULT_BRANCH): + return [] + + def get_repository(self, url, branch=DEFAULT_BRANCH): + return None + +class RepositoryStorage(RepositoryStorageInterface): + directory = '/tmp' + + def __init__(self, git, directory): + self.directory = directory + self.git = git + makeDirectory(directory) + + def __iter__(self): + for dn in listDirectory(self.directory, onlyDir=True, fullPath=True): + if self.git.is_git(dn): + yield ProfileRepository(self.git, path.basename(dn), self) + + def get_profiles(self, url, branch=DEFAULT_BRANCH): + return [] + + def get_repository(self, url, branch=DEFAULT_BRANCH): + return None + + +class ProfileStorage(RepositoryStorage): + def get_profiles(self, url, branch=DEFAULT_BRANCH): + rep = self.get_repository(url, branch) + if rep: + return rep.get_profiles() + return None + + def get_repository(self, url, branch=DEFAULT_BRANCH): + return None + + +class LocalStorage(ProfileStorage): + """ + Локальное хранилище репозиториев, при запросе по урлу смотрит, доступные + репозитории если находит подходящий - возвращает его профили + """ + def get_repository(self, url, branch=DEFAULT_BRANCH): + for rep in self: + if rep.is_like(url, branch): + return rep + +class CacheStorage(ProfileStorage): + """ + Хранилище репозиториев, при запросе по урлу смотрит, доступные + репозитории если находит подходящий - возвращает его профили, + если не находит - скачивает и возвращает профили + """ + def get_repository(self, url, branch=DEFAULT_BRANCH): + for rep in self: + if rep.is_like(url, branch): + return rep + else: + return ProfileRepository.clone(self.git, url, self, + branch or DEFAULT_BRANCH) + +class RepositoryStorageSet(RepositoryStorageInterface): + """ + Набор хранилищ репозиториев + """ + def __init__(self, *storages): + self.storages = storages + + def get_profiles(self, url, branch=DEFAULT_BRANCH): + """ + Получить профили из указанного репозитория + """ + for storage in self.storages: + profiles = storage.get_profiles(url, branch) + if profiles is not None: + return profiles + return None + + def __iter__(self): + for storage in self.storages: + for rep in storage: + yield rep + + def get_repository(self, url, branch=DEFAULT_BRANCH): + """ + Получить репозиторий по параметрам + """ + for rep in self: + if rep.is_like(url, branch): + return rep + for storage in self.storages: + rep = storage.get_repository(url, branch) + if rep: + return rep + return None + + def is_local(self, url, branch=DEFAULT_BRANCH): + """ + Проверить является ли репозиторий с указанными параметрами + локальным + """ + rep = self.get_repository(url, branch) + if rep and isinstance(rep.storage, LocalStorage): + return True + return False + + def __repr__(self): + return "Repository set" + +class Profile(object): + """ + Профиль репозитория + """ + available_arch = ["amd64", "x86"] + + def __init__(self, repository, profile, arch): + self.repository = repository + self.profile = profile + self.arch = arch + + @property + def path(self): + return path.join(self.repository.directory,"profiles", self.profile) + + @classmethod + def from_string(cls, repository, s): + parts = filter(None, s.split()) + if len(parts) == 3 and parts[0] in cls.available_arch: + return Profile(repository, parts[1], parts[0]) + return None + + def __repr__(self): + return "" % (self.arch, + self.repository.repo_name, + self.profile, + self.repository.directory) + + +class ProfileRepository(object): + """ + Репозиторий либо скачивается, либо берется из кэша + """ + def __init__(self, git, name, storage): + self._storage = storage + self.name = name + self.git = git + + @property + def storage(self): + return self._storage + + @storage.setter + def storage(self, storage): + if storage.directory != self._storage.directory: + newpath = path.join(storage.directory, self.name) + if self.directory == newpath: + return + try: + if path.exists(newpath): + removeDir(newpath) + shutil.move(self.directory, newpath) + self._storage = storage + except OSError as e: + raise UpdateError(_("Failed to move the profile: %s") % + str(e)) + + @classmethod + def clone(cls, git, url, storage, branch=DEFAULT_BRANCH): + name = path.basename(url) + if name.endswith(".git"): + name = name[:-4] + rpath = path.join(storage.directory, name) + if path.exists(rpath): + removeDir(rpath) + if git.is_private_url(url): + try: + makeDirectory(rpath) + os.chmod(rpath, 0o700) + except OSError: + pass + git.cloneRepository(url, rpath, branch) + pr = cls(git, name, storage) + repo_name = pr.repo_name + if name != repo_name: + rpath_new = path.join(storage.directory, repo_name) + if path.exists(rpath_new): + removeDir(rpath_new) + shutil.move(rpath, rpath_new) + pr = cls(git, repo_name, storage) + return pr + + @property + def repo_name(self): + return readFile(path.join(self.directory, + "profiles/repo_name")).strip() + + def is_like(self, url, branch=DEFAULT_BRANCH): + if self.url == url and (branch is None or self.branch == branch): + return True + return False + + @property + def directory(self): + """ + Получить локальную директорию на данные репозитория + """ + return path.join(self.storage.directory, self.name) + + @property + def url(self): + return self.git.get_url(self.directory, "origin") + + @property + def branch(self): + return self.git.getBranch(self.directory) + + def sync(self): + """ + Синхронизировать репозиторий + """ + if not self.git.pullRepository(self.directory, quiet_error=True): + self.git.resetRepository(self.directory, to_origin=True) + self.git.pullRepository(self.directory, quiet_error=True) + + def get_profiles(self): + """ + Получить список профилей репозитория + """ + profiles_desc = path.join(self.directory, "profiles/profiles.desc") + return filter(None, (Profile.from_string(self, line) + for line in readLinesFile(profiles_desc))) + + def __repr__(self): + return "" % (self.directory, self.url) diff --git a/libs_crutch/update/update.py b/libs_crutch/update/update.py new file mode 100644 index 0000000..3f474d4 --- /dev/null +++ b/libs_crutch/update/update.py @@ -0,0 +1,1764 @@ +# -*- coding: utf-8 -*- + +# Copyright 2014-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from functools import wraps +import random + +import sys +from os import path +import os +import time +from calculate.core.server.gen_pid import search_worked_process +from calculate.core.setup_cache import Cache as SetupCache +from calculate.core.server.func import MethodsInterface +from calculate.lib.cl_template import SystemIni, LayeredIni +from calculate.lib.datavars import DataVarsError, VariableError, Variable + +from calculate.lib.utils.tools import AddonError +from calculate.lib.utils.colortext.palette import TextState +from calculate.lib.utils.colortext import get_color_print +from .emerge_parser import RevdepPercentBlock +from .datavars import DataVarsUpdate +from .update_info import UpdateInfo +from calculate.lib.utils.binhosts import (Binhosts, BinhostSignError, + BinhostError, PackagesIndex, DAYS) +from calculate.lib.utils.gpg import GPG, GPGError +from calculate.lib.utils.common import get_fastlogin_domain_path +from calculate.lib.cl_log import log +import hashlib +import re +import shutil +from collections import MutableSet +from contextlib import contextmanager +import tempfile +from fnmatch import fnmatch + +from calculate.lib.utils.vardb import Vardb + +from calculate.lib.utils.git import Git, GitError, MTimeKeeper, NotGitError +from calculate.lib.utils.portage import (ReposConf, EmergeLog, + EmergeLogNamedTask, + VDB_PATH, + PackageInformation, + PortageState, + get_packages_files_directory, + get_manifest_files_directory, + get_remove_list) +from calculate.lib.utils.text import _u8, _u + +Colors = TextState.Colors +from calculate.lib.utils.files import (getProgPath, STDOUT, removeDir, + getParentPids, + PercentProgress, process, getRunCommands, + readFile, listDirectory, pathJoin, + find, FindFileType,quite_unlink, + writeFile, makeDirectory) +from . import emerge_parser +import logging +from .emerge_parser import (EmergeParser, EmergeCommand, EmergeError, + EmergeCache, Chroot) + +from calculate.lib.cl_lang import (setLocalTranslate, getLazyLocalTranslate, + RegexpLocalization, _) + +setLocalTranslate('cl_update3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class UpdateError(AddonError): + """Update Error""" + + +class OverlayOwnCache(MutableSet): + """ + Сет оверлеев с интегрированным кэшем + """ + + def __init__(self, dv=None): + self.dv = dv + + def __get_overlays(self): + own_cache_value = SystemIni(self.dv).getVar('system', 'own_cache') or "" + return [x.strip() for x in own_cache_value.split(',') if x.strip()] + + def __write_overlays(self, overlays): + if not overlays: + SystemIni(self.dv).delVar('system', 'own_cache') + else: + SystemIni(self.dv).setVar('system', + {'own_cache': ",".join(overlays)}) + + def __contains__(self, item): + return item in self.__get_overlays() + + def __iter__(self): + return iter(self.__get_overlays()) + + def __len__(self): + return len(self.__get_overlays()) + + def __append_value(self, overlays, value): + if value not in overlays: + overlays.append(value) + self.__write_overlays(overlays) + + def add(self, value): + overlays = self.__get_overlays() + self.__append_value(overlays, value) + + def discard(self, value): + overlays = self.__get_overlays() + if value in overlays: + overlays.remove(value) + self.__write_overlays(overlays) + + +def variable_module(var_env): + def variable_module_decor(f): + @wraps(f) + def wrapper(self, *args, **kw): + old_env = self.clVars.defaultModule + try: + self.clVars.defaultModule = var_env + return f(self, *args, **kw) + finally: + self.clVars.defaultModule = old_env + + return wrapper + + return variable_module_decor + + +class Update(MethodsInterface): + """Основной объект для выполнения действий связанных с обновлением системы + + """ + + def init(self): + commandLog = path.join(self.clVars.Get('core.cl_log_path'), + 'lastcommand.log') + emerge_parser.CommandExecutor.logfile = commandLog + self.color_print = get_color_print() + self.emerge_cache = EmergeCache() + if self.clVars.Get('cl_env_debug_set') == 'off': + EmergeCache.logger.logger.setLevel(logging.WARNING) + self.emerge_cache.check_list = ( + self.emerge_cache.check_list + + map(lambda x:emerge_parser.GitCheckvalue(x, self.getGit()), + self.clVars.Get('update.cl_update_rep_path'))) + self.update_map = {} + self.refresh_binhost = False + self.pkgnum = None + self.pkgnummax = None + self.gpgdata_md5 = [] + self.gpg_changed = False + + def get_prog_path(self, program_name): + return getProgPath(program_name) + + def getGit(self): + return self.clVars.Get('cl_update_git') + + @contextmanager + def private_repo(self, rpath, url): + if Git.is_private_url(url): + try: + if not path.exists(rpath): + makeDirectory(rpath) + os.chmod(rpath, 0o700) + yield + finally: + try: + for dn in (Git._gitDir(rpath), path.join(rpath, "profiles/templates")): + if path.exists(dn): + os.chmod(dn, 0o700) + for fn in find(path.join(rpath, "profiles"), True, FindFileType.RegularFile, + True, None, downfilter=lambda x: not x.endswith("/templates")): + if fn.endswith("calculate.env") or fn.endswith("ini.env"): + os.chmod(fn, 0o600) + if path.exists(rpath): + os.chmod(rpath, 0o755) + except OSError: + pass + else: + yield + + def _syncRepository(self, name, url, rpath, revision, + cb_progress=None, clean=False, notask=False): + """ + Синхронизировать репозитори + """ + dv = self.clVars + git = self.getGit() + info_outdated = False + old_dir = "%s.old" % git._gitDir(rpath) + if path.exists(old_dir): + clean = True + try: + self.stash_cache(rpath, name) + if not git.checkExistsRep(rpath): + if not notask: + self.startTask(_("Syncing the {rep} repository").format( + rep=name.capitalize())) + self.addProgress() + with self.private_repo(rpath, url): + git.cloneTagRepository(url, rpath, revision, + cb_progress=cb_progress) + info_outdated = True + else: + cr = "" + try: + need_update = False + tag_cr = git.getCommit(rpath, revision) + cr = git.getCurrentCommit(rpath) + ref_type = git.reference_type(rpath, revision) + if tag_cr != cr or ref_type == Git.Reference.Branch: + need_update = True + elif clean: + status = git.getStatusInfo(rpath) + if not status or status['files']: + need_update = True + except GitError as e: + need_update = True + if need_update: + if not notask: + self.startTask(_("Syncing the {rep} repository").format( + rep=name.capitalize())) + self.addProgress() + with self.private_repo(rpath, url): + git.updateTagRepository(url, rpath, revision, + cb_progress=cb_progress, + clean=clean) + new_cr = git.getCurrentCommit(rpath) + if new_cr != cr: + info_outdated = True + if info_outdated: + self.raiseOutdate() + dv.Set('cl_update_outdate_set', 'on', force=True) + finally: + self.unstash_cache(rpath, name) + # TODO: debug1 + #dv.Set('cl_update_outdate_set', 'on', force=True) + return True + + def raiseOutdate(self): + self.clVars.Set('cl_update_outdate_set', 'on', force=True) + + def setAutocheckParams(self, status, interval, update_other, cleanpkg): + """ + Настроить параметры автопроверки обновлений + """ + onoff = lambda x: "on" if x else "off" + self.clVars.Write('cl_update_autocheck_set', onoff(status), True) + self.clVars.Write('cl_update_autocheck_interval', interval, True) + self.clVars.Write('cl_update_other_set', onoff(update_other), True) + self.clVars.Write('cl_update_cleanpkg_set', onoff(cleanpkg), True) + if not status: + UpdateInfo.set_update_ready(False) + return True + + def checkSchedule(self, interval, status): + """ + Проверить по расписанию необходимость запуска команды + """ + if not status: + self.printWARNING(_("Updates autocheck is not enabled")) + return False + last_check = SystemIni(self.clVars).getVar('system', 'last_check') or "" + re_interval = re.compile("^(\d+)\s*(hours?|days?|weeks?)?", re.I) + interval_match = re_interval.search(interval) + MINUTE = 60 + HOUR = MINUTE * 60 + DAY = HOUR * 24 + WEEK = DAY * 7 + if interval_match: + if interval_match.group(2): + suffix_map = {'h': HOUR, 'd': DAY, 'w': WEEK} + k = suffix_map.get(interval_match.group(2).lower()[0], HOUR) + else: + k = HOUR + est = int(interval_match.group(1)) * k + else: + est = 3 * HOUR + if last_check: + if last_check.isdigit(): + if (time.time() - int(last_check)) < (est - 10 * MINUTE): + self.printWARNING(_("Please wait for the update time")) + return False + self.mark_schedule() + return True + + def checkRun(self, wait_update): + """ + Проверить повторный запуск + """ + update_running = lambda: any(os.getpid() != x + for x in + search_worked_process('update', dv)) + dv = self.clVars + if update_running(): + if not wait_update: + raise UpdateError(_("Update is already running. " + "Try to run later.")) + else: + self.startTask(_("Waiting for another update to be complete")) + + while update_running(): + self.pauseProcess() + while update_running(): + time.sleep(0.3) + self.resumeProcess() + time.sleep(random.random() * 3) + self.endTask() + + if self.clVars.Get('cl_chroot_status') == 'off': + parents_pids = set(getParentPids(os.getpid())) + emerge_running = lambda : ( + any((fnmatch(x[1], "*python-exec/*/emerge*") and + x[0] not in parents_pids) + for x in getRunCommands(True, withpid=True))) + if emerge_running(): + if not wait_update: + raise UpdateError(_("Emerge is running. " + "Try to run later.")) + else: + self.startTask(_("Waiting for emerge to be complete")) + while emerge_running(): + time.sleep(1) + self.endTask() + return True + + @variable_module("update") + def trimRepositories(self, repname): + """ + Синхронизировать репозитории + """ + dv = self.clVars + rpath = \ + dv.select('cl_update_rep_path', cl_update_rep_name=repname, limit=1) + git = self.getGit() + self.addProgress() + git.trimRepository(rpath, cb_progress=self.setProgress) + return True + + def migrateLayman(self, reposdir, laymandir, repname, rpath_orig): + if rpath_orig.startswith("/var/db/repos/"): + dn_name = os.path.basename(rpath_orig) + repos_fullname = pathJoin(reposdir, dn_name) + layman_fullname = pathJoin(laymandir, dn_name) + if (not os.path.islink(layman_fullname) and + os.path.isdir(layman_fullname) and + not os.path.exists(repos_fullname)): + self.startTask(_("Move {repname} from {laymandir} to {reposdir}").format( + repname=repname.capitalize(), + laymandir="/var/lib/layman", + reposdir="/var/db/repos")) + symlink_target = os.path.relpath(repos_fullname, laymandir) + if not os.path.exists(reposdir): + makeDirectory(reposdir) + os.rename(layman_fullname, repos_fullname) + os.symlink(symlink_target, layman_fullname) + #print "MYDEBUG", reposdir, laymandir, rpath_orig + self.endTask(True) + return True + + @variable_module("update") + def syncRepositories(self, repname, fallback_sync=False, + clean_on_error=True): + """ + Синхронизировать репозитории + """ + dv = self.clVars + check_status = dv.GetBool('update.cl_update_check_rep_set') + url, rpath, revision = ( + dv.Select(["cl_update_rep_url", "cl_update_rep_path", + "cl_update_rep_rev"], + where="cl_update_rep_name", eq=repname, limit=1)) + + if not url or not rpath: + raise UpdateError(_("Configuration variables for repositories " + "are not setup")) + git = self.getGit() + if not git.checkUrl(url): + raise UpdateError(_("Git %s is unavailable") % url) + chroot_path = path.normpath(self.clVars.Get('cl_chroot_path')) + if chroot_path == '/': + rpath_orig = rpath + else: + rpath_orig = rpath[len(chroot_path):] + + mtime = MTimeKeeper(path.join(rpath, "profiles/updates")) + mtime.save() + self.migrateLayman(dv.Get('cl_update_repos_storage'), + dv.Get('cl_update_layman_storage'), repname, rpath_orig) + try: + if clean_on_error: + try: + repconf = ReposConf(dv.Get('cl_update_reposconf'), + dv.Get('cl_update_reposconf_dir'), + prefix=chroot_path) + repconf.add(repname, url, rpath_orig) + if not self._syncRepository(repname, url, rpath, revision, + cb_progress=self.setProgress, + clean=check_status, + notask=fallback_sync): + return "skip" + return True + except GitError as e: + if not isinstance(e, NotGitError): + if e.addon: + self.printWARNING(str(e.addon)) + self.printWARNING(str(e)) + self.endTask(False) + self.startTask( + _("Re-fetching the {name} repository").format( + name=repname)) + self.addProgress() + rpath_new = "%s_new" % rpath + try: + self._syncRepository(repname, url, rpath_new, revision, + cb_progress=self.setProgress, + clean=check_status, + notask=fallback_sync) + removeDir(rpath) + shutil.move(rpath_new, rpath) + except OSError as e: + raise UpdateError(_("Failed to modify the " + "{repname} repository").format( + repname=repname) + _(": ") + str(e)) + finally: + if path.exists(rpath_new): + removeDir(rpath_new) + else: + if not self._syncRepository(repname, url, rpath, revision, + clean=check_status): + return "skip" + + repconf = ReposConf(dv.Get('cl_update_reposconf'), + dv.Get('cl_update_reposconf_dir'), + prefix=chroot_path) + repconf.add(repname, url, rpath_orig) + finally: + mtime.restore() + return True + + metadata_cache_names = ("metadata/md5-cache", "metadata/cache") + + def stash_cache(self, rpath, name): + """ + Спрятать кэш + """ + if name in ("portage", "gentoo"): + return + if not name in OverlayOwnCache(self.clVars): + for cachename in self.metadata_cache_names: + cachedir = path.join(rpath, cachename) + if path.exists(cachedir): + try: + cachedir_s = path.join(path.dirname(rpath), + path.basename( + cachename) + ".stash") + if path.exists(cachedir_s): + removeDir(cachedir_s) + shutil.move(cachedir, cachedir_s) + except BaseException as e: + pass + + def unstash_cache(self, rpath, name): + """ + Извлеч кэш + """ + if name in ("portage", "gentoo"): + return + cachenames = self.metadata_cache_names + if not name in OverlayOwnCache(self.clVars): + if any(path.exists(path.join(rpath, x)) for x in cachenames): + for cachename in cachenames: + cachedir_s = path.join(path.dirname(rpath), + path.basename(cachename) + ".stash") + if path.exists(cachedir_s): + try: + removeDir(cachedir_s) + except BaseException as e: + pass + OverlayOwnCache(self.clVars).add(name) + else: + for cachename in cachenames: + cachedir = path.join(rpath, cachename) + cachedir_s = path.join(path.dirname(rpath), + path.basename(cachename) + ".stash") + if path.exists(cachedir_s): + try: + shutil.move(cachedir_s, cachedir) + except BaseException as e: + pass + else: + if all(not path.exists(path.join(rpath, x)) for x in cachenames): + OverlayOwnCache(self.clVars).discard(name) + + def syncOtherRepository(self, repname): + """ + Обновить репозиторий через emerge --sync + """ + emerge = self.get_prog_path('/usr/bin/emerge') + if not emerge: + raise UpdateError(_("The Emerge is not found")) + + rpath = self.clVars.Select('cl_update_other_rep_path', + where='cl_update_other_rep_name', eq=repname, + limit=1) + repdirname = path.basename(rpath) + self.stash_cache(rpath, repdirname) + try: + if Git.is_git(rpath): + self.addProgress() + p = PercentProgress(emerge, "--sync", repname, part=1, atty=True) + for perc in p.progress(): + self.setProgress(perc) + else: + p = process(emerge, "--sync", repname, stderr=STDOUT) + if p.failed(): + raise UpdateError( + _("Failed to update the {rname} repository").format( + rname=repname), + addon=p.read()) + finally: + self.unstash_cache(rpath, repdirname) + return True + + def _regenCache_process(self, progname, repname, cpu_num): + return process(progname, "--repo=%s" % repname, "--update", + "--jobs=%s" % cpu_num, stderr=STDOUT) + + def regenCache(self, repname): + """ + Обновить кэш метаданных репозитория + """ + egenCache = self.get_prog_path('/usr/bin/egencache') + if not egenCache: + raise UpdateError(_("The Portage tool is not found")) + if repname in self.clVars.Get('cl_update_rep_name'): + path_rep = self.clVars.Select('cl_update_rep_path', + where='cl_update_rep_name', + eq=repname, limit=1) + repo_name = readFile( + path.join(path_rep, "profiles/repo_name")).strip() + if repo_name != repname: + self.printWARNING( + _("Repository '{repo_name}' called '{repname}'" + " in cl_update_rep_name").format( + repo_name=repo_name, repname=repname)) + raise UpdateError(_("Failed to update the cache of the {rname} " + "repository").format(rname=repname)) + cpu_num = self.clVars.Get('hr_cpu_num') + if repname in OverlayOwnCache(self.clVars): + self.printWARNING( + _("Repository %s has its own cache") % repname.capitalize()) + else: + self.startTask(_("Updating the %s repository cache") % + repname.capitalize()) + p = self._regenCache_process(egenCache, repname, cpu_num) + if p.failed(): + raise UpdateError(_("Failed to update the cache of the {rname} " + "repository").format(rname=repname), + addon=p.read()) + return True + + def emergeMetadata(self): + """ + Выполнить egencache и emerge --metadata + """ + emerge = self.get_prog_path("/usr/bin/emerge") + if not emerge: + raise UpdateError(_("The Emerge tool is not found")) + self.addProgress() + p = PercentProgress(emerge, "--ask=n", "--metadata", part=1, atty=True) + for perc in p.progress(): + self.setProgress(perc) + if p.failed(): + data = p.read() + with open('/var/log/calculate/failed-metadata-%d.log' % time.time(), + 'w') as f: + f.write(data + p.alldata) + raise UpdateError(_("Failed to update metadata"), addon=data) + return True + + def _eixUpdateCommand(self, eix_cmd, countRep): + return PercentProgress(eix_cmd, "-F", part=countRep or 1, atty=True) + + def _copyPreviousEix(self): + chroot_path = self.clVars.Get("cl_chroot_path") + portage_eix = pathJoin(chroot_path, "var/cache/eix/portage.eix") + previous_eix = pathJoin(chroot_path, "var/cache/eix/previous.eix") + if path.exists(portage_eix): + if path.exists(previous_eix): + os.unlink(previous_eix) + try: + with writeFile(previous_eix) as f: + f.write(readFile(portage_eix)) + portage_eix_stat = os.stat(portage_eix) + os.chmod(previous_eix, portage_eix_stat.st_mode) + os.chown(previous_eix, portage_eix_stat.st_uid, + portage_eix_stat.st_gid) + except (OSError,IOError): + self.printWARNING(_("Failed to create previous eix")) + return True + + + def eixUpdate(self, repositories): + """ + Выполенине eix-update для репозиторием + + eix-update выполнятется только для тех репозиториев, которые + обновлялись, если cl_update_eixsync_force==auto, либо + все, если cl_update_eixupdate_force==force + """ + self._copyPreviousEix() + + eixupdate = self.get_prog_path("/usr/bin/eix-update") + if not eixupdate: + raise UpdateError(_("The Eix tool is not found")) + self.addProgress() + countRep = len(repositories) + p = self._eixUpdateCommand(eixupdate, countRep) + for perc in p.progress(): + self.setProgress(perc) + if p.failed(): + raise UpdateError(_("Failed to update eix cache"), addon=p.read()) + return True + + def is_binary_pkg(self, pkg, binary=None): + """ + Является ли пакет бинарным + """ + if binary: + return True + if 'PN' in pkg and pkg['PN'].endswith('-bin'): + return True + if binary is not None: + return binary + if "binary" in pkg and pkg['binary']: + return True + return False + + def _printEmergePackage(self, pkg, binary=False, num=1, max_num=1): + """ + Вывод сообщения сборки пакета + """ + self.endTask() + _print = self.color_print + if self.pkgnum is not None: + num = self.pkgnum + if self.pkgnummax is not None: + max_num = self.pkgnummax + one = _print("{0}", num) + two = _print("{0}", max_num) + part = _("({current} of {maximum})").format(current=one, maximum=two) + _print = _print.foreground(Colors.DEFAULT) + if self.is_binary_pkg(pkg, binary): + _colorprint = _print.foreground(Colors.PURPLE) + else: + _colorprint = _print.foreground(Colors.GREEN) + + PackageInformation.add_info(pkg) + name = "" + if pkg.info['DESCRIPTION']: + name = _(pkg.info['DESCRIPTION']) + name = name[:1].upper() + name[1:] + if not name.strip(): + name = str(pkg) + + self.printSUCCESS( + _u(_("{part} {package}")).format(part=_u(part), package=_u(name))) + self.startTask( + _u(_("Emerging {package}")).format(package=_u(_colorprint(str(pkg))))) + + def _printInstallPackage(self, pkg, binary=False): + """ + Вывод сообщения установки пакета + """ + self.endTask() + _print = self.color_print + if self.is_binary_pkg(pkg, binary): + _print = _print.foreground(Colors.PURPLE) + else: + _print = _print.foreground(Colors.GREEN) + pkg_key = "{CATEGORY}/{PF}".format(**pkg) + if pkg_key in self.update_map: + self.startTask(_("Installing {pkg} [{oldver}]").format( + pkg=_print(str(pkg)), oldver=self.update_map[pkg_key])) + self.update_map.pop(pkg_key) + else: + self.startTask(_("Installing %s") % (_print(str(pkg)))) + + def _printFetching(self, fn): + """ + Вывод сообщения о скачивании + """ + self.endTask() + self.startTask(_("Fetching binary packages")) + + def _printUninstallPackage(self, pkg, num=1, max_num=1): + """ + Вывод сообщения удаления пакета + """ + self.endTask() + _print = self.color_print + if num and max_num: + one = _print("{0}", num) + two = _print("{0}", max_num) + part = _(" ({current} of {maximum})").format(current=one, + maximum=two) + else: + part = "" + _print = _print.foreground(Colors.LIGHT_RED) + + self.startTask( + _("Unmerging{part} {package}").format(part=part, + package=_print(str(pkg)))) + + def emergelike(self, cmd, *params): + """ + Запуск команды, которая подразумевает выполнение emerge + """ + cmd_path = self.get_prog_path(cmd) + if not cmd_path: + raise UpdateError(_("Failed to find the %s command") % cmd) + with EmergeParser( + emerge_parser.CommandExecutor(cmd_path, params)) as emerge: + self._startEmerging(emerge) + return True + + def revdep_rebuild(self, cmd, *params): + """ + Запуск revdep-rebulid + """ + cmd_path = self.get_prog_path(cmd) + if not cmd_path: + raise UpdateError(_("Failed to find the %s command") % cmd) + with EmergeParser( + emerge_parser.CommandExecutor(cmd_path, params)) as emerge: + revdep = RevdepPercentBlock(emerge) + self.addProgress() + revdep.add_observer(self.setProgress) + revdep.action = lambda x: ( + self.endTask(), self.startTask(_("Assigning files to packages")) + if "Assign" in revdep else None) + self._startEmerging(emerge) + return True + + def _display_pretty_package_list(self, pkglist, remove_list=False): + """ + Отобразить список пакетов в "удобочитаемом" виде + """ + _print = self.color_print + ebuild_color = TextState.Colors.GREEN + binary_color = TextState.Colors.PURPLE + remove_color = TextState.Colors.LIGHT_RED + flag_map = {"updating": + _print.foreground(TextState.Colors.LIGHT_CYAN)("U"), + "reinstall": + _print.foreground(TextState.Colors.YELLOW)("rR"), + "new": + _print.foreground(TextState.Colors.LIGHT_GREEN)("N"), + "newslot": + _print.foreground(TextState.Colors.LIGHT_GREEN)("NS"), + "downgrading": ( + _print.foreground(TextState.Colors.LIGHT_CYAN)("U") + + _print.foreground(TextState.Colors.LIGHT_BLUE)("D"))} + for pkg in sorted([PackageInformation.add_info(x) for x in + pkglist], + key=lambda y: y['CATEGORY/PN']): + install_flag = "" + if remove_list: + pkgcolor = _print.foreground(remove_color) + else: + for flag in flag_map: + if pkg[flag]: + install_flag = "(%s) " % flag_map[flag] + break + if self.is_binary_pkg(pkg): + pkgcolor = _print.foreground(binary_color) + else: + pkgcolor = _print.foreground(ebuild_color) + + if pkg.info['DESCRIPTION']: + fullname = "%s " % _(pkg.info['DESCRIPTION']) + fullname = fullname[:1].upper() + fullname[1:] + else: + fullname = "" + shortname = pkgcolor("%s-%s" % (pkg["CATEGORY/PN"], pkg["PVR"])) + if "SIZE" in pkg and pkg['SIZE'] and pkg["SIZE"] != "0 kB": + size = " (%s)" % pkg["SIZE"] + else: + size = "" + mult = _print.bold("*") + self.printDefault( + _u8(" {mult} {fullname}{flag}{shortname}{size}").format( + mult=_u8(mult), fullname=_u8(fullname), shortname=_u8(shortname), + size=_u8(size), + flag=_u8(install_flag))) + + def _display_install_package(self, emerge, emergelike=False): + """ + Отобразить список устанавливаемых пакетов + """ + # подробный список пакетов + _print = self.color_print + if emergelike: + self.printPre(str(emerge.install_packages)) + else: + pkglist = emerge.install_packages.list + self.printSUCCESS(_print( + _("Listing packages for installation"))) + self._display_pretty_package_list(pkglist) + if emerge.install_packages.remove_list: + self.printSUCCESS(_print( + _("Listing packages for removal"))) + self._display_pretty_package_list( + emerge.install_packages.remove_list, remove_list=True) + if len(emerge.install_packages.list) > 0: + install_mess = (_("{count} packages will be installed").format( + count=len(emerge.install_packages.list)) + ", ") + else: + install_mess = "" + if str(emerge.download_size) != "0 kB": + self.printSUCCESS(_("{install}{size} will be downloaded").format( + install=install_mess, + size=str(emerge.download_size))) + + def _display_remove_list(self, emerge): + """ + Отобразить список удаляемых пакетов + """ + # подробный список пакетов + if self.clVars.Get('update.cl_update_emergelist_set') == 'on': + self.printPre(self._emerge_translate( + emerge.uninstall_packages.verbose_result)) + else: + _print = self.color_print + pkglist = emerge.uninstall_packages.list + self.printSUCCESS(_print.bold( + _("Listing packages for removal"))) + self._display_pretty_package_list(pkglist, remove_list=True) + + def mark_schedule(self): + """ + Установить отметку о запуске запланированной проверки + """ + SystemIni(self.clVars).setVar('system', {'last_check': str(int(time.time()))}) + + def get_default_emerge_opts(self, depclean=False): + bdeps_val = self.clVars.Get('cl_update_with_bdeps_set') + if bdeps_val == "auto": + bdeps = " --with-bdeps-auto=y" + elif bdeps_val == "on": + bdeps = " --with-bdeps=y" + else: + bdeps = " --with-bdeps=n" + return self.clVars.Get('cl_emerge_default_opts') + bdeps + + def premerge(self, param, *packages): + """ + Вывести информацию об обновлении + """ + deo = self.get_default_emerge_opts() + param = [param, "-pv"] + + with EmergeParser(EmergeCommand(list(packages), emerge_default_opts=deo, + extra_params=param)) as emerge: + try: + emerge.run() + if not emerge.install_packages.list: + self.printSUCCESS(_("The system is up to date")) + self.set_need_update(False) + return True + emergelike = self.clVars.Get('cl_update_emergelist_set') == 'on' + self._display_install_package(emerge, emergelike) + if str(emerge.skipped_packages): + self._display_error(emerge.skipped_packages) + except EmergeError: + self.set_need_update(False) + self._display_install_package(emerge, emergelike=True) + self._display_error(emerge.prepare_error) + raise + if self.clVars.Get('cl_update_pretend_set') == 'on': + # установить кэш: есть обновления + self.set_need_update() + return True + self.set_need_update(False) + answer = self.askConfirm( + _("Would you like to merge these packages?"), "yes") + if answer == "no": + raise KeyboardInterrupt + return "yes" + return True + + def set_need_update(self, val=True): + """ + Установить флаг: есть обновления + """ + if self.clVars.Get('update.cl_update_autocheck_set') == 'off': + val = False + UpdateInfo.set_update_ready(val) + return True + + def _emerge_translate(self, s): + """ + Перевести текст из emerge + """ + return RegexpLocalization('cl_emerge').translate(str(s)) + + def setUpToDateCache(self): + """ + Установить кэш - "нет пакетов для обновления" + """ + #self.updateCache(PackageList([])) + self.set_need_update(False) + return True + + def _startEmerging(self, emerge): + """ + Настроить и выполнить emerge + """ + if emerge.install_packages and emerge.install_packages.list: + for pkg in emerge.install_packages.list: + rv = pkg.get('REPLACING_VERSIONS', '') + if rv: + self.update_map["{CATEGORY}/{PF}".format(**pkg)] = \ + rv.partition(":")[0] + emerge.command.send("yes\n") + emerge.emerging.add_observer(self._printEmergePackage) + emerge.installing.add_observer(self._printInstallPackage) + emerge.uninstalling.add_observer(self._printUninstallPackage) + emerge.fetching.add_observer(self._printFetching) + + def cancel_observing_fetch(fn): + emerge.fetching.clear_observers() + + emerge.fetching.add_observer(cancel_observing_fetch) + try: + emerge.run() + except EmergeError: + if emerge.emerging_error: + self._display_error(emerge.emerging_error.log) + else: + self._display_error(emerge.prepare_error) + raise + + def _display_error(self, error): + lines_num = int(self.clVars.Get('update.cl_update_lines_limit')) + error = "
".join(str(error).split('
')[-lines_num:]) + self.printPre(self._emerge_translate(error)) + + def emerge(self, use, param, *packages): + """ + Выполнить сборку пакета + """ + deo = self.get_default_emerge_opts() + if not packages: + packages = [param] + extra_params = None + else: + extra_params = [param] + + command = EmergeCommand(list(packages), emerge_default_opts=deo, + extra_params=extra_params, + use=use) + if self.clVars.Get('cl_chroot_path') != '/': + command = Chroot(self.clVars.Get('cl_chroot_path'), command) + + with EmergeParser(command) as emerge: + try: + emerge.question.action = lambda x: False + emerge.run() + if not emerge.install_packages.list: + return True + except EmergeError: + self._display_error(emerge.prepare_error) + raise + self._startEmerging(emerge) + return True + + def emerge_ask(self, pretend, *params): + """ + Вывести информацию об обновлении + """ + deo = self.get_default_emerge_opts() + param = [x for x in params if x.startswith("-")] + packages = [x for x in params if not x.startswith("-")] + command = EmergeCommand(list(packages), emerge_default_opts=deo, + extra_params=param) + with EmergeParser(command) as emerge: + try: + emerge.question.action = lambda x: False + emerge.run() + if emerge.install_packages.list: + emergelike = self.clVars.Get( + 'update.cl_update_emergelist_set') == 'on' + self._display_install_package(emerge, emergelike) + if emerge.skipped_packages: + self._display_error(emerge.skipped_packages) + if not pretend: + answer = self.askConfirm( + _("Would you like to merge these packages?"), "yes") + if answer == "no": + emerge.command.send("no\n") + raise KeyboardInterrupt + else: + return True + else: + self.set_need_update(False) + self.printSUCCESS(_("The system is up to date")) + except EmergeError: + self.set_need_update(False) + self._display_install_package(emerge, emergelike=True) + self._display_error(emerge.prepare_error) + raise + self._startEmerging(emerge) + return True + + def depclean(self): + """ + Выполнить очистку системы от лишних пакетов + """ + deo = self.get_default_emerge_opts(depclean=True) + emerge = None + try: + emerge = EmergeParser(EmergeCommand(["--depclean", + "--dynamic-deps=n"], + emerge_default_opts=deo)) + outdated_kernel = False + try: + emerge.question.action = lambda x: False + emerge.run() + if not emerge.uninstall_packages.list: + UpdateInfo(self.clVars).outdated_kernel = False + return True + kernel_pkg = self.clVars.Get('cl_update_kernel_pkg') + if any(("%s-%s" % (x['CATEGORY/PN'], x['PVR'])) == kernel_pkg + for x in emerge.uninstall_packages.list): + pkglist = [ + "=%s-%s" % (x['CATEGORY/PN'], x['PVR']) for x in + emerge.uninstall_packages.list + if ("%s-%s" % (x['CATEGORY/PN'], + x['PVR'])) != kernel_pkg] + emerge.command.send('n\n') + emerge.close() + emerge = None + if not pkglist: + UpdateInfo(self.clVars).outdated_kernel = True + return True + emerge = EmergeParser( + EmergeCommand(pkglist, + extra_params=["--unmerge", '--ask=y'], + emerge_default_opts=deo)) + emerge.question.action = lambda x: False + emerge.run() + outdated_kernel = True + else: + outdated_kernel = False + self._display_remove_list(emerge) + except EmergeError: + self._display_error(emerge.prepare_error) + raise + if (self.askConfirm( + _("Would you like to unmerge these unused packages " + "(recommended)?")) != 'yes'): + return True + UpdateInfo(self.clVars).outdated_kernel = outdated_kernel + self._startEmerging(emerge) + finally: + if emerge: + emerge.close() + return True + + def update_task(self, task_name): + """ + Декоратор для добавления меток запуска и останова задачи + """ + + def decor(f): + def wrapper(*args, **kwargs): + logger = EmergeLog(EmergeLogNamedTask(task_name)) + logger.mark_begin_task() + ret = f(*args, **kwargs) + if ret: + logger.mark_end_task() + return ret + + return wrapper + + return decor + + def migrateCacheRepository(self, url, branch, storage): + """ + Перенести репозиторий из кэша в локальный + """ + rep = storage.get_repository(url, branch) + if rep: + rep.storage = storage.storages[0] + self.clVars.Invalidate('cl_update_profile_storage') + return True + + def reconfigureProfileVars(self, profile_dv, chroot): + """ + Синхронизировать репозитории + Настройка переменных для выполнения синхронизации репозиториев + """ + dv = self.clVars + + try: + if not profile_dv: + raise UpdateError( + _("Failed to use the new profile. Try again.")) + for var_name in ('cl_update_rep_path', + 'cl_update_rep_url', + 'cl_update_rep_name', + 'cl_update_branch', + 'cl_update_binhost_list', + 'cl_update_binhost_unstable_list', + 'cl_update_binhost_stable_set', + 'cl_update_binhost_stable_opt_set', + 'cl_update_branch_name', + 'cl_profile_system', + 'cl_update_rep'): + dv.Set(var_name, profile_dv.Get(var_name), force=True) + dv.Set('cl_chroot_path', chroot, force=True) + except DataVarsError as e: + error = UpdateError(_("Wrong profile")) + error.addon = e + raise error + return True + + def setProfile(self, profile_shortname): + profile = self.clVars.Select('cl_update_profile_path', + where='cl_update_profile_shortname', + eq=profile_shortname, limit=1) + if not profile: + raise UpdateError(_("Failed to determine profile %s") % + self.clVars.Get('cl_update_profile_system')) + profile_path = path.relpath(profile, '/etc/portage') + try: + profile_file = '/etc/portage/make.profile' + if not path.exists( + path.join(path.dirname(profile_file), profile_path)): + raise UpdateError( + _("Failed to set the profile: %s") % _("Profile not found")) + for rm_fn in filter(path.lexists, + ('/etc/make.profile', + '/etc/portage/make.profile')): + os.unlink(rm_fn) + os.symlink(profile_path, profile_file) + except (OSError, IOError) as e: + raise UpdateError(_("Failed to set the profile: %s") % str(e)) + return True + + def applyProfileTemplates(self, useClt=None, cltFilter=False, + useDispatch=True, action="merge"): + """ + Наложить шаблоны из профиля + """ + from calculate.lib.cl_template import TemplatesError, ProgressTemplate + + dv = DataVarsUpdate() + try: + dv.importUpdate() + dv.flIniFile() + dv.Set('cl_action', action, force=True) + try: + dv.Set('cl_templates_locate', + self.clVars.Get('cl_update_templates_locate')) + except VariableError: + self.printERROR(_("Failed to apply profiles templates")) + return True + dv.Set("cl_chroot_path", '/', True) + dv.Set("cl_root_path", '/', True) + for copyvar in ("cl_dispatch_conf", "cl_verbose_set", + "update.cl_update_world"): + dv.Set(copyvar, self.clVars.Get(copyvar), True) + # определение каталогов содержащих шаблоны + useClt = useClt in ("on", True) + self.addProgress() + nullProgress = lambda *args, **kw: None + dispatch = self.dispatchConf if useDispatch else None + clTempl = ProgressTemplate(nullProgress, dv, cltObj=useClt, + cltFilter=cltFilter, + printSUCCESS=self.printSUCCESS, + printWARNING=self.printWARNING, + askConfirm=self.askConfirm, + dispatchConf=dispatch, + printERROR=self.printERROR) + try: + clTempl.applyTemplates() + if clTempl.hasError(): + if clTempl.getError(): + raise TemplatesError(clTempl.getError()) + finally: + if clTempl: + if clTempl.cltObj: + clTempl.cltObj.closeFiles() + clTempl.closeFiles() + finally: + dv.close() + return True + + def cleanpkg(self): + """ + Очистить PKGDIR и DISTFILES в текущей системе + """ + portdirs = ([self.clVars.Get('cl_portdir')] + + self.clVars.Get('cl_portdir_overlay')) + pkgfiles = get_packages_files_directory(*portdirs) + distdirfiles = get_manifest_files_directory(*portdirs) + distdir = self.clVars.Get('install.cl_distfiles_path') + pkgdir = self.clVars.Get('cl_pkgdir') + + logger = log("update_cleanpkg.log", + filename="/var/log/calculate/update_cleanpkg.log", + formatter="%(asctime)s - %(clean)s - %(message)s") + + return self._cleanpkg( + distdir, pkgdir, distdirfiles, pkgfiles, logger) + + def _update_binhost_packages(self): + os.system('/usr/sbin/emaint binhost -f &>/dev/null') + + def _cleanpkg(self, distdir, pkgdir, distdirfiles, pkgfiles, logger): + """ + Общий алгоритм очистки distfiles и pkgdir от устаревших пакетов + """ + skip_files = ["/metadata.dtd", "/Packages"] + try: + if self.clVars.Get('client.os_remote_auth'): + skip_files += ['portage_lockfile'] + except DataVarsError: + pass + + for cleantype, filelist in ( + ("packages", + get_remove_list(pkgdir, list(pkgfiles), depth=4)), + ("distfiles", + get_remove_list(distdir, list(distdirfiles), depth=1))): + removelist = [] + for fn in filelist: + try: + if not any(fn.endswith(x) for x in skip_files): + os.unlink(fn) + removelist.append(path.basename(fn)) + except OSError: + pass + removelist_str = ",".join(removelist) + if removelist_str: + logger.info(removelist_str, extra={'clean': cleantype}) + if cleantype == "packages": + try: + self._update_binhost_packages() + for dn in listDirectory(pkgdir, fullPath=True): + if path.isdir(dn) and not listDirectory(dn): + os.rmdir(dn) + except OSError: + pass + return True + + def updateSetupCache(self): + cache = SetupCache(self.clVars) + cache.update(force=True) + return True + + def get_bin_cache_filename(self): + return pathJoin(self.clVars.Get('cl_chroot_path'), + LayeredIni.IniPath.Grp) + + def update_local_info_binhost(self, write_binhost=True): + """ + Проверить, что доступен хотя бы один из binhost'ов + :return: + """ + hosts = self.clVars.Get("update.cl_update_binhost_host") + datas = self.clVars.Get("update.cl_update_binhost_revisions") + if not hosts: + self.delete_binhost() + raise UpdateError(_("Failed to find the server with " + "appropriate updates")) + else: + with writeFile(self.get_bin_cache_filename()) as f: + f.write(datas[0].strip()+"\n") + if write_binhost: + if hosts[0] != self.clVars.Get('update.cl_update_binhost'): + self.refresh_binhost = True + self.clVars.Set('cl_update_package_cache_set', 'on') + self.clVars.Write('cl_update_binhost', hosts[0], location="system") + new_ts = self.clVars.Get("update.cl_update_binhost_timestamp") + if new_ts: + new_ts = new_ts[0] + if new_ts.isdigit(): + ini = SystemIni(self.clVars) + ini.setVar('system', {'last_update': new_ts}) + if self.is_update_action(self.clVars.Get("cl_action")): + value = self.clVars.GetBool('update.cl_update_binhost_stable_set') + new_value = self.clVars.GetBool('update.cl_update_binhost_stable_opt_set') + if value != new_value: + self.clVars.Write( + 'cl_update_binhost_stable_set', + self.clVars.Get('update.cl_update_binhost_stable_opt_set'), + location="system") + return True + + def is_update_action(self, action): + return action == 'sync' + + def modify_binary_depends(self, bdeps, prefix="/"): + """ + Включить/выключить сборочные зависимости для бинарных пакетов + """ + hide = bdeps == "auto" + self.startTask(_("Updating binary build dependences")) + vardb = Vardb(pathJoin(prefix,VDB_PATH)) + if hide: + vardb.hide_binary_depends() + else: + vardb.unhide_binary_depends() + self.endTask() + return True + + def save_with_bdeps(self): + oldval = self.clVars.Get('cl_update_with_bdeps_set') + newval = self.clVars.Get('cl_update_with_bdeps_opt_set') + if oldval != newval: + self.modify_binary_depends(newval) + self.clVars.Write('cl_update_with_bdeps_set', newval, + location="system") + self.clVars.Set('cl_update_force_depclean_set', 'on') + return True + + def message_binhost_changed(self): + if self.refresh_binhost: + self.printWARNING(_("Update server was changed to %s") % + self.clVars.Get('update.cl_update_binhost')) + self.clVars.Set("update.cl_update_package_cache_set", + Variable.On, force=True) + else: + self.printSUCCESS(_("Update server %s") % + self.clVars.Get('update.cl_update_binhost')) + return True + + def delete_binhost(self): + self.clVars.Delete('cl_update_binhost', location="system") + try: + bin_cache_fn = self.get_bin_cache_filename() + if path.exists(bin_cache_fn): + os.unlink(bin_cache_fn) + except OSError: + raise UpdateError( + _("Failed to remove cached ini.env of binary repository")) + try: + for varname in ('update.cl_update_package_cache', + 'update.cl_update_package_cache_sign'): + fn = self.clVars.Get(varname) + if path.exists(fn): + os.unlink(fn) + except OSError: + raise UpdateError( + _("Failed to remove cached Package index")) + # удалить binhost + binhost_fn = self.inchroot( + self.clVars.Get("update.cl_update_portage_binhost_path")) + if path.exists(binhost_fn): + os.unlink(binhost_fn) + return False + + def update_binhost_list(self, dv=None): + """ + Обновить список binhost'ов после обновления до master веток + :return: + """ + changes = False + try: + if dv is None: + dv = DataVarsUpdate() + dv.importUpdate() + dv.flIniFile() + for varname in ('update.cl_update_binhost_list', + 'update.cl_update_binhost_unstable_list', + 'update.cl_update_binhost_timestamp_path', + 'update.cl_update_binhost_revision_path'): + new_value = dv.Get(varname) + old_value = self.clVars.Get(varname) + if new_value != old_value: + changes = True + self.clVars.Set(varname, new_value, force=True) + except DataVarsError as e: + raise UpdateError(_("Failed to get values for binhost search")) + + if not changes: + return False + self.create_binhost_data() + return True + + def drop_binhosts(self, dv): + """ + Обновление до master веток + """ + branch = dv.Get('update.cl_update_branch') + revs = [ + branch for x in dv.Get('update.cl_update_rep_name') + ] + dv.Set('update.cl_update_branch_name', revs) + dv.Invalidate('update.cl_update_rep_rev') + return True + + def inchroot(self, fn): + return pathJoin(self.clVars.Get("cl_chroot_path"), fn) + + def prepare_gpg(self): + """ + Получить объект для проверки подписи, либо получить заглушку + """ + gpg_force = self.clVars.Get('cl_update_gpg_force') + gpg_keys = [self.inchroot(x) + for x in self.clVars.Get('cl_update_gpg_keys')] + if gpg_force == "skip": + return True + gpgtmpdir = "/var/calculate/tmp/update" + if not path.exists(gpgtmpdir): + makeDirectory(gpgtmpdir) + gpg = GPG(tempfile.mkdtemp(dir=gpgtmpdir, + prefix="gpg-")) + for keyfn in gpg_keys: + if path.exists(keyfn): + try: + key = readFile(keyfn) + gpg.import_key(key) + except GPGError as e: + self.printWARNING(_("Failed to load public keys from '%s' " + "for signature checking") % keyfn) + if not gpg.count_public(): + if gpg_force == "force": + raise UpdateError(_("Public keys for Packages signature checking not found")) + else: + return True + self.clVars.Set('update.cl_update_gpg', gpg, force=True) + return True + + def download_packages(self, url_binhost, packages_fn, packages_sign_fn, gpg): + quite_unlink(packages_fn) + orig_packages = Binhosts.fetch_packages(url_binhost) + try: + with writeFile(packages_fn) as f: + pi = PackagesIndex(orig_packages) + pi["TTL"] = str(30 * DAYS) + pi["DOWNLOAD_TIMESTAMP"] = str(int(time.time())) + pi.write(f) + except (OSError, IOError): + raise UpdateError(_("Failed to save Packages")) + self.endTask(True) + self.startTask(_("Check packages index signature")) + if not gpg: + self.endTask("skip") + self.clVars.Set('cl_update_package_cache_set', Variable.Off, force=True) + return True + try: + Binhosts.check_packages_signature( + url_binhost, orig_packages, gpg) + with writeFile(packages_sign_fn) as f: + f.write(Binhosts.fetch_packages_sign(url_binhost)) + except BinhostSignError: + for fn in (packages_fn, packages_sign_fn): + if path.exists(fn): + try: + os.unlink(fn) + except OSError: + pass + self.clVars.Set("update.cl_update_bad_sign_set", Variable.On) + self.clVars.Set('update.cl_update_binhost_recheck_set', Variable.On) + self.clVars.Set('cl_update_package_cache_set', Variable.Off, force=True) + raise + return True + + class Reason(object): + Success = 0 + BadSign = 1 + Outdated = 2 + Skip = 3 + Updating = 4 + WrongBinhost = 5 + BadEnv = 6 + EnvNotFound = 7 + SkipSlower = 8 + UnknownError = 9 + + + @staticmethod + def humanReadable(reason): + return { + Update.Reason.WrongBinhost: "FAILED (Wrong binhost)", + Update.Reason.Outdated: "OUTDATED", + Update.Reason.Updating: "UPDATING", + Update.Reason.BadEnv: "FAILED (Bad env)", + Update.Reason.EnvNotFound: "FAILED (Env not found)", + Update.Reason.UnknownError: "FAILED (Unknown error)", + Update.Reason.BadSign: "FAILED (Bad sign)", + Update.Reason.Skip: "SKIP", + Update.Reason.SkipSlower: "", + Update.Reason.Success: "" + }.get(reason,reason) + + def _get_binhost_logger(self): + return log("binhost-scan.log", + filename=pathJoin( + self.clVars.Get('cl_chroot_path'), + "/var/log/calculate/binhost-scan.log"), + formatter="%(message)s") + + def get_arch_machine(self): + return self.clVars.Get('os_arch_machine') + + @variable_module("update") + def create_binhost_data(self): + dv = self.clVars + last_ts = dv.Get('cl_update_last_timestamp') + if dv.GetBool('cl_update_binhost_stable_opt_set'): + binhost_list = dv.Get('cl_update_binhost_list') + else: + binhost_list = dv.Get('cl_update_binhost_unstable_list') + self.binhosts_data = Binhosts( + # значение малозначимо, поэтому берётся из собирающей системы + dv.GetInteger('cl_update_binhost_timeout'), + dv.Get('cl_update_binhost_revision_path'), + dv.Get('cl_update_binhost_timestamp_path'), + last_ts, binhost_list, + self.get_arch_machine(), + gpg=dv.Get('update.cl_update_gpg')) + return True + + @variable_module("update") + def _search_best_binhost(self, binhosts_data, stabilization): + if not self.clVars.Get('cl_ebuild_phase'): + logger = self._get_binhost_logger() + if logger: + logger.info( + "Started scan on: {date}, current timestamp: {ts}".format( + date=time.ctime(), ts=binhosts_data.last_ts)) + retval = [] + skip_check_status = False + actual_reason = self.Reason.UnknownError + for binhost in sorted(binhosts_data.get_binhosts(), reverse=True): + host = binhost.host + if not binhost.valid: + reason = self.Reason.WrongBinhost + elif binhost.outdated: + reason = self.Reason.Outdated + elif not skip_check_status: + status = binhost.status + if status is not binhosts_data.BinhostStatus.Success: + errors = { + binhosts_data.BinhostStatus.Updating: self.Reason.Updating, + binhosts_data.BinhostStatus.BadEnv: self.Reason.BadEnv, + binhosts_data.BinhostStatus.EnvNotFound: self.Reason.EnvNotFound + } + reason = errors.get(status, self.Reason.UnknownError) + elif binhost.bad_sign: + reason = self.Reason.BadSign + else: + # SUCCESS + if not binhost.downgraded or stabilization: + host = "-> %s" % host + reason = self.Reason.Success + else: + reason = self.Reason.Skip + elif binhost.downgraded: + reason = self.Reason.Skip + else: + reason = self.Reason.SkipSlower + + if reason == self.Reason.Success: + retval.append([binhost.host, binhost.data, + str(binhost.timestamp), + str(binhost.duration)]) + skip_check_status = True + if reason < actual_reason: + actual_reason = reason + logger.info("{host:<60} {speed:<7} {timestamp:<10} {reason}".format( + host=host, speed=float(binhost.duration) / 1000.0, + timestamp=binhost.timestamp, + reason=Update.Reason.humanReadable(reason))) + if not retval: + if actual_reason is self.Reason.BadSign: + raise UpdateError(_("Failed to find the reliable server with appropriate updates")) + elif actual_reason in (self.Reason.Outdated, + self.Reason.Skip, + self.Reason.Updating): + raise UpdateError(_("Failed to find the server with appropriate updates")) + else: + raise UpdateError(_("Failed to find the working server with updates")) + return retval + + def check_current_binhost(self, binhost_url): + """ + Проверка текущего сервера обновлений на валидность + """ + if not binhost_url in self.binhosts_data.binhost_list: + raise UpdateError(_("Current binhost is absent in list of update servers")) + binhost = self.binhosts_data.get_binhost(binhost_url) + + if binhost.valid and not binhost.outdated and not binhost.downgraded: + if binhost.status == self.binhosts_data.BinhostStatus.Success: + self.clVars.Set('update.cl_update_binhost_data', + [[binhost.host, binhost.data, + str(binhost.timestamp), + str(binhost.duration)]], + force=True) + self.endTask() + else: + if not binhost.valid: + raise UpdateError( + _("Current binhost {} is not valid").format(binhost_url)) + elif binhost.outdated: + raise UpdateError( + _("Current binhost {} is outdated").format(binhost_url)) + elif binhost.downgraded: + raise UpdateError( + _("Binary packages on the current binhost {} " + "are older than local").format(binhost_url)) + if self.binhosts_data.gpg: + packages_fn = self.clVars.Get('update.cl_update_package_cache') + packages_sign_fn = self.clVars.Get('update.cl_update_package_cache_sign') + if path.exists(packages_fn) and path.exists(packages_sign_fn): + packages_sign = readFile(packages_sign_fn) + pi = PackagesIndex(readFile(packages_fn)) + pi.clean() + try: + Binhosts.check_packages_signature( + None, pi.get_value(), self.binhosts_data.gpg, + sign=packages_sign) + return True + except BinhostSignError: + for fn in (packages_fn, packages_sign_fn): + if path.exists(fn): + try: + os.unlink(fn) + except OSError: + pass + if binhost.bad_sign: + raise UpdateError( + _("Current binhost {} has wrong signature").format( + binhost_url)) + return True + + @variable_module("update") + def detect_best_binhost(self): + # выполняется переход с серверов unstable обновлней на stable + # в этом случае не важно, что бинари могут старее текущих + if (self.clVars.GetBool('cl_update_binhost_stable_opt_set') and + not self.clVars.GetBool('cl_update_binhost_stable_set')): + stabilization = True + else: + stabilization = False + + self.startTask(_("Searching new binhost")) + retval = self._search_best_binhost(self.binhosts_data, stabilization) + + self.clVars.Set('update.cl_update_binhost_data', + retval or Variable.EmptyTable, force=True) + + self.endTask() + return True + + def update_rep_list(self): + """ + Обновить список доступных репозиториев + :param builder_path: + :return: + """ + cmd = "/usr/bin/eselect" + cmd_path = self.get_prog_path(cmd) + if not cmd_path: + raise UpdateError(_("Failed to find the %s command") % cmd) + repsync = emerge_parser.CommandExecutor(cmd_path, ["repository", "list"]) + repsync.execute() + return repsync.success() + + def rename_custom_files(self): + """ + Переименовать все custom файлы: keywords, use, sets и т.д. в связи + с изменением профиля + """ + newdv = self.clVars.Get("cl_update_profile_datavars") + cur_short = self.clVars.Get("os_linux_shortname").lower() + new_short = newdv["os_linux_shortname"].lower() + if cur_short != new_short: + for fn in ("/etc/portage/package.keywords/custom.{}", + "/etc/portage/package.use/custom.{}", + "/etc/portage/package.mask/custom.{}", + "/etc/portage/package.unmask/custom.{}", + "/etc/portage/sets/custom.{}", + "/etc/portage/make.conf/custom.{}"): + fn_source = fn.format(cur_short) + fn_target = fn.format(new_short) + try: + if path.exists(fn_source) and not path.exists(fn_target): + os.rename(fn_source, fn_target) + except (OSError, IOError) as e: + self.printWARNING(str(e)) + world_sets_fn = "/var/lib/portage/world_sets" + if path.exists(world_sets_fn): + worlds_sets = readFile(world_sets_fn) + new_worlds_sets = re.sub("^@custom.{}$".format(cur_short), + "@custom.{}".format(new_short), + worlds_sets, flags=re.M) + try: + with open(world_sets_fn, 'w') as fd: + fd.write(new_worlds_sets) + except IOError as e: + self.printWARNING(str(e)) + return True + + def save_portage_state_hash(self): + """ + Сохранить состояние + """ + ini = SystemIni(self.clVars) + ps = PortageState() + new_portage_state_hash = ps.get_state() + ini.setVar('system', {'portage_hash': new_portage_state_hash}) + return True + + def drop_portage_state_hash(self): + """ + Сбросить состояние + """ + ini = SystemIni(self.clVars) + ini.delVar('system', 'portage_hash') + return True + + def update_fastlogin_domain_path(self): + try: + if not self.clVars.Get('client.cl_remote_host'): + return True + except: + return True + fn = '/var/lib/calculate/calculate-desktop/fastlogin-domain' + try: + with writeFile(fn) as f: + f.write("{}\n".format( + "\n".join(get_fastlogin_domain_path(self.clVars)))) + except Exception: + if os.path.exists(fn): + try: + os.unlink(fn) + except Exception: + pass + return True diff --git a/libs_crutch/update/update_info.py b/libs_crutch/update/update_info.py index 40c0a47..7b22157 100644 --- a/libs_crutch/update/update_info.py +++ b/libs_crutch/update/update_info.py @@ -16,50 +16,13 @@ import os from os import path -# from itertools import ifilter from calculate.core.datavars import DataVarsCore -# from calculate.core.server.gen_pid import search_worked_process +from calculate.core.server.gen_pid import search_worked_process from calculate.lib.cl_template import SystemIni from calculate.lib.utils.content import getCfgFiles from calculate.lib.utils.files import getRunCommands, readFile, writeFile -# from core: -####################################### -class ProcessStatus(object): - SuccessFinished = 0 - Worked = 1 - FailedFinished = 2 - NotFound = 3 - Paused = 4 - -def search_worked_process(method_name, clVars, - statuses=(ProcessStatus.Worked,)): - """ - Найти все работающие процессы - - Возвращает список процессов со статусом Worked и существующем системным - процессом - """ - - def generator(): - pids = clVars.Get('core.cl_core_pids_path') - for pidfile in listDirectory(pids, fullPath=True): - try: - status = pickle.load(open(pidfile)) - if ((method_name is None or status['name'] == method_name) and - status['status'] in statuses): - pid_path = path.join("/proc", str(status['os_pid'])) - if path.exists(pid_path): - cmdline = readFile(path.join(pid_path, "cmdline")) - if cmdline and any(x in cmdline - for x in get_symlink_commands()): - yield status['os_pid'] - except (socket.error, ValueError, KeyError, EOFError, OSError): - pass - - return list(generator()) -################################### class UpdateInfo(object): """ Информационный объект о процессе обновления diff --git a/libs_crutch/update/update_tasks.py b/libs_crutch/update/update_tasks.py new file mode 100644 index 0000000..ffaf0e9 --- /dev/null +++ b/libs_crutch/update/update_tasks.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +# Copyright 2015-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class EmergeMark: + Schedule = "schedule" + Premerge = "check updates" + PythonUpdater = "update python modules" + PerlCleaner = "update perl modules" + KernelModules = "update kernel modules" + Depclean = "depclean" + XorgModules = "update xorg modules" + PreservedLibs = "update preserved libs" + RevdepRebuild = "revdep rebuild" + Prelink = "prelink" + Automagic = "check for auto depends" diff --git a/libs_crutch/update/utils/__init__.py b/libs_crutch/update/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs_crutch/update/utils/cl_setup_update.py b/libs_crutch/update/utils/cl_setup_update.py new file mode 100644 index 0000000..c51fefa --- /dev/null +++ b/libs_crutch/update/utils/cl_setup_update.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +# Copyright 2010-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.utils.files import FilesError +from calculate.update.update import UpdateError +from calculate.lib.utils.git import GitError + +_ = lambda x: x +setLocalTranslate('cl_update3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClSetupUpdateAction(Action): + """ + Действие для настройки параметров автопроверки обновлений + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError, UpdateError, GitError) + + successMessage = __("Updates autocheck configured!") + failedMessage = __("Failed to configure the updates autocheck procedure!") + interruptMessage = __("Configuration manually interrupted") + + # список задач для действия + tasks = [ + {'name': 'set_variables', + 'method': 'Update.setAutocheckParams(cl_update_autocheck_set,' + 'cl_update_autocheck_interval,' + 'cl_update_other_set,' + 'cl_update_cleanpkg_set)'} + ] diff --git a/libs_crutch/update/utils/cl_update.py b/libs_crutch/update/utils/cl_update.py new file mode 100644 index 0000000..f139015 --- /dev/null +++ b/libs_crutch/update/utils/cl_update.py @@ -0,0 +1,457 @@ +# -*- coding: utf-8 -*- + +# Copyright 2010-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action, Tasks, AllTasks +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.cl_template import TemplatesError +from calculate.lib.utils.binhosts import BinhostError +from calculate.lib.utils.files import FilesError, readFile +from calculate.update.update import UpdateError +from calculate.update.emerge_parser import EmergeError +from calculate.lib.utils.git import GitError +from calculate.lib.utils.portage import (EmergeLog, isPkgInstalled, + EmergeLogNamedTask, PackageList) +from calculate.update.update_tasks import EmergeMark + +_ = lambda x: x +setLocalTranslate('cl_update3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +def get_synchronization_tasks(object_name): + Object = lambda s: "%s.%s"%(object_name, s) + return [ + + {'name': 'reps_synchronization', + 'group': __("Repositories synchronization"), + 'tasks': [ + # создать объект проверки PGP + {'name': 'prepare_gpg', + 'method': Object("prepare_gpg()"), + }, + # создать объект хранилище серверов обновлений + {'name': 'create_binhost_data', + 'method': Object('create_binhost_data()') + }, + # проверить валиден ли текущий хост + {'name': 'check_current_binhost', + 'message': __("Checking current binhost"), + 'essential': False, + 'method': Object('check_current_binhost(update.cl_update_binhost)'), + 'condition': lambda GetBool, Get: ( + not GetBool('update.cl_update_binhost_recheck_set') and + Get('update.cl_update_sync_rep') and + Get('update.cl_update_binhost')) + }, + {'name': 'not_use_search:failed_base_binhost', + 'error': __("Failed to use base binhost"), + 'method': Object("delete_binhost()"), + 'depend': AllTasks.failed_all("check_current_binhost") + }, + {'name': 'group_find_binhost', + 'group': '', + 'while': (~AllTasks.has_any("detect_best_binhost") & + ((AllTasks.failed_all("update_packages_cache") + & ~AllTasks.has_any("not_use_search")) | + ~AllTasks.has_any("sync_reps"))) & Tasks.success(), + 'condition': lambda GetBool, Get: (GetBool('update.cl_update_usetag_set') and + Get('update.cl_update_sync_rep')), + 'tasks': [ + # найти лучший сервер обновлений + {'name': 'detect_best_binhost', + 'method': Object('detect_best_binhost()'), + 'essential': False, + 'depend': (Tasks.success() & ~AllTasks.has_any("not_use_search") & + (~AllTasks.success_one_of("check_current_binhost") | + AllTasks.success_all("sync_reps"))), + }, + # запасная синхронизация, в ходе которой ветки обновляются до + # master + {'name': 'sync_reps_fallback', + 'foreach': 'update.cl_update_sync_rep', + 'message': + __("Fallback syncing the {eachvar:capitalize} repository"), + 'method': Object('syncRepositories(eachvar,True)'), + 'depend': Tasks.success() & AllTasks.failed_one_of("detect_best_binhost"), + }, + # обновление переменных информации из binhost + {'name': 'sync_reps_fallback:update_binhost_list', + 'method': Object('update_binhost_list()'), + 'depend': Tasks.success() & AllTasks.failed_one_of("detect_best_binhost"), + }, + # найти лучший сервер обновлений + {'name': 'sync_reps_fallback:detect_best_binhost', + 'method': Object('detect_best_binhost()'), + 'depend': Tasks.success() & AllTasks.failed_one_of("detect_best_binhost"), + }, + {'name': 'sync_reps', + 'foreach': 'update.cl_update_sync_rep', + 'message': __("Checking {eachvar:capitalize} updates"), + 'method': Object('syncRepositories(eachvar)'), + 'condition': lambda Get: Get('update.cl_update_sync_rep'), + 'depend': Tasks.success() & ~AllTasks.success_all("update_packages_cache") + }, + {'name': 'sync_reps:update_local_info_binhost', + 'method': Object('update_local_info_binhost()'), + }, + {'name': 'sync_reps:update_binhost_list', + 'essential': False, + 'method': Object('update_binhost_list()'), + 'condition': lambda GetBool: GetBool('update.cl_update_outdate_set') + }, + {'name': 'sync_reps:update_packages_cache', + 'message': __("Update packages index"), + 'method': Object('download_packages(update.cl_update_portage_binhost,' + 'update.cl_update_package_cache,update.cl_update_package_cache_sign,' + 'update.cl_update_gpg)'), + 'essential': False, + 'condition': lambda Get, GetBool: ( + Get('update.cl_update_package_cache') and ( + Get('update.cl_update_outdate_set') == 'on' or + Get('update.cl_update_package_cache_set') == 'on')) + }, + ], + }, + {'name': 'no_server', + 'error': __("Failed to find the binary updates server"), + 'method': Object("delete_binhost()"), + # method: который должен удалить текущую информацию о сервере обновлений + 'depend': (~Tasks.has_any("failed_base_binhost") & (Tasks.failed() | + Tasks.success() & AllTasks.failed_one_of("update_packages_cache"))), + 'condition': lambda GetBool, Get: (GetBool('update.cl_update_usetag_set') and + Get('update.cl_update_sync_rep')), + }, + {'name': 'sync_reps', + 'foreach': 'update.cl_update_sync_rep', + 'message': __("Checking {eachvar:capitalize} updates"), + 'method': Object('syncRepositories(eachvar)'), + 'condition': lambda Get, GetBool: (Get('update.cl_update_sync_rep') and + not GetBool('update.cl_update_usetag_set')), + }, + {'name': 'update_rep_list', + 'message': __("Repository cache update"), + 'method': Object('update_rep_list()'), + 'condition': lambda Get: (isPkgInstalled( + "app-eselect/eselect-repository", prefix=Get('cl_chroot_path')) and + Get('cl_chroot_path') != "/"), + 'essential': False, + }, + {'name': 'sync_other_reps', + 'foreach': 'update.cl_update_other_rep_name', + 'message': __("Syncing the {eachvar:capitalize} repository"), + 'method': Object('syncOtherRepository(eachvar)'), + 'condition': lambda GetBool: GetBool('update.cl_update_other_set') + }, + {'name': 'trim_reps', + 'foreach': 'update.cl_update_sync_rep', + 'message': __("Cleaning the history of the " + "{eachvar:capitalize} repository"), + 'method': Object('trimRepositories(eachvar)'), + 'condition': lambda Get: (Get('update.cl_update_sync_rep') and + Get('update.cl_update_onedepth_set') == 'on') + }, + {'name': 'sync_reps:regen_cache', + 'foreach': 'update.cl_update_sync_overlay_rep', + 'essential': False, + 'method': Object('regenCache(eachvar)'), + 'condition': ( + lambda Get: (Get('update.cl_update_outdate_set') == 'on' and + Get('update.cl_update_egencache_force') != 'skip' or + Get('update.cl_update_egencache_force') == 'force')) + }, + {'name': 'sync_other_reps:regen_other_cache', + 'foreach': 'update.cl_update_other_rep_name', + 'method': Object('regenCache(eachvar)'), + 'essential': False, + }, + {'name': 'eix_update', + 'message': __("Updating the eix cache for " + "{update.cl_update_eix_repositories}"), + 'method': Object('eixUpdate(cl_repository_name)'), + 'condition': ( + lambda Get: (Get('update.cl_update_outdate_set') == 'on' and + Get('update.cl_update_eixupdate_force') != 'skip' or + Get('update.cl_update_eixupdate_force') == 'force')) + }, + {'name': 'update_setup_cache', + 'message': __("Updating the cache of configurable packages"), + 'method': Object('updateSetupCache()'), + 'essential': False, + 'condition': lambda Get: Get('update.cl_update_outdate_set') == 'on' + }, + {'name': 'sync_reps:cleanpkg', + 'message': __("Removing obsolete distfiles and binary packages"), + 'method': Object('cleanpkg()'), + 'condition': ( + lambda Get: Get('update.cl_update_cleanpkg_set') == 'on' and + Get('update.cl_update_outdate_set') == 'on'), + 'essential': False + }, + # сообщение удачного завершения при обновлении репозиториев + {'name': 'success_syncrep', + 'message': __("Synchronization finished"), + 'depend': (Tasks.success() & Tasks.has_any("sync_reps", + "sync_other_reps", + "emerge_metadata", + "eix_update")), + } + ] + }, + ] + +class UpdateConditions(object): + @staticmethod + def was_installed(pkg, task_name): + def func(): + task = EmergeLog(EmergeLogNamedTask(task_name)) + return bool(PackageList(task.list)[pkg]) + return func + + @staticmethod + def need_depclean(pkg, task_name): + def func(Get): + task = EmergeLog(EmergeLogNamedTask(task_name)) + return (bool(PackageList(task.list)[pkg]) + or Get('cl_update_force_depclean_set') == 'on' + or Get('cl_update_outdated_kernel_set') == 'on') + return func + + @staticmethod + def force_preserved(Get): + pfile = "/var/lib/portage/preserved_libs_registry" + content = readFile(pfile).strip() + if not content or content[1:-1].strip() == '': + return False + else: + return True + +class ClUpdateAction(Action): + """ + Действие обновление конфигурационных файлов + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError, UpdateError, + TemplatesError, BinhostError, + GitError, EmergeError) + + successMessage = None + failedMessage = None + interruptMessage = __("Update manually interrupted") + + emerge_tasks = [ + {'name': 'save_bdeps_val', + 'method': 'Update.save_with_bdeps()', + 'essential': False + }, + {'name': 'update_fastlogin_domain', + 'method': "Update.update_fastlogin_domain_path()" + }, + {'name': 'drop_portage_hash_on_sync', + 'method': 'Update.drop_portage_state_hash()', + 'condition': lambda Get: Get('cl_update_outdate_set') == 'on' + }, + {'name': 'premerge_group', + 'group': __("Checking for updates"), + 'tasks': [ + {'name': 'premerge', + 'message': __("Calculating dependencies"), + 'method': 'Update.premerge("-uDN","@world")', + 'condition': lambda Get: ( + Get('cl_update_sync_only_set') == 'off' and + Get('cl_update_pretend_set') == 'on') and \ + (Get('cl_update_world') != "update" or + Get('cl_update_outdate_set') == 'on' or + Get('cl_update_settings_changes_set') == 'on' or + Get('cl_update_binhost_recheck_set') == 'on' or + Get('cl_update_force_fix_set') == 'on' or + Get('update.cl_update_package_cache_set') == 'on') + }], + }, + {'name': 'update', + 'condition': lambda Get:Get('cl_update_pretend_set') == 'off' and \ + (Get('cl_update_world') != "update" or + Get('cl_update_outdate_set') == 'on' or + Get('cl_update_settings_changes_set') == 'on' or + Get('cl_update_binhost_recheck_set') == 'on' or + Get('cl_update_force_fix_set') == 'on' or + Get('cl_update_available_set') == 'on' or + Get('update.cl_update_package_cache_set') == 'on') + }, + {'name': 'update_other', + 'condition': lambda Get: ( Get('cl_update_pretend_set') == 'off' and + Get('cl_update_sync_only_set') == 'off') + }, + {'name': 'update:update_world', + 'group': __("Updating packages"), + 'tasks': [ + {'name': 'update_world', + 'message': __("Calculating dependencies"), + 'method': 'Update.emerge_ask(cl_update_pretend_set,' + '"-uDN","@world")', + } + ], + 'condition': lambda Get: Get('cl_update_sync_only_set') == 'off' + }, + {'name': 'update_other:update_perl', + 'group': __("Updating Perl modules"), + 'tasks': [ + {'name': 'update_other:perl_cleaner', + 'message': __('Find & rebuild packages and Perl header files ' + 'broken due to a perl upgrade'), + 'method': 'Update.emergelike("perl-cleaner", "all")', + 'condition': UpdateConditions.was_installed( + 'dev-lang/perl$', EmergeMark.PerlCleaner), + 'decoration': 'Update.update_task("%s")' % EmergeMark.PerlCleaner + }, + ] + }, + {'name': 'update_other:depclean', + 'group': __("Cleaning the system from needless packages"), + 'tasks': [ + {'name': 'update_other:update_depclean', + 'message': __("Calculating dependencies"), + 'method': 'Update.depclean()', + 'condition': UpdateConditions.need_depclean( + '.*', EmergeMark.Depclean), + 'decoration': 'Update.update_task("%s")' % EmergeMark.Depclean + }, + ] + }, + {'name': 'update_other:update_modules', + 'group': __("Rebuilding dependent modules"), + 'tasks': [ + {'name': 'update_other:module_rebuild', + 'message': __('Updating Kernel modules'), + 'method': 'Update.emerge("","@module-rebuild")', + 'condition': UpdateConditions.was_installed( + 'sys-kernel/.*source', EmergeMark.KernelModules), + 'decoration': 'Update.update_task("%s")' % + EmergeMark.KernelModules + }, + {'name': 'update_other:x11_module_rebuild', + 'message': __('Updating X.Org server modules'), + 'method': 'Update.emerge("","@x11-module-rebuild")', + 'condition': UpdateConditions.was_installed( + 'x11-base/xorg-server', EmergeMark.XorgModules), + 'decoration': 'Update.update_task("%s")' % + EmergeMark.XorgModules + }, + {'name': 'update_other:preserved_rebuild', + 'message': __('Updating preserved libraries'), + 'method': 'Update.emerge("","@preserved-rebuild")', + 'condition': lambda Get: (UpdateConditions.was_installed( + '.*', EmergeMark.PreservedLibs)() or + UpdateConditions.force_preserved(Get)), + 'decoration': 'Update.update_task("%s")' % + EmergeMark.PreservedLibs + }, + {'name': 'update_other:revdev_rebuild', + 'message': __('Checking reverse dependencies'), + 'method': 'Update.revdep_rebuild("revdep-rebuild")', + 'condition': lambda Get: (Get( + 'cl_update_revdep_rebuild_set') == 'on' and + UpdateConditions.was_installed( + '.*', EmergeMark.RevdepRebuild)()), + 'decoration': 'Update.update_task("%s")' % + EmergeMark.RevdepRebuild + }, + {'name': 'update_other:dispatch_conf_end', + 'message': __("Updating configuration files"), + 'method': 'Update.dispatchConf()', + 'condition': lambda Get: (Get('cl_dispatch_conf') != 'skip' and + Get('cl_update_pretend_set') == 'off') + }, + ] + }, + {'name': 'set_upto_date_cache', + 'method': 'Update.setUpToDateCache()', + 'condition': lambda Get: Get('cl_update_sync_only_set') == 'off' and \ + Get('cl_update_pretend_set') == 'off' + } + ] + + # список задач для действия + tasks = [ + {'name': 'check_schedule', + 'method': 'Update.checkSchedule(cl_update_autocheck_interval,' + 'cl_update_autocheck_set)', + 'condition': lambda Get: ( + Get('cl_update_autocheck_schedule_set') == 'on'), + }, + {'name': 'check_run', + 'method': 'Update.checkRun(cl_update_wait_another_set)' + }, + ] + get_synchronization_tasks("Update") + [ + {'name': 'system_configuration', + 'group': __("System configuration"), + 'tasks': [ + {'name': 'binhost_changed', + 'method': 'Update.message_binhost_changed()' + }, + {'name': 'revision', + 'message': __("Fixing the settings"), + 'method': 'Update.applyTemplates(install.cl_source,' + 'cl_template_clt_set,True,None,False)', + 'condition': lambda Get, GetBool: (Get('cl_templates_locate') and + (Get('cl_update_world') != "update" or + GetBool('cl_update_outdate_set') or + GetBool('cl_update_binhost_recheck_set') or + GetBool('cl_update_force_fix_set') or + GetBool('update.cl_update_package_cache_set'))) + }, + {'name': 'dispatch_conf', + 'message': __("Updating configuration files"), + 'method': 'Update.dispatchConf()', + 'condition': lambda Get, GetBool: (Get('cl_dispatch_conf') != 'skip' and + Get('cl_update_pretend_set') == 'off' and + (GetBool('cl_update_outdate_set') or + GetBool('cl_update_binhost_recheck_set') or + GetBool('cl_update_force_fix_set') or + GetBool('update.cl_update_package_cache_set'))) + }, + ] + } + ] + emerge_tasks + [ + {'name': 'failed', + 'error': __("Update failed"), + 'depend': (Tasks.failed() & Tasks.hasnot("interrupt") & + (Tasks.hasnot("check_schedule") | + Tasks.success_all("check_schedule")))}, + {'name': 'failed', + 'depend': Tasks.failed_all("check_schedule") + }, + # сообщение удачного завершения при обновлении ревизии + {'name': 'update:save_portage_hash', + 'method': 'Update.save_portage_state_hash()', + 'condition': lambda Get: Get('cl_update_sync_only_set') == 'off' + }, + {'name': 'drop_portage_hash', + 'method': 'Update.drop_portage_state_hash()', + 'depend': Tasks.failed() + }, + # сообщение удачного завершения при обновлении ревизии + {'name': 'success_rev', + 'message': __("System update finished!"), + 'condition': lambda Get: (Get('cl_update_rev_set') == 'on' and + Get('cl_update_pretend_set') == 'off') + }, + # сообщение удачного завершения при пересоздании world + {'name': 'success_world', + 'message': __("World rebuild finished!"), + 'condition': lambda Get: Get('cl_rebuild_world_set') == 'on' + }, + ] diff --git a/libs_crutch/update/utils/cl_update_profile.py b/libs_crutch/update/utils/cl_update_profile.py new file mode 100644 index 0000000..6a9ae9f --- /dev/null +++ b/libs_crutch/update/utils/cl_update_profile.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- + +# Copyright 2010-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from calculate.core.server.func import Action, Tasks +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate +from calculate.lib.cl_template import TemplatesError +from calculate.lib.utils.binhosts import BinhostError +from calculate.lib.utils.files import FilesError +from calculate.update.update import UpdateError +from calculate.lib.utils.git import GitError + +_ = lambda x: x +setLocalTranslate('cl_update3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class ClUpdateProfileAction(Action): + """ + Действие обновление конфигурационных файлов + """ + # ошибки, которые отображаются без подробностей + native_error = (FilesError, + TemplatesError, BinhostError, + UpdateError, GitError) + + successMessage = __("The profile was successfully updated") + failedMessage = __("Failed to update the profile") + interruptMessage = __("Profile update manually interrupted") + + # список задач для действия + tasks = [ + {'name': 'migrate_repository', + 'method': 'Update.migrateCacheRepository(' + 'cl_update_profile_url,cl_update_profile_branch,' + 'cl_update_profile_storage)', + 'message': __("Repository transfer"), + 'condition': lambda Get: not ( + Get('cl_update_profile_storage').is_local( + Get('cl_update_profile_url'), + Get('cl_update_profile_branch'))) + }, + {'name': 'reconfigure_vars1', + 'method': 'Update.invalidateVariables("cl_update_profile_storage")', + 'depend': Tasks.has('migrate_repository') + }, + {'name': 'check_datavars', + 'error': _("Profile not found in master"), + 'condition': lambda Get: not Get('update.cl_update_profile_datavars') + }, + {'name': 'drop_binhosts', + 'method': 'Update.drop_binhosts(update.cl_update_profile_datavars)' + }, + {'name': 'reconfigure_vars', + 'method': 'Update.reconfigureProfileVars(cl_update_profile_datavars,' + 'cl_chroot_path)' + }, + {'name': 'reps_synchronization', + 'group': __("Repositories synchronization"), + 'tasks': [ + {'name': 'sync_reps', + 'foreach': 'cl_update_profile_sync_rep', + 'message': __("Syncing the {eachvar:capitalize} repository"), + 'method': 'Update.syncRepositories(eachvar)', + }, + {'name': 'sync_reps:regen_cache', + 'foreach': 'cl_update_sync_overlay_rep', + 'message': __("Updating the {eachvar:capitalize} repository cache"), + 'essential': False, + 'method': 'Update.regenCache(eachvar)', + 'condition': ( + lambda Get: (Get('cl_update_outdate_set') == 'on' and + Get('cl_update_metadata_force') != 'skip' or + Get('cl_update_metadata_force') == 'force')) + }, + {'name': 'emerge_metadata', + 'message': __("Metadata transfer"), + 'method': 'Update.emergeMetadata()', + 'condition': ( + lambda Get: (Get('cl_update_outdate_set') == 'on' and + Get('cl_update_metadata_force') != 'skip' or + Get('cl_update_metadata_force') == 'force')) + }, + {'name': 'eix_update', + 'message': __("Updating the eix cache for " + "{cl_update_eix_repositories}"), + 'method': 'Update.eixUpdate(cl_repository_name)', + 'condition': ( + lambda Get: (Get('cl_update_outdate_set') == 'on' and + Get('cl_update_eixupdate_force') != 'skip' or + Get('cl_update_eixupdate_force') == 'force')) + }, + # сообщение удачного завершения при обновлении репозиториев + {'name': 'success_syncrep', + 'message': __("Synchronization finished"), + 'depend': (Tasks.success() & Tasks.has_any("sync_reps", + "sync_other_reps", + "emerge_metadata", + "eix_update")), + } + ] + }, + {'name': 'reps_synchronization', + 'group': __("Setting up the profile"), + 'tasks': [ + {'name': 'set_profile', + 'message': __("Switching to profile {cl_update_profile_system}"), + 'method': 'Update.setProfile(cl_update_profile_system)' + }, + {'name': 'rename_custom', + 'method': 'Update.rename_custom_files()', + }, + {'name': 'revision', + 'message': __("Fixing the settings"), + 'method': 'Update.applyProfileTemplates(cl_template_clt_set,' + 'True,False,"update_profile")', + 'condition': lambda Get: Get('cl_templates_locate') + }, + {'name': 'reconfigure', + 'message': __("The system is being configured"), + 'method': 'Update.applyProfileTemplates(cl_template_clt_set,' + 'True,False,"merge")', + 'condition': lambda Get: ( + Get('cl_update_templates_locate') and + Get('cl_update_skip_setup_set') == 'off') + }, + {'name': 'dispatch_conf', + 'message': __("Updating configuration files"), + 'method': 'Update.dispatchConf()', + 'condition': lambda Get: Get('cl_dispatch_conf') != 'skip' + }, + ] + } + ] diff --git a/libs_crutch/update/variables/__init__.py b/libs_crutch/update/variables/__init__.py index 0581c8b..e074ab8 100644 --- a/libs_crutch/update/variables/__init__.py +++ b/libs_crutch/update/variables/__init__.py @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import action -import update +from . import action +from . import update section = "update" diff --git a/libs_crutch/update/variables/update.py b/libs_crutch/update/variables/update.py index 2f7ace8..41a3559 100644 --- a/libs_crutch/update/variables/update.py +++ b/libs_crutch/update/variables/update.py @@ -42,7 +42,7 @@ from calculate.lib.variables import env from calculate.lib.variables import system as lib_system from calculate.update.update_info import UpdateInfo from itertools import chain -from urlparse import urlparse +from urllib.parse import urlparse import io import time diff --git a/libs_crutch/update/wsdl_update.py b/libs_crutch/update/wsdl_update.py new file mode 100644 index 0000000..e39dfa7 --- /dev/null +++ b/libs_crutch/update/wsdl_update.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- + +# Copyright 2010-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from calculate.lib.datavars import VariableError,DataVarsError + +from calculate.core.server.func import WsdlBase +from calculate.install.install import InstallError +from .update import Update, UpdateError +from calculate.lib.utils.git import GitError +from utils.cl_update import ClUpdateAction +from utils.cl_update_profile import ClUpdateProfileAction +from utils.cl_setup_update import ClSetupUpdateAction +from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate, _ + +setLocalTranslate('cl_update3', sys.modules[__name__]) +__ = getLazyLocalTranslate(_) + + +class Wsdl(WsdlBase): + methods = [ + # + # Обновить текущую конфигурацию системы (world,ревизия) + # + { + # идентификатор метода + 'method_name': "update", + # категория метода + 'category': __('Update '), + # заголовок метода + 'title': __("Update the System"), + # иконка для графической консоли + 'image': 'calculate-update', + # метод присутствует в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-update', + # права для запуска метода + 'rights': ['update'], + # объект содержащий модули для действия + 'logic': {'Update': Update}, + # описание действия + 'action': ClUpdateAction, + # объект переменных + 'datavars': "update", + 'native_error': (VariableError, DataVarsError, + InstallError, UpdateError, GitError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'sync'}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Update the system"), + normal=( + 'cl_update_binhost_stable_opt_set', + 'cl_update_binhost_recheck_set', + 'cl_update_with_bdeps_opt_set' + ), + expert=( + 'cl_update_sync_only_set', + 'cl_update_other_set', + 'cl_update_pretend_set', + 'cl_update_sync_rep', + 'cl_update_emergelist_set', + 'cl_update_check_rep_set', + 'cl_update_force_fix_set', + 'cl_update_world', + 'cl_update_gpg_force', + 'cl_update_egencache_force', + 'cl_update_eixupdate_force', + 'cl_update_revdep_rebuild_set', + 'cl_update_wait_another_set', + 'cl_update_autocheck_schedule_set', + 'cl_update_onedepth_set', + 'cl_update_cleanpkg_set', + 'cl_update_branch_data', + 'cl_templates_locate', + 'cl_verbose_set', 'cl_dispatch_conf'), + next_label=_("Run"))]}, + # + # Сменить профиль + # + { + # идентификатор метода + 'method_name': "update_profile", + # категория метода + 'category': __('Update '), + # заголовок метода + 'title': __("Change the Profile"), + # иконка для графической консоли + 'image': 'calculate-update-profile,' + 'notification-display-brightness-full,gtk-dialog-info,' + 'help-hint', + # метод присутствует в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-update-profile', + # права для запуска метода + 'rights': ['change_profile'], + # объект содержащий модули для действия + 'logic': {'Update': Update}, + # описание действия + 'action': ClUpdateProfileAction, + # объект переменных + 'datavars': "update", + 'native_error': (VariableError, DataVarsError, + InstallError, UpdateError, GitError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'update_profile'}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Repository"), + brief=('cl_update_profile_repo_name',), + hide=('cl_update_profile_url', + 'cl_update_profile_sync_set'), + normal=('cl_update_profile_url',), + expert=('cl_update_profile_sync_set',)), + lambda group: group(_("Profile"), + normal=('cl_update_profile_system', + 'cl_update_world'), + expert=('cl_update_skip_setup_set', + 'cl_update_templates_locate', + 'cl_verbose_set', + 'cl_dispatch_conf'), + hide=('cl_update_templates_locate', + 'cl_verbose_set', + 'cl_dispatch_conf'), + brief=('cl_update_profile_system', + 'cl_update_profile_linux_fullname', + 'cl_update_profile_depend_data') + )], + 'brief': {'next': __("Run"), + 'name': __("Set the profile")}}, + # + # Настроить автопроверку обновлений + # + { + # идентификатор метода + 'method_name': "setup_update", + # категория метода + 'category': __('Configuration'), + # заголовок метода + 'title': __("Update Check"), + # иконка для графической консоли + 'image': 'calculate-setup-update,software-properties,preferences-desktop', + # метод присутствует в графической консоли + 'gui': True, + # консольная команда + 'command': 'cl-setup-update', + # права для запуска метода + 'rights': ['setup_update'], + # объект содержащий модули для действия + 'logic': {'Update': Update}, + # описание действия + 'action': ClSetupUpdateAction, + # объект переменных + 'datavars': "update", + 'native_error': (VariableError, DataVarsError, + InstallError, UpdateError, GitError), + # значения по умолчанию для переменных этого метода + 'setvars': {'cl_action!': 'merge'}, + # описание груп (список лямбда функций) + 'groups': [ + lambda group: group(_("Updates autocheck settings"), + normal=('cl_update_autocheck_set', + 'cl_update_autocheck_interval', + 'cl_update_cleanpkg_set', + 'cl_update_other_set'), + next_label=_("Save"))]}, + ] diff --git a/pym/consolegui/application/more.py b/pym/consolegui/application/more.py index f243e12..c08cd9b 100644 --- a/pym/consolegui/application/more.py +++ b/pym/consolegui/application/more.py @@ -24,7 +24,8 @@ import sys from calculate.consolegui import qt # from calculate.core.client.function import create_obj -from .utils import create_obj +# from .utils import create_obj +from . import utils from calculate.lib.utils.files import readLinesFile, listDirectory, readFile, \ getProgPath, process, STDOUT from calculate.lib.utils.colortext.palette import ( @@ -2500,14 +2501,12 @@ def icon_visible (mainwgt, name, action): if hasattr (mainwgt.topmenu, name): getattr (mainwgt.topmenu, name).setVisible(action) -# from calculate.core.client.function import get_ip_mac_type -from .utils import get_ip_mac_type from calculate.lib.utils import ip as ip_mod def get_ip_mac(): for Interfaces in ip_mod.getInterfaces(): try: - ip, mac, client_type = get_ip_mac_type('gui') + ip, mac, client_type = utils.get_ip_mac_type('gui') return (ip, mac) except: pass @@ -2733,7 +2732,7 @@ class ImageLabel(qt.QLabel): self.setFixedHeight(sizes[1]) def get_view_params(client, method, step = None, expert = None, brief = None): - view_params = create_obj(client, method) + view_params = utils.create_obj(client, method) view_params.step = step view_params.expert = expert view_params.brief = brief diff --git a/pym/consolegui/application/user_update.py b/pym/consolegui/application/user_update.py new file mode 100644 index 0000000..7168961 --- /dev/null +++ b/pym/consolegui/application/user_update.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- + +# Copyright 2012-2016 Mir Calculate. http://www.calculate-linux.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import sys +from os import path +import calculate.contrib +from suds.transport import TransportError +from suds import WebFault +from .client_class import Client_suds +import urllib.request as urllib2 + +from calculate.core.datavars import DataVarsCore +from .client_class import HTTPSClientCertTransport +from calculate.lib.utils.dbus_tools import get_dbus_hostname +from functools import partial +from M2Crypto import X509 +from .utils import clear + +_ = lambda x: x +from calculate.lib.cl_lang import setLocalTranslate +setLocalTranslate('cl_core3', sys.modules[__name__]) + +def get_cert_groups(cert_file): + """ + Получить строку с группами сертификата + :param cert_file: файл сертификата + """ + try: + cert = X509.load_cert(cert_file) + val = cert.get_ext('nsComment').get_value() + if val.startswith('group:'): + return val[6:] + return "" + except (IOError, LookupError): + return "" + + +def get_certifactions_for_host(host, use_dbus=False): + """ + Получить список сертификатов подходящих для host + :param host: + :return: список полных путей файлов сертификатов + """ + log = logging.getLogger("certification") + cl_vars_core = DataVarsCore() + cl_vars_core.importCore() + cl_vars_core.flIniFile() + + home_path = cl_vars_core.Get('ur_home_path') + + port = cl_vars_core.GetInteger('core.cl_core_port') + path_to_cert = cl_vars_core.Get('core.cl_client_cert_dir') + path_to_cert = path_to_cert.replace("~", home_path) + + url = "https://%s:%d/?wsdl" % (host, port) + + clear() + if not use_dbus: + try: + client = Client_suds(url, transport=HTTPSClientCertTransport( + None, None, path_to_cert, None)) + client.wsdl.services[0].setlocation(url) + server_host_name = client.service.get_server_host_name() + del client + except (urllib2.URLError, TransportError) as e: + log.debug(_('Failed to connect', ) + _(': ') + str(e)) + return [] + except KeyboardInterrupt: + log.debug(_("Manually interrupted")) + return [] + else: + server_host_name = get_dbus_hostname() or "localhost" + try: + import glob + + all_cert_list = glob.glob(os.path.join(path_to_cert, '*.crt')) + fit_cert_list = [] + for client_cert_path in all_cert_list: + client_cert = client_cert_path.replace(path_to_cert, '') + client_cert_name = client_cert.replace('.crt', '') + if server_host_name.endswith(client_cert_name): + fit_cert_list.append(client_cert_name) + fit_cert_list.sort(key=len) + mkfullpath = partial(path.join, path_to_cert) + return map(mkfullpath, map(lambda x: "%s.crt" % x, fit_cert_list)) + # ---------------------------------------------------- + except WebFault as f: + log.debug(_("Exception: %s") % f) + log.debug(f.fault) + except TransportError as te: + log.debug(_("Exception: %s") % te) + except KeyboardInterrupt: + log.debug(_("Manually interrupted")) + except Exception as e: + log.debug(str(e)) + return [] + + +def user_can_run_update(use_dbus): + """ + Текущий пользователь может запускать обновление системы + """ + for cert in get_certifactions_for_host("localhost", use_dbus=use_dbus): + groups = get_cert_groups(cert) + if any(x in groups for x in ("all", "update")): + return True + return False + diff --git a/pym/consolegui/application/utils.py b/pym/consolegui/application/utils.py index 1d5f18e..3309fb1 100644 --- a/pym/consolegui/application/utils.py +++ b/pym/consolegui/application/utils.py @@ -14,15 +14,17 @@ # See the License for the specific language governing permissions and # limitations under the License. + import os + import sys import calculate.contrib from suds import MethodNotFound + from calculate.lib.utils import ip as ip _ = lambda x: x from calculate.lib.cl_lang import setLocalTranslate - setLocalTranslate('cl_core3', sys.modules[__name__]) def getIpLocal(): @@ -146,3 +148,4 @@ class switch(object): return True else: return False +