summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/server/plugins/generators/cfg.txt114
-rw-r--r--doc/server/plugins/generators/packages.txt76
-rw-r--r--doc/server/plugins/generators/rules.txt7
-rw-r--r--man/bcfg2.conf.515
-rw-r--r--src/lib/Bcfg2/Client/Tools/YUMng.py7
-rwxr-xr-xsrc/lib/Bcfg2/Encryption.py75
-rw-r--r--src/lib/Bcfg2/Options.py23
-rw-r--r--src/lib/Bcfg2/Server/Admin/Reports.py4
-rw-r--r--src/lib/Bcfg2/Server/Core.py27
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor.py315
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/Fam.py82
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/Gamin.py64
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/Inotify.py60
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/Pseudo.py30
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/__init__.py143
-rw-r--r--src/lib/Bcfg2/Server/Plugin.py45
-rw-r--r--src/lib/Bcfg2/Server/Plugins/BB.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Bundler.py5
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py8
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedCheetahGenerator.py14
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py63
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenshiGenerator.py26
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py22
-rw-r--r--src/lib/Bcfg2/Server/Plugins/FileProbes.py5
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Metadata.py36
-rw-r--r--src/lib/Bcfg2/Server/Plugins/NagiosGen.py5
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py5
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Source.py3
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Yum.py18
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/__init__.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Probes.py8
-rw-r--r--src/lib/Bcfg2/Server/Plugins/SGenshi.py5
-rw-r--r--src/lib/Bcfg2/Server/Plugins/SSLCA.py6
-rw-r--r--src/lib/Bcfg2/Server/Reports/reports/models.py2
-rw-r--r--src/lib/Bcfg2/Server/Reports/settings.py10
-rw-r--r--src/lib/Bcfg2/Server/__init__.py6
-rwxr-xr-xsrc/sbin/bcfg2-admin1
-rwxr-xr-xsrc/sbin/bcfg2-info1
-rwxr-xr-xsrc/sbin/bcfg2-lint1
-rwxr-xr-xsrc/sbin/bcfg2-server1
-rwxr-xr-xsrc/sbin/bcfg2-test5
43 files changed, 901 insertions, 458 deletions
diff --git a/doc/server/plugins/generators/cfg.txt b/doc/server/plugins/generators/cfg.txt
index ba02b929d..829130ee4 100644
--- a/doc/server/plugins/generators/cfg.txt
+++ b/doc/server/plugins/generators/cfg.txt
@@ -35,24 +35,24 @@ templating -- see below).
Group-Specific Files
====================
-It is often that you want one version of a config file for all of your
-machines except those in a particular group. For example, ``/etc/fstab``
-should look alike on all of your desktop machines, but should be
-different on your file servers. Bcfg2 can handle this case through use
-of group-specific files.
+It is often the case that you want one version of a config file for
+all of your machines except those in a particular group. For example,
+``/etc/fstab`` should look alike on all of your desktop machines, but
+should be different on your file servers. Bcfg2 can handle this case
+through use of group-specific files.
As mentioned above, all Cfg entries live in like-named directories
at the end of their directory tree. In the case of fstab, the file at
``Cfg/etc/fstab/fstab`` will be handed out by default to any client that
asks for a copy of ``/etc/fstab``. Group-specific files are located in
-the same directory and are named with the syntax::
+the same directory and are named with the following syntax::
/path/to/filename/filename.GNN_groupname
-in which **NN** is a priority number where **00** is lowest and
-**99** is highest, and **groupname** is the name of a group defined in
+**NN** is a priority number where **00** is lowest and **99**
+is highest, and **groupname** is the name of a group defined in
``Metadata/groups.xml``. Back to our fstab example, we might have a
-``Cfg/etc/fstab/`` directory that looks like::
+``Cfg/etc/fstab/`` directory that looks like this::
fstab
fstab.G50_server
@@ -139,6 +139,84 @@ using different host-specific or group-specific files. For example:
Cfg/etc/fstab/fstab.H_host.example.com.genshi
Cfg/etc/fstab/fstab.G50_server.cheetah
+Encrypted Files
+===============
+
+.. versionadded:: 1.3.0
+
+Bcfg2 allows you to encrypt files stored in ``Cfg/`` to protect the
+data in them from other people who need access to the repository.
+
+.. note::
+
+ This feature is *not* intended to secure the files against a
+ malicious attacker who has gained access to your Bcfg2 server, as
+ the encryption passphrases are held in plaintext in
+ ``bcfg2.conf``. This is only intended to make it easier to use a
+ single Bcfg2 repository with multiple admins who should not
+ necessarily have access to each other's sensitive data.
+
+Encrypting Files
+----------------
+
+An encrypted file should end with ``.crypt``, e.g.::
+
+ Cfg/etc/foo.conf
+ Cfg/etc/foo.conf/foo.conf.crypt
+ Cfg/etc/foo.conf/foo.conf.G10_foo.crypt
+
+Encrypted Genshi or Cheetah templates can have the extensions in
+either order, e.g.::
+
+ Cfg/etc/foo.conf/foo.conf.crypt.genshi
+ Cfg/etc/foo.conf/foo.conf.G10_foo.genshi.crypt
+ Cfg/etc/foo.conf/foo.conf.H_bar.example.com.crypt.cheetah
+
+To encrypt a file, you can run::
+
+ openssl enc -aes-256-cbc -k <passphrase> -in foo.conf -out foo.conf.crypt -a
+
+Once you are satisfied that the file has been encrypted as you wish,
+you can remove the plaintext version.
+
+To decrypt a file, you can run::
+
+ openssl enc -d -aes-256-cbc -k <passphrase> -in foo.conf.crypt -out foo.conf -a
+
+Configuring Encryption
+----------------------
+
+To configure encryption, add a ``[cfg:encryption]`` section to
+``bcfg2.conf`` with any number of name-passphrase pairs. When
+decrypting a file, _all_ passphrases will be tried; the passphrase
+name is currently purely cosmetic, but at some point in the future the
+ability to give Bcfg2 a "hint" about which passphrase to use will be
+added.
+
+For instance::
+
+ [cfg:encryption]
+ foo_team=P4ssphr4se
+ bar_team=Pa55phra5e
+
+This would define two separate encryption passphrases, presumably for
+use by two separate teams. The passphrase names are completely
+arbitrary.
+
+Note that this does entail a chicken-and-egg problem. In order for
+the Bcfg2 server to be able to decrypt encrypted files, the
+passphrases must exist in ``bcfg2.conf`` in plaintext; but, if you're
+encrypting data, presumably you don't want to include those plaintext
+passphrases in your Bcfg2 repository, so you'll want to encrypt
+``bcfg2.conf``. The best way to solve this is:
+
+#. On your Bcfg2 server, manually add the ``[cfg:encryption]`` section
+ to ``bcfg2.conf`` and restart the Bcfg2 server.
+#. Update ``bcfg2.conf`` in your Bcfg2 repository with the
+ passphrases, and encrypt it.
+
+The first (manual) step breaks the mutual dependency.
+
Deltas
======
@@ -237,13 +315,19 @@ For ``sudoers``, a very simple validator is::
This uses the ``visudo`` command's built-in validation.
-.. note:
+If you wish to disable validation, this can be done with the following
+setting in ``bcfg2.conf``::
+
+ [cfg]
+ validation=no
- Before 1.3 is released, it will be possible to disable validation
- in the configuration, but enable it for ``bcfg2-test``. This is
- recommended for heavily-used servers, since running an external
- command is fairly resource intensive and could quickly exhaust the
- file descriptors of a server.
+If you have a very large number of validators, you may wish to disable
+validation by default to avoid slowing down the generation of
+configurations on the server, and use ``bcfg2-test`` (for instance, as
+a post-commit hook or as part of a code review process) to run
+validation. You can do this by setting ``validation=no`` in
+``bcfg2.conf`` as described above, and then calling ``bcfg2-test``
+with the ``--cfg-validation`` flag.
File permissions
================
diff --git a/doc/server/plugins/generators/packages.txt b/doc/server/plugins/generators/packages.txt
index 62574be76..3c1d304c1 100644
--- a/doc/server/plugins/generators/packages.txt
+++ b/doc/server/plugins/generators/packages.txt
@@ -126,17 +126,19 @@ Disabling dependency resolution
.. versionadded:: 1.1.0
-Dependency resolution can be disabled by adding this to
-``Packages/packages.conf`` in the ``global`` section::
+Dependency resolution can be disabled by adding the following setting
+to ``bcfg2.conf`` in the ``packages`` section::
- [global]
+ [packages]
resolver=0
All metadata processing can be disabled as well::
- [global]
+ [packages]
metadata=0
+This setting implies disabling the resolver.
+
Blacklisting faulty dependencies
--------------------------------
@@ -353,15 +355,6 @@ be validated using ``bcfg2-lint``.
.. note:: The schema requires that elements be specified in the above order.
-Limitations
-===========
-
-Packages does not do traditional caching as other plugins
-do. Modifying sources in the Packages ``sources.xml`` file requires a
-server restart for the time being. You do not have to restart the
-server after changing ``packages.conf`` or after adding new sources to
-``sources.xml``.
-
Package Checking and Verification
=================================
@@ -380,10 +373,10 @@ Generating Client APT/Yum Configurations
.. versionadded:: 1.2.0
The Packages plugin has native support for generating Yum configs.
-You must set ``yum_config`` in ``Packages/packages.conf`` to the path
-to the yum config file you want to generate::
+You must set ``yum_config`` in ``bcfg2.conf`` to the path to the yum
+config file you want to generate::
- [global]
+ [packages]
yum_config=/etc/yum.repos.d/all.repo
Then add the corresponding Path entry to your Yum bundle.
@@ -414,7 +407,7 @@ resolution and other routines so that the Bcfg2 server can be run on a
host that does not support Yum itself. If you run the Bcfg2 server on
a machine that does have Yum libraries, however, you can enable use of
those native libraries in Bcfg2 by setting ``use_yum_libraries`` to
-``1`` in the ``[yum]`` section of ``Packages/packages.conf``.
+``1`` in the ``[packages:yum]`` section of ``bcfg2.conf``.
Benefits to this include:
@@ -440,10 +433,11 @@ Configuring the Yum Helper
Due to poor memory management by the Yum API, the long-lived
bcfg2-server process uses an external short-lived helper,
``bcfg2-yum-helper``, to do the actual Yum API calls for native yum
-library support. By default, Bcfg2 looks for this helper at
-``/usr/sbin/bcfg2-yum-helper``. If you have installed the helper
-elsewhere, you will need to configure that location with the
-``helper`` option in the ``[yum]`` section, e.g.::
+library support. By default, Bcfg2 looks for this helper in
+``$PATH``, or, failing that, at ``/usr/sbin/bcfg2-yum-helper``. If
+you have installed the helper elsewhere, you will need to configure
+that location with the ``helper`` option in the ``[yum]`` section,
+e.g.::
[yum]
use_yum_libraries = 1
@@ -452,11 +446,11 @@ elsewhere, you will need to configure that location with the
Setting Yum Options
-------------------
-In ``Packages/packages.conf``, any options you set in the ``[yum]``
+In ``bcfg2.conf``, any options you set in the ``[packages:yum]``
section other than ``use_yum_libraries`` and ``helper`` will be passed
along verbatim to the configuration of the Yum objects used in the
-Bcfg2 server. The following options are set by default, and should
-not generally be overridden:
+Bcfg2 server. The following options are set by default, and should not
+generally be overridden:
* ``cachedir`` is set to a hashed value unique to each distinct Yum
configuration. Don't set this unless you know what you're doing.
@@ -521,8 +515,8 @@ server must have a valid ``/etc/pulp/consumer/consumer.conf`` that is
readable by the user your Bcfg2 server runs as; the Pulp server,
URLs, and so on, are determined from this.
-Secondly, in ``Packages/packages.conf`` you must set the following
-options in the ``[pulp]`` section:
+Secondly, in ``bcfg2.conf`` you must set the following
+options in the ``[packages:pulp]`` section:
* ``username`` and ``password``: The username and password of a Pulp
user that will be used to register new clients and bind them to
@@ -643,26 +637,27 @@ multiple data sources need to be multiplexed.
The APT source in ``src/lib/Server/Plugins/Packages.py`` provides a
relatively simple implementation of a source.
-packages.conf
+Configuration
=============
-``packages.conf`` contains miscellaneous configuration options for the
+``bcfg2.conf`` contains miscellaneous configuration options for the
Packages plugin. Any booleans in the config file accept the values
"1", "yes", "true", and "on" for True, and "0", "no", "false", and
-"off" for False
+"off" for False.
It understands the following directives:
-[global] section
+[packages] section
----------------
* ``resolver``: Enable dependency resolution. Default is ``1``
(true). For historical reasons, this also accepts "enabled" and
"disabled".
-* ``metadata``: Enable metadata processing. Default is ``1``
- (true). For historical reasons, this also accepts "enabled" and
- "disabled".
-* ``yum_config``: The path at which to generate Yum configs. No
+* ``metadata``: Enable metadata processing. Default is ``1``
+ (true). If ``metadata`` is disabled, it's implied that ``resolver``
+ is also disabled. For historical reasons, this also accepts
+ "enabled" and "disabled".
+* ``yum_config``: The path at which to generate Yum configs. No
default.
* ``apt_config``: The path at which to generate APT configs. No
default.
@@ -672,17 +667,20 @@ It understands the following directives:
* ``version``: Set the version attribute used when binding
Packages. Default is ``auto``.
-[yum] section
+[packages:yum] section
-------------
* ``use_yum_libraries``: Whether or not to use the :ref:`native yum
library support <native-yum-libraries>`. Default is ``0`` (false).
+* ``helper``: Path to ``bcfg2-yum-helper``. By default, Bcfg2 looks
+ first in ``$PATH`` and then in ``/usr/sbin/bcfg2-yum-helper`` for
+ the helper.
-All other options in the ``[yum]`` section will be passed along
-verbatim to the Yum configuration if you are using the native Yum
-library support.
+All other options in the ``[packages:yum]`` section will be passed
+along verbatim to the Yum configuration if you are using the native
+Yum library support.
-[pulp] section
+[packages:pulp] section
--------------
* ``username`` and ``password``: The username and password of a Pulp
diff --git a/doc/server/plugins/generators/rules.txt b/doc/server/plugins/generators/rules.txt
index c084c5681..03372b120 100644
--- a/doc/server/plugins/generators/rules.txt
+++ b/doc/server/plugins/generators/rules.txt
@@ -356,14 +356,11 @@ Using Regular Expressions in Rules
If you wish, you can configure the Rules plugin to support regular
expressions. This entails a small performance and memory usage
-penalty. To do so, create a file, "Rules/rules.conf", and add the
-following text::
+penalty. To do so, add the following setting to ``bcfg2.conf``::
[rules]
regex = yes
-You will have to restart the Bcfg2 server after making that change.
-
With regular expressions enabled, you can use a regex in the ``name``
attribute to match multiple abstract configuration entries.
@@ -372,4 +369,4 @@ name="bcfg2".../>`` will *not* match a Service named ``bcfg2-server``;
you'd have to explicitly specify ``<Service name="bcfg2.*".../>``.
Note that only one Rule can apply to any abstract entry, so you cannot
-specify multiple regexs to match the same rule.
+specify multiple regexes to match the same rule.
diff --git a/man/bcfg2.conf.5 b/man/bcfg2.conf.5
index 684586892..812a86c76 100644
--- a/man/bcfg2.conf.5
+++ b/man/bcfg2.conf.5
@@ -35,7 +35,13 @@ using the 'bcfg2-admin init' command.
.TP
.B filemonitor
The file monitor used to watch for changes in the repository.
-Values of 'gamin', 'fam', or 'pseudo' are valid.
+Values of 'inotify', 'gamin', 'fam', or 'pseudo' are valid. The
+default is the best available monitor.
+
+.TP
+.B ignore_files
+A comma-separated list of globs that should be ignored by the file
+monitor. Default: '*~,.#*,*#,*.swp,SCCS,.svn,4913,.gitignore'
.TP
.B listen_all
@@ -133,7 +139,7 @@ administrator supervision is available. (0.9.6 and later)
\(bu
.B Deps
The Deps plugin allows you to make a series of assertions like
-"Package X requires Package Y (and optionally also Package Z etc.)
+"Package X requires Package Y (and optionally also Package Z etc.)"
\(bu
.B Editor
@@ -441,6 +447,11 @@ sqlite
The name of the database to use for statistics data.
eg: $REPOSITORY_DIR/etc/bcfg2.sqlite
+.SH PLUGIN-SPECIFIC OPTIONS
+
+Many plugins specify their own options in bcfg2.conf; see the online
+documentation about each plugin for more information on these.
+
.SH SEE ALSO
.BR bcfg2(1),
.BR bcfg2-server(8)
diff --git a/src/lib/Bcfg2/Client/Tools/YUMng.py b/src/lib/Bcfg2/Client/Tools/YUMng.py
index 244b66cf4..da627ed7a 100644
--- a/src/lib/Bcfg2/Client/Tools/YUMng.py
+++ b/src/lib/Bcfg2/Client/Tools/YUMng.py
@@ -483,6 +483,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
_POs = POs
if len(_POs) == 0:
# Package (name, arch) not installed
+ entry.set('current_exists', 'false')
self.logger.debug(" %s is not installed" % nevraString(nevra))
stat['installed'] = False
package_fail = True
@@ -513,6 +514,12 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
package_fail = True
stat['version_fail'] = True
# Just chose the first pkg for the error message
+ entry.set('current_version', "%s-%s.%s" % (POs[0]['version'],
+ POs[0]['release'],
+ POs[0]['arch']))
+ entry.set('version', "%s-%s.%s" % (nevra['version'],
+ nevra['release'],
+ nevra['arch']))
self.logger.info(" %s: Wrong version installed. "
"Want %s, but have %s" % (entry.get("name"),
nevraString(nevra),
diff --git a/src/lib/Bcfg2/Encryption.py b/src/lib/Bcfg2/Encryption.py
new file mode 100755
index 000000000..62b22d7de
--- /dev/null
+++ b/src/lib/Bcfg2/Encryption.py
@@ -0,0 +1,75 @@
+#!/usr/bin/python -Ott
+
+import os
+import base64
+from M2Crypto import Rand
+from M2Crypto.EVP import Cipher, EVPError
+from Bcfg2.Bcfg2Py3k import StringIO
+
+try:
+ from hashlib import md5
+except ImportError:
+ from md5 import md5
+
+ENCRYPT = 1
+DECRYPT = 0
+ALGORITHM = "aes_256_cbc"
+IV = '\0' * 16
+
+Rand.rand_seed(os.urandom(1024))
+
+def _cipher_filter(cipher, instr):
+ inbuf = StringIO(instr)
+ outbuf = StringIO()
+ while 1:
+ buf = inbuf.read()
+ if not buf:
+ break
+ outbuf.write(cipher.update(buf))
+ outbuf.write(cipher.final())
+ rv = outbuf.getvalue()
+ inbuf.close()
+ outbuf.close()
+ return rv
+
+def str_encrypt(plaintext, key, iv=IV, algorithm=ALGORITHM, salt=None):
+ """ encrypt a string """
+ cipher = Cipher(alg=algorithm, key=key, iv=iv, op=ENCRYPT, salt=salt)
+ return _cipher_filter(cipher, plaintext)
+
+def str_decrypt(crypted, key, iv=IV, algorithm=ALGORITHM):
+ """ decrypt a string """
+ cipher = Cipher(alg=algorithm, key=key, iv=iv, op=DECRYPT)
+ return _cipher_filter(cipher, crypted)
+
+def ssl_decrypt(data, passwd, algorithm=ALGORITHM):
+ """ decrypt openssl-encrypted data """
+ # base64-decode the data if necessary
+ try:
+ data = base64.b64decode(data)
+ except TypeError:
+ # already decoded
+ pass
+
+ salt = data[8:16]
+ hashes = [md5(passwd + salt).digest()]
+ for i in range(1,3):
+ hashes.append(md5(hashes[i-1] + passwd + salt).digest())
+ key = hashes[0] + hashes[1]
+ iv = hashes[2]
+
+ return str_decrypt(data[16:], key=key, iv=iv)
+
+def ssl_encrypt(plaintext, passwd, algorithm=ALGORITHM, salt=None):
+ """ encrypt data in a format that is openssl compatible """
+ if salt is None:
+ salt = Rand.rand_bytes(8)
+
+ hashes = [md5(passwd + salt).digest()]
+ for i in range(1,3):
+ hashes.append(md5(hashes[i-1] + passwd + salt).digest())
+ key = hashes[0] + hashes[1]
+ iv = hashes[2]
+
+ crypted = str_encrypt(plaintext, key=key, salt=salt, iv=iv)
+ return base64.b64encode("Salted__" + salt + crypted) + "\n"
diff --git a/src/lib/Bcfg2/Options.py b/src/lib/Bcfg2/Options.py
index dfb062341..129a6c31e 100644
--- a/src/lib/Bcfg2/Options.py
+++ b/src/lib/Bcfg2/Options.py
@@ -1,10 +1,11 @@
"""Option parsing library for utilities."""
-import getopt
import re
import os
import sys
+import copy
import shlex
+import getopt
import Bcfg2.Client.Tools
# Compatibility imports
from Bcfg2.Bcfg2Py3k import ConfigParser
@@ -149,6 +150,7 @@ class OptionSet(dict):
self.hm = self.buildHelpMessage()
if 'configfile' in kwargs:
self.cfile = kwargs['configfile']
+ os.environ['BCFG2_CONFIG_FILE'] = self.cfile
else:
self.cfile = DEFAULT_CONFIG_LOCATION
self.cfp = DefaultConfigParser()
@@ -307,6 +309,10 @@ SERVER_MCONNECT = Option('Server Metadata Connector list', cook=list_split,
cf=('server', 'connectors'), default=['Probes'], )
SERVER_FILEMONITOR = Option('Server file monitor', cf=('server', 'filemonitor'),
default='default', odesc='File monitoring driver')
+SERVER_FAM_IGNORE = Option('File globs to ignore',
+ cf=('server', 'ignore_files'), cook=list_split,
+ default=['*~', '.#*', '*#', '*.swp', 'SCCS', '.svn',
+ '4913', '.gitignore'])
SERVER_LISTEN_ALL = Option('Listen on all interfaces',
cf=('server', 'listen_all'),
cmd='--listen-all',
@@ -425,10 +431,14 @@ class OptionParser(OptionSet):
self.Bootstrap = OptionSet([('configfile', CFILE)], quiet=True)
self.Bootstrap.parse(sys.argv[1:], do_getopt=False)
OptionSet.__init__(self, args, configfile=self.Bootstrap['configfile'])
- self.optinfo = args
+ self.optinfo = copy.copy(args)
def HandleEvent(self, event):
- if not self['configfile'].endswith(event.filename):
+ if 'configfile' not in self or not isinstance(self['configfile'], str):
+ # we haven't parsed options yet, or CFILE wasn't included
+ # in the options
+ return
+ if event.filename != self['configfile']:
print("Got event for unknown file: %s" % event.filename)
return
if event.code2str() == 'deleted':
@@ -447,3 +457,10 @@ class OptionParser(OptionSet):
self.do_getopt = do_getopt
OptionSet.parse(self, self.argv, do_getopt=self.do_getopt)
+ def add_option(self, name, opt):
+ self[name] = opt
+ self.optinfo[name] = opt
+
+ def update(self, optdict):
+ dict.update(self, optdict)
+ self.optinfo.update(optdict)
diff --git a/src/lib/Bcfg2/Server/Admin/Reports.py b/src/lib/Bcfg2/Server/Admin/Reports.py
index 974cdff9d..d38889b52 100644
--- a/src/lib/Bcfg2/Server/Admin/Reports.py
+++ b/src/lib/Bcfg2/Server/Admin/Reports.py
@@ -95,8 +95,8 @@ class Reports(Bcfg2.Server.Admin.Mode):
" scrub Scrub the database for duplicate reasons and orphaned entries\n"
" update Apply any updates to the reporting database\n"
"\n"
- " Django commands:\n "
- "\n ".join(django_commands))
+ " Django commands:\n " \
+ + "\n ".join(django_commands))
def __init__(self, setup):
Bcfg2.Server.Admin.Mode.__init__(self, setup)
diff --git a/src/lib/Bcfg2/Server/Core.py b/src/lib/Bcfg2/Server/Core.py
index 8482925b7..c8ef5b1f7 100644
--- a/src/lib/Bcfg2/Server/Core.py
+++ b/src/lib/Bcfg2/Server/Core.py
@@ -16,6 +16,7 @@ except ImportError:
from Bcfg2.Component import Component, exposed
from Bcfg2.Server.Plugin import PluginInitError, PluginExecutionError
+import Bcfg2.Server
import Bcfg2.Server.FileMonitor
import Bcfg2.Server.Plugins.Metadata
# Compatibility imports
@@ -66,17 +67,28 @@ class Core(Component):
filemonitor='default', start_fam_thread=False):
Component.__init__(self)
self.datastore = repo
- if filemonitor not in Bcfg2.Server.FileMonitor.available:
+
+ try:
+ fm = Bcfg2.Server.FileMonitor.available[filemonitor]
+ except KeyError:
logger.error("File monitor driver %s not available; "
"forcing to default" % filemonitor)
- filemonitor = 'default'
+ fm = Bcfg2.Server.FileMonitor.available['default']
+ famargs = dict(ignore=[], debug=False)
+ if 'ignore' in setup:
+ famargs['ignore'] = setup['ignore']
+ if 'debug' in setup:
+ famargs['debug'] = setup['debug']
+ elif 'event debug' in setup:
+ famargs['debug'] = setup['event debug']
try:
- self.fam = Bcfg2.Server.FileMonitor.available[filemonitor]()
+ self.fam = fm(**famargs)
except IOError:
logger.error("Failed to instantiate fam driver %s" % filemonitor,
exc_info=1)
- raise CoreInitError("failed to instantiate fam driver (used %s)" % \
+ raise CoreInitError("Failed to instantiate fam driver (used %s)" %
filemonitor)
+
self.pubspace = {}
self.cfile = cfile
self.cron = {}
@@ -197,6 +209,7 @@ class Core(Component):
"""Shutting down the plugins."""
if not self.terminate.isSet():
self.terminate.set()
+ self.fam.shutdown()
for plugin in list(self.plugins.values()):
plugin.shutdown()
@@ -423,7 +436,8 @@ class Core(Component):
# clear dynamic groups
self.metadata.cgroups[meta.hostname] = []
try:
- xpdata = lxml.etree.XML(probedata.encode('utf-8'))
+ xpdata = lxml.etree.XML(probedata.encode('utf-8'),
+ parser=Bcfg2.Server.XMLParser)
except:
self.logger.error("Failed to parse probe data from client %s" % \
(address[0]))
@@ -472,7 +486,8 @@ class Core(Component):
@exposed
def RecvStats(self, address, stats):
"""Act on statistics upload."""
- sdata = lxml.etree.XML(stats.encode('utf-8'))
+ sdata = lxml.etree.XML(stats.encode('utf-8'),
+ parser=Bcfg2.Server.XMLParser)
client = self.metadata.resolve_client(address)
self.process_statistics(client, sdata)
return "<ok/>"
diff --git a/src/lib/Bcfg2/Server/FileMonitor.py b/src/lib/Bcfg2/Server/FileMonitor.py
deleted file mode 100644
index d6b313e6b..000000000
--- a/src/lib/Bcfg2/Server/FileMonitor.py
+++ /dev/null
@@ -1,315 +0,0 @@
-"""Bcfg2.Server.FileMonitor provides the support for monitorung files."""
-
-import logging
-import os
-import stat
-from time import sleep, time
-
-logger = logging.getLogger('Bcfg2.Server.FileMonitor')
-
-
-def ShouldIgnore(event):
- """Test if the event should be suppresed."""
- # FIXME should move event suppression out of the core
- if event.filename.split('/')[-1] == '.svn':
- return True
- if event.filename.endswith('~') or \
- event.filename.startswith('#') or event.filename.startswith('.#'):
- #logger.error("Suppressing event for file %s" % (event.filename))
- return True
- return False
-
-
-class Event(object):
- def __init__(self, request_id, filename, code):
- self.requestID = request_id
- self.filename = filename
- self.action = code
-
- def code2str(self):
- """return static code for event"""
- return self.action
-
-available = {}
-
-
-class FileMonitor(object):
- """File Monitor baseclass."""
- def __init__(self, debug=False):
- object.__init__(self)
- self.debug = debug
- self.handles = dict()
-
- def get_event(self):
- return None
-
- def pending(self):
- return False
-
- def fileno(self):
- return 0
-
- def handle_one_event(self, event):
- if ShouldIgnore(event):
- return
- if event.requestID not in self.handles:
- logger.info("Got event for unexpected id %s, file %s" %
- (event.requestID, event.filename))
- return
- if self.debug:
- logger.info("Dispatching event %s %s to obj %s" \
- % (event.code2str(), event.filename,
- self.handles[event.requestID]))
- try:
- self.handles[event.requestID].HandleEvent(event)
- except:
- logger.error("error in handling of gamin event for %s" % \
- (event.filename), exc_info=1)
-
- def handle_event_set(self, lock=None):
- count = 1
- event = self.get_event()
- start = time()
- if lock:
- lock.acquire()
- try:
- self.handle_one_event(event)
- while self.pending():
- self.handle_one_event(self.get_event())
- count += 1
- except:
- pass
- if lock:
- lock.release()
- end = time()
- logger.info("Handled %d events in %.03fs" % (count, (end - start)))
-
- def handle_events_in_interval(self, interval):
- end = time() + interval
- while time() < end:
- if self.pending():
- self.handle_event_set()
- end = time() + interval
- else:
- sleep(0.5)
-
-
-class FamFam(object):
- """The fam object is a set of callbacks for
- file alteration events (FAM support).
- """
-
- def __init__(self):
- object.__init__(self)
- self.fm = _fam.open()
- self.users = {}
- self.handles = {}
- self.debug = False
-
- def fileno(self):
- """Return fam file handle number."""
- return self.fm.fileno()
-
- def handle_event_set(self, _):
- self.Service()
-
- def handle_events_in_interval(self, interval):
- now = time()
- while (time() - now) < interval:
- if self.Service():
- now = time()
-
- def AddMonitor(self, path, obj):
- """Add a monitor to path, installing a callback to obj.HandleEvent."""
- mode = os.stat(path)[stat.ST_MODE]
- if stat.S_ISDIR(mode):
- handle = self.fm.monitorDirectory(path, None)
- else:
- handle = self.fm.monitorFile(path, None)
- self.handles[handle.requestID()] = handle
- if obj != None:
- self.users[handle.requestID()] = obj
- return handle.requestID()
-
- def Service(self, interval=0.50):
- """Handle all fam work."""
- count = 0
- collapsed = 0
- rawevents = []
- start = time()
- now = time()
- while (time() - now) < interval:
- if self.fm.pending():
- while self.fm.pending():
- count += 1
- rawevents.append(self.fm.nextEvent())
- now = time()
- unique = []
- bookkeeping = []
- for event in rawevents:
- if ShouldIgnore(event):
- continue
- if event.code2str() != 'changed':
- # process all non-change events
- unique.append(event)
- else:
- if (event.filename, event.requestID) not in bookkeeping:
- bookkeeping.append((event.filename, event.requestID))
- unique.append(event)
- else:
- collapsed += 1
- for event in unique:
- if event.requestID in self.users:
- try:
- self.users[event.requestID].HandleEvent(event)
- except:
- logger.error("handling event for file %s" % (event.filename), exc_info=1)
- end = time()
- logger.info("Processed %s fam events in %03.03f seconds. %s coalesced" %
- (count, (end - start), collapsed))
- return count
-
-
-class Fam(FileMonitor):
- """
- The fam object is a set of callbacks for
- file alteration events (FAM support).
- """
-
- def __init__(self, debug=False):
- FileMonitor.__init__(self, debug)
- self.fm = _fam.open()
-
- def fileno(self):
- return self.fm.fileno()
-
- def AddMonitor(self, path, obj):
- """Add a monitor to path, installing a callback to obj.HandleEvent."""
- mode = os.stat(path)[stat.ST_MODE]
- if stat.S_ISDIR(mode):
- handle = self.fm.monitorDirectory(path, None)
- else:
- handle = self.fm.monitorFile(path, None)
- if obj != None:
- self.handles[handle.requestID()] = obj
- return handle.requestID()
-
- def pending(self):
- return self.fm.pending()
-
- def get_event(self):
- return self.fm.nextEvent()
-
-
-class Pseudo(FileMonitor):
- """
- The fam object is a set of callbacks for
- file alteration events (static monitor support).
- """
-
- def __init__(self, debug=False):
- FileMonitor.__init__(self, debug=False)
- self.pending_events = []
-
- def pending(self):
- return len(self.pending_events) != 0
-
- def get_event(self):
- return self.pending_events.pop()
-
- def AddMonitor(self, path, obj):
- """add a monitor to path, installing a callback to obj.HandleEvent"""
- handleID = len(list(self.handles.keys()))
- mode = os.stat(path)[stat.ST_MODE]
- handle = Event(handleID, path, 'exists')
- if stat.S_ISDIR(mode):
- dirList = os.listdir(path)
- self.pending_events.append(handle)
- for includedFile in dirList:
- self.pending_events.append(Event(handleID,
- includedFile,
- 'exists'))
- self.pending_events.append(Event(handleID, path, 'endExist'))
- else:
- self.pending_events.append(Event(handleID, path, 'exists'))
- if obj != None:
- self.handles[handleID] = obj
- return handleID
-
-
-try:
- from gamin import WatchMonitor, GAMCreated, GAMExists, GAMEndExist, \
- GAMChanged, GAMDeleted, GAMMoved
-
- class GaminEvent(Event):
- """
- This class provides an event analogous to
- python-fam events based on gamin sources.
- """
- def __init__(self, request_id, filename, code):
- Event.__init__(self, request_id, filename, code)
- action_map = {GAMCreated: 'created', GAMExists: 'exists',
- GAMChanged: 'changed', GAMDeleted: 'deleted',
- GAMEndExist: 'endExist', GAMMoved: 'moved'}
- if code in action_map:
- self.action = action_map[code]
-
- class Gamin(FileMonitor):
- """
- The fam object is a set of callbacks for
- file alteration events (Gamin support)
- """
- def __init__(self, debug=False):
- FileMonitor.__init__(self, debug)
- self.mon = WatchMonitor()
- self.counter = 0
- self.events = []
-
- def fileno(self):
- return self.mon.get_fd()
-
- def queue(self, path, action, request_id):
- """queue up the event for later handling"""
- self.events.append(GaminEvent(request_id, path, action))
-
- def AddMonitor(self, path, obj):
- """Add a monitor to path, installing a callback to obj.HandleEvent."""
- handle = self.counter
- self.counter += 1
- mode = os.stat(path)[stat.ST_MODE]
-
- # Flush queued gamin events
- while self.mon.event_pending():
- self.mon.handle_one_event()
-
- if stat.S_ISDIR(mode):
- self.mon.watch_directory(path, self.queue, handle)
- else:
- self.mon.watch_file(path, self.queue, handle)
- self.handles[handle] = obj
- return handle
-
- def pending(self):
- return len(self.events) > 0 or self.mon.event_pending()
-
- def get_event(self):
- if self.mon.event_pending():
- self.mon.handle_one_event()
- return self.events.pop(0)
-
- available['gamin'] = Gamin
-except ImportError:
- # fall back to _fam
- pass
-
-try:
- import _fam
- available['fam'] = FamFam
-except ImportError:
- pass
-available['pseudo'] = Pseudo
-
-for fdrv in ['gamin', 'fam', 'pseudo']:
- if fdrv in available:
- available['default'] = available[fdrv]
- break
diff --git a/src/lib/Bcfg2/Server/FileMonitor/Fam.py b/src/lib/Bcfg2/Server/FileMonitor/Fam.py
new file mode 100644
index 000000000..1a00fffa0
--- /dev/null
+++ b/src/lib/Bcfg2/Server/FileMonitor/Fam.py
@@ -0,0 +1,82 @@
+""" Fam provides FAM support for file alteration events """
+
+import os
+import _fam
+import stat
+import logging
+from time import time
+from Bcfg2.Server.FileMonitor import FileMonitor
+
+logger = logging.getLogger(__name__)
+
+class Fam(FileMonitor):
+ __priority__ = 90
+
+ def __init__(self, ignore=None, debug=False):
+ FileMonitor.__init__(self, ignore=ignore, debug=debug)
+ self.fm = _fam.open()
+ self.users = {}
+
+ def fileno(self):
+ """Return fam file handle number."""
+ return self.fm.fileno()
+
+ def handle_event_set(self, _):
+ self.Service()
+
+ def handle_events_in_interval(self, interval):
+ now = time()
+ while (time() - now) < interval:
+ if self.Service():
+ now = time()
+
+ def AddMonitor(self, path, obj):
+ """Add a monitor to path, installing a callback to obj.HandleEvent."""
+ mode = os.stat(path)[stat.ST_MODE]
+ if stat.S_ISDIR(mode):
+ handle = self.fm.monitorDirectory(path, None)
+ else:
+ handle = self.fm.monitorFile(path, None)
+ self.handles[handle.requestID()] = handle
+ if obj != None:
+ self.users[handle.requestID()] = obj
+ return handle.requestID()
+
+ def Service(self, interval=0.50):
+ """Handle all fam work."""
+ count = 0
+ collapsed = 0
+ rawevents = []
+ start = time()
+ now = time()
+ while (time() - now) < interval:
+ if self.fm.pending():
+ while self.fm.pending():
+ count += 1
+ rawevents.append(self.fm.nextEvent())
+ now = time()
+ unique = []
+ bookkeeping = []
+ for event in rawevents:
+ if self.should_ignore(event):
+ continue
+ if event.code2str() != 'changed':
+ # process all non-change events
+ unique.append(event)
+ else:
+ if (event.filename, event.requestID) not in bookkeeping:
+ bookkeeping.append((event.filename, event.requestID))
+ unique.append(event)
+ else:
+ collapsed += 1
+ for event in unique:
+ if event.requestID in self.users:
+ try:
+ self.users[event.requestID].HandleEvent(event)
+ except:
+ logger.error("Handling event for file %s" % event.filename,
+ exc_info=1)
+ end = time()
+ logger.info("Processed %s fam events in %03.03f seconds. %s coalesced" %
+ (count, (end - start), collapsed))
+ return count
diff --git a/src/lib/Bcfg2/Server/FileMonitor/Gamin.py b/src/lib/Bcfg2/Server/FileMonitor/Gamin.py
new file mode 100644
index 000000000..60f80c9c3
--- /dev/null
+++ b/src/lib/Bcfg2/Server/FileMonitor/Gamin.py
@@ -0,0 +1,64 @@
+""" Gamin driver for file alteration events """
+
+import os
+import stat
+import logging
+from gamin import WatchMonitor, GAMCreated, GAMExists, GAMEndExist, \
+ GAMChanged, GAMDeleted
+from Bcfg2.Server.FileMonitor import Event, FileMonitor
+
+logger = logging.getLogger(__name__)
+
+class GaminEvent(Event):
+ """
+ This class provides an event analogous to
+ python-fam events based on gamin sources.
+ """
+ action_map = {GAMCreated: 'created', GAMExists: 'exists',
+ GAMChanged: 'changed', GAMDeleted: 'deleted',
+ GAMEndExist: 'endExist'}
+
+ def __init__(self, request_id, filename, code):
+ Event.__init__(self, request_id, filename, code)
+ if code in self.action_map:
+ self.action = self.action_map[code]
+
+class Gamin(FileMonitor):
+ __priority__ = 10
+
+ def __init__(self, ignore=None, debug=False):
+ FileMonitor.__init__(self, ignore=ignore, debug=debug)
+ self.mon = WatchMonitor()
+ self.counter = 0
+
+ def fileno(self):
+ return self.mon.get_fd()
+
+ def queue(self, path, action, request_id):
+ """queue up the event for later handling"""
+ self.events.append(GaminEvent(request_id, path, action))
+
+ def AddMonitor(self, path, obj):
+ """Add a monitor to path, installing a callback to obj."""
+ handle = self.counter
+ self.counter += 1
+ mode = os.stat(path)[stat.ST_MODE]
+
+ # Flush queued gamin events
+ while self.mon.event_pending():
+ self.mon.handle_one_event()
+
+ if stat.S_ISDIR(mode):
+ self.mon.watch_directory(path, self.queue, handle)
+ else:
+ self.mon.watch_file(path, self.queue, handle)
+ self.handles[handle] = obj
+ return handle
+
+ def pending(self):
+ return FileMonitor.pending(self) or self.mon.event_pending()
+
+ def get_event(self):
+ if self.mon.event_pending():
+ self.mon.handle_one_event()
+ return FileMonitor.get_event(self)
diff --git a/src/lib/Bcfg2/Server/FileMonitor/Inotify.py b/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
new file mode 100644
index 000000000..9743d868d
--- /dev/null
+++ b/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
@@ -0,0 +1,60 @@
+""" Inotify driver for file alteration events """
+
+import os
+import stat
+import logging
+import operator
+import pyinotify
+from Bcfg2.Server.FileMonitor import FileMonitor, Event
+
+logger = logging.getLogger(__name__)
+
+class InotifyEvent(Event):
+ action_map = {pyinotify.IN_CREATE: 'created',
+ pyinotify.IN_DELETE: 'deleted',
+ pyinotify.IN_MODIFY: 'changed'}
+
+ def __init__(self, event):
+ Event.__init__(self, event.wd, event.pathname, event.mask)
+ if event.mask in self.action_map:
+ self.action = self.action_map[event.mask]
+
+
+class Inotify(FileMonitor, pyinotify.ProcessEvent):
+ __priority__ = 1
+ mask = pyinotify.IN_CREATE | pyinotify.IN_DELETE | pyinotify.IN_MODIFY
+
+ def __init__(self, ignore=None, debug=False):
+ FileMonitor.__init__(self, ignore=ignore, debug=debug)
+ self.wm = pyinotify.WatchManager()
+ self.notifier = pyinotify.ThreadedNotifier(self.wm, self)
+ self.notifier.start()
+
+ def fileno(self):
+ return self.wm.get_fd()
+
+ def process_default(self, event):
+ self.events.append(InotifyEvent(event))
+
+ def AddMonitor(self, path, obj):
+ res = self.wm.add_watch(path, self.mask, quiet=False)
+ if not res:
+ # if we didn't get a return, but we also didn't get an
+ # exception, we're already watching this directory, so we
+ # need to find the watch descriptor for it
+ for wd, watch in self.wm.watches.items():
+ if watch.path == path:
+ wd = watch.wd
+ else:
+ wd = res[path]
+ self.handles[wd] = obj
+ self.events.append(Event(wd, path, "exists"))
+
+ mode = os.stat(path)[stat.ST_MODE]
+ if stat.S_ISDIR(mode):
+ for wname in os.listdir(path):
+ self.events.append(Event(wd, wname, "exists"))
+ return wd
+
+ def shutdown(self):
+ self.notifier.stop()
diff --git a/src/lib/Bcfg2/Server/FileMonitor/Pseudo.py b/src/lib/Bcfg2/Server/FileMonitor/Pseudo.py
new file mode 100644
index 000000000..4c2d90250
--- /dev/null
+++ b/src/lib/Bcfg2/Server/FileMonitor/Pseudo.py
@@ -0,0 +1,30 @@
+""" Pseudo provides static monitor support for file alteration events """
+
+import os
+import stat
+import logging
+from Bcfg2.Server.FileMonitor import FileMonitor, Event
+
+logger = logging.getLogger(__name__)
+
+class Pseudo(FileMonitor):
+ __priority__ = 99
+
+ def AddMonitor(self, path, obj):
+ """add a monitor to path, installing a callback to obj.HandleEvent"""
+ handleID = len(list(self.handles.keys()))
+ mode = os.stat(path)[stat.ST_MODE]
+ handle = Event(handleID, path, 'exists')
+ if stat.S_ISDIR(mode):
+ dirList = os.listdir(path)
+ self.pending_events.append(handle)
+ for includedFile in dirList:
+ self.pending_events.append(Event(handleID,
+ includedFile,
+ 'exists'))
+ self.pending_events.append(Event(handleID, path, 'endExist'))
+ else:
+ self.pending_events.append(Event(handleID, path, 'exists'))
+ if obj != None:
+ self.handles[handleID] = obj
+ return handleID
diff --git a/src/lib/Bcfg2/Server/FileMonitor/__init__.py b/src/lib/Bcfg2/Server/FileMonitor/__init__.py
new file mode 100644
index 000000000..40c3253b9
--- /dev/null
+++ b/src/lib/Bcfg2/Server/FileMonitor/__init__.py
@@ -0,0 +1,143 @@
+"""Bcfg2.Server.FileMonitor provides the support for monitoring files."""
+
+import os
+import fnmatch
+import logging
+import pkgutil
+from time import sleep, time
+
+logger = logging.getLogger(__name__)
+
+class Event(object):
+ def __init__(self, request_id, filename, code):
+ self.requestID = request_id
+ self.filename = filename
+ self.action = code
+
+ def code2str(self):
+ """return static code for event"""
+ return self.action
+
+ def __str__(self):
+ return "%s: %s %s" % (self.__class__.__name__,
+ self.filename, self.action)
+
+ def __repr__(self):
+ return "%s (request ID %s)" % (str(self), self.requestID)
+
+
+class FileMonitor(object):
+ """File Monitor baseclass."""
+ def __init__(self, ignore=None, debug=False):
+ object.__init__(self)
+ self.debug = debug
+ self.handles = dict()
+ self.events = []
+ if ignore is None:
+ ignore = []
+ self.ignore = ignore
+
+ def __str__(self):
+ return "%s: %s" % (__name__, self.__class__.__name__)
+
+ def __repr__(self):
+ if self.pending():
+ events = "has"
+ else:
+ events = "no"
+ return "%s (%s events, fd %s)" % (str(self), events, self.fileno)
+
+ def should_ignore(self, event):
+ for pattern in self.ignore:
+ if (fnmatch.fnmatch(event.filename, pattern) or
+ fnmatch.fnmatch(os.path.split(event.filename)[-1], pattern)):
+ if self.debug:
+ logger.info("Ignoring %s" % event)
+ return True
+ return False
+
+ def pending(self):
+ return bool(self.events)
+
+ def get_event(self):
+ return self.events.pop(0)
+
+ def fileno(self):
+ return 0
+
+ def handle_one_event(self, event):
+ if self.should_ignore(event):
+ return
+ if event.requestID not in self.handles:
+ logger.info("Got event for unexpected id %s, file %s" %
+ (event.requestID, event.filename))
+ return
+ if self.debug:
+ logger.info("Dispatching event %s %s to obj %s" %
+ (event.code2str(), event.filename,
+ self.handles[event.requestID]))
+ try:
+ self.handles[event.requestID].HandleEvent(event)
+ except:
+ logger.error("Error in handling of event for %s" %
+ event.filename, exc_info=1)
+
+ def handle_event_set(self, lock=None):
+ count = 1
+ event = self.get_event()
+ start = time()
+ if lock:
+ lock.acquire()
+ try:
+ self.handle_one_event(event)
+ while self.pending():
+ self.handle_one_event(self.get_event())
+ count += 1
+ except:
+ pass
+ if lock:
+ lock.release()
+ end = time()
+ logger.info("Handled %d events in %.03fs" % (count, (end - start)))
+
+ def handle_events_in_interval(self, interval):
+ end = time() + interval
+ while time() < end:
+ if self.pending():
+ self.handle_event_set()
+ end = time() + interval
+ else:
+ sleep(0.5)
+
+ def shutdown(self):
+ pass
+
+
+available = dict()
+
+# todo: loading the monitor drivers should be automatic
+from Bcfg2.Server.FileMonitor.Pseudo import Pseudo
+available['pseudo'] = Pseudo
+
+try:
+ from Bcfg2.Server.FileMonitor.Fam import Fam
+ available['fam'] = Fam
+except ImportError:
+ pass
+
+try:
+ from Bcfg2.Server.FileMonitor.Gamin import Gamin
+ available['gamin'] = Gamin
+except ImportError:
+ pass
+
+try:
+ from Bcfg2.Server.FileMonitor.Inotify import Inotify
+ available['inotify'] = Inotify
+except ImportError:
+ pass
+
+for fdrv in sorted(available.keys(), key=lambda k: available[k].__priority__):
+ if fdrv in available:
+ available['default'] = available[fdrv]
+ break
diff --git a/src/lib/Bcfg2/Server/Plugin.py b/src/lib/Bcfg2/Server/Plugin.py
index ca37431a2..90e39e52d 100644
--- a/src/lib/Bcfg2/Server/Plugin.py
+++ b/src/lib/Bcfg2/Server/Plugin.py
@@ -480,8 +480,8 @@ class DirectoryBacked(object):
return
if event.requestID not in self.handles:
- logger.warn("Got %s event with unknown handle (%s) for %s"
- % (action, event.requestID, abspath))
+ logger.warn("Got %s event with unknown handle (%s) for %s" %
+ (action, event.requestID, event.filename))
return
# Calculate the absolute and relative paths this event refers to
@@ -522,21 +522,13 @@ class DirectoryBacked(object):
# didn't know about. Go ahead and treat it like a
# "created" event, but log a warning, because this
# is unexpected.
- logger.warn("Got %s event for unexpected dir %s" % (action,
- abspath))
+ logger.warn("Got %s event for unexpected dir %s" %
+ (action, abspath))
self.add_directory_monitor(relpath)
else:
- logger.warn("Got unknown dir event %s %s %s" % (event.requestID,
- event.code2str(),
- abspath))
+ logger.warn("Got unknown dir event %s %s %s" %
+ (event.requestID, event.code2str(), abspath))
else:
- # Deal with events for non-directories
- if ((event.filename[-1] == '~') or
- (event.filename[:2] == '.#') or
- (event.filename[-4:] == '.swp') or
- (event.filename in ['SCCS', '.svn', '4913']) or
- (not self.patterns.match(event.filename))):
- return
if action in ['exists', 'created']:
self.add_entry(relpath, event)
elif action == 'changed':
@@ -547,13 +539,13 @@ class DirectoryBacked(object):
# know about. Go ahead and treat it like a
# "created" event, but log a warning, because this
# is unexpected.
- logger.warn("Got %s event for unexpected file %s" % (action,
- abspath))
+ logger.warn("Got %s event for unexpected file %s" %
+ (action,
+ abspath))
self.add_entry(relpath, event)
else:
- logger.warn("Got unknown file event %s %s %s" % (event.requestID,
- event.code2str(),
- abspath))
+ logger.warn("Got unknown file event %s %s %s" %
+ (event.requestID, event.code2str(), abspath))
class XMLFileBacked(FileBacked):
@@ -583,16 +575,18 @@ class XMLFileBacked(FileBacked):
return iter(self.entries)
def __str__(self):
- return "%s: %s" % (self.name, lxml.etree.tostring(self.xdata))
+ return "%s at %s" % (self.__class__.__name__, self.name)
class SingleXMLFileBacked(XMLFileBacked):
"""This object is a coherent cache for an independent XML file."""
- def __init__(self, filename, fam):
+ def __init__(self, filename, fam, should_monitor=True):
XMLFileBacked.__init__(self, filename)
self.extras = []
self.fam = fam
- self.fam.AddMonitor(filename, self)
+ self.should_monitor = should_monitor
+ if should_monitor:
+ self.fam.AddMonitor(filename, self)
def _follow_xincludes(self, fname=None, xdata=None):
''' follow xincludes, adding included files to fam and to
@@ -614,8 +608,9 @@ class SingleXMLFileBacked(XMLFileBacked):
self._follow_xincludes(fname=fpath)
def add_monitor(self, fpath, fname):
- self.fam.AddMonitor(fpath, self)
- self.extras.append(fname)
+ if self.should_monitor:
+ self.fam.AddMonitor(fpath, self)
+ self.extras.append(fname)
def Index(self):
"""Build local data structures."""
@@ -1207,7 +1202,7 @@ class GroupSpool(Plugin, Generator):
name = self.data + relative
if relative not in list(self.handles.values()):
if not posixpath.isdir(name):
- print("Failed to open directory %s" % (name))
+ self.logger.error("Failed to open directory %s" % name)
return
reqid = self.core.fam.AddMonitor(name, self)
self.handles[reqid] = relative
diff --git a/src/lib/Bcfg2/Server/Plugins/BB.py b/src/lib/Bcfg2/Server/Plugins/BB.py
index c015ec47c..bd518ad19 100644
--- a/src/lib/Bcfg2/Server/Plugins/BB.py
+++ b/src/lib/Bcfg2/Server/Plugins/BB.py
@@ -1,4 +1,5 @@
import lxml.etree
+import Bcfg2.Server
import Bcfg2.Server.Plugin
import glob
import os
@@ -16,7 +17,8 @@ class BBfile(Bcfg2.Server.Plugin.XMLFileBacked):
"""Build data into an xml object."""
try:
- self.data = lxml.etree.XML(self.data)
+ self.data = lxml.etree.XML(self.data,
+ parser=Bcfg2.Server.XMLParser)
except lxml.etree.XMLSyntaxError:
Bcfg2.Server.Plugin.logger.error("Failed to parse %s" % self.name)
return
diff --git a/src/lib/Bcfg2/Server/Plugins/Bundler.py b/src/lib/Bcfg2/Server/Plugins/Bundler.py
index ccb99481e..cbc452608 100644
--- a/src/lib/Bcfg2/Server/Plugins/Bundler.py
+++ b/src/lib/Bcfg2/Server/Plugins/Bundler.py
@@ -6,7 +6,7 @@ import os
import os.path
import re
import sys
-
+import Bcfg2.Server
import Bcfg2.Server.Plugin
try:
@@ -51,7 +51,8 @@ class Bundler(Bcfg2.Server.Plugin.Plugin,
raise Bcfg2.Server.Plugin.PluginInitError
def template_dispatch(self, name):
- bundle = lxml.etree.parse(name)
+ bundle = lxml.etree.parse(name,
+ parser=Bcfg2.Server.XMLParser)
nsmap = bundle.getroot().nsmap
if name.endswith('.xml'):
if have_genshi and \
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py
index 3edd1d8cb..e74b77e83 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py
@@ -6,8 +6,7 @@ from Bcfg2.Server.Plugins.Cfg import CfgGenerator
logger = logging.getLogger(__name__)
try:
- import Cheetah.Template
- import Cheetah.Parser
+ from Cheetah.Template import Template
have_cheetah = True
except ImportError:
have_cheetah = False
@@ -25,9 +24,8 @@ class CfgCheetahGenerator(CfgGenerator):
raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
def get_data(self, entry, metadata):
- template = Cheetah.Template.Template(self.data,
- compilerSettings=self.settings)
+ template = Template(self.data, compilerSettings=self.settings)
template.metadata = metadata
template.path = entry.get('realname', entry.get('name'))
- template.source_path = self.path
+ template.source_path = self.name
return template.respond()
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedCheetahGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedCheetahGenerator.py
new file mode 100644
index 000000000..a75329d2a
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedCheetahGenerator.py
@@ -0,0 +1,14 @@
+import logging
+from Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator import CfgCheetahGenerator
+from Bcfg2.Server.Plugins.Cfg.CfgEncryptedGenerator import CfgEncryptedGenerator
+
+logger = logging.getLogger(__name__)
+
+class CfgEncryptedCheetahGenerator(CfgCheetahGenerator, CfgEncryptedGenerator):
+ __extensions__ = ['cheetah.crypt', 'crypt.cheetah']
+
+ def handle_event(self, event):
+ CfgEncryptedGenerator.handle_event(self, event)
+
+ def get_data(self, entry, metadata):
+ return CfgCheetahGenerator.get_data(self, entry, metadata)
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py
new file mode 100644
index 000000000..0839e3536
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py
@@ -0,0 +1,63 @@
+import logging
+import Bcfg2.Server.Plugin
+from Bcfg2.Server.Plugins.Cfg import CfgGenerator, SETUP
+try:
+ from Bcfg2.Encryption import ssl_decrypt, EVPError
+ have_crypto = True
+except ImportError:
+ have_crypto = False
+
+logger = logging.getLogger(__name__)
+
+def passphrases():
+ section = "cfg:encryption"
+ if SETUP.cfp.has_section(section):
+ return dict([(o, SETUP.cfp.get(section, o))
+ for o in SETUP.cfp.options(section)])
+ else:
+ return dict()
+
+def decrypt(crypted):
+ if not have_crypto:
+ msg = "Cfg: M2Crypto is not available: %s" % entry.get("name")
+ logger.error(msg)
+ raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ for passwd in passphrases().values():
+ try:
+ return ssl_decrypt(crypted, passwd)
+ except EVPError:
+ pass
+ raise EVPError("Failed to decrypt")
+
+class CfgEncryptedGenerator(CfgGenerator):
+ __extensions__ = ["crypt"]
+
+ def __init__(self, fname, spec, encoding):
+ CfgGenerator.__init__(self, fname, spec, encoding)
+ if not have_crypto:
+ msg = "Cfg: M2Crypto is not available: %s" % entry.get("name")
+ logger.error(msg)
+ raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+
+ def handle_event(self, event):
+ if event.code2str() == 'deleted':
+ return
+ try:
+ crypted = open(self.name).read()
+ except UnicodeDecodeError:
+ crypted = open(self.name, mode='rb').read()
+ except:
+ logger.error("Failed to read %s" % self.name)
+ return
+ # todo: let the user specify a passphrase by name
+ try:
+ self.data = decrypt(crypted)
+ except EVPError:
+ msg = "Failed to decrypt %s" % self.name
+ logger.error(msg)
+ raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+
+ def get_data(self, entry, metadata):
+ if self.data is None:
+ raise Bcfg2.Server.Plugin.PluginExecutionError("Failed to decrypt %s" % self.name)
+ return CfgGenerator.get_data(self, entry, metadata)
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenshiGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenshiGenerator.py
new file mode 100644
index 000000000..649bc38a5
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenshiGenerator.py
@@ -0,0 +1,26 @@
+import logging
+from Bcfg2.Bcfg2Py3k import StringIO
+from Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator import CfgGenshiGenerator
+from Bcfg2.Server.Plugins.Cfg.CfgEncryptedGenerator import decrypt, \
+ CfgEncryptedGenerator
+
+logger = logging.getLogger(__name__)
+
+try:
+ from genshi.template import TemplateLoader
+except ImportError:
+ # CfgGenshiGenerator will raise errors if genshi doesn't exist
+ pass
+
+
+class EncryptedTemplateLoader(TemplateLoader):
+ def _instantiate(self, cls, fileobj, filepath, filename, encoding=None):
+ plaintext = StringIO(decrypt(fileobj.read()))
+ return TemplateLoader._instantiate(self, cls, plaintext, filepath,
+ filename, encoding=encoding)
+
+
+class CfgEncryptedGenshiGenerator(CfgGenshiGenerator):
+ __extensions__ = ['genshi.crypt', 'crypt.genshi']
+ __loader_cls__ = EncryptedTemplateLoader
+
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py
index 2c0a076d7..7c84648d7 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py
@@ -23,10 +23,12 @@ def removecomment(stream):
class CfgGenshiGenerator(CfgGenerator):
__extensions__ = ['genshi']
+ __loader_cls__ = TemplateLoader
def __init__(self, fname, spec, encoding):
CfgGenerator.__init__(self, fname, spec, encoding)
- self.loader = TemplateLoader()
+ self.loader = self.__loader_cls__()
+ self.template = None
if not have_genshi:
msg = "Cfg: Genshi is not available: %s" % entry.get("name")
logger.error(msg)
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py
index 54c17c6c5..85c13c1ac 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py
@@ -7,6 +7,10 @@ logger = logging.getLogger(__name__)
class CfgLegacyInfo(CfgInfo):
__basenames__ = ['info', ':info']
+ def __init__(self, path):
+ CfgInfo.__init__(self, path)
+ self.path = path
+
def bind_info_to_entry(self, entry, metadata):
self._set_info(entry, self.metadata)
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
index 6c7585993..af6e1c7b1 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
@@ -152,7 +152,19 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
global PROCESSORS
if PROCESSORS is None:
PROCESSORS = []
- for submodule in pkgutil.walk_packages(path=__path__):
+ if hasattr(pkgutil, 'walk_packages'):
+ submodules = pkgutil.walk_packages(path=__path__)
+ else:
+ #python 2.4
+ import glob
+ submodules = []
+ for path in __path__:
+ for submodule in glob.glob("%s/*.py" % path):
+ mod = '.'.join(submodule.split("/")[-1].split('.')[:-1])
+ if mod != '__init__':
+ submodules.append((None, mod, True))
+
+ for submodule in submodules:
module = getattr(__import__("%s.%s" %
(__name__,
submodule[1])).Server.Plugins.Cfg,
@@ -287,6 +299,10 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
logger.error("You need to specify base64 encoding for %s." %
entry.get('name'))
raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ except TypeError:
+ # data is already unicode; newer versions of Cheetah
+ # seem to return unicode
+ pass
if data:
entry.text = data
@@ -298,7 +314,7 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
generators = [ent for ent in list(self.entries.values())
if (isinstance(ent, CfgGenerator) and
ent.specific.matches(metadata))]
- if not matching:
+ if not generators:
msg = "No base file found for %s" % entry.get('name')
logger.error(msg)
raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
@@ -385,7 +401,7 @@ class Cfg(Bcfg2.Server.Plugin.GroupSpool,
SETUP = core.setup
if 'validate' not in SETUP:
- SETUP['validate'] = Bcfg2.Options.CFG_VALIDATION
+ SETUP.add_option('validate', Bcfg2.Options.CFG_VALIDATION)
SETUP.reparse()
def AcceptChoices(self, entry, metadata):
diff --git a/src/lib/Bcfg2/Server/Plugins/FileProbes.py b/src/lib/Bcfg2/Server/Plugins/FileProbes.py
index 5beec7be0..556965fca 100644
--- a/src/lib/Bcfg2/Server/Plugins/FileProbes.py
+++ b/src/lib/Bcfg2/Server/Plugins/FileProbes.py
@@ -10,6 +10,7 @@ import errno
import binascii
import lxml.etree
import Bcfg2.Options
+import Bcfg2.Server
import Bcfg2.Server.Plugin
probecode = """#!/usr/bin/env python
@@ -102,7 +103,9 @@ class FileProbes(Bcfg2.Server.Plugin.Plugin,
(data.get('name'), metadata.hostname))
else:
try:
- self.write_data(lxml.etree.XML(data.text), metadata)
+ self.write_data(lxml.etree.XML(data.text,
+ parser=Bcfg2.Server.XMLParser),
+ metadata)
except lxml.etree.XMLSyntaxError:
# if we didn't get XML back from the probe, assume
# it's an error message
diff --git a/src/lib/Bcfg2/Server/Plugins/Metadata.py b/src/lib/Bcfg2/Server/Plugins/Metadata.py
index 970126b80..50604f5cb 100644
--- a/src/lib/Bcfg2/Server/Plugins/Metadata.py
+++ b/src/lib/Bcfg2/Server/Plugins/Metadata.py
@@ -6,11 +6,10 @@ import copy
import fcntl
import lxml.etree
import os
-import os.path
import socket
import sys
import time
-
+import Bcfg2.Server
import Bcfg2.Server.FileMonitor
import Bcfg2.Server.Plugin
@@ -39,13 +38,18 @@ class MetadataRuntimeError(Exception):
class XMLMetadataConfig(Bcfg2.Server.Plugin.SingleXMLFileBacked):
"""Handles xml config files and all XInclude statements"""
def __init__(self, metadata, watch_clients, basefile):
- Bcfg2.Server.Plugin.SingleXMLFileBacked.__init__(self,
- os.path.join(metadata.data,
- basefile),
- metadata.core.fam)
+ # we tell SingleXMLFileBacked _not_ to add a monitor for this
+ # file, because the main Metadata plugin has already added
+ # one. then we immediately set should_monitor to the proper
+ # value, so that XIinclude'd files get properly watched
+ fpath = os.path.join(metadata.data, basefile)
+ Bcfg2.Server.Plugin.SingleXMLFileBacked.__init__(self, fpath,
+ metadata.core.fam,
+ should_monitor=False)
+ self.should_monitor = watch_clients
self.metadata = metadata
+ self.fam = metadata.core.fam
self.basefile = basefile
- self.should_monitor = watch_clients
self.data = None
self.basedata = None
self.basedir = metadata.data
@@ -65,16 +69,11 @@ class XMLMetadataConfig(Bcfg2.Server.Plugin.SingleXMLFileBacked):
raise MetadataRuntimeError
return self.basedata
- def add_monitor(self, fpath, fname):
- """Add a fam monitor for an included file"""
- if self.should_monitor:
- self.metadata.core.fam.AddMonitor(fpath, self.metadata)
- self.extras.append(fname)
-
def load_xml(self):
"""Load changes from XML"""
try:
- xdata = lxml.etree.parse(os.path.join(self.basedir, self.basefile))
+ xdata = lxml.etree.parse(os.path.join(self.basedir, self.basefile),
+ parser=Bcfg2.Server.XMLParser)
except lxml.etree.XMLSyntaxError:
self.logger.error('Failed to parse %s' % self.basefile)
return
@@ -145,7 +144,8 @@ class XMLMetadataConfig(Bcfg2.Server.Plugin.SingleXMLFileBacked):
for included in self.extras:
try:
xdata = lxml.etree.parse(os.path.join(self.basedir,
- included))
+ included),
+ parser=Bcfg2.Server.XMLParser)
cli = xdata.xpath(xpath)
if len(cli) > 0:
return {'filename': os.path.join(self.basedir,
@@ -282,7 +282,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin,
def get_groups(self):
'''return groups xml tree'''
- groups_tree = lxml.etree.parse(os.path.join(self.data, "groups.xml"))
+ groups_tree = lxml.etree.parse(os.path.join(self.data, "groups.xml"),
+ parser=Bcfg2.Server.XMLParser)
root = groups_tree.getroot()
return root
@@ -792,7 +793,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin,
def include_group(group):
return not only_client or group in clientmeta.groups
- groups_tree = lxml.etree.parse(os.path.join(self.data, "groups.xml"))
+ groups_tree = lxml.etree.parse(os.path.join(self.data, "groups.xml"),
+ parser=Bcfg2.Server.XMLParser)
try:
groups_tree.xinclude()
except lxml.etree.XIncludeError:
diff --git a/src/lib/Bcfg2/Server/Plugins/NagiosGen.py b/src/lib/Bcfg2/Server/Plugins/NagiosGen.py
index 4dbd57d16..d132b0ff4 100644
--- a/src/lib/Bcfg2/Server/Plugins/NagiosGen.py
+++ b/src/lib/Bcfg2/Server/Plugins/NagiosGen.py
@@ -7,7 +7,7 @@ import glob
import socket
import logging
import lxml.etree
-
+import Bcfg2.Server
import Bcfg2.Server.Plugin
LOGGER = logging.getLogger('Bcfg2.Plugins.NagiosGen')
@@ -84,7 +84,8 @@ class NagiosGen(Bcfg2.Server.Plugin.Plugin,
LOGGER.warn("Parsing deprecated NagiosGen/parents.xml. "
"Update to the new-style config with "
"nagiosgen-convert.py.")
- parents = lxml.etree.parse(pfile)
+ parents = lxml.etree.parse(pfile,
+ parser=Bcfg2.Server.XMLParser)
for el in parents.xpath("//Depend[@name='%s']" % metadata.hostname):
if 'parent' in xtra:
xtra['parent'] += "," + el.get("on")
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py b/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py
index 7796b9e34..2e035a785 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py
@@ -106,4 +106,7 @@ class PackagesSources(Bcfg2.Server.Plugin.SingleXMLFileBacked,
return "PackagesSources: %s" % repr(self.entries)
def __str__(self):
- return "PackagesSources: %s" % str(self.entries)
+ return "PackagesSources: %s sources" % len(self.entries)
+
+ def __len__(self):
+ return len(self.entries)
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Source.py b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
index edcdcd9f2..2bc4b4dc2 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
@@ -169,6 +169,9 @@ class Source(Bcfg2.Server.Plugin.Debuggable):
else:
return self.__class__.__name__
+ def __repr__(self):
+ return str(self)
+
def get_urls(self):
return []
urls = property(get_urls)
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
index 53344e200..effec1c0e 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
@@ -105,10 +105,24 @@ class YumCollection(Collection):
if has_pulp and self.has_pulp_sources:
_setup_pulp(self.setup)
+ self._helper = None
+
@property
def helper(self):
- return self.setup.cfp.get("packages:yum", "helper",
- default="/usr/sbin/bcfg2-yum-helper")
+ try:
+ return self.config.get("yum", "helper")
+ except:
+ pass
+
+ if not self._helper:
+ # first see if bcfg2-yum-helper is in PATH
+ try:
+ Popen(['bcfg2-yum-helper'],
+ stdin=PIPE, stdout=PIPE, stderr=PIPE).wait()
+ self._helper = 'bcfg2-yum-helper'
+ except OSError:
+ self._helper = "/usr/sbin/bcfg2-yum-helper"
+ return self._helper
@property
def use_yum(self):
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
index d789a6d39..72913a60a 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
@@ -45,6 +45,10 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
@property
def disableResolver(self):
+ if self.disableMetaData:
+ # disabling metadata without disabling the resolver Breaks
+ # Things
+ return True
try:
return not self.core.setup.cfp.getboolean("packages", "resolver")
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
diff --git a/src/lib/Bcfg2/Server/Plugins/Probes.py b/src/lib/Bcfg2/Server/Plugins/Probes.py
index af908eee8..22cacde55 100644
--- a/src/lib/Bcfg2/Server/Plugins/Probes.py
+++ b/src/lib/Bcfg2/Server/Plugins/Probes.py
@@ -2,6 +2,8 @@ import time
import lxml.etree
import operator
import re
+import os
+import Bcfg2.Server
try:
import json
@@ -93,7 +95,8 @@ class ProbeData(object):
def xdata(self):
if self._xdata is None:
try:
- self._xdata = lxml.etree.XML(self.data)
+ self._xdata = lxml.etree.XML(self.data,
+ parser=Bcfg2.Server.XMLParser)
except lxml.etree.XMLSyntaxError:
pass
return self._xdata
@@ -220,7 +223,8 @@ class Probes(Bcfg2.Server.Plugin.Plugin,
def load_data(self):
try:
- data = lxml.etree.parse(self.data + '/probed.xml').getroot()
+ data = lxml.etree.parse(os.path.join(self.data, 'probed.xml'),
+ parser=Bcfg2.Server.XMLParser).getroot()
except:
self.logger.error("Failed to read file probed.xml")
return
diff --git a/src/lib/Bcfg2/Server/Plugins/SGenshi.py b/src/lib/Bcfg2/Server/Plugins/SGenshi.py
index 0ba08125e..12c125c62 100644
--- a/src/lib/Bcfg2/Server/Plugins/SGenshi.py
+++ b/src/lib/Bcfg2/Server/Plugins/SGenshi.py
@@ -7,7 +7,7 @@ import logging
import copy
import sys
import os.path
-
+import Bcfg2.Server
import Bcfg2.Server.Plugin
import Bcfg2.Server.Plugins.TGenshi
@@ -28,7 +28,8 @@ class SGenshiTemplateFile(Bcfg2.Server.Plugins.TGenshi.TemplateFile,
try:
stream = self.template.generate(metadata=metadata).filter( \
Bcfg2.Server.Plugins.TGenshi.removecomment)
- data = lxml.etree.XML(stream.render('xml', strip_whitespace=False))
+ data = lxml.etree.XML(stream.render('xml', strip_whitespace=False),
+ parser=Bcfg2.Server.XMLParser)
bundlename = os.path.splitext(os.path.basename(self.name))[0]
bundle = lxml.etree.Element('Bundle', name=bundlename)
for item in self.Match(metadata, data):
diff --git a/src/lib/Bcfg2/Server/Plugins/SSLCA.py b/src/lib/Bcfg2/Server/Plugins/SSLCA.py
index 0072dc62d..1091fc2c8 100644
--- a/src/lib/Bcfg2/Server/Plugins/SSLCA.py
+++ b/src/lib/Bcfg2/Server/Plugins/SSLCA.py
@@ -42,14 +42,16 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool):
if event.filename.endswith('.xml'):
if action in ['exists', 'created', 'changed']:
if event.filename.endswith('key.xml'):
- key_spec = dict(list(lxml.etree.parse(epath).find('Key').items()))
+ key_spec = dict(list(lxml.etree.parse(epath,
+ parser=Bcfg2.Server.XMLParser).find('Key').items()))
self.key_specs[ident] = {
'bits': key_spec.get('bits', 2048),
'type': key_spec.get('type', 'rsa')
}
self.Entries['Path'][ident] = self.get_key
elif event.filename.endswith('cert.xml'):
- cert_spec = dict(list(lxml.etree.parse(epath).find('Cert').items()))
+ cert_spec = dict(list(lxml.etree.parse(epath,
+ parser=Bcfg2.Server.XMLParser).find('Cert').items()))
ca = cert_spec.get('ca', 'default')
self.cert_specs[ident] = {
'ca': ca,
diff --git a/src/lib/Bcfg2/Server/Reports/reports/models.py b/src/lib/Bcfg2/Server/Reports/reports/models.py
index 35f2a4393..9a71fb16a 100644
--- a/src/lib/Bcfg2/Server/Reports/reports/models.py
+++ b/src/lib/Bcfg2/Server/Reports/reports/models.py
@@ -154,7 +154,7 @@ class InteractiveManager(models.Manager):
cursor.execute(sql)
return [item[0] for item in cursor.fetchall()]
except:
- '''FIXME - really need some error hadling'''
+ '''FIXME - really need some error handling'''
pass
return []
diff --git a/src/lib/Bcfg2/Server/Reports/settings.py b/src/lib/Bcfg2/Server/Reports/settings.py
index 4d567f1a2..9eb558ac8 100644
--- a/src/lib/Bcfg2/Server/Reports/settings.py
+++ b/src/lib/Bcfg2/Server/Reports/settings.py
@@ -1,11 +1,16 @@
import django
+import os
import sys
# Compatibility import
from Bcfg2.Bcfg2Py3k import ConfigParser
# Django settings for bcfg2 reports project.
c = ConfigParser.ConfigParser()
-if len(c.read(['/etc/bcfg2.conf', '/etc/bcfg2-web.conf'])) == 0:
+if 'BCFG2_CONFIG_FILE' in os.environ:
+ cfiles=os.environ['BCFG2_CONFIG_FILE']
+else:
+ cfiles=['/etc/bcfg2.conf', '/etc/bcfg2-web.conf']
+if len(c.read(cfiles)) == 0:
raise ImportError("Please check that bcfg2.conf or bcfg2-web.conf exists "
"and is readable by your web server.")
@@ -43,6 +48,9 @@ DATABASES = {
}
}
+if db_engine == 'ibm_db_django':
+ DATABASES['default']['ENGINE'] = db_engine
+
if db_engine != 'sqlite3':
DATABASES['default']['USER'] = c.get('statistics', 'database_user')
DATABASES['default']['PASSWORD'] = c.get('statistics', 'database_password')
diff --git a/src/lib/Bcfg2/Server/__init__.py b/src/lib/Bcfg2/Server/__init__.py
index 96777b0bf..320371284 100644
--- a/src/lib/Bcfg2/Server/__init__.py
+++ b/src/lib/Bcfg2/Server/__init__.py
@@ -1,4 +1,8 @@
"""This is the set of modules for Bcfg2.Server."""
+import lxml.etree
+
__all__ = ["Admin", "Core", "FileMonitor", "Plugin", "Plugins",
- "Hostbase", "Reports", "Snapshots"]
+ "Hostbase", "Reports", "Snapshots", "XMLParser"]
+
+XMLParser = lxml.etree.XMLParser(remove_blank_text=True)
diff --git a/src/sbin/bcfg2-admin b/src/sbin/bcfg2-admin
index 007dd0af3..7cc19be8f 100755
--- a/src/sbin/bcfg2-admin
+++ b/src/sbin/bcfg2-admin
@@ -44,6 +44,7 @@ def main():
'plugins': Bcfg2.Options.SERVER_PLUGINS,
'event debug': Bcfg2.Options.DEBUG,
'filemonitor': Bcfg2.Options.SERVER_FILEMONITOR,
+ 'ignore': Bcfg2.Options.SERVER_FAM_IGNORE,
'password': Bcfg2.Options.SERVER_PASSWORD,
'encoding': Bcfg2.Options.ENCODING,
}
diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info
index 8598a58eb..fdcf9ac17 100755
--- a/src/sbin/bcfg2-info
+++ b/src/sbin/bcfg2-info
@@ -648,6 +648,7 @@ if __name__ == '__main__':
'password': Bcfg2.Options.SERVER_PASSWORD,
'mconnect': Bcfg2.Options.SERVER_MCONNECT,
'filemonitor': Bcfg2.Options.SERVER_FILEMONITOR,
+ 'ignore': Bcfg2.Options.SERVER_FAM_IGNORE,
'location': Bcfg2.Options.SERVER_LOCATION,
'static': Bcfg2.Options.SERVER_STATIC,
'key': Bcfg2.Options.SERVER_KEY,
diff --git a/src/sbin/bcfg2-lint b/src/sbin/bcfg2-lint
index 78b833f02..bc1e5b70e 100755
--- a/src/sbin/bcfg2-lint
+++ b/src/sbin/bcfg2-lint
@@ -82,6 +82,7 @@ if __name__ == '__main__':
'plugins': Bcfg2.Options.SERVER_PLUGINS,
'mconnect': Bcfg2.Options.SERVER_MCONNECT,
'filemonitor': Bcfg2.Options.SERVER_FILEMONITOR,
+ 'ignore': Bcfg2.Options.SERVER_FAM_IGNORE,
'location': Bcfg2.Options.SERVER_LOCATION,
'static': Bcfg2.Options.SERVER_STATIC,
'key': Bcfg2.Options.SERVER_KEY,
diff --git a/src/sbin/bcfg2-server b/src/sbin/bcfg2-server
index 757172464..1b8b0d158 100755
--- a/src/sbin/bcfg2-server
+++ b/src/sbin/bcfg2-server
@@ -26,6 +26,7 @@ if __name__ == '__main__':
'plugins' : Bcfg2.Options.SERVER_PLUGINS,
'password' : Bcfg2.Options.SERVER_PASSWORD,
'fm' : Bcfg2.Options.SERVER_FILEMONITOR,
+ 'ignore' : Bcfg2.Options.SERVER_FAM_IGNORE,
'key' : Bcfg2.Options.SERVER_KEY,
'cert' : Bcfg2.Options.SERVER_CERT,
'ca' : Bcfg2.Options.SERVER_CA,
diff --git a/src/sbin/bcfg2-test b/src/sbin/bcfg2-test
index e3cfd27cc..7ddbb3509 100755
--- a/src/sbin/bcfg2-test
+++ b/src/sbin/bcfg2-test
@@ -70,7 +70,8 @@ def main():
'password': Bcfg2.Options.SERVER_PASSWORD,
'verbose': Bcfg2.Options.VERBOSE,
'noseopts': Bcfg2.Options.TEST_NOSEOPTS,
- 'ignore': Bcfg2.Options.TEST_IGNORE,
+ 'ignore': Bcfg2.Options.SERVER_FAM_IGNORE,
+ 'test_ignore': Bcfg2.Options.TEST_IGNORE,
'validate': Bcfg2.Options.CFG_VALIDATION,
}
setup = Bcfg2.Options.OptionParser(optinfo)
@@ -92,7 +93,7 @@ def main():
)
ignore = dict()
- for entry in setup['ignore']:
+ for entry in setup['test_ignore']:
tag, name = entry.split(":")
try:
ignore[tag].append(name)