From ebe7542db7217c2fac3d7111e80f94caedfb69e2 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 16 Jan 2013 13:28:06 -0500 Subject: added module-level OptionParser to avoid passing it as an argument or global all over --- src/lib/Bcfg2/Server/Plugin/helpers.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index acf6af1f8..7b22d52ca 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -23,18 +23,6 @@ try: except ImportError: HAS_DJANGO = False -#: A dict containing default metadata for Path entries from bcfg2.conf -DEFAULT_FILE_METADATA = Bcfg2.Options.OptionParser(dict( - owner=Bcfg2.Options.MDATA_OWNER, - group=Bcfg2.Options.MDATA_GROUP, - mode=Bcfg2.Options.MDATA_MODE, - secontext=Bcfg2.Options.MDATA_SECONTEXT, - important=Bcfg2.Options.MDATA_IMPORTANT, - paranoid=Bcfg2.Options.MDATA_PARANOID, - sensitive=Bcfg2.Options.MDATA_SENSITIVE)) -DEFAULT_FILE_METADATA.parse(sys.argv[1:]) -del DEFAULT_FILE_METADATA['args'] - LOGGER = logging.getLogger(__name__) #: a compiled regular expression for parsing info and :info files @@ -49,7 +37,20 @@ INFO_REGEX = re.compile('owner:(\s)*(?P\S+)|' + 'mtime:(\s)*(?P\w+)|') -def bind_info(entry, metadata, infoxml=None, default=DEFAULT_FILE_METADATA): +def default_path_metadata(): + """ Get the default Path entry metadata from the config. + + :returns: dict of metadata attributes and their default values + """ + attrs = Bcfg2.Options.PATH_METADATA_OPTIONS.keys() + setup = Bcfg2.Options.get_option_parser() + if not set(attrs).issubset(setup.keys()): + setup.add_options(Bcfg2.Options.PATH_METADATA_OPTIONS) + setup.reparse() + return dict([(k, setup[k]) for k in attrs]) + + +def bind_info(entry, metadata, infoxml=None, default=None): """ Bind the file metadata in the given :class:`Bcfg2.Server.Plugin.helpers.InfoXML` object to the given entry. @@ -66,6 +67,8 @@ def bind_info(entry, metadata, infoxml=None, default=DEFAULT_FILE_METADATA): :returns: None :raises: :class:`Bcfg2.Server.Plugin.exceptions.PluginExecutionError` """ + if default is None: + default = default_path_metadata() for attr, val in list(default.items()): entry.set(attr, val) if infoxml: @@ -1154,7 +1157,7 @@ class EntrySet(Debuggable): self.path = path self.entry_type = entry_type self.entries = {} - self.metadata = DEFAULT_FILE_METADATA.copy() + self.metadata = default_path_metadata() self.infoxml = None self.encoding = encoding @@ -1376,7 +1379,7 @@ class EntrySet(Debuggable): if event.filename == 'info.xml': self.infoxml = None elif event.filename in [':info', 'info']: - self.metadata = DEFAULT_FILE_METADATA.copy() + self.metadata = default_path_metadata() def bind_info_to_entry(self, entry, metadata): """ Shortcut to call :func:`bind_info` with the base -- cgit v1.2.3-1-g7c22 From 72a80f89361145f1560ccc248f357a9de82eded6 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 17 Jan 2013 08:01:44 -0500 Subject: abstracted encryption support from Properties/CfgPrivateKeyCreator to StructFile --- src/lib/Bcfg2/Server/Plugin/helpers.py | 64 +++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 7b22d52ca..879c68b85 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -17,6 +17,12 @@ from Bcfg2.Server.Plugin.interfaces import Generator from Bcfg2.Server.Plugin.exceptions import SpecificityError, \ PluginExecutionError +try: + import Bcfg2.Encryption + HAS_CRYPTO = True +except ImportError: + HAS_CRYPTO = False + try: import django # pylint: disable=W0611 HAS_DJANGO = True @@ -571,13 +577,69 @@ class XMLFileBacked(FileBacked): class StructFile(XMLFileBacked): """ StructFiles are XML files that contain a set of structure file formatting logic for handling ```` and ```` - tags. """ + tags. + + .. ----- + .. autoattribute:: __identifier__ + """ #: If ``__identifier__`` is not None, then it must be the name of #: an XML attribute that will be required on the top-level tag of #: the file being cached __identifier__ = None + #: Whether or not encryption support is enabled in this file + encryption = True + + def __init__(self, filename, fam=None, should_monitor=False): + XMLFileBacked.__init__(self, filename, fam=fam, + should_monitor=should_monitor) + self.setup = Bcfg2.Options.get_option_parser() + + def Index(self): + Bcfg2.Server.Plugin.XMLFileBacked.Index(self) + if self.encryption: + strict = self.xdata.get( + "decrypt", + self.setup.cfp.get(Bcfg2.Encryption.CFG_SECTION, "decrypt", + default="strict")) == "strict" + for el in self.xdata.xpath("//*[@encrypted]"): + if not HAS_CRYPTO: + raise PluginExecutionError("Properties: M2Crypto is not " + "available: %s" % self.name) + try: + el.text = self._decrypt(el).encode('ascii', + 'xmlcharrefreplace') + except UnicodeDecodeError: + LOGGER.info("%s: Decrypted %s to gibberish, skipping" % + (self.name, el.tag)) + except Bcfg2.Encryption.EVPError: + msg = "Failed to decrypt %s element in %s" % (el.tag, + self.name) + if strict: + raise PluginExecutionError(msg) + else: + LOGGER.warning(msg) + Index.__doc__ = XMLFileBacked.Index.__doc__ + + def _decrypt(self, element): + """ Decrypt a single encrypted properties file element """ + if not element.text or not element.text.strip(): + return + passes = Bcfg2.Encryption.get_passphrases() + try: + passphrase = passes[element.get("encrypted")] + try: + return Bcfg2.Encryption.ssl_decrypt(element.text, passphrase) + except Bcfg2.Encryption.EVPError: + # error is raised below + pass + except KeyError: + # bruteforce_decrypt raises an EVPError with a sensible + # error message, so we just let it propagate up the stack + return Bcfg2.Encryption.bruteforce_decrypt(element.text) + raise Bcfg2.Encryption.EVPError("Failed to decrypt") + def _include_element(self, item, metadata): """ determine if an XML element matches the metadata """ if isinstance(item, lxml.etree._Comment): # pylint: disable=W0212 -- cgit v1.2.3-1-g7c22 From f45551e656bc9a7894ddbf4412263e9045e73154 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 17 Jan 2013 09:27:58 -0500 Subject: StructFile: minor cleanup --- src/lib/Bcfg2/Server/Plugin/helpers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 879c68b85..5ffdde49a 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -597,7 +597,7 @@ class StructFile(XMLFileBacked): self.setup = Bcfg2.Options.get_option_parser() def Index(self): - Bcfg2.Server.Plugin.XMLFileBacked.Index(self) + XMLFileBacked.Index(self) if self.encryption: strict = self.xdata.get( "decrypt", @@ -605,8 +605,8 @@ class StructFile(XMLFileBacked): default="strict")) == "strict" for el in self.xdata.xpath("//*[@encrypted]"): if not HAS_CRYPTO: - raise PluginExecutionError("Properties: M2Crypto is not " - "available: %s" % self.name) + raise PluginExecutionError("%s: M2Crypto is not available" + % self.name) try: el.text = self._decrypt(el).encode('ascii', 'xmlcharrefreplace') -- cgit v1.2.3-1-g7c22 From a6b269011b2f1e84c650239065e56778591f087e Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 30 Oct 2012 11:03:10 -0400 Subject: removed support for info/:info files --- src/lib/Bcfg2/Server/Plugin/helpers.py | 36 ++++------------------------------ 1 file changed, 4 insertions(+), 32 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 5ffdde49a..c4316d785 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -31,17 +31,6 @@ except ImportError: LOGGER = logging.getLogger(__name__) -#: a compiled regular expression for parsing info and :info files -INFO_REGEX = re.compile('owner:(\s)*(?P\S+)|' + - 'group:(\s)*(?P\S+)|' + - 'mode:(\s)*(?P\w+)|' + - 'secontext:(\s)*(?P\S+)|' + - 'paranoid:(\s)*(?P\S+)|' + - 'sensitive:(\s)*(?P\S+)|' + - 'encoding:(\s)*(?P\S+)|' + - 'important:(\s)*(?P\S+)|' + - 'mtime:(\s)*(?P\w+)|') - def default_path_metadata(): """ Get the default Path entry metadata from the config. @@ -1296,7 +1285,7 @@ class EntrySet(Debuggable): """ action = event.code2str() - if event.filename in ['info', 'info.xml', ':info']: + if event.filename == 'info.xml': if action in ['exists', 'created', 'changed']: self.update_metadata(event) elif action == 'deleted': @@ -1401,8 +1390,8 @@ class EntrySet(Debuggable): return Specificity(**kwargs) def update_metadata(self, event): - """ Process changes to or creation of info, :info, and - info.xml files for the EntrySet. + """ Process changes to or creation of info.xml files for the + EntrySet. :param event: An event that applies to an info handled by this EntrySet @@ -1414,24 +1403,9 @@ class EntrySet(Debuggable): if not self.infoxml: self.infoxml = InfoXML(fpath) self.infoxml.HandleEvent(event) - elif event.filename in [':info', 'info']: - for line in open(fpath).readlines(): - match = INFO_REGEX.match(line) - if not match: - LOGGER.warning("Failed to match line in %s: %s" % (fpath, - line)) - continue - else: - mgd = match.groupdict() - for key, value in list(mgd.items()): - if value: - self.metadata[key] = value - if len(self.metadata['mode']) == 3: - self.metadata['mode'] = "0%s" % self.metadata['mode'] def reset_metadata(self, event): - """ Reset metadata to defaults if info. :info, or info.xml are - removed. + """ Reset metadata to defaults if info.xml is removed. :param event: An event that applies to an info handled by this EntrySet @@ -1440,8 +1414,6 @@ class EntrySet(Debuggable): """ if event.filename == 'info.xml': self.infoxml = None - elif event.filename in [':info', 'info']: - self.metadata = default_path_metadata() def bind_info_to_entry(self, entry, metadata): """ Shortcut to call :func:`bind_info` with the base -- cgit v1.2.3-1-g7c22 From 45c7bbf24ae3c6530f33ebb33c062818ad44816d Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 30 Oct 2012 11:35:46 -0400 Subject: added a module-level FAM object to avoid passing it as an argument a billion times --- src/lib/Bcfg2/Server/Plugin/helpers.py | 35 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 21 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index c4316d785..674aa5ffd 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -12,6 +12,7 @@ import Bcfg2.Server import Bcfg2.Options import Bcfg2.Statistics from Bcfg2.Compat import CmpMixin, wraps +from Bcfg2.Server.FileMonitor import get_fam from Bcfg2.Server.Plugin.base import Debuggable, Plugin from Bcfg2.Server.Plugin.interfaces import Generator from Bcfg2.Server.Plugin.exceptions import SpecificityError, \ @@ -193,13 +194,10 @@ class FileBacked(object): principally meant to be used as a part of :class:`Bcfg2.Server.Plugin.helpers.DirectoryBacked`. """ - def __init__(self, name, fam=None): + def __init__(self, name): """ :param name: The full path to the file to cache and monitor :type name: string - :param fam: The FAM object used to receive notifications of - changes - :type fam: Bcfg2.Server.FileMonitor.FileMonitor """ object.__init__(self) @@ -210,7 +208,7 @@ class FileBacked(object): self.name = name #: The FAM object used to receive notifications of changes - self.fam = fam + self.fam = get_fam() def HandleEvent(self, event=None): """ HandleEvent is called whenever the FAM registers an event. @@ -263,14 +261,11 @@ class DirectoryBacked(object): #: :attr:`patterns` or ``ignore``, then a warning will be produced. ignore = None - def __init__(self, data, fam): + def __init__(self, data): """ :param data: The path to the data directory that will be monitored :type data: string - :param fam: The FAM object used to receive notifications of - changes - :type fam: Bcfg2.Server.FileMonitor.FileMonitor .. ----- .. autoattribute:: __child__ @@ -278,7 +273,7 @@ class DirectoryBacked(object): object.__init__(self) self.data = os.path.normpath(data) - self.fam = fam + self.fam = get_fam() #: self.entries contains information about the files monitored #: by this object. The keys of the dict are the relative @@ -332,8 +327,7 @@ class DirectoryBacked(object): :returns: None """ self.entries[relative] = self.__child__(os.path.join(self.data, - relative), - self.fam) + relative)) self.entries[relative].HandleEvent(event) def HandleEvent(self, event): # pylint: disable=R0912 @@ -454,13 +448,10 @@ class XMLFileBacked(FileBacked): #: behavior, set ``__identifier__`` to ``None``. __identifier__ = 'name' - def __init__(self, filename, fam=None, should_monitor=False): + def __init__(self, filename, should_monitor=False): """ :param filename: The full path to the file to cache and monitor :type filename: string - :param fam: The FAM object used to receive notifications of - changes - :type fam: Bcfg2.Server.FileMonitor.FileMonitor :param should_monitor: Whether or not to monitor this file for changes. It may be useful to disable monitoring when, for instance, the file @@ -473,7 +464,7 @@ class XMLFileBacked(FileBacked): .. ----- .. autoattribute:: __identifier__ """ - FileBacked.__init__(self, filename, fam=fam) + FileBacked.__init__(self, filename) #: The raw XML data contained in the file as an #: :class:`lxml.etree.ElementTree` object, with XIncludes @@ -494,7 +485,7 @@ class XMLFileBacked(FileBacked): #: Whether or not to monitor this file for changes. self.should_monitor = should_monitor - if fam and should_monitor: + if should_monitor: self.fam.AddMonitor(filename, self) def _follow_xincludes(self, fname=None, xdata=None): @@ -553,7 +544,7 @@ class XMLFileBacked(FileBacked): :returns: None """ self.extras.append(fpath) - if self.fam and self.should_monitor: + if self.should_monitor: self.fam.AddMonitor(fpath, self) def __iter__(self): @@ -908,7 +899,7 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked): def __init__(self, core, datastore): Plugin.__init__(self, core, datastore) Generator.__init__(self) - XMLDirectoryBacked.__init__(self, self.data, self.core.fam) + XMLDirectoryBacked.__init__(self, self.data) __init__.__doc__ = Plugin.__init__.__doc__ def HandleEvent(self, event): @@ -1478,6 +1469,8 @@ class GroupSpool(Plugin, Generator): if self.data[-1] == '/': self.data = self.data[:-1] + self.fam = get_fam() + #: See :class:`Bcfg2.Server.Plugins.interfaces.Generator` for #: details on the Entries attribute. self.Entries[self.entry_type] = {} @@ -1624,5 +1617,5 @@ class GroupSpool(Plugin, Generator): if not os.path.isdir(name): self.logger.error("Failed to open directory %s" % name) return - reqid = self.core.fam.AddMonitor(name, self) + reqid = self.fam.AddMonitor(name, self) self.handles[reqid] = relative -- cgit v1.2.3-1-g7c22 From cebd0d7ad54995c37f68586a14540ad64d99d762 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 31 Oct 2012 11:46:51 -0400 Subject: fixed unit tests --- src/lib/Bcfg2/Server/Plugin/helpers.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 674aa5ffd..6ad18fa43 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -11,8 +11,8 @@ import lxml.etree import Bcfg2.Server import Bcfg2.Options import Bcfg2.Statistics +import Bcfg2.Server.FileMonitor from Bcfg2.Compat import CmpMixin, wraps -from Bcfg2.Server.FileMonitor import get_fam from Bcfg2.Server.Plugin.base import Debuggable, Plugin from Bcfg2.Server.Plugin.interfaces import Generator from Bcfg2.Server.Plugin.exceptions import SpecificityError, \ @@ -208,7 +208,7 @@ class FileBacked(object): self.name = name #: The FAM object used to receive notifications of changes - self.fam = get_fam() + self.fam = Bcfg2.Server.FileMonitor.get_fam() def HandleEvent(self, event=None): """ HandleEvent is called whenever the FAM registers an event. @@ -273,7 +273,7 @@ class DirectoryBacked(object): object.__init__(self) self.data = os.path.normpath(data) - self.fam = get_fam() + self.fam = Bcfg2.Server.FileMonitor.get_fam() #: self.entries contains information about the files monitored #: by this object. The keys of the dict are the relative @@ -809,8 +809,8 @@ class XMLSrc(XMLFileBacked): __cacheobj__ = dict __priority_required__ = True - def __init__(self, filename, fam=None, should_monitor=False): - XMLFileBacked.__init__(self, filename, fam, should_monitor) + def __init__(self, filename, should_monitor=False): + XMLFileBacked.__init__(self, filename, should_monitor) self.items = {} self.cache = None self.pnode = None @@ -1469,7 +1469,7 @@ class GroupSpool(Plugin, Generator): if self.data[-1] == '/': self.data = self.data[:-1] - self.fam = get_fam() + self.fam = Bcfg2.Server.FileMonitor.get_fam() #: See :class:`Bcfg2.Server.Plugins.interfaces.Generator` for #: details on the Entries attribute. -- cgit v1.2.3-1-g7c22 From 19d20b6f254c67ab2de4a932f1e5bbb0e30dc1f3 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 17 Jan 2013 11:01:51 -0500 Subject: fixed unit tests from merge --- src/lib/Bcfg2/Server/Plugin/helpers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 6ad18fa43..8d97ed6b4 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -571,9 +571,8 @@ class StructFile(XMLFileBacked): #: Whether or not encryption support is enabled in this file encryption = True - def __init__(self, filename, fam=None, should_monitor=False): - XMLFileBacked.__init__(self, filename, fam=fam, - should_monitor=should_monitor) + def __init__(self, filename, should_monitor=False): + XMLFileBacked.__init__(self, filename, should_monitor=should_monitor) self.setup = Bcfg2.Options.get_option_parser() def Index(self): -- cgit v1.2.3-1-g7c22 From 9be9cfec322518f764be9766b27d24132fc6a66f Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 16 Jan 2013 13:28:06 -0500 Subject: added module-level OptionParser to avoid passing it as an argument or global all over --- src/lib/Bcfg2/Server/Plugin/helpers.py | 35 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 41c450b4e..0df568045 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -23,20 +23,6 @@ try: except ImportError: HAS_DJANGO = False -#: A dict containing default metadata for Path entries from bcfg2.conf -DEFAULT_FILE_METADATA = Bcfg2.Options.OptionParser(dict( - configfile=Bcfg2.Options.CFILE, - owner=Bcfg2.Options.MDATA_OWNER, - group=Bcfg2.Options.MDATA_GROUP, - mode=Bcfg2.Options.MDATA_MODE, - secontext=Bcfg2.Options.MDATA_SECONTEXT, - important=Bcfg2.Options.MDATA_IMPORTANT, - paranoid=Bcfg2.Options.MDATA_PARANOID, - sensitive=Bcfg2.Options.MDATA_SENSITIVE)) -DEFAULT_FILE_METADATA.parse([Bcfg2.Options.CFILE.cmd, Bcfg2.Options.CFILE]) -del DEFAULT_FILE_METADATA['args'] -del DEFAULT_FILE_METADATA['configfile'] - LOGGER = logging.getLogger(__name__) #: a compiled regular expression for parsing info and :info files @@ -51,7 +37,20 @@ INFO_REGEX = re.compile('owner:(\s)*(?P\S+)|' + 'mtime:(\s)*(?P\w+)|') -def bind_info(entry, metadata, infoxml=None, default=DEFAULT_FILE_METADATA): +def default_path_metadata(): + """ Get the default Path entry metadata from the config. + + :returns: dict of metadata attributes and their default values + """ + attrs = Bcfg2.Options.PATH_METADATA_OPTIONS.keys() + setup = Bcfg2.Options.get_option_parser() + if not set(attrs).issubset(setup.keys()): + setup.add_options(Bcfg2.Options.PATH_METADATA_OPTIONS) + setup.reparse(argv=[Bcfg2.Options.CFILE.cmd, Bcfg2.Options.CFILE]) + return dict([(k, setup[k]) for k in attrs]) + + +def bind_info(entry, metadata, infoxml=None, default=None): """ Bind the file metadata in the given :class:`Bcfg2.Server.Plugin.helpers.InfoXML` object to the given entry. @@ -68,6 +67,8 @@ def bind_info(entry, metadata, infoxml=None, default=DEFAULT_FILE_METADATA): :returns: None :raises: :class:`Bcfg2.Server.Plugin.exceptions.PluginExecutionError` """ + if default is None: + default = default_path_metadata() for attr, val in list(default.items()): entry.set(attr, val) if infoxml: @@ -1156,7 +1157,7 @@ class EntrySet(Debuggable): self.path = path self.entry_type = entry_type self.entries = {} - self.metadata = DEFAULT_FILE_METADATA.copy() + self.metadata = default_path_metadata() self.infoxml = None self.encoding = encoding @@ -1378,7 +1379,7 @@ class EntrySet(Debuggable): if event.filename == 'info.xml': self.infoxml = None elif event.filename in [':info', 'info']: - self.metadata = DEFAULT_FILE_METADATA.copy() + self.metadata = default_path_metadata() def bind_info_to_entry(self, entry, metadata): """ Shortcut to call :func:`bind_info` with the base -- cgit v1.2.3-1-g7c22 From 1a031fc131d950dd49dc425ac1726337d8e93910 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 17 Jan 2013 08:01:44 -0500 Subject: abstracted encryption support from Properties/CfgPrivateKeyCreator to StructFile --- src/lib/Bcfg2/Server/Plugin/helpers.py | 64 +++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 0df568045..c8e09d3d1 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -17,6 +17,12 @@ from Bcfg2.Server.Plugin.interfaces import Generator from Bcfg2.Server.Plugin.exceptions import SpecificityError, \ PluginExecutionError +try: + import Bcfg2.Encryption + HAS_CRYPTO = True +except ImportError: + HAS_CRYPTO = False + try: import django # pylint: disable=W0611 HAS_DJANGO = True @@ -571,13 +577,69 @@ class XMLFileBacked(FileBacked): class StructFile(XMLFileBacked): """ StructFiles are XML files that contain a set of structure file formatting logic for handling ```` and ```` - tags. """ + tags. + + .. ----- + .. autoattribute:: __identifier__ + """ #: If ``__identifier__`` is not None, then it must be the name of #: an XML attribute that will be required on the top-level tag of #: the file being cached __identifier__ = None + #: Whether or not encryption support is enabled in this file + encryption = True + + def __init__(self, filename, fam=None, should_monitor=False): + XMLFileBacked.__init__(self, filename, fam=fam, + should_monitor=should_monitor) + self.setup = Bcfg2.Options.get_option_parser() + + def Index(self): + Bcfg2.Server.Plugin.XMLFileBacked.Index(self) + if self.encryption: + strict = self.xdata.get( + "decrypt", + self.setup.cfp.get(Bcfg2.Encryption.CFG_SECTION, "decrypt", + default="strict")) == "strict" + for el in self.xdata.xpath("//*[@encrypted]"): + if not HAS_CRYPTO: + raise PluginExecutionError("Properties: M2Crypto is not " + "available: %s" % self.name) + try: + el.text = self._decrypt(el).encode('ascii', + 'xmlcharrefreplace') + except UnicodeDecodeError: + LOGGER.info("%s: Decrypted %s to gibberish, skipping" % + (self.name, el.tag)) + except Bcfg2.Encryption.EVPError: + msg = "Failed to decrypt %s element in %s" % (el.tag, + self.name) + if strict: + raise PluginExecutionError(msg) + else: + LOGGER.warning(msg) + Index.__doc__ = XMLFileBacked.Index.__doc__ + + def _decrypt(self, element): + """ Decrypt a single encrypted properties file element """ + if not element.text or not element.text.strip(): + return + passes = Bcfg2.Encryption.get_passphrases() + try: + passphrase = passes[element.get("encrypted")] + try: + return Bcfg2.Encryption.ssl_decrypt(element.text, passphrase) + except Bcfg2.Encryption.EVPError: + # error is raised below + pass + except KeyError: + # bruteforce_decrypt raises an EVPError with a sensible + # error message, so we just let it propagate up the stack + return Bcfg2.Encryption.bruteforce_decrypt(element.text) + raise Bcfg2.Encryption.EVPError("Failed to decrypt") + def _include_element(self, item, metadata): """ determine if an XML element matches the metadata """ if isinstance(item, lxml.etree._Comment): # pylint: disable=W0212 -- cgit v1.2.3-1-g7c22 From f8b31cfa1596a17b248e28b82362e2d5e542b8ff Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 17 Jan 2013 09:27:58 -0500 Subject: StructFile: minor cleanup --- src/lib/Bcfg2/Server/Plugin/helpers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index c8e09d3d1..399ab6679 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -597,7 +597,7 @@ class StructFile(XMLFileBacked): self.setup = Bcfg2.Options.get_option_parser() def Index(self): - Bcfg2.Server.Plugin.XMLFileBacked.Index(self) + XMLFileBacked.Index(self) if self.encryption: strict = self.xdata.get( "decrypt", @@ -605,8 +605,8 @@ class StructFile(XMLFileBacked): default="strict")) == "strict" for el in self.xdata.xpath("//*[@encrypted]"): if not HAS_CRYPTO: - raise PluginExecutionError("Properties: M2Crypto is not " - "available: %s" % self.name) + raise PluginExecutionError("%s: M2Crypto is not available" + % self.name) try: el.text = self._decrypt(el).encode('ascii', 'xmlcharrefreplace') -- cgit v1.2.3-1-g7c22 From c2133f115673670992048f3567c22e7478281a79 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 18 Jan 2013 11:06:41 -0500 Subject: StructFile: fixed lax/strict decryption setting with no crypto libs installed --- src/lib/Bcfg2/Server/Plugin/helpers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 399ab6679..b036fc31d 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -598,15 +598,12 @@ class StructFile(XMLFileBacked): def Index(self): XMLFileBacked.Index(self) - if self.encryption: + if self.encryption and HAS_CRYPTO: strict = self.xdata.get( "decrypt", self.setup.cfp.get(Bcfg2.Encryption.CFG_SECTION, "decrypt", default="strict")) == "strict" for el in self.xdata.xpath("//*[@encrypted]"): - if not HAS_CRYPTO: - raise PluginExecutionError("%s: M2Crypto is not available" - % self.name) try: el.text = self._decrypt(el).encode('ascii', 'xmlcharrefreplace') -- cgit v1.2.3-1-g7c22 From 22029e107420ff21cf9f1811bf4bb6dc2aba1dde Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 22 Jan 2013 11:16:19 -0500 Subject: made genshi a requirement --- src/lib/Bcfg2/Server/Plugin/helpers.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 59796a556..c85253be6 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -17,6 +17,7 @@ from Bcfg2.Server.Plugin.base import Debuggable, Plugin from Bcfg2.Server.Plugin.interfaces import Generator from Bcfg2.Server.Plugin.exceptions import SpecificityError, \ PluginExecutionError +import genshi.core try: import Bcfg2.Encryption @@ -33,6 +34,21 @@ except ImportError: LOGGER = logging.getLogger(__name__) +def removecomment(stream): + """ A Genshi filter that removes comments from the stream. This + function is a generator. + + :param stream: The Genshi stream to remove comments from + :type stream: genshi.core.Stream + :returns: tuple of ``(kind, data, pos)``, as when iterating + through a Genshi stream + """ + for kind, data, pos in stream: + if kind is genshi.core.COMMENT: + continue + yield kind, data, pos + + def default_path_metadata(): """ Get the default Path entry metadata from the config. -- cgit v1.2.3-1-g7c22 From cfa769abf8e464acee43e1cac359b0f7e5fc5e4b Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 23 Jan 2013 14:13:51 -0500 Subject: added genshi support to StructFile --- src/lib/Bcfg2/Server/Plugin/helpers.py | 52 ++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index c85253be6..9d76337e0 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -5,6 +5,7 @@ import re import sys import copy import time +import genshi import logging import operator import lxml.etree @@ -17,7 +18,6 @@ from Bcfg2.Server.Plugin.base import Debuggable, Plugin from Bcfg2.Server.Plugin.interfaces import Generator from Bcfg2.Server.Plugin.exceptions import SpecificityError, \ PluginExecutionError -import genshi.core try: import Bcfg2.Encryption @@ -590,9 +590,31 @@ class StructFile(XMLFileBacked): def __init__(self, filename, should_monitor=False): XMLFileBacked.__init__(self, filename, should_monitor=should_monitor) self.setup = Bcfg2.Options.get_option_parser() + self.encoding = self.setup['encoding'] + self.template = None def Index(self): XMLFileBacked.Index(self) + if (self.name.endswith('.genshi') or + ('py' in self.xdata.nsmap and + self.xdata.nsmap['py'] == 'http://genshi.edgewall.org/')): + try: + loader = genshi.template.TemplateLoader() + self.template = loader.load(self.name, + cls=genshi.template.MarkupTemplate, + encoding=self.encoding) + except LookupError: + err = sys.exc_info()[1] + LOGGER.error('Genshi lookup error in %s: %s' % (self.name, + err)) + except genshi.template.TemplateError: + err = sys.exc_info()[1] + LOGGER.error('Genshi template error in %s: %s' % (self.name, + err)) + except genshi.input.ParseError: + err = sys.exc_info()[1] + LOGGER.error('Genshi parse error in %s: %s' % (self.name, err)) + if self.encryption and HAS_CRYPTO: strict = self.xdata.get( "decrypt", @@ -644,6 +666,21 @@ class StructFile(XMLFileBacked): else: return True + def _render(self, metadata): + """ Render the template for the given client metadata + + :param metadata: Client metadata to match against. + :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata + :returns: lxml.etree._Element object representing the rendered + XML data + """ + stream = self.template.generate( + metadata=metadata, + repo=self.setup['repo']).filter(removecomment) + return lxml.etree.XML(stream.render('xml', + strip_whitespace=False), + parser=Bcfg2.Server.XMLParser) + def _match(self, item, metadata): """ recursive helper for Match() """ if self._include_element(item, metadata): @@ -677,7 +714,13 @@ class StructFile(XMLFileBacked): :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata :returns: list of lxml.etree._Element objects """ rv = [] - for child in self.entries: + if self.template is None: + entries = self.entries + else: + entries = self._render(metadata).getchildren() + print "rendered: %s" % lxml.etree.tostring(self._render(metadata), + pretty_print=True) + for child in entries: rv.extend(self._match(child, metadata)) return rv @@ -713,7 +756,10 @@ class StructFile(XMLFileBacked): :param metadata: Client metadata to match against. :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata :returns: lxml.etree._Element """ - rv = copy.deepcopy(self.xdata) + if self.template is None: + rv = copy.deepcopy(self.xdata) + else: + rv = self._render(metadata) for child in rv.iterchildren(): self._xml_match(child, metadata) return rv -- cgit v1.2.3-1-g7c22 From 1f6cb52d0c43f842766f3ecd6c8286f0f4eed5c2 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Mon, 4 Feb 2013 16:20:46 -0500 Subject: Bundler: various changes * Deprecated use of an explicit name attribute * Deprecated .genshi bundles * Minor restructuring for better performance * bcfg2-lint updates --- src/lib/Bcfg2/Server/Plugin/helpers.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 9d76337e0..871b5eb4e 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -570,7 +570,7 @@ class XMLFileBacked(FileBacked): return "%s at %s" % (self.__class__.__name__, self.name) -class StructFile(XMLFileBacked): +class StructFile(XMLFileBacked, Debuggable): """ StructFiles are XML files that contain a set of structure file formatting logic for handling ```` and ```` tags. @@ -589,6 +589,7 @@ class StructFile(XMLFileBacked): def __init__(self, filename, should_monitor=False): XMLFileBacked.__init__(self, filename, should_monitor=should_monitor) + Debuggable.__init__(self) self.setup = Bcfg2.Options.get_option_parser() self.encoding = self.setup['encoding'] self.template = None @@ -605,15 +606,16 @@ class StructFile(XMLFileBacked): encoding=self.encoding) except LookupError: err = sys.exc_info()[1] - LOGGER.error('Genshi lookup error in %s: %s' % (self.name, - err)) + self.logger.error('Genshi lookup error in %s: %s' % (self.name, + err)) except genshi.template.TemplateError: err = sys.exc_info()[1] - LOGGER.error('Genshi template error in %s: %s' % (self.name, - err)) + self.logger.error('Genshi template error in %s: %s' % + (self.name, err)) except genshi.input.ParseError: err = sys.exc_info()[1] - LOGGER.error('Genshi parse error in %s: %s' % (self.name, err)) + self.logger.error('Genshi parse error in %s: %s' % (self.name, + err)) if self.encryption and HAS_CRYPTO: strict = self.xdata.get( @@ -625,15 +627,15 @@ class StructFile(XMLFileBacked): el.text = self._decrypt(el).encode('ascii', 'xmlcharrefreplace') except UnicodeDecodeError: - LOGGER.info("%s: Decrypted %s to gibberish, skipping" % - (self.name, el.tag)) + self.logger.info("%s: Decrypted %s to gibberish, skipping" + % (self.name, el.tag)) except Bcfg2.Encryption.EVPError: msg = "Failed to decrypt %s element in %s" % (el.tag, self.name) if strict: raise PluginExecutionError(msg) else: - LOGGER.warning(msg) + self.logger.warning(msg) Index.__doc__ = XMLFileBacked.Index.__doc__ def _decrypt(self, element): -- cgit v1.2.3-1-g7c22 From 25cb6db5ccb0c8e8302c220a90344a95baf3909b Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 5 Feb 2013 14:04:09 -0500 Subject: moved some libraries in Bcfg2/ into more specific (Server/ or Client/) places --- src/lib/Bcfg2/Server/Plugin/helpers.py | 55 +++++++--------------------------- 1 file changed, 10 insertions(+), 45 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 871b5eb4e..bc82ee6c2 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -4,14 +4,12 @@ import os import re import sys import copy -import time import genshi import logging import operator import lxml.etree import Bcfg2.Server import Bcfg2.Options -import Bcfg2.Statistics import Bcfg2.Server.FileMonitor from Bcfg2.Compat import CmpMixin, wraps from Bcfg2.Server.Plugin.base import Debuggable, Plugin @@ -20,7 +18,7 @@ from Bcfg2.Server.Plugin.exceptions import SpecificityError, \ PluginExecutionError try: - import Bcfg2.Encryption + import Bcfg2.Server.Encryption HAS_CRYPTO = True except ImportError: HAS_CRYPTO = False @@ -94,40 +92,6 @@ def bind_info(entry, metadata, infoxml=None, default=None): entry.set(attr, val) -class track_statistics(object): # pylint: disable=C0103 - """ Decorator that tracks execution time for the given - :class:`Plugin` method with :mod:`Bcfg2.Statistics` for reporting - via ``bcfg2-admin perf`` """ - - def __init__(self, name=None): - """ - :param name: The name under which statistics for this function - will be tracked. By default, the name will be - the name of the function concatenated with the - name of the class the function is a member of. - :type name: string - """ - # if this is None, it will be set later during __call_ - self.name = name - - def __call__(self, func): - if self.name is None: - self.name = func.__name__ - - @wraps(func) - def inner(obj, *args, **kwargs): - """ The decorated function """ - name = "%s:%s" % (obj.__class__.__name__, self.name) - - start = time.time() - try: - return func(obj, *args, **kwargs) - finally: - Bcfg2.Statistics.stats.add_value(name, time.time() - start) - - return inner - - class DatabaseBacked(Plugin): """ Provides capabilities for a plugin to read and write to a database. @@ -620,8 +584,8 @@ class StructFile(XMLFileBacked, Debuggable): if self.encryption and HAS_CRYPTO: strict = self.xdata.get( "decrypt", - self.setup.cfp.get(Bcfg2.Encryption.CFG_SECTION, "decrypt", - default="strict")) == "strict" + self.setup.cfp.get(Bcfg2.Server.Encryption.CFG_SECTION, + "decrypt", default="strict")) == "strict" for el in self.xdata.xpath("//*[@encrypted]"): try: el.text = self._decrypt(el).encode('ascii', @@ -629,7 +593,7 @@ class StructFile(XMLFileBacked, Debuggable): except UnicodeDecodeError: self.logger.info("%s: Decrypted %s to gibberish, skipping" % (self.name, el.tag)) - except Bcfg2.Encryption.EVPError: + except Bcfg2.Server.Encryption.EVPError: msg = "Failed to decrypt %s element in %s" % (el.tag, self.name) if strict: @@ -642,19 +606,20 @@ class StructFile(XMLFileBacked, Debuggable): """ Decrypt a single encrypted properties file element """ if not element.text or not element.text.strip(): return - passes = Bcfg2.Encryption.get_passphrases() + passes = Bcfg2.Server.Encryption.get_passphrases() try: passphrase = passes[element.get("encrypted")] try: - return Bcfg2.Encryption.ssl_decrypt(element.text, passphrase) - except Bcfg2.Encryption.EVPError: + return Bcfg2.Server.Encryption.ssl_decrypt(element.text, + passphrase) + except Bcfg2.Server.Encryption.EVPError: # error is raised below pass except KeyError: # bruteforce_decrypt raises an EVPError with a sensible # error message, so we just let it propagate up the stack - return Bcfg2.Encryption.bruteforce_decrypt(element.text) - raise Bcfg2.Encryption.EVPError("Failed to decrypt") + return Bcfg2.Server.Encryption.bruteforce_decrypt(element.text) + raise Bcfg2.Server.Encryption.EVPError("Failed to decrypt") def _include_element(self, item, metadata): """ determine if an XML element matches the metadata """ -- cgit v1.2.3-1-g7c22 From 2169edc1bba82076db776b75db89b79d6f2f4786 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 6 Feb 2013 15:45:20 -0500 Subject: converted InfoXML objects from XMLSrc to StructFile --- src/lib/Bcfg2/Server/Plugin/helpers.py | 359 +++++++++++++++++---------------- 1 file changed, 188 insertions(+), 171 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index bc82ee6c2..6b73ea66a 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -60,38 +60,6 @@ def default_path_metadata(): return dict([(k, setup[k]) for k in attrs]) -def bind_info(entry, metadata, infoxml=None, default=None): - """ Bind the file metadata in the given - :class:`Bcfg2.Server.Plugin.helpers.InfoXML` object to the given - entry. - - :param entry: The abstract entry to bind the info to - :type entry: lxml.etree._Element - :param metadata: The client metadata to get info for - :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata - :param infoxml: The info.xml file to pull file metadata from - :type infoxml: Bcfg2.Server.Plugin.helpers.InfoXML - :param default: Default metadata to supply when the info.xml file - does not include a particular attribute - :type default: dict - :returns: None - :raises: :class:`Bcfg2.Server.Plugin.exceptions.PluginExecutionError` - """ - if default is None: - default = default_path_metadata() - for attr, val in list(default.items()): - entry.set(attr, val) - if infoxml: - mdata = dict() - infoxml.pnode.Match(metadata, mdata, entry=entry) - if 'Info' not in mdata: - msg = "Failed to set metadata for file %s" % entry.get('name') - LOGGER.error(msg) - raise PluginExecutionError(msg) - for attr, val in list(mdata['Info'][None].items()): - entry.set(attr, val) - - class DatabaseBacked(Plugin): """ Provides capabilities for a plugin to read and write to a database. @@ -541,6 +509,7 @@ class StructFile(XMLFileBacked, Debuggable): .. ----- .. autoattribute:: __identifier__ + .. autofunction:: _include_element """ #: If ``__identifier__`` is not None, then it must be the name of @@ -551,6 +520,20 @@ class StructFile(XMLFileBacked, Debuggable): #: Whether or not encryption support is enabled in this file encryption = True + #: Callbacks used to determine if children of items with the given + #: tags should be included in the return value of + #: :func:`Bcfg2.Server.Plugin.helpers.StructFile.Match` and + #: :func:`Bcfg2.Server.Plugin.helpers.StructFile.XMLMatch`. Each + #: callback is passed the same arguments as + #: :func:`Bcfg2.Server.Plugin.helpers.StructFile._include_element`. + #: It should return True if children of the element should be + #: included in the match, False otherwise. The callback does + #: *not* need to consider negation; that will be handled in + #: :func:`Bcfg2.Server.Plugin.helpers.StructFile._include_element` + _include_tests = \ + dict(Group=lambda el, md, *args: el.get('name') in md.groups, + Client=lambda el, md, *args: el.get('name') == md.hostname) + def __init__(self, filename, should_monitor=False): XMLFileBacked.__init__(self, filename, should_monitor=should_monitor) Debuggable.__init__(self) @@ -621,15 +604,25 @@ class StructFile(XMLFileBacked, Debuggable): return Bcfg2.Server.Encryption.bruteforce_decrypt(element.text) raise Bcfg2.Server.Encryption.EVPError("Failed to decrypt") - def _include_element(self, item, metadata): - """ determine if an XML element matches the metadata """ + def _include_element(self, item, metadata, *args): + """ Determine if an XML element matches the other arguments. + + The first argument is always the XML element to match, and the + second will always be a single + :class:`Bcfg2.Server.Plugins.Metadata.ClientMetadata` object + representing the metadata to match against. Subsequent + arguments are as given to + :func:`Bcfg2.Server.Plugin.helpers.StructFile.Match` or + :func:`Bcfg2.Server.Plugin.helpers.StructFile.XMLMatch`. In + the base StructFile implementation, there are no additional + arguments; in classes that inherit from StructFile, see the + :func:`Match` and :func:`XMLMatch` method signatures.""" if isinstance(item, lxml.etree._Comment): # pylint: disable=W0212 return False - negate = item.get('negate', 'false').lower() == 'true' - if item.tag == 'Group': - return negate == (item.get('name') not in metadata.groups) - elif item.tag == 'Client': - return negate == (item.get('name') != metadata.hostname) + if item.tag in self._include_tests: + negate = item.get('negate', 'false').lower() == 'true' + return negate != self._include_tests[item.tag](item, metadata, + *args) else: return True @@ -648,25 +641,42 @@ class StructFile(XMLFileBacked, Debuggable): strip_whitespace=False), parser=Bcfg2.Server.XMLParser) - def _match(self, item, metadata): - """ recursive helper for Match() """ - if self._include_element(item, metadata): - if item.tag == 'Group' or item.tag == 'Client': + def _match(self, item, metadata, *args): + """ recursive helper for + :func:`Bcfg2.Server.Plugin.helpers.StructFile.Match` """ + if self._include_element(item, metadata, *args): + if item.tag in self._include_tests.keys(): rv = [] - if self._include_element(item, metadata): + if self._include_element(item, metadata, *args): for child in item.iterchildren(): - rv.extend(self._match(child, metadata)) + rv.extend(self._match(child, metadata, *args)) return rv else: rv = copy.deepcopy(item) for child in rv.iterchildren(): rv.remove(child) for child in item.iterchildren(): - rv.extend(self._match(child, metadata)) + rv.extend(self._match(child, metadata, *args)) return [rv] else: return [] + def _do_match(self, metadata, *args): + """ Helper for + :func:`Bcfg2.Server.Plugin.helpers.StructFile.Match` that lets + a subclass of StructFile easily redefine the public Match() + interface to accept a different number of arguments. This + provides a sane prototype for the Match() function while + keeping the internals consistent. """ + rv = [] + if self.template is None: + entries = self.entries + else: + entries = self._render(metadata).getchildren() + for child in entries: + rv.extend(self._match(child, metadata, *args)) + return rv + def Match(self, metadata): """ Return matching fragments of the data in this file. A tag is considered to match if all ```` and ```` @@ -680,25 +690,17 @@ class StructFile(XMLFileBacked, Debuggable): :param metadata: Client metadata to match against. :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata :returns: list of lxml.etree._Element objects """ - rv = [] - if self.template is None: - entries = self.entries - else: - entries = self._render(metadata).getchildren() - print "rendered: %s" % lxml.etree.tostring(self._render(metadata), - pretty_print=True) - for child in entries: - rv.extend(self._match(child, metadata)) - return rv + return self._do_match(metadata) - def _xml_match(self, item, metadata): - """ recursive helper for XMLMatch """ - if self._include_element(item, metadata): - if item.tag == 'Group' or item.tag == 'Client': + def _xml_match(self, item, metadata, *args): + """ recursive helper for + :func:`Bcfg2.Server.Plugin.helpers.StructFile.XMLMatch` """ + if self._include_element(item, metadata, *args): + if item.tag in self._include_tests.keys(): for child in item.iterchildren(): item.remove(child) item.getparent().append(child) - self._xml_match(child, metadata) + self._xml_match(child, metadata, *args) if item.text: if item.getparent().text is None: item.getparent().text = item.text @@ -707,10 +709,25 @@ class StructFile(XMLFileBacked, Debuggable): item.getparent().remove(item) else: for child in item.iterchildren(): - self._xml_match(child, metadata) + self._xml_match(child, metadata, *args) else: item.getparent().remove(item) + def _do_xmlmatch(self, metadata, *args): + """ Helper for + :func:`Bcfg2.Server.Plugin.helpers.StructFile.XMLMatch` that lets + a subclass of StructFile easily redefine the public Match() + interface to accept a different number of arguments. This + provides a sane prototype for the Match() function while + keeping the internals consistent. """ + if self.template is None: + rv = copy.deepcopy(self.xdata) + else: + rv = self._render(metadata) + for child in rv.iterchildren(): + self._xml_match(child, metadata, *args) + return rv + def XMLMatch(self, metadata): """ Return a rebuilt XML document that only contains the matching portions of the original file. A tag is considered @@ -723,14 +740,7 @@ class StructFile(XMLFileBacked, Debuggable): :param metadata: Client metadata to match against. :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata :returns: lxml.etree._Element """ - if self.template is None: - rv = copy.deepcopy(self.xdata) - else: - rv = self._render(metadata) - for child in rv.iterchildren(): - self._xml_match(child, metadata) - return rv - + return self._do_xmlmatch(metadata) class INode(object): """ INodes provide lists of things available at a particular group @@ -804,26 +814,6 @@ class INode(object): child.Match(metadata, data, entry=entry) -class InfoNode (INode): - """ :class:`Bcfg2.Server.Plugin.helpers.INode` implementation that - includes ```` tags, suitable for use with :file:`info.xml` - files.""" - - raw = dict( - Client="lambda m, e: '%(name)s' == m.hostname and predicate(m, e)", - Group="lambda m, e: '%(name)s' in m.groups and predicate(m, e)", - Path="lambda m, e: ('%(name)s' == e.get('name') or " + - "'%(name)s' == e.get('realname')) and " + - "predicate(m, e)") - nraw = dict( - Client="lambda m, e: '%(name)s' != m.hostname and predicate(m, e)", - Group="lambda m, e: '%(name)s' not in m.groups and predicate(m, e)", - Path="lambda m, e: '%(name)s' != e.get('name') and " + - "'%(name)s' != e.get('realname') and " + - "predicate(m, e)") - containers = ['Group', 'Client', 'Path'] - - class XMLSrc(XMLFileBacked): """ XMLSrc files contain a :class:`Bcfg2.Server.Plugin.helpers.INode` hierarchy that returns @@ -886,13 +876,49 @@ class XMLSrc(XMLFileBacked): return str(self.items) -class InfoXML(XMLSrc): - """ InfoXML files contain a - :class:`Bcfg2.Server.Plugin.helpers.InfoNode` hierarchy that - returns matching entries, suitable for use with :file:`info.xml` - files.""" - __node__ = InfoNode - __priority_required__ = False +class InfoXML(StructFile): + """ InfoXML files contain Group, Client, and Path tags to set the + metadata (permissions, owner, etc.) of files. """ + encryption = False + + _include_tests = StructFile._include_tests + _include_tests['Path'] = lambda el, md, entry, *args: \ + entry.get("name") == el.get("name") + + def Match(self, metadata, entry): # pylint: disable=W0221 + """ Implementation of + :func:`Bcfg2.Server.Plugin.helpers.StructFile.Match` that + considers Path tags to allow ``info.xml`` files to set + different file metadata for different file paths. """ + return self._do_match(metadata, entry) + + def XMLMatch(self, metadata, entry): # pylint: disable=W0221 + """ Implementation of + :func:`Bcfg2.Server.Plugin.helpers.StructFile.XMLMatch` that + considers Path tags to allow ``info.xml`` files to set + different file metadata for different file paths. """ + return self._do_xmlmatch(metadata, entry) + + def BindEntry(self, entry, metadata): + """ Bind the matching file metadata for this client and entry + to the entry. + + :param entry: The abstract entry to bind the info to. This + will be modified in place + :type entry: lxml.etree._Element + :param metadata: The client metadata to get info for + :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata + :returns: None + """ + fileinfo = self.Match(metadata, entry) + if len(fileinfo) == 0: + raise PluginExecutionError("No metadata found in %s for %s" % + (self.name, entry.get('name'))) + elif len(fileinfo) > 1: + self.logger.warning("Multiple file metadata found in %s for %s" % + (self.name, entry.get('name'))) + for attr, val in fileinfo[0].attrib.items(): + entry.set(attr, val) class XMLDirectoryBacked(DirectoryBacked): @@ -908,6 +934,24 @@ class XMLDirectoryBacked(DirectoryBacked): __child__ = XMLFileBacked +class PriorityStructFile(StructFile): + """ A StructFile where each file has a priority, given as a + top-level XML attribute. """ + + def __init__(self, filename, should_monitor=False): + StructFile.__init__(self, filename, should_monitor=should_monitor) + self.priority = -1 + __init__.__doc__ = StructFile.__init__.__doc__ + + def Index(self): + try: + self.priority = int(self.xdata.get('priority')) + except (ValueError, TypeError): + raise PluginExecutionError("Got bogus priority %s for file %s" % + (self.xdata.get('priority'), self.name)) + Index.__doc__ = StructFile.Index.__doc__ + + class PrioDir(Plugin, Generator, XMLDirectoryBacked): """ PrioDir handles a directory of XML files where each file has a set priority. @@ -918,8 +962,8 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked): #: The type of child objects to create for files contained within #: the directory that is tracked. Default is - #: :class:`Bcfg2.Server.Plugin.helpers.XMLSrc` - __child__ = XMLSrc + #: :class:`Bcfg2.Server.Plugin.helpers.PriorityStructFile` + __child__ = PriorityStructFile def __init__(self, core, datastore): Plugin.__init__(self, core, datastore) @@ -939,21 +983,22 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked): self.Entries[itype] = {child: self.BindEntry} HandleEvent.__doc__ = XMLDirectoryBacked.HandleEvent.__doc__ - def _matches(self, entry, metadata, rules): # pylint: disable=W0613 - """ Whether or not a given entry has a matching entry in this - PrioDir. By default this does strict matching (i.e., the - entry name is in ``rules.keys()``), but this can be overridden - to provide regex matching, etc. + def _matches(self, entry, metadata, candidate): # pylint: disable=W0613 + """ Whether or not a given candidate matches the abstract + entry given. By default this does strict matching (i.e., the + entry name matches the candidate name), but this can be + overridden to provide regex matching, etc. :param entry: The entry to find a match for :type entry: lxml.etree._Element :param metadata: The metadata to get attributes for :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata - :rules: A dict of rules to look in for a matching rule - :type rules: dict + :candidate: A candidate concrete entry to match with + :type candidate: lxml.etree._Element :returns: bool """ - return entry.get('name') in rules + return (entry.tag == candidate.tag and + entry.get('name') == candidate.get('name')) def BindEntry(self, entry, metadata): """ Bind the attributes that apply to an entry to it. The @@ -965,71 +1010,40 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked): :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata :returns: None """ - attrs = self.get_attrs(entry, metadata) - for key, val in list(attrs.items()): - entry.attrib[key] = val - - def get_attrs(self, entry, metadata): - """ Get a list of attributes to add to the entry during the - bind. This is a complex method, in that it both modifies the - entry, and returns attributes that need to be added to the - entry. That seems sub-optimal, and should probably be changed - at some point. Namely: - - * The return value includes all XML attributes that need to be - added to the entry, but it does not add them. - * If text contents or child tags need to be added to the - entry, they are added to the entry in place. - - :param entry: The entry to add attributes to. - :type entry: lxml.etree._Element - :param metadata: The metadata to get attributes for - :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata - :returns: dict of : - :raises: :class:`Bcfg2.Server.Plugin.exceptions.PluginExecutionError` - """ + matching = [] for src in self.entries.values(): - src.Cache(metadata) - - matching = [src for src in list(self.entries.values()) - if (src.cache and - entry.tag in src.cache[1] and - self._matches(entry, metadata, - src.cache[1][entry.tag]))] + for candidate in src.XMLMatch(metadata).xpath("//%s" % entry.tag): + if self._matches(entry, metadata, candidate): + matching.append((src, candidate)) if len(matching) == 0: raise PluginExecutionError("No matching source for entry when " - "retrieving attributes for %s(%s)" % - (entry.tag, entry.attrib.get('name'))) + "retrieving attributes for %s:%s" % + (entry.tag, entry.get('name'))) elif len(matching) == 1: - index = 0 + data = matching[0][1] else: - prio = [int(src.priority) for src in matching] - if prio.count(max(prio)) > 1: - msg = "Found conflicting sources with same priority for " + \ - "%s:%s for %s" % (entry.tag, entry.get("name"), - metadata.hostname) + prio = [int(m[0].priority) for m in matching] + priority = max(prio) + if prio.count(priority) > 1: + msg = "Found conflicting sources with same priority (%s) " \ + "for %s:%s for %s" % (priority, entry.tag, + entry.get("name"), metadata.hostname) self.logger.error(msg) - self.logger.error([item.name for item in matching]) - self.logger.error("Priority was %s" % max(prio)) + self.logger.error([m[0].name for m in matching]) raise PluginExecutionError(msg) - index = prio.index(max(prio)) - for rname in list(matching[index].cache[1][entry.tag].keys()): - if self._matches(entry, metadata, [rname]): - data = matching[index].cache[1][entry.tag][rname] - break - else: - # Fall back on __getitem__. Required if override used - data = matching[index].cache[1][entry.tag][entry.get('name')] - if '__text__' in data: - entry.text = data['__text__'] - if '__children__' in data: - for item in data['__children__']: - entry.append(copy.copy(item)) + for src, candidate in matching: + if int(src.priority) == priority: + data = candidate + break + + entry.text = data.text + for item in data.getchildren(): + entry.append(copy.copy(item)) - return dict([(key, data[key]) - for key in list(data.keys()) - if not key.startswith('__')]) + for key, val in list(data.attrib.items()): + if key not in entry.attrib: + entry.attrib[key] = val class Specificity(CmpMixin): @@ -1312,8 +1326,8 @@ class EntrySet(Debuggable): self.entry_init(event) else: if event.filename not in self.entries: - LOGGER.warning("Got %s event for unknown file %s" % - (action, event.filename)) + self.logger.warning("Got %s event for unknown file %s" % + (action, event.filename)) if action == 'changed': # received a bogus changed event; warn, but treat # it like a created event @@ -1349,7 +1363,7 @@ class EntrySet(Debuggable): entry_type = self.entry_type if event.filename in self.entries: - LOGGER.warn("Got duplicate add for %s" % event.filename) + self.logger.warn("Got duplicate add for %s" % event.filename) else: fpath = os.path.join(self.path, event.filename) try: @@ -1357,8 +1371,8 @@ class EntrySet(Debuggable): specific=specific) except SpecificityError: if not self.ignore.match(event.filename): - LOGGER.error("Could not process filename %s; ignoring" % - fpath) + self.logger.error("Could not process filename %s; ignoring" + % fpath) return self.entries[event.filename] = entry_type(fpath, spec, self.encoding) @@ -1432,8 +1446,8 @@ class EntrySet(Debuggable): self.infoxml = None def bind_info_to_entry(self, entry, metadata): - """ Shortcut to call :func:`bind_info` with the base - info/info.xml for this EntrySet. + """ Bind the metadata for the given client in the base + info.xml for this EntrySet to the entry. :param entry: The abstract entry to bind the info to. This will be modified in place @@ -1442,7 +1456,10 @@ class EntrySet(Debuggable): :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata :returns: None """ - bind_info(entry, metadata, infoxml=self.infoxml, default=self.metadata) + for attr, val in list(self.metadata.items()): + entry.set(attr, val) + if self.infoxml is not None: + self.infoxml.BindEntry(entry, metadata) def bind_entry(self, entry, metadata): """ Return the single best fully-bound entry from the set of -- cgit v1.2.3-1-g7c22 From 65caf3f586d7985d88652c73e7b214ba3e40eac2 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 6 Feb 2013 16:47:44 -0500 Subject: documented which XML files have which features --- src/lib/Bcfg2/Server/Plugin/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 6b73ea66a..2daf4e0df 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -509,7 +509,7 @@ class StructFile(XMLFileBacked, Debuggable): .. ----- .. autoattribute:: __identifier__ - .. autofunction:: _include_element + .. automethod:: _include_element """ #: If ``__identifier__`` is not None, then it must be the name of -- cgit v1.2.3-1-g7c22 From 7db65d41386768a5081c34c16db17e82b96a5b7a Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 7 Feb 2013 08:26:39 -0500 Subject: made XInlcude and Encryption support more consistent --- src/lib/Bcfg2/Server/Plugin/helpers.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 2daf4e0df..9bdfe347f 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -517,9 +517,6 @@ class StructFile(XMLFileBacked, Debuggable): #: the file being cached __identifier__ = None - #: Whether or not encryption support is enabled in this file - encryption = True - #: Callbacks used to determine if children of items with the given #: tags should be included in the return value of #: :func:`Bcfg2.Server.Plugin.helpers.StructFile.Match` and @@ -564,7 +561,7 @@ class StructFile(XMLFileBacked, Debuggable): self.logger.error('Genshi parse error in %s: %s' % (self.name, err)) - if self.encryption and HAS_CRYPTO: + if HAS_CRYPTO: strict = self.xdata.get( "decrypt", self.setup.cfp.get(Bcfg2.Server.Encryption.CFG_SECTION, @@ -879,7 +876,6 @@ class XMLSrc(XMLFileBacked): class InfoXML(StructFile): """ InfoXML files contain Group, Client, and Path tags to set the metadata (permissions, owner, etc.) of files. """ - encryption = False _include_tests = StructFile._include_tests _include_tests['Path'] = lambda el, md, entry, *args: \ -- cgit v1.2.3-1-g7c22 From d0cb9264234851ad65ec8502a56c3afefd39fbad Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 12 Feb 2013 08:20:28 -0500 Subject: expose VCS revision via RMI --- src/lib/Bcfg2/Server/Plugin/interfaces.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/interfaces.py b/src/lib/Bcfg2/Server/Plugin/interfaces.py index f42ada773..fcd342b33 100644 --- a/src/lib/Bcfg2/Server/Plugin/interfaces.py +++ b/src/lib/Bcfg2/Server/Plugin/interfaces.py @@ -531,6 +531,8 @@ class Version(Plugin): #: be ".svn" __vcs_metadata_path__ = None + __rmi__ = Bcfg2.Server.Plugin.Version.__rmi__ + ['get_revision'] + def __init__(self, core, datastore): Plugin.__init__(self, core, datastore) -- cgit v1.2.3-1-g7c22 From 5363e6d9a53146333da0d109aae170befc1b9481 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 12 Feb 2013 07:48:33 -0500 Subject: Added client ACLs: * IP and CIDR-based ACLs * Metadata (group/hostname)-based ACLs * Documentation * Unit tests --- src/lib/Bcfg2/Server/Plugin/helpers.py | 36 ++++++++++++++++++------------- src/lib/Bcfg2/Server/Plugin/interfaces.py | 30 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 15 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 9bdfe347f..ae3b84fc2 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -137,7 +137,7 @@ class PluginDatabaseModel(object): app_label = "Server" -class FileBacked(object): +class FileBacked(Debuggable): """ This object caches file data in memory. FileBacked objects are principally meant to be used as a part of :class:`Bcfg2.Server.Plugin.helpers.DirectoryBacked`. """ @@ -147,7 +147,7 @@ class FileBacked(object): :param name: The full path to the file to cache and monitor :type name: string """ - object.__init__(self) + Debuggable.__init__(self) #: A string containing the raw data in this file self.data = '' @@ -172,10 +172,10 @@ class FileBacked(object): self.Index() except IOError: err = sys.exc_info()[1] - LOGGER.error("Failed to read file %s: %s" % (self.name, err)) + self.logger.error("Failed to read file %s: %s" % (self.name, err)) except: err = sys.exc_info()[1] - LOGGER.error("Failed to parse file %s: %s" % (self.name, err)) + self.logger.error("Failed to parse file %s: %s" % (self.name, err)) def Index(self): """ Index() is called by :func:`HandleEvent` every time the @@ -462,9 +462,9 @@ class XMLFileBacked(FileBacked): else: msg = "%s: %s does not exist, skipping" % (self.name, name) if el.findall('./%sfallback' % Bcfg2.Server.XI_NAMESPACE): - LOGGER.debug(msg) + self.logger.debug(msg) else: - LOGGER.warning(msg) + self.logger.warning(msg) def Index(self): self.xdata = lxml.etree.XML(self.data, base_url=self.name, @@ -475,7 +475,8 @@ class XMLFileBacked(FileBacked): self.xdata.getroottree().xinclude() except lxml.etree.XIncludeError: err = sys.exc_info()[1] - LOGGER.error("XInclude failed on %s: %s" % (self.name, err)) + self.logger.error("XInclude failed on %s: %s" % (self.name, + err)) self.entries = self.xdata.getchildren() if self.__identifier__ is not None: @@ -502,7 +503,7 @@ class XMLFileBacked(FileBacked): return "%s at %s" % (self.__class__.__name__, self.name) -class StructFile(XMLFileBacked, Debuggable): +class StructFile(XMLFileBacked): """ StructFiles are XML files that contain a set of structure file formatting logic for handling ```` and ```` tags. @@ -533,7 +534,6 @@ class StructFile(XMLFileBacked, Debuggable): def __init__(self, filename, should_monitor=False): XMLFileBacked.__init__(self, filename, should_monitor=should_monitor) - Debuggable.__init__(self) self.setup = Bcfg2.Options.get_option_parser() self.encoding = self.setup['encoding'] self.template = None @@ -684,6 +684,8 @@ class StructFile(XMLFileBacked, Debuggable): Match() (and *not* their descendents) should be considered to match the metadata. + Match() returns matching fragments in document order. + :param metadata: Client metadata to match against. :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata :returns: list of lxml.etree._Element objects """ @@ -734,11 +736,15 @@ class StructFile(XMLFileBacked, Debuggable): All ```` and ```` tags will have been stripped out. + The new document produced by XMLMatch() is not necessarily in + the same order as the original document. + :param metadata: Client metadata to match against. :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata :returns: lxml.etree._Element """ return self._do_xmlmatch(metadata) + class INode(object): """ INodes provide lists of things available at a particular group intersection. INodes are deprecated; new plugins should use @@ -834,7 +840,7 @@ class XMLSrc(XMLFileBacked): data = open(self.name).read() except IOError: msg = "Failed to read file %s: %s" % (self.name, sys.exc_info()[1]) - LOGGER.error(msg) + self.logger.error(msg) raise PluginExecutionError(msg) self.items = {} try: @@ -842,7 +848,7 @@ class XMLSrc(XMLFileBacked): except lxml.etree.XMLSyntaxError: msg = "Failed to parse file %s: %s" % (self.name, sys.exc_info()[1]) - LOGGER.error(msg) + self.logger.error(msg) raise PluginExecutionError(msg) self.pnode = self.__node__(xdata, self.items) self.cache = None @@ -852,7 +858,7 @@ class XMLSrc(XMLFileBacked): if self.__priority_required__: msg = "Got bogus priority %s for file %s" % \ (xdata.get('priority'), self.name) - LOGGER.error(msg) + self.logger.error(msg) raise PluginExecutionError(msg) del xdata, data @@ -862,8 +868,8 @@ class XMLSrc(XMLFileBacked): if self.cache is None or self.cache[0] != metadata: cache = (metadata, self.__cacheobj__()) if self.pnode is None: - LOGGER.error("Cache method called early for %s; " - "forcing data load" % self.name) + self.logger.error("Cache method called early for %s; " + "forcing data load" % self.name) self.HandleEvent() return self.pnode.Match(metadata, cache[1]) @@ -1165,7 +1171,7 @@ class SpecificData(object): except UnicodeDecodeError: self.data = open(self.name, mode='rb').read() except: # pylint: disable=W0201 - LOGGER.error("Failed to read file %s" % self.name) + self.logger.error("Failed to read file %s" % self.name) class EntrySet(Debuggable): diff --git a/src/lib/Bcfg2/Server/Plugin/interfaces.py b/src/lib/Bcfg2/Server/Plugin/interfaces.py index fcd342b33..c1dbb1578 100644 --- a/src/lib/Bcfg2/Server/Plugin/interfaces.py +++ b/src/lib/Bcfg2/Server/Plugin/interfaces.py @@ -596,3 +596,33 @@ class ClientRunHooks(object): :returns: None """ pass + + +class ClientACLs(object): + """ ClientACLs are used to grant or deny access to different + XML-RPC calls based on client IP or metadata. """ + + def check_acl_ip(self, address, rmi): + """ Check if the given IP address is authorized to make the + named XML-RPC call. + + :param address: The address pair of the client to check ACLs for + :type address: tuple of (, ) + :param rmi: The fully-qualified name of the RPC call + :param rmi: string + :returns: bool or None - True to allow, False to deny, None to + defer to metadata ACLs + """ + return True + + def check_acl_metadata(self, metadata, rmi): + """ Check if the given client is authorized to make the named + XML-RPC call. + + :param metadata: The client metadata + :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata + :param rmi: The fully-qualified name of the RPC call + :param rmi: string + :returns: bool + """ + return True -- cgit v1.2.3-1-g7c22 From 088ca5fee4cc99f9143f18a880cdec6712326e1e Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 12 Feb 2013 09:47:04 -0500 Subject: fixed unit tests --- src/lib/Bcfg2/Server/Plugin/helpers.py | 2 +- src/lib/Bcfg2/Server/Plugin/interfaces.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index ae3b84fc2..827c884d2 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -1171,7 +1171,7 @@ class SpecificData(object): except UnicodeDecodeError: self.data = open(self.name, mode='rb').read() except: # pylint: disable=W0201 - self.logger.error("Failed to read file %s" % self.name) + LOGGER.error("Failed to read file %s" % self.name) class EntrySet(Debuggable): diff --git a/src/lib/Bcfg2/Server/Plugin/interfaces.py b/src/lib/Bcfg2/Server/Plugin/interfaces.py index c1dbb1578..3ef29775d 100644 --- a/src/lib/Bcfg2/Server/Plugin/interfaces.py +++ b/src/lib/Bcfg2/Server/Plugin/interfaces.py @@ -531,7 +531,7 @@ class Version(Plugin): #: be ".svn" __vcs_metadata_path__ = None - __rmi__ = Bcfg2.Server.Plugin.Version.__rmi__ + ['get_revision'] + __rmi__ = Plugin.__rmi__ + ['get_revision'] def __init__(self, core, datastore): Plugin.__init__(self, core, datastore) @@ -602,7 +602,7 @@ class ClientACLs(object): """ ClientACLs are used to grant or deny access to different XML-RPC calls based on client IP or metadata. """ - def check_acl_ip(self, address, rmi): + def check_acl_ip(self, address, rmi): # pylint: disable=W0613 """ Check if the given IP address is authorized to make the named XML-RPC call. @@ -615,7 +615,7 @@ class ClientACLs(object): """ return True - def check_acl_metadata(self, metadata, rmi): + def check_acl_metadata(self, metadata, rmi): # pylint: disable=W0613 """ Check if the given client is authorized to make the named XML-RPC call. -- cgit v1.2.3-1-g7c22 From e989e62b24479fac99e607d9e4fb3a292bd20418 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 5 Feb 2013 11:36:49 -0500 Subject: added support for wildcard XInclude in XMLFileBacked --- src/lib/Bcfg2/Server/Plugin/helpers.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 827c884d2..187c594fd 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -4,6 +4,7 @@ import os import re import sys import copy +import glob import genshi import logging import operator @@ -438,13 +439,14 @@ class XMLFileBacked(FileBacked): def _follow_xincludes(self, fname=None, xdata=None): """ follow xincludes, adding included files to self.extras """ + xinclude = '%sinclude' % Bcfg2.Server.XI_NAMESPACE + if xdata is None: if fname is None: xdata = self.xdata.getroottree() else: xdata = lxml.etree.parse(fname) - included = [el for el in xdata.findall('//%sinclude' % - Bcfg2.Server.XI_NAMESPACE)] + included = [el for el in xdata.findall('//' + xinclude)] for el in included: name = el.get("href") if name.startswith("/"): @@ -455,16 +457,23 @@ class XMLFileBacked(FileBacked): else: rel = self.name fpath = os.path.join(os.path.dirname(rel), name) - if fpath not in self.extras: - if os.path.exists(fpath): - self._follow_xincludes(fname=fpath) - self.add_monitor(fpath) + + # expand globs in xinclude, a bcfg2-specific extension + extras = glob.glob(fpath) + if not extras: + msg = "%s: %s does not exist, skipping" % (self.name, name) + if el.findall('./%sfallback' % Bcfg2.Server.XI_NAMESPACE): + self.logger.debug(msg) else: - msg = "%s: %s does not exist, skipping" % (self.name, name) - if el.findall('./%sfallback' % Bcfg2.Server.XI_NAMESPACE): - self.logger.debug(msg) - else: - self.logger.warning(msg) + self.logger.warning(msg) + + parent = el.getparent() + parent.remove(el) + for extra in extras: + if extra != self.name and extra not in self.extras: + self.add_monitor(extra) + lxml.etree.SubElement(parent, xinclude, href=extra) + self._follow_xincludes(fname=extra) def Index(self): self.xdata = lxml.etree.XML(self.data, base_url=self.name, -- cgit v1.2.3-1-g7c22 From f91163abed4aa739f7f8b772eabb403f01b94a88 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 13 Feb 2013 16:08:08 -0500 Subject: extended usage of Executor class, added client-side timeout options --- src/lib/Bcfg2/Server/Plugin/base.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) (limited to 'src/lib/Bcfg2/Server/Plugin') diff --git a/src/lib/Bcfg2/Server/Plugin/base.py b/src/lib/Bcfg2/Server/Plugin/base.py index e74909ee9..25a687874 100644 --- a/src/lib/Bcfg2/Server/Plugin/base.py +++ b/src/lib/Bcfg2/Server/Plugin/base.py @@ -2,6 +2,7 @@ import os import logging +from Bcfg2.Utils import ClassName class Debuggable(object): @@ -59,18 +60,6 @@ class Debuggable(object): self.logger.error(message) -class ClassName(object): - """ This very simple descriptor class exists only to get the name - of the owner class. This is used because, for historical reasons, - we expect every plugin to have a ``name`` attribute that is in - almost all cases the same as the ``__class__.__name__`` attribute - of the plugin object. This makes that more dynamic so that each - plugin isn't repeating its own name. """ - - def __get__(self, inst, owner): - return owner.__name__ - - class Plugin(Debuggable): """ The base class for all Bcfg2 Server plugins. """ -- cgit v1.2.3-1-g7c22