[entropy.client] move ClientSystemSettingsPlugin to a separate module
This will break the circular dependency between loaders and client modules
This commit is contained in:
@@ -28,15 +28,16 @@
|
||||
../lib/entropy/client/__init__.py
|
||||
../lib/entropy/client/mirrors.py
|
||||
../lib/entropy/client/misc.py
|
||||
../lib/entropy/client/interfaces/sets.py
|
||||
../lib/entropy/client/interfaces/cache.py
|
||||
../lib/entropy/client/interfaces/__init__.py
|
||||
../lib/entropy/client/interfaces/methods.py
|
||||
../lib/entropy/client/interfaces/noticeboard.py
|
||||
../lib/entropy/client/interfaces/settings.py
|
||||
../lib/entropy/client/interfaces/loaders.py
|
||||
../lib/entropy/client/interfaces/db.py
|
||||
../lib/entropy/client/interfaces/client.py
|
||||
../lib/entropy/client/interfaces/repository.py
|
||||
../lib/entropy/client/interfaces/sets.py
|
||||
../lib/entropy/client/interfaces/dep.py
|
||||
../lib/entropy/client/interfaces/qa.py
|
||||
../lib/entropy/client/interfaces/package/_content.py
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
"""
|
||||
import os
|
||||
import codecs
|
||||
import threading
|
||||
|
||||
from entropy.core import Singleton
|
||||
@@ -24,12 +23,11 @@ from entropy.client.interfaces.dep import CalculatorsMixin
|
||||
from entropy.client.interfaces.methods import RepositoryMixin, MiscMixin, \
|
||||
MatchMixin
|
||||
from entropy.client.interfaces.noticeboard import NoticeBoardMixin
|
||||
from entropy.client.interfaces.settings import ClientSystemSettingsPlugin
|
||||
from entropy.const import etpConst, const_debug_write, \
|
||||
const_convert_to_unicode, const_file_readable
|
||||
const_convert_to_unicode
|
||||
from entropy.core.settings.base import SystemSettings
|
||||
from entropy.core.settings.plugins.skel import SystemSettingsPlugin
|
||||
from entropy.misc import LogFile
|
||||
from entropy.exceptions import SystemDatabaseError, RepositoryError
|
||||
from entropy.cache import EntropyCacher
|
||||
from entropy.i18n import _
|
||||
|
||||
@@ -37,675 +35,6 @@ import entropy.dump
|
||||
import entropy.dep
|
||||
import entropy.tools
|
||||
|
||||
class ClientSystemSettingsPlugin(SystemSettingsPlugin):
|
||||
|
||||
ID = etpConst['system_settings_plugins_ids']['client_plugin']
|
||||
|
||||
def __init__(self, helper_interface):
|
||||
SystemSettingsPlugin.__init__(
|
||||
self, ClientSystemSettingsPlugin.ID, helper_interface)
|
||||
self.__repos_files = {}
|
||||
self.__repos_mtime = {}
|
||||
self._mtime_cache = {}
|
||||
# Package repositories must be able to live across
|
||||
# SystemSettings.clear() calls, because they are very
|
||||
# special and 3rd-party (but even Sulfur) tools expect to always
|
||||
# have them there after having called Client.add_package_repository
|
||||
self.__package_repositories = []
|
||||
self.__package_repositories_meta = {}
|
||||
|
||||
@staticmethod
|
||||
def client_conf_path():
|
||||
"""
|
||||
Return current client.conf path, this takes into account the current
|
||||
configuration files directory path (which is affected by "root" path
|
||||
changes [default: /])
|
||||
"""
|
||||
# path to /etc/entropy/server.conf (usually, depends on systemroot)
|
||||
return os.path.join(etpConst['confdir'], "client.conf")
|
||||
|
||||
def _add_package_repository(self, repository_id, repository_metadata):
|
||||
"""
|
||||
Internal method, used by Entropy Client. Add a package repository
|
||||
to this SystemSettings plugins to make it able to live across
|
||||
SystemSettings.clear() calls.
|
||||
|
||||
@param repository_id: repository identifier
|
||||
@type repository_id: string
|
||||
@param repository_metadata: a dict object that will be merged
|
||||
into SystemSettings['repositories'] when clear() will be called
|
||||
@type repository_metadata: dict
|
||||
@raise KeyError: if repository_id is already stored.
|
||||
"""
|
||||
if repository_id in self.__package_repositories:
|
||||
raise KeyError("%s already added" % (repository_id,))
|
||||
self.__package_repositories.append(repository_id)
|
||||
self.__package_repositories_meta[repository_id] = repository_metadata
|
||||
|
||||
def _drop_package_repository(self, repository_id):
|
||||
"""
|
||||
Drop package repository previously added by calling
|
||||
_add_package_repository()
|
||||
|
||||
@param repository_id: repository identifier
|
||||
@type repository_id: string
|
||||
@raise KeyError: if repository_id is not available
|
||||
"""
|
||||
del self.__package_repositories_meta[repository_id]
|
||||
self.__package_repositories.remove(repository_id)
|
||||
|
||||
def __setup_repos_files(self, system_settings):
|
||||
"""
|
||||
This function collects available repositories configuration files
|
||||
by filling internal dict() __repos_files and __repos_mtime.
|
||||
|
||||
@param system_settings: SystemSettings instance
|
||||
@type system_settings: instance of SystemSettings
|
||||
@return: None
|
||||
@rtype: None
|
||||
"""
|
||||
|
||||
self.__repos_mtime = {
|
||||
'repos_license_whitelist': {},
|
||||
'repos_mask': {},
|
||||
'repos_system_mask': {},
|
||||
'repos_critical_updates': {},
|
||||
'repos_keywords': {},
|
||||
}
|
||||
self.__repos_files = {
|
||||
'repos_license_whitelist': {},
|
||||
'repos_mask': {},
|
||||
'repos_system_mask': {},
|
||||
'repos_critical_updates': {},
|
||||
'repos_keywords': {},
|
||||
}
|
||||
|
||||
dmp_dir = etpConst['dumpstoragedir']
|
||||
avail_data = system_settings['repositories']['available']
|
||||
for repoid in system_settings['repositories']['order']:
|
||||
|
||||
repo_data = avail_data[repoid]
|
||||
if "__temporary__" in repo_data:
|
||||
continue
|
||||
|
||||
repos_mask_setting = {}
|
||||
repos_mask_mtime = {}
|
||||
repos_lic_wl_setting = {}
|
||||
repos_lic_wl_mtime = {}
|
||||
repos_sm_mask_setting = {}
|
||||
repos_sm_mask_mtime = {}
|
||||
confl_tagged = {}
|
||||
repos_critical_updates_setting = {}
|
||||
repos_critical_updates_mtime = {}
|
||||
repos_keywords_setting = {}
|
||||
repos_keywords_mtime = {}
|
||||
|
||||
maskpath = os.path.join(repo_data['dbpath'],
|
||||
etpConst['etpdatabasemaskfile'])
|
||||
wlpath = os.path.join(repo_data['dbpath'],
|
||||
etpConst['etpdatabaselicwhitelistfile'])
|
||||
sm_path = os.path.join(repo_data['dbpath'],
|
||||
etpConst['etpdatabasesytemmaskfile'])
|
||||
critical_path = os.path.join(repo_data['dbpath'],
|
||||
etpConst['etpdatabasecriticalfile'])
|
||||
keywords_path = os.path.join(repo_data['dbpath'],
|
||||
etpConst['etpdatabasekeywordsfile'])
|
||||
|
||||
if const_file_readable(maskpath):
|
||||
repos_mask_setting[repoid] = maskpath
|
||||
repos_mask_mtime[repoid] = dmp_dir + "/repo_" + \
|
||||
repoid + "_" + etpConst['etpdatabasemaskfile'] + ".mtime"
|
||||
|
||||
if const_file_readable(wlpath):
|
||||
repos_lic_wl_setting[repoid] = wlpath
|
||||
repos_lic_wl_mtime[repoid] = dmp_dir + "/repo_" + \
|
||||
repoid + "_" + etpConst['etpdatabaselicwhitelistfile'] + \
|
||||
".mtime"
|
||||
|
||||
if const_file_readable(sm_path):
|
||||
repos_sm_mask_setting[repoid] = sm_path
|
||||
repos_sm_mask_mtime[repoid] = dmp_dir + "/repo_" + \
|
||||
repoid + "_" + etpConst['etpdatabasesytemmaskfile'] + \
|
||||
".mtime"
|
||||
|
||||
if const_file_readable(critical_path):
|
||||
repos_critical_updates_setting[repoid] = critical_path
|
||||
repos_critical_updates_mtime[repoid] = dmp_dir + "/repo_" + \
|
||||
repoid + "_" + etpConst['etpdatabasecriticalfile'] + \
|
||||
".mtime"
|
||||
|
||||
if const_file_readable(keywords_path):
|
||||
repos_keywords_setting[repoid] = keywords_path
|
||||
repos_keywords_mtime[repoid] = dmp_dir + "/repo_" + \
|
||||
repoid + "_" + etpConst['etpdatabasekeywordsfile'] + \
|
||||
".mtime"
|
||||
|
||||
self.__repos_files['repos_mask'].update(repos_mask_setting)
|
||||
self.__repos_mtime['repos_mask'].update(repos_mask_mtime)
|
||||
|
||||
self.__repos_files['repos_license_whitelist'].update(
|
||||
repos_lic_wl_setting)
|
||||
self.__repos_mtime['repos_license_whitelist'].update(
|
||||
repos_lic_wl_mtime)
|
||||
|
||||
self.__repos_files['repos_system_mask'].update(
|
||||
repos_sm_mask_setting)
|
||||
self.__repos_mtime['repos_system_mask'].update(
|
||||
repos_sm_mask_mtime)
|
||||
|
||||
self.__repos_files['repos_critical_updates'].update(
|
||||
repos_critical_updates_setting)
|
||||
self.__repos_mtime['repos_critical_updates'].update(
|
||||
repos_critical_updates_mtime)
|
||||
|
||||
self.__repos_files['repos_keywords'].update(
|
||||
repos_keywords_setting)
|
||||
self.__repos_mtime['repos_keywords'].update(
|
||||
repos_keywords_mtime)
|
||||
|
||||
def __generic_parser(self, filepath):
|
||||
"""
|
||||
Internal method. This is the generic file parser here.
|
||||
|
||||
@param filepath: valid path
|
||||
@type filepath: string
|
||||
@return: raw text extracted from file
|
||||
@rtype: list
|
||||
"""
|
||||
root = etpConst['systemroot']
|
||||
try:
|
||||
mtime = os.path.getmtime(filepath)
|
||||
except (OSError, IOError):
|
||||
mtime = 0.0
|
||||
|
||||
cache_key = (root, filepath)
|
||||
cache_obj = self._mtime_cache.get(cache_key)
|
||||
if cache_obj is not None:
|
||||
if cache_obj['mtime'] == mtime:
|
||||
return cache_obj['data']
|
||||
|
||||
cache_obj = {'mtime': mtime,}
|
||||
|
||||
enc = etpConst['conf_encoding']
|
||||
data = entropy.tools.generic_file_content_parser(filepath,
|
||||
comment_tag = "##", encoding = enc)
|
||||
if SystemSettings.DISK_DATA_CACHE:
|
||||
cache_obj['data'] = data
|
||||
self._mtime_cache[cache_key] = cache_obj
|
||||
return data
|
||||
|
||||
def __run_post_branch_migration_hooks(self, sys_settings_instance):
|
||||
|
||||
# only root can do this
|
||||
if os.getuid() != 0:
|
||||
return
|
||||
|
||||
old_branch_path = etpConst['etp_previous_branch_file']
|
||||
in_branch_upgrade_path = etpConst['etp_in_branch_upgrade_file']
|
||||
current_branch = sys_settings_instance['repositories']['branch']
|
||||
enc = etpConst['conf_encoding']
|
||||
|
||||
def write_current_branch(branch):
|
||||
with codecs.open(old_branch_path, "w", encoding=enc) as old_brf:
|
||||
old_brf.write(branch)
|
||||
|
||||
def write_in_branch_upgrade(branch):
|
||||
with codecs.open(in_branch_upgrade_path, "w", encoding=enc) as brf:
|
||||
brf.write("in branch upgrade: %s" % (branch,))
|
||||
|
||||
if not os.path.isfile(old_branch_path):
|
||||
write_current_branch(current_branch)
|
||||
return
|
||||
|
||||
with codecs.open(old_branch_path, "r", encoding=enc) as old_f:
|
||||
old_branch = old_f.readline().strip()
|
||||
|
||||
if old_branch == current_branch: # all fine, no need to run
|
||||
return
|
||||
|
||||
repos, err = self._helper._run_repositories_post_branch_switch_hooks(
|
||||
old_branch, current_branch)
|
||||
if not err:
|
||||
write_in_branch_upgrade(current_branch)
|
||||
write_current_branch(current_branch)
|
||||
|
||||
def __run_post_branch_upgrade_hooks(self, sys_settings_instance):
|
||||
|
||||
# only root can do this
|
||||
if os.getuid() != 0:
|
||||
return
|
||||
|
||||
repos, errors = self._helper._run_repository_post_branch_upgrade_hooks(
|
||||
pretend = True)
|
||||
if not repos:
|
||||
# no scripts to run
|
||||
return
|
||||
|
||||
# look for updates
|
||||
# critical_updates = False is needed to avoid
|
||||
# issues with metadata not being available
|
||||
try:
|
||||
outcome = self._helper.calculate_updates(critical_updates = False)
|
||||
update, remove = outcome['update'], outcome['remove']
|
||||
fine, spm_fine = outcome['fine'], outcome['spm_fine']
|
||||
except (ValueError, SystemDatabaseError, RepositoryError,):
|
||||
# RepositoryError is triggered when branch is hopped
|
||||
# SystemDatabaseError is triggered when no client db is avail
|
||||
# ValueError is triggered when repos are broken
|
||||
update = 1 # foo!
|
||||
|
||||
def delete_in_branch_upgrade():
|
||||
br_path = etpConst['etp_in_branch_upgrade_file']
|
||||
try:
|
||||
os.remove(br_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# actually execute this only if
|
||||
# there are no updates left
|
||||
if not update:
|
||||
self._helper._run_repository_post_branch_upgrade_hooks()
|
||||
delete_in_branch_upgrade()
|
||||
|
||||
def system_mask_parser(self, system_settings_instance):
|
||||
|
||||
parser_data = {}
|
||||
# match installed packages of system_mask
|
||||
mask_installed = []
|
||||
mask_installed_keys = {}
|
||||
while (self._helper.installed_repository() != None):
|
||||
try:
|
||||
self._helper.installed_repository().validate()
|
||||
except SystemDatabaseError:
|
||||
break
|
||||
mc_cache = set()
|
||||
repos_mask_list = self.__repositories_system_mask(
|
||||
system_settings_instance)
|
||||
m_list = repos_mask_list + system_settings_instance['system_mask']
|
||||
for atom in m_list:
|
||||
m_ids, m_r = self._helper.installed_repository().atomMatch(atom,
|
||||
multiMatch = True)
|
||||
if m_r != 0:
|
||||
continue
|
||||
mykey = entropy.dep.dep_getkey(atom)
|
||||
obj = mask_installed_keys.setdefault(mykey, set())
|
||||
for m_id in m_ids:
|
||||
if m_id in mc_cache:
|
||||
continue
|
||||
mc_cache.add(m_id)
|
||||
mask_installed.append(m_id)
|
||||
obj.add(m_id)
|
||||
break
|
||||
|
||||
parser_data.update({
|
||||
'repos_installed': mask_installed,
|
||||
'repos_installed_keys': mask_installed_keys,
|
||||
})
|
||||
return parser_data
|
||||
|
||||
def masking_validation_parser(self, system_settings_instance):
|
||||
data = {
|
||||
'cache': {}, # package masking validation cache
|
||||
}
|
||||
return data
|
||||
|
||||
def __repositories_repos_keywords(self, repo_keywords_path):
|
||||
"""
|
||||
Parser returning system packages mask metadata read from
|
||||
packages.db.keywords file inside the repository directory.
|
||||
This file contains maintainer supplied per-repository extra
|
||||
package keywords.
|
||||
"""
|
||||
root = etpConst['systemroot']
|
||||
try:
|
||||
mtime = os.path.getmtime(repo_keywords_path)
|
||||
except (OSError, IOError):
|
||||
mtime = 0.0
|
||||
|
||||
cache_key = (root, repo_keywords_path)
|
||||
cache_obj = self._mtime_cache.get(cache_key)
|
||||
if cache_obj is not None:
|
||||
if cache_obj['mtime'] == mtime:
|
||||
return cache_obj['data']
|
||||
|
||||
cache_obj = {'mtime': mtime,}
|
||||
|
||||
data = {
|
||||
# universal keywords: keywords added repository-wide to all
|
||||
# the available packages (in repo).
|
||||
'universal': set(),
|
||||
# per-package keywording, keys are atoms/dep (first line argument)
|
||||
# values are provided keywords
|
||||
'packages': {},
|
||||
'packages_ids': None, # reserved for entropy.db package validation
|
||||
}
|
||||
|
||||
enc = etpConst['conf_encoding']
|
||||
entries = entropy.tools.generic_file_content_parser(
|
||||
repo_keywords_path, encoding = enc)
|
||||
|
||||
# iterate over config file data
|
||||
for entry in entries:
|
||||
entry = entry.split()
|
||||
if len(entry) == 1:
|
||||
# universal keyword
|
||||
item = entry[0]
|
||||
if item == "**":
|
||||
item = ''
|
||||
data['universal'].add(item)
|
||||
|
||||
elif len(entry) > 1:
|
||||
# per package keyword
|
||||
pkg = entry[0]
|
||||
keywords = entry[1:]
|
||||
obj = data['packages'].setdefault(pkg, set())
|
||||
obj.update(keywords)
|
||||
|
||||
if SystemSettings.DISK_DATA_CACHE:
|
||||
cache_obj['data'] = data
|
||||
self._mtime_cache[cache_key] = cache_obj
|
||||
return data
|
||||
|
||||
def __repositories_system_mask(self, sys_settings_instance):
|
||||
"""
|
||||
Parser returning system packages mask metadata read from
|
||||
packages.db.system_mask file inside the repository directory.
|
||||
This file contains packages that should be always kept
|
||||
installed, extending the already defined (in repository database)
|
||||
set of atoms.
|
||||
"""
|
||||
system_mask = []
|
||||
enc = etpConst['conf_encoding']
|
||||
for repoid in self.__repos_files['repos_system_mask']:
|
||||
filepath = self.__repos_files['repos_system_mask'][repoid]
|
||||
mtimepath = self.__repos_mtime['repos_system_mask'][repoid]
|
||||
sys_settings_instance.validate_entropy_cache(
|
||||
filepath, mtimepath, repoid = repoid)
|
||||
|
||||
entries = self.__generic_parser(filepath)
|
||||
system_mask += [x for x in entries if x not in system_mask]
|
||||
|
||||
return system_mask
|
||||
|
||||
def repositories_parser(self, sys_settings_instance):
|
||||
"""
|
||||
Parser that generates repository settings metadata.
|
||||
|
||||
@param sys_settings_instance: SystemSettings instance
|
||||
@type sys_settings_instance: instance of SystemSettings
|
||||
@return: parsed metadata
|
||||
@rtype: dict
|
||||
"""
|
||||
|
||||
# add back repository metadata to SystemSettings['repositories']
|
||||
avail_data = sys_settings_instance['repositories']['available']
|
||||
for repository_id in self.__package_repositories:
|
||||
if repository_id not in avail_data:
|
||||
repodata = self.__package_repositories_meta[repository_id]
|
||||
# if correct, this won't trigger a stack overflow
|
||||
# add_repository calling SystemSettings.clear() I mean
|
||||
added = self._helper.add_repository(repodata)
|
||||
if not added:
|
||||
raise ValueError("wtf? cannot add repository")
|
||||
|
||||
# fill repositories metadata dictionaries
|
||||
self.__setup_repos_files(sys_settings_instance)
|
||||
|
||||
data = {
|
||||
'license_whitelist': {},
|
||||
'mask': {},
|
||||
'system_mask': [],
|
||||
'critical_updates': {},
|
||||
'repos_keywords': {},
|
||||
}
|
||||
|
||||
# parse license whitelist
|
||||
# Parser returning licenses considered accepted by default
|
||||
# (= GPL compatibles) read from package.lic_whitelist.
|
||||
for repoid in self.__repos_files['repos_license_whitelist']:
|
||||
sys_settings_instance.validate_entropy_cache(
|
||||
self.__repos_files['repos_license_whitelist'][repoid],
|
||||
self.__repos_mtime['repos_license_whitelist'][repoid],
|
||||
repoid = repoid)
|
||||
|
||||
data['license_whitelist'][repoid] = self.__generic_parser(
|
||||
self.__repos_files['repos_license_whitelist'][repoid])
|
||||
|
||||
# package masking
|
||||
# Parser returning packages masked at repository level read from
|
||||
# packages.db.mask inside the repository database directory.
|
||||
for repoid in self.__repos_files['repos_mask']:
|
||||
sys_settings_instance.validate_entropy_cache(
|
||||
self.__repos_files['repos_mask'][repoid],
|
||||
self.__repos_mtime['repos_mask'][repoid], repoid = repoid)
|
||||
|
||||
data['mask'][repoid] = self.__generic_parser(
|
||||
self.__repos_files['repos_mask'][repoid])
|
||||
|
||||
# keywords masking
|
||||
# Parser returning packages masked at repository level read from
|
||||
# packages.db.keywords inside the repository database directory.
|
||||
for repoid in self.__repos_files['repos_keywords']:
|
||||
sys_settings_instance.validate_entropy_cache(
|
||||
self.__repos_files['repos_keywords'][repoid],
|
||||
self.__repos_mtime['repos_keywords'][repoid],
|
||||
repoid = repoid)
|
||||
|
||||
data['repos_keywords'][repoid] = \
|
||||
self.__repositories_repos_keywords(
|
||||
self.__repos_files['repos_keywords'][repoid])
|
||||
|
||||
# system masking
|
||||
data['system_mask'] = self.__repositories_system_mask(
|
||||
sys_settings_instance)
|
||||
|
||||
# critical updates
|
||||
# Parser returning critical packages list metadata read from
|
||||
# packages.db.critical file inside the repository directory.
|
||||
# This file contains packages that should be always updated
|
||||
# before anything else.
|
||||
for repoid in self.__repos_files['repos_critical_updates']:
|
||||
sys_settings_instance.validate_entropy_cache(
|
||||
self.__repos_files['repos_critical_updates'][repoid],
|
||||
self.__repos_mtime['repos_critical_updates'][repoid],
|
||||
repoid = repoid)
|
||||
|
||||
data['critical_updates'][repoid] = self.__generic_parser(
|
||||
self.__repos_files['repos_critical_updates'][repoid])
|
||||
|
||||
return data
|
||||
|
||||
def misc_parser(self, sys_settings_instance):
|
||||
|
||||
"""
|
||||
Parses Entropy client system configuration file.
|
||||
|
||||
@return dict data
|
||||
"""
|
||||
|
||||
data = {
|
||||
'filesbackup': etpConst['filesbackup'],
|
||||
'forcedupdates': etpConst['forcedupdates'],
|
||||
'packagehashes': etpConst['packagehashes'],
|
||||
'gpg': etpConst['client_gpg'],
|
||||
'ignore_spm_downgrades': False,
|
||||
'splitdebug': etpConst['splitdebug'],
|
||||
'splitdebug_dirs': etpConst['splitdebug_dirs'],
|
||||
'multifetch': 1,
|
||||
'collisionprotect': etpConst['collisionprotect'],
|
||||
'configprotect': set(),
|
||||
'configprotectmask': set(),
|
||||
'configprotectskip': set(),
|
||||
'autoprune_days': None, # disabled by default
|
||||
'edelta_support': False, # disabled by default
|
||||
}
|
||||
|
||||
cli_conf = ClientSystemSettingsPlugin.client_conf_path()
|
||||
root = etpConst['systemroot']
|
||||
try:
|
||||
mtime = os.path.getmtime(cli_conf)
|
||||
except (OSError, IOError):
|
||||
mtime = 0.0
|
||||
|
||||
cache_key = (root, cli_conf)
|
||||
cache_obj = self._mtime_cache.get(cache_key)
|
||||
if cache_obj is not None:
|
||||
if cache_obj['mtime'] == mtime:
|
||||
return cache_obj['data']
|
||||
|
||||
cache_obj = {'mtime': mtime,}
|
||||
|
||||
if not const_file_readable(cli_conf):
|
||||
if SystemSettings.DISK_DATA_CACHE:
|
||||
cache_obj['data'] = data
|
||||
self._mtime_cache[cache_key] = cache_obj
|
||||
return data
|
||||
|
||||
def _filesbackup(setting):
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if bool_setting is not None:
|
||||
data['filesbackup'] = bool_setting
|
||||
|
||||
def _forcedupdates(setting):
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if bool_setting is not None:
|
||||
data['forcedupdates'] = bool_setting
|
||||
|
||||
def _autoprune(setting):
|
||||
int_setting = entropy.tools.setting_to_int(setting, 0, 365)
|
||||
if int_setting is not None:
|
||||
data['autoprune_days'] = int_setting
|
||||
|
||||
def _packagesdelta(setting):
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if bool_setting is not None:
|
||||
data['edelta_support'] = bool_setting
|
||||
|
||||
def _packagehashes(setting):
|
||||
setting = setting.lower().split()
|
||||
hashes = set()
|
||||
for opt in setting:
|
||||
if opt in etpConst['packagehashes']:
|
||||
hashes.add(opt)
|
||||
if hashes:
|
||||
data['packagehashes'] = tuple(sorted(hashes))
|
||||
|
||||
def _multifetch(setting):
|
||||
int_setting = entropy.tools.setting_to_int(setting, None, None)
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if int_setting is not None:
|
||||
if int_setting not in range(2, 11):
|
||||
int_setting = 10
|
||||
data['multifetch'] = int_setting
|
||||
if bool_setting is not None:
|
||||
if bool_setting:
|
||||
data['multifetch'] = 3
|
||||
|
||||
def _gpg(setting):
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if bool_setting is not None:
|
||||
data['gpg'] = bool_setting
|
||||
|
||||
def _spm_downgrades(setting):
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if bool_setting is not None:
|
||||
data['ignore_spm_downgrades'] = bool_setting
|
||||
|
||||
def _splitdebug(setting):
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if bool_setting is not None:
|
||||
data['splitdebug'] = bool_setting
|
||||
|
||||
def _collisionprotect(setting):
|
||||
int_setting = entropy.tools.setting_to_int(setting, 0, 2)
|
||||
if int_setting is not None:
|
||||
data['collisionprotect'] = int_setting
|
||||
|
||||
def _configprotect(setting):
|
||||
for opt in setting.split():
|
||||
data['configprotect'].add(const_convert_to_unicode(opt))
|
||||
|
||||
def _configprotectmask(setting):
|
||||
for opt in setting.split():
|
||||
data['configprotectmask'].add(const_convert_to_unicode(opt))
|
||||
|
||||
def _configprotectskip(setting):
|
||||
for opt in setting.split():
|
||||
data['configprotectskip'].add(
|
||||
etpConst['systemroot'] + const_convert_to_unicode(opt))
|
||||
|
||||
settings_map = {
|
||||
# backward compatibility
|
||||
'filesbackup': _filesbackup,
|
||||
'files-backup': _filesbackup,
|
||||
# backward compatibility
|
||||
'forcedupdates': _forcedupdates,
|
||||
'forced-updates': _forcedupdates,
|
||||
'packages-autoprune-days': _autoprune,
|
||||
'packages-delta': _packagesdelta,
|
||||
# backward compatibility
|
||||
'packagehashes': _packagehashes,
|
||||
'package-hashes': _packagehashes,
|
||||
'multifetch': _multifetch,
|
||||
'gpg': _gpg,
|
||||
'ignore-spm-downgrades': _spm_downgrades,
|
||||
'splitdebug': _splitdebug,
|
||||
# backward compatibility
|
||||
'collisionprotect': _collisionprotect,
|
||||
'collision-protect': _collisionprotect,
|
||||
# backward compatibility
|
||||
'configprotect': _configprotect,
|
||||
'config-protect': _configprotect,
|
||||
# backward compatibility
|
||||
'configprotectmask': _configprotectmask,
|
||||
'config-protect-mask': _configprotectmask,
|
||||
# backward compatibility
|
||||
'configprotectskip': _configprotectskip,
|
||||
'config-protect-skip': _configprotectskip,
|
||||
}
|
||||
|
||||
enc = etpConst['conf_encoding']
|
||||
with codecs.open(cli_conf, "r", encoding=enc) as client_f:
|
||||
clientconf = [x.strip() for x in client_f.readlines() if \
|
||||
x.strip() and not x.strip().startswith("#")]
|
||||
for line in clientconf:
|
||||
|
||||
key, value = entropy.tools.extract_setting(line)
|
||||
if key is None:
|
||||
continue
|
||||
|
||||
func = settings_map.get(key)
|
||||
if func is None:
|
||||
continue
|
||||
func(value)
|
||||
|
||||
# completely disable GPG feature
|
||||
if not data['gpg'] and ("gpg" in data['packagehashes']):
|
||||
data['packagehashes'] = tuple((x for x in data['packagehashes'] \
|
||||
if x != "gpg"))
|
||||
|
||||
# support ETP_SPLITDEBUG
|
||||
split_debug = os.getenv("ETP_SPLITDEBUG")
|
||||
if split_debug is not None:
|
||||
_splitdebug(split_debug)
|
||||
|
||||
if SystemSettings.DISK_DATA_CACHE:
|
||||
cache_obj['data'] = data
|
||||
self._mtime_cache[cache_key] = cache_obj
|
||||
return data
|
||||
|
||||
def post_setup(self, system_settings_instance):
|
||||
"""
|
||||
Reimplemented from SystemSettingsPlugin.
|
||||
"""
|
||||
|
||||
if self._helper._can_run_sys_set_hooks:
|
||||
# run post-branch migration scripts if branch setting got changed
|
||||
self.__run_post_branch_migration_hooks(system_settings_instance)
|
||||
# run post-branch upgrade migration scripts if the function
|
||||
# above created migration files to handle
|
||||
self.__run_post_branch_upgrade_hooks(system_settings_instance)
|
||||
|
||||
|
||||
class Client(Singleton, TextInterface, LoadersMixin, CacheMixin,
|
||||
CalculatorsMixin, RepositoryMixin, MiscMixin,
|
||||
|
||||
@@ -2223,8 +2223,6 @@ class AvailablePackagesRepositoryUpdater(object):
|
||||
return EntropyRepositoryBase.REPOSITORY_UPDATED_OK
|
||||
|
||||
|
||||
_CL_PLUGIN_ID = etpConst['system_settings_plugins_ids']['client_plugin']
|
||||
|
||||
class MaskableRepository(EntropyRepositoryBase):
|
||||
"""
|
||||
Objects inheriting from this class support package masking.
|
||||
@@ -2235,6 +2233,26 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
"""
|
||||
_MASK_FILTER_CACHE_ID = EntropyCacher.CACHE_IDS['mask_filter']
|
||||
|
||||
_real_client_settings = None
|
||||
_real_client_settings_lock = threading.Lock()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MaskableRepository, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def _client_settings(self):
|
||||
"""
|
||||
Load the Entropy Client settings object.
|
||||
"""
|
||||
if self._real_client_settings is None:
|
||||
with self._real_client_settings_lock:
|
||||
if self._real_client_settings is None:
|
||||
from entropy.client.interfaces import Client
|
||||
|
||||
self._real_client_settings = Client().ClientSettings()
|
||||
|
||||
return self._real_client_settings
|
||||
|
||||
def _mask_filter_fetch_cache(self, package_id):
|
||||
if self._caching:
|
||||
return loadobj("%s/%s/%s" % (
|
||||
@@ -2294,8 +2312,8 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
myr = ref['user_package_mask']
|
||||
|
||||
try:
|
||||
cl_data = self._settings[_CL_PLUGIN_ID]
|
||||
validator_cache = cl_data['masking_validation']['cache']
|
||||
clset = self._client_settings
|
||||
validator_cache = clset['masking_validation']['cache']
|
||||
validator_cache[(package_id, self.name, live)] = -1, myr
|
||||
except KeyError: # system settings client plugin not found
|
||||
pass
|
||||
@@ -2334,8 +2352,8 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
ref = self._settings['pkg_masking_reference']
|
||||
myr = ref['user_package_unmask']
|
||||
try:
|
||||
cl_data = self._settings[_CL_PLUGIN_ID]
|
||||
validator_cache = cl_data['masking_validation']['cache']
|
||||
clset = self._client_settings
|
||||
validator_cache = clset['masking_validation']['cache']
|
||||
validator_cache[(package_id, self.name, live)] = \
|
||||
package_id, myr
|
||||
except KeyError: # system settings client plugin not found
|
||||
@@ -2347,10 +2365,9 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
|
||||
# check if repository packages.db.mask needs it masked
|
||||
repos_mask = {}
|
||||
client_plg_id = etpConst['system_settings_plugins_ids']['client_plugin']
|
||||
client_settings = self._settings.get(client_plg_id, {})
|
||||
if client_settings:
|
||||
repos_mask = client_settings['repositories']['mask']
|
||||
clset = self._client_settings
|
||||
if clset:
|
||||
repos_mask = clset['repositories']['mask']
|
||||
|
||||
repomask = repos_mask.get(self.name)
|
||||
if isinstance(repomask, (list, set, frozenset)):
|
||||
@@ -2376,8 +2393,7 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
myr = ref['repository_packages_db_mask']
|
||||
|
||||
try:
|
||||
cl_data = self._settings[_CL_PLUGIN_ID]
|
||||
validator_cache = cl_data['masking_validation']['cache']
|
||||
validator_cache = clset['masking_validation']['cache']
|
||||
validator_cache[(package_id, self.name, live)] = \
|
||||
-1, myr
|
||||
except KeyError: # system settings client plugin not found
|
||||
@@ -2401,8 +2417,8 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
ref = self._settings['pkg_masking_reference']
|
||||
myr = ref['user_license_mask']
|
||||
try:
|
||||
cl_data = self._settings[_CL_PLUGIN_ID]
|
||||
validator_cache = cl_data['masking_validation']['cache']
|
||||
clset = self._client_settings
|
||||
validator_cache = clset['masking_validation']['cache']
|
||||
validator_cache[(package_id, self.name, live)] = -1, myr
|
||||
except KeyError: # system settings client plugin not found
|
||||
pass
|
||||
@@ -2426,9 +2442,8 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
if same_keywords:
|
||||
myr = mask_ref['system_keyword']
|
||||
try:
|
||||
|
||||
cl_data = self._settings[_CL_PLUGIN_ID]
|
||||
validator_cache = cl_data['masking_validation']['cache']
|
||||
clset = self._client_settings
|
||||
validator_cache = clset['masking_validation']['cache']
|
||||
validator_cache[(package_id, self.name, live)] = \
|
||||
package_id, myr
|
||||
|
||||
@@ -2456,8 +2471,8 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
# all packages in this repo with keyword "keyword" are ok
|
||||
myr = mask_ref['user_repo_package_keywords_all']
|
||||
try:
|
||||
cl_data = self._settings[_CL_PLUGIN_ID]
|
||||
validator_cache = cl_data['masking_validation']['cache']
|
||||
clset = self._client_settings
|
||||
validator_cache = clset['masking_validation']['cache']
|
||||
validator_cache[(package_id, self.name, live)] = \
|
||||
package_id, myr
|
||||
except KeyError: # system settings client plugin not found
|
||||
@@ -2483,8 +2498,8 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
|
||||
myr = mask_ref['user_repo_package_keywords']
|
||||
try:
|
||||
cl_data = self._settings[_CL_PLUGIN_ID]
|
||||
validator_cache = cl_data['masking_validation']['cache']
|
||||
clset = self._client_settings
|
||||
validator_cache = clset['masking_validation']['cache']
|
||||
validator_cache[(package_id, self.name, live)] = \
|
||||
package_id, myr
|
||||
except KeyError: # system settings client plugin not found
|
||||
@@ -2526,8 +2541,8 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
# valid!
|
||||
myr = mask_ref['user_package_keywords']
|
||||
try:
|
||||
cl_data = self._settings[_CL_PLUGIN_ID]
|
||||
validator_cache = cl_data['masking_validation']['cache']
|
||||
clset = self._client_settings
|
||||
validator_cache = clset['masking_validation']['cache']
|
||||
validator_cache[(package_id, self.name, live)] = \
|
||||
package_id, myr
|
||||
except KeyError: # system settings client plugin not found
|
||||
@@ -2541,12 +2556,13 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
## package keywords
|
||||
# check if repository contains keyword unmasking data
|
||||
|
||||
cl_data = self._settings.get(_CL_PLUGIN_ID)
|
||||
if cl_data is None:
|
||||
clset = self._client_settings
|
||||
if clset is None:
|
||||
# SystemSettings Entropy Client plugin not available
|
||||
return
|
||||
|
||||
# let's see if something is available in repository config
|
||||
repo_keywords = cl_data['repositories']['repos_keywords'].get(
|
||||
repo_keywords = clset['repositories']['repos_keywords'].get(
|
||||
self.name)
|
||||
if repo_keywords is None:
|
||||
# nopers, sorry!
|
||||
@@ -2557,7 +2573,7 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
if same_keywords:
|
||||
# universal keyword matches!
|
||||
myr = mask_ref['repository_packages_db_keywords']
|
||||
validator_cache = cl_data['masking_validation']['cache']
|
||||
validator_cache = clset['masking_validation']['cache']
|
||||
validator_cache[(package_id, self.name, live)] = \
|
||||
package_id, myr
|
||||
return package_id, myr
|
||||
@@ -2594,7 +2610,7 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
if same_keywords:
|
||||
# found! this pkg is not masked, yay!
|
||||
myr = mask_ref['repository_packages_db_keywords']
|
||||
validator_cache = cl_data['masking_validation']['cache']
|
||||
validator_cache = clset['masking_validation']['cache']
|
||||
validator_cache[(package_id, self.name, live)] = \
|
||||
package_id, myr
|
||||
return package_id, myr
|
||||
@@ -2603,7 +2619,7 @@ class MaskableRepository(EntropyRepositoryBase):
|
||||
"""
|
||||
Reimplemented from EntropyRepositoryBase
|
||||
"""
|
||||
validator_cache = self._settings.get(_CL_PLUGIN_ID, {}).get(
|
||||
validator_cache = self._client_settings.get(
|
||||
'masking_validation', {}).get('cache', {})
|
||||
|
||||
cached = validator_cache.get((package_id, self.name, live))
|
||||
@@ -2663,7 +2679,8 @@ class AvailablePackagesRepository(CachedRepository, MaskableRepository):
|
||||
to make possible to update the repository.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
EntropyRepository.__init__(self, *args, **kwargs)
|
||||
super(AvailablePackagesRepository, self).__init__(*args, **kwargs)
|
||||
|
||||
# ensure proper repository file permissions
|
||||
if entropy.tools.is_root() and os.path.isfile(self._db):
|
||||
const_setup_file(self._db, etpConst['entropygid'], 0o644,
|
||||
@@ -2740,8 +2757,15 @@ class AvailablePackagesRepository(CachedRepository, MaskableRepository):
|
||||
|
||||
def clearCache(self):
|
||||
# clear package masking filter
|
||||
cl_data = self._settings.get(_CL_PLUGIN_ID, {})
|
||||
cl_data.get('masking_validation', {}).get('cache', {}).clear()
|
||||
try:
|
||||
clset = self._client_settings
|
||||
except AttributeError:
|
||||
# EntropyBaseRepository constructor calls
|
||||
# _maybeDatabaseSchemaUpdates that calls us here
|
||||
# while _real_client_settings is not set yet.
|
||||
pass
|
||||
else:
|
||||
clset.get('masking_validation', {}).get('cache', {}).clear()
|
||||
EntropyRepository.clearCache(self)
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from entropy.const import etpConst
|
||||
from entropy.qa import QAInterface
|
||||
from entropy.security import System
|
||||
from entropy.security import Repository as RepositorySecurity
|
||||
from entropy.client.interfaces.settings import ClientSystemSettingsPlugin
|
||||
|
||||
class LoadersMixin:
|
||||
|
||||
@@ -167,7 +168,7 @@ class LoadersMixin:
|
||||
"""
|
||||
Return SystemSettings Entropy Client plugin metadata dictionary
|
||||
"""
|
||||
p_id = etpConst['system_settings_plugins_ids']['client_plugin']
|
||||
p_id = ClientSystemSettingsPlugin.ID
|
||||
return self._settings[p_id]
|
||||
|
||||
def Cacher(self):
|
||||
|
||||
693
lib/entropy/client/interfaces/settings.py
Normal file
693
lib/entropy/client/interfaces/settings.py
Normal file
@@ -0,0 +1,693 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
|
||||
@author: Fabio Erculiani <lxnay@sabayon.org>
|
||||
@contact: lxnay@sabayon.org
|
||||
@copyright: Fabio Erculiani
|
||||
@license: GPL-2
|
||||
|
||||
B{Entropy Package Manager Client Settings module}.
|
||||
|
||||
"""
|
||||
import codecs
|
||||
import os
|
||||
|
||||
from entropy.const import etpConst, const_file_readable, \
|
||||
const_convert_to_unicode
|
||||
from entropy.core.settings.plugins.skel import SystemSettingsPlugin
|
||||
from entropy.core.settings.base import SystemSettings
|
||||
|
||||
from entropy.exceptions import SystemDatabaseError, RepositoryError
|
||||
|
||||
import entropy.dep
|
||||
import entropy.tools
|
||||
|
||||
|
||||
class ClientSystemSettingsPlugin(SystemSettingsPlugin):
|
||||
|
||||
ID = etpConst['system_settings_plugins_ids']['client_plugin']
|
||||
|
||||
def __init__(self, helper_interface):
|
||||
SystemSettingsPlugin.__init__(
|
||||
self, self.ID, helper_interface)
|
||||
self.__repos_files = {}
|
||||
self.__repos_mtime = {}
|
||||
self._mtime_cache = {}
|
||||
# Package repositories must be able to live across
|
||||
# SystemSettings.clear() calls, because they are very
|
||||
# special and 3rd-party (but even Sulfur) tools expect to always
|
||||
# have them there after having called Client.add_package_repository
|
||||
self.__package_repositories = []
|
||||
self.__package_repositories_meta = {}
|
||||
|
||||
@staticmethod
|
||||
def client_conf_path():
|
||||
"""
|
||||
Return current client.conf path, this takes into account the current
|
||||
configuration files directory path (which is affected by "root" path
|
||||
changes [default: /])
|
||||
"""
|
||||
# path to /etc/entropy/server.conf (usually, depends on systemroot)
|
||||
return os.path.join(etpConst['confdir'], "client.conf")
|
||||
|
||||
def _add_package_repository(self, repository_id, repository_metadata):
|
||||
"""
|
||||
Internal method, used by Entropy Client. Add a package repository
|
||||
to this SystemSettings plugins to make it able to live across
|
||||
SystemSettings.clear() calls.
|
||||
|
||||
@param repository_id: repository identifier
|
||||
@type repository_id: string
|
||||
@param repository_metadata: a dict object that will be merged
|
||||
into SystemSettings['repositories'] when clear() will be called
|
||||
@type repository_metadata: dict
|
||||
@raise KeyError: if repository_id is already stored.
|
||||
"""
|
||||
if repository_id in self.__package_repositories:
|
||||
raise KeyError("%s already added" % (repository_id,))
|
||||
self.__package_repositories.append(repository_id)
|
||||
self.__package_repositories_meta[repository_id] = repository_metadata
|
||||
|
||||
def _drop_package_repository(self, repository_id):
|
||||
"""
|
||||
Drop package repository previously added by calling
|
||||
_add_package_repository()
|
||||
|
||||
@param repository_id: repository identifier
|
||||
@type repository_id: string
|
||||
@raise KeyError: if repository_id is not available
|
||||
"""
|
||||
del self.__package_repositories_meta[repository_id]
|
||||
self.__package_repositories.remove(repository_id)
|
||||
|
||||
def __setup_repos_files(self, system_settings):
|
||||
"""
|
||||
This function collects available repositories configuration files
|
||||
by filling internal dict() __repos_files and __repos_mtime.
|
||||
|
||||
@param system_settings: SystemSettings instance
|
||||
@type system_settings: instance of SystemSettings
|
||||
@return: None
|
||||
@rtype: None
|
||||
"""
|
||||
|
||||
self.__repos_mtime = {
|
||||
'repos_license_whitelist': {},
|
||||
'repos_mask': {},
|
||||
'repos_system_mask': {},
|
||||
'repos_critical_updates': {},
|
||||
'repos_keywords': {},
|
||||
}
|
||||
self.__repos_files = {
|
||||
'repos_license_whitelist': {},
|
||||
'repos_mask': {},
|
||||
'repos_system_mask': {},
|
||||
'repos_critical_updates': {},
|
||||
'repos_keywords': {},
|
||||
}
|
||||
|
||||
dmp_dir = etpConst['dumpstoragedir']
|
||||
avail_data = system_settings['repositories']['available']
|
||||
for repoid in system_settings['repositories']['order']:
|
||||
|
||||
repo_data = avail_data[repoid]
|
||||
if "__temporary__" in repo_data:
|
||||
continue
|
||||
|
||||
repos_mask_setting = {}
|
||||
repos_mask_mtime = {}
|
||||
repos_lic_wl_setting = {}
|
||||
repos_lic_wl_mtime = {}
|
||||
repos_sm_mask_setting = {}
|
||||
repos_sm_mask_mtime = {}
|
||||
confl_tagged = {}
|
||||
repos_critical_updates_setting = {}
|
||||
repos_critical_updates_mtime = {}
|
||||
repos_keywords_setting = {}
|
||||
repos_keywords_mtime = {}
|
||||
|
||||
maskpath = os.path.join(repo_data['dbpath'],
|
||||
etpConst['etpdatabasemaskfile'])
|
||||
wlpath = os.path.join(repo_data['dbpath'],
|
||||
etpConst['etpdatabaselicwhitelistfile'])
|
||||
sm_path = os.path.join(repo_data['dbpath'],
|
||||
etpConst['etpdatabasesytemmaskfile'])
|
||||
critical_path = os.path.join(repo_data['dbpath'],
|
||||
etpConst['etpdatabasecriticalfile'])
|
||||
keywords_path = os.path.join(repo_data['dbpath'],
|
||||
etpConst['etpdatabasekeywordsfile'])
|
||||
|
||||
if const_file_readable(maskpath):
|
||||
repos_mask_setting[repoid] = maskpath
|
||||
repos_mask_mtime[repoid] = dmp_dir + "/repo_" + \
|
||||
repoid + "_" + etpConst['etpdatabasemaskfile'] + ".mtime"
|
||||
|
||||
if const_file_readable(wlpath):
|
||||
repos_lic_wl_setting[repoid] = wlpath
|
||||
repos_lic_wl_mtime[repoid] = dmp_dir + "/repo_" + \
|
||||
repoid + "_" + etpConst['etpdatabaselicwhitelistfile'] + \
|
||||
".mtime"
|
||||
|
||||
if const_file_readable(sm_path):
|
||||
repos_sm_mask_setting[repoid] = sm_path
|
||||
repos_sm_mask_mtime[repoid] = dmp_dir + "/repo_" + \
|
||||
repoid + "_" + etpConst['etpdatabasesytemmaskfile'] + \
|
||||
".mtime"
|
||||
|
||||
if const_file_readable(critical_path):
|
||||
repos_critical_updates_setting[repoid] = critical_path
|
||||
repos_critical_updates_mtime[repoid] = dmp_dir + "/repo_" + \
|
||||
repoid + "_" + etpConst['etpdatabasecriticalfile'] + \
|
||||
".mtime"
|
||||
|
||||
if const_file_readable(keywords_path):
|
||||
repos_keywords_setting[repoid] = keywords_path
|
||||
repos_keywords_mtime[repoid] = dmp_dir + "/repo_" + \
|
||||
repoid + "_" + etpConst['etpdatabasekeywordsfile'] + \
|
||||
".mtime"
|
||||
|
||||
self.__repos_files['repos_mask'].update(repos_mask_setting)
|
||||
self.__repos_mtime['repos_mask'].update(repos_mask_mtime)
|
||||
|
||||
self.__repos_files['repos_license_whitelist'].update(
|
||||
repos_lic_wl_setting)
|
||||
self.__repos_mtime['repos_license_whitelist'].update(
|
||||
repos_lic_wl_mtime)
|
||||
|
||||
self.__repos_files['repos_system_mask'].update(
|
||||
repos_sm_mask_setting)
|
||||
self.__repos_mtime['repos_system_mask'].update(
|
||||
repos_sm_mask_mtime)
|
||||
|
||||
self.__repos_files['repos_critical_updates'].update(
|
||||
repos_critical_updates_setting)
|
||||
self.__repos_mtime['repos_critical_updates'].update(
|
||||
repos_critical_updates_mtime)
|
||||
|
||||
self.__repos_files['repos_keywords'].update(
|
||||
repos_keywords_setting)
|
||||
self.__repos_mtime['repos_keywords'].update(
|
||||
repos_keywords_mtime)
|
||||
|
||||
def __generic_parser(self, filepath):
|
||||
"""
|
||||
Internal method. This is the generic file parser here.
|
||||
|
||||
@param filepath: valid path
|
||||
@type filepath: string
|
||||
@return: raw text extracted from file
|
||||
@rtype: list
|
||||
"""
|
||||
root = etpConst['systemroot']
|
||||
try:
|
||||
mtime = os.path.getmtime(filepath)
|
||||
except (OSError, IOError):
|
||||
mtime = 0.0
|
||||
|
||||
cache_key = (root, filepath)
|
||||
cache_obj = self._mtime_cache.get(cache_key)
|
||||
if cache_obj is not None:
|
||||
if cache_obj['mtime'] == mtime:
|
||||
return cache_obj['data']
|
||||
|
||||
cache_obj = {'mtime': mtime,}
|
||||
|
||||
enc = etpConst['conf_encoding']
|
||||
data = entropy.tools.generic_file_content_parser(filepath,
|
||||
comment_tag = "##", encoding = enc)
|
||||
if SystemSettings.DISK_DATA_CACHE:
|
||||
cache_obj['data'] = data
|
||||
self._mtime_cache[cache_key] = cache_obj
|
||||
return data
|
||||
|
||||
def __run_post_branch_migration_hooks(self, sys_settings_instance):
|
||||
|
||||
# only root can do this
|
||||
if os.getuid() != 0:
|
||||
return
|
||||
|
||||
old_branch_path = etpConst['etp_previous_branch_file']
|
||||
in_branch_upgrade_path = etpConst['etp_in_branch_upgrade_file']
|
||||
current_branch = sys_settings_instance['repositories']['branch']
|
||||
enc = etpConst['conf_encoding']
|
||||
|
||||
def write_current_branch(branch):
|
||||
with codecs.open(old_branch_path, "w", encoding=enc) as old_brf:
|
||||
old_brf.write(branch)
|
||||
|
||||
def write_in_branch_upgrade(branch):
|
||||
with codecs.open(in_branch_upgrade_path, "w", encoding=enc) as brf:
|
||||
brf.write("in branch upgrade: %s" % (branch,))
|
||||
|
||||
if not os.path.isfile(old_branch_path):
|
||||
write_current_branch(current_branch)
|
||||
return
|
||||
|
||||
with codecs.open(old_branch_path, "r", encoding=enc) as old_f:
|
||||
old_branch = old_f.readline().strip()
|
||||
|
||||
if old_branch == current_branch: # all fine, no need to run
|
||||
return
|
||||
|
||||
repos, err = self._helper._run_repositories_post_branch_switch_hooks(
|
||||
old_branch, current_branch)
|
||||
if not err:
|
||||
write_in_branch_upgrade(current_branch)
|
||||
write_current_branch(current_branch)
|
||||
|
||||
def __run_post_branch_upgrade_hooks(self, sys_settings_instance):
|
||||
|
||||
# only root can do this
|
||||
if os.getuid() != 0:
|
||||
return
|
||||
|
||||
repos, errors = self._helper._run_repository_post_branch_upgrade_hooks(
|
||||
pretend = True)
|
||||
if not repos:
|
||||
# no scripts to run
|
||||
return
|
||||
|
||||
# look for updates
|
||||
# critical_updates = False is needed to avoid
|
||||
# issues with metadata not being available
|
||||
try:
|
||||
outcome = self._helper.calculate_updates(critical_updates = False)
|
||||
update, remove = outcome['update'], outcome['remove']
|
||||
fine, spm_fine = outcome['fine'], outcome['spm_fine']
|
||||
except (ValueError, SystemDatabaseError, RepositoryError,):
|
||||
# RepositoryError is triggered when branch is hopped
|
||||
# SystemDatabaseError is triggered when no client db is avail
|
||||
# ValueError is triggered when repos are broken
|
||||
update = 1 # foo!
|
||||
|
||||
def delete_in_branch_upgrade():
|
||||
br_path = etpConst['etp_in_branch_upgrade_file']
|
||||
try:
|
||||
os.remove(br_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# actually execute this only if
|
||||
# there are no updates left
|
||||
if not update:
|
||||
self._helper._run_repository_post_branch_upgrade_hooks()
|
||||
delete_in_branch_upgrade()
|
||||
|
||||
def system_mask_parser(self, system_settings_instance):
|
||||
|
||||
parser_data = {}
|
||||
# match installed packages of system_mask
|
||||
mask_installed = []
|
||||
mask_installed_keys = {}
|
||||
while (self._helper.installed_repository() != None):
|
||||
try:
|
||||
self._helper.installed_repository().validate()
|
||||
except SystemDatabaseError:
|
||||
break
|
||||
mc_cache = set()
|
||||
repos_mask_list = self.__repositories_system_mask(
|
||||
system_settings_instance)
|
||||
m_list = repos_mask_list + system_settings_instance['system_mask']
|
||||
for atom in m_list:
|
||||
m_ids, m_r = self._helper.installed_repository().atomMatch(atom,
|
||||
multiMatch = True)
|
||||
if m_r != 0:
|
||||
continue
|
||||
mykey = entropy.dep.dep_getkey(atom)
|
||||
obj = mask_installed_keys.setdefault(mykey, set())
|
||||
for m_id in m_ids:
|
||||
if m_id in mc_cache:
|
||||
continue
|
||||
mc_cache.add(m_id)
|
||||
mask_installed.append(m_id)
|
||||
obj.add(m_id)
|
||||
break
|
||||
|
||||
parser_data.update({
|
||||
'repos_installed': mask_installed,
|
||||
'repos_installed_keys': mask_installed_keys,
|
||||
})
|
||||
return parser_data
|
||||
|
||||
def masking_validation_parser(self, system_settings_instance):
|
||||
data = {
|
||||
'cache': {}, # package masking validation cache
|
||||
}
|
||||
return data
|
||||
|
||||
def __repositories_repos_keywords(self, repo_keywords_path):
|
||||
"""
|
||||
Parser returning system packages mask metadata read from
|
||||
packages.db.keywords file inside the repository directory.
|
||||
This file contains maintainer supplied per-repository extra
|
||||
package keywords.
|
||||
"""
|
||||
root = etpConst['systemroot']
|
||||
try:
|
||||
mtime = os.path.getmtime(repo_keywords_path)
|
||||
except (OSError, IOError):
|
||||
mtime = 0.0
|
||||
|
||||
cache_key = (root, repo_keywords_path)
|
||||
cache_obj = self._mtime_cache.get(cache_key)
|
||||
if cache_obj is not None:
|
||||
if cache_obj['mtime'] == mtime:
|
||||
return cache_obj['data']
|
||||
|
||||
cache_obj = {'mtime': mtime,}
|
||||
|
||||
data = {
|
||||
# universal keywords: keywords added repository-wide to all
|
||||
# the available packages (in repo).
|
||||
'universal': set(),
|
||||
# per-package keywording, keys are atoms/dep (first line argument)
|
||||
# values are provided keywords
|
||||
'packages': {},
|
||||
'packages_ids': None, # reserved for entropy.db package validation
|
||||
}
|
||||
|
||||
enc = etpConst['conf_encoding']
|
||||
entries = entropy.tools.generic_file_content_parser(
|
||||
repo_keywords_path, encoding = enc)
|
||||
|
||||
# iterate over config file data
|
||||
for entry in entries:
|
||||
entry = entry.split()
|
||||
if len(entry) == 1:
|
||||
# universal keyword
|
||||
item = entry[0]
|
||||
if item == "**":
|
||||
item = ''
|
||||
data['universal'].add(item)
|
||||
|
||||
elif len(entry) > 1:
|
||||
# per package keyword
|
||||
pkg = entry[0]
|
||||
keywords = entry[1:]
|
||||
obj = data['packages'].setdefault(pkg, set())
|
||||
obj.update(keywords)
|
||||
|
||||
if SystemSettings.DISK_DATA_CACHE:
|
||||
cache_obj['data'] = data
|
||||
self._mtime_cache[cache_key] = cache_obj
|
||||
return data
|
||||
|
||||
def __repositories_system_mask(self, sys_settings_instance):
|
||||
"""
|
||||
Parser returning system packages mask metadata read from
|
||||
packages.db.system_mask file inside the repository directory.
|
||||
This file contains packages that should be always kept
|
||||
installed, extending the already defined (in repository database)
|
||||
set of atoms.
|
||||
"""
|
||||
system_mask = []
|
||||
enc = etpConst['conf_encoding']
|
||||
for repoid in self.__repos_files['repos_system_mask']:
|
||||
filepath = self.__repos_files['repos_system_mask'][repoid]
|
||||
mtimepath = self.__repos_mtime['repos_system_mask'][repoid]
|
||||
sys_settings_instance.validate_entropy_cache(
|
||||
filepath, mtimepath, repoid = repoid)
|
||||
|
||||
entries = self.__generic_parser(filepath)
|
||||
system_mask += [x for x in entries if x not in system_mask]
|
||||
|
||||
return system_mask
|
||||
|
||||
def repositories_parser(self, sys_settings_instance):
|
||||
"""
|
||||
Parser that generates repository settings metadata.
|
||||
|
||||
@param sys_settings_instance: SystemSettings instance
|
||||
@type sys_settings_instance: instance of SystemSettings
|
||||
@return: parsed metadata
|
||||
@rtype: dict
|
||||
"""
|
||||
|
||||
# add back repository metadata to SystemSettings['repositories']
|
||||
avail_data = sys_settings_instance['repositories']['available']
|
||||
for repository_id in self.__package_repositories:
|
||||
if repository_id not in avail_data:
|
||||
repodata = self.__package_repositories_meta[repository_id]
|
||||
# if correct, this won't trigger a stack overflow
|
||||
# add_repository calling SystemSettings.clear() I mean
|
||||
added = self._helper.add_repository(repodata)
|
||||
if not added:
|
||||
raise ValueError("wtf? cannot add repository")
|
||||
|
||||
# fill repositories metadata dictionaries
|
||||
self.__setup_repos_files(sys_settings_instance)
|
||||
|
||||
data = {
|
||||
'license_whitelist': {},
|
||||
'mask': {},
|
||||
'system_mask': [],
|
||||
'critical_updates': {},
|
||||
'repos_keywords': {},
|
||||
}
|
||||
|
||||
# parse license whitelist
|
||||
# Parser returning licenses considered accepted by default
|
||||
# (= GPL compatibles) read from package.lic_whitelist.
|
||||
for repoid in self.__repos_files['repos_license_whitelist']:
|
||||
sys_settings_instance.validate_entropy_cache(
|
||||
self.__repos_files['repos_license_whitelist'][repoid],
|
||||
self.__repos_mtime['repos_license_whitelist'][repoid],
|
||||
repoid = repoid)
|
||||
|
||||
data['license_whitelist'][repoid] = self.__generic_parser(
|
||||
self.__repos_files['repos_license_whitelist'][repoid])
|
||||
|
||||
# package masking
|
||||
# Parser returning packages masked at repository level read from
|
||||
# packages.db.mask inside the repository database directory.
|
||||
for repoid in self.__repos_files['repos_mask']:
|
||||
sys_settings_instance.validate_entropy_cache(
|
||||
self.__repos_files['repos_mask'][repoid],
|
||||
self.__repos_mtime['repos_mask'][repoid], repoid = repoid)
|
||||
|
||||
data['mask'][repoid] = self.__generic_parser(
|
||||
self.__repos_files['repos_mask'][repoid])
|
||||
|
||||
# keywords masking
|
||||
# Parser returning packages masked at repository level read from
|
||||
# packages.db.keywords inside the repository database directory.
|
||||
for repoid in self.__repos_files['repos_keywords']:
|
||||
sys_settings_instance.validate_entropy_cache(
|
||||
self.__repos_files['repos_keywords'][repoid],
|
||||
self.__repos_mtime['repos_keywords'][repoid],
|
||||
repoid = repoid)
|
||||
|
||||
data['repos_keywords'][repoid] = \
|
||||
self.__repositories_repos_keywords(
|
||||
self.__repos_files['repos_keywords'][repoid])
|
||||
|
||||
# system masking
|
||||
data['system_mask'] = self.__repositories_system_mask(
|
||||
sys_settings_instance)
|
||||
|
||||
# critical updates
|
||||
# Parser returning critical packages list metadata read from
|
||||
# packages.db.critical file inside the repository directory.
|
||||
# This file contains packages that should be always updated
|
||||
# before anything else.
|
||||
for repoid in self.__repos_files['repos_critical_updates']:
|
||||
sys_settings_instance.validate_entropy_cache(
|
||||
self.__repos_files['repos_critical_updates'][repoid],
|
||||
self.__repos_mtime['repos_critical_updates'][repoid],
|
||||
repoid = repoid)
|
||||
|
||||
data['critical_updates'][repoid] = self.__generic_parser(
|
||||
self.__repos_files['repos_critical_updates'][repoid])
|
||||
|
||||
return data
|
||||
|
||||
def misc_parser(self, sys_settings_instance):
|
||||
|
||||
"""
|
||||
Parses Entropy client system configuration file.
|
||||
|
||||
@return dict data
|
||||
"""
|
||||
|
||||
data = {
|
||||
'filesbackup': etpConst['filesbackup'],
|
||||
'forcedupdates': etpConst['forcedupdates'],
|
||||
'packagehashes': etpConst['packagehashes'],
|
||||
'gpg': etpConst['client_gpg'],
|
||||
'ignore_spm_downgrades': False,
|
||||
'splitdebug': etpConst['splitdebug'],
|
||||
'splitdebug_dirs': etpConst['splitdebug_dirs'],
|
||||
'multifetch': 1,
|
||||
'collisionprotect': etpConst['collisionprotect'],
|
||||
'configprotect': set(),
|
||||
'configprotectmask': set(),
|
||||
'configprotectskip': set(),
|
||||
'autoprune_days': None, # disabled by default
|
||||
'edelta_support': False, # disabled by default
|
||||
}
|
||||
|
||||
cli_conf = ClientSystemSettingsPlugin.client_conf_path()
|
||||
root = etpConst['systemroot']
|
||||
try:
|
||||
mtime = os.path.getmtime(cli_conf)
|
||||
except (OSError, IOError):
|
||||
mtime = 0.0
|
||||
|
||||
cache_key = (root, cli_conf)
|
||||
cache_obj = self._mtime_cache.get(cache_key)
|
||||
if cache_obj is not None:
|
||||
if cache_obj['mtime'] == mtime:
|
||||
return cache_obj['data']
|
||||
|
||||
cache_obj = {'mtime': mtime,}
|
||||
|
||||
if not const_file_readable(cli_conf):
|
||||
if SystemSettings.DISK_DATA_CACHE:
|
||||
cache_obj['data'] = data
|
||||
self._mtime_cache[cache_key] = cache_obj
|
||||
return data
|
||||
|
||||
def _filesbackup(setting):
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if bool_setting is not None:
|
||||
data['filesbackup'] = bool_setting
|
||||
|
||||
def _forcedupdates(setting):
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if bool_setting is not None:
|
||||
data['forcedupdates'] = bool_setting
|
||||
|
||||
def _autoprune(setting):
|
||||
int_setting = entropy.tools.setting_to_int(setting, 0, 365)
|
||||
if int_setting is not None:
|
||||
data['autoprune_days'] = int_setting
|
||||
|
||||
def _packagesdelta(setting):
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if bool_setting is not None:
|
||||
data['edelta_support'] = bool_setting
|
||||
|
||||
def _packagehashes(setting):
|
||||
setting = setting.lower().split()
|
||||
hashes = set()
|
||||
for opt in setting:
|
||||
if opt in etpConst['packagehashes']:
|
||||
hashes.add(opt)
|
||||
if hashes:
|
||||
data['packagehashes'] = tuple(sorted(hashes))
|
||||
|
||||
def _multifetch(setting):
|
||||
int_setting = entropy.tools.setting_to_int(setting, None, None)
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if int_setting is not None:
|
||||
if int_setting not in range(2, 11):
|
||||
int_setting = 10
|
||||
data['multifetch'] = int_setting
|
||||
if bool_setting is not None:
|
||||
if bool_setting:
|
||||
data['multifetch'] = 3
|
||||
|
||||
def _gpg(setting):
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if bool_setting is not None:
|
||||
data['gpg'] = bool_setting
|
||||
|
||||
def _spm_downgrades(setting):
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if bool_setting is not None:
|
||||
data['ignore_spm_downgrades'] = bool_setting
|
||||
|
||||
def _splitdebug(setting):
|
||||
bool_setting = entropy.tools.setting_to_bool(setting)
|
||||
if bool_setting is not None:
|
||||
data['splitdebug'] = bool_setting
|
||||
|
||||
def _collisionprotect(setting):
|
||||
int_setting = entropy.tools.setting_to_int(setting, 0, 2)
|
||||
if int_setting is not None:
|
||||
data['collisionprotect'] = int_setting
|
||||
|
||||
def _configprotect(setting):
|
||||
for opt in setting.split():
|
||||
data['configprotect'].add(const_convert_to_unicode(opt))
|
||||
|
||||
def _configprotectmask(setting):
|
||||
for opt in setting.split():
|
||||
data['configprotectmask'].add(const_convert_to_unicode(opt))
|
||||
|
||||
def _configprotectskip(setting):
|
||||
for opt in setting.split():
|
||||
data['configprotectskip'].add(
|
||||
etpConst['systemroot'] + const_convert_to_unicode(opt))
|
||||
|
||||
settings_map = {
|
||||
# backward compatibility
|
||||
'filesbackup': _filesbackup,
|
||||
'files-backup': _filesbackup,
|
||||
# backward compatibility
|
||||
'forcedupdates': _forcedupdates,
|
||||
'forced-updates': _forcedupdates,
|
||||
'packages-autoprune-days': _autoprune,
|
||||
'packages-delta': _packagesdelta,
|
||||
# backward compatibility
|
||||
'packagehashes': _packagehashes,
|
||||
'package-hashes': _packagehashes,
|
||||
'multifetch': _multifetch,
|
||||
'gpg': _gpg,
|
||||
'ignore-spm-downgrades': _spm_downgrades,
|
||||
'splitdebug': _splitdebug,
|
||||
# backward compatibility
|
||||
'collisionprotect': _collisionprotect,
|
||||
'collision-protect': _collisionprotect,
|
||||
# backward compatibility
|
||||
'configprotect': _configprotect,
|
||||
'config-protect': _configprotect,
|
||||
# backward compatibility
|
||||
'configprotectmask': _configprotectmask,
|
||||
'config-protect-mask': _configprotectmask,
|
||||
# backward compatibility
|
||||
'configprotectskip': _configprotectskip,
|
||||
'config-protect-skip': _configprotectskip,
|
||||
}
|
||||
|
||||
enc = etpConst['conf_encoding']
|
||||
with codecs.open(cli_conf, "r", encoding=enc) as client_f:
|
||||
clientconf = [x.strip() for x in client_f.readlines() if \
|
||||
x.strip() and not x.strip().startswith("#")]
|
||||
for line in clientconf:
|
||||
|
||||
key, value = entropy.tools.extract_setting(line)
|
||||
if key is None:
|
||||
continue
|
||||
|
||||
func = settings_map.get(key)
|
||||
if func is None:
|
||||
continue
|
||||
func(value)
|
||||
|
||||
# completely disable GPG feature
|
||||
if not data['gpg'] and ("gpg" in data['packagehashes']):
|
||||
data['packagehashes'] = tuple((x for x in data['packagehashes'] \
|
||||
if x != "gpg"))
|
||||
|
||||
# support ETP_SPLITDEBUG
|
||||
split_debug = os.getenv("ETP_SPLITDEBUG")
|
||||
if split_debug is not None:
|
||||
_splitdebug(split_debug)
|
||||
|
||||
if SystemSettings.DISK_DATA_CACHE:
|
||||
cache_obj['data'] = data
|
||||
self._mtime_cache[cache_key] = cache_obj
|
||||
return data
|
||||
|
||||
def post_setup(self, system_settings_instance):
|
||||
"""
|
||||
Reimplemented from SystemSettingsPlugin.
|
||||
"""
|
||||
|
||||
if self._helper._can_run_sys_set_hooks:
|
||||
# run post-branch migration scripts if branch setting got changed
|
||||
self.__run_post_branch_migration_hooks(system_settings_instance)
|
||||
# run post-branch upgrade migration scripts if the function
|
||||
# above created migration files to handle
|
||||
self.__run_post_branch_upgrade_hooks(system_settings_instance)
|
||||
@@ -56,7 +56,8 @@ class SecurityTest(unittest.TestCase):
|
||||
|
||||
def test_security_cache(self):
|
||||
|
||||
cacher = EntropyCacher()
|
||||
cacher = self._entropy._cacher
|
||||
# this starts the cacher as well
|
||||
|
||||
cache_key = "zomg"
|
||||
data = {"1": 1, "2": 2}
|
||||
|
||||
Reference in New Issue
Block a user