6411 lines
236 KiB
Python
6411 lines
236 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
|
|
@author: Fabio Erculiani <lxnay@sabayon.org>
|
|
@contact: lxnay@sabayon.org
|
|
@copyright: Fabio Erculiani
|
|
@license: GPL-2
|
|
|
|
B{Entropy Package Manager Server Main Interfaces}.
|
|
|
|
"""
|
|
import sys
|
|
import os
|
|
import shutil
|
|
import copy
|
|
import tempfile
|
|
import time
|
|
import re
|
|
import errno
|
|
import hashlib
|
|
import subprocess
|
|
import stat
|
|
import codecs
|
|
|
|
from entropy.exceptions import OnlineMirrorError, PermissionDenied, \
|
|
SystemDatabaseError, RepositoryError
|
|
from entropy.const import etpConst, etpSys, const_setup_perms, \
|
|
const_create_working_dirs, const_convert_to_unicode, \
|
|
const_setup_file, const_get_stringtype, const_debug_write, \
|
|
const_debug_enabled, const_convert_to_rawstring
|
|
from entropy.output import purple, red, darkgreen, \
|
|
bold, brown, blue, darkred, teal
|
|
from entropy.cache import EntropyCacher
|
|
from entropy.server.interfaces.mirrors import Server as MirrorsServer
|
|
from entropy.i18n import _
|
|
from entropy.core.settings.base import SystemSettings
|
|
from entropy.core.settings.plugins.skel import SystemSettingsPlugin
|
|
from entropy.transceivers import EntropyTransceiver
|
|
from entropy.db import EntropyRepository
|
|
from entropy.db.skel import EntropyRepositoryPlugin
|
|
from entropy.server.interfaces.db import ServerRepositoryStatus, \
|
|
ServerPackagesRepository
|
|
from entropy.spm.plugins.factory import get_default_instance as get_spm, \
|
|
get_default_class as get_spm_class
|
|
from entropy.qa import QAInterfacePlugin
|
|
from entropy.security import Repository as RepositorySecurity
|
|
from entropy.db.exceptions import ProgrammingError
|
|
from entropy.client.interfaces import Client
|
|
from entropy.client.interfaces.db import InstalledPackagesRepository, \
|
|
GenericRepository
|
|
from entropy.client.misc import ConfigurationUpdates, ConfigurationFiles
|
|
|
|
import entropy.dep
|
|
import entropy.tools
|
|
import entropy.dump
|
|
|
|
SERVER_QA_PLUGIN = "ServerQAInterfacePlugin"
|
|
|
|
|
|
class ServerEntropyRepositoryPlugin(EntropyRepositoryPlugin):
|
|
|
|
PLUGIN_ID = "__server__"
|
|
|
|
def __init__(self, server_interface, metadata = None):
|
|
"""
|
|
Entropy server-side repository ServerPackagesRepository Plugin class.
|
|
This class will be instantiated and automatically added to
|
|
ServerPackagesRepository instances generated by Entropy Server.
|
|
|
|
@param server_interface: Entropy Server interface instance
|
|
@type server_interface: entropy.server.interfaces.Server class
|
|
@param metadata: any dict form metadata map (key => value)
|
|
@type metadata: dict
|
|
"""
|
|
EntropyRepositoryPlugin.__init__(self)
|
|
self._cacher = EntropyCacher()
|
|
self._settings = SystemSettings()
|
|
self.srv_sys_settings_plugin = \
|
|
etpConst['system_settings_plugins_ids']['server_plugin']
|
|
self._server = server_interface
|
|
if metadata is None:
|
|
self._metadata = {}
|
|
else:
|
|
self._metadata = metadata
|
|
|
|
def get_id(self):
|
|
return ServerEntropyRepositoryPlugin.PLUGIN_ID
|
|
|
|
def get_metadata(self):
|
|
"""
|
|
This method should always return a direct reference to the object and
|
|
NOT a copy.
|
|
"""
|
|
return self._metadata
|
|
|
|
def add_plugin_hook(self, entropy_repository_instance):
|
|
const_debug_write(__name__,
|
|
"ServerEntropyRepositoryPlugin: calling add_plugin_hook => %s" % (
|
|
self,)
|
|
)
|
|
|
|
repo = entropy_repository_instance.repository_id()
|
|
local_dbfile = self._metadata['local_dbfile']
|
|
if local_dbfile is not None:
|
|
taint_file = self._server._get_local_repository_taint_file(
|
|
repo)
|
|
if os.path.isfile(taint_file):
|
|
dbs = ServerRepositoryStatus()
|
|
dbs.set_tainted(local_dbfile)
|
|
dbs.set_bumped(local_dbfile)
|
|
|
|
if "__temporary__" in self._metadata: # in-memory db?
|
|
local_dbfile_exists = True
|
|
else:
|
|
local_dbfile_exists = os.path.lexists(local_dbfile)
|
|
|
|
if not local_dbfile_exists:
|
|
# better than having a completely broken db
|
|
self._metadata['read_only'] = False
|
|
# force parameters, only ServerEntropyRepository exposes
|
|
# the setReadonly method
|
|
entropy_repository_instance.setReadonly(False)
|
|
entropy_repository_instance.initializeRepository()
|
|
entropy_repository_instance.commit()
|
|
|
|
out_intf = self._metadata.get('output_interface')
|
|
if out_intf is not None:
|
|
entropy_repository_instance.output = out_intf.output
|
|
entropy_repository_instance.ask_question = out_intf.ask_question
|
|
|
|
return 0
|
|
|
|
def close_repo_hook(self, entropy_repository_instance):
|
|
const_debug_write(__name__,
|
|
"ServerEntropyRepositoryPlugin: calling close_repo_hook => %s" % (
|
|
self,)
|
|
)
|
|
|
|
# this happens because close_repositories() might be called
|
|
# before _setup_services() and in general, at any time, so, in this
|
|
# case, there is no need to print bullshit to dev.
|
|
if self._server.Mirrors is None:
|
|
return 0
|
|
|
|
repo = entropy_repository_instance.repository_id()
|
|
dbfile = self._metadata['local_dbfile']
|
|
if dbfile is None:
|
|
# fake repo, or temporary one
|
|
return 0
|
|
|
|
read_only = self._metadata['read_only']
|
|
if not read_only:
|
|
sts = ServerRepositoryStatus()
|
|
if sts.is_tainted(dbfile) and not sts.is_unlock_msg(dbfile):
|
|
u_msg = "[%s] %s" % (brown(repo),
|
|
darkgreen(_("mirrors have not been unlocked. Sync them.")),)
|
|
self._server.output(
|
|
u_msg,
|
|
importance = 1,
|
|
level = "warning",
|
|
header = brown(" * ")
|
|
)
|
|
# avoid spamming
|
|
sts.set_unlock_msg(dbfile)
|
|
|
|
return 0
|
|
|
|
def commit_hook(self, entropy_repository_instance):
|
|
|
|
const_debug_write(__name__,
|
|
"ServerEntropyRepositoryPlugin: calling commit_hook => %s" % (
|
|
self,)
|
|
)
|
|
|
|
dbs = ServerRepositoryStatus()
|
|
dbfile = self._metadata['local_dbfile']
|
|
if dbfile is None:
|
|
# fake repo, or temporary one
|
|
return 0
|
|
repo = entropy_repository_instance.repository_id()
|
|
read_only = self._metadata['read_only']
|
|
if read_only:
|
|
# do not taint database
|
|
return 0
|
|
|
|
# taint the database status
|
|
taint_file = self._server._get_local_repository_taint_file(repo)
|
|
enc = etpConst['conf_encoding']
|
|
with codecs.open(taint_file, "w", encoding=enc) as f:
|
|
f.write("repository tainted\n")
|
|
f.flush()
|
|
const_setup_file(taint_file, etpConst['entropygid'], 0o664)
|
|
dbs.set_tainted(dbfile)
|
|
|
|
if not dbs.is_bumped(dbfile):
|
|
# bump revision, setting DatabaseBump causes
|
|
# the session to just bump once
|
|
dbs.set_bumped(dbfile)
|
|
"""
|
|
Entropy repository revision bumping function.
|
|
Every time it's called,
|
|
revision is incremented by 1.
|
|
"""
|
|
revision_file = self._server._get_local_repository_revision_file(
|
|
repo)
|
|
enc = etpConst['conf_encoding']
|
|
if not os.path.isfile(revision_file):
|
|
revision = 1
|
|
else:
|
|
with codecs.open(revision_file, "r", encoding=enc) as rev_f:
|
|
revision = int(rev_f.readline().strip())
|
|
revision += 1
|
|
|
|
tmp_revision_file = revision_file + ".tmp"
|
|
with codecs.open(tmp_revision_file, "w", encoding=enc) as rev_fw:
|
|
rev_fw.write(str(revision)+"\n")
|
|
rev_fw.flush()
|
|
# atomic !
|
|
os.rename(tmp_revision_file, revision_file)
|
|
|
|
if not dbs.are_sets_synced(dbfile):
|
|
# auto-update package sets
|
|
self._server._sync_package_sets(entropy_repository_instance)
|
|
dbs.set_synced_sets(dbfile)
|
|
|
|
return 0
|
|
|
|
def _get_category_description_from_disk(self, category):
|
|
"""
|
|
Get category name description from Source Package Manager.
|
|
|
|
@param category: category name
|
|
@type category: string
|
|
@return: category description
|
|
@rtype: string
|
|
"""
|
|
spm = self._server.Spm()
|
|
return spm.get_package_category_description_metadata(category)
|
|
|
|
def __save_rss(self, srv_repo, rss_name, srv_updates):
|
|
# save to disk
|
|
try:
|
|
self._cacher.save(rss_name, srv_updates,
|
|
cache_dir = Server.CACHE_DIR)
|
|
except IOError as err:
|
|
e_msg = "[%s] %s: %s" % (brown(srv_repo),
|
|
purple(_("cannot store updates RSS cache")),
|
|
repr(err),)
|
|
self._server.output(
|
|
e_msg,
|
|
importance = 1,
|
|
level = "warning",
|
|
header = brown(" * ")
|
|
)
|
|
|
|
def _write_rss_for_removed_package(self, repo_db, package_id):
|
|
|
|
# setup variables we're going to use
|
|
srv_repo = repo_db.repository_id()
|
|
rss_revision = repo_db.retrieveRevision(package_id)
|
|
rss_atom = "%s~%s" % (repo_db.retrieveAtom(package_id), rss_revision,)
|
|
status = ServerRepositoryStatus()
|
|
srv_updates = status.get_updates_log(srv_repo)
|
|
rss_name = srv_repo + etpConst['rss-dump-name']
|
|
|
|
# load metadata from on disk cache, if available
|
|
rss_obj = self._cacher.pop(rss_name, cache_dir = Server.CACHE_DIR)
|
|
if rss_obj:
|
|
srv_updates.update(rss_obj)
|
|
|
|
# setup metadata keys, if not available
|
|
if 'added' not in srv_updates:
|
|
srv_updates['added'] = {}
|
|
if 'removed' not in srv_updates:
|
|
srv_updates['removed'] = {}
|
|
if 'light' not in srv_updates:
|
|
srv_updates['light'] = {}
|
|
|
|
# if pkgatom (rss_atom) is in the "added" metadata, drop it
|
|
if rss_atom in srv_updates['added']:
|
|
del srv_updates['added'][rss_atom]
|
|
# same thing for light key
|
|
if rss_atom in srv_updates['light']:
|
|
del srv_updates['light'][rss_atom]
|
|
|
|
# add metadata
|
|
mydict = {}
|
|
try:
|
|
mydict['description'] = repo_db.retrieveDescription(package_id)
|
|
except TypeError:
|
|
mydict['description'] = "N/A"
|
|
try:
|
|
mydict['homepage'] = repo_db.retrieveHomepage(package_id)
|
|
except TypeError:
|
|
mydict['homepage'] = ""
|
|
srv_updates['removed'][rss_atom] = mydict
|
|
|
|
# save to disk
|
|
self.__save_rss(srv_repo, rss_name, srv_updates)
|
|
|
|
def _write_rss_for_added_package(self, repo_db, package_id, package_data):
|
|
|
|
# setup variables we're going to use
|
|
srv_repo = repo_db.repository_id()
|
|
rss_atom = "%s~%s" % (package_data['atom'], package_data['revision'],)
|
|
status = ServerRepositoryStatus()
|
|
srv_updates = status.get_updates_log(srv_repo)
|
|
rss_name = srv_repo + etpConst['rss-dump-name']
|
|
|
|
# load metadata from on disk cache, if available
|
|
rss_obj = self._cacher.pop(rss_name, cache_dir = Server.CACHE_DIR)
|
|
if rss_obj:
|
|
srv_updates.update(rss_obj)
|
|
|
|
# setup metadata keys, if not available
|
|
if 'added' not in srv_updates:
|
|
srv_updates['added'] = {}
|
|
if 'removed' not in srv_updates:
|
|
srv_updates['removed'] = {}
|
|
if 'light' not in srv_updates:
|
|
srv_updates['light'] = {}
|
|
|
|
# if package_data['atom'] (rss_atom) is in the
|
|
# "removed" metadata, drop it
|
|
if rss_atom in srv_updates['removed']:
|
|
del srv_updates['removed'][rss_atom]
|
|
|
|
# add metadata
|
|
srv_updates['added'][rss_atom] = {}
|
|
srv_updates['added'][rss_atom]['description'] = \
|
|
package_data['description']
|
|
srv_updates['added'][rss_atom]['homepage'] = \
|
|
package_data['homepage']
|
|
|
|
srv_updates['light'][rss_atom] = {}
|
|
srv_updates['light'][rss_atom]['description'] = \
|
|
package_data['description']
|
|
srv_updates['light'][rss_atom]['homepage'] = \
|
|
package_data['homepage']
|
|
srv_updates['light'][rss_atom]['package_id'] = package_id
|
|
date_raw_str = const_convert_to_rawstring(package_data['datecreation'])
|
|
srv_updates['light'][rss_atom]['time_hash'] = \
|
|
hashlib.sha256(date_raw_str).hexdigest()
|
|
|
|
# save to disk
|
|
self.__save_rss(srv_repo, rss_name, srv_updates)
|
|
|
|
def add_package_hook(self, entropy_repository_instance, package_id,
|
|
package_data):
|
|
|
|
const_debug_write(__name__,
|
|
"ServerEntropyRepositoryPlugin: calling add_package_hook => %s" % (
|
|
self,)
|
|
)
|
|
|
|
# handle server-side repo RSS support
|
|
sys_set_plug = self.srv_sys_settings_plugin
|
|
if self._settings[sys_set_plug]['server']['rss']['enabled']:
|
|
self._write_rss_for_added_package(entropy_repository_instance,
|
|
package_id, package_data)
|
|
|
|
try:
|
|
descdata = self._get_category_description_from_disk(
|
|
package_data['category'])
|
|
entropy_repository_instance.setCategoryDescription(
|
|
package_data['category'], descdata)
|
|
except (IOError, OSError, EOFError,):
|
|
pass
|
|
entropy_repository_instance.commit()
|
|
|
|
return 0
|
|
|
|
def remove_package_hook(self, entropy_repository_instance, package_id,
|
|
from_add_package):
|
|
|
|
const_debug_write(__name__,
|
|
"ServerEntropyRepositoryPlugin: calling remove_package_hook => %s" % (
|
|
self,)
|
|
)
|
|
|
|
# handle server-side repo RSS support
|
|
sys_set_plug = self.srv_sys_settings_plugin
|
|
if self._settings[sys_set_plug]['server']['rss']['enabled'] \
|
|
and (not from_add_package):
|
|
|
|
# store addPackage action
|
|
self._write_rss_for_removed_package(entropy_repository_instance,
|
|
package_id)
|
|
|
|
return 0
|
|
|
|
def treeupdates_move_action_hook(self, entropy_repository_instance,
|
|
package_id):
|
|
# check for injection and warn the developer
|
|
injected = entropy_repository_instance.isInjected(package_id)
|
|
new_atom = entropy_repository_instance.retrieveAtom(package_id)
|
|
if injected:
|
|
mytxt = "%s: %s %s. %s !!! %s." % (
|
|
bold(_("INJECT")),
|
|
blue(str(new_atom)),
|
|
red(_("has been injected")),
|
|
red(_("quickpkg manually to update embedded db")),
|
|
red(_("Repository updated anyway")),
|
|
)
|
|
self._server.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkred(" * ")
|
|
)
|
|
return 0
|
|
|
|
def treeupdates_slot_move_action_hook(self, entropy_repository_instance,
|
|
package_id):
|
|
return self.treeupdates_move_action_hook(entropy_repository_instance,
|
|
package_id)
|
|
|
|
|
|
class ServerSystemSettingsPlugin(SystemSettingsPlugin):
|
|
|
|
# List of static server-side repositories that must survive
|
|
# a repositories metadata reload
|
|
REPOSITORIES = {}
|
|
|
|
|
|
|
|
def __init__(self, plugin_id, helper_interface):
|
|
SystemSettingsPlugin.__init__(self, plugin_id, helper_interface)
|
|
self._mtime_cache = {}
|
|
|
|
@staticmethod
|
|
def server_conf_path():
|
|
"""
|
|
Return current server.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'], "server.conf")
|
|
|
|
@staticmethod
|
|
def analyze_server_repo_string(repostring, product = None):
|
|
"""
|
|
Analyze a server repository string (usually contained in server.conf),
|
|
extracting all the parameters.
|
|
|
|
@param repostring: repository string
|
|
@type repostring: string
|
|
@keyword product: system product which repository belongs to
|
|
@rtype: None
|
|
@return: None
|
|
"""
|
|
|
|
if product is None:
|
|
product = etpConst['product']
|
|
|
|
mydata = {}
|
|
repo_key, repostring = entropy.tools.extract_setting(repostring)
|
|
if repo_key != "repository":
|
|
raise AttributeError("invalid repostring passed")
|
|
|
|
repo_split = repostring.split("|")
|
|
if len(repo_split) < 3:
|
|
raise AttributeError("invalid repostring passed (2)")
|
|
|
|
repoid = repo_split[0].strip()
|
|
repodesc = repo_split[1].strip()
|
|
repouris = repo_split[2].strip()
|
|
|
|
mydata = {}
|
|
mydata['repoid'] = repoid
|
|
mydata['description'] = repodesc
|
|
mydata['pkg_mirrors'] = []
|
|
mydata['repo_mirrors'] = []
|
|
mydata['community'] = False
|
|
uris = repouris.split()
|
|
for uri in uris:
|
|
do_pkg = False
|
|
do_repo = False
|
|
while True:
|
|
if uri.startswith("<p>"):
|
|
do_pkg = True
|
|
uri = uri[3:]
|
|
continue
|
|
if uri.startswith("<r>"):
|
|
do_repo = True
|
|
uri = uri[3:]
|
|
continue
|
|
break
|
|
if not (do_repo or do_pkg):
|
|
do_repo = True
|
|
do_pkg = True
|
|
if do_repo:
|
|
mydata['repo_mirrors'].append(uri)
|
|
if do_pkg:
|
|
mydata['pkg_mirrors'].append(uri)
|
|
|
|
return repoid, mydata
|
|
|
|
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 get_updatable_configuration_files(self, repository_id):
|
|
"""
|
|
Overridden from SystemSettings.
|
|
"""
|
|
files = set()
|
|
# hope that all the repos get synchronized with respect to
|
|
# package names moves
|
|
dep_rewrite_file = Server._get_conf_dep_rewrite_file()
|
|
dep_blacklist_file = Server._get_conf_dep_blacklist_file()
|
|
files.add(dep_rewrite_file)
|
|
files.add(dep_blacklist_file)
|
|
|
|
if (repository_id is not None) and \
|
|
(repository_id in self._helper.repositories()):
|
|
|
|
critical_file = self._helper._get_local_critical_updates_file(
|
|
repository_id)
|
|
files.add(critical_file)
|
|
keywords_file = self._helper._get_local_repository_keywords_file(
|
|
repository_id)
|
|
files.add(keywords_file)
|
|
mask_file = self._helper._get_local_repository_mask_file(
|
|
repository_id)
|
|
files.add(mask_file)
|
|
bl_file = self._helper._get_missing_dependencies_blacklist_file(
|
|
repository_id)
|
|
files.add(bl_file)
|
|
restricted_file = self._helper._get_local_restricted_file(
|
|
repository_id)
|
|
files.add(restricted_file)
|
|
system_mask_file = \
|
|
self._helper._get_local_repository_system_mask_file(
|
|
repository_id)
|
|
files.add(system_mask_file)
|
|
|
|
return files
|
|
|
|
def dep_rewrite_parser(self, sys_set):
|
|
|
|
cached = getattr(self, '_mod_rewrite_data', None)
|
|
if cached is not None:
|
|
return cached
|
|
|
|
data = {}
|
|
rewrite_file = Server._get_conf_dep_rewrite_file()
|
|
if not os.path.isfile(rewrite_file):
|
|
return data
|
|
rewrite_content = self.__generic_parser(rewrite_file)
|
|
|
|
for line in rewrite_content:
|
|
params = line.split()
|
|
if len(params) < 2:
|
|
continue
|
|
pkg_match, pattern, replaces = params[0], params[1], params[2:]
|
|
if pattern.startswith("++"):
|
|
compiled_pattern = None
|
|
pattern = pattern[2:]
|
|
if not pattern:
|
|
# malformed
|
|
continue
|
|
else:
|
|
try:
|
|
compiled_pattern = re.compile(pattern)
|
|
except re.error:
|
|
# invalid pattern
|
|
continue
|
|
# use this key to make sure to not overwrite similar entries
|
|
data[(pkg_match, pattern)] = (compiled_pattern, replaces)
|
|
|
|
self._mod_rewrite_data = data
|
|
return data
|
|
|
|
def dep_blacklist_parser(self, sys_set):
|
|
|
|
data = {}
|
|
blacklist_file = Server._get_conf_dep_blacklist_file()
|
|
if not os.path.isfile(blacklist_file):
|
|
return data
|
|
blacklist_content = self.__generic_parser(blacklist_file)
|
|
|
|
for line in blacklist_content:
|
|
params = line.split()
|
|
if len(params) < 2:
|
|
continue
|
|
pkg_match, blacklisted_deps = params[0], params[1:]
|
|
# use this key to make sure to not overwrite similar entries
|
|
obj = data.setdefault(pkg_match, [])
|
|
obj.extend(blacklisted_deps)
|
|
|
|
return data
|
|
|
|
def qa_sets_parser(self, sys_set):
|
|
|
|
data = {}
|
|
sets_file = Server._get_conf_qa_sets_file()
|
|
if not os.path.isfile(sets_file):
|
|
return data
|
|
sets_content = self.__generic_parser(sets_file)
|
|
|
|
for line in sets_content:
|
|
params = line.split()
|
|
if len(params) < 2:
|
|
continue
|
|
repo_id, qa_sets = params[0], params[1:]
|
|
obj = data.setdefault(repo_id, set())
|
|
obj.update(qa_sets)
|
|
|
|
return data
|
|
|
|
def server_parser(self, sys_set):
|
|
|
|
"""
|
|
Parses Entropy server system configuration file.
|
|
|
|
@return dict data
|
|
"""
|
|
server_conf = ServerSystemSettingsPlugin.server_conf_path()
|
|
root = etpConst['systemroot']
|
|
try:
|
|
mtime = os.path.getmtime(server_conf)
|
|
except (OSError, IOError):
|
|
mtime = 0.0
|
|
|
|
serverconf = None
|
|
cache_key = (root, server_conf)
|
|
cache_obj = self._mtime_cache.get(cache_key)
|
|
if cache_obj is not None:
|
|
if cache_obj['mtime'] == mtime:
|
|
serverconf = cache_obj['data']
|
|
else:
|
|
cache_obj = {'mtime': mtime,}
|
|
|
|
if serverconf is None:
|
|
enc = etpConst['conf_encoding']
|
|
try:
|
|
with codecs.open(server_conf, "r", encoding=enc) \
|
|
as server_f:
|
|
serverconf = [x.strip() for x in server_f.readlines() \
|
|
if x.strip()]
|
|
except IOError as err:
|
|
if err.errno != errno.ENOENT:
|
|
raise
|
|
# if file doesn't exist, provide empty
|
|
# serverconf list. In this way, we make sure that
|
|
# any additional metadata gets added.
|
|
# see the for loop iterating through the
|
|
# repository identifiers
|
|
serverconf = []
|
|
|
|
if SystemSettings.DISK_DATA_CACHE:
|
|
cache_obj['data'] = serverconf
|
|
self._mtime_cache[cache_key] = cache_obj
|
|
|
|
data = {
|
|
'repositories': ServerSystemSettingsPlugin.REPOSITORIES.copy(),
|
|
'community_mode': False,
|
|
'qa_langs': [const_convert_to_unicode("en_US"),
|
|
const_convert_to_unicode("C")],
|
|
'default_repository_id': const_convert_to_unicode(
|
|
etpConst['defaultserverrepositoryid']),
|
|
'base_repository_id': None,
|
|
'packages_expiration_days': etpConst['packagesexpirationdays'],
|
|
'database_file_format': const_convert_to_unicode(
|
|
etpConst['etpdatabasefileformat']),
|
|
'disabled_eapis': set(),
|
|
'broken_revdeps_qa_check': True,
|
|
'exp_based_scope': etpConst['expiration_based_scope'],
|
|
# disabled by default for now
|
|
'nonfree_packages_dir_support': False,
|
|
'sync_speed_limit': None,
|
|
'changelog': True,
|
|
'rss': {
|
|
'enabled': etpConst['rss-feed'],
|
|
'name': const_convert_to_unicode(etpConst['rss-name']),
|
|
'light_name': const_convert_to_unicode(
|
|
etpConst['rss-light-name']),
|
|
'base_url': const_convert_to_unicode(etpConst['rss-base-url']),
|
|
'website_url': const_convert_to_unicode(
|
|
etpConst['rss-website-url']),
|
|
'editor': const_convert_to_unicode(
|
|
etpConst['rss-managing-editor']),
|
|
'max_entries': etpConst['rss-max-entries'],
|
|
'light_max_entries': etpConst['rss-light-max-entries'],
|
|
},
|
|
}
|
|
|
|
fake_instance = self._helper.fake_default_repo
|
|
default_repo_changed = False
|
|
|
|
def _offservrepoid(line, setting):
|
|
# NOTE: remove this in future, supported for backward compat.
|
|
# NOTE: added for backward and mixed compat.
|
|
if default_repo_changed:
|
|
return
|
|
if not fake_instance:
|
|
data['default_repository_id'] = setting.strip()
|
|
|
|
def _default_repo(line, setting):
|
|
if not fake_instance:
|
|
data['default_repository_id'] = setting.strip()
|
|
default_repo_changed = True
|
|
|
|
def _exp_days(line, setting):
|
|
mydays = setting.strip()
|
|
try:
|
|
mydays = int(mydays)
|
|
data['packages_expiration_days'] = mydays
|
|
except ValueError:
|
|
return
|
|
|
|
def _exp_based_scope(line, setting):
|
|
exp_opt = entropy.tools.setting_to_bool(setting)
|
|
if exp_opt is not None:
|
|
data['exp_based_scope'] = exp_opt
|
|
|
|
def _nf_packages_dir_sup(line, setting):
|
|
opt = entropy.tools.setting_to_bool(setting)
|
|
if opt is not None:
|
|
data['nonfree_packages_dir_support'] = opt
|
|
|
|
def _disabled_eapis(line, setting):
|
|
mydis = setting.strip().split(",")
|
|
try:
|
|
mydis = [int(x) for x in mydis]
|
|
mydis = set([x for x in mydis if x in (1, 2, 3,)])
|
|
except ValueError:
|
|
return
|
|
if (len(mydis) < 3) and mydis:
|
|
data['disabled_eapis'] = mydis
|
|
|
|
def _server_basic_lang(line, setting):
|
|
data['qa_langs'] = setting.strip().split()
|
|
|
|
def _broken_revdeps_qa(line, setting):
|
|
opt = entropy.tools.setting_to_bool(setting)
|
|
if opt is not None:
|
|
data['broken_revdeps_qa_check'] = opt
|
|
|
|
def _repository_func(line, setting):
|
|
try:
|
|
repoid, repodata = \
|
|
ServerSystemSettingsPlugin.analyze_server_repo_string(
|
|
line, product = sys_set['repositories']['product'])
|
|
except AttributeError:
|
|
# error parsing string
|
|
return
|
|
|
|
# validate repository id string
|
|
if not entropy.tools.validate_repository_id(repoid):
|
|
sys.stderr.write("!!! invalid repository id '%s' in '%s'\n" % (
|
|
repoid, ServerSystemSettingsPlugin.server_conf_path()))
|
|
return
|
|
|
|
if repoid in data['repositories']:
|
|
# just update mirrors
|
|
data['repositories'][repoid]['pkg_mirrors'].extend(
|
|
repodata['pkg_mirrors'])
|
|
data['repositories'][repoid]['repo_mirrors'].extend(
|
|
repodata['repo_mirrors'])
|
|
else:
|
|
data['repositories'][repoid] = repodata.copy()
|
|
|
|
# base_repository_id support
|
|
if data['base_repository_id'] is None:
|
|
data['base_repository_id'] = repoid
|
|
|
|
def _database_format(line, setting):
|
|
if setting in etpConst['etpdatabasesupportedcformats']:
|
|
data['database_file_format'] = setting
|
|
|
|
def _syncspeedlimit(line, setting):
|
|
try:
|
|
speed_limit = int(setting)
|
|
except ValueError:
|
|
speed_limit = None
|
|
data['sync_speed_limit'] = speed_limit
|
|
|
|
def _changelog(line, setting):
|
|
bool_setting = entropy.tools.setting_to_bool(setting)
|
|
if bool_setting is not None:
|
|
data['changelog'] = bool_setting
|
|
|
|
def _community_mode(line, setting):
|
|
bool_setting = entropy.tools.setting_to_bool(setting)
|
|
if bool_setting is not None:
|
|
data['community_mode'] = bool_setting
|
|
|
|
def _rss_feed(line, setting):
|
|
bool_setting = entropy.tools.setting_to_bool(setting)
|
|
if bool_setting is not None:
|
|
data['rss']['enabled'] = bool_setting
|
|
|
|
def _rss_name(line, setting):
|
|
data['rss']['name'] = setting
|
|
|
|
def _rss_light_name(line, setting):
|
|
data['rss']['light_name'] = setting
|
|
|
|
def _rss_base_url(line, setting):
|
|
data['rss']['base_url'] = setting
|
|
|
|
def _rss_website_url(line, setting):
|
|
data['rss']['website_url'] = setting
|
|
|
|
def _managing_editor(line, setting):
|
|
data['rss']['editor'] = setting
|
|
|
|
def _max_rss_entries(line, setting):
|
|
try:
|
|
entries = int(setting)
|
|
data['rss']['max_entries'] = entries
|
|
except (ValueError, IndexError,):
|
|
return
|
|
|
|
def _max_rss_light_entries(line, setting):
|
|
try:
|
|
entries = int(setting)
|
|
data['rss']['light_max_entries'] = entries
|
|
except (ValueError, IndexError,):
|
|
return
|
|
|
|
settings_map = {
|
|
'officialserverrepositoryid': _offservrepoid,
|
|
'default-repository': _default_repo,
|
|
'expiration-days': _exp_days,
|
|
'community-mode': _community_mode,
|
|
'expiration-based-scope': _exp_based_scope,
|
|
'nonfree-packages-directory-support': _nf_packages_dir_sup,
|
|
'disabled-eapis': _disabled_eapis,
|
|
'broken-reverse-deps': _broken_revdeps_qa,
|
|
'server-basic-languages': _server_basic_lang,
|
|
'repository': _repository_func,
|
|
'database-format': _database_format,
|
|
# backward compatibility
|
|
'sync-speed-limit': _syncspeedlimit,
|
|
'syncspeedlimit': _syncspeedlimit,
|
|
'changelog': _changelog,
|
|
'rss-feed': _rss_feed,
|
|
'rss-name': _rss_name,
|
|
'rss-light-name': _rss_light_name,
|
|
'rss-base-url': _rss_base_url,
|
|
'rss-website-url': _rss_website_url,
|
|
'managing-editor': _managing_editor,
|
|
'max-rss-entries': _max_rss_entries,
|
|
'max-rss-light-entries': _max_rss_light_entries,
|
|
}
|
|
|
|
for line in serverconf:
|
|
|
|
key, value = entropy.tools.extract_setting(line)
|
|
if key is None:
|
|
continue
|
|
|
|
func = settings_map.get(key)
|
|
if func is None:
|
|
continue
|
|
func(line, value)
|
|
|
|
env_community_mode = os.getenv("ETP_COMMUNITY_MODE")
|
|
if env_community_mode == "0":
|
|
data['community_mode'] = False
|
|
elif env_community_mode == "1":
|
|
data['community_mode'] = True
|
|
|
|
# add system database if community repository mode is enabled
|
|
if data['community_mode']:
|
|
client_repository_id = etpConst['clientserverrepoid']
|
|
data['repositories'][client_repository_id] = {}
|
|
mydata = {}
|
|
mydata['description'] = const_convert_to_unicode(
|
|
"Community Repositories System Repository")
|
|
mydata['pkg_mirrors'] = []
|
|
mydata['repo_mirrors'] = []
|
|
mydata['community'] = False
|
|
data['repositories'][client_repository_id].update(mydata)
|
|
ServerSystemSettingsPlugin.REPOSITORIES[client_repository_id] = \
|
|
mydata
|
|
# installed packages repository is now the base repository
|
|
data['base_repository_id'] = etpConst['clientserverrepoid']
|
|
|
|
# expand paths
|
|
for repoid in data['repositories']:
|
|
ServerSystemSettingsPlugin.extend_repository_metadata(
|
|
sys_set, repoid, data['repositories'][repoid])
|
|
|
|
# Support for shell variables
|
|
shell_repoid = os.getenv('ETP_REPO')
|
|
if shell_repoid:
|
|
data['default_repository_id'] = shell_repoid
|
|
|
|
expiration_days = os.getenv('ETP_EXPIRATION_DAYS')
|
|
if expiration_days:
|
|
try:
|
|
expiration_days = int(expiration_days)
|
|
data['packages_expiration_days'] = expiration_days
|
|
except ValueError:
|
|
pass
|
|
|
|
return data
|
|
|
|
@staticmethod
|
|
def extend_repository_metadata(system_settings, repository_id, metadata):
|
|
"""
|
|
Extend server-side Repository metadata dictionary
|
|
with information required by Entropy Server.
|
|
"""
|
|
metadata['repo_basedir'] = os.path.join(
|
|
etpConst['entropyworkdir'],
|
|
"server",
|
|
repository_id)
|
|
|
|
metadata['packages_dir'] = os.path.join(
|
|
etpConst['entropyworkdir'],
|
|
"server",
|
|
repository_id,
|
|
etpConst['packagesrelativepath_basedir'],
|
|
etpConst['currentarch'])
|
|
|
|
metadata['packages_dir_nonfree'] = os.path.join(
|
|
etpConst['entropyworkdir'],
|
|
"server",
|
|
repository_id,
|
|
etpConst['packagesrelativepath_basedir_nonfree'],
|
|
etpConst['currentarch'])
|
|
|
|
metadata['packages_dir_restricted'] = os.path.join(
|
|
etpConst['entropyworkdir'],
|
|
"server",
|
|
repository_id,
|
|
etpConst['packagesrelativepath_basedir_restricted'],
|
|
etpConst['currentarch'])
|
|
|
|
metadata['store_dir'] = os.path.join(
|
|
etpConst['entropyworkdir'],
|
|
"server",
|
|
repository_id,
|
|
"store",
|
|
etpConst['currentarch'])
|
|
|
|
# consider this a base dir
|
|
metadata['upload_basedir'] = os.path.join(
|
|
etpConst['entropyworkdir'],
|
|
"server",
|
|
repository_id,
|
|
"upload")
|
|
|
|
metadata['database_dir'] = os.path.join(
|
|
etpConst['entropyworkdir'],
|
|
"server",
|
|
repository_id,
|
|
"database",
|
|
etpConst['currentarch'])
|
|
|
|
metadata['remote_repo_basedir'] = os.path.join(
|
|
system_settings['repositories']['product'],
|
|
repository_id)
|
|
|
|
metadata['database_remote_path'] = \
|
|
ServerSystemSettingsPlugin.get_repository_remote_path(
|
|
system_settings, repository_id)
|
|
metadata['override_database_remote_path'] = None
|
|
|
|
@staticmethod
|
|
def get_repository_remote_path(system_settings, repository_id):
|
|
return system_settings['repositories']['product'] + "/" + \
|
|
repository_id + "/database/" + etpConst['currentarch']
|
|
|
|
@staticmethod
|
|
def set_override_remote_repository(system_settings, repository_id,
|
|
override_repository_id):
|
|
"""
|
|
Used to set an overridden remote path where to push repository
|
|
database. This can be used for quickly testing repository changes
|
|
without directly overwriting the real repository.
|
|
"""
|
|
repo_path = ServerSystemSettingsPlugin.get_repository_remote_path(
|
|
system_settings, override_repository_id)
|
|
|
|
sys_settings_plugin_id = \
|
|
etpConst['system_settings_plugins_ids']['server_plugin']
|
|
srv_data = system_settings[sys_settings_plugin_id]['server']
|
|
repo_data = srv_data['repositories'][repository_id]
|
|
repo_data['override_database_remote_path'] = repo_path
|
|
|
|
|
|
class ServerFatscopeSystemSettingsPlugin(SystemSettingsPlugin):
|
|
|
|
def repos_parser(self, sys_set):
|
|
|
|
cached = getattr(self, '_repos_data', None)
|
|
if cached is not None:
|
|
return cached
|
|
|
|
data = {}
|
|
srv_plug_id = etpConst['system_settings_plugins_ids']['server_plugin']
|
|
# if support is not enabled, don't waste time scanning files
|
|
srv_parser_data = sys_set[srv_plug_id]['server']
|
|
if not srv_parser_data['exp_based_scope']:
|
|
return data
|
|
|
|
# get expiration-based packages removal data from config files
|
|
for repoid in srv_parser_data['repositories']:
|
|
|
|
# filter out system repository if community repository
|
|
# mode is enabled
|
|
if repoid == etpConst['clientserverrepoid']:
|
|
continue
|
|
|
|
idpackages = set()
|
|
exp_fp = self._helper._get_local_exp_based_pkgs_rm_whitelist_file(
|
|
repoid)
|
|
try:
|
|
dbconn = self._helper.open_server_repository(
|
|
repoid, just_reading = True)
|
|
except RepositoryError:
|
|
# ignore
|
|
continue
|
|
|
|
if os.access(exp_fp, os.R_OK) and os.path.isfile(exp_fp):
|
|
pkgs = entropy.tools.generic_file_content_parser(
|
|
exp_fp, encoding = etpConst['conf_encoding'])
|
|
if '*' in pkgs: # wildcard support
|
|
idpackages.add(-1)
|
|
else:
|
|
for pkg in pkgs:
|
|
idpackage, rc_match = dbconn.atomMatch(pkg)
|
|
if rc_match:
|
|
continue
|
|
idpackages.add(idpackage)
|
|
|
|
data[repoid] = idpackages
|
|
|
|
self._repos_data = data
|
|
return data
|
|
|
|
class ServerFakeClientSystemSettingsPlugin(SystemSettingsPlugin):
|
|
|
|
def fake_cli_parser(self, sys_set):
|
|
"""
|
|
This is just fake, doesn't bring any new metadata but just tweak
|
|
Entropy client ones.
|
|
"""
|
|
data = {}
|
|
srv_plug_id = etpConst['system_settings_plugins_ids']['server_plugin']
|
|
# if support is not enabled, don't waste time scanning files
|
|
srv_parser_data = sys_set[srv_plug_id]['server']
|
|
|
|
# now setup fake Entropy Client repositories, so that Entropy Server
|
|
# can use Entropy Client interfaces transparently
|
|
srv_repodata = srv_parser_data['repositories']
|
|
cli_repodata = sys_set['repositories']
|
|
# remove unavailable server repos in client metadata first
|
|
cli_repodata['available'].clear()
|
|
|
|
for repoid, repo_data in srv_repodata.items():
|
|
|
|
try:
|
|
# we must skip the repository validation because
|
|
# repositories have been already validated.
|
|
# moreover, the system repository_id might not be
|
|
# valid (__system__). But still, this is the wanted
|
|
# behaviour.
|
|
xxx, my_data = sys_set._analyze_client_repo_string(
|
|
"repository = %s|%s|http://--fake--|http://--fake--" \
|
|
% (repoid, repo_data['description'],),
|
|
_skip_repository_validation=True)
|
|
except AttributeError as err:
|
|
# yeah, at least let stderr know.
|
|
sys.stderr.write(repr(err) + "\n")
|
|
continue # sorry!
|
|
|
|
my_data['repoid'] = repoid
|
|
if '__temporary__' in repo_data:
|
|
# fake repositories, temp ones
|
|
# can't go into Entropy Client, they miss
|
|
# 'database_dir' and other metadata
|
|
my_data['dbpath'] = None
|
|
my_data['__temporary__'] = repo_data['__temporary__']
|
|
my_data['dbrevision'] = 0
|
|
else:
|
|
my_data['dbpath'] = self._helper._get_local_repository_dir(
|
|
repoid)
|
|
my_data['dbrevision'] = \
|
|
self._helper.local_repository_revision(
|
|
repoid)
|
|
cli_repodata['available'][repoid] = my_data
|
|
|
|
cli_repodata['default_repository'] = \
|
|
srv_parser_data['default_repository_id']
|
|
|
|
del cli_repodata['order'][:]
|
|
if srv_parser_data['base_repository_id'] is not None:
|
|
cli_repodata['order'].append(srv_parser_data['base_repository_id'])
|
|
for repoid in sorted(srv_repodata):
|
|
if repoid not in cli_repodata['order']:
|
|
cli_repodata['order'].append(repoid)
|
|
|
|
return data
|
|
|
|
class ServerQAInterfacePlugin(QAInterfacePlugin):
|
|
|
|
def __init__(self, entropy_server_instance):
|
|
self._server = entropy_server_instance
|
|
|
|
def __check_package_using_spm(self, package_path):
|
|
|
|
spm_class = get_spm_class()
|
|
spm_rc, spm_msg = spm_class.execute_qa_tests(package_path)
|
|
|
|
if spm_rc == 0:
|
|
return True
|
|
sys.stderr.write("QA Error: " + spm_msg + "\n")
|
|
sys.stderr.flush()
|
|
return False
|
|
|
|
def __extract_edb_analyze_metadata(self, package_path):
|
|
|
|
def _is_supported(keywords):
|
|
for arch in etpConst['keywords']:
|
|
if arch in keywords:
|
|
return True
|
|
return False
|
|
|
|
tmp_fd, tmp_f = tempfile.mkstemp(prefix = 'entropy.server')
|
|
dbc = None
|
|
try:
|
|
found_edb = entropy.tools.dump_entropy_metadata(package_path, tmp_f)
|
|
if not found_edb:
|
|
return False
|
|
dbc = self._server._open_temp_repository("test", temp_file = tmp_f,
|
|
initialize = False)
|
|
for package_id in dbc.listAllPackageIds():
|
|
# NOTE: content is tested in entropy.qa builtin package test
|
|
# test content safety
|
|
dbc.retrieveContentSafety(package_id)
|
|
# test keywords
|
|
keywords = dbc.retrieveKeywords(package_id)
|
|
if not _is_supported(keywords):
|
|
atom = dbc.retrieveAtom(package_id)
|
|
# big PHAT warning !!
|
|
self._server.output(darkred("~"*40), level = "warning")
|
|
self._server.output("[%s, %s] %s" % (
|
|
brown(os.path.basename(package_path)), teal(atom),
|
|
purple(_("package has no keyword set, it will be masked !"))),
|
|
level = "warning", header = darkred(" !!! "))
|
|
self._server.output(darkred("~"*40), level = "warning")
|
|
time.sleep(10)
|
|
finally:
|
|
if dbc is not None:
|
|
dbc.close()
|
|
os.close(tmp_fd)
|
|
|
|
return True
|
|
|
|
def get_tests(self):
|
|
return [self.__check_package_using_spm,
|
|
self.__extract_edb_analyze_metadata]
|
|
|
|
def get_id(self):
|
|
return SERVER_QA_PLUGIN
|
|
|
|
|
|
class ServerConfigurationFiles(ConfigurationFiles):
|
|
|
|
"""
|
|
Subclass Entropy Client version in order to return
|
|
our repository identifiers
|
|
"""
|
|
|
|
@property
|
|
def _repository_ids(self):
|
|
"""
|
|
Return a the list of repository identifiers the object
|
|
is using.
|
|
"""
|
|
return self._entropy.repositories()
|
|
|
|
|
|
class Server(Client):
|
|
|
|
# Entropy Server cache directory, mainly used for storing commit changes
|
|
CACHE_DIR = os.path.join(etpConst['entropyworkdir'], "server_cache")
|
|
|
|
# SystemSettings class variables
|
|
SYSTEM_SETTINGS_PLG_ID = etpConst['system_settings_plugins_ids']['server_plugin']
|
|
|
|
def init_singleton(self, default_repository = None, save_repository = False,
|
|
fake_default_repo = False, fake_default_repo_id = None,
|
|
fake_default_repo_desc = None, handle_uninitialized = True,
|
|
**kwargs):
|
|
|
|
if fake_default_repo_desc is None:
|
|
fake_default_repo_desc = 'this is a fake repository'
|
|
self.__instance_destroyed = False
|
|
|
|
self._cacher = EntropyCacher()
|
|
# settings
|
|
self._memory_db_srv_instances = {}
|
|
self._treeupdates_repos = set()
|
|
self._server_dbcache = {}
|
|
self._settings = SystemSettings()
|
|
etpSys['serverside'] = True
|
|
self.fake_default_repo = fake_default_repo
|
|
self.fake_default_repo_id = fake_default_repo_id
|
|
self._indexing = False
|
|
self.xcache = False
|
|
self.Mirrors = None
|
|
self._settings_to_backup = []
|
|
self._save_repository = save_repository
|
|
self._sync_lock_cache = set()
|
|
|
|
self.sys_settings_fake_cli_plugin_id = \
|
|
etpConst['system_settings_plugins_ids']['server_plugin_fake_client']
|
|
self.sys_settings_fatscope_plugin_id = \
|
|
etpConst['system_settings_plugins_ids']['server_plugin_fatscope']
|
|
|
|
# create our SystemSettings plugin
|
|
with self._settings:
|
|
self.sys_settings_plugin = ServerSystemSettingsPlugin(
|
|
Server.SYSTEM_SETTINGS_PLG_ID, self)
|
|
self._settings.add_plugin(self.sys_settings_plugin)
|
|
|
|
# Fatscope support SystemSettings plugin
|
|
self.sys_settings_fatscope_plugin = \
|
|
ServerFatscopeSystemSettingsPlugin(
|
|
self.sys_settings_fatscope_plugin_id, self)
|
|
self._settings.add_plugin(self.sys_settings_fatscope_plugin)
|
|
|
|
# Fatscope support SystemSettings plugin
|
|
self.sys_settings_fake_cli_plugin = \
|
|
ServerFakeClientSystemSettingsPlugin(
|
|
self.sys_settings_fake_cli_plugin_id, self)
|
|
self._settings.add_plugin(self.sys_settings_fake_cli_plugin)
|
|
|
|
# setup fake repository
|
|
if fake_default_repo:
|
|
default_repository = fake_default_repo_id
|
|
self._init_generic_memory_server_repository(
|
|
fake_default_repo_id,
|
|
fake_default_repo_desc, set_as_default = True)
|
|
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
self._repository = default_repository
|
|
if self._repository is None:
|
|
self._repository = srv_set['default_repository_id']
|
|
|
|
if not fake_default_repo:
|
|
if self._repository in srv_set['repositories']:
|
|
try:
|
|
self._ensure_paths(self._repository)
|
|
except OSError as err:
|
|
if err.errno != errno.EACCES:
|
|
raise
|
|
# sigh, ignore during init
|
|
|
|
# if repository is still None, fallback to internal
|
|
# fake repository. This way Entropy Server will work
|
|
# out of the box without any server.conf tweak
|
|
# (and eit bashcomp is happy)
|
|
if self._repository is None:
|
|
repository_id = "__builtin__"
|
|
self._init_generic_memory_server_repository(
|
|
repository_id, "Built-in fallback fake repository",
|
|
set_as_default=True)
|
|
self._repository = repository_id
|
|
|
|
if self._repository not in srv_set['repositories']:
|
|
raise PermissionDenied("PermissionDenied: %s %s" % (
|
|
self._repository,
|
|
_("repository not configured"),
|
|
)
|
|
)
|
|
if etpConst['clientserverrepoid'] == self._repository:
|
|
raise PermissionDenied("PermissionDenied: %s %s" % (
|
|
etpConst['clientserverrepoid'],
|
|
_("protected repository id, can't use this, sorry dude..."),
|
|
)
|
|
)
|
|
|
|
self.switch_default_repository(
|
|
self._repository, handle_uninitialized=handle_uninitialized)
|
|
# initialize Entropy Client superclass
|
|
if "installed_repo" not in kwargs:
|
|
kwargs["installed_repo"] = False
|
|
if "repo_validation" not in kwargs:
|
|
kwargs["repo_validation"] = False
|
|
Client.init_singleton(self,
|
|
indexing = self._indexing,
|
|
xcache = self.xcache,
|
|
**kwargs
|
|
)
|
|
|
|
def destroy(self, _from_shutdown = False):
|
|
"""
|
|
Destroy this singleton instance.
|
|
"""
|
|
self.__instance_destroyed = True
|
|
Client.close_repositories(self, mask_clear = False)
|
|
Client.destroy(self, _from_shutdown = _from_shutdown)
|
|
|
|
if not _from_shutdown:
|
|
plug_id2 = self.sys_settings_fake_cli_plugin_id
|
|
plug_id1 = self.sys_settings_fatscope_plugin_id
|
|
plug_id = Server.SYSTEM_SETTINGS_PLG_ID
|
|
# reverse insert order
|
|
plugs = [plug_id2, plug_id1, plug_id]
|
|
for plug in plugs:
|
|
if plug is None:
|
|
continue
|
|
if not self._settings.has_plugin(plug):
|
|
continue
|
|
self._settings.remove_plugin(plug)
|
|
|
|
self.close_repositories()
|
|
|
|
def is_destroyed(self):
|
|
"""
|
|
Return whether the singleton instance is destroyed.
|
|
"""
|
|
return self.__instance_destroyed
|
|
|
|
def _get_branch_from_download_relative_uri(self, db_download_uri):
|
|
return db_download_uri.split("/")[2]
|
|
|
|
def _swap_branch_in_download_relative_uri(self, new_branch,
|
|
db_download_uri):
|
|
cur_branch = self._get_branch_from_download_relative_uri(
|
|
db_download_uri)
|
|
return db_download_uri.replace("/%s/" % (cur_branch,),
|
|
"/%s/" % (new_branch,))
|
|
|
|
def _get_basedir_pkg_listing(self, base_dir, extension, branch = None):
|
|
|
|
pkgs_dir_types = set(self._get_pkg_dir_names())
|
|
basedir_raw_content = []
|
|
entropy.tools.recursive_directory_relative_listing(
|
|
basedir_raw_content, base_dir)
|
|
|
|
pkg_ext = extension
|
|
pkg_list = [x for x in basedir_raw_content if x.endswith(pkg_ext)]
|
|
pkg_list = [x for x in pkg_list if \
|
|
x.split(os.path.sep)[0] in pkgs_dir_types]
|
|
|
|
if branch is not None:
|
|
branch_extractor = \
|
|
self._get_branch_from_download_relative_uri
|
|
pkg_list = [x for x in pkg_list if branch_extractor(x) == branch]
|
|
|
|
return pkg_list
|
|
|
|
def _get_pkg_dir_names(self):
|
|
return [etpConst['packagesrelativepath_basedir'],
|
|
etpConst['packagesrelativepath_basedir_nonfree'],
|
|
etpConst['packagesrelativepath_basedir_restricted']]
|
|
|
|
def _get_remote_repository_relative_path(self, repository_id):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return srv_set['repositories'][repository_id]['database_remote_path']
|
|
|
|
def _get_override_remote_repository_relative_path(self, repository_id):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
repo_data = srv_set['repositories'][repository_id]
|
|
return repo_data['override_database_remote_path']
|
|
|
|
def _get_local_repository_file(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasefile'])
|
|
|
|
def _get_local_store_directory(self, repository_id):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return srv_set['repositories'][repository_id]['store_dir']
|
|
|
|
def _get_local_upload_directory(self, repository_id):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return srv_set['repositories'][repository_id]['upload_basedir']
|
|
|
|
def _get_local_repository_base_directory(self, repository_id):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return srv_set['repositories'][repository_id]['repo_basedir']
|
|
|
|
def _get_local_repository_taint_file(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasetaintfile'])
|
|
|
|
def _get_local_repository_revision_file(self, repository_id, branch = None):
|
|
return os.path.join(
|
|
self._get_local_repository_dir(repository_id, branch = branch),
|
|
etpConst['etpdatabaserevisionfile'])
|
|
|
|
def _get_local_repository_timestamp_file(self, repository_id,
|
|
branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasetimestampfile'])
|
|
|
|
def _get_local_repository_mask_file(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasemaskfile'])
|
|
|
|
def _get_local_repository_system_mask_file(self, repository_id,
|
|
branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasesytemmaskfile'])
|
|
|
|
def _get_local_repository_licensewhitelist_file(self, repository_id,
|
|
branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabaselicwhitelistfile'])
|
|
|
|
def _get_local_repository_mirrors_file(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasemirrorsfile'])
|
|
|
|
def _get_local_repository_fallback_mirrors_file(self, repository_id,
|
|
branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasefallbackmirrorsfile'])
|
|
|
|
def _get_local_repository_rss_file(self, repository_id, branch = None):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), srv_set['rss']['name'])
|
|
|
|
def _get_local_repository_changelog_file(self, repository_id,
|
|
branch = None):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['changelog_filename'])
|
|
|
|
def _get_local_repository_compressed_changelog_file(self, repository_id,
|
|
branch = None):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['changelog_filename_compressed'])
|
|
|
|
def _get_local_repository_rsslight_file(self, repository_id, branch = None):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), srv_set['rss']['light_name'])
|
|
|
|
def _get_local_repository_notice_board_file(self, repository_id,
|
|
branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['rss-notice-board'])
|
|
|
|
def _get_local_repository_treeupdates_file(self, repository_id,
|
|
branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabaseupdatefile'])
|
|
|
|
def _get_local_repository_compressed_metafiles_file(self, repository_id,
|
|
branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasemetafilesfile'])
|
|
|
|
def _get_local_repository_metafiles_not_found_file(self, repository_id,
|
|
branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasemetafilesnotfound'])
|
|
|
|
def _get_local_repository_gpg_signature_file(self, repository_id,
|
|
branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasegpgfile'])
|
|
|
|
def _get_local_exp_based_pkgs_rm_whitelist_file(self, repository_id,
|
|
branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabaseexpbasedpkgsrm'])
|
|
|
|
def _get_local_pkglist_file(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasepkglist'])
|
|
|
|
def _get_local_extra_pkglist_file(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabaseextrapkglist'])
|
|
|
|
def _get_local_database_sets_dir(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['confsetsdirname'])
|
|
|
|
def _get_local_post_branch_mig_script(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etp_post_branch_hop_script'])
|
|
|
|
def _get_local_post_branch_upg_script(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etp_post_branch_upgrade_script'])
|
|
|
|
def _get_local_post_repo_update_script(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etp_post_repo_update_script'])
|
|
|
|
def _get_local_critical_updates_file(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasecriticalfile'])
|
|
|
|
def _get_local_restricted_file(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabaserestrictedfile'])
|
|
|
|
def _get_local_repository_keywords_file(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasekeywordsfile'])
|
|
|
|
def _get_local_repository_webserv_file(self, repository_id, branch = None):
|
|
return os.path.join(self._get_local_repository_dir(repository_id,
|
|
branch = branch), etpConst['etpdatabasewebservicesfile'])
|
|
|
|
def _get_local_repository_dir(self, repository_id, branch = None):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
if branch is None:
|
|
branch = self._settings['repositories']['branch']
|
|
return os.path.join(
|
|
srv_set['repositories'][repository_id]['database_dir'], branch)
|
|
|
|
def _get_missing_dependencies_blacklist_file(self, repository_id,
|
|
branch = None):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
if branch is None:
|
|
branch = self._settings['repositories']['branch']
|
|
return os.path.join(
|
|
srv_set['repositories'][repository_id]['database_dir'],
|
|
branch, etpConst['etpdatabasemissingdepsblfile'])
|
|
|
|
def _get_repository_lockfile(self, repository_id):
|
|
return os.path.join(self._get_local_repository_dir(repository_id),
|
|
etpConst['etpdatabaselockfile'])
|
|
|
|
def _get_repository_download_lockfile(self, repository_id):
|
|
return os.path.join(self._get_local_repository_dir(repository_id),
|
|
etpConst['etpdatabasedownloadlockfile'])
|
|
|
|
def _create_local_repository_download_lockfile(self, repository_id):
|
|
lock_file = self._get_repository_download_lockfile(repository_id)
|
|
enc = etpConst['conf_encoding']
|
|
with codecs.open(lock_file, "w", encoding=enc) as f_lock:
|
|
f_lock.write("download locked")
|
|
f_lock.flush()
|
|
|
|
def _create_local_repository_lockfile(self, repository_id):
|
|
lock_file = self._get_repository_lockfile(repository_id)
|
|
enc = etpConst['conf_encoding']
|
|
with codecs.open(lock_file, "w", encoding=enc) as f_lock:
|
|
f_lock.write("database locked")
|
|
f_lock.flush()
|
|
|
|
def _remove_local_repository_lockfile(self, repository_id):
|
|
lock_file = self._get_repository_lockfile(repository_id)
|
|
try:
|
|
os.remove(lock_file)
|
|
except OSError:
|
|
pass
|
|
|
|
def _remove_local_repository_download_lockfile(self, repository_id):
|
|
lock_file = self._get_repository_download_lockfile(repository_id)
|
|
try:
|
|
os.remove(lock_file)
|
|
except OSError:
|
|
pass
|
|
|
|
@staticmethod
|
|
def _get_conf_dep_rewrite_file():
|
|
packages_dir = SystemSettings.packages_config_directory()
|
|
return os.path.join(packages_dir, "packages.server.dep_rewrite")
|
|
|
|
@staticmethod
|
|
def _get_conf_dep_blacklist_file():
|
|
packages_dir = SystemSettings.packages_config_directory()
|
|
return os.path.join(packages_dir, "packages.server.dep_blacklist")
|
|
|
|
@staticmethod
|
|
def _get_conf_qa_sets_file():
|
|
packages_dir = SystemSettings.packages_config_directory()
|
|
return os.path.join(packages_dir, "packages.server.sets")
|
|
|
|
def complete_remote_package_relative_path(self, pkg_rel_url, repository_id):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return os.path.join(
|
|
srv_set['repositories'][repository_id]['remote_repo_basedir'],
|
|
pkg_rel_url)
|
|
|
|
def complete_local_upload_package_path(self, pkg_rel_url, repository_id):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return os.path.join(
|
|
srv_set['repositories'][repository_id]['upload_basedir'],
|
|
pkg_rel_url)
|
|
|
|
def complete_local_package_path(self, pkg_rel_url, repository_id):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return os.path.join(
|
|
srv_set['repositories'][repository_id]['repo_basedir'],
|
|
pkg_rel_url)
|
|
|
|
def remote_repository_mirrors(self, repository_id):
|
|
"""
|
|
Return a list of remote repository mirrors (database) for given
|
|
repository.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@return: list of available repository mirrors
|
|
@rtype: list
|
|
@raise KeyError: if repository_id is invalid
|
|
"""
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return srv_set['repositories'][repository_id]['repo_mirrors'][:]
|
|
|
|
def remote_packages_mirrors(self, repository_id):
|
|
"""
|
|
Return a list of remote packages mirrors (packages) for given
|
|
repository.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@return: list of available packages mirrors
|
|
@rtype: list
|
|
@raise KeyError: if repository_id is invalid
|
|
"""
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return srv_set['repositories'][repository_id]['pkg_mirrors'][:]
|
|
|
|
def local_repository_revision(self, repository_id):
|
|
"""
|
|
Return local repository revision.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@return: the actual repository revision
|
|
@rtype: int
|
|
"""
|
|
dbrev_file = self._get_local_repository_revision_file(repository_id)
|
|
if not os.path.isfile(dbrev_file):
|
|
return 0
|
|
|
|
enc = etpConst['conf_encoding']
|
|
with codecs.open(dbrev_file, "r", encoding=enc) as f_rev:
|
|
rev = f_rev.readline().strip()
|
|
try:
|
|
rev = int(rev)
|
|
except ValueError:
|
|
self.output(
|
|
"[%s] %s: %s - %s" % (
|
|
darkgreen(repository_id),
|
|
blue(_("invalid repository revision")),
|
|
bold(rev),
|
|
blue(_("defaulting to 0")),
|
|
),
|
|
importance = 2,
|
|
level = "error",
|
|
header = darkred(" !!! ")
|
|
)
|
|
rev = 0
|
|
return rev
|
|
|
|
def remote_repository_revision(self, repository_id):
|
|
"""
|
|
Return the highest repository revision available on mirrors for
|
|
given repository.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@return: remote repository revision
|
|
@rtype: int
|
|
"""
|
|
repo_status = self.Mirrors.remote_repository_status(repository_id)
|
|
remote_status = list(repo_status.items())
|
|
if not [x for x in remote_status if x[1]]:
|
|
return 0
|
|
return max([x[1] for x in remote_status])
|
|
|
|
def repositories(self):
|
|
"""
|
|
Return a list of available Entropy Server repositories.
|
|
|
|
@return: list of available Entropy Server repositories
|
|
@rtype: list
|
|
"""
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return sorted(srv_set['repositories'])
|
|
|
|
def repository(self):
|
|
"""
|
|
Return the current repository marked as default.
|
|
"""
|
|
return self._repository
|
|
|
|
|
|
def QA(self):
|
|
"""
|
|
Get Entropy QA Interface instance.
|
|
|
|
@return: Entropy QA Interface instance
|
|
@rtype: entropy.qa.QAInterface instance
|
|
"""
|
|
qa_plugin = ServerQAInterfacePlugin(self)
|
|
qa = Client.QA(self)
|
|
qa.add_plugin(qa_plugin)
|
|
return qa
|
|
|
|
def Spm(self):
|
|
"""
|
|
Get Source Package Manager interface instance.
|
|
|
|
#@return: Source Package Manager interface instance
|
|
@rtype: entropy.spm.plugins.skel.SpmPlugin based instance
|
|
"""
|
|
return get_spm(self)
|
|
|
|
def Spm_class(self):
|
|
"""
|
|
Get Source Package Manager interface class.
|
|
"""
|
|
return get_spm_class()
|
|
|
|
def ConfigurationUpdates(self):
|
|
"""
|
|
Return Entropy Configuration File Updates management object.
|
|
"""
|
|
return ConfigurationUpdates(
|
|
self, _config_class=ServerConfigurationFiles)
|
|
|
|
def Transceiver(self, uri):
|
|
"""
|
|
Get EntropyTransceiver interface instance.
|
|
|
|
@param uri: EntropyTransceiver URI
|
|
@type uri: string
|
|
@return: EntropyTransceiver instance
|
|
@rtype: entropy.transceivers.EntropyTransceiver
|
|
"""
|
|
txc = EntropyTransceiver(uri)
|
|
txc.set_output_interface(self)
|
|
return txc
|
|
|
|
def _sync_package_sets(self, entropy_repository):
|
|
"""
|
|
Synchronize repository package sets.
|
|
|
|
@param entropy_repository: EntropyRepositoryBase object
|
|
@type entropy_repository: entropy.db.skel.EntropyRepositoryBase
|
|
"""
|
|
repository_id = entropy_repository.repository_id()
|
|
self.output(
|
|
"[%s|%s] %s" % (
|
|
blue(repository_id),
|
|
red(_("repository")),
|
|
blue(_("syncing package sets")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" @@ ")
|
|
)
|
|
cur_sets = entropy_repository.retrievePackageSets()
|
|
sys_sets = self._get_configured_package_sets(repository_id)
|
|
if cur_sets != sys_sets:
|
|
self._update_package_sets(repository_id, entropy_repository)
|
|
# NOTE: this is called by the commit hook plugin, keep no_plugins=True!
|
|
entropy_repository.commit(no_plugins = True)
|
|
|
|
def sets_available(self, *args, **kwargs):
|
|
sets = Client.Sets(self)
|
|
return sets.available(*args, **kwargs)
|
|
|
|
def sets_search(self, *args, **kwargs):
|
|
sets = Client.Sets(self)
|
|
return sets.search(*args, **kwargs)
|
|
|
|
def sets_match(self, *args, **kwargs):
|
|
sets = Client.Sets(self)
|
|
return sets.match(*args, **kwargs)
|
|
|
|
def atom_match(self, *args, **kwargs):
|
|
# disable masked packages for server-side repos
|
|
kwargs['mask_filter'] = False
|
|
return Client.atom_match(self, *args, **kwargs)
|
|
|
|
def _match_packages(self, repository_id, packages):
|
|
|
|
dbconn = self.open_server_repository(repository_id, read_only = True,
|
|
no_upload = True)
|
|
if ("world" in packages) or not packages:
|
|
return dbconn.listAllPackageIds(), True
|
|
else:
|
|
idpackages = set()
|
|
for package in packages:
|
|
matches = dbconn.atomMatch(package, multiMatch = True)
|
|
if matches[1] == 0:
|
|
idpackages |= matches[0]
|
|
else:
|
|
mytxt = "%s: %s: %s" % (
|
|
red(_("Attention")),
|
|
blue(_("cannot match")),
|
|
bold(package),
|
|
)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkred(" !!! ")
|
|
)
|
|
return idpackages, False
|
|
|
|
def mask_packages(self, repository_id, packages):
|
|
"""
|
|
Mask given package dependencies for given repository, if any (otherwise
|
|
use default one).
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@param packages: list of package dependency strings
|
|
@type packages: list
|
|
@return: mask status, True if ok, False if not
|
|
@rtype: bool
|
|
"""
|
|
mask_file = self._get_local_repository_mask_file(repository_id)
|
|
current_packages = []
|
|
|
|
if os.path.isfile(mask_file) and os.access(mask_file, os.R_OK):
|
|
current_packages = entropy.tools.generic_file_content_parser(
|
|
mask_file, comment_tag = "##", filter_comments = False,
|
|
encoding = etpConst['conf_encoding'])
|
|
# this is untrusted input, it's fine because that config file is
|
|
# untrusted too
|
|
current_packages.extend(packages)
|
|
|
|
mask_file_tmp = mask_file + ".mask_packages_tmp"
|
|
enc = etpConst['conf_encoding']
|
|
with codecs.open(mask_file_tmp, "w", encoding=enc) as mask_f:
|
|
for package in current_packages:
|
|
mask_f.write(package + "\n")
|
|
mask_f.flush()
|
|
os.rename(mask_file_tmp, mask_file)
|
|
|
|
return True
|
|
|
|
def unmask_packages(self, repository_id, packages):
|
|
"""
|
|
Unmask given package dependencies for given repository, if any
|
|
(otherwise use default one).
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@param packages: list of package dependency strings
|
|
@type packages: list
|
|
@return: mask status, True if ok, False if not
|
|
@rtype: bool
|
|
"""
|
|
mask_file = self._get_local_repository_mask_file(repository_id)
|
|
current_packages = []
|
|
|
|
if os.path.isfile(mask_file) and os.access(mask_file, os.R_OK):
|
|
current_packages = entropy.tools.generic_file_content_parser(
|
|
mask_file, comment_tag = "##", filter_comments = False,
|
|
encoding = etpConst['conf_encoding'])
|
|
|
|
def mask_filter(package):
|
|
if package.startswith("#"):
|
|
# comment, always valid
|
|
return True
|
|
in_file_pkg_match = self.atom_match(package)
|
|
for req_package in packages:
|
|
if package == req_package:
|
|
# of course remove if it's equal
|
|
return False
|
|
req_package_match = self.atom_match(req_package)
|
|
if req_package_match == in_file_pkg_match:
|
|
# drop it, they point to the same package match
|
|
return False
|
|
return True
|
|
|
|
current_packages = list(filter(mask_filter, current_packages))
|
|
|
|
mask_file_tmp = mask_file + ".mask_packages_tmp"
|
|
enc = etpConst['conf_encoding']
|
|
with codecs.open(mask_file_tmp, "w", encoding=enc) as mask_f:
|
|
for package in current_packages:
|
|
mask_f.write(package + "\n")
|
|
mask_f.flush()
|
|
os.rename(mask_file_tmp, mask_file)
|
|
|
|
return True
|
|
|
|
def initialize_repository(self, repository_id, ask = True):
|
|
"""
|
|
Initialize (and wipe all data!) given repository to empty status.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@keyword ask: ask before making any change?
|
|
@type ask: bool
|
|
@return: execution status (0 = fine)
|
|
@rtype: int
|
|
"""
|
|
self.output(
|
|
"[%s] %s..." % (
|
|
purple(repository_id), darkgreen(_("initializing repository")),
|
|
),
|
|
importance = 1,
|
|
level = "info", header = darkgreen(" * ")
|
|
)
|
|
self.close_repositories()
|
|
|
|
rc_question = self.ask_question(
|
|
"[%s] %s" % (
|
|
purple(repository_id),
|
|
teal(_("do you really want to initialize this repository ?"))
|
|
)
|
|
)
|
|
if rc_question == _("No"):
|
|
return 1
|
|
|
|
try:
|
|
os.remove(self._get_local_repository_file(repository_id))
|
|
except OSError as err:
|
|
if err.errno != errno.ENOENT:
|
|
raise
|
|
|
|
# initialize
|
|
dbconn = self.open_server_repository(repository_id, read_only = False,
|
|
no_upload = True, is_new = True)
|
|
dbconn.initializeRepository()
|
|
dbconn.commit()
|
|
|
|
# create the store directory
|
|
store_dir = self._get_local_store_directory(repository_id)
|
|
if not os.path.isdir(store_dir):
|
|
try:
|
|
os.makedirs(store_dir)
|
|
except (IOError, OSError) as err:
|
|
self.output(
|
|
"%s: %s" % (_("Cannot create store directory"), err),
|
|
header=brown(" !!! "),
|
|
importance=1,
|
|
level="error")
|
|
return 1
|
|
|
|
# create the upload directory
|
|
upload_dir = self._get_local_upload_directory(repository_id)
|
|
if not os.path.isdir(upload_dir):
|
|
try:
|
|
os.makedirs(upload_dir)
|
|
except (IOError, OSError) as err:
|
|
self.output(
|
|
"%s: %s" % (_("Cannot create upload directory"), err),
|
|
header=brown(" !!! "),
|
|
importance=1,
|
|
level="error")
|
|
return 1
|
|
|
|
return 0
|
|
|
|
def tag_packages(self, package_matches, package_tag, ask = True):
|
|
"""
|
|
Change version tag for given package matches.
|
|
|
|
@param package_matches: list of Entropy package matches
|
|
@type package_matches: list
|
|
@param package_tag: new Entropy package tag string
|
|
@type package_tag: string
|
|
@return: execution status (0 = fine)
|
|
@rtype: int
|
|
"""
|
|
# repo
|
|
# idpackages
|
|
|
|
try:
|
|
package_tag = str(package_tag)
|
|
if " " in package_tag:
|
|
raise ValueError
|
|
except (UnicodeDecodeError, UnicodeEncodeError, ValueError,):
|
|
self.output(
|
|
"%s: %s" % (
|
|
blue(_("Invalid tag specified")),
|
|
package_tag,
|
|
),
|
|
importance = 1, level = "error", header = darkred(" !! ")
|
|
)
|
|
return 1
|
|
|
|
pkg_map = {}
|
|
for pkg_id, pkg_repo in package_matches:
|
|
obj = pkg_map.setdefault(pkg_repo, [])
|
|
obj.append(pkg_id)
|
|
|
|
for pkg_repo in sorted(pkg_map.keys()):
|
|
switched = self._move_packages(pkg_map[pkg_repo], pkg_repo,
|
|
pkg_repo, ask = ask, do_copy = True, new_tag = package_tag)
|
|
if not switched:
|
|
return 1
|
|
return 0
|
|
|
|
def flushback_packages(self, repository_id, from_branches, ask = True):
|
|
"""
|
|
When creating a new branch, for space reasons, packages are not
|
|
moved to a new location. This works fine until old branch is removed.
|
|
To avoid inconsistences, before deciding to do that, all the packages
|
|
in the old branch should be flushed back to the the currently configured
|
|
branch.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@param from_branches: list of branches to move packages from
|
|
@type from_branches: list
|
|
@keyword ask: ask before making any change?
|
|
@type ask: bool
|
|
@return execution status (0 = fine)
|
|
@rtype: int
|
|
"""
|
|
branch = self._settings['repositories']['branch']
|
|
|
|
if branch in from_branches:
|
|
from_branches = [x for x in from_branches if x != branch]
|
|
|
|
self.output(
|
|
"[%s=>%s|%s] %s" % (
|
|
darkgreen(', '.join(from_branches)),
|
|
darkred(branch),
|
|
brown(repository_id),
|
|
blue(_("flushing back selected packages from branches")),
|
|
),
|
|
importance = 2,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
|
|
dbconn = self.open_server_repository(repository_id, read_only = True,
|
|
no_upload = True)
|
|
|
|
idpackage_map = dict(((x, [],) for x in from_branches))
|
|
idpackages = dbconn.listAllPackageIds(order_by = 'atom')
|
|
for idpackage in idpackages:
|
|
download_url = dbconn.retrieveDownloadURL(idpackage)
|
|
url_br = self._get_branch_from_download_relative_uri(
|
|
download_url)
|
|
if url_br in from_branches:
|
|
idpackage_map[url_br].append(idpackage)
|
|
|
|
mapped_branches = [x for x in idpackage_map if idpackage_map[x]]
|
|
if not mapped_branches:
|
|
self.output(
|
|
"[%s=>%s|%s] %s !" % (
|
|
darkgreen(', '.join(from_branches)),
|
|
darkred(branch),
|
|
brown(repository_id),
|
|
blue(_("nothing to do")),
|
|
),
|
|
importance = 0,
|
|
level = "warning",
|
|
header = blue(" @@ ")
|
|
)
|
|
return 0
|
|
|
|
|
|
all_fine = True
|
|
tmp_down_dir = tempfile.mkdtemp(prefix = "entropy.server")
|
|
|
|
download_queue = {}
|
|
dbconn = self.open_server_repository(repository_id, read_only = False,
|
|
no_upload = True)
|
|
|
|
def generate_queue(branch, repository_id, from_branch, down_q,
|
|
idpackage_map):
|
|
|
|
self.output(
|
|
"[%s=>%s|%s] %s" % (
|
|
darkgreen(from_branch),
|
|
darkred(branch),
|
|
brown(repository_id),
|
|
brown(_("these are the packages that will be flushed")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" @@ ")
|
|
)
|
|
|
|
|
|
for idpackage in idpackage_map[from_branch]:
|
|
atom = dbconn.retrieveAtom(idpackage)
|
|
self.output(
|
|
"[%s=>%s|%s] %s" % (
|
|
darkgreen(from_branch),
|
|
darkred(branch),
|
|
brown(repository_id),
|
|
purple(atom),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = blue(" # ")
|
|
)
|
|
pkg_fp = os.path.basename(dbconn.retrieveDownloadURL(idpackage))
|
|
pkg_fp = os.path.join(tmp_down_dir, pkg_fp)
|
|
down_q.append((pkg_fp, idpackage,))
|
|
|
|
|
|
for from_branch in sorted(mapped_branches):
|
|
|
|
download_queue[from_branch] = []
|
|
all_fine = False
|
|
generate_queue(branch, repository_id, from_branch,
|
|
download_queue[from_branch], idpackage_map)
|
|
|
|
if ask:
|
|
rc_question = self.ask_question(
|
|
_("Would you like to continue ?"))
|
|
if rc_question == _("No"):
|
|
continue
|
|
|
|
for uri in self.remote_packages_mirrors(repository_id):
|
|
|
|
crippled_uri = EntropyTransceiver.get_uri_name(uri)
|
|
|
|
queue_map = {}
|
|
|
|
for pkg_fp, idpackage in download_queue[from_branch]:
|
|
down_url = dbconn.retrieveDownloadURL(idpackage)
|
|
down_rel = self.complete_remote_package_relative_path(
|
|
down_url, repository_id)
|
|
down_rel_dir = os.path.dirname(down_rel)
|
|
obj = queue_map.setdefault(down_rel_dir, [])
|
|
obj.append(pkg_fp)
|
|
|
|
errors = False
|
|
m_fine_uris = set()
|
|
m_broken_uris = set()
|
|
|
|
for down_rel_dir, downloader_queue in queue_map.items():
|
|
|
|
downloader = self.Mirrors.TransceiverServerHandler(
|
|
self,
|
|
[uri],
|
|
downloader_queue,
|
|
critical_files = downloader_queue,
|
|
txc_basedir = down_rel_dir,
|
|
local_basedir = tmp_down_dir,
|
|
download = True,
|
|
repo = repository_id
|
|
)
|
|
xerrors, xm_fine_uris, xm_broken_uris = downloader.go()
|
|
if xerrors:
|
|
errors = True
|
|
m_fine_uris.update(xm_fine_uris)
|
|
m_broken_uris.update(xm_broken_uris)
|
|
|
|
if not errors:
|
|
for downloaded_path, idpackage in \
|
|
download_queue[from_branch]:
|
|
|
|
self.output(
|
|
"[%s=>%s|%s|%s] %s: %s" % (
|
|
darkgreen(from_branch),
|
|
darkred(branch),
|
|
brown(repository_id),
|
|
dbconn.retrieveAtom(idpackage),
|
|
blue(_("checking package hash")),
|
|
darkgreen(os.path.basename(downloaded_path)),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" "),
|
|
back = True
|
|
)
|
|
|
|
md5hash = entropy.tools.md5sum(downloaded_path)
|
|
db_md5hash = dbconn.retrieveDigest(idpackage)
|
|
if md5hash != db_md5hash:
|
|
errors = True
|
|
self.output(
|
|
"[%s=>%s|%s|%s] %s: %s" % (
|
|
darkgreen(from_branch),
|
|
darkred(branch),
|
|
brown(repository_id),
|
|
dbconn.retrieveAtom(idpackage),
|
|
blue(_("hash does not match for")),
|
|
darkgreen(os.path.basename(downloaded_path)),
|
|
),
|
|
importance = 0,
|
|
level = "error",
|
|
header = brown(" ")
|
|
)
|
|
continue
|
|
|
|
if errors:
|
|
reason = _("wrong md5")
|
|
if m_broken_uris:
|
|
my_broken_uris = [
|
|
(EntropyTransceiver.get_uri_name(x), y,) \
|
|
for x, y in m_broken_uris]
|
|
reason = my_broken_uris[0][1]
|
|
|
|
self.output(
|
|
"[%s=>%s|%s] %s, %s: %s" % (
|
|
darkgreen(from_branch),
|
|
darkred(branch),
|
|
brown(repository_id),
|
|
blue(_("download errors")),
|
|
blue(_("reason")),
|
|
reason,
|
|
),
|
|
importance = 1,
|
|
level = "error",
|
|
header = darkred(" !!! ")
|
|
)
|
|
# continuing if possible
|
|
continue
|
|
|
|
all_fine = True
|
|
|
|
self.output(
|
|
"[%s=>%s|%s] %s: %s" % (
|
|
darkgreen(from_branch),
|
|
darkred(branch),
|
|
brown(repository_id),
|
|
blue(_("download completed successfully")),
|
|
darkgreen(crippled_uri),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = darkgreen(" * ")
|
|
)
|
|
|
|
if not all_fine:
|
|
self.output(
|
|
"[%s=>%s|%s] %s" % (
|
|
darkgreen(', '.join(from_branches)),
|
|
darkred(branch),
|
|
brown(repository_id),
|
|
blue(_("error downloading packages from mirrors")),
|
|
),
|
|
importance = 2,
|
|
level = "error",
|
|
header = darkred(" !!! ")
|
|
)
|
|
return 1
|
|
|
|
for from_branch in sorted(mapped_branches):
|
|
|
|
self.output(
|
|
"[%s=>%s|%s] %s: %s" % (
|
|
darkgreen(from_branch),
|
|
darkred(branch),
|
|
brown(repository_id),
|
|
blue(_("working on branch")),
|
|
darkgreen(from_branch),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" @@ ")
|
|
)
|
|
|
|
down_queue = download_queue[from_branch]
|
|
for package_path, idpackage in down_queue:
|
|
|
|
self.output(
|
|
"[%s=>%s|%s] %s: %s" % (
|
|
darkgreen(from_branch),
|
|
darkred(branch),
|
|
brown(repository_id),
|
|
blue(_("updating package")),
|
|
darkgreen(os.path.basename(package_path)),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" "),
|
|
back = True
|
|
)
|
|
|
|
# build new download url
|
|
download_url = dbconn.retrieveDownloadURL(idpackage)
|
|
download_url = \
|
|
self._swap_branch_in_download_relative_uri(
|
|
branch, download_url)
|
|
|
|
# move files to upload
|
|
new_package_path = self.complete_local_upload_package_path(
|
|
download_url, repository_id)
|
|
self._ensure_dir_path(os.path.dirname(new_package_path))
|
|
|
|
try:
|
|
os.rename(package_path, new_package_path)
|
|
except OSError as err:
|
|
if err.errno != errno.EXDEV:
|
|
raise
|
|
shutil.move(package_path, new_package_path)
|
|
|
|
# update database
|
|
dbconn.setDownloadURL(idpackage, download_url)
|
|
dbconn.commit()
|
|
dbconn.switchBranch(idpackage, branch)
|
|
dbconn.commit()
|
|
|
|
self.output(
|
|
"[%s=>%s|%s] %s: %s" % (
|
|
darkgreen(from_branch),
|
|
darkred(branch),
|
|
brown(repository_id),
|
|
blue(_("package flushed")),
|
|
darkgreen(os.path.basename(package_path)),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" ")
|
|
)
|
|
|
|
try:
|
|
os.rmdir(tmp_down_dir)
|
|
except OSError:
|
|
pass
|
|
|
|
return 0
|
|
|
|
def move_packages(self, package_ids, from_repository_id, to_repository_id,
|
|
ask = True, pull_dependencies = False):
|
|
"""
|
|
Move packages from a repository to another.
|
|
|
|
@param package_ids: list of package identifiers contained in
|
|
from_repository_id repository
|
|
@type package_ids: list
|
|
@param from_repository_id: source repository identifier
|
|
@type from_repository_id: string
|
|
@param to_repository_id: destination repository identifier
|
|
@type to_repository_id: string
|
|
@keyword ask: execute in interactive mode
|
|
@type ask: bool
|
|
@keyword pull_dependencies: also include reverse dependencies in
|
|
move
|
|
@type pull_dependencies: bool
|
|
@return: list (set) of moved package identifiers
|
|
@rtype: set
|
|
"""
|
|
return self._move_packages(package_ids, from_repository_id,
|
|
to_repository_id, ask = ask, pull_deps = pull_dependencies,
|
|
do_copy = False)
|
|
|
|
def copy_packages(self, package_ids, from_repository_id, to_repository_id,
|
|
ask = True, pull_dependencies = False):
|
|
"""
|
|
Copy packages from a repository to another.
|
|
|
|
@param package_ids: list of package identifiers contained in
|
|
from_repository_id repository
|
|
@type package_ids: list
|
|
@param from_repository_id: source repository identifier
|
|
@type from_repository_id: string
|
|
@param to_repository_id: destination repository identifier
|
|
@type to_repository_id: string
|
|
@keyword ask: execute in interactive mode
|
|
@type ask: bool
|
|
@keyword pull_dependencies: also include reverse dependencies in
|
|
copy
|
|
@type pull_dependencies: bool
|
|
@return: list (set) of copied package identifiers
|
|
@rtype: set
|
|
"""
|
|
return self._move_packages(package_ids, from_repository_id,
|
|
to_repository_id, ask = ask, pull_deps = pull_dependencies,
|
|
do_copy = True)
|
|
|
|
def _move_packages(self, package_ids, from_repository_id, to_repository_id,
|
|
ask = True, do_copy = False, new_tag = None, pull_deps = False):
|
|
"""
|
|
Move, copy or re-tag packages in repositories.
|
|
If do_copy is True, a copy is executed of package_ids from
|
|
from_repository_id to to_repository_id. If new_tag is not None,
|
|
and from_repository_id == to_repository_id a re-tag will be executed.
|
|
"""
|
|
|
|
switched = set()
|
|
my_matches = [(x, from_repository_id) for x in package_ids]
|
|
|
|
# avoid setting __default__ as default server repo
|
|
if etpConst['clientserverrepoid'] in (to_repository_id,
|
|
from_repository_id):
|
|
self.output(
|
|
"%s: %s" % (
|
|
blue(_("Cannot touch system repository")),
|
|
red(etpConst['clientserverrepoid']),
|
|
),
|
|
importance = 2, level = "warning", header = darkred(" @@ ")
|
|
)
|
|
return switched
|
|
|
|
if not my_matches and from_repository_id:
|
|
dbconn = self.open_server_repository(from_repository_id,
|
|
read_only = True, no_upload = True)
|
|
my_matches = set( \
|
|
[(x, from_repository_id) for x in \
|
|
dbconn.listAllPackageIds()]
|
|
)
|
|
|
|
mytxt = _("Preparing to move selected packages to")
|
|
if do_copy:
|
|
mytxt = _("Preparing to copy selected packages to")
|
|
self.output(
|
|
"%s %s:" % (
|
|
blue(mytxt),
|
|
red(to_repository_id),
|
|
),
|
|
importance = 2,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
self.output(
|
|
"%s: %s" % (
|
|
bold(_("Note")),
|
|
red(_("all old packages with conflicting scope will be " \
|
|
"removed from destination repo unless injected")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
|
|
new_tag_string = ''
|
|
if new_tag != None:
|
|
new_tag_string = "[%s: %s]" % (darkgreen(_("new tag")),
|
|
brown(new_tag),)
|
|
|
|
# open both repos here to make sure it's all fine with them
|
|
dbconn = self.open_server_repository(from_repository_id,
|
|
read_only = True, no_upload = True)
|
|
todbconn = self.open_server_repository(to_repository_id,
|
|
read_only = False, no_upload = True)
|
|
|
|
my_qa = self.QA()
|
|
branch = self._settings['repositories']['branch']
|
|
pull_deps_matches = []
|
|
for idpackage, repo in my_matches:
|
|
dbconn = self.open_server_repository(repo, read_only = True,
|
|
no_upload = True)
|
|
self.output(
|
|
"[%s=>%s|%s] %s " % (
|
|
darkgreen(repo),
|
|
darkred(to_repository_id),
|
|
brown(branch),
|
|
blue(dbconn.retrieveAtom(idpackage)),
|
|
) + new_tag_string,
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
|
|
# check if there are pkgs that are going to be overwritten
|
|
# and warn user about that.
|
|
from_name, from_category, from_slot, from_injected = \
|
|
dbconn.retrieveName(idpackage), \
|
|
dbconn.retrieveCategory(idpackage), \
|
|
dbconn.retrieveSlot(idpackage), \
|
|
dbconn.isInjected(idpackage)
|
|
to_rm_idpackages = todbconn.getPackagesToRemove(from_name,
|
|
from_category, from_slot, from_injected)
|
|
for to_rm_idpackage in to_rm_idpackages:
|
|
self.output(
|
|
" [=>%s|%s] %s" % (
|
|
darkred(to_repository_id),
|
|
bold(_("remove")),
|
|
blue(todbconn.retrieveAtom(to_rm_idpackage)),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = purple(" # ")
|
|
)
|
|
|
|
# do we want to pull in also package dependencies?
|
|
if pull_deps:
|
|
dep_matches = my_qa.get_deep_dependency_list(self,
|
|
(idpackage, repo), match_repo = (repo,))
|
|
revdep_matches = self.get_reverse_queue(dep_matches,
|
|
system_packages = False)
|
|
dep_matches.update(revdep_matches)
|
|
|
|
for dep_idpackage, dep_repo in dep_matches:
|
|
|
|
my_dep_match = (dep_idpackage, dep_repo,)
|
|
if my_dep_match in pull_deps_matches:
|
|
continue
|
|
if my_dep_match in my_matches:
|
|
continue
|
|
|
|
pull_deps_matches.append(my_dep_match)
|
|
dep_dbconn = self.open_server_repository(dep_repo,
|
|
read_only = True, no_upload = True)
|
|
dep_atom = dep_dbconn.retrieveAtom(dep_idpackage)
|
|
if my_dep_match in revdep_matches:
|
|
self.output(
|
|
"[%s|%s] %s" % (
|
|
brown(branch),
|
|
blue(_("reverse dependency")),
|
|
teal(dep_atom),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = purple(" >> ")
|
|
)
|
|
else:
|
|
self.output(
|
|
"[%s|%s] %s" % (
|
|
brown(branch),
|
|
blue(_("dependency")),
|
|
purple(dep_atom),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = purple(" >> ")
|
|
)
|
|
|
|
if pull_deps:
|
|
# put deps first!
|
|
my_matches = pull_deps_matches + [x for x in my_matches if x not \
|
|
in pull_deps_matches]
|
|
|
|
if ask:
|
|
rc_question = self.ask_question(_("Would you like to continue ?"))
|
|
if rc_question == _("No"):
|
|
return switched
|
|
|
|
package_ids_added = set()
|
|
for idpackage, repo in my_matches:
|
|
|
|
dbconn = self.open_server_repository(repo, read_only = False,
|
|
no_upload = True)
|
|
|
|
match_atom = dbconn.retrieveAtom(idpackage)
|
|
package_rel_path = dbconn.retrieveDownloadURL(idpackage)
|
|
|
|
self.output(
|
|
"[%s=>%s|%s] %s: %s" % (
|
|
darkgreen(repo),
|
|
darkred(to_repository_id),
|
|
brown(branch),
|
|
blue(_("switching")),
|
|
darkgreen(match_atom),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = red(" @@ "),
|
|
back = True
|
|
)
|
|
# move binary file
|
|
from_file = self.complete_local_package_path(package_rel_path,
|
|
repo)
|
|
if not os.path.isfile(from_file):
|
|
from_file = self.complete_local_upload_package_path(
|
|
package_rel_path, repo)
|
|
if not os.path.isfile(from_file):
|
|
self.output(
|
|
"[%s=>%s|%s] %s: %s -> %s" % (
|
|
darkgreen(repo),
|
|
darkred(to_repository_id),
|
|
brown(branch),
|
|
bold(_("cannot switch, package not found, skipping")),
|
|
darkgreen(match_atom),
|
|
red(from_file),
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkred(" !!! ")
|
|
)
|
|
continue
|
|
|
|
# we need to ask SpmPlugin to re-extract metadata from pkg file
|
|
# and grab the new "download" metadatum value using our
|
|
# license check callback. It has to be done here because
|
|
# we need the new path.
|
|
|
|
def _package_injector_check_license(pkg_data):
|
|
licenses = pkg_data['license'].split()
|
|
return self._is_pkg_free(to_repository_id, licenses)
|
|
|
|
def _package_injector_check_restricted(pkg_data):
|
|
pkgatom = entropy.dep.create_package_atom_string(
|
|
pkg_data['category'], pkg_data['name'], pkg_data['version'],
|
|
pkg_data['versiontag'])
|
|
return self._is_pkg_restricted(to_repository_id, pkgatom,
|
|
pkg_data['slot'])
|
|
|
|
# check if pkg is restricted
|
|
# and check if pkg is free, we must do this step in any case
|
|
# NOTE: it sucks!
|
|
tmp_data = self.Spm().extract_package_metadata(from_file,
|
|
license_callback = _package_injector_check_license,
|
|
restricted_callback = _package_injector_check_restricted)
|
|
# NOTE: since ~0.tbz2 << revision is lost, we need to trick
|
|
# the logic.
|
|
updated_package_rel_path = os.path.join(
|
|
os.path.dirname(tmp_data['download']),
|
|
os.path.basename(package_rel_path))
|
|
del tmp_data
|
|
|
|
to_file = self.complete_local_upload_package_path(
|
|
updated_package_rel_path, to_repository_id)
|
|
|
|
if new_tag != None:
|
|
|
|
match_category = dbconn.retrieveCategory(idpackage)
|
|
match_name = dbconn.retrieveName(idpackage)
|
|
match_version = dbconn.retrieveVersion(idpackage)
|
|
tagged_package_filename = \
|
|
entropy.dep.create_package_filename(
|
|
match_category, match_name, match_version, new_tag)
|
|
|
|
to_file = self.complete_local_upload_package_path(
|
|
updated_package_rel_path, to_repository_id)
|
|
# directly move to correct place, tag changed, so file name
|
|
to_file = os.path.join(os.path.dirname(to_file),
|
|
tagged_package_filename)
|
|
|
|
self._ensure_dir_path(os.path.dirname(to_file))
|
|
|
|
copy_data = [
|
|
(from_file, to_file,),
|
|
(from_file + etpConst['packagesexpirationfileext'],
|
|
to_file + etpConst['packagesexpirationfileext'],)
|
|
]
|
|
extra_downloads = dbconn.retrieveExtraDownload(idpackage)
|
|
for extra_download in extra_downloads:
|
|
# just need the first entry, "download"
|
|
extra_rel = extra_download['download']
|
|
|
|
from_extra = self.complete_local_package_path(extra_rel,
|
|
repo)
|
|
if not os.path.isfile(from_extra):
|
|
from_extra = self.complete_local_upload_package_path(
|
|
extra_rel, repo)
|
|
|
|
to_extra = self.complete_local_upload_package_path(
|
|
extra_rel, to_repository_id)
|
|
|
|
copy_data.append((from_extra, to_extra))
|
|
|
|
for from_item, to_item in copy_data:
|
|
self.output(
|
|
"[%s=>%s|%s] %s: %s" % (
|
|
darkgreen(repo),
|
|
darkred(to_repository_id),
|
|
brown(branch),
|
|
blue(_("moving file")),
|
|
darkgreen(os.path.basename(from_item)),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = red(" @@ "),
|
|
back = True
|
|
)
|
|
if os.path.isfile(from_item):
|
|
shutil.copy2(from_item, to_item)
|
|
|
|
self.output(
|
|
"[%s=>%s|%s] %s: %s" % (
|
|
darkgreen(repo),
|
|
darkred(to_repository_id),
|
|
brown(branch),
|
|
blue(_("loading data from source repository")),
|
|
darkgreen(repo),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = red(" @@ "),
|
|
back = True
|
|
)
|
|
# install package into destination db
|
|
data = dbconn.getPackageData(idpackage)
|
|
if new_tag != None:
|
|
data['versiontag'] = new_tag
|
|
|
|
# need to set back data['download'], because pkg path might got
|
|
# changed, due to license re-validation
|
|
data['download'] = updated_package_rel_path
|
|
|
|
# GPG
|
|
# before inserting new pkg, drop GPG signature and re-sign
|
|
old_gpg = copy.copy(data['signatures']['gpg'])
|
|
data['signatures']['gpg'] = None
|
|
for extra_download in data['extra_download']:
|
|
extra_download['gpg'] = None
|
|
try:
|
|
repo_sec = RepositorySecurity()
|
|
except RepositorySecurity.GPGError as err:
|
|
if old_gpg:
|
|
self.output(
|
|
"[%s] %s %s: %s." % (
|
|
darkgreen(to_repository_id),
|
|
darkred(_("GPG key was available in")),
|
|
bold(from_repository_id),
|
|
err,
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = bold(" !!! ")
|
|
)
|
|
repo_sec = None
|
|
|
|
if repo_sec is not None:
|
|
data['signatures']['gpg'] = self._get_gpg_signature(repo_sec,
|
|
to_repository_id, to_file)
|
|
|
|
for extra_download in data['extra_download']:
|
|
to_extra = self.complete_local_upload_package_path(
|
|
extra_download['download'], to_repository_id)
|
|
extra_download['gpg'] = self._get_gpg_signature(repo_sec,
|
|
to_repository_id, to_extra)
|
|
|
|
self.output(
|
|
"[%s=>%s|%s] %s: %s" % (
|
|
darkgreen(repo),
|
|
darkred(to_repository_id),
|
|
brown(branch),
|
|
blue(_("injecting data to destination repository")),
|
|
darkgreen(to_repository_id),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = red(" @@ "),
|
|
back = True
|
|
)
|
|
data['original_repository'] = to_repository_id
|
|
new_idpackage = todbconn.handlePackage(data)
|
|
del data
|
|
todbconn.commit()
|
|
package_ids_added.add(new_idpackage)
|
|
|
|
if not do_copy:
|
|
self.output(
|
|
"[%s=>%s|%s] %s: %s" % (
|
|
darkgreen(repo),
|
|
darkred(to_repository_id),
|
|
brown(branch),
|
|
blue(_("removing entry from source repository")),
|
|
darkgreen(repo),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = red(" @@ "),
|
|
back = True
|
|
)
|
|
|
|
# remove package from old db
|
|
dbconn.removePackage(idpackage)
|
|
dbconn.clean()
|
|
dbconn.commit()
|
|
|
|
self.output(
|
|
"[%s=>%s|%s] %s: %s" % (
|
|
darkgreen(repo),
|
|
darkred(to_repository_id),
|
|
brown(branch),
|
|
blue(_("successfully handled atom")),
|
|
darkgreen(match_atom),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = blue(" @@ ")
|
|
)
|
|
switched.add(idpackage)
|
|
|
|
todbconn = self.open_server_repository(to_repository_id,
|
|
read_only = False, no_upload = True)
|
|
todbconn.clean()
|
|
|
|
if package_ids_added:
|
|
self._add_packages_qa_tests(
|
|
[(x, to_repository_id) for x in package_ids_added], ask = ask)
|
|
# just run this to make dev aware
|
|
self.extended_dependencies_test([to_repository_id])
|
|
|
|
return switched
|
|
|
|
def _inject_database_into_packages(self, repository_id, injection_data):
|
|
|
|
# now inject metadata into tbz2 packages
|
|
self.output(
|
|
"[%s] %s:" % (
|
|
darkgreen(repository_id),
|
|
blue(_("Injecting entropy metadata into built packages")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
|
|
dbconn = self.open_server_repository(repository_id, read_only = False,
|
|
no_upload = True)
|
|
|
|
try:
|
|
repo_sec = RepositorySecurity()
|
|
except RepositorySecurity.GPGError as err:
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
darkgreen(repository_id),
|
|
blue(_("JFYI, GPG infrastructure failed to load")),
|
|
err,
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = red(" @@ ")
|
|
)
|
|
repo_sec = None # gnupg not found, perhaps report it
|
|
|
|
treeupdates_actions = dbconn.listAllTreeUpdatesActions()
|
|
|
|
# generate empty repository file and re-use it every time
|
|
# this improves the execution a lot
|
|
orig_fd = None
|
|
tmp_repo_orig_path = None
|
|
try:
|
|
orig_fd, tmp_repo_orig_path = tempfile.mkstemp(
|
|
prefix="entropy.server._inject")
|
|
|
|
empty_repo = GenericRepository(
|
|
readOnly = False,
|
|
dbFile = tmp_repo_orig_path,
|
|
name = None,
|
|
xcache = False,
|
|
indexing = False,
|
|
skipChecks = True)
|
|
empty_repo.initializeRepository()
|
|
empty_repo.bumpTreeUpdatesActions(treeupdates_actions)
|
|
empty_repo.commit()
|
|
except Exception:
|
|
os.remove(tmp_repo_orig_path)
|
|
raise
|
|
finally:
|
|
if empty_repo is not None:
|
|
empty_repo.close()
|
|
if orig_fd is not None:
|
|
os.close(orig_fd)
|
|
|
|
try:
|
|
for idpackage, package_path in injection_data:
|
|
|
|
tmp_repo_file = None
|
|
tmp_fd = None
|
|
try:
|
|
tmp_fd, tmp_repo_file = tempfile.mkstemp(
|
|
prefix="entropy.server._inject_for")
|
|
with os.fdopen(tmp_fd, "wb") as tmp_f:
|
|
with open(tmp_repo_orig_path, "rb") as empty_f:
|
|
shutil.copyfileobj(empty_f, tmp_f)
|
|
|
|
self.output(
|
|
"[%s|%s] %s: %s" % (
|
|
darkgreen(repository_id),
|
|
brown(str(idpackage)),
|
|
blue(_("injecting entropy metadata")),
|
|
darkgreen(os.path.basename(package_path)),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" @@ "),
|
|
back = True
|
|
)
|
|
data = dbconn.getPackageData(idpackage)
|
|
self._inject_entropy_database_into_package(
|
|
package_path, data,
|
|
treeupdates_actions = treeupdates_actions,
|
|
initialized_repository_path = tmp_repo_file)
|
|
finally:
|
|
if tmp_fd is not None:
|
|
try:
|
|
os.close(tmp_fd)
|
|
except OSError as err:
|
|
if err.errno != errno.EBADF:
|
|
raise
|
|
if tmp_repo_file is not None:
|
|
os.remove(tmp_repo_file)
|
|
|
|
# GPG-sign package if GPG signature is set
|
|
gpg_sign = None
|
|
if repo_sec is not None:
|
|
gpg_sign = self._get_gpg_signature(repo_sec, repository_id,
|
|
package_path)
|
|
|
|
digest = entropy.tools.md5sum(package_path)
|
|
# update digest
|
|
dbconn.setDigest(idpackage, digest)
|
|
# update signatures
|
|
signatures = data['signatures'].copy()
|
|
for hash_key in sorted(signatures):
|
|
if hash_key == "gpg": # gpg already created
|
|
continue
|
|
hash_func = getattr(entropy.tools, hash_key)
|
|
signatures[hash_key] = hash_func(package_path)
|
|
dbconn.setSignatures(idpackage, signatures['sha1'],
|
|
signatures['sha256'], signatures['sha512'],
|
|
gpg_sign)
|
|
dbconn.commit()
|
|
const_setup_file(package_path, etpConst['entropygid'], 0o664)
|
|
self.output(
|
|
"[%s|%s] %s: %s" % (
|
|
darkgreen(repository_id),
|
|
brown(str(idpackage)),
|
|
blue(_("injection complete")),
|
|
darkgreen(os.path.basename(package_path)),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
finally:
|
|
os.remove(tmp_repo_orig_path)
|
|
|
|
def remove_packages(self, repository_id, package_ids):
|
|
"""
|
|
Remove packages from given repository.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@param package_ids: list of package identifiers contained in
|
|
from_repository_id repository
|
|
@type package_ids: list
|
|
"""
|
|
dbconn = self.open_server_repository(repository_id, read_only = False,
|
|
no_upload = True)
|
|
for idpackage in package_ids:
|
|
atom = dbconn.retrieveAtom(idpackage)
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
darkgreen(repository_id),
|
|
blue(_("removing package")),
|
|
darkgreen(atom),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" @@ ")
|
|
)
|
|
dbconn.removePackage(idpackage)
|
|
dbconn.clean()
|
|
dbconn.commit()
|
|
self.close_repository(dbconn)
|
|
self.output(
|
|
"[%s] %s" % (
|
|
darkgreen(repository_id),
|
|
blue(_("removal complete")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" @@ ")
|
|
)
|
|
|
|
def _verify_remote_packages(self, repository_id, packages, ask = True):
|
|
|
|
self.output(
|
|
"[%s] %s:" % (
|
|
red("remote"),
|
|
blue(_("Integrity verification of the selected packages")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" @@ ")
|
|
)
|
|
|
|
idpackages, world = self._match_packages(repository_id, packages)
|
|
dbconn = self.open_server_repository(repository_id, read_only = True,
|
|
no_upload = True)
|
|
branch = self._settings['repositories']['branch']
|
|
|
|
if world:
|
|
self.output(
|
|
blue(
|
|
_("All the packages in repository will be checked.")),
|
|
importance = 1,
|
|
level = "info",
|
|
header = " "
|
|
)
|
|
else:
|
|
mytxt = red("%s:") % (
|
|
_("This is the list of the packages that would be checked"),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = " "
|
|
)
|
|
for idpackage in idpackages:
|
|
pkgatom = dbconn.retrieveAtom(idpackage)
|
|
down_url = dbconn.retrieveDownloadURL(idpackage)
|
|
pkgfile = os.path.basename(down_url)
|
|
self.output(
|
|
red(pkgatom) + " -> " + bold(os.path.join(branch, pkgfile)),
|
|
importance = 1,
|
|
level = "info",
|
|
header = darkgreen(" - ")
|
|
)
|
|
|
|
if ask:
|
|
rc_question = self.ask_question(
|
|
_("Would you like to continue ?"))
|
|
if rc_question == _("No"):
|
|
return set(), set(), {}
|
|
|
|
match = set()
|
|
not_match = set()
|
|
broken_packages = {}
|
|
|
|
for uri in self.remote_packages_mirrors(repository_id):
|
|
|
|
crippled_uri = EntropyTransceiver.get_uri_name(uri)
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
darkgreen(repository_id),
|
|
blue(_("Working on mirror")),
|
|
brown(crippled_uri),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
|
|
|
|
totalcounter = len(idpackages)
|
|
currentcounter = 0
|
|
|
|
txc = self.Transceiver(uri)
|
|
txc.set_verbosity(False)
|
|
with txc as handler:
|
|
|
|
for idpackage in idpackages:
|
|
|
|
currentcounter += 1
|
|
pkgfile = dbconn.retrieveDownloadURL(idpackage)
|
|
pkgfile = self.complete_remote_package_relative_path(
|
|
pkgfile, repository_id)
|
|
pkghash = dbconn.retrieveDigest(idpackage)
|
|
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
brown(crippled_uri),
|
|
blue(_("checking hash")),
|
|
darkgreen(pkgfile),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" @@ "),
|
|
back = True,
|
|
count = (currentcounter, totalcounter,)
|
|
)
|
|
|
|
ck_remote = handler.get_md5(pkgfile)
|
|
if ck_remote is None:
|
|
self.output(
|
|
"[%s] %s: %s %s" % (
|
|
brown(crippled_uri),
|
|
blue(_("digest verification of")),
|
|
bold(pkgfile),
|
|
blue(_("not supported")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" @@ "),
|
|
count = (currentcounter, totalcounter,)
|
|
)
|
|
continue
|
|
|
|
if ck_remote == pkghash:
|
|
match.add(idpackage)
|
|
else:
|
|
not_match.add(idpackage)
|
|
self.output(
|
|
"[%s] %s: %s %s" % (
|
|
brown(crippled_uri),
|
|
blue(_("package")),
|
|
bold(pkgfile),
|
|
red(_("NOT healthy")),
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkred(" !!! "),
|
|
count = (currentcounter, totalcounter,)
|
|
)
|
|
if crippled_uri not in broken_packages:
|
|
broken_packages[crippled_uri] = []
|
|
broken_packages[crippled_uri].append(pkgfile)
|
|
|
|
if broken_packages:
|
|
mytxt = blue("%s:") % (
|
|
_("This is the list of broken packages"),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" * ")
|
|
)
|
|
for mirror in list(broken_packages.keys()):
|
|
mytxt = "%s: %s" % (
|
|
brown(_("Mirror")),
|
|
bold(mirror),
|
|
)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" <> ")
|
|
)
|
|
for broken_package in broken_packages[mirror]:
|
|
self.output(
|
|
blue(broken_package),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" - ")
|
|
)
|
|
|
|
self.output(
|
|
"%s:" % (
|
|
blue(_("Statistics")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
red(crippled_uri),
|
|
brown(_("Number of checked packages")),
|
|
brown(str(len(match) + len(not_match))),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
red(crippled_uri),
|
|
darkgreen(_("Number of healthy packages")),
|
|
darkgreen(str(len(match))),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
red(crippled_uri),
|
|
darkred(_("Number of broken packages")),
|
|
darkred(str(len(not_match))),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
|
|
return match, not_match, broken_packages
|
|
|
|
def _verify_local_packages(self, repository_id, packages, ask = True):
|
|
|
|
self.output(
|
|
"[%s] %s:" % (
|
|
red(_("local")),
|
|
blue(_("Integrity verification of the selected packages")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = darkgreen(" * ")
|
|
)
|
|
|
|
idpackages, world = self._match_packages(repository_id, packages)
|
|
dbconn = self.open_server_repository(repository_id, read_only = True)
|
|
if world:
|
|
self.output(
|
|
blue(_("All the packages in repository will be checked.")),
|
|
importance = 1,
|
|
level = "info",
|
|
header = " "
|
|
)
|
|
|
|
fine = set()
|
|
failed = set()
|
|
|
|
rc_status, available, downloaded_fine, downloaded_errors = \
|
|
self._download_locally_missing_files(idpackages, repository_id,
|
|
ask = ask)
|
|
if not rc_status:
|
|
return fine, failed, downloaded_fine, downloaded_errors
|
|
|
|
my_qa = self.QA()
|
|
|
|
totalcounter = str(len(available))
|
|
currentcounter = 0
|
|
for idpackage in available:
|
|
currentcounter += 1
|
|
pkg_path = dbconn.retrieveDownloadURL(idpackage)
|
|
|
|
self.output(
|
|
"%s: %s" % (
|
|
blue(_("checking status of")),
|
|
darkgreen(pkg_path),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = " ",
|
|
back = True,
|
|
count = (currentcounter, totalcounter,)
|
|
)
|
|
|
|
storedmd5 = dbconn.retrieveDigest(idpackage)
|
|
pkgpath = self._get_package_path(repository_id, dbconn, idpackage)
|
|
result = entropy.tools.compare_md5(pkgpath, storedmd5)
|
|
qa_fine = my_qa.entropy_package_checks(pkgpath)
|
|
if result and qa_fine:
|
|
fine.add(idpackage)
|
|
else:
|
|
failed.add(idpackage)
|
|
self.output(
|
|
"%s: %s -- %s: %s" % (
|
|
blue(_("package")),
|
|
darkgreen(pkg_path),
|
|
blue(_("is corrupted, stored checksum")),
|
|
brown(storedmd5),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = " ",
|
|
count = (currentcounter, totalcounter,)
|
|
)
|
|
|
|
if failed:
|
|
mytxt = blue("%s:") % (_("This is the list of broken packages"),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkred(" # ")
|
|
)
|
|
for idpackage in failed:
|
|
atom = dbconn.retrieveAtom(idpackage)
|
|
down_p = dbconn.retrieveDownloadURL(idpackage)
|
|
self.output(
|
|
blue("[atom:%s] %s" % (atom, down_p,)),
|
|
importance = 0,
|
|
level = "warning",
|
|
header = brown(" # ")
|
|
)
|
|
|
|
# print stats
|
|
self.output(
|
|
red("Statistics:"),
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" * ")
|
|
)
|
|
self.output(
|
|
brown("%s => %s" % (
|
|
len(fine) + len(failed),
|
|
_("checked packages"),
|
|
)
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
self.output(
|
|
darkgreen("%s => %s" % (
|
|
len(fine),
|
|
_("healthy packages"),
|
|
)
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
self.output(
|
|
darkred("%s => %s" % (
|
|
len(failed),
|
|
_("broken packages"),
|
|
)
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
self.output(
|
|
blue("%s => %s" % (
|
|
len(downloaded_fine),
|
|
_("downloaded packages"),
|
|
)
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
self.output(
|
|
bold("%s => %s" % (
|
|
len(downloaded_errors),
|
|
_("failed downloads"),
|
|
)
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
|
|
self.close_repository(dbconn)
|
|
return fine, failed, downloaded_fine, downloaded_errors
|
|
|
|
def sign_local_packages(self, repository_id, ask = True):
|
|
"""
|
|
Sign local packages in given repository using GPG key hopefully set
|
|
for it.
|
|
|
|
@raises OnlineMirrorError: if package path is not available after
|
|
having tried to download it, this should never happen btw.
|
|
"""
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
red(_("local")),
|
|
blue(_("GPG signing packages for repository")),
|
|
repository_id,
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = darkgreen(" @@ ")
|
|
)
|
|
|
|
dbconn = self.open_server_repository(repository_id, read_only = False)
|
|
idpackages = dbconn.listAllPackageIds()
|
|
|
|
self.output(
|
|
blue(_("All the missing packages in repository will be downloaded.")),
|
|
importance = 1,
|
|
level = "info",
|
|
header = " "
|
|
)
|
|
|
|
rc_status, available, downloaded_fine, downloaded_errors = \
|
|
self._download_locally_missing_files(idpackages, repository_id,
|
|
ask = ask)
|
|
if not rc_status:
|
|
return False, 0, 0
|
|
|
|
try:
|
|
repo_sec = RepositorySecurity()
|
|
except RepositorySecurity.GPGError as err:
|
|
self.output("%s: %s" % (
|
|
darkgreen(_("GnuPG not available")),
|
|
err,
|
|
),
|
|
level = "error"
|
|
)
|
|
return False, 0, 0
|
|
|
|
kp_expired = False
|
|
try:
|
|
kp_avail = repo_sec.is_keypair_available(repository_id)
|
|
except RepositorySecurity.KeyExpired:
|
|
kp_avail = False
|
|
kp_expired = True
|
|
|
|
if kp_expired:
|
|
for x in (1, 2, 3):
|
|
# SPAM!
|
|
self.output("%s: %s" % (
|
|
darkred(_("Keys for repository are expired")),
|
|
bold(repository_id),
|
|
),
|
|
level = "warning",
|
|
header = bold(" !!! ")
|
|
)
|
|
elif not kp_avail:
|
|
self.output("%s: %s" % (
|
|
darkgreen(_("Keys not available for")),
|
|
bold(repository_id),
|
|
),
|
|
level = "error"
|
|
)
|
|
return False, 0, 0
|
|
|
|
fine = 0
|
|
failed = 0
|
|
totalcounter = len(available)
|
|
currentcounter = 0
|
|
|
|
# clear all GPG signatures?
|
|
|
|
# we can eventually sign!
|
|
for idpackage in available:
|
|
|
|
currentcounter += 1
|
|
|
|
pkg_path = self._get_package_path(repository_id, dbconn, idpackage)
|
|
if not os.path.isfile(pkg_path):
|
|
pkg_path = self._get_upload_package_path(repository_id, dbconn,
|
|
idpackage)
|
|
if not os.path.isfile(pkg_path):
|
|
# wtf!?
|
|
pkg_atom = dbconn.retrieveAtom(idpackage)
|
|
raise OnlineMirrorError("WTF!?!?! => %s, %s" % (
|
|
pkg_path, pkg_atom,))
|
|
|
|
self.output(
|
|
"%s: %s" % (
|
|
blue(_("signing package")),
|
|
darkgreen(os.path.basename(pkg_path)),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = " ",
|
|
back = True,
|
|
count = (currentcounter, totalcounter,)
|
|
)
|
|
|
|
gpg_sign = self._get_gpg_signature(repo_sec, repository_id,
|
|
pkg_path)
|
|
if gpg_sign is None:
|
|
self.output(
|
|
"%s: %s" % (
|
|
darkred(_("Unknown error signing package")),
|
|
darkgreen(os.path.basename(pkg_path)),
|
|
),
|
|
importance = 1,
|
|
level = "error",
|
|
header = " ",
|
|
count = (currentcounter, totalcounter,)
|
|
)
|
|
failed += 1
|
|
continue
|
|
|
|
# now inject gpg signature into repo
|
|
sha1, sha256, sha512, old_gpg = dbconn.retrieveSignatures(idpackage)
|
|
dbconn.setSignatures(idpackage, sha1, sha256, sha512,
|
|
gpg = gpg_sign)
|
|
|
|
fine += 1
|
|
|
|
dbconn.commit()
|
|
|
|
# print stats
|
|
self.output(
|
|
red("Statistics:"),
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" * ")
|
|
)
|
|
self.output(
|
|
brown("%s => %s" % (
|
|
fine,
|
|
_("signed packages"),
|
|
)
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
self.output(
|
|
darkred("%s => %s" % (
|
|
failed,
|
|
_("broken packages"),
|
|
)
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
self.output(
|
|
blue("%s => %s" % (
|
|
len(downloaded_fine),
|
|
_("downloaded packages"),
|
|
)
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
self.output(
|
|
bold("%s => %s" % (
|
|
len(downloaded_errors),
|
|
_("failed downloads"),
|
|
)
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
|
|
self.close_repository(dbconn)
|
|
return True, fine, failed
|
|
|
|
def _download_locally_missing_files(self, package_ids, repository_id,
|
|
ask = True):
|
|
|
|
dbconn = self.open_server_repository(repository_id, read_only = True)
|
|
to_download = set()
|
|
available = set()
|
|
for idpackage in package_ids:
|
|
|
|
bindir_path = self._get_package_path(repository_id, dbconn,
|
|
idpackage)
|
|
uploaddir_path = self._get_upload_package_path(repository_id,
|
|
dbconn, idpackage)
|
|
pkg_path = dbconn.retrieveDownloadURL(idpackage)
|
|
|
|
pkgatom = dbconn.retrieveAtom(idpackage)
|
|
if os.path.isfile(bindir_path):
|
|
self.output(
|
|
"[%s] %s :: %s" % (
|
|
darkgreen(_("available")),
|
|
blue(pkgatom),
|
|
darkgreen(pkg_path),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = darkgreen(" # ")
|
|
)
|
|
available.add(idpackage)
|
|
elif os.path.isfile(uploaddir_path):
|
|
self.output(
|
|
"[%s] %s :: %s" % (
|
|
darkred(_("upload/ignored")),
|
|
blue(pkgatom),
|
|
darkgreen(pkg_path),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = darkgreen(" # ")
|
|
)
|
|
else:
|
|
self.output(
|
|
"[%s] %s :: %s" % (
|
|
brown(_("download")),
|
|
blue(pkgatom),
|
|
darkgreen(pkg_path),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = darkgreen(" # ")
|
|
)
|
|
to_download.add((idpackage, pkg_path,))
|
|
|
|
if to_download and ask:
|
|
rc_question = self.ask_question(_("Would you like to continue ?"))
|
|
if rc_question == _("No"):
|
|
# = downloaded fine, downloaded error
|
|
return False, available, set(), set()
|
|
|
|
if not to_download:
|
|
# = downloaded fine, downloaded error
|
|
return True, available, set(), set()
|
|
|
|
downloaded_fine = set()
|
|
downloaded_errors = set()
|
|
|
|
not_downloaded = set()
|
|
mytxt = blue("%s ...") % (_("Starting to download missing files"),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = " "
|
|
)
|
|
for uri in self.remote_packages_mirrors(repository_id):
|
|
|
|
if not_downloaded:
|
|
mytxt = blue("%s ...") % (
|
|
_("Searching missing/broken files on another mirror"),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = " "
|
|
)
|
|
to_download = not_downloaded.copy()
|
|
not_downloaded = set()
|
|
|
|
for idpackage, pkg_path in to_download:
|
|
rc_down = self.Mirrors.download_package(repository_id, uri,
|
|
pkg_path)
|
|
if rc_down:
|
|
downloaded_fine.add(idpackage)
|
|
available.add(idpackage)
|
|
else:
|
|
not_downloaded.add(pkg_path)
|
|
|
|
if not not_downloaded:
|
|
self.output(
|
|
red(_("Binary packages downloaded successfully.")),
|
|
importance = 1,
|
|
level = "info",
|
|
header = " "
|
|
)
|
|
break
|
|
|
|
if not_downloaded:
|
|
mytxt = blue("%s:") % (
|
|
_("These are the packages that cannot be found online"),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = " "
|
|
)
|
|
for pkg_path in not_downloaded:
|
|
downloaded_errors.add(pkg_path)
|
|
self.output(
|
|
brown(pkg_path),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = red(" * ")
|
|
)
|
|
downloaded_errors |= not_downloaded
|
|
mytxt = "%s." % (_("They won't be checked"),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "warning",
|
|
header = " "
|
|
)
|
|
|
|
return True, available, downloaded_fine, downloaded_errors
|
|
|
|
def _switch_packages_branch(self, repository_id, from_branch, to_branch):
|
|
|
|
if to_branch != self._settings['repositories']['branch']:
|
|
mytxt = "%s: %s %s" % (
|
|
blue(_("Please setup your branch to")),
|
|
bold(to_branch),
|
|
blue(_("and retry")),
|
|
)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "error",
|
|
header = darkred(" !! ")
|
|
)
|
|
return None
|
|
|
|
mytxt = red("%s ...") % (_("Copying repository (if not exists)"),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = darkgreen(" @@ ")
|
|
)
|
|
branch_dbdir = self._get_local_repository_dir(repository_id)
|
|
old_branch_dbdir = self._get_local_repository_dir(repository_id,
|
|
branch = from_branch)
|
|
|
|
# close all our databases
|
|
self.close_repositories()
|
|
|
|
# if database file did not exist got created as an empty file
|
|
# we can just rm -rf it
|
|
branch_dbfile = self._get_local_repository_file(repository_id)
|
|
if os.path.isfile(branch_dbfile):
|
|
if entropy.tools.get_file_size(branch_dbfile) == 0:
|
|
shutil.rmtree(branch_dbdir, True)
|
|
|
|
if os.path.isdir(branch_dbdir):
|
|
|
|
while True:
|
|
rnd_num = entropy.tools.get_random_number()
|
|
backup_dbdir = branch_dbdir + str(rnd_num)
|
|
if not os.path.isdir(backup_dbdir):
|
|
break
|
|
os.rename(branch_dbdir, backup_dbdir)
|
|
|
|
if os.path.isdir(old_branch_dbdir):
|
|
shutil.copytree(old_branch_dbdir, branch_dbdir)
|
|
|
|
mytxt = red("%s ...") % (_("Switching packages"),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = darkgreen(" @@ ")
|
|
)
|
|
|
|
dbconn = self.open_server_repository(repository_id, read_only = False,
|
|
no_upload = True, lock_remote = False)
|
|
try:
|
|
dbconn.validate()
|
|
except SystemDatabaseError:
|
|
self._handle_uninitialized_repository(repository_id)
|
|
dbconn = self.open_server_repository(repository_id,
|
|
read_only = False, no_upload = True, lock_remote = False)
|
|
|
|
idpackages = dbconn.listAllPackageIds()
|
|
already_switched = set()
|
|
not_found = set()
|
|
switched = set()
|
|
ignored = set()
|
|
no_checksum = set()
|
|
|
|
maxcount = len(idpackages)
|
|
count = 0
|
|
for idpackage in idpackages:
|
|
count += 1
|
|
|
|
cur_branch = dbconn.retrieveBranch(idpackage)
|
|
atom = dbconn.retrieveAtom(idpackage)
|
|
if cur_branch == to_branch:
|
|
already_switched.add(idpackage)
|
|
self.output(
|
|
red("%s %s, %s %s" % (
|
|
_("Ignoring"),
|
|
bold(atom),
|
|
_("already in branch"),
|
|
cur_branch,
|
|
)
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = darkgreen(" @@ "),
|
|
count = (count, maxcount,)
|
|
)
|
|
ignored.add(idpackage)
|
|
continue
|
|
|
|
self.output(
|
|
"[%s=>%s] %s" % (
|
|
brown(cur_branch),
|
|
bold(to_branch),
|
|
darkgreen(atom),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = darkgreen(" @@ "),
|
|
back = True,
|
|
count = (count, maxcount,)
|
|
)
|
|
dbconn.switchBranch(idpackage, to_branch)
|
|
dbconn.commit()
|
|
switched.add(idpackage)
|
|
|
|
dbconn.commit()
|
|
|
|
# now migrate counters
|
|
dbconn.moveSpmUidsToBranch(to_branch)
|
|
|
|
self.close_repository(dbconn)
|
|
mytxt = blue("%s.") % (_("migration loop completed"),)
|
|
self.output(
|
|
"[%s=>%s] %s" % (
|
|
brown(from_branch),
|
|
bold(to_branch),
|
|
mytxt,
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = darkgreen(" * ")
|
|
)
|
|
|
|
return switched, already_switched, ignored, not_found, no_checksum
|
|
|
|
|
|
def orphaned_spm_packages_test(self):
|
|
|
|
mytxt = "%s %s" % (
|
|
blue(_("Running orphaned SPM packages test")), red("..."),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 2,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
installed_packages = self.Spm().get_installed_packages()
|
|
length = len(installed_packages)
|
|
installed_packages.sort()
|
|
not_found = {}
|
|
count = 0
|
|
for installed_package in installed_packages:
|
|
count += 1
|
|
self.output(
|
|
"%s: %s" % (
|
|
darkgreen(_("Scanning package")),
|
|
brown(installed_package),),
|
|
importance = 0,
|
|
level = "info",
|
|
back = True,
|
|
count = (count, length),
|
|
header = darkred(" @@ ")
|
|
)
|
|
key = entropy.dep.dep_getkey(installed_package)
|
|
try:
|
|
slot = self.Spm().get_installed_package_metadata(
|
|
installed_package, "SLOT")
|
|
except KeyError:
|
|
# ignore, race condition, pkg got removed in the meantime
|
|
continue
|
|
pkg_atom = "%s%s%s" % (key, ":", slot,)
|
|
try:
|
|
tree_atoms = self.Spm().match_package(pkg_atom,
|
|
match_type = "match-all")
|
|
except (KeyError,):
|
|
tree_atoms = None # ouch!
|
|
if not tree_atoms:
|
|
not_found[installed_package] = pkg_atom
|
|
self.output(
|
|
"%s: %s" % (
|
|
blue(pkg_atom),
|
|
darkred(_("not found anymore")),
|
|
),
|
|
importance = 0,
|
|
level = "warning",
|
|
count = (count, length),
|
|
header = darkred(" @@ ")
|
|
)
|
|
|
|
if not_found:
|
|
not_found_list = ' '.join([not_found[x] for x in sorted(not_found)])
|
|
self.output(
|
|
"%s: %s" % (
|
|
blue(_("Packages string")),
|
|
not_found_list,
|
|
),
|
|
importance = 0,
|
|
level = "warning",
|
|
count = (count, length),
|
|
header = darkred(" @@ ")
|
|
)
|
|
|
|
return not_found
|
|
|
|
def _deps_tester(self, default_repository_id, match_repo = None):
|
|
|
|
sys_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
server_repos = list(sys_set['repositories'].keys())
|
|
if match_repo is None:
|
|
match_repo = server_repos
|
|
installed_packages = set()
|
|
# if a default repository is passed, we will just test against it
|
|
if default_repository_id:
|
|
server_repos = [default_repository_id]
|
|
|
|
for repo in server_repos:
|
|
dbconn = self.open_server_repository(repo, read_only = True,
|
|
no_upload = True, do_treeupdates = False)
|
|
installed_packages |= set([(x, repo) for x in \
|
|
dbconn.listAllPackageIds()])
|
|
|
|
deps_not_satisfied = set()
|
|
length = str((len(installed_packages)))
|
|
count = 0
|
|
mytxt = _("Checking")
|
|
_deps_cache = set()
|
|
|
|
for idpackage, repo in installed_packages:
|
|
count += 1
|
|
dbconn = self.open_server_repository(repo, read_only = True,
|
|
no_upload = True, do_treeupdates = False)
|
|
|
|
if (count%150 == 0) or (count == length) or (count == 1):
|
|
atom = dbconn.retrieveAtom(idpackage)
|
|
self.output(
|
|
darkgreen(mytxt)+" "+bold(atom),
|
|
importance = 0,
|
|
level = "info",
|
|
back = True,
|
|
count = (count, length),
|
|
header = darkred(" @@ ")
|
|
)
|
|
|
|
# NOTE: this must also test build dependencies to make sure
|
|
# that every packages comes out with all of them.
|
|
xdeps = dbconn.retrieveDependencies(idpackage)
|
|
xdeps = [x for x in xdeps if x not in _deps_cache]
|
|
_deps_cache.update(xdeps)
|
|
for xdep in xdeps:
|
|
xid, xuseless = self.atom_match(xdep,
|
|
match_repo = match_repo)
|
|
if xid == -1:
|
|
deps_not_satisfied.add(xdep)
|
|
|
|
return deps_not_satisfied
|
|
|
|
def extended_dependencies_test(self, repository_ids):
|
|
"""
|
|
Test repository against missing dependencies.
|
|
Moreover, the base repository (the first listed in server.conf)
|
|
is tested by itself, since it must always be self-contained.
|
|
|
|
@param repository_ids: list of repository identifiers to test
|
|
@type repository_ids: list
|
|
@return: list (set) of unsatisfied dependencies
|
|
@rtype: set
|
|
"""
|
|
# just run this to make dev aware
|
|
unsatisfied_deps = set()
|
|
for repository_id in repository_ids:
|
|
unsatisfied_deps |= self.dependencies_test(repository_id)
|
|
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
base_repository_id = srv_set['base_repository_id']
|
|
if base_repository_id is not None:
|
|
# dependency test base repository, which must always be
|
|
# self-contained
|
|
unsatisfied_deps |= self.dependencies_test(base_repository_id,
|
|
match_repo = [base_repository_id])
|
|
return unsatisfied_deps
|
|
|
|
def dependencies_test(self, repository_id, match_repo = None):
|
|
"""
|
|
Test repository against missing dependencies.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@keyword match_repo: list of repositories to look for missing deps
|
|
@type match_repo: list
|
|
@return: list (set) of unsatisfied dependencies
|
|
@rtype: set
|
|
"""
|
|
mytxt = "%s %s" % (blue(_("Running dependencies test")), red("..."))
|
|
self.output(
|
|
mytxt,
|
|
importance = 2,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
server_repos = list(srv_set['repositories'].keys())
|
|
if match_repo is not None:
|
|
server_repo = match_repo
|
|
deps_not_matched = self._deps_tester(repository_id,
|
|
match_repo = match_repo)
|
|
|
|
if deps_not_matched:
|
|
|
|
crying_atoms = {}
|
|
for atom in deps_not_matched:
|
|
for repo in server_repos:
|
|
dbconn = self.open_server_repository(repo,
|
|
just_reading = True, do_treeupdates = False)
|
|
riddep = dbconn.searchDependency(atom)
|
|
if riddep == -1:
|
|
continue
|
|
ridpackages = dbconn.searchPackageIdFromDependencyId(riddep)
|
|
for i in ridpackages:
|
|
iatom = dbconn.retrieveAtom(i)
|
|
if atom not in crying_atoms:
|
|
crying_atoms[atom] = set()
|
|
crying_atoms[atom].add((iatom, repo))
|
|
|
|
mytxt = blue("%s:") % (_("These are the dependencies not found"),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
mytxt = "%s:" % (_("Needed by"),)
|
|
for atom in deps_not_matched:
|
|
self.output(
|
|
red(atom),
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" # ")
|
|
)
|
|
if atom in crying_atoms:
|
|
self.output(
|
|
red(mytxt),
|
|
importance = 0,
|
|
level = "info",
|
|
header = blue(" # ")
|
|
)
|
|
for my_dep, myrepo in crying_atoms[atom]:
|
|
self.output(
|
|
"[%s:%s] %s" % (
|
|
blue(_("by repo")),
|
|
darkred(myrepo),
|
|
darkgreen(my_dep),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = blue(" # ")
|
|
)
|
|
else:
|
|
|
|
mytxt = blue(_("Every dependency is satisfied. It's all fine."))
|
|
self.output(
|
|
mytxt,
|
|
importance = 2,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
|
|
return deps_not_matched
|
|
|
|
def test_shared_objects(self, repository_id, dump_results_to_file = False):
|
|
"""
|
|
Test packages in repository, against missing shared objects.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@keyword dump_results_to_file: dump results to file
|
|
@type dump_results_to_file: bool
|
|
@return: execution status (0 = fine)
|
|
@rtype: int
|
|
"""
|
|
pkg_list_path = None
|
|
if dump_results_to_file:
|
|
tmp_dir = tempfile.mkdtemp(prefix = "entropy.server")
|
|
pkg_list_path = os.path.join(tmp_dir, "libtest_broken.txt")
|
|
dmp_data = [
|
|
(_("Broken and matched packages list"), pkg_list_path,),
|
|
]
|
|
mytxt = "%s:" % (purple(_("Dumping results into these files")),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" @@ ")
|
|
)
|
|
for txt, path in dmp_data:
|
|
mytxt = "%s: %s" % (blue(txt), path,)
|
|
self.output(
|
|
mytxt,
|
|
importance = 0,
|
|
level = "info",
|
|
header = darkgreen(" ## ")
|
|
)
|
|
|
|
# load db
|
|
dbconn = self.open_server_repository(repository_id, read_only = True,
|
|
no_upload = True)
|
|
QA = self.QA()
|
|
packages_matched, brokenexecs, status = QA.test_shared_objects(dbconn,
|
|
broken_symbols = True, dump_results_to_file = dump_results_to_file)
|
|
if status != 0:
|
|
return 1
|
|
|
|
if (not brokenexecs) and (not packages_matched):
|
|
mytxt = "%s." % (_("System is healthy"),)
|
|
self.output(
|
|
blue(mytxt),
|
|
importance = 2,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
return 0
|
|
|
|
mytxt = "%s..." % (_("Matching libraries with Spm, please wait"),)
|
|
self.output(
|
|
blue(mytxt),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
|
|
real_brokenexecs = [os.path.realpath(x) for x in brokenexecs if \
|
|
x != os.path.realpath(x)]
|
|
brokenexecs.update(real_brokenexecs)
|
|
packages = self.Spm().search_paths_owners(brokenexecs)
|
|
|
|
if packages:
|
|
mytxt = "%s:" % (_("These are the matched packages"),)
|
|
self.output(
|
|
red(mytxt),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
for my_atom, my_elf_id in packages:
|
|
package_slot = my_atom, my_elf_id
|
|
self.output(
|
|
"%s [elf:%s]" % (
|
|
purple(my_atom),
|
|
darkgreen(str(my_elf_id)),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = red(" # ")
|
|
)
|
|
for filename in sorted(packages[package_slot]):
|
|
self.output(
|
|
darkgreen(filename),
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" => ")
|
|
)
|
|
|
|
pkgstring_list = sorted(["%s%s%s" % (
|
|
entropy.dep.dep_getkey(x[0]), etpConst['entropyslotprefix'],
|
|
x[1],) for x in sorted(packages)])
|
|
if pkg_list_path is not None:
|
|
enc = etpConst['conf_encoding']
|
|
with codecs.open(pkg_list_path, "w", encoding=enc) as pkg_f:
|
|
for pkgstr in pkgstring_list:
|
|
pkg_f.write(pkgstr + "\n")
|
|
pkg_f.flush()
|
|
pkgstring = ' '.join(pkgstring_list)
|
|
mytxt = "%s: %s" % (darkgreen(_("Packages string")), pkgstring,)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
else:
|
|
self.output(
|
|
red(_("No matched packages")),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
|
|
return 0
|
|
|
|
def close_repositories(self, mask_clear = False):
|
|
|
|
srv_dbcache = self._server_dbcache
|
|
if srv_dbcache is not None:
|
|
for item in srv_dbcache.keys():
|
|
try:
|
|
srv_dbcache[item].close()
|
|
except ProgrammingError: # already closed?
|
|
pass
|
|
srv_dbcache.clear()
|
|
if mask_clear:
|
|
self._settings.clear()
|
|
|
|
def commit_repositories(self):
|
|
"""
|
|
Execute commit on all the open (in rw) repositories.
|
|
"""
|
|
srv_dbcache = self._server_dbcache
|
|
if srv_dbcache is not None:
|
|
for repo in srv_dbcache.values():
|
|
# commit on readonly repos has no effect
|
|
repo.commit()
|
|
|
|
def close_repository(self, entropy_repository):
|
|
"""
|
|
Close single EntropyRepositoryBase instance, given its class object.
|
|
|
|
@param entropy_repository: EntropyRepositoryBase instance
|
|
@type entropy_repository: entropy.db.skel.EntropyRepositoryBase
|
|
"""
|
|
found = None
|
|
for item in self._server_dbcache:
|
|
if entropy_repository is self._server_dbcache[item]:
|
|
found = item
|
|
break
|
|
if found is not None:
|
|
instance = self._server_dbcache.pop(found)
|
|
instance.close()
|
|
|
|
def repository_metadata(self, repository_id):
|
|
"""
|
|
Return Entropy Server repository configuration metadata.
|
|
|
|
@return: repository configuration metadata
|
|
@rtype: dict
|
|
@raise KeyError: if repository is not configured or available
|
|
"""
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return srv_set['repositories'][repository_id]
|
|
|
|
def available_repositories(self):
|
|
"""
|
|
Return a dictionary representing available repositories metadata.
|
|
|
|
@return: available repository metadata
|
|
@rtype: dict
|
|
"""
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
return srv_set['repositories'].copy()
|
|
|
|
def switch_default_repository(self, repository_id, save = None,
|
|
handle_uninitialized = True):
|
|
"""
|
|
Change Entropy Server default (current) repository.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@keyword save: if True, save the default repository in server
|
|
configuration
|
|
@type save: bool
|
|
@keyword handle_uninitialized: if repository is uninitialized, handle
|
|
the case automatically
|
|
@type handle_uninitialized: bool
|
|
"""
|
|
# avoid setting __default__ as default server repo
|
|
if repository_id == etpConst['clientserverrepoid']:
|
|
return
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
|
|
if save is None:
|
|
save = self._save_repository
|
|
if repository_id not in srv_set['repositories']:
|
|
raise PermissionDenied("PermissionDenied: %s %s" % (
|
|
repository_id,
|
|
_("repository not configured"),
|
|
)
|
|
)
|
|
self.close_repositories()
|
|
srv_set['default_repository_id'] = repository_id
|
|
self._repository = repository_id
|
|
self._setup_services()
|
|
if save:
|
|
self._save_default_repository(repository_id)
|
|
|
|
self._setup_community_repositories_settings()
|
|
if handle_uninitialized:
|
|
self._handle_uninitialized_repository(repository_id)
|
|
|
|
def _setup_community_repositories_settings(self):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
if srv_set['community_mode']:
|
|
for repoid in srv_set['repositories']:
|
|
srv_set['repositories'][repoid]['community'] = True
|
|
|
|
def _handle_uninitialized_repository(self, repoid):
|
|
|
|
if self._is_repository_initialized(repoid):
|
|
return
|
|
|
|
mytxt = blue("%s.") % (
|
|
_("Your default repository is not initialized"),)
|
|
self.output(
|
|
"[%s:%s] %s" % (
|
|
brown("repo"),
|
|
purple(repoid),
|
|
mytxt,
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkred(" !!! ")
|
|
)
|
|
answer = self.ask_question(
|
|
_("Do you want to initialize your default repository ?"))
|
|
if answer == _("No"):
|
|
mytxt = red("%s.") % (
|
|
_("Continuing with an uninitialized repository"),)
|
|
self.output(
|
|
"[%s:%s] %s" % (
|
|
brown("repo"),
|
|
purple(repoid),
|
|
mytxt,
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkred(" !!! ")
|
|
)
|
|
else:
|
|
# move empty database for security sake
|
|
dbfile = self._get_local_repository_file(repoid)
|
|
if os.path.isfile(dbfile):
|
|
shutil.move(dbfile, dbfile+".backup")
|
|
self.initialize_repository(repoid)
|
|
|
|
def _save_default_repository(self, repoid):
|
|
|
|
# avoid setting __default__ as default server repo
|
|
if repoid == etpConst['clientserverrepoid']:
|
|
return
|
|
|
|
enc = etpConst['conf_encoding']
|
|
server_conf = ServerSystemSettingsPlugin.server_conf_path()
|
|
try:
|
|
with codecs.open(server_conf, "r", encoding=enc) as f_srv:
|
|
content = f_srv.readlines()
|
|
except IOError as err:
|
|
if err.errno == errno.ENOENT:
|
|
content = None
|
|
else:
|
|
raise
|
|
|
|
if content is not None:
|
|
content = [x.strip() for x in content]
|
|
found = False
|
|
new_content = []
|
|
for line in content:
|
|
key, value = entropy.tools.extract_setting(line)
|
|
if key == "default-repository":
|
|
line = "default-repository = %s" % (repoid,)
|
|
found = True
|
|
new_content.append(line)
|
|
if not found:
|
|
new_content.append("default-repository = %s" % (repoid,))
|
|
|
|
tmp_fd, tmp_path = tempfile.mkstemp()
|
|
with entropy.tools.codecs_fdopen(tmp_fd, "w", enc) as f_srv_t:
|
|
for line in new_content:
|
|
f_srv_t.write(line+"\n")
|
|
f_srv_t.flush()
|
|
|
|
entropy.tools.rename_keep_permissions(
|
|
tmp_path, server_conf)
|
|
|
|
else:
|
|
with codecs.open(server_conf, "w", encoding=enc) as f_srv:
|
|
f_srv.write("default-repository = %s\n" % (repoid,))
|
|
f_srv.flush()
|
|
|
|
def enable_repository(self, repository_id):
|
|
"""
|
|
Enable a repository.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@param enable: True for enable, False for disable
|
|
@type enable: bool
|
|
@return: True, if switch went fine, False otherwise
|
|
@rtype: bool
|
|
"""
|
|
return self._toggle_repository(repository_id, True)
|
|
|
|
def disable_repository(self, repository_id):
|
|
"""
|
|
Disable a repository.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@return: True, if switch went fine, False otherwise
|
|
@rtype: bool
|
|
"""
|
|
return self._toggle_repository(repository_id, False)
|
|
|
|
def _toggle_repository(self, repository_id, enable):
|
|
"""
|
|
Enable or disable a repository.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@param enable: True for enable, False for disable
|
|
@type enable: bool
|
|
@return: True, if switch went fine, False otherwise
|
|
@rtype: bool
|
|
"""
|
|
|
|
# avoid setting __default__ as default server repo
|
|
if repository_id == etpConst['clientserverrepoid']:
|
|
return False
|
|
|
|
server_conf = ServerSystemSettingsPlugin.server_conf_path()
|
|
enc = etpConst['conf_encoding']
|
|
try:
|
|
with codecs.open(server_conf, "r", encoding=enc) as f_srv:
|
|
content = [x.strip() for x in f_srv.readlines()]
|
|
except IOError as err:
|
|
if err.errno != errno.ENOENT:
|
|
raise
|
|
return None
|
|
|
|
tmp_fd, tmp_path = tempfile.mkstemp()
|
|
with entropy.tools.codecs_fdopen(tmp_fd, "w", enc) as f_tmp:
|
|
status = False
|
|
for line in content:
|
|
key, value = entropy.tools.extract_setting(line)
|
|
if key is not None:
|
|
key = key.replace(" ", "")
|
|
key = key.replace("\t", "")
|
|
if key in ("repository", "#repository"):
|
|
if enable and (key == "#repository"):
|
|
line = "repository = %s" % (value,)
|
|
status = True
|
|
elif not enable and (key == "repository"):
|
|
line = "# repository = %s" % (value,)
|
|
status = True
|
|
f_tmp.write(line+"\n")
|
|
f_tmp.flush()
|
|
|
|
if status:
|
|
entropy.tools.rename_keep_permissions(
|
|
tmp_path, server_conf)
|
|
self.close_repositories()
|
|
self._settings.clear()
|
|
self._setup_services()
|
|
return status
|
|
|
|
def _is_repository_initialized(self, repo):
|
|
|
|
def do_validate(dbc):
|
|
try:
|
|
dbc.validate()
|
|
return True
|
|
except SystemDatabaseError:
|
|
return False
|
|
|
|
try:
|
|
dbc = self.open_server_repository(repo, just_reading = True)
|
|
except RepositoryError:
|
|
return False
|
|
valid = do_validate(dbc)
|
|
if not valid:
|
|
dbc = self.open_server_repository(repo, read_only = False,
|
|
no_upload = True, is_new = True)
|
|
valid = do_validate(dbc)
|
|
|
|
return valid
|
|
|
|
def _server_repository_sync_lock(self, repo, no_upload):
|
|
|
|
if repo is None:
|
|
repo = self._repository
|
|
|
|
# check if the database is locked locally
|
|
lock_file = self._get_repository_lockfile(repo)
|
|
if os.path.isfile(lock_file):
|
|
self.output(
|
|
red(_("Entropy repository is already locked by you :-)")),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" * ")
|
|
)
|
|
else:
|
|
# check if the database is locked REMOTELY
|
|
mytxt = "%s ..." % (_("Locking and Syncing Entropy repository"),)
|
|
self.output(
|
|
red(mytxt),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" * "),
|
|
back = True
|
|
)
|
|
for uri in self.remote_repository_mirrors(repo):
|
|
|
|
crippled_uri = EntropyTransceiver.get_uri_name(uri)
|
|
|
|
given_up = self.Mirrors.mirror_locked(repo, uri)
|
|
if given_up:
|
|
mytxt = "%s:" % (_("Mirrors status table"),)
|
|
self.output(
|
|
darkgreen(mytxt),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" * ")
|
|
)
|
|
dbstatus = self.Mirrors.mirrors_status(repo)
|
|
for db_uri, db_st1, db_st2 in dbstatus:
|
|
db_st1_info = darkgreen(_("Unlocked"))
|
|
if db_st1:
|
|
db_st1_info = red(_("Locked"))
|
|
db_st2_info = darkgreen(_("Unlocked"))
|
|
if db_st2:
|
|
db_st2_info = red(_("Locked"))
|
|
|
|
crippled_uri = EntropyTransceiver.get_uri_name(db_uri)
|
|
self.output(
|
|
"%s: [%s: %s] [%s: %s]" % (
|
|
bold(crippled_uri),
|
|
brown(_("repository")),
|
|
db_st1_info,
|
|
brown(_("download")),
|
|
db_st2_info,
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = "\t"
|
|
)
|
|
|
|
raise OnlineMirrorError("OnlineMirrorError: %s %s" % (
|
|
_("cannot lock mirror"),
|
|
crippled_uri,
|
|
)
|
|
)
|
|
|
|
# if we arrive here, it is because all the mirrors are unlocked
|
|
self.Mirrors.lock_mirrors(repo, True)
|
|
self.Mirrors.sync_repository(repo, enable_upload = not no_upload)
|
|
|
|
|
|
def _init_generic_memory_server_repository(self, repository_id, description,
|
|
pkg_mirrors = None, repo_mirrors = None, community_repo = False,
|
|
service_url = None, set_as_default = False):
|
|
|
|
if pkg_mirrors is None:
|
|
pkg_mirrors = []
|
|
if repo_mirrors is None:
|
|
repo_mirrors = []
|
|
|
|
repo = self._open_temp_repository(repository_id, temp_file = ":memory:")
|
|
self._memory_db_srv_instances[repository_id] = repo
|
|
|
|
# add to settings
|
|
repodata = {
|
|
'repoid': repository_id,
|
|
'description': description,
|
|
'pkg_mirrors': pkg_mirrors,
|
|
'repo_mirrors': repo_mirrors,
|
|
'community': community_repo,
|
|
'handler': '', # not supported
|
|
'__temporary__': True,
|
|
}
|
|
ServerSystemSettingsPlugin.extend_repository_metadata(
|
|
self._settings, repository_id, repodata)
|
|
|
|
# this will make the change permanent until remove_repository()
|
|
ServerSystemSettingsPlugin.REPOSITORIES[repository_id] = repodata
|
|
# this will add our repo to the metadata stuff
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
srv_set['repositories'][repository_id] = repodata
|
|
if set_as_default:
|
|
# this will make the change permanent
|
|
etpConst['defaultserverrepositoryid'] = repository_id
|
|
# this will affect the current settings
|
|
srv_set['default_repository_id'] = repository_id
|
|
# not calling SystemSettings.clear()
|
|
# because it's not strictly needed, but match metadata
|
|
# could be available if this method gets called after
|
|
# init_singleton(). In that case, it's up the user to call
|
|
# clear()
|
|
|
|
etp_repo_meta = {
|
|
'lock_remote': False,
|
|
'no_upload': True,
|
|
'output_interface': self,
|
|
'read_only': False,
|
|
'local_dbfile': None,
|
|
'__temporary__': True,
|
|
}
|
|
srv_plug = ServerEntropyRepositoryPlugin(self, metadata = etp_repo_meta)
|
|
repo.add_plugin(srv_plug)
|
|
return repo
|
|
|
|
def _open_temp_repository(self, repo, temp_file = None, initialize = True):
|
|
"""
|
|
Open temporary ServerPackagesRepository interface.
|
|
|
|
@keyword dbname: database name
|
|
@type dbname: string
|
|
@keyword output_interface: entropy.output.TextInterface based instance
|
|
@type output_interface: entropy.output.TextInterface based instance
|
|
"""
|
|
if temp_file is None:
|
|
tmp_fd, temp_file = tempfile.mkstemp(prefix = 'entropy.server')
|
|
os.close(tmp_fd)
|
|
|
|
conn = ServerPackagesRepository(
|
|
readOnly = False,
|
|
dbFile = temp_file,
|
|
name = repo,
|
|
xcache = False,
|
|
indexing = False,
|
|
skipChecks = True,
|
|
temporary = True
|
|
)
|
|
if initialize:
|
|
conn.initializeRepository()
|
|
return conn
|
|
|
|
@staticmethod
|
|
def get_repository(repoid):
|
|
"""
|
|
Reimplemented from entropy.client.interfaces.client.Client class
|
|
"""
|
|
if repoid == etpConst['clientserverrepoid']:
|
|
return InstalledPackagesRepository
|
|
return ServerPackagesRepository
|
|
|
|
def open_repository(self, repository_id):
|
|
"""
|
|
This method aims to improve class usability by providing an easier
|
|
method to open Entropy Server-side repositories like it happens
|
|
with Entropy Client-side ones. If you just want open a read-only
|
|
repository, feel free to use this wrapping method.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@return: ServerPackagesRepository instance
|
|
@rtype: entropy.server.interfaces.ServerPackagesRepository
|
|
"""
|
|
return self.open_server_repository(repository_id,
|
|
just_reading = True, do_treeupdates = False)
|
|
|
|
def remove_repository(self, repository_id, disable = False):
|
|
"""
|
|
Remove Repository from those available, server-side override.
|
|
"""
|
|
try:
|
|
repo_data = ServerSystemSettingsPlugin.REPOSITORIES
|
|
del repo_data[repository_id]
|
|
except KeyError:
|
|
pass
|
|
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
try:
|
|
del srv_set['repositories'][repository_id]
|
|
except KeyError as err:
|
|
pass
|
|
|
|
return super(Server, self).remove_repository(
|
|
repository_id, disable = disable)
|
|
|
|
def open_server_repository(self, repository_id, read_only = True,
|
|
no_upload = True, just_reading = False, indexing = True,
|
|
warnings = True, do_cache = True, use_branch = None,
|
|
lock_remote = True, is_new = False, do_treeupdates = True):
|
|
"""
|
|
Open Server-side Entropy repository given its repository identifier.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@keyword read_only: open repository in read-only
|
|
@type read_only: bool
|
|
@param no_upload: allow to force the upload of the repository if
|
|
required
|
|
@type no_upload: bool
|
|
@keyword just_reading: open the repository just for the purpose of
|
|
getting data from it, without any fancy action. This avoids
|
|
triggering some forced behaviours. If just_reading is True,
|
|
read_only is forced to True.
|
|
@type just_reading: bool
|
|
@keyword indexing: allow repository indexing
|
|
@type indexing: bool
|
|
@keyword warnings: show warnings to user if True
|
|
@type warnings: bool
|
|
@keyword do_cache: use cache to retrieve a fresh repository object
|
|
@type do_cache: bool
|
|
@keyword use_branch: override default package branch when opening a
|
|
new repository
|
|
@type use_branch: bool
|
|
@keyword lock_remote: lock remote mirrors when opening
|
|
@type lock_remote: bool
|
|
@keyword is_new: set this to True if the repository has been just
|
|
created
|
|
@type is_new: bool
|
|
@type do_treeupdates: allow the execution of package names and slot
|
|
updates
|
|
@type do_treeupdates: bool
|
|
@raise RepositoryError: if repository doesn't exist
|
|
@return: EntropyRepositoryBase instance
|
|
@rtype: EntropyRepositoryBase
|
|
"""
|
|
# in-memory server repos
|
|
if repository_id in self._memory_db_srv_instances:
|
|
return self._memory_db_srv_instances[repository_id]
|
|
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
if repository_id == etpConst['clientserverrepoid'] and \
|
|
srv_set['community_mode']:
|
|
return self.installed_repository()
|
|
|
|
if just_reading:
|
|
read_only = True
|
|
no_upload = True
|
|
|
|
try:
|
|
local_dbfile = self._get_local_repository_file(
|
|
repository_id, branch = use_branch)
|
|
except KeyError:
|
|
# repository not available
|
|
raise RepositoryError(repository_id)
|
|
if do_cache:
|
|
cached = self._server_dbcache.get(
|
|
(repository_id, etpConst['systemroot'], local_dbfile, read_only,
|
|
no_upload, just_reading, use_branch, lock_remote,)
|
|
)
|
|
if cached != None:
|
|
return cached
|
|
|
|
local_dbfile_dir = os.path.dirname(local_dbfile)
|
|
try:
|
|
self._ensure_dir_path(local_dbfile_dir)
|
|
except OSError as err:
|
|
if err.errno != errno.EACCES:
|
|
raise
|
|
raise RepositoryError(repository_id)
|
|
|
|
if (not read_only) and (lock_remote) and \
|
|
(repository_id not in self._sync_lock_cache):
|
|
self._server_repository_sync_lock(repository_id, no_upload)
|
|
self._sync_lock_cache.add(repository_id)
|
|
|
|
conn = ServerPackagesRepository(
|
|
readOnly = read_only,
|
|
dbFile = local_dbfile,
|
|
name = repository_id,
|
|
xcache = False # always set to False, if you want to enable
|
|
# you need to make sure that client-side and server-side caches
|
|
# don't collide due to sharing ServerPackagesRepository.repository_id()
|
|
)
|
|
etp_repo_meta = {
|
|
'lock_remote': lock_remote,
|
|
'no_upload': no_upload,
|
|
'output_interface': self,
|
|
'read_only': read_only,
|
|
'local_dbfile': local_dbfile,
|
|
}
|
|
srv_plug = ServerEntropyRepositoryPlugin(self, metadata = etp_repo_meta)
|
|
conn.add_plugin(srv_plug)
|
|
|
|
valid = True
|
|
try:
|
|
conn.validate()
|
|
except SystemDatabaseError:
|
|
valid = False
|
|
|
|
# verify if we need to update the database to sync
|
|
# with portage updates, we just ignore being readonly in the case
|
|
if (repository_id not in self._treeupdates_repos) and \
|
|
(not just_reading):
|
|
# sometimes, when filling a new server db
|
|
# we need to avoid tree updates
|
|
if valid:
|
|
if do_treeupdates and not conn.readonly():
|
|
# readonly() always returns the effective
|
|
# write access to repository (despire what is
|
|
# really set during instantiation)
|
|
self._repository_packages_spm_sync(repository_id, conn,
|
|
branch = use_branch)
|
|
elif warnings and not is_new:
|
|
mytxt = _("Repository is corrupted!")
|
|
self.output(
|
|
darkred(mytxt),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = bold(" !!! ")
|
|
)
|
|
|
|
if not read_only and valid and indexing:
|
|
|
|
self.output(
|
|
"[%s|%s] %s" % (
|
|
blue(repository_id),
|
|
red(_("repository")),
|
|
blue(_("indexing repository")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" @@ ")
|
|
)
|
|
conn.createAllIndexes()
|
|
|
|
if do_cache:
|
|
# !!! also cache just_reading otherwise there will be
|
|
# real issues if the connection is opened several times
|
|
self._server_dbcache[
|
|
(repository_id, etpConst['systemroot'], local_dbfile, read_only,
|
|
no_upload, just_reading, use_branch, lock_remote,)] = conn
|
|
|
|
# this ensures that side effect outcomes are committed
|
|
# to db (treeupdates for example)
|
|
conn.commit(force = True, no_plugins = True)
|
|
return conn
|
|
|
|
def _repository_packages_spm_sync(self, repository_id, repo_db,
|
|
branch = None):
|
|
"""
|
|
Service method used to sync package names with Source Package Manager.
|
|
Source Package Manager can change package names, categories or slot
|
|
and Entropy repositories must be kept in sync.
|
|
"""
|
|
if branch is None:
|
|
branch = self._settings['repositories']['branch']
|
|
self._treeupdates_repos.add(repository_id)
|
|
self.Spm().package_names_update(repo_db, repository_id, self, branch)
|
|
|
|
def _setup_empty_repository(self, repository_path):
|
|
|
|
dbdir = os.path.dirname(repository_path)
|
|
if not os.path.isdir(dbdir):
|
|
os.makedirs(dbdir)
|
|
|
|
mytxt = red("%s ...") % (_("Initializing an empty repository"),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = darkgreen(" * "),
|
|
back = True
|
|
)
|
|
dbconn = self.open_generic_repository(repository_path)
|
|
dbconn.initializeRepository()
|
|
dbconn.commit()
|
|
dbconn.close()
|
|
mytxt = "%s %s %s." % (
|
|
red(_("Entropy repository file")),
|
|
bold(repository_path),
|
|
red(_("successfully initialized")),
|
|
)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = darkgreen(" * ")
|
|
)
|
|
|
|
def _get_whitelisted_licenses(self, repository_id):
|
|
wl_file = self._get_local_repository_licensewhitelist_file(
|
|
repository_id)
|
|
if not os.path.isfile(wl_file):
|
|
return []
|
|
return entropy.tools.generic_file_content_parser(
|
|
wl_file, encoding = etpConst['conf_encoding'])
|
|
|
|
def _get_restricted_packages(self, repository_id):
|
|
rl_file = self._get_local_restricted_file(repository_id)
|
|
if not os.path.isfile(rl_file):
|
|
return []
|
|
return entropy.tools.generic_file_content_parser(rl_file,
|
|
comment_tag = "##", encoding = etpConst['conf_encoding'])
|
|
|
|
def _is_pkg_restricted(self, repository_id, pkg_atom, pkg_slot):
|
|
|
|
restricted_pkgs = self._get_restricted_packages(repository_id)
|
|
if not restricted_pkgs:
|
|
return False
|
|
|
|
pkg_key = entropy.dep.dep_getkey(pkg_atom)
|
|
for r_dep in restricted_pkgs:
|
|
r_key, r_slot = entropy.dep.dep_getkey(r_dep), \
|
|
entropy.dep.dep_getslot(r_dep)
|
|
if r_slot is None:
|
|
r_slot = pkg_slot
|
|
|
|
if (r_key == pkg_key) and (r_slot == pkg_slot):
|
|
return True
|
|
|
|
return False
|
|
|
|
def _is_pkg_free(self, repository_id, pkg_licenses):
|
|
|
|
# check if nonfree directory support is enabled, if not,
|
|
# always return True.
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
if not srv_set['nonfree_packages_dir_support']:
|
|
return True
|
|
|
|
wl_licenses = self._get_whitelisted_licenses(repository_id)
|
|
|
|
if not pkg_licenses:
|
|
return True # free if no licenses provided
|
|
if not wl_licenses:
|
|
return True # no whitelist !
|
|
|
|
for lic in pkg_licenses:
|
|
if lic not in wl_licenses:
|
|
return False
|
|
return True
|
|
|
|
def _package_injector(self, repository_id, package_files, inject = False):
|
|
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
|
|
def _package_injector_check_license(pkg_data):
|
|
licenses = pkg_data['license'].split()
|
|
return self._is_pkg_free(repository_id, licenses)
|
|
|
|
def _package_injector_check_restricted(pkg_data):
|
|
pkgatom = entropy.dep.create_package_atom_string(
|
|
pkg_data['category'], pkg_data['name'], pkg_data['version'],
|
|
pkg_data['versiontag'])
|
|
return self._is_pkg_restricted(repository_id,
|
|
pkgatom, pkg_data['slot'])
|
|
|
|
dbconn = self.open_server_repository(repository_id, read_only = False,
|
|
no_upload = True)
|
|
package_file = package_files[0]
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
darkgreen(repository_id),
|
|
_("adding package"),
|
|
bold(os.path.basename(package_file)),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" * "),
|
|
back = True
|
|
)
|
|
mydata = self.Spm().extract_package_metadata(package_file,
|
|
license_callback = _package_injector_check_license,
|
|
restricted_callback = _package_injector_check_restricted)
|
|
is_licensed_ugly = not _package_injector_check_license(mydata)
|
|
is_restricted = _package_injector_check_restricted(mydata)
|
|
|
|
try:
|
|
repo_sec = RepositorySecurity()
|
|
except RepositorySecurity.GPGError as err:
|
|
# GPG not available
|
|
repo_sec = None
|
|
|
|
def _generate_extra_download(path, down_type):
|
|
extra_url = entropy.tools.create_package_dirpath(mydata['branch'],
|
|
nonfree = is_licensed_ugly, restricted = is_restricted) + \
|
|
"/" + os.path.basename(path)
|
|
size = entropy.tools.get_file_size(path)
|
|
disksize = entropy.tools.get_uncompressed_size(path)
|
|
md5 = entropy.tools.md5sum(path)
|
|
sha1 = entropy.tools.sha1(path)
|
|
sha256 = entropy.tools.sha256(path)
|
|
sha512 = entropy.tools.sha512(path)
|
|
gpg = None
|
|
if repo_sec is not None:
|
|
gpg = self._get_gpg_signature(repo_sec, repository_id, path)
|
|
edw = {
|
|
'download': extra_url,
|
|
'type': down_type,
|
|
'size': size,
|
|
'disksize': disksize,
|
|
'md5': md5,
|
|
'sha1': sha1,
|
|
'sha256': sha256,
|
|
'sha512': sha512,
|
|
'gpg': gpg,
|
|
}
|
|
return edw
|
|
|
|
# ~~~
|
|
# Support for separate debug package files
|
|
extra_files = package_files[1:]
|
|
debuginfo_files = []
|
|
for extra_filepath in extra_files:
|
|
if extra_filepath.endswith(etpConst['packagesdebugext']):
|
|
debuginfo_files.append(extra_filepath)
|
|
for debuginfo_filepath in debuginfo_files:
|
|
extra_files.remove(debuginfo_filepath)
|
|
|
|
extra_download = []
|
|
for debuginfo_filepath in debuginfo_files:
|
|
extra_download.append(
|
|
_generate_extra_download(debuginfo_filepath, "debug"))
|
|
|
|
for extra_filepath in extra_files:
|
|
extra_download.append(
|
|
_generate_extra_download(extra_filepath, "data"))
|
|
|
|
self._pump_extracted_package_metadata(mydata, repository_id,
|
|
{
|
|
'injected': inject,
|
|
'original_repository': repository_id,
|
|
'extra_download': extra_download,
|
|
}
|
|
)
|
|
idpackage = dbconn.handlePackage(mydata)
|
|
revision = dbconn.retrieveRevision(idpackage)
|
|
# make sure that info have been written to disk
|
|
dbconn.commit()
|
|
|
|
myserver_repos = list(srv_set['repositories'].keys())
|
|
|
|
### since we are handling more repositories, we need to make sure
|
|
### that there are no packages in other repositories with same atom
|
|
### and greater revision
|
|
rev_test_atom = mydata['atom']
|
|
max_rev = -1
|
|
for myrepo in myserver_repos:
|
|
|
|
# not myself
|
|
if myrepo == repository_id:
|
|
continue
|
|
|
|
mydbconn = self.open_server_repository(myrepo, read_only = True,
|
|
no_upload = True)
|
|
|
|
myrepo_idpackages = mydbconn.getPackageIds(rev_test_atom)
|
|
for myrepo_idpackage in myrepo_idpackages:
|
|
myrev = mydbconn.retrieveRevision(myrepo_idpackage)
|
|
if myrev > max_rev:
|
|
max_rev = myrev
|
|
|
|
if max_rev >= revision:
|
|
max_rev += 1
|
|
revision = max_rev
|
|
mydata['revision'] = revision
|
|
# update revision for pkg now
|
|
dbconn.setRevision(idpackage, revision)
|
|
|
|
# make sure that info have been written to disk, again
|
|
dbconn.commit()
|
|
|
|
# set trashed counters
|
|
trashing_counters = set()
|
|
|
|
for myrepo in myserver_repos:
|
|
mydbconn = self.open_server_repository(myrepo, read_only = True,
|
|
no_upload = True)
|
|
mylist = mydbconn.getPackagesToRemove(
|
|
mydata['name'],
|
|
mydata['category'],
|
|
mydata['slot'],
|
|
mydata['injected']
|
|
)
|
|
for myitem in mylist:
|
|
trashing_counters.add(mydbconn.retrieveSpmUid(myitem))
|
|
|
|
for mycounter in trashing_counters:
|
|
dbconn.setTrashedUid(mycounter)
|
|
|
|
# make sure that info have been written to disk, again
|
|
dbconn.commit()
|
|
|
|
atom = dbconn.retrieveAtom(idpackage)
|
|
self.output(
|
|
"[%s] %s: %s %s: %s" % (
|
|
darkgreen(repository_id),
|
|
blue(_("added package")),
|
|
darkgreen(atom),
|
|
blue(_("rev")), # as in revision
|
|
bold(str(revision)),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
|
|
manual_deps = sorted(dbconn.retrieveManualDependencies(idpackage,
|
|
resolve_conditional_deps = False))
|
|
if manual_deps:
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
darkgreen(repository_id),
|
|
blue(_("manual dependencies for")),
|
|
darkgreen(atom),
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkgreen(" ## ")
|
|
)
|
|
for m_dep in manual_deps:
|
|
self.output(
|
|
brown(m_dep),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkred(" # ")
|
|
)
|
|
|
|
download_url = self._setup_repository_package_filename(dbconn,
|
|
idpackage)
|
|
destination_path = self.complete_local_upload_package_path(
|
|
download_url, repository_id)
|
|
destination_dir = os.path.dirname(destination_path)
|
|
self._ensure_dir_path(destination_dir)
|
|
|
|
# since destination_path determines the final path name
|
|
# make sure it corresponds to the one in package_files
|
|
# but copy the object first (shallow)
|
|
move_files = package_files[:]
|
|
package_file = move_files[0]
|
|
# rename to final name first
|
|
final_path = os.path.join(os.path.dirname(package_file),
|
|
os.path.basename(destination_path))
|
|
if package_file != final_path:
|
|
os.rename(package_file, final_path)
|
|
move_files[0] = final_path
|
|
|
|
# run in reverse order, this way, the base package file will
|
|
# be moved at the end, resulting in improved reliability.
|
|
destination_paths = []
|
|
for package_file in reversed(move_files):
|
|
final_path = os.path.join(destination_dir,
|
|
os.path.basename(package_file))
|
|
destination_paths.append(final_path)
|
|
try:
|
|
os.rename(package_file, final_path)
|
|
except OSError as err:
|
|
if err.errno != errno.EXDEV:
|
|
raise
|
|
shutil.move(package_file, final_path)
|
|
|
|
# make sure that info have been written to disk, again
|
|
dbconn.commit()
|
|
|
|
# reverse it again, since we wrote it reversed already
|
|
destination_paths.reverse()
|
|
return idpackage, destination_paths
|
|
|
|
# this function changes the final repository package filename
|
|
def _setup_repository_package_filename(self, dbconn, idpackage):
|
|
|
|
downloadurl = dbconn.retrieveDownloadURL(idpackage)
|
|
packagerev = dbconn.retrieveRevision(idpackage)
|
|
downloaddir = os.path.dirname(downloadurl)
|
|
downloadfile = os.path.basename(downloadurl)
|
|
# add revision
|
|
pkg_ext = etpConst['packagesext']
|
|
downloadfile = downloadfile[:-len(pkg_ext)]+"~%s%s" % (packagerev,
|
|
pkg_ext,)
|
|
downloadurl = os.path.join(downloaddir, downloadfile)
|
|
|
|
# update url
|
|
dbconn.setDownloadURL(idpackage, downloadurl)
|
|
dbconn.commit()
|
|
|
|
return downloadurl
|
|
|
|
def __user_filter_out_missing_deps(self, pkg_repo, entropy_repository,
|
|
missing_map, ask):
|
|
|
|
def _show_missing_deps(missing_deps):
|
|
self.output(
|
|
"[%s] %s:" % (
|
|
darkgreen(pkg_repo),
|
|
teal(_("these are the missing dependencies")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = purple(" @@ ")
|
|
)
|
|
for pkg_match, deps in missing_deps.items():
|
|
pkg_id, repo = pkg_match
|
|
atom = entropy_repository.retrieveAtom(pkg_id)
|
|
slot = entropy_repository.retrieveSlot(pkg_id)
|
|
self.output(
|
|
"%s:%s" % (
|
|
teal(atom),
|
|
purple(slot),
|
|
),
|
|
level = "info",
|
|
header = " :: "
|
|
)
|
|
for dep in sorted(deps):
|
|
self.output(
|
|
brown(dep),
|
|
level = "info",
|
|
header = red(" # ")
|
|
)
|
|
|
|
missing_deps = {}
|
|
if not ask:
|
|
# not interactive, add everything
|
|
for pkg_match, missing_extended in missing_map.items():
|
|
if not missing_extended:
|
|
continue
|
|
obj = missing_deps.setdefault(pkg_match, set())
|
|
for dep_list in missing_extended.values():
|
|
obj.update(dep_list)
|
|
|
|
_show_missing_deps(missing_deps)
|
|
return missing_deps
|
|
|
|
header_txt = """\
|
|
# Some missing dependencies have been found.
|
|
# Please use this text file to commit the ones you want
|
|
# to get added to their respective packages.
|
|
#
|
|
# HOWTO: just remove the lines containing unwanted dependencies and
|
|
# keep those you think are sane.
|
|
#
|
|
# IMPORTANT:
|
|
# Lines starting with "#" are comments and will be ignored.
|
|
# Lines starting with "##" are reserved to this application for
|
|
# parsing purposes.
|
|
|
|
|
|
"""
|
|
|
|
editor_lines = []
|
|
for pkg_match, missing_extended in missing_map.items():
|
|
|
|
if not missing_extended:
|
|
# wtf
|
|
continue
|
|
|
|
pkg_id, missing_pkg_repo = pkg_match
|
|
if pkg_repo != missing_pkg_repo:
|
|
raise AttributeError("this cannot happen")
|
|
|
|
atom = entropy_repository.retrieveAtom(pkg_id)
|
|
slot = entropy_repository.retrieveSlot(pkg_id)
|
|
line = "## %s %s => %s:%s" % (pkg_id, pkg_repo, atom, slot)
|
|
editor_lines.append(const_convert_to_unicode(line))
|
|
|
|
for lib_match, dep_list in missing_extended.items():
|
|
library, elf = lib_match
|
|
line = "# %s, %s" % (library, elf)
|
|
editor_lines.append(const_convert_to_unicode(line))
|
|
for dep in sorted(dep_list):
|
|
editor_lines.append(dep)
|
|
editor_lines.append(const_convert_to_unicode(""))
|
|
editor_lines.append(const_convert_to_unicode(""))
|
|
|
|
if not editor_lines:
|
|
# wtf!?
|
|
return {}
|
|
|
|
enc = etpConst['conf_encoding']
|
|
tmp_path = None
|
|
while True:
|
|
|
|
if tmp_path is None:
|
|
tmp_fd, tmp_path = tempfile.mkstemp(prefix = 'entropy.server',
|
|
suffix = ".conf")
|
|
with entropy.tools.codecs_fdopen(tmp_fd, "w", enc) as tmp_f:
|
|
tmp_f.write(header_txt)
|
|
for editor_line in editor_lines:
|
|
tmp_f.write(editor_line)
|
|
tmp_f.write("\n")
|
|
tmp_f.flush()
|
|
|
|
success = self.edit_file(tmp_path)
|
|
if not success:
|
|
# retry ?
|
|
os.remove(tmp_path)
|
|
tmp_path = None
|
|
continue
|
|
|
|
# parse the file back, build missing_deps
|
|
all_good = True
|
|
missing_deps = {}
|
|
with codecs.open(tmp_path, "r", encoding=enc) as tmp_f:
|
|
pkg_match = None
|
|
for line in tmp_f.readlines():
|
|
line = line.strip()
|
|
|
|
if not line:
|
|
# empty line
|
|
continue
|
|
|
|
elif line.startswith("##"):
|
|
# new package match, hopefully
|
|
line_data = line.lstrip("## ").split()
|
|
if len(line_data) < 2:
|
|
# something is really bad
|
|
all_good = False
|
|
break
|
|
try:
|
|
pkg_id = int(line_data[0])
|
|
except ValueError:
|
|
# something is really bad
|
|
all_good = False
|
|
break
|
|
|
|
repo = line_data[1]
|
|
if repo != pkg_repo:
|
|
# bad here too
|
|
all_good = False
|
|
break
|
|
|
|
pkg_match = pkg_id, repo
|
|
continue
|
|
|
|
elif line.startswith("#"):
|
|
# ignore comment line
|
|
continue
|
|
|
|
elif pkg_match:
|
|
# real line, should contain a valid dependency string
|
|
# if pkg_match is set
|
|
obj = missing_deps.setdefault(pkg_match, set())
|
|
obj.add(line)
|
|
|
|
if not all_good:
|
|
os.remove(tmp_path)
|
|
tmp_path = None
|
|
continue
|
|
|
|
if not missing_deps:
|
|
self.output(
|
|
"[%s] %s:" % (
|
|
darkgreen(pkg_repo),
|
|
teal(_("no missing dependencies !")),
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = red(" @@ ")
|
|
)
|
|
else:
|
|
_show_missing_deps(missing_deps)
|
|
|
|
# ask confirmation
|
|
while True:
|
|
try:
|
|
rc_question = self.ask_question(
|
|
"[%s] %s" % (
|
|
purple(pkg_repo),
|
|
teal(_("Do you agree?"))
|
|
),
|
|
responses = (_("Yes"), _("Repeat"),)
|
|
)
|
|
except KeyboardInterrupt:
|
|
# do not allow, we're in a critical region
|
|
continue
|
|
break
|
|
if rc_question == _("Yes"):
|
|
break
|
|
# otherwise repeat everything again
|
|
# keep tmp_path
|
|
|
|
|
|
if tmp_path is not None:
|
|
try:
|
|
os.remove(tmp_path)
|
|
except (OSError) as err:
|
|
if err.errno != errno.ENOENT:
|
|
raise
|
|
return missing_deps
|
|
|
|
def missing_runtime_dependencies_test(self, package_matches, ask = True,
|
|
bump_packages = False):
|
|
"""
|
|
Use Entropy QA interface to check package matches against missing
|
|
runtime dependencies, adding them.
|
|
|
|
@param package_matches: list of Entropy package matches
|
|
@type package_matches: list
|
|
@keyword ask: if missing runtime dependencies should be validated
|
|
interactively
|
|
@type ask: bool
|
|
@keyword bump_packages: True, if packages should be bumped (revision
|
|
bumped)
|
|
@type bump_packages: bool
|
|
"""
|
|
blacklisted_deps = \
|
|
self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['dep_blacklist']
|
|
my_qa = self.QA()
|
|
|
|
def get_blacklisted_deps(package_ids, repo_db):
|
|
my_blacklist = set()
|
|
for pkg_dep, bl_pkg_deps in blacklisted_deps.items():
|
|
pkg_ids, rc = repo_db.atomMatch(pkg_dep, multiMatch = True)
|
|
for package_id in package_ids:
|
|
if package_id in pkg_ids:
|
|
my_blacklist.update(bl_pkg_deps)
|
|
break
|
|
return my_blacklist
|
|
|
|
pkg_map = {}
|
|
for pkg_id, pkg_repo in package_matches:
|
|
obj = pkg_map.setdefault(pkg_repo, [])
|
|
obj.append(pkg_id)
|
|
|
|
for pkg_repo, package_ids in pkg_map.items():
|
|
|
|
repo_blacklist = self._get_missing_dependencies_blacklist(pkg_repo)
|
|
|
|
dbconn = self.open_server_repository(pkg_repo, read_only = False,
|
|
no_upload = True)
|
|
pkg_blacklisted_deps = get_blacklisted_deps(package_ids, dbconn)
|
|
pkg_blacklisted_deps |= repo_blacklist
|
|
|
|
# missing dependencies check
|
|
missing_map = my_qa.test_missing_dependencies(
|
|
self, [(x, pkg_repo) for x in package_ids],
|
|
self_check = True, black_list = pkg_blacklisted_deps)
|
|
|
|
missing_deps = self.__user_filter_out_missing_deps(pkg_repo,
|
|
dbconn, missing_map, ask)
|
|
|
|
for (pkg_id, missing_pkg_repo), missing in missing_deps.items():
|
|
if pkg_repo != missing_pkg_repo:
|
|
# with current API, this never happens!
|
|
# but since this is a critical region, better being
|
|
# safe than sorry.
|
|
# pkg_repo is always the same...
|
|
raise AssertionError(
|
|
"pkg_repo and missing_pkg_repo must be equal")
|
|
|
|
if bump_packages:
|
|
# in this case, a new package should be generated, with
|
|
# bumped revision
|
|
pkg_data = dbconn.getPackageData(pkg_id)
|
|
# also bump injected packages properly
|
|
original_injected = pkg_data['injected']
|
|
pkg_data['injected'] = False
|
|
pkg_id = dbconn.handlePackage(pkg_data)
|
|
if original_injected:
|
|
dbconn.setInjected(pkg_id)
|
|
# make sure that info have been written to disk
|
|
dbconn.commit()
|
|
|
|
# NOTE that missing is a list here, so no fancy
|
|
# dependency type is set.
|
|
dbconn.insertDependencies(pkg_id, missing)
|
|
|
|
if missing_deps:
|
|
# save changes here again
|
|
dbconn.commit()
|
|
|
|
def _external_metadata_qa_hook_test(self, package_matches):
|
|
"""
|
|
Execute external metadata QA check, if executable exists.
|
|
Returns True for success, False for error. Warnings are not considered
|
|
blocking.
|
|
"""
|
|
qa_exec = os.path.join(SystemSettings.packages_config_directory(),
|
|
"packages.server.qa.exec")
|
|
if not os.path.isfile(qa_exec):
|
|
return True
|
|
if not os.access(qa_exec, os.X_OK | os.R_OK | os.F_OK):
|
|
return True
|
|
# avoid privs escalation
|
|
st = os.stat(qa_exec)
|
|
file_uid = st[stat.ST_UID]
|
|
file_gid = st[stat.ST_GID]
|
|
if not ((file_uid == 0) and (file_gid == 0)):
|
|
self.output(
|
|
"[%s] %s: %s, %s" % (
|
|
purple("qa"),
|
|
brown(_("metadata QA hook")),
|
|
purple(qa_exec),
|
|
brown(_("not owned by uid and gid = 0")),
|
|
),
|
|
importance = 1,
|
|
level = "error",
|
|
header = darkred(" !!! "),
|
|
)
|
|
return False
|
|
|
|
count = 0
|
|
maxcount = len(package_matches)
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
purple("qa"),
|
|
teal(_("using metadata QA hook")),
|
|
darkgreen(qa_exec),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" @@ ")
|
|
)
|
|
qa_success = True
|
|
for package_id, repository_id in package_matches:
|
|
count += 1
|
|
repo_db = self.open_server_repository(repository_id,
|
|
read_only = False, no_upload = True)
|
|
|
|
pkg_atom, pkg_name, pkg_version, pkg_tag, \
|
|
pkg_description, pkg_category, pkg_chost, \
|
|
pkg_cflags, pkg_cxxflags, pkg_homepage, \
|
|
pkg_license, pkg_branch, pkg_uri, \
|
|
pkg_md5, pkg_slot, pkg_etpapi, \
|
|
pkg_date, pkg_size, pkg_rev = repo_db.getBaseData(package_id)
|
|
pkg_deps = repo_db.retrieveDependenciesList(package_id)
|
|
pkg_needed = repo_db.retrieveNeeded(package_id, extended = True)
|
|
pkg_provided_libs = repo_db.retrieveProvidedLibraries(package_id)
|
|
pkg_keywords = repo_db.retrieveKeywords(package_id)
|
|
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
purple("qa"),
|
|
teal(_("metadata QA check for")),
|
|
darkgreen(pkg_atom),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" @@ "),
|
|
count = (count, maxcount),
|
|
back = True
|
|
)
|
|
env = os.environ.copy()
|
|
env['REPOSITORY_ID'] = str(repository_id)
|
|
env['PKG_ID'] = str(package_id)
|
|
env['PKG_ATOM'] = str(pkg_atom)
|
|
env['PKG_NAME'] = str(pkg_name)
|
|
env['PKG_VERSION'] = str(pkg_version)
|
|
env['PKG_TAG'] = str(pkg_tag or "")
|
|
env['PKG_DESCRIPTION'] = const_convert_to_rawstring(pkg_description)
|
|
env['PKG_CATEGORY'] = str(pkg_category)
|
|
env['PKG_CHOST'] = str(pkg_chost)
|
|
env['PKG_CFLAGS'] = str(pkg_cflags)
|
|
env['PKG_CXXFLAGS'] = str(pkg_cxxflags)
|
|
env['PKG_HOMEPAGE'] = const_convert_to_rawstring(pkg_homepage)
|
|
env['PKG_LICENSE'] = str(pkg_license)
|
|
env['PKG_BRANCH'] = str(pkg_branch)
|
|
env['PKG_KEYWORDS'] = str(" ".join(pkg_keywords))
|
|
env['PKG_DOWNLOAD'] = str(pkg_uri)
|
|
env['PKG_MD5'] = str(pkg_md5)
|
|
env['PKG_SLOT'] = str(pkg_slot)
|
|
env['PKG_ETPAPI'] = str(pkg_etpapi)
|
|
env['PKG_DATE'] = str(pkg_date)
|
|
env['PKG_SIZE'] = str(pkg_size)
|
|
env['PKG_REVISION'] = str(pkg_rev)
|
|
env['PKG_DEPS'] = str("\n".join(pkg_deps))
|
|
env['PKG_NEEDED_LIBS'] = str("\n".join(["%s|%s" % (x, y) \
|
|
for x, y in pkg_needed]))
|
|
env['PKG_PROVIDED_LIBS'] = str("\n".join(
|
|
["%s|%s|%s" % (x, y, z) for x, y, z in \
|
|
pkg_provided_libs]))
|
|
|
|
# now call the script
|
|
try:
|
|
proc = subprocess.Popen(
|
|
[qa_exec], stdout = sys.stdout, stderr = sys.stderr,
|
|
stdin = sys.stdin, env = env)
|
|
rc = proc.wait()
|
|
except OSError as err:
|
|
self.output(
|
|
"[%s] %s: %s, %s" % (
|
|
purple("qa"),
|
|
bold(_("cannot execute metadata QA hook for")),
|
|
darkgreen(pkg_atom),
|
|
repr(err),
|
|
),
|
|
importance = 1,
|
|
level = "error",
|
|
header = darkred(" !!! "),
|
|
count = (count, maxcount)
|
|
)
|
|
rc = 2
|
|
if const_debug_enabled():
|
|
import pdb
|
|
pdb.set_trace()
|
|
|
|
if rc == 0:
|
|
# all good
|
|
continue
|
|
elif rc == 1:
|
|
self.output("",
|
|
importance = 0,
|
|
level = "warning",
|
|
header = darkred(" !!! ")
|
|
)
|
|
self.output(
|
|
"[%s] %s !" % (
|
|
darkred("qa"),
|
|
brown(_("attention, QA hook returned a warning")),
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkred(" !!! "),
|
|
count = (count, maxcount)
|
|
)
|
|
self.output("",
|
|
importance = 0,
|
|
level = "warning",
|
|
header = darkred(" !!! ")
|
|
)
|
|
else:
|
|
# anything != 0 and 1 is considered error
|
|
self.output("",
|
|
importance = 0,
|
|
level = "warning",
|
|
header = darkred(" !!! ")
|
|
)
|
|
self.output(
|
|
"[%s] %s !" % (
|
|
darkred("qa"),
|
|
brown(_("attention, QA hook returned an error")),
|
|
),
|
|
importance = 1,
|
|
level = "error",
|
|
header = darkred(" !!! "),
|
|
count = (count, maxcount)
|
|
)
|
|
self.output("",
|
|
importance = 0,
|
|
level = "warning",
|
|
header = darkred(" !!! ")
|
|
)
|
|
qa_success = False
|
|
|
|
self.output(
|
|
"[%s] %s" % (
|
|
purple("qa"),
|
|
teal(_("metadata QA check complete")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" @@ ")
|
|
)
|
|
|
|
return qa_success
|
|
|
|
def _add_packages_qa_tests(self, package_matches, ask = True):
|
|
"""
|
|
Execute some generic QA checks for broken libraries on packages added
|
|
to repository.
|
|
"""
|
|
self.missing_runtime_dependencies_test(package_matches, ask = ask)
|
|
|
|
my_settings = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
my_qa = self.QA()
|
|
my_qa.test_missing_runtime_libraries(self, package_matches,
|
|
base_repository_id = my_settings['base_repository_id'])
|
|
|
|
qa_success = self._external_metadata_qa_hook_test(package_matches)
|
|
if my_settings['broken_revdeps_qa_check']:
|
|
my_qa.test_reverse_dependencies_linking(self, package_matches)
|
|
return qa_success
|
|
|
|
def add_packages_to_repository(self, repository_id, packages_data,
|
|
ask = True):
|
|
"""
|
|
Add package files to given repository. packages_data contains a list
|
|
of tuples composed by (path to package files, execute_injection boolean).
|
|
Injection is a way to avoid a package being removed from the repository
|
|
automatically when an updated package is added.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@param packages_data: list of tuples composed by ([path, path1], bool)
|
|
The first path object represents the main package file.
|
|
While the others represent further package files.
|
|
If one ends with eptConst['packagesdebugext'], it will be considered
|
|
as debuginfo package file.
|
|
@type packages_data: list
|
|
@return: list (set) of package identifiers added
|
|
@rtype: set
|
|
"""
|
|
mycount = 0
|
|
maxcount = len(packages_data)
|
|
idpackages_added = set()
|
|
to_be_injected = set()
|
|
|
|
for package_filepaths, inject in packages_data:
|
|
|
|
mycount += 1
|
|
for package_filepath in package_filepaths:
|
|
header = blue(" @@ ")
|
|
count = (mycount, maxcount,)
|
|
if package_filepaths[0] != package_filepath:
|
|
self.output(
|
|
"%s" % (
|
|
brown(os.path.basename(package_filepath)),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = teal(" # ")
|
|
)
|
|
else:
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
darkgreen(repository_id),
|
|
blue(_("adding package")),
|
|
darkgreen(os.path.basename(package_filepath)),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" @@ "),
|
|
count = (mycount, maxcount,)
|
|
)
|
|
|
|
if inject and len(package_filepaths) == 1:
|
|
# just make sure user is aware of the fact that no separate
|
|
# debug packages will be made.
|
|
self.output(
|
|
"%s" % (
|
|
brown(_("injected package, no separate debug package")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = teal(" !! ")
|
|
)
|
|
|
|
try:
|
|
# add to database
|
|
idpackage, destination_paths = self._package_injector(
|
|
repository_id, package_filepaths, inject = inject)
|
|
idpackages_added.add(idpackage)
|
|
to_be_injected.add((idpackage, destination_paths[0]))
|
|
except Exception as err:
|
|
entropy.tools.print_traceback()
|
|
self.output(
|
|
"[%s] %s: %s" % (
|
|
darkgreen(repository_id),
|
|
darkred(_("Exception caught, closing tasks")),
|
|
darkgreen(str(err)),
|
|
),
|
|
importance = 1,
|
|
level = "error",
|
|
header = bold(" !!! "),
|
|
count = (mycount, maxcount,)
|
|
)
|
|
# reinit librarypathsidpackage table
|
|
if idpackages_added:
|
|
self._add_packages_qa_tests(
|
|
[(x, repository_id) for x in idpackages_added],
|
|
ask = ask)
|
|
if to_be_injected:
|
|
self._inject_database_into_packages(repository_id,
|
|
to_be_injected)
|
|
self.close_repositories()
|
|
raise
|
|
|
|
# make sure packages are really available, it can happen
|
|
# after a previous failure to have garbage here
|
|
dbconn = self.open_server_repository(repository_id, just_reading = True)
|
|
idpackages_added = set((x for x in idpackages_added if \
|
|
dbconn.isPackageIdAvailable(x)))
|
|
|
|
if idpackages_added:
|
|
self._add_packages_qa_tests(
|
|
[(x, repository_id) for x in idpackages_added], ask = ask)
|
|
|
|
# inject database into packages
|
|
self._inject_database_into_packages(repository_id, to_be_injected)
|
|
|
|
return idpackages_added
|
|
|
|
def _taint_database(self, repository_id):
|
|
|
|
# taint the database status
|
|
db_file = self._get_local_repository_file(repository_id)
|
|
taint_file = self._get_local_repository_taint_file(repository_id)
|
|
with codecs.open(taint_file, "w") as f:
|
|
f.write("repository tainted\n")
|
|
f.flush()
|
|
const_setup_file(taint_file, etpConst['entropygid'], 0o664)
|
|
ServerRepositoryStatus().set_tainted(db_file)
|
|
|
|
def _bump_database(self, repository_id):
|
|
dbconn = self.open_server_repository(repository_id, read_only = False,
|
|
no_upload = True)
|
|
self._taint_database(repository_id)
|
|
dbconn.commit()
|
|
self.close_repository(dbconn)
|
|
|
|
|
|
def _ensure_paths(self, repo):
|
|
upload_dir = self._get_local_upload_directory(repo)
|
|
db_dir = self._get_local_repository_dir(repo)
|
|
for mydir in [upload_dir, db_dir]:
|
|
if (not os.path.isdir(mydir)) and (not os.path.lexists(mydir)):
|
|
os.makedirs(mydir, 0o775)
|
|
const_setup_perms(mydir, etpConst['entropygid'],
|
|
recursion = False, uid = etpConst['uid'])
|
|
|
|
def _ensure_dir_path(self, dir_path):
|
|
if not os.path.isdir(dir_path):
|
|
os.makedirs(dir_path, 0o775)
|
|
const_setup_perms(dir_path, etpConst['entropygid'],
|
|
recursion = False, uid = etpConst['uid'])
|
|
|
|
def _setup_services(self):
|
|
self._setup_entropy_settings(self._repository)
|
|
self._backup_entropy_settings()
|
|
self.Mirrors = MirrorsServer(self, self._repository)
|
|
|
|
def _setup_entropy_settings(self, repository_id):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
backup_list = [
|
|
'etpdatabaseclientfilepath',
|
|
'clientdbid',
|
|
{'server': srv_set.copy()},
|
|
]
|
|
for setting in backup_list:
|
|
if setting not in self._settings_to_backup:
|
|
self._settings_to_backup.append(setting)
|
|
# setup entropy client repo
|
|
if not srv_set['community_mode']:
|
|
repo_data = srv_set['repositories'][repository_id]
|
|
if "__temporary__" not in repo_data:
|
|
etpConst['etpdatabaseclientfilepath'] = \
|
|
self._get_local_repository_file(repository_id)
|
|
etpConst['clientdbid'] = etpConst['serverdbid']
|
|
const_create_working_dirs()
|
|
|
|
def _show_interface_status(self):
|
|
type_txt = _("server-side repository")
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
if srv_set['community_mode']:
|
|
type_txt = _("community repository")
|
|
# ..on repository: <repository_name>
|
|
mytxt = _("Entropy Server Interface Instance on repository")
|
|
self.output(
|
|
"%s: %s" % (blue(mytxt),
|
|
red(self._repository),
|
|
),
|
|
importance = 2,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
self.output(
|
|
"%s: %s (%s: %s)" % (
|
|
brown(_("current branch")),
|
|
darkgreen(self._settings['repositories']['branch']),
|
|
purple(_("type")),
|
|
bold(type_txt),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" ")
|
|
)
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
repos = list(srv_set['repositories'].keys())
|
|
mytxt = blue("%s:") % (_("Currently configured repositories"),)
|
|
self.output(
|
|
mytxt,
|
|
importance = 1,
|
|
level = "info",
|
|
header = red(" @@ ")
|
|
)
|
|
for repo in repos:
|
|
self.output(
|
|
darkgreen(repo),
|
|
importance = 0,
|
|
level = "info",
|
|
header = brown(" # ")
|
|
)
|
|
|
|
def _backup_constant(self, constant_name):
|
|
if constant_name not in etpConst:
|
|
raise KeyError("%s not available" % (constant_name,))
|
|
etpConst['backed_up'].update(
|
|
{constant_name: copy.copy(etpConst[constant_name])})
|
|
|
|
def _backup_entropy_settings(self):
|
|
for setting in self._settings_to_backup:
|
|
if isinstance(setting, const_get_stringtype()):
|
|
self._backup_constant(setting)
|
|
elif isinstance(setting, dict):
|
|
self._settings.set_persistent_setting(setting)
|
|
|
|
def _get_gpg_signature(self, repo_sec, repo, pkg_path):
|
|
try:
|
|
if not repo_sec.is_keypair_available(repo):
|
|
return None # GPG is not enabled
|
|
except RepositorySecurity.KeyExpired as err:
|
|
self.output(
|
|
"[%s] %s: %s, %s." % (
|
|
darkgreen(repo),
|
|
darkred(_("GPG key expired")),
|
|
err,
|
|
darkred(_("please frigging fix")),
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = bold(" !!! ")
|
|
)
|
|
return None
|
|
except RepositorySecurity.GPGError as err:
|
|
self.output(
|
|
"[%s] %s: %s, %s." % (
|
|
darkgreen(repo),
|
|
darkred(_("GPG got unexpected error")),
|
|
err,
|
|
darkred(_("skipping")),
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = red(" @@ ")
|
|
)
|
|
return None
|
|
gpg_sign_path = repo_sec.sign_file(repo, pkg_path)
|
|
# read file content and add to 'gpg' signature
|
|
with open(gpg_sign_path, "rb") as gpg_f:
|
|
gpg_signature = gpg_f.read()
|
|
os.remove(gpg_sign_path)
|
|
return gpg_signature
|
|
|
|
def _check_config_file_updates(self):
|
|
self.output(
|
|
"[%s] %s" % (
|
|
red(_("config files")), # something short please
|
|
blue(_("checking system")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = blue(" @@ "),
|
|
back = True
|
|
)
|
|
# scanning for config files not updated
|
|
updates = self.ConfigurationUpdates()
|
|
file_updates = updates.get()
|
|
if file_updates:
|
|
self.output(
|
|
"[%s] %s" % (
|
|
red(_("config files")), # something short please
|
|
blue(_("there are configuration files not updated yet")),
|
|
),
|
|
importance = 1,
|
|
level = "error",
|
|
header = darkred(" @@ ")
|
|
)
|
|
for val in file_updates.values():
|
|
self.output(
|
|
val['destination'],
|
|
importance = 1,
|
|
level = "info",
|
|
header = "\t"
|
|
)
|
|
return True
|
|
return False
|
|
|
|
def _get_missing_dependencies_blacklist(self, repository_id, branch = None):
|
|
if branch is None:
|
|
branch = self._settings['repositories']['branch']
|
|
wl_file = self._get_missing_dependencies_blacklist_file(repository_id,
|
|
branch = branch)
|
|
wl_data = []
|
|
if os.path.isfile(wl_file) and os.access(wl_file, os.R_OK):
|
|
f_wl = codecs.open(wl_file, "r", encoding=etpConst['conf_encoding'])
|
|
wl_data = [x.strip() for x in f_wl.readlines() if x.strip() and \
|
|
not x.strip().startswith("#")]
|
|
f_wl.close()
|
|
return set(wl_data)
|
|
|
|
def _get_package_path(self, repo, dbconn, idpackage):
|
|
"""
|
|
Given EntropyRepository instance and package identifier, return local
|
|
path of package. This method does not check for path validity though.
|
|
"""
|
|
pkg_rel_url = dbconn.retrieveDownloadURL(idpackage)
|
|
complete_path = self.complete_local_package_path(pkg_rel_url, repo)
|
|
return complete_path
|
|
|
|
def _get_upload_package_path(self, repo, dbconn, idpackage):
|
|
"""
|
|
Given ServerPackagesRepository instance and package identifier,
|
|
return local path of package.
|
|
This method does not check for path validity though.
|
|
"""
|
|
pkg_path = dbconn.retrieveDownloadURL(idpackage)
|
|
return os.path.join(self._get_local_upload_directory(repo), pkg_path)
|
|
|
|
def scan_package_changes(self):
|
|
"""
|
|
Scan, using Source Package Manager, for added/removed/updated packages.
|
|
Please note that in order to trigger SPM level package moves, it is better
|
|
to also call Spm().package_names_update() before this, or opening all the repos
|
|
in read/write (read_only=False).
|
|
|
|
@return: tuple composed of (1) list of spm package name and spm package
|
|
id, (2) list of entropy package matches for packages to be removed (3)
|
|
list of entropy package matches for packages to be injected.
|
|
"""
|
|
spm = self.Spm()
|
|
|
|
spm_packages = spm.get_installed_packages()
|
|
installed_packages = []
|
|
for spm_package in spm_packages:
|
|
try:
|
|
pkg_counter = spm.resolve_spm_package_uid(spm_package)
|
|
except KeyError:
|
|
# not found
|
|
continue
|
|
installed_packages.append((spm_package, pkg_counter,))
|
|
|
|
installed_counters = set()
|
|
to_be_added = set()
|
|
to_be_removed = set()
|
|
to_be_injected = set()
|
|
my_settings = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
exp_based_scope = my_settings['exp_based_scope']
|
|
|
|
server_repos = list(my_settings['repositories'].keys())
|
|
|
|
# packages to be added
|
|
for spm_atom, spm_counter in installed_packages:
|
|
found = False
|
|
for server_repo in server_repos:
|
|
installed_counters.add(spm_counter)
|
|
server_dbconn = self.open_server_repository(server_repo,
|
|
read_only = True, no_upload = True)
|
|
counter = server_dbconn.isSpmUidAvailable(spm_counter)
|
|
if counter:
|
|
found = True
|
|
break
|
|
if not found:
|
|
to_be_added.add((spm_atom, spm_counter,))
|
|
|
|
# packages to be removed from the database
|
|
database_counters = {}
|
|
for server_repo in server_repos:
|
|
server_dbconn = self.open_server_repository(server_repo,
|
|
read_only = True, no_upload = True)
|
|
database_counters[server_repo] = server_dbconn.listAllSpmUids()
|
|
|
|
ordered_counters = set()
|
|
for server_repo in database_counters:
|
|
for data in database_counters[server_repo]:
|
|
ordered_counters.add((data, server_repo))
|
|
database_counters = ordered_counters
|
|
|
|
# do some memoization to speed up the scanning
|
|
_spm_key_slot_map = {}
|
|
for _spm_pkg, _spm_pkg_id in to_be_added:
|
|
key = entropy.dep.dep_getkey(_spm_pkg)
|
|
obj = _spm_key_slot_map.setdefault(key, set())
|
|
try:
|
|
slot = spm.get_installed_package_metadata(
|
|
_spm_pkg, "SLOT")
|
|
# workaround for ebuilds without SLOT
|
|
if slot is None:
|
|
slot = "0"
|
|
obj.add(slot)
|
|
except KeyError:
|
|
continue
|
|
|
|
for (counter, idpackage), xrepo in database_counters:
|
|
|
|
if counter < 0:
|
|
continue # skip packages without valid counter
|
|
|
|
if counter in installed_counters:
|
|
continue
|
|
|
|
dbconn = self.open_server_repository(xrepo, read_only = True,
|
|
no_upload = True)
|
|
|
|
dorm = True
|
|
# check if the package is in to_be_added
|
|
if to_be_added:
|
|
|
|
dorm = False
|
|
atom = dbconn.retrieveAtom(idpackage)
|
|
atomkey = entropy.dep.dep_getkey(atom)
|
|
atomtag = entropy.dep.dep_gettag(atom)
|
|
atomslot = dbconn.retrieveSlot(idpackage)
|
|
add = True
|
|
|
|
spm_slots = _spm_key_slot_map.get(atomkey, [])
|
|
# atomtag != None is for handling tagged pkgs correctly
|
|
if (atomslot in spm_slots) or (atomtag is not None):
|
|
# do not add to to_be_removed
|
|
add = False
|
|
|
|
if not add:
|
|
continue
|
|
dorm = True
|
|
|
|
# checking if we are allowed to remove stuff on this repo
|
|
# if xrepo is not the default one, we MUST skip this to
|
|
# avoid touching what developer doesn't expect
|
|
if dorm and (xrepo == self._repository):
|
|
trashed = self._is_spm_uid_trashed(counter)
|
|
if trashed:
|
|
# search into portage then
|
|
try:
|
|
key, slot = dbconn.retrieveKeySlot(idpackage)
|
|
slot = slot.split(",")[0]
|
|
try:
|
|
trashed = spm.match_installed_package(
|
|
key + ":" + slot)
|
|
except KeyError:
|
|
trashed = True
|
|
except TypeError: # referred to retrieveKeySlot
|
|
trashed = True
|
|
if not trashed:
|
|
|
|
dbtag = dbconn.retrieveTag(idpackage)
|
|
if dbtag:
|
|
is_injected = dbconn.isInjected(idpackage)
|
|
if not is_injected:
|
|
to_be_injected.add((idpackage, xrepo))
|
|
|
|
elif exp_based_scope:
|
|
|
|
# check if support for this is set
|
|
plg_id = self.sys_settings_fatscope_plugin_id
|
|
exp_data = self._settings[plg_id]['repos'].get(
|
|
xrepo, set())
|
|
|
|
# only some packages are set, check if our is
|
|
# in the list
|
|
if (idpackage not in exp_data) and (-1 not in exp_data):
|
|
to_be_removed.add((idpackage, xrepo))
|
|
continue
|
|
|
|
idpackage_expired = self._is_match_expired((idpackage,
|
|
xrepo,))
|
|
|
|
if idpackage_expired:
|
|
# expired !!!
|
|
# add this and its depends (reverse deps)
|
|
|
|
rm_match = (idpackage, xrepo)
|
|
#to_be_removed.add(rm_match)
|
|
revdep_matches = self.get_reverse_queue([rm_match],
|
|
system_packages = False)
|
|
to_be_removed.update(revdep_matches)
|
|
|
|
else:
|
|
to_be_removed.add((idpackage, xrepo))
|
|
|
|
return to_be_added, to_be_removed, to_be_injected
|
|
|
|
def _is_match_expired(self, match):
|
|
|
|
idpackage, repoid = match
|
|
dbconn = self.open_server_repository(repoid, just_reading = True)
|
|
# 3600 * 24 = 86400
|
|
my_settings = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
pkg_exp_secs = my_settings['packages_expiration_days'] * 86400
|
|
cur_unix_time = time.time()
|
|
# if packages removal is triggered by expiration
|
|
# we will have to check if our package is really
|
|
# expired and remove its reverse deps too
|
|
mydate = dbconn.retrieveCreationDate(idpackage)
|
|
# cross fingers hoping that time is set correctly
|
|
mydelta = cur_unix_time - float(mydate)
|
|
if mydelta > pkg_exp_secs:
|
|
return True
|
|
return False
|
|
|
|
def _is_spm_uid_trashed(self, counter):
|
|
srv_set = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['server']
|
|
server_repos = list(srv_set['repositories'].keys())
|
|
for repo in server_repos:
|
|
dbconn = self.open_server_repository(repo, read_only = True,
|
|
no_upload = True)
|
|
if dbconn.isSpmUidTrashed(counter):
|
|
return True
|
|
return False
|
|
|
|
def _transform_package_into_injected(self, package_id, repository_id):
|
|
dbconn = self.open_server_repository(repository_id, read_only = False,
|
|
no_upload = True)
|
|
counter = dbconn.getFakeSpmUid()
|
|
dbconn.setSpmUid(package_id, counter)
|
|
dbconn.setInjected(package_id)
|
|
dbconn.commit()
|
|
|
|
def _pump_extracted_package_metadata(self, pkg_meta, repo, extra_metadata):
|
|
"""
|
|
Add to pkg_meta dict, server-side package metadata information before
|
|
injecting it into repository database.
|
|
"""
|
|
# add extra metadata
|
|
pkg_meta.update(extra_metadata)
|
|
# do not set GPG signature here for performance, just provide an
|
|
# empty default. Real GPG signature will be written inside
|
|
# _inject_database_into_packages()
|
|
pkg_meta['signatures']['gpg'] = None
|
|
|
|
# rewrite dependency strings using dep_rewrite metadata
|
|
self.__handle_dep_rewrite(pkg_meta, repo)
|
|
|
|
def __handle_dep_rewrite(self, pkg_meta, repo):
|
|
|
|
dep_rewrite = self._settings[Server.SYSTEM_SETTINGS_PLG_ID]['dep_rewrite']
|
|
|
|
# NOTE: to be able to match the package, we need to add it
|
|
# to a temp repo
|
|
tmp_repo = self._open_temp_repository("dep_rewrite_temp",
|
|
temp_file = ":memory:")
|
|
new_idpackage = tmp_repo.handlePackage(pkg_meta)
|
|
pkg_atom = tmp_repo.retrieveAtom(new_idpackage)
|
|
|
|
rewrites_enabled = []
|
|
wildcard_rewrite = False
|
|
for dep_string_rewrite, dep_pattern in dep_rewrite:
|
|
# magic catch-all support
|
|
if dep_string_rewrite == "*":
|
|
pkg_id, rc = None, 0
|
|
wildcard_rewrite = True
|
|
else:
|
|
wildcard_rewrite = False
|
|
pkg_id, rc = tmp_repo.atomMatch(dep_string_rewrite)
|
|
if rc == 0:
|
|
rewrites_enabled.append((dep_string_rewrite, dep_pattern))
|
|
tmp_repo.close()
|
|
|
|
if not rewrites_enabled:
|
|
return
|
|
|
|
if not wildcard_rewrite:
|
|
self.output(
|
|
"[%s|%s] %s:" % (
|
|
blue(repo),
|
|
brown(pkg_atom),
|
|
teal(
|
|
_("found available dep_rewrites for this package")),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = brown(" @@ ")
|
|
)
|
|
for dep_string_rewrite, dep_pattern in rewrites_enabled:
|
|
compiled_pattern, replaces = \
|
|
dep_rewrite[(dep_string_rewrite, dep_pattern)]
|
|
if compiled_pattern is None:
|
|
# this means that user is asking to add dep_pattern
|
|
# as a dependency to package
|
|
replaces_str = _("added")
|
|
elif not replaces:
|
|
# this means that user is asking to remove dep_pattern
|
|
replaces_str = _("removed")
|
|
else:
|
|
replaces_str = "=> " + ', '.join(replaces)
|
|
self.output(
|
|
"%s %s" % (
|
|
purple(dep_pattern),
|
|
replaces_str,
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = teal(" # ")
|
|
)
|
|
|
|
def _extract_dep_add_from_dep_pattern(_dep_pattern):
|
|
"""
|
|
when action is to add a dependency, extracts the dependency type
|
|
from dependency string, if found.
|
|
"""
|
|
_dep_type = etpConst['dependency_type_ids']['mdepend_id']
|
|
bracket_start_idx = _dep_pattern.rfind("<")
|
|
_is_conflict = False
|
|
|
|
if _dep_pattern.endswith(">") and (bracket_start_idx != -1):
|
|
_c_dep_type = _dep_pattern[bracket_start_idx+1:-1]
|
|
try:
|
|
_c_dep_type = int(_c_dep_type)
|
|
if _c_dep_type not in (1, 2, 3, 4):
|
|
raise ValueError()
|
|
except ValueError:
|
|
# cannot correctly evaluate, giving up silently
|
|
return _dep_pattern, _dep_type
|
|
|
|
_dep_pattern = _dep_pattern[:bracket_start_idx]
|
|
# given packages.server.dep_rewrite.example specifications
|
|
# this is the mapping:
|
|
if _c_dep_type == 1:
|
|
_dep_type = etpConst['dependency_type_ids']['bdepend_id']
|
|
elif _c_dep_type == 2:
|
|
_dep_type = etpConst['dependency_type_ids']['rdepend_id']
|
|
elif _c_dep_type == 3:
|
|
_dep_type = etpConst['dependency_type_ids']['pdepend_id']
|
|
elif _c_dep_type == 4:
|
|
# manual dependency
|
|
_dep_type = etpConst['dependency_type_ids']['mdepend_id']
|
|
|
|
if _dep_pattern.startswith("!"):
|
|
_is_conflict = True
|
|
_dep_pattern = _dep_pattern[1:]
|
|
|
|
return _dep_pattern, _dep_type, _is_conflict
|
|
|
|
# pkg_meta['conflicts'] is a frozenset
|
|
conflicts = set(pkg_meta['conflicts'])
|
|
|
|
for dep_string, dep_value in pkg_meta['dependencies'].items():
|
|
|
|
dep_string_matched = False
|
|
matched_pattern = False
|
|
|
|
for key in rewrites_enabled:
|
|
|
|
compiled_pattern, replaces = dep_rewrite[key]
|
|
if compiled_pattern is None:
|
|
# user is asking to add dep_pattern to dependency list
|
|
dep_pattern_string, dep_pattern_type, conflict = \
|
|
_extract_dep_add_from_dep_pattern(dep_pattern)
|
|
if conflict:
|
|
conflicts.add(dep_pattern_string)
|
|
else:
|
|
pkg_meta['dependencies'][dep_pattern_string] = \
|
|
dep_pattern_type
|
|
continue
|
|
|
|
if not compiled_pattern.match(dep_string):
|
|
# dep_string not matched, skipping
|
|
continue
|
|
matched_pattern = True
|
|
|
|
if not replaces:
|
|
# then it's a removal
|
|
dep_string_matched = True
|
|
|
|
for replace in replaces:
|
|
new_dep_string, number_of_subs_made = \
|
|
compiled_pattern.subn(replace, dep_string)
|
|
if number_of_subs_made:
|
|
dep_string_matched = True
|
|
if new_dep_string and (new_dep_string != "-"):
|
|
pkg_meta['dependencies'][new_dep_string] = dep_value
|
|
self.output(
|
|
"%s: %s => %s" % (
|
|
teal(_("replaced")),
|
|
brown(dep_string),
|
|
purple(new_dep_string),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = purple(" ! ")
|
|
)
|
|
else:
|
|
self.output(
|
|
"%s: %s => X" % (
|
|
teal(_("removed")),
|
|
brown(dep_string),
|
|
),
|
|
importance = 1,
|
|
level = "info",
|
|
header = purple(" ! ")
|
|
)
|
|
|
|
else:
|
|
self.output(
|
|
"%s: %s + %s" % (
|
|
darkred(_("No dependency rewrite made for")),
|
|
brown(dep_string),
|
|
purple(replace),
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkred(" !!! ")
|
|
)
|
|
|
|
if dep_string_matched:
|
|
del pkg_meta['dependencies'][dep_string]
|
|
elif (not dep_string_matched) and matched_pattern:
|
|
self.output(
|
|
"%s: %s :: %s" % (
|
|
darkred(_("No dependency rewrite made for")),
|
|
brown(pkg_atom),
|
|
purple(dep_string),
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkred(" !x!x!x! ")
|
|
)
|
|
|
|
# save conflicts metadata back in place
|
|
pkg_meta['conflicts'] = frozenset(conflicts)
|
|
|
|
def _get_entropy_sets(self, repository_id, branch = None):
|
|
if branch is None:
|
|
branch = self._settings['repositories']['branch']
|
|
sets_dir = self._get_local_database_sets_dir(repository_id,
|
|
branch = branch)
|
|
if not (os.path.isdir(sets_dir) and os.access(sets_dir, os.R_OK)):
|
|
return {}
|
|
|
|
mydata = {}
|
|
items = os.listdir(sets_dir)
|
|
for item in items:
|
|
|
|
try:
|
|
item_clean = str(item)
|
|
except (UnicodeEncodeError, UnicodeDecodeError,):
|
|
continue
|
|
item_path = os.path.join(sets_dir, item)
|
|
if not (os.path.isfile(item_path) and \
|
|
os.access(item_path, os.R_OK)):
|
|
continue
|
|
item_elements = self._settings._extract_packages_from_set_file(
|
|
item_path)
|
|
if item_elements:
|
|
mydata[item_clean] = item_elements.copy()
|
|
|
|
return mydata
|
|
|
|
def _get_configured_package_sets(self, repository_id):
|
|
|
|
branch = self._settings['repositories']['branch']
|
|
# portage sets
|
|
sets_data = self.Spm().get_package_sets(False)
|
|
sets_data.update(self._get_entropy_sets(repository_id, branch = branch))
|
|
|
|
invalid_sets = set()
|
|
# validate
|
|
for setname in sets_data:
|
|
good = True
|
|
for atom in sets_data[setname]:
|
|
if atom.startswith(etpConst['packagesetprefix']):
|
|
# ignore nested package sets
|
|
continue
|
|
dbconn = self.open_server_repository(repository_id,
|
|
just_reading = True)
|
|
match = dbconn.atomMatch(atom)
|
|
if match[0] == -1:
|
|
good = False
|
|
break
|
|
if not good:
|
|
invalid_sets.add(setname)
|
|
for invalid_set in invalid_sets:
|
|
del sets_data[invalid_set]
|
|
|
|
return sets_data
|
|
|
|
def _update_package_sets(self, repoitory_id, entropy_repository):
|
|
|
|
self.output(
|
|
"[%s|%s] %s..." % (
|
|
darkgreen(repoitory_id),
|
|
purple(_("sets")),
|
|
blue(_("updating package sets")),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = blue(" @@ "),
|
|
back = True
|
|
)
|
|
|
|
package_sets = self._get_configured_package_sets(repoitory_id)
|
|
|
|
# tell what package sets got added, and what got removed
|
|
current_sets = set(entropy_repository.retrievePackageSets())
|
|
configured_sets = set(package_sets)
|
|
new_sets = sorted(configured_sets - current_sets)
|
|
removed_sets = sorted(current_sets - configured_sets)
|
|
for new_set in new_sets:
|
|
self.output(
|
|
"[%s|%s] %s: %s" % (
|
|
darkgreen(repoitory_id),
|
|
purple(_("sets")),
|
|
blue(_("adding package set")),
|
|
brown(new_set),
|
|
),
|
|
importance = 0,
|
|
level = "info",
|
|
header = darkgreen(" @@ ")
|
|
)
|
|
for removed_set in removed_sets:
|
|
self.output(
|
|
"[%s|%s] %s: %s" % (
|
|
teal(repoitory_id),
|
|
brown(_("sets")),
|
|
purple(_("removing package set")),
|
|
bold(removed_set),
|
|
),
|
|
importance = 1,
|
|
level = "warning",
|
|
header = darkred(" @@ ")
|
|
)
|
|
|
|
entropy_repository.clearPackageSets()
|
|
if package_sets:
|
|
entropy_repository.insertPackageSets(package_sets)
|