summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--debian/changelog6
-rw-r--r--doc/appendix/guides/authentication.txt2
-rw-r--r--doc/development/compat.txt12
-rw-r--r--doc/releases/1.3.6.txt11
-rw-r--r--doc/server/plugins/connectors/templatehelper.txt6
-rw-r--r--doc/server/plugins/generators/nagiosgen.txt1
-rw-r--r--schemas/clients.xsd2
-rw-r--r--src/lib/Bcfg2/Client/Tools/APT.py4
-rw-r--r--src/lib/Bcfg2/Client/Tools/FreeBSDInit.py1
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/base.py59
-rw-r--r--src/lib/Bcfg2/Compat.py6
-rw-r--r--src/lib/Bcfg2/Reporting/Collector.py2
-rw-r--r--src/lib/Bcfg2/Reporting/models.py2
-rw-r--r--src/lib/Bcfg2/Reporting/templates/clients/detail.html4
-rw-r--r--src/lib/Bcfg2/Server/CherrypyCore.py16
-rw-r--r--src/lib/Bcfg2/Server/Core.py11
-rw-r--r--src/lib/Bcfg2/Server/Lint/__init__.py1
-rw-r--r--src/lib/Bcfg2/Server/Plugin/__init__.py1
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Metadata.py9
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Source.py120
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Yum.py145
-rw-r--r--src/lib/Bcfg2/Server/Plugins/SSHbase.py14
-rwxr-xr-xsrc/sbin/bcfg2-info1
-rw-r--r--testsuite/ext/ssl_protocols.py17
-rw-r--r--testsuite/pylintrc.conf2
-rw-r--r--testsuite/requirements.txt2
26 files changed, 358 insertions, 99 deletions
diff --git a/debian/changelog b/debian/changelog
index c41b2ecc2..59589de44 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -4,6 +4,12 @@ bcfg2 (1.4.0pre1-0.0) unstable; urgency=low
-- Sol Jerome <sol.jerome@gmail.com> Mon, 16 Jun 2014 09:36:13 -0500
+bcfg2 (1.3.6-0.0) unstable; urgency=low
+
+ * New upstream release
+
+ -- Sol Jerome <sol.jerome@gmail.com> Thu, 11 Jun 2015 15:30:04 -0500
+
bcfg2 (1.3.5-0.0) unstable; urgency=low
* New upstream release
diff --git a/doc/appendix/guides/authentication.txt b/doc/appendix/guides/authentication.txt
index 93a34c9bc..eba01ee3c 100644
--- a/doc/appendix/guides/authentication.txt
+++ b/doc/appendix/guides/authentication.txt
@@ -145,7 +145,7 @@ Allowed values are:
+-------------------+------------------------------------------+
``cert+password`` is the default. This can be changed by setting the
-``authentication`` parameter in the ``[communcation]`` section of
+``authentication`` parameter in the ``[communication]`` section of
``bcfg2.conf``. For instance, to set ``bootstrap`` mode as the global
default, you would add the following to ``bcfg2.conf``::
diff --git a/doc/development/compat.txt b/doc/development/compat.txt
index 8700c46d3..132bf67c0 100644
--- a/doc/development/compat.txt
+++ b/doc/development/compat.txt
@@ -113,7 +113,7 @@ with Python 2.4 (and occasionally 2.5). Be sure to read the notes
below, since some of these implementations may be feature-incomplete.
+----------------+--------------------------------+--------------------------------------------+
-| Name | Python 2.4 | Python 2.4+ |
+| Name | Python 2.4 | Python 2.5+ |
+================+================================+============================================+
| formatdate | :func:`email.Utils.formatdate` | :func:`email.utils.formatdate` |
+----------------+--------------------------------+--------------------------------------------+
@@ -129,6 +129,8 @@ below, since some of these implementations may be feature-incomplete.
+----------------+--------------------------------+--------------------------------------------+
| MutableMapping | :class:`UserDict.DictMixin` | :class:`collections.MutableMapping` (2.6+) |
+----------------+--------------------------------+--------------------------------------------+
+| literal_eval | :func:`eval` | :func:`ast.literal_eval`(2.6+) |
++----------------+--------------------------------+--------------------------------------------+
walk_packages
~~~~~~~~~~~~~
@@ -171,6 +173,14 @@ mind.
:class:`collections.MutableMapping` is available in Python 2.6+, and
will be used if available.
+literal_eval
+~~~~~~~~~~~~
+
+:func:`ast.literal_eval` is a safe version of :func:`eval` that will only
+allow delaration of literal strings, ints, list, dicts, etc. This was
+introduced in Python 2.6, and as such Python 2.4 uses the plain-old
+:func:`eval`.
+
Other Symbols
-------------
diff --git a/doc/releases/1.3.6.txt b/doc/releases/1.3.6.txt
index 757fbf6f5..9ab024674 100644
--- a/doc/releases/1.3.6.txt
+++ b/doc/releases/1.3.6.txt
@@ -30,5 +30,14 @@ This is primarily a bugfix release.
https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-OPTIONS
+* SYSV: change instances of simplename to simplefile
+
+ Previous configurations can be updated using the migration tool.
+
+* Authentication: Reject passwd auth, if authentication is set to "cert"
+* Server/Core: drop privileges even if not running as daemon
+* Packages/Yum.py: Fix dependency resolution logic
+* Handle filesystem secontexts properly for contextless filesystems
+
Special thanks to the following contributors for this release: Michael
-Fenn, Matt Kemp, Alexander Sulfrian, Jonathan Billings.
+Fenn, Matt Kemp, Alexander Sulfrian, Jonathan Billings, Ross Smith.
diff --git a/doc/server/plugins/connectors/templatehelper.txt b/doc/server/plugins/connectors/templatehelper.txt
index d113dcab7..e24ba10cb 100644
--- a/doc/server/plugins/connectors/templatehelper.txt
+++ b/doc/server/plugins/connectors/templatehelper.txt
@@ -54,9 +54,9 @@ See ``examples/TemplateHelper`` for examples of helper modules.
Usage
=====
-Specific helpers can be referred to in
-templates as ``metadata.TemplateHelper[<modulename>]``. That accesses
-a HelperModule object will have, as attributes, all symbols listed in
+Specific helpers can be referred to in templates as
+``metadata.TemplateHelper[<modulename>]``. That returns a HelperModule
+object which will have, as attributes, all symbols listed in
``__export__``. For example, consider this helper module::
__export__ = ["hello"]
diff --git a/doc/server/plugins/generators/nagiosgen.txt b/doc/server/plugins/generators/nagiosgen.txt
index 1ccdd66c1..746adf44c 100644
--- a/doc/server/plugins/generators/nagiosgen.txt
+++ b/doc/server/plugins/generators/nagiosgen.txt
@@ -29,7 +29,6 @@ Create default host, and group specs in:
check_period 24x7
contact_groups admins
event_handler_enabled 1
- failure_prediction_enabled 1
flap_detection_enabled 1
initial_state o
max_check_attempts 10
diff --git a/schemas/clients.xsd b/schemas/clients.xsd
index 20a77b0dd..b6d04b3bf 100644
--- a/schemas/clients.xsd
+++ b/schemas/clients.xsd
@@ -60,7 +60,7 @@
<xsd:attribute type='xsd:string' name='profile' use='required'>
<xsd:annotation>
<xsd:documentation>
- Profile group naem to associate this client with.
+ Profile group name to associate this client with.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
diff --git a/src/lib/Bcfg2/Client/Tools/APT.py b/src/lib/Bcfg2/Client/Tools/APT.py
index 5a86e8cd4..abc76ef1c 100644
--- a/src/lib/Bcfg2/Client/Tools/APT.py
+++ b/src/lib/Bcfg2/Client/Tools/APT.py
@@ -68,8 +68,8 @@ class APT(Bcfg2.Client.Tools.Tool):
Bcfg2.Options.setup.apt_etc_path))]
self.nonexistent = [entry.get('name') for struct in config
for entry in struct
- if entry.tag == 'Path' and
- entry.get('type') == 'nonexistent']
+ if (entry.tag == 'Path' and
+ entry.get('type') == 'nonexistent')]
os.environ["DEBIAN_FRONTEND"] = 'noninteractive'
self.actions = {}
if Bcfg2.Options.setup.kevlar and not Bcfg2.Options.setup.dry_run:
diff --git a/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py b/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py
index 24bc4cf36..7c25e6804 100644
--- a/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py
+++ b/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py
@@ -42,7 +42,6 @@ class FreeBSDInit(Bcfg2.Client.Tools.SvcTool):
self.logger.debug('Stopping service %s' % service.get('name'))
return self.cmd.run(self.get_svc_command(service, 'onestop'))
-
def VerifyService(self, entry, _):
"""Verify Service status for entry."""
entry.set('target_status', entry.get('status')) # for reporting
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/base.py b/src/lib/Bcfg2/Client/Tools/POSIX/base.py
index 8895eaae1..488920989 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/base.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/base.py
@@ -6,9 +6,11 @@ import pwd
import grp
import stat
import copy
+import errno
import shutil
import Bcfg2.Client.Tools
import Bcfg2.Client.XML
+import Bcfg2.Options
from Bcfg2.Compat import oct_mode
try:
@@ -37,6 +39,22 @@ device_map = dict(block=stat.S_IFBLK, # pylint: disable=C0103
class POSIXTool(Bcfg2.Client.Tools.Tool):
""" Base class for tools that handle POSIX (Path) entries """
+
+ options = [
+ Bcfg2.Options.Option(
+ cf=('POSIX', 'secontext_ignore'),
+ default=['anon_inodefs_t', 'bdev_t', 'binfmt_misc_fs_t',
+ 'capifs_t', 'configfs_t', 'cpusetfs_t', 'ecryptfs_t',
+ 'eventpollfs_t', 'futexfs_t', 'hugetlbfs_t', 'ibmasmfs_t',
+ 'inotifyfs_t', 'mvfs_t', 'nfsd_fs_t', 'oprofilefs_t',
+ 'ramfs_t', 'romfs_t', 'rpc_pipefs_t', 'spufs_t',
+ 'squash_t', 'vmblock_t', 'vxfs_t', 'xenfs_t', 'autofs_t',
+ 'cifs_t', 'dosfs_t', 'fusefs_t', 'iso9660_t',
+ 'removable_t', 'nfs_t'],
+ help='secontext types to ignore labeling errors',
+ type=Bcfg2.Options.Types.colon_list)
+ ]
+
def fully_specified(self, entry): # pylint: disable=W0613
""" return True if the entry is fully specified """
# checking is done by __req__
@@ -272,7 +290,7 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
rv &= self._apply_acl(defacl, path, posix1e.ACL_TYPE_DEFAULT)
return rv
- def _set_secontext(self, entry, path=None):
+ def _set_secontext(self, entry, path=None): # pylint: disable=R0911
""" set the SELinux context of the file on disk according to the
config"""
if not HAS_SELINUX:
@@ -284,25 +302,28 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
if not context:
# no context listed
return True
-
- if context == '__default__':
- try:
+ secontext = selinux.lgetfilecon(path)[1].split(":")[2]
+ if secontext in Bcfg2.Options.setup.posix_secontext_ignore:
+ return True
+ try:
+ if context == '__default__':
selinux.restorecon(path)
- rv = True
- except OSError:
- err = sys.exc_info()[1]
- self.logger.error("POSIX: Failed to restore SELinux context "
- "for %s: %s" % (path, err))
- rv = False
- else:
- try:
- rv = selinux.lsetfilecon(path, context) == 0
- except OSError:
- err = sys.exc_info()[1]
- self.logger.error("POSIX: Failed to restore SELinux context "
- "for %s: %s" % (path, err))
- rv = False
- return rv
+ return True
+ else:
+ return selinux.lsetfilecon(path, context) == 0
+ except OSError:
+ err = sys.exc_info()[1]
+ if err.errno == errno.EOPNOTSUPP:
+ # Operation not supported
+ if context != '__default__':
+ self.logger.debug("POSIX: Failed to set SELinux context "
+ "for %s: %s" % (path, err))
+ return False
+ return True
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to set or restore SELinux "
+ "context for %s: %s" % (path, err))
+ return False
def _norm_gid(self, gid):
""" This takes a group name or gid and returns the
diff --git a/src/lib/Bcfg2/Compat.py b/src/lib/Bcfg2/Compat.py
index b8a75a0c5..1c2420ccf 100644
--- a/src/lib/Bcfg2/Compat.py
+++ b/src/lib/Bcfg2/Compat.py
@@ -286,3 +286,9 @@ except NameError:
def cmp(a, b):
""" Py3k implementation of cmp() """
return (a > b) - (a < b)
+
+# ast was introduced in python 2.6
+try:
+ from ast import literal_eval
+except ImportError:
+ literal_eval = eval
diff --git a/src/lib/Bcfg2/Reporting/Collector.py b/src/lib/Bcfg2/Reporting/Collector.py
index 153809a35..f05a25732 100644
--- a/src/lib/Bcfg2/Reporting/Collector.py
+++ b/src/lib/Bcfg2/Reporting/Collector.py
@@ -116,7 +116,7 @@ class ReportingCollector(object):
self.storage.__class__.__name__)
self.storage.validate()
except:
- self.logger.error("Storage backed %s failed to validate: %s" %
+ self.logger.error("Storage backend %s failed to validate: %s" %
(self.storage.__class__.__name__,
sys.exc_info()[1]))
diff --git a/src/lib/Bcfg2/Reporting/models.py b/src/lib/Bcfg2/Reporting/models.py
index 2380621e6..8e2c644fb 100644
--- a/src/lib/Bcfg2/Reporting/models.py
+++ b/src/lib/Bcfg2/Reporting/models.py
@@ -632,7 +632,7 @@ class POSIXGroupEntry(SuccessEntry):
class PackageEntry(SuccessEntry):
""" The new model for package information """
- # if this is an extra entry trget_version will be empty
+ # if this is an extra entry target_version will be empty
target_version = models.CharField(max_length=1024, default='')
current_version = models.CharField(max_length=1024)
verification_details = models.TextField(default="")
diff --git a/src/lib/Bcfg2/Reporting/templates/clients/detail.html b/src/lib/Bcfg2/Reporting/templates/clients/detail.html
index e890589a7..6732bb8c9 100644
--- a/src/lib/Bcfg2/Reporting/templates/clients/detail.html
+++ b/src/lib/Bcfg2/Reporting/templates/clients/detail.html
@@ -90,7 +90,7 @@ span.history_links a {
<div class='entry_list'>
<div class='entry_list_head' onclick='javascript:toggleMe("bundles_table");'>
<h3>Bundle membership</h3>
- <div class='entry_expand_tab' id='plusminus_bundless_table'>[+]</div>
+ <div class='entry_expand_tab' id='plusminus_bundles_table'>[+]</div>
</div>
<table id='bundles_table' class='entry_list' style='display: none'>
{% endif %}
@@ -127,7 +127,7 @@ span.history_links a {
<div class='entry_list'>
<div class='entry_list_head failed-lineitem' onclick='javascript:toggleMe("failures_table");'>
<h3>Failed Entries &#8212; {{ interaction.failures.all|length }}</h3>
- <div class='entry_expand_tab' id='plusminus_failuress_table'>[+]</div>
+ <div class='entry_expand_tab' id='plusminus_failures_table'>[+]</div>
</div>
<table id='failures_table' class='entry_list' style='display: none'>
{% for failure in interaction.failures.all %}
diff --git a/src/lib/Bcfg2/Server/CherrypyCore.py b/src/lib/Bcfg2/Server/CherrypyCore.py
index 3cb0e291b..05c6c5a94 100644
--- a/src/lib/Bcfg2/Server/CherrypyCore.py
+++ b/src/lib/Bcfg2/Server/CherrypyCore.py
@@ -110,17 +110,21 @@ class CherrypyCore(NetworkCore):
return cherrypy.serving.response.body
def _daemonize(self):
- """ Drop privileges with
- :class:`cherrypy.process.plugins.DropPrivileges`, daemonize
- with :class:`cherrypy.process.plugins.Daemonizer`, and write a
+ """ Drop privileges, daemonize
+ with :class:`cherrypy.process.plugins.Daemonizer` and write a
PID file with :class:`cherrypy.process.plugins.PIDFile`. """
+ self._drop_privileges()
+ Daemonizer(cherrypy.engine).subscribe()
+ PIDFile(cherrypy.engine, Bcfg2.Options.setup.daemon).subscribe()
+ return True
+
+ def _drop_privileges(self):
+ """ Drop privileges with
+ :class:`cherrypy.process.plugins.DropPrivileges` """
DropPrivileges(cherrypy.engine,
uid=Bcfg2.Options.setup.daemon_uid,
gid=Bcfg2.Options.setup.daemon_gid,
umask=int(Bcfg2.Options.setup.umask, 8)).subscribe()
- Daemonizer(cherrypy.engine).subscribe()
- PIDFile(cherrypy.engine, Bcfg2.Options.setup.daemon).subscribe()
- return True
def _run(self):
""" Start the server listening. """
diff --git a/src/lib/Bcfg2/Server/Core.py b/src/lib/Bcfg2/Server/Core.py
index 03ab40343..dc9c91556 100644
--- a/src/lib/Bcfg2/Server/Core.py
+++ b/src/lib/Bcfg2/Server/Core.py
@@ -11,6 +11,7 @@ import threading
import time
import inspect
import lxml.etree
+import daemon
import Bcfg2.Server
import Bcfg2.Logger
import Bcfg2.Options
@@ -1486,3 +1487,13 @@ class NetworkCore(Core):
""" Daemonize the server and write the pidfile. This must be
overridden by a core implementation. """
raise NotImplementedError
+
+ def _drop_privileges(self):
+ """ This is called if not daemonized and running as root to
+ drop the privileges to the configured daemon_uid and daemon_gid.
+ """
+ daemon.daemon.change_process_owner(
+ Bcfg2.Options.setup.daemon_uid,
+ Bcfg2.Options.setup.daemon_gid)
+ self.logger.debug("Dropped privileges to %s:%s." %
+ (os.getuid(), os.getgid()))
diff --git a/src/lib/Bcfg2/Server/Lint/__init__.py b/src/lib/Bcfg2/Server/Lint/__init__.py
index 61f704206..873e5f149 100644
--- a/src/lib/Bcfg2/Server/Lint/__init__.py
+++ b/src/lib/Bcfg2/Server/Lint/__init__.py
@@ -14,6 +14,7 @@ import time
import lxml.etree
+
import Bcfg2.Options
import Bcfg2.Server.Core
import Bcfg2.Server.Plugins
diff --git a/src/lib/Bcfg2/Server/Plugin/__init__.py b/src/lib/Bcfg2/Server/Plugin/__init__.py
index e28e458b3..69fc90b2f 100644
--- a/src/lib/Bcfg2/Server/Plugin/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugin/__init__.py
@@ -11,7 +11,6 @@ documentation it's not necessary to use the submodules. E.g., you can
from Bcfg2.Server.Plugin.base import Plugin
"""
-
import Bcfg2.Options
# pylint: disable=W0401
diff --git a/src/lib/Bcfg2/Server/Plugins/Metadata.py b/src/lib/Bcfg2/Server/Plugins/Metadata.py
index b850c1870..657e4df31 100644
--- a/src/lib/Bcfg2/Server/Plugins/Metadata.py
+++ b/src/lib/Bcfg2/Server/Plugins/Metadata.py
@@ -1394,8 +1394,6 @@ class Metadata(Bcfg2.Server.Plugin.Metadata,
# look at cert.cN
client = certinfo['commonName']
self.debug_log("Got cN %s; using as client name" % client)
- auth_type = self.auth.get(client,
- Bcfg2.Options.setup.authentication)
elif user == 'root':
id_method = 'address'
try:
@@ -1417,6 +1415,13 @@ class Metadata(Bcfg2.Server.Plugin.Metadata,
# we have the client name
self.debug_log("Authenticating client %s" % client)
+ # validate id_method
+ auth_type = self.auth.get(client, Bcfg2.Options.setup.authentication)
+ if auth_type == 'cert' and id_method != 'cert':
+ self.logger.error("Client %s does not provide a cert, but only "
+ "cert auth is allowed" % client)
+ return False
+
# next we validate the address
if (id_method != 'uuid' and
not self.validate_client_address(client, address)):
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Source.py b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
index c9f6ea14a..86f7698f7 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
@@ -135,22 +135,22 @@ class Source(Debuggable): # pylint: disable=R0902
#: A list of the text of all 'Component' attributes of this
#: source from XML
- self.components = [item.text for item in xsource.findall('Component')]
+ self.components = []
#: A list of the arches supported by this source
- self.arches = [item.text for item in xsource.findall('Arch')]
+ self.arches = []
#: A list of the the names of packages that are blacklisted
#: from this source
- self.blacklist = [item.text for item in xsource.findall('Blacklist')]
+ self.blacklist = []
#: A list of the the names of packages that are whitelisted in
#: this source
- self.whitelist = [item.text for item in xsource.findall('Whitelist')]
+ self.whitelist = []
#: Whether or not to include deb-src lines in the generated APT
#: configuration
- self.debsrc = xsource.get('debsrc', 'false') == 'true'
+ self.debsrc = False
#: A dict of repository options that will be included in the
#: configuration generated on the server side (if such is
@@ -162,51 +162,38 @@ class Source(Debuggable): # pylint: disable=R0902
#: configuration generated for the client (if that is
#: supported by the backend)
self.client_options = dict()
- opts = xsource.findall("Options")
- for el in opts:
- repoopts = dict([(k, v)
- for k, v in el.attrib.items()
- if k != "clientonly" and k != "serveronly"])
- if el.get("clientonly", "false").lower() == "false":
- self.server_options.update(repoopts)
- if el.get("serveronly", "false").lower() == "false":
- self.client_options.update(repoopts)
#: A list of URLs to GPG keys that apply to this source
- self.gpgkeys = [el.text for el in xsource.findall("GPGKey")]
+ self.gpgkeys = []
#: Whether or not to include essential packages from this source
- self.essential = xsource.get('essential', 'true').lower() == 'true'
+ self.essential = True
#: Whether or not to include recommended packages from this source
- self.recommended = xsource.get('recommended',
- 'false').lower() == 'true'
+ self.recommended = False
#: The "rawurl" attribute from :attr:`xsource`, if applicable.
#: A trailing slash is automatically appended to this if there
#: wasn't one already present.
- self.rawurl = xsource.get('rawurl', '')
- if self.rawurl and not self.rawurl.endswith("/"):
- self.rawurl += "/"
+ self.rawurl = None
#: The "url" attribute from :attr:`xsource`, if applicable. A
#: trailing slash is automatically appended to this if there
#: wasn't one already present.
- self.url = xsource.get('url', '')
- if self.url and not self.url.endswith("/"):
- self.url += "/"
+ self.url = None
#: The "version" attribute from :attr:`xsource`
- self.version = xsource.get('version', '')
+ self.version = None
#: The "name" attribute from :attr:`xsource`
- self.name = xsource.get('name', None)
+ self.name = None
#: A list of predicates that are used to determine if this
#: source applies to a given
#: :class:`Bcfg2.Server.Plugins.Metadata.ClientMetadata`
#: object.
self.conditions = []
+
#: Formerly, :ref:`server-plugins-generators-packages` only
#: supported applying package sources to groups; that is, they
#: could not be assigned by more complicated logic like
@@ -214,22 +201,6 @@ class Source(Debuggable): # pylint: disable=R0902
#: attribute attempts to provide for some limited backwards
#: compat with older code that relies on this.
self.groups = []
- for el in xsource.iterancestors():
- if el.tag == "Group":
- if el.get("negate", "false").lower() == "true":
- self.conditions.append(lambda m, el=el:
- el.get("name") not in m.groups)
- else:
- self.groups.append(el.get("name"))
- self.conditions.append(lambda m, el=el:
- el.get("name") in m.groups)
- elif el.tag == "Client":
- if el.get("negate", "false").lower() == "true":
- self.conditions.append(lambda m, el=el:
- el.get("name") != m.hostname)
- else:
- self.conditions.append(lambda m, el=el:
- el.get("name") == m.hostname)
#: A set of all package names in this source. This will not
#: necessarily be populated, particularly by backends that
@@ -253,6 +224,8 @@ class Source(Debuggable): # pylint: disable=R0902
#: symbols>``. This will not necessarily be populated.
self.recommends = dict()
+ self._init_attributes(xsource)
+
#: The file (or directory) used for this source's cache data
self.cachefile = os.path.join(self.basepath,
"cache-%s" % self.cachekey)
@@ -292,6 +265,69 @@ class Source(Debuggable): # pylint: disable=R0902
setting['name'] = self.get_repo_name(setting)
self.url_map.extend(usettings)
+ def _init_attributes(self, xsource):
+ """
+ This functions evaluates the Source tag and parses all
+ attributes. Override this function in a sub class to
+ parse specific attributes. Do not use ``__init__`` because
+ ``Source.__init__`` may call other functions that already
+ need this specific fields. This functions is called before
+ any other function.
+
+ :param xsource: The XML tag that describes this source
+ :type source: lxml.etree._Element
+ """
+
+ self.components = [item.text for item in xsource.findall('Component')]
+ self.arches = [item.text for item in xsource.findall('Arch')]
+ self.blacklist = [item.text for item in xsource.findall('Blacklist')]
+ self.whitelist = [item.text for item in xsource.findall('Whitelist')]
+ self.debsrc = xsource.get('debsrc', 'false') == 'true'
+
+ opts = xsource.findall("Options")
+ for el in opts:
+ repoopts = dict([(k, v)
+ for k, v in el.attrib.items()
+ if k != "clientonly" and k != "serveronly"])
+ if el.get("clientonly", "false").lower() == "false":
+ self.server_options.update(repoopts)
+ if el.get("serveronly", "false").lower() == "false":
+ self.client_options.update(repoopts)
+
+ self.gpgkeys = [el.text for el in xsource.findall("GPGKey")]
+
+ self.essential = xsource.get('essential', 'true').lower() == 'true'
+ self.recommended = xsource.get('recommended',
+ 'false').lower() == 'true'
+
+ self.rawurl = xsource.get('rawurl', '')
+ if self.rawurl and not self.rawurl.endswith("/"):
+ self.rawurl += "/"
+
+ self.url = xsource.get('url', '')
+ if self.url and not self.url.endswith("/"):
+ self.url += "/"
+
+ self.version = xsource.get('version', '')
+ self.name = xsource.get('name', None)
+
+ for el in xsource.iterancestors():
+ if el.tag == "Group":
+ if el.get("negate", "false").lower() == "true":
+ self.conditions.append(lambda m, el=el:
+ el.get("name") not in m.groups)
+ else:
+ self.groups.append(el.get("name"))
+ self.conditions.append(lambda m, el=el:
+ el.get("name") in m.groups)
+ elif el.tag == "Client":
+ if el.get("negate", "false").lower() == "true":
+ self.conditions.append(lambda m, el=el:
+ el.get("name") != m.hostname)
+ else:
+ self.conditions.append(lambda m, el=el:
+ el.get("name") == m.hostname)
+
@property
def cachekey(self):
""" A unique key for this source that will be used to generate
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
index dbe3f9ce5..14d6db8a0 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
@@ -1004,8 +1004,20 @@ class YumSource(Source):
ptype = 'yum'
def __init__(self, basepath, xsource):
- Source.__init__(self, basepath, xsource)
+ self.filemap = dict()
+ self.file_to_arch = dict()
+ self.needed_paths = set()
+ self.packages = dict()
+ self.yumgroups = dict()
self.pulp_id = None
+ self.repo = None
+
+ Source.__init__(self, basepath, xsource)
+ __init__.__doc__ = Source.__init__.__doc__
+
+ def _init_attributes(self, xsource):
+ Source._init_attributes(self, xsource)
+
if HAS_PULP and xsource.get("pulp_id"):
self.pulp_id = xsource.get("pulp_id")
@@ -1034,15 +1046,11 @@ class YumSource(Source):
self.repo['relative_path'])
self.arches = [self.repo['arch']]
- self.packages = dict()
self.deps = dict([('global', dict())])
self.provides = dict([('global', dict())])
self.filemap = dict([(x, dict())
for x in ['global'] + self.arches])
- self.needed_paths = set()
- self.file_to_arch = dict()
- self.yumgroups = dict()
- __init__.__doc__ = Source.__init__.__doc__
+ _init_attributes.__doc__ = Source._init_attributes.__doc__
@property
def use_yum(self):
@@ -1130,6 +1138,94 @@ class YumSource(Source):
self.file_to_arch[self.escape_url(fullurl)] = arch
return urls
+ # pylint: disable=R0911,R0912
+ # disabling the pylint errors above because we are interesting in
+ # replicating the flow of the RPM code.
+ def _compare_rpm_versions(self, str1, str2):
+ """ Compare RPM versions.
+
+ This is an attempt to reimplement RPM's rpmvercmp method in python.
+
+ :param str1: package 1 version string
+ :param str2: package 2 version string
+ :return: 1 - str1 is newer than str2
+ 0 - str1 and str2 are the same version
+ -1 - str2 is newer than str1"""
+ if str1 == str2:
+ return 0
+
+ front_strip_re = re.compile('^[^A-Za-z0-9~]+')
+ risdigit = re.compile('(^[0-9]+)')
+ risalpha = re.compile('(^[A-Za-z])')
+ lzeroes = re.compile('^0+')
+
+ while len(str1) > 0 or len(str2) > 0:
+ str1 = front_strip_re.sub('', str1)
+ str2 = front_strip_re.sub('', str2)
+
+ if len(str1) == 0 or len(str2) == 0:
+ break
+
+ # handle the tilde separator
+ if str1[0] == '~' and str2[0] == '~':
+ str1 = str1[1:]
+ str2 = str2[1:]
+ elif str1[0] == '~':
+ return -1
+ elif str2[0] == '~':
+ return 1
+
+ # grab continuous segments from each string
+ isnum = False
+ if risdigit.match(str1):
+ segment1 = risdigit.split(str1)[1]
+ str1 = risdigit.split(str1)[2]
+ if risdigit.match(str2):
+ segment2 = risdigit.split(str2)[1]
+ str2 = risdigit.split(str2)[2]
+ else:
+ segment2 = ''
+ isnum = True
+ else:
+ segment1 = risalpha.split(str1)[1]
+ str1 = risalpha.split(str1)[2]
+ if risalpha.match(str2):
+ segment2 = risalpha.split(str2)[1]
+ str2 = risalpha.split(str2)[2]
+ else:
+ segment2 = ''
+
+ # numeric segments are always newer than alpha segments
+ if len(segment2) == 0:
+ if isnum:
+ return 1
+ return -1
+
+ if isnum:
+ # discard leading zeroes
+ segment1 = lzeroes.sub('', segment1)
+ segment2 = lzeroes.sub('', segment2)
+ # higher number has more digits
+ if len(segment1) > len(segment2):
+ return 1
+ elif len(segment2) > len(segment1):
+ return -1
+ # do a simple string comparison
+ if segment1 > segment2:
+ return 1
+ elif segment2 > segment1:
+ return -1
+
+ # if one of the strings is empty, the version of the longer
+ # string is higher
+ if len(str1) > len(str2):
+ return 1
+ elif len(str2) > len(str1):
+ return -1
+ else:
+ return 0
+ # pylint: enable=R0911,R0912
+
@track_statistics()
def read_files(self):
""" When using the builtin yum parser, read and parse locally
@@ -1198,13 +1294,33 @@ class YumSource(Source):
if arch not in self.packages:
self.packages[arch] = set()
if arch not in self.deps:
- self.deps[arch] = dict()
+ self.deps[arch] = {}
if arch not in self.provides:
- self.provides[arch] = dict()
+ self.provides[arch] = {}
+ versionmap = {}
for pkg in data.getchildren():
if not pkg.tag.endswith('package'):
continue
pkgname = pkg.find(XP + 'name').text
+ vtag = pkg.find(XP + 'version')
+ epoch = vtag.get('epoch')
+ version = vtag.get('ver')
+ release = vtag.get('rel')
+ if pkgname in self.packages[arch]:
+ # skip if version older than a previous version
+ if (self._compare_rpm_versions(
+ epoch, versionmap[pkgname]['epoch']) < 0):
+ continue
+ elif (self._compare_rpm_versions(
+ version, versionmap[pkgname]['version']) < 0):
+ continue
+ elif (self._compare_rpm_versions(
+ release, versionmap[pkgname]['release']) < 0):
+ continue
+ versionmap[pkgname] = {}
+ versionmap[pkgname]['epoch'] = epoch
+ versionmap[pkgname]['version'] = version
+ versionmap[pkgname]['release'] = release
self.packages[arch].add(pkgname)
pdata = pkg.find(XP + 'format')
@@ -1256,10 +1372,15 @@ class YumSource(Source):
arch = [a for a in self.arches if a in metadata.groups]
if not arch:
return False
- return ((package in self.packages['global'] or
- package in self.packages[arch[0]]) and
- package not in self.blacklist and
- (len(self.whitelist) == 0 or package in self.whitelist))
+ try:
+ return ((package in self.packages['global'] or
+ package in self.packages[arch[0]]) and
+ package not in self.blacklist and
+ (len(self.whitelist) == 0 or package in self.whitelist))
+ except KeyError:
+ self.logger.debug("Packages: Unable to find %s for arch %s" %
+ (package, arch[0]))
+ return False
is_package.__doc__ = Source.is_package.__doc__
def get_vpkgs(self, metadata):
diff --git a/src/lib/Bcfg2/Server/Plugins/SSHbase.py b/src/lib/Bcfg2/Server/Plugins/SSHbase.py
index b06e28651..7736bd050 100644
--- a/src/lib/Bcfg2/Server/Plugins/SSHbase.py
+++ b/src/lib/Bcfg2/Server/Plugins/SSHbase.py
@@ -103,6 +103,7 @@ class KnownHostsEntrySet(Bcfg2.Server.Plugin.EntrySet):
class SSHbase(Bcfg2.Server.Plugin.Plugin,
+ Bcfg2.Server.Plugin.Connector,
Bcfg2.Server.Plugin.Generator,
Bcfg2.Server.Plugin.PullTarget):
"""
@@ -141,6 +142,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
def __init__(self, core):
Bcfg2.Server.Plugin.Plugin.__init__(self, core)
+ Bcfg2.Server.Plugin.Connector.__init__(self)
Bcfg2.Server.Plugin.Generator.__init__(self)
Bcfg2.Server.Plugin.PullTarget.__init__(self)
self.ipcache = {}
@@ -489,3 +491,15 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
self.logger.error("Failed to pull %s. This file does not "
"currently exist on the client" %
entry.get('name'))
+
+ def get_additional_data(self, metadata):
+ data = dict()
+ for key in self.keypatterns:
+ if key.endswith(".pub"):
+ try:
+ keyfile = "/etc/ssh/" + key
+ entry = self.entries[keyfile].best_matching(metadata)
+ data[key] = entry.data
+ except Bcfg2.Server.Plugin.PluginExecutionError:
+ pass
+ return data
diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info
index adfa96852..3d9b0d87b 100755
--- a/src/sbin/bcfg2-info
+++ b/src/sbin/bcfg2-info
@@ -4,5 +4,6 @@
import sys
from Bcfg2.Server.Info import CLI
+
if __name__ == '__main__':
sys.exit(CLI().run())
diff --git a/testsuite/ext/ssl_protocols.py b/testsuite/ext/ssl_protocols.py
new file mode 100644
index 000000000..66068d2a9
--- /dev/null
+++ b/testsuite/ext/ssl_protocols.py
@@ -0,0 +1,17 @@
+try:
+ from logilab.astng import MANAGER, scoped_nodes, node_classes
+ PYLINT=0
+except ImportError:
+ from astroid import MANAGER, scoped_nodes, node_classes
+ PYLINT=1
+
+def ssl_transform(module):
+ if module.name == 'ssl':
+ for proto in ('SSLv23', 'TLSv1'):
+ module.locals['PROTOCOL_%s' % proto] = [node_classes.Const()]
+
+def register(linter):
+ if PYLINT == 0:
+ MANAGER.register_transformer(ssl_transform)
+ else:
+ MANAGER.register_transform(scoped_nodes.Module, ssl_transform)
diff --git a/testsuite/pylintrc.conf b/testsuite/pylintrc.conf
index 1d3ba8c88..50ece77db 100644
--- a/testsuite/pylintrc.conf
+++ b/testsuite/pylintrc.conf
@@ -19,7 +19,7 @@ persistent=no
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
-load-plugins=ext.exception_messages
+load-plugins=ext.exception_messages,ext.ssl_protocols
[MESSAGES CONTROL]
diff --git a/testsuite/requirements.txt b/testsuite/requirements.txt
index dce47c338..d67c64db6 100644
--- a/testsuite/requirements.txt
+++ b/testsuite/requirements.txt
@@ -4,6 +4,6 @@ mock
sphinx
pylint<0.29
pep8
-python-daemon
+python-daemon<2.0.0
genshi
argparse