summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Bcfg2/Server/Plugins')
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Apt.py21
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Collection.py35
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Layman.py133
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py2
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Portage.py328
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Source.py54
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Yum.py5
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/__init__.py26
-rw-r--r--src/lib/Bcfg2/Server/Plugins/PkgVars.py65
9 files changed, 641 insertions, 28 deletions
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py b/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py
index 27a725f23..de16dabcf 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py
@@ -76,11 +76,9 @@ class AptSource(Source):
def read_files(self):
bdeps = dict()
+ brecs = dict()
bprov = dict()
self.essentialpkgs = set()
- depfnames = ['Depends', 'Pre-Depends']
- if self.recommended:
- depfnames.append('Recommends')
for fname in self.files:
if not self.rawurl:
barch = [x
@@ -92,6 +90,7 @@ class AptSource(Source):
barch = self.arches[0]
if barch not in bdeps:
bdeps[barch] = dict()
+ brecs[barch] = dict()
bprov[barch] = dict()
try:
reader = gzip.GzipFile(fname)
@@ -106,9 +105,10 @@ class AptSource(Source):
pkgname = words[1].strip().rstrip()
self.pkgnames.add(pkgname)
bdeps[barch][pkgname] = []
+ brecs[barch][pkgname] = []
elif words[0] == 'Essential' and self.essential:
self.essentialpkgs.add(pkgname)
- elif words[0] in depfnames:
+ elif words[0] in ['Depends', 'Pre-Depends', 'Recommends']:
vindex = 0
for dep in words[1].split(','):
if '|' in dep:
@@ -119,17 +119,24 @@ class AptSource(Source):
barch,
vindex)
vindex += 1
- bdeps[barch][pkgname].append(dyn_dname)
+
+ if words[0] == 'Recommends':
+ brecs[barch][pkgname].append(dyn_dname)
+ else:
+ bdeps[barch][pkgname].append(dyn_dname)
bprov[barch][dyn_dname] = set(cdeps)
else:
raw_dep = re.sub(r'\(.*\)', '', dep)
raw_dep = raw_dep.rstrip().strip()
- bdeps[barch][pkgname].append(raw_dep)
+ if words[0] == 'Recommends':
+ brecs[barch][pkgname].append(raw_dep)
+ else:
+ bdeps[barch][pkgname].append(raw_dep)
elif words[0] == 'Provides':
for pkg in words[1].split(','):
dname = pkg.rstrip().strip()
if dname not in bprov[barch]:
bprov[barch][dname] = set()
bprov[barch][dname].add(pkgname)
- self.process_files(bdeps, bprov)
+ self.process_files(bdeps, bprov, brecs)
read_files.__doc__ = Source.read_files.__doc__
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py b/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py
index 39c51f351..4f630fecf 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py
@@ -308,7 +308,7 @@ class Collection(list, Bcfg2.Server.Plugin.Debuggable):
return any(source.is_virtual_package(self.metadata, package)
for source in self)
- def get_deps(self, package):
+ def get_deps(self, package, recs=None, pinnings=None):
""" Get a list of the dependencies of the given package.
The base implementation simply aggregates the results of
@@ -316,11 +316,35 @@ class Collection(list, Bcfg2.Server.Plugin.Debuggable):
:param package: The name of the symbol, but see :ref:`pkg-objects`
:type package: string
+ :param pinnings: Mapping from package names to source names.
+ :type pinnings: dict
:returns: list of strings, but see :ref:`pkg-objects`
"""
+ recommended = None
+ if recs and package in recs:
+ recommended = recs[package]
+
+ pin_found = False
+ pin_source = None
+ if pinnings and package in pinnings:
+ pin_source = pinnings[package]
+
for source in self:
+ if pin_source and source.name not in pin_source:
+ continue
+ pin_found = True
+
if source.is_package(self.metadata, package):
- return source.get_deps(self.metadata, package)
+ return source.get_deps(self.metadata, package, recommended)
+
+ if not pin_found:
+ if pin_source:
+ self.logger.error("Packages: Source '%s' for package '%s' not found" %
+ (' or '.join(pin_source), package))
+ else:
+ self.logger.error("Packages: No source found for package '%s'" %
+ package);
+
return []
def get_essential(self):
@@ -500,12 +524,15 @@ class Collection(list, Bcfg2.Server.Plugin.Debuggable):
return list(complete.difference(initial))
@Bcfg2.Server.Plugin.track_statistics()
- def complete(self, packagelist): # pylint: disable=R0912,R0914
+ def complete(self, packagelist, recommended=None,
+ pinnings=None): # pylint: disable=R0912,R0914
""" Build a complete list of all packages and their dependencies.
:param packagelist: Set of initial packages computed from the
specification.
:type packagelist: set of strings, but see :ref:`pkg-objects`
+ :param pinnings: Mapping from package names to source names.
+ :type pinnings: dict
:returns: tuple of sets - The first element contains a set of
strings (but see :ref:`pkg-objects`) describing the
complete package list, and the second element is a
@@ -564,7 +591,7 @@ class Collection(list, Bcfg2.Server.Plugin.Debuggable):
self.debug_log("Packages: handling package requirement %s" %
(current,))
packages.add(current)
- deps = self.get_deps(current)
+ deps = self.get_deps(current, recommended, pinnings)
newdeps = set(deps).difference(examined)
if newdeps:
self.debug_log("Packages: Package %s added requirements %s"
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Layman.py b/src/lib/Bcfg2/Server/Plugins/Packages/Layman.py
new file mode 100644
index 000000000..947fa0319
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Layman.py
@@ -0,0 +1,133 @@
+import os
+import layman
+import Bcfg2.Server.Plugin
+
+class LaymanSource(Bcfg2.Server.Plugin.Debuggable):
+ basegroups = ['portage', 'gentoo', 'emerge']
+ ptype = 'layman'
+ cclass = 'Portage'
+
+ def __init__(self, basepath, xsource, setup):
+ Bcfg2.Server.Plugin.Debuggable.__init__(self)
+ self.basepath = basepath
+ self.xsource = xsource
+ self.setup = setup
+
+ self.url = xsource.get('url', 'http://www.gentoo.org/proj/en/overlays/repositories.xml')
+ self.name = xsource.get('name', '')
+ self.priority = xsource.get('priority', 0)
+ self.cachefile = None
+ self.gpgkeys = []
+ self.recommended = False
+ self.essentialpkgs = set()
+ self.arches = [item.text for item in xsource.findall('Arch')]
+
+ self.url_map = [dict(version=None, component=None, arch=arch,
+ url=self.url, baseurl=self.url) for arch in self.arches]
+
+ #: A list of the the names of packages that are blacklisted
+ #: from this source
+ self.blacklist = [item.text for item in xsource.findall('Blacklist')]
+
+ #: A list of the the names of packages that are whitelisted in
+ #: this source
+ self.whitelist = [item.text for item in xsource.findall('Whitelist')]
+
+
+ # configure layman
+ base = os.path.join(basepath, 'layman')
+ storage = os.path.join(base, 'overlays')
+ config = layman.config.OptionConfig(options = {
+ 'storage': os.path.join(base, 'overlays'),
+ 'cache': os.path.join(base, 'cache'),
+ 'installed': os.path.join(base, 'installed.xml'),
+ 'local_list': os.path.join(base, 'overlays.xml'),
+ 'overlays': [self.url]
+ })
+ self.api = layman.LaymanAPI(config)
+
+ # path
+ self.dir = os.path.join(storage, self.name)
+
+ # build the set of conditions to see if this source applies to
+ # a given set of metadata
+ self.conditions = []
+ self.groups = [] # provided for some limited backwards compat
+ for el in xsource.iterancestors():
+ if el.tag == "Group":
+ if el.get("negate", "false").lower() == "true":
+ self.conditions.append(lambda m, el=el:
+ el.get("name") not in m.groups)
+ else:
+ self.groups.append(el.get("name"))
+ self.conditions.append(lambda m, el=el:
+ el.get("name") in m.groups)
+ elif el.tag == "Client":
+ if el.get("negate", "false").lower() == "true":
+ self.conditions.append(lambda m, el=el:
+ el.get("name") != m.hostname)
+ else:
+ self.conditions.append(lambda m, el=el:
+ el.get("name") == m.hostname)
+
+ def get_repo_name(self, url_map):
+ return self.name
+
+ def save_state(self):
+ pass
+
+ def load_state(self):
+ pass
+
+ def filter_unknown(self, unknown):
+ filtered = set([u for u in unknown if u.startswith('choice')])
+ unknown.difference_update(filtered)
+
+ def get_urls(self):
+ return self.url
+ urls = property(get_urls)
+
+ def magic_groups_match(self, metadata):
+ if self.setup.cfp.getboolean("global", "magic_groups",
+ default=True) == False:
+ return True
+ else:
+ for group in self.basegroups:
+ if group in metadata.groups:
+ return True
+ return False
+
+ def setup_data(self, force_update=False):
+ self.api.fetch_remote_list()
+ if not self.api.is_repo(self.name):
+ self.logger.error("Packages: Layman overlay '%s' not"
+ " found" % self.name)
+ return False
+
+ if not self.api.is_installed(self.name):
+ self.logger.info("Packages: Adding layman overlay '%s'" %
+ self.name)
+ if not self.api.add_repos(self.name):
+ self.logger.error("Packages: Failed adding layman"
+ " overlay '%s'" % self.name)
+ return False
+
+ if force_update:
+ if not self.api.sync(self.name):
+ self.logger.error("Packages: Failed syncing layman"
+ " overlay '%s'" % self.name)
+ return False
+
+ return True
+
+ def applies(self, metadata):
+ # check base groups
+ if not self.magic_groups_match(metadata):
+ return False
+
+ # check Group/Client tags from sources.xml
+ for condition in self.conditions:
+ if not condition(metadata):
+ return False
+
+ return True
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py b/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py
index c47e18201..b18db5baf 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py
@@ -115,6 +115,8 @@ class PackagesSources(Bcfg2.Server.Plugin.StructFile,
source = self.source_from_xml(xsource)
if source is not None:
self.entries.append(source)
+ sorted(self.entries, key=(lambda source: source.priority),
+ reverse=True)
Index.__doc__ = Bcfg2.Server.Plugin.StructFile.Index.__doc__ + """
``Index`` is responsible for calling :func:`source_from_xml`
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Portage.py b/src/lib/Bcfg2/Server/Plugins/Packages/Portage.py
new file mode 100644
index 000000000..e7dcbeaa1
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Portage.py
@@ -0,0 +1,328 @@
+import re
+import gzip
+import sys
+import os
+import lxml.etree
+import Bcfg2.Server.Plugin
+from Bcfg2.Server.Plugins.Packages.Collection import Collection
+from Bcfg2.Server.Plugins.Packages.Layman import LaymanSource
+
+_portage_python = '/usr/lib/portage/pym/'
+
+def _import_portage(caller):
+ # generate prefix path
+ caller.prefix = os.path.join(caller.basepath, 'cache', 'portage')
+ if not os.path.isdir(caller.prefix):
+ caller.logger.error("Packages: %s is not a dir. "
+ "Portage will not work. Please "
+ "remember to setup a EPREFIX there." %
+ caller.prefix)
+ # TODO: automatic EPREFIX setup
+ raise Exception('Invalid EPREFIX')
+
+ os.environ['PORTAGE_OVERRIDE_EPREFIX'] = caller.prefix
+
+ if not os.path.isdir(_portage_python):
+ self.logger.error("Packages: %s not found. Have you installed "
+ "the portage python modules?" % _portage_python)
+ raise Exception('Portage not found')
+
+ sys.path = sys.path + [_portage_python]
+ portage = __import__('portage', globals(), locals(),
+ ['_sets', 'dbapi.porttree' ])
+ emerge = __import__('_emerge', globals(), locals(),
+ ['RootConfig', 'depgraph', 'Package', 'actions'])
+
+ # setup profile
+ if '_setup_profile' in dir(caller):
+ caller._setup_profile(portage)
+
+ # fix some default settings
+ portage.settings.unlock()
+ portage.settings['PORTAGE_RSYNC_INITIAL_TIMEOUT'] = '0'
+ portage.settings.lock()
+
+ porttree = portage.db[portage.root]['porttree']
+ caller._import_portage(portage, emerge, porttree)
+
+
+class PortageCollection(Collection):
+ def __init__(self, metadata, sources, cachepath, basepath, fam,
+ debug=False):
+ Collection.__init__(self, metadata, sources, cachepath,
+ basepath,fam, debug)
+
+ self.portage = None
+ self.emerge = None
+ self.porttree = None
+
+ @property
+ def cachefiles(self):
+ return []
+
+ def complete(self, packagelist, pinnings=None, recommended=None):
+ if not self.portage:
+ _import_portage(self)
+
+ # calculate deps
+ setconfig = self.portage._sets.load_default_config(
+ self.portage.settings,
+ self.portage.db[self.portage.root])
+ rconfig = self.emerge.RootConfig.RootConfig(
+ self.portage.settings,
+ self.portage.db[self.portage.root],
+ setconfig)
+ self.portage.db[self.portage.root]['root_config'] = rconfig
+
+ pkgs = ["=" + j.cpv for (i, j) in packagelist if i == 'ok']
+ fail = [j for (i, j) in packagelist if i == 'fail']
+
+ x = self.emerge.depgraph.backtrack_depgraph(
+ self.portage.settings,
+ self.portage.db,
+ {'--pretend': True},
+ {'recurse': True},
+ 'merge',
+ pkgs,
+ None)
+
+ g = x[1]._dynamic_config.digraph
+ packages = [i for i in g.all_nodes() \
+ if isinstance(i, self.emerge.Package.Package)]
+
+ return (set(packages), set(fail))
+
+ def get_additional_data(self):
+ return []
+
+ def get_group(self, group):
+ self.logger.warning("Packages: Package sets are currently not supported")
+ return []
+
+ def packages_from_entry(self, entry):
+ if not self.portage:
+ _import_portage(self)
+
+ try:
+ name = entry.get('name')
+ pkgs = self.porttree.dep_bestmatch(name)
+ except self.portage.exception.AmbiguousPackageName as e:
+ self.logger.error("Packages: AmbiguousPackageName: %s" % e)
+ pkgs = ''
+
+ if pkgs == '':
+ return [('fail', name)]
+
+ return [('ok', pkgs)]
+
+ def packages_to_entry(self, pkgs, entry):
+ for pkg in pkgs:
+ if pkg.slot != '0':
+ name = "%s:%s" % (pkg.cp, pkg.slot)
+ else:
+ name = pkg.cp
+
+ lxml.etree.SubElement(entry, 'BoundPackage', name=name,
+ version=self.setup.cfp.get("packages",
+ "version",
+ default="auto"),
+ type=self.ptype, origin='Packages')
+
+ def get_new_packages(self, initial, complete):
+ new = []
+ init = [pkg.cp for status, pkg in initial if status == 'ok']
+
+ for pkg in complete:
+ if pkg.cp not in init:
+ new.append(pkg)
+
+ return new
+
+ def setup_data(self, force_update=False):
+ pass
+
+ def _setup_profile(self, portage):
+ if 'gentoo-profile' not in self.metadata.Probes:
+ raise Exception('Unknown profile.')
+
+ profile = os.path.join(self.prefix, 'usr/portage/profiles/',
+ self.metadata.Probes['gentoo-profile'].strip())
+
+ env = portage.settings.configdict['backupenv']
+
+ # add layman overlays
+ env['PORTDIR_OVERLAY'] = ''
+ for overlay in self:
+ if isinstance(overlay, LaymanSource):
+ env['PORTDIR_OVERLAY'] += ' '
+ env['PORTDIR_OVERLAY'] += overlay.dir
+
+ portage.settings = portage.package.ebuild.config.config(
+ config_root=portage.settings['PORTAGE_CONFIGROOT'],
+ target_root=portage.settings['ROOT'],
+ env=env,
+ eprefix=portage.settings['EPREFIX'],
+ config_profile_path=profile)
+
+ portage.db[portage.root]['porttree'] \
+ = portage.dbapi.porttree.portagetree(portage.settings)
+ portage.db[portage.root]['vartree'] \
+ = portage.dbapi.vartree.vartree(portage.settings)
+
+ def _set_portage_config(self):
+ # get global use flags
+ self.portage.settings.unlock()
+ self.portage.settings['USE'] = ''
+ if 'gentoo-use-flags' in self.metadata.Probes:
+ self.portage.settings['USE'] = \
+ self.metadata.Probes['gentoo-use-flags']
+
+ # add package flags (accept_keywords, use)
+ if hasattr(self.metadata, 'PkgVars'):
+ for k in self.metadata.PkgVars['keywords']:
+ keyword = self.metadata.PkgVars['keywords'][k]
+ self.portage.settings._keywords_manager.pkeywordsdict[k] = \
+ {self.portage.dep.Atom(k): tuple(keyword)}
+
+
+ for u in self.metadata.PkgVars['use']:
+ use = self.metadata.PkgVars['use'][u]
+ self.portage.settings._use_manager._pusedict[u] = \
+ {u: tuple(use)}
+
+ self.portage.settings.lock()
+
+ def _import_portage(self, portage, emerge, porttree):
+ self.portage = portage
+ self.emerge = emerge
+ self.porttree = porttree
+ self._set_portage_config()
+
+ for s in self:
+ if isinstance(s, PortageSource):
+ s._import_portage(portage, emerge, porttree)
+
+
+class PortageSource(Bcfg2.Server.Plugin.Debuggable):
+ basegroups = ['portage', 'gentoo', 'emerge']
+ ptype = 'ebuild'
+
+ def __init__(self, basepath, xsource, setup):
+ Bcfg2.Server.Plugin.Debuggable.__init__(self)
+ self.basepath = basepath
+ self.xsource = xsource
+ self.setup = setup
+
+ self.url = xsource.get('url', '')
+ self.priority = xsource.get('priority', 0)
+ self.cachefile = None
+ self.gpgkeys = []
+ self.recommended = False
+ self.essentialpkgs = set()
+ self.arches = [item.text for item in xsource.findall('Arch')]
+
+ self.url_map = [dict(version=None, component=None, arch=arch,
+ url=self.url, baseurl=self.url) for arch in self.arches]
+
+ #: A list of the the names of packages that are blacklisted
+ #: from this source
+ self.blacklist = [item.text for item in xsource.findall('Blacklist')]
+
+ #: A list of the the names of packages that are whitelisted in
+ #: this source
+ self.whitelist = [item.text for item in xsource.findall('Whitelist')]
+
+
+ self.portage = None
+ self.emerge = None
+ self.porttree = None
+
+ # build the set of conditions to see if this source applies to
+ # a given set of metadata
+ self.conditions = []
+ self.groups = [] # provided for some limited backwards compat
+ for el in xsource.iterancestors():
+ if el.tag == "Group":
+ if el.get("negate", "false").lower() == "true":
+ self.conditions.append(lambda m, el=el:
+ el.get("name") not in m.groups)
+ else:
+ self.groups.append(el.get("name"))
+ self.conditions.append(lambda m, el=el:
+ el.get("name") in m.groups)
+ elif el.tag == "Client":
+ if el.get("negate", "false").lower() == "true":
+ self.conditions.append(lambda m, el=el:
+ el.get("name") != m.hostname)
+ else:
+ self.conditions.append(lambda m, el=el:
+ el.get("name") == m.hostname)
+ def get_repo_name(self, url_map):
+ return "portage"
+
+ def _import_portage(self, portage, emerge, porttree):
+ self.portage = portage
+ self.emerge = emerge
+ self.porttree = porttree
+
+ def save_state(self):
+ pass
+
+ def load_state(self):
+ pass
+
+ def filter_unknown(self, unknown):
+ filtered = set([u for u in unknown if u.startswith('choice')])
+ unknown.difference_update(filtered)
+
+ def get_urls(self):
+ return self.url
+ urls = property(get_urls)
+
+ def magic_groups_match(self, metadata):
+ if self.setup.cfp.getboolean("global", "magic_groups",
+ default=True) == False:
+ return True
+ else:
+ for group in self.basegroups:
+ if group in metadata.groups:
+ return True
+ return False
+
+ def setup_data(self, force_update=False):
+ if not self.porttree:
+ _import_portage(self)
+
+ timestamp = os.path.join(self.porttree.portroot, 'metadata/timestamp.chk')
+ if not os.path.isfile(timestamp):
+ self.logger.error("Packages: Timestamp not found; "
+ "falling back to sync")
+ force_update = True
+
+ if force_update:
+ # update sync url
+ self.portage.settings.unlock()
+ self.portage.settings['SYNC'] = self.url
+ self.portage.settings.lock()
+
+ # realy force the sync
+ if os.path.isfile(timestamp):
+ os.unlink(timestamp)
+
+ # sync
+ self.logger.info("Packages: Syncing with %s" % self.url)
+ self.emerge.actions.action_sync(self.portage.settings,
+ self.portage.db, None,
+ {'--quiet': True}, 'sync')
+
+ def applies(self, metadata):
+ # check base groups
+ if not self.magic_groups_match(metadata):
+ return False
+
+ # check Group/Client tags from sources.xml
+ for condition in self.conditions:
+ if not condition(metadata):
+ return False
+
+ return True
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Source.py b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
index 22073493c..c04611d77 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
@@ -209,6 +209,15 @@ class Source(Bcfg2.Server.Plugin.Debuggable): # pylint: disable=R0902
#: The "version" attribute from :attr:`xsource`
self.version = xsource.get('version', '')
+ #: The "priority" attribute from :attr:`xsource`
+ self.priority = xsource.get('priority', 500)
+
+ #: The "name" attribute from :attr:`xsource`
+ self.name = xsource.get('name', '')
+
+ #: The "priority" attribute from :attr:`xsource`
+ self.pin = xsource.get('pin', '')
+
#: A list of predicates that are used to determine if this
#: source applies to a given
#: :class:`Bcfg2.Server.Plugins.Metadata.ClientMetadata`
@@ -256,6 +265,10 @@ class Source(Bcfg2.Server.Plugin.Debuggable): # pylint: disable=R0902
#: :class:`Bcfg2.Server.Plugins.Packages.Collection.Collection`
self.provides = dict()
+ #: A dict of ``<package name>`` -> ``<list of recommended
+ #: symbols>``. This will not necessarily be populated.
+ self.recommends = dict()
+
#: The file (or directory) used for this source's cache data
self.cachefile = os.path.join(self.basepath,
"cache-%s" % self.cachekey)
@@ -280,7 +293,8 @@ class Source(Bcfg2.Server.Plugin.Debuggable): # pylint: disable=R0902
for arch in self.arches:
if self.url:
usettings = [dict(version=self.version, component=comp,
- arch=arch)
+ arch=arch, priority=self.priority,
+ name=self.name, pin=self.pin)
for comp in self.components]
else: # rawurl given
usettings = [dict(version=self.version, component=None,
@@ -321,7 +335,7 @@ class Source(Bcfg2.Server.Plugin.Debuggable): # pylint: disable=R0902
:raises: cPickle.UnpicklingError - If the saved data is corrupt """
data = open(self.cachefile, 'rb')
(self.pkgnames, self.deps, self.provides,
- self.essentialpkgs) = cPickle.load(data)
+ self.essentialpkgs, self.recommends) = cPickle.load(data)
def save_state(self):
""" Save state to :attr:`cachefile`. If caching and
@@ -329,7 +343,7 @@ class Source(Bcfg2.Server.Plugin.Debuggable): # pylint: disable=R0902
does not need to be implemented. """
cache = open(self.cachefile, 'wb')
cPickle.dump((self.pkgnames, self.deps, self.provides,
- self.essentialpkgs), cache, 2)
+ self.essentialpkgs, self.recommends), cache, 2)
cache.close()
@Bcfg2.Server.Plugin.track_statistics()
@@ -524,13 +538,13 @@ class Source(Bcfg2.Server.Plugin.Debuggable): # pylint: disable=R0902
as its final step."""
pass
- def process_files(self, dependencies, provides):
+ def process_files(self, dependencies, provides, recommends=dict()):
""" Given dicts of depends and provides generated by
:func:`read_files`, this generates :attr:`deps` and
:attr:`provides` and calls :func:`save_state` to save the
cached data to disk.
- Both arguments are dicts of dicts of lists. Keys are the
+ All arguments are dicts of dicts of lists. Keys are the
arches of packages contained in this source; values are dicts
whose keys are package names and values are lists of either
dependencies for each package the symbols provided by each
@@ -542,14 +556,20 @@ class Source(Bcfg2.Server.Plugin.Debuggable): # pylint: disable=R0902
:param provides: A dict of symbols provided by packages in
this repository.
:type provides: dict; see above.
+ :param recommends: A dict of recommended dependencies
+ found for this source.
+ :type recommends: dict; see above.
"""
self.deps['global'] = dict()
+ self.recommends['global'] = dict()
self.provides['global'] = dict()
for barch in dependencies:
self.deps[barch] = dict()
+ self.recommends[barch] = dict()
self.provides[barch] = dict()
for pkgname in self.pkgnames:
pset = set()
+ rset = set()
for barch in dependencies:
if pkgname not in dependencies[barch]:
dependencies[barch][pkgname] = []
@@ -559,6 +579,17 @@ class Source(Bcfg2.Server.Plugin.Debuggable): # pylint: disable=R0902
else:
for barch in dependencies:
self.deps[barch][pkgname] = dependencies[barch][pkgname]
+
+ for barch in recommends:
+ if pkgname not in recommends[barch]:
+ recommends[barch][pkgname] = []
+ rset.add(tuple(recommends[barch][pkgname]))
+ if len(rset) == 1:
+ self.recommends['global'][pkgname] = rset.pop()
+ else:
+ for barch in recommends:
+ self.recommends[barch][pkgname] = recommends[barch][pkgname]
+
provided = set()
for bprovided in list(provides.values()):
provided.update(set(bprovided))
@@ -667,17 +698,24 @@ class Source(Bcfg2.Server.Plugin.Debuggable): # pylint: disable=R0902
"""
return ['global'] + [a for a in self.arches if a in metadata.groups]
- def get_deps(self, metadata, package):
+ def get_deps(self, metadata, package, recommended=None):
""" Get a list of the dependencies of the given package.
:param package: The name of the symbol
:type package: string
:returns: list of strings
"""
+ recs = []
+ if ((recommended is None and self.recommended) or
+ (recommended and recommended.lower() == 'true')):
+ for arch in self.get_arches(metadata):
+ if package in self.recommends[arch]:
+ recs.extend(self.recommends[arch][package])
+
for arch in self.get_arches(metadata):
if package in self.deps[arch]:
- return self.deps[arch][package]
- return []
+ recs.extend(self.deps[arch][package])
+ return recs
def get_provides(self, metadata, package):
""" Get a list of all symbols provided by the given package.
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
index 67ff05ca1..6100813f9 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
@@ -853,7 +853,7 @@ class YumCollection(Collection):
return new
@Bcfg2.Server.Plugin.track_statistics()
- def complete(self, packagelist):
+ def complete(self, packagelist, recommended=None, pinnings=None):
""" Build a complete list of all packages and their dependencies.
When using the Python yum libraries, this defers to the
@@ -871,7 +871,8 @@ class YumCollection(Collection):
resolved.
"""
if not self.use_yum:
- return Collection.complete(self, packagelist)
+ return Collection.complete(self, packagelist, recommended,
+ pinnings)
lock = FileLock(os.path.join(self.cachefile, "lock"))
slept = 0
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
index 4b58c0fdb..ae275f93e 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
@@ -396,10 +396,19 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
initial = set()
to_remove = []
groups = []
+ recommended = dict()
+
+ pinned_src = dict()
+ if hasattr(metadata, 'PkgVars'):
+ pinned_src = metadata.PkgVars['pin']
+
for struct in structures:
for pkg in struct.xpath('//Package | //BoundPackage'):
if pkg.get("name"):
initial.update(collection.packages_from_entry(pkg))
+
+ if pkg.get("recommended"):
+ recommended[pkg.get("name")] = pkg.get("recommended")
elif pkg.get("group"):
groups.append((pkg.get("group"),
pkg.get("type")))
@@ -433,11 +442,12 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
base.update(collection.get_essential())
# check for this set of packages in the package cache
- pkey = hash(tuple(base))
+ pkey = hash((tuple(base), tuple(recommended), tuple(pinned_src)))
if pkey not in self.pkgcache[collection.cachekey]:
self.pkgcache[collection.cachekey][pkey] = \
- collection.complete(base)
+ collection.complete(base, recommended, pinned_src)
packages, unknown = self.pkgcache[collection.cachekey][pkey]
+
if unknown:
self.logger.info("Packages: Got %d unknown entries" % len(unknown))
self.logger.info("Packages: %s" % list(unknown))
@@ -589,21 +599,23 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
for source in self.sources.entries:
if source.applies(metadata):
relevant.append(source)
- sclasses.update([source.__class__])
+ if 'cclass' in dir(source):
+ sclasses.update([source.cclass])
+ else:
+ sclass = source.__class__.__name__.replace("Source", "")
+ sclasses.update([sclass])
if len(sclasses) > 1:
self.logger.warning("Packages: Multiple source types found for "
"%s: %s" %
- (metadata.hostname,
- ",".join([s.__name__ for s in sclasses])))
+ (metadata.hostname, ",".join([sclasses])))
cclass = Collection
elif len(sclasses) == 0:
self.logger.error("Packages: No sources found for %s" %
metadata.hostname)
cclass = Collection
else:
- cclass = get_collection_class(
- sclasses.pop().__name__.replace("Source", ""))
+ cclass = get_collection_class(sclasses.pop())
if self.debug_flag:
self.logger.error("Packages: Using %s for Collection of sources "
diff --git a/src/lib/Bcfg2/Server/Plugins/PkgVars.py b/src/lib/Bcfg2/Server/Plugins/PkgVars.py
new file mode 100644
index 000000000..40582bcb3
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/PkgVars.py
@@ -0,0 +1,65 @@
+import os
+import re
+import sys
+import copy
+import logging
+import lxml.etree
+import Bcfg2.Server.Plugin
+
+logger = logging.getLogger('Bcfg2.Plugins.PkgVars')
+vars = ['pin', 'use', 'keywords']
+
+class PkgVarsFile(Bcfg2.Server.Plugin.StructFile):
+ def get_additional_data(self, meta):
+ data = self.Match(meta)
+ results = {}
+ for d in data:
+ name = d.get('name', '')
+
+ for v in vars:
+ value = d.get(v, None)
+ if value:
+ if v not in results:
+ results[v] = {}
+ if name not in results[v]:
+ results[v][name] = set()
+
+ results[v][name].add(value)
+
+ return results
+
+class PkgVarsDirectoryBacked(Bcfg2.Server.Plugin.DirectoryBacked):
+ __child__ = PkgVarsFile
+ patterns = re.compile(r'.*\.xml$')
+
+ def get_additional_data(self, meta):
+ results = {}
+ for v in vars:
+ results[v] = {}
+
+ for files in self.entries:
+ new = self.entries[files].get_additional_data(meta)
+ for x in vars:
+ if x in new:
+ results[x].update(new[x])
+
+ return results
+
+class PkgVars(Bcfg2.Server.Plugin.Plugin,
+ Bcfg2.Server.Plugin.Connector):
+ name = 'PkgVars'
+ version = '$Revision$'
+
+ def __init__(self, core, datastore):
+ Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
+ Bcfg2.Server.Plugin.Connector.__init__(self)
+ try:
+ self.store = PkgVarsDirectoryBacked(self.data, core.fam)
+ except OSError:
+ e = sys.exc_info()[1]
+ self.logger.error("Error while creating PkgVars store: %s %s" %
+ (e.strerror, e.filename))
+ raise Bcfg2.Server.Plugin.PluginInitError
+
+ def get_additional_data(self, meta):
+ return self.store.get_additional_data(meta)