summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2012-09-17 10:31:38 -0400
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2012-09-20 11:37:55 -0400
commit1587dcb17c310d5ffb22bd7060c1cf18696eba28 (patch)
tree76105afb0a1fc7657d884939cd65c5ca4d9dc962 /src/lib/Bcfg2/Server/Plugins/Packages/Collection.py
parentaf07f60e2e5c9c26ab1ef1d0ecc0565672a85f56 (diff)
downloadbcfg2-1587dcb17c310d5ffb22bd7060c1cf18696eba28.tar.gz
bcfg2-1587dcb17c310d5ffb22bd7060c1cf18696eba28.tar.bz2
bcfg2-1587dcb17c310d5ffb22bd7060c1cf18696eba28.zip
development docs for Packages: Collection docs written
Diffstat (limited to 'src/lib/Bcfg2/Server/Plugins/Packages/Collection.py')
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Collection.py561
1 files changed, 427 insertions, 134 deletions
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py b/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py
index 4b86add24..31c832893 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py
@@ -1,42 +1,136 @@
+"""``_Collection`` objects represent the set of
+:class:`Bcfg2.Server.Plugins.Packages.Source.Source` objects that apply
+to a given client, and can be used to query all software repositories
+for a client in aggregate. In some cases this can give faster or more
+accurate results.
+
+In most cases, ``_Collection`` methods have been designed to defer the
+call to the Sources in the ``_Collection`` and aggregate the results
+as appropriate. The simplest ``_Collection`` implemention is thus
+often a simple subclass that adds no additional functionality.
+
+Overriding Methods
+------------------
+
+As noted above, the ``_Collection`` object is written expressly so
+that you can subclass it and override no methods or attributes, and it
+will work by deferring all calls to the Source objects it contains.
+If you do choose to override methods, there are two approaches:
+
+#. Keep :func:`_Collection.complete` intact, and override the methods
+ it calls: :func:`_Collection.is_package`,
+ :func:`_Collection.is_virtual_package`,
+ :func:`_Collection.get_deps`, :func:`_Collection.get_provides`,
+ :func:`_Collection.get_vpkgs`, and :func:`_Collection.setup_data`.
+
+#. Provide your own implementation of :func:`_Collection.complete`, in
+ which case you do not have to override the above methods. You may
+ want to override :func:`_Collection.packages_from_entry`,
+ :func:`_Collection.packages_to_entry`, and
+ :func:`_Collection.get_new_packages`.
+
+In either case, you may want to override
+:func:`_Collection.get_groups`, :func:`_Collection.get_group`,
+:func:`_Collection.get_essential`, :func:`_Collection.get_config`,
+:func:`_Collection.filter_unknown`, and
+:func:`_Collection.build_extra_structures`.
+
+.. _pkg-objects::
+
+Conversion Between Package Objects and XML Entries
+--------------------------------------------------
+
+_Collection objects have to translate Bcfg2 entries,
+:class:`lxml.etree._Element` objects, into objects suitable for use by
+the backend for resolving dependencies. This is handled by two
+functions:
+
+* :func:`_Collection.packages_from_entry` is called to translate an
+ XML entry into a list of packages;
+* :func:`_Collection.packages_to_entry` is called to translate a list
+ of packages back into an XML entry.
+
+Because of this translation layer, the return type of any functions
+below that return packages (e.g., :func:`_Collection.get_group`) is
+actually indeterminate; they must return an object suitable for
+passing to :func:`_Collection.packages_to_entry`. Similarly,
+functions that take a package as an argument (e.g.,
+:func:`_Collection.is_package`) take the appropriate package object.
+In the documentation below, the actual parameter return type (usually
+.``string``) used in this base implementation is noted, as well as this
+fact.
+"""
+
import sys
import copy
import logging
-import lxml
+import lxml.etree
import Bcfg2.Server.Plugin
+from Bcfg2.Compat import any, md5
+
+LOGGER = logging.getLogger(__name__)
+
+#: We cache _Collection objects in ``COLLECTIONS`` so that calling
+#: :func:`Bcfg2.Server.Plugins.Packages.Packages.Refresh` or
+#: :func:`Bcfg2.Server.Plugins.Packages.Packages.Reload` can tell the
+#: collection objects to clean up their cache, but we don't actually
+#: use the cache to return a _Collection object when one is requested,
+#: because that prevents new machines from working, since a
+#: _Collection object gets created by
+#: :func:`Bcfg2.Server.Plugins.Packages.Packages.get_additional_data`,
+#: which is called for all clients at server startup. (It would also
+#: prevent machines that change groups from working properly; e.g., if
+#: you reinstall a machine with a new OS, then returning a cached
+#: _Collection object would give the wrong sources to that client.)
+#: These are keyed by the collection :attr:`_Collection.cachekey`, a
+#: unique key identifying the collection by its *config*, which could
+#: be shared among multiple clients.
+COLLECTIONS = dict()
+
+#: CLIENTS is a cache mapping of hostname ->
+#: :attr:`_Collection.cachekey`. This _is_ used to return a
+#: _Collection object when one is requested, so each entry is very
+#: short-lived -- it's purged at the end of each client run.
+CLIENTS = dict()
+
+
+class _Collection(list, Bcfg2.Server.Plugin.Debuggable):
+ """ ``_Collection`` objects represent the set of
+ :class:`Bcfg2.Server.Plugins.Packages.Source` objects that apply
+ to a given client, and can be used to query all software
+ repositories for a client in aggregate. In some cases this can
+ give faster or more accurate results.
+
+ Note that the name of this class starts with an underscore; the
+ factory function :func:`Collection` must be used to instantiate
+ the correct subclass of ``_Collection`` when creating an actual
+ collection object. """
+
+ #: Whether or not this Packages backend supports package groups
+ __package_groups__ = False
-logger = logging.getLogger(__name__)
-
-try:
- from hashlib import md5
-except ImportError:
- from md5 import md5
-
-# we have to cache Collection objects so that calling Packages.Refresh
-# or .Reload can tell the collection objects to clean up their cache,
-# but we don't actually use the cache to return a Collection object
-# when one is requested, because that prevents new machines from
-# working, since a Collection object gets created by
-# get_additional_data(), which is called for all clients at server
-# startup. (It would also prevent machines that change groups from
-# working properly; e.g., if you reinstall a machine with a new OS,
-# then returning a cached Collection object would give the wrong
-# sources to that client.) These are keyed by the collection
-# cachekey, a unique key identifying the collection by its _config_,
-# which could be shared among multiple clients.
-collections = dict()
-
-# cache mapping of hostname -> collection cachekey. this _is_ used to
-# return a Collection object when one is requested, so each entry is
-# very short-lived -- it's purged at the end of each client run.
-clients = dict()
-
-class Collection(Bcfg2.Server.Plugin.Debuggable):
def __init__(self, metadata, sources, basepath, debug=False):
- """ don't call this directly; use the factory function """
+ """ Don't call ``__init__`` directly; use :func:`Collection`
+ to instantiate a new ``_Collection`` object.
+
+ :param metadata: The client metadata for this collection
+ :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
+ :param sources: A list of all sources known to the server that
+ will be used to generate the list of sources
+ that apply to this client
+ :type sources: list of
+ :class:`Bcfg2.Server.Plugins.Packages.Source.Source`
+ objects
+ :param basepath: The base filesystem path where cache and
+ other temporary data will be stored
+ :type basepath: string
+ :param debug: Enable debugging output
+ :type debug: bool
+ """
Bcfg2.Server.Plugin.Debuggable.__init__(self)
+ list.__init__(self, sources)
self.debug_flag = debug
self.metadata = metadata
- self.sources = sources
self.basepath = basepath
self.virt_pkgs = dict()
@@ -53,17 +147,33 @@ class Collection(Bcfg2.Server.Plugin.Debuggable):
@property
def cachekey(self):
+ """ A unique identifier for the set of sources contained in
+ this ``_Collection`` object. This is unique to a set of
+ sources, **not** necessarily to the client, which lets clients
+ with identical sources share cache data."""
return md5(self.sourcelist().encode('UTF-8')).hexdigest()
def get_config(self):
+ """ Get the configuration for the package tool used by this
+ source type. This should be a config appropriate for use on
+ either the server (to resolve dependencies) or the client.
+
+ Subclasses must override this method. By default it logs an
+ error and returns the empty string.
+
+ :returns: string """
self.logger.error("Packages: Cannot generate config for host %s with "
"no sources or multiple source types" %
self.metadata.hostname)
return ""
def sourcelist(self):
+ """ Get a human-readable list of sources in this collection,
+ including some information about each source.
+
+ :returns: string """
srcs = []
- for source in self.sources:
+ for source in self:
for url_map in source.url_map:
if url_map['arch'] not in self.metadata.groups:
continue
@@ -79,42 +189,94 @@ class Collection(Bcfg2.Server.Plugin.Debuggable):
else:
srcs.append(" GPG Key(s): None")
if len(source.blacklist):
- srcs.append(" Blacklist: %s" % ", ".join(source.blacklist))
+ srcs.append(" Blacklist: %s" %
+ ", ".join(source.blacklist))
if len(source.whitelist):
- srcs.append(" Whitelist: %s" % ", ".join(source.whitelist))
+ srcs.append(" Whitelist: %s" %
+ ", ".join(source.whitelist))
srcs.append("")
return "\n".join(srcs)
def get_relevant_groups(self):
+ """ Get all groups that might be relevant to determining which
+ sources apply to this collection's client.
+
+ The base implementation simply aggregates the results of
+ :func:`Bcfg2.Server.Plugins.Packages.Source.Source.get_relevant_groups`.
+
+ :return: list of strings - group names"""
groups = []
- for source in self.sources:
+ for source in self:
groups.extend(source.get_relevant_groups(self.metadata))
return sorted(list(set(groups)))
@property
def basegroups(self):
+ """ Get a list of group names used by this Collection type in
+ resolution of
+ :ref:`server-plugins-generators-packages-magic-groups`.
+
+ The base implementation simply aggregates the results of
+ :attr:`Bcfg2.Server.Plugins.Packages.Source.Source.basegroups`."""
groups = set()
- for source in self.sources:
+ for source in self:
groups.update(source.basegroups)
return list(groups)
@property
def cachefiles(self):
- cachefiles = set([self.cachefile])
- for source in self.sources:
+ """ Geta list of the full path to all cachefiles used by this
+ collection.
+
+ The base implementation simply aggregates the results of
+ :attr:`Bcfg2.Server.Plugins.Packages.Source.Source.cachefiles`."""
+ cachefiles = set([self.cachepath])
+ for source in self:
cachefiles.add(source.cachefile)
return list(cachefiles)
def get_groups(self, grouplist):
- """ provided since some backends may be able to query multiple
- groups at once faster than serially """
+ """ Given a list of package group names, return a dict of
+ ``<group name>: <list of packages>``. This method is provided
+ since some backends may be able to query multiple groups at
+ once faster than serially.
+
+ The base implementation simply aggregates the results of
+ :func:`Bcfg2.Server.Plugins.Packages.Source.Source.get_groups`.
+
+ :param grouplist: The list of groups to query
+ :type grouplist: list of strings - group names
+ :returns: dict of ``<group name>: <list of packages>``
+
+ In this implementation the packages will be strings, but see
+ :ref:`pkg-objects`."""
rv = dict()
for group, ptype in grouplist:
rv[group] = self.get_group(group, ptype)
return rv
def get_group(self, group, ptype=None):
- for source in self.sources:
+ """ Get the list of packages of the given type in a package
+ group.
+
+ The base implementation simply aggregates the results of
+ :func:`Bcfg2.Server.Plugins.Packages.Source.Source.get_group`.
+
+ :param group: The name of the group to query
+ :type group: string
+ :param ptype: The type of packages to get, for backends that
+ support multiple package types in package groups
+ (e.g., "recommended," "optional," etc.)
+ :type ptype: string
+ :returns: list of strings - package names, but see
+ :ref:`pkg-objects`
+ """
+ if not self.__package_groups__:
+ self.logger.error("Packages: Package groups are not supported by %s"
+ % self.__class__.__name__)
+ return []
+
+ for source in self:
pkgs = source.get_group(self.metadata, group, ptype=ptype)
if pkgs:
return pkgs
@@ -122,40 +284,88 @@ class Collection(Bcfg2.Server.Plugin.Debuggable):
return []
def is_package(self, package):
- for source in self.sources:
- if source.is_package(self.metadata, package):
- return True
- return False
+ """ Return True if a package is a package, False otherwise.
+
+ The base implementation returns True if any Source object's
+ :func:`Bcfg2.Server.Plugins.Packages.Source.Source.is_package`
+ returns True.
+
+ :param package: The name of the package, but see :ref:`pkg-objects`
+ :type package: string
+ :returns: bool
+ """
+ return any(source.is_package(self.metadata, package)
+ for source in self)
def is_virtual_package(self, package):
- for source in self.sources:
- if source.is_virtual_package(self.metadata, package):
- return True
- return False
+ """ Return True if a name is a virtual package (i.e., is a
+ symbol provided by a real package), False otherwise.
+
+ The base implementation returns True if any Source object's
+ :func:`Bcfg2.Server.Plugins.Packages.Source.Source.is_virtual_package`
+ returns True.
+
+ :param package: The name of the symbol, but see :ref:`pkg-objects`
+ :type package: string
+ :returns: bool
+ """
+ return any(source.is_virtual_package(self.metadata, package)
+ for source in self)
def get_deps(self, package):
- for source in self.sources:
+ """ Get a list of the dependencies of the given package.
+
+ The base implementation simply aggregates the results of
+ :func:`Bcfg2.Server.Plugins.Packages.Source.Source.get_deps`.
+
+ :param package: The name of the symbol, but see :ref:`pkg-objects`
+ :type package: string
+ :returns: list of strings, but see :ref:`pkg-objects`
+ """
+ for source in self:
if source.is_package(self.metadata, package):
return source.get_deps(self.metadata, package)
return []
def get_essential(self):
+ """ Get a list of packages that are essential to the repository.
+
+ The base implementation simply aggregates the results of
+ :func:`Bcfg2.Server.Plugins.Packages.Source.Source.get_essential`.
+
+ :returns: list of strings, but see :ref:`pkg-objects`
+ """
essential = set()
- for source in self.sources:
+ for source in self:
essential |= source.essentialpkgs
return essential
def get_provides(self, package):
- for source in self.sources:
+ """ Get a list of all symbols provided by the given package.
+
+ The base implementation simply aggregates the results of
+ :func:`Bcfg2.Server.Plugins.Packages.Source.Source.get_provides`.
+
+ :param package: The name of the package, but see :ref:`pkg-objects`
+ :type package: string
+ :returns: list of strings, but see :ref:`pkg-objects`
+ """
+ for source in self:
providers = source.get_provides(self.metadata, package)
if providers:
return providers
return []
def get_vpkgs(self):
- """ get virtual packages """
+ """ Get a list of all virtual packages provided by all sources.
+
+ The base implementation simply aggregates the results of
+ :func:`Bcfg2.Server.Plugins.Packages.Source.Source.get_vpkgs`.
+
+ :returns: list of strings, but see :ref:`pkg-objects`
+ """
vpkgs = dict()
- for source in self.sources:
+ for source in self:
s_vpkgs = source.get_vpkgs(self.metadata)
for name, prov_set in list(s_vpkgs.items()):
if name not in vpkgs:
@@ -165,35 +375,103 @@ class Collection(Bcfg2.Server.Plugin.Debuggable):
return vpkgs
def filter_unknown(self, unknown):
- for source in self.sources:
+ """ After :func:`complete`, filter out packages that appear in
+ the list of unknown packages but should not be presented to
+ the user. E.g., packages that you expect to be unknown.
+
+ :param unknown: A set of unknown packages. The set should be
+ modified in place.
+ :type unknown: set of strings, but see :ref:`pkg-objects`
+ """
+ for source in self:
source.filter_unknown(unknown)
def magic_groups_match(self):
- for source in self.sources:
- if source.magic_groups_match(self.metadata):
- return True
+ """ Returns True if the client's
+ :ref:`server-plugins-generators-packages-magic-groups` match
+ the magic groups for any of the sources contained in this
+ Collection.
+
+ The base implementation simply aggregates the results of
+ :func:`Bcfg2.Server.Plugins.Packages.Source.Source.magic_groups_match`.
+
+ :returns: bool
+ """
+ return any(s.magic_groups_match(self.metadata) for s in self)
def build_extra_structures(self, independent):
+ """ Add additional entries to the ``<Independent/>`` section
+ of the final configuration. This can be used to handle, e.g.,
+ GPG keys and other entries besides packages that need to be
+ handled for a complete client configuration.
+
+ :param independent: The XML tag to add extra entries to. This
+ should be modified in place.
+ :type independent: lxml.etree._Element
+ """
pass
def get_additional_data(self):
+ """ Get additional Connector data to be supplied to
+ :func:`Bcfg2.Server.Plugins.Packages.Packages.get_additional_data`
+ (and thence to client metadata objects).
+
+ The base implementation simply aggregates the results of
+ :func:`Bcfg2.Server.Plugins.Packages.Source.Source.get_additional_data`
+
+ :returns: list of additional Connector data
+ """
sdata = []
- for source in self.sources:
+ for source in self:
sdata.extend(copy.deepcopy(source.url_map))
return sdata
def setup_data(self, force_update=False):
- """ do any collection-level data setup tasks """
+ """ Do any collection-level data setup tasks. This is called
+ when sources are loaded or reloaded by
+ :class:`Bcfg2.Server.Plugins.Packages.Packages`.
+
+ The base implementation is a no-op; the child
+ :class:`Bcfg2.Server.Plugins.Packages.Source.Source` objects
+ will handle all data setup.
+
+ :param force_update: Ignore all local cache and setup data
+ from its original upstream sources (i.e.,
+ the package repositories)
+ :type force_update: bool
+ """
pass
def packages_from_entry(self, entry):
- """ given a Package or BoundPackage entry, get a list of the
+ """ Given a Package or BoundPackage entry, get a list of the
package(s) described by it in a format appropriate for passing
- to complete(). by default, that's just the name; only the Yum
- backend supports getting versions"""
+ to :func:`Bcfg2.Server.Plugins.Packages.Packages.complete`.
+ By default, that's just the name; only the
+ :module:`Bcfg2.Server.Plugins.Packages.Yum` backend supports
+ versions or other extended data. See :ref:`pkg-objects` for
+ more details.
+
+ :param entry: The XML entry describing the package or packages.
+ :type entry: lxml.etree._Element
+ :returns: list of strings, but see :ref:`pkg-objects`
+ """
return [entry.get("name")]
def packages_to_entry(self, pkglist, entry):
+ """ Given a list of package objects as returned by
+ :func:`packages_from_entry` or
+ :func:`Bcfg2.Server.Plugins.Packages.Packages.complete`,
+ return an XML tree describing the BoundPackage entries that
+ should be included in the client configuration. See
+ :ref:`pkg-objects` for more details.
+
+ :param pkglist: A list of packages as returned by
+ :func:`Bcfg2.Server.Plugins.Packages.Packages.complete`
+ :type pkglist: list of strings, but see :ref:`pkg-objects`
+ :param entry: The base XML entry to add all of the Package
+ entries to. This should be modified in place.
+ :type entry: lxml.etree._Element
+ """
for pkg in pkglist:
lxml.etree.SubElement(entry, 'BoundPackage', name=pkg,
version=self.setup.cfp.get("packages",
@@ -202,20 +480,37 @@ class Collection(Bcfg2.Server.Plugin.Debuggable):
type=self.ptype, origin='Packages')
def get_new_packages(self, initial, complete):
- """ compute the difference between the complete package list
- and the initial package list. this is necessary because the
- format may be different between the two lists due to
- packages_{to,from}_entry() """
+ """ Compute the difference between the complete package list
+ (as returned by
+ :func:`Bcfg2.Server.Plugins.Packages.Packages.complete`) and
+ the initial package list computed from the specification.
+ This is necessary because the format may be different between
+ the two lists due to :func:`packages_to_entry` and
+ :func:`packages_from_entry`. See :ref:`pkg-objects` for more
+ details.
+
+ :param initial: The initial package list
+ :type initial: set of strings, but see :ref:`pkg-objects`
+ :param complete: The final package list
+ :type complete: set of strings, but see :ref:`pkg-objects`
+ :return: set of strings, but see :ref:`pkg-objects` - the set
+ of packages that are in ``complete`` but not in
+ ``initial``
+ """
return list(complete.difference(initial))
def complete(self, packagelist):
- '''Build the transitive closure of all package dependencies
-
- Arguments:
- packageslist - set of package names
- returns => (set(packages), set(unsatisfied requirements))
- '''
-
+ """ 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`
+ :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
+ set of symbols whose dependencies could not be
+ resolved.
+ """
# setup vpkg cache
pgrps = tuple(self.get_relevant_groups())
if pgrps not in self.virt_pkgs:
@@ -315,48 +610,21 @@ class Collection(Bcfg2.Server.Plugin.Debuggable):
final_pass = False
self.filter_unknown(unknown)
-
return packages, unknown
- def __len__(self):
- return len(self.sources)
-
- def __getitem__(self, item):
- return self.sources[item]
-
- def __setitem__(self, item, value):
- self.sources[item] = value
- def __delitem__(self, item):
- del self.sources[item]
+def _get_collection_class(source_type):
+ """ Given a source type, determine the class of _Collection object
+ that should be used to contain these sources. Note that
+ ``source_type`` is *not* a
+ :class:`Bcfg2.Server.Plugins.Packages.Source.Source` subclass;
+ it's the name of a source type as given in ``sources.xml``.
- def append(self, item):
- self.sources.append(item)
-
- def count(self):
- return self.sources.count()
-
- def index(self, item):
- return self.sources.index(item)
-
- def extend(self, items):
- self.sources.extend(items)
-
- def insert(self, index, item):
- self.sources.insert(index, item)
-
- def pop(self, index=None):
- self.sources.pop(index)
-
- def remove(self, item):
- self.sources.remove(item)
-
- def sort(self, cmp=None, key=None, reverse=False):
- self.sources.sort(cmp, key, reverse)
-
-def get_collection_class(source_type):
+ :param source_type: The type of source, e.g., "yum" or "apt"
+ :type source_type: string
+ :returns: type - the _Collection subclass that should be used to
+ instantiate an object to contain sources of the given type. """
modname = "Bcfg2.Server.Plugins.Packages.%s" % source_type.title()
-
try:
module = sys.modules[modname]
except KeyError:
@@ -364,34 +632,59 @@ def get_collection_class(source_type):
module = __import__(modname).Server.Plugins.Packages
except ImportError:
msg = "Packages: Unknown source type %s" % source_type
- logger.error(msg)
+ LOGGER.error(msg)
raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
-
+
try:
cclass = getattr(module, source_type.title() + "Collection")
except AttributeError:
msg = "Packages: No collection class found for %s sources" % source_type
- logger.error(msg)
+ LOGGER.error(msg)
raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
-
return cclass
-def clear_cache():
- global collections
- global clients
- collections = dict()
- clients = dict()
-def factory(metadata, sources, basepath, debug=False):
- global collections
+def clear_cache():
+ """ Clear the caches kept by this
+ module. (:attr:`Bcfg2.Server.Plugins.Packages.Collection.COLLECTIONS`
+ and:attr:`Bcfg2.Server.Plugins.Packages.Collection.CLIENTS`) """
+ global COLLECTIONS, CLIENTS # pylint: disable=W0603
+ COLLECTIONS = dict()
+ CLIENTS = dict()
+
+
+def Collection(metadata, sources, basepath, debug=False):
+ """ Object factory for subclasses of
+ :class:`Bcfg2.Server.Plugins.Packages.Collection._Collection`.
+
+ :param metadata: The client metadata to create a _Collection
+ object for
+ :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
+ :param sources: A list of all sources known to the server that
+ will be used to generate the list of sources that
+ apply to this client
+ :type sources: list of
+ :class:`Bcfg2.Server.Plugins.Packages.Source.Source`
+ objects
+ :param basepath: The base filesystem path where cache and other
+ temporary data will be stored
+ :type basepath: string
+ :param debug: Enable debugging output
+ :type debug: bool
+ :return: An instance of the appropriate subclass of
+ :class:`Bcfg2.Server.Plugins.Packages.Collection._Collection`
+ that contains all relevant sources that apply to the
+ given client
+ """
+ global COLLECTIONS # pylint: disable=W0602
if not sources.loaded:
# if sources.xml has not received a FAM event yet, defer;
- # instantiate a dummy Collection object
- return Collection(metadata, [], basepath)
+ # instantiate a dummy _Collection object
+ return _Collection(metadata, [], basepath)
- if metadata.hostname in clients:
- return collections[clients[metadata.hostname]]
+ if metadata.hostname in CLIENTS:
+ return COLLECTIONS[CLIENTS[metadata.hostname]]
sclasses = set()
relevant = list()
@@ -402,22 +695,22 @@ def factory(metadata, sources, basepath, debug=False):
sclasses.update([source.__class__])
if len(sclasses) > 1:
- logger.warning("Packages: Multiple source types found for %s: %s" %
+ LOGGER.warning("Packages: Multiple source types found for %s: %s" %
",".join([s.__name__ for s in sclasses]))
- cclass = Collection
+ cclass = _Collection
elif len(sclasses) == 0:
- logger.error("Packages: No sources found for %s" % metadata.hostname)
- cclass = Collection
+ 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().__name__.replace("Source",
+ ""))
if debug:
- logger.error("Packages: Using %s for Collection of sources for %s" %
+ LOGGER.error("Packages: Using %s for Collection of sources for %s" %
(cclass.__name__, metadata.hostname))
collection = cclass(metadata, relevant, basepath, debug=debug)
ckey = collection.cachekey
- clients[metadata.hostname] = ckey
- collections[ckey] = collection
+ CLIENTS[metadata.hostname] = ckey
+ COLLECTIONS[ckey] = collection
return collection