summaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Bcfg2/Client/Client.py1
-rw-r--r--src/lib/Bcfg2/Client/Frame.py3
-rw-r--r--src/lib/Bcfg2/Client/Proxy.py10
-rw-r--r--src/lib/Bcfg2/Client/Tools/APT.py9
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/base.py2
-rw-r--r--src/lib/Bcfg2/Client/Tools/Portage.py3
-rw-r--r--src/lib/Bcfg2/Client/Tools/RcUpdate.py12
-rw-r--r--src/lib/Bcfg2/Client/Tools/VCS.py3
-rw-r--r--src/lib/Bcfg2/Client/Tools/YUM.py84
-rw-r--r--src/lib/Bcfg2/Client/Tools/__init__.py15
-rw-r--r--src/lib/Bcfg2/Compat.py7
-rw-r--r--src/lib/Bcfg2/Logger.py18
-rw-r--r--src/lib/Bcfg2/Options.py45
-rw-r--r--src/lib/Bcfg2/Reporting/Storage/DjangoORM.py372
-rw-r--r--src/lib/Bcfg2/Reporting/Transport/LocalFilesystem.py6
-rw-r--r--src/lib/Bcfg2/Reporting/migrations/0005_add_selinux_entry_support.py485
-rw-r--r--src/lib/Bcfg2/Reporting/migrations/0006_add_user_group_entry_support.py340
-rw-r--r--src/lib/Bcfg2/Reporting/models.py225
-rw-r--r--src/lib/Bcfg2/Reporting/templates/base.html2
-rw-r--r--src/lib/Bcfg2/Reporting/templates/config_items/item.html31
-rw-r--r--src/lib/Bcfg2/Reporting/templates/widgets/filter_bar.html2
-rw-r--r--src/lib/Bcfg2/Reporting/views.py24
-rw-r--r--src/lib/Bcfg2/Server/Admin/Init.py27
-rw-r--r--src/lib/Bcfg2/Server/Core.py133
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/Inotify.py23
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/__init__.py14
-rw-r--r--src/lib/Bcfg2/Server/Lint/RequiredAttrs.py2
-rw-r--r--src/lib/Bcfg2/Server/Plugin/base.py5
-rw-r--r--src/lib/Bcfg2/Server/Plugin/helpers.py1
-rw-r--r--src/lib/Bcfg2/Server/Plugin/interfaces.py1
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/DBStats.py5
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Metadata.py119
-rw-r--r--src/lib/Bcfg2/Server/Plugins/POSIXCompat.py5
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Yum.py85
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/__init__.py23
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Reporting.py5
-rw-r--r--src/lib/Bcfg2/Server/SSLServer.py9
-rw-r--r--src/lib/Bcfg2/Utils.py28
-rw-r--r--src/lib/Bcfg2/settings.py52
-rw-r--r--src/lib/Bcfg2/version.py2
41 files changed, 1889 insertions, 353 deletions
diff --git a/src/lib/Bcfg2/Client/Client.py b/src/lib/Bcfg2/Client/Client.py
index e2521c0f8..66c1ce430 100644
--- a/src/lib/Bcfg2/Client/Client.py
+++ b/src/lib/Bcfg2/Client/Client.py
@@ -105,6 +105,7 @@ class Client(object):
self._probe_failure(name, "Return value %s" % rv)
self.logger.info("Probe %s has result:" % name)
self.logger.info(rv.stdout)
+ ret.text = rv.stdout
finally:
os.unlink(scriptname)
except SystemExit:
diff --git a/src/lib/Bcfg2/Client/Frame.py b/src/lib/Bcfg2/Client/Frame.py
index 82512130c..6ef686c10 100644
--- a/src/lib/Bcfg2/Client/Frame.py
+++ b/src/lib/Bcfg2/Client/Frame.py
@@ -481,7 +481,8 @@ class Frame(object):
len(list(self.states.values())))
self.logger.info('Unmanaged entries: %d' % len(self.extra))
if phase == 'final' and self.setup['extra']:
- for entry in self.extra:
+ for entry in sorted(self.extra, key=lambda e: e.tag + ":" +
+ e.get('name')):
etype = entry.get('type')
if etype:
self.logger.info("%s:%s:%s" % (entry.tag, etype,
diff --git a/src/lib/Bcfg2/Client/Proxy.py b/src/lib/Bcfg2/Client/Proxy.py
index 1276c3ce9..f8817bb27 100644
--- a/src/lib/Bcfg2/Client/Proxy.py
+++ b/src/lib/Bcfg2/Client/Proxy.py
@@ -1,6 +1,6 @@
-import logging
import re
import socket
+import logging
# The ssl module is provided by either Python 2.6 or a separate ssl
# package that works on older versions of Python (see
@@ -20,7 +20,7 @@ import sys
import time
# Compatibility imports
-from Bcfg2.Compat import httplib, xmlrpclib, urlparse
+from Bcfg2.Compat import httplib, xmlrpclib, urlparse, quote_plus
version = sys.version_info[:2]
has_py26 = version >= (2, 6)
@@ -51,13 +51,16 @@ class ProxyError(Exception):
msg = str(err)
Exception.__init__(self, msg)
+
class CertificateError(Exception):
def __init__(self, commonName):
self.commonName = commonName
+
def __str__(self):
return ("Got unallowed commonName %s from server"
% self.commonName)
+
_orig_Method = xmlrpclib._Method
class RetryMethod(xmlrpclib._Method):
@@ -350,7 +353,8 @@ def ComponentProxy(url, user=None, password=None, key=None, cert=None, ca=None,
if user and password:
method, path = urlparse(url)[:2]
- newurl = "%s://%s:%s@%s" % (method, user, password, path)
+ newurl = "%s://%s:%s@%s" % (method, quote_plus(user, ''),
+ quote_plus(password, ''), path)
else:
newurl = url
ssl_trans = XMLRPCTransport(key, cert, ca,
diff --git a/src/lib/Bcfg2/Client/Tools/APT.py b/src/lib/Bcfg2/Client/Tools/APT.py
index cc2f657d0..f449557aa 100644
--- a/src/lib/Bcfg2/Client/Tools/APT.py
+++ b/src/lib/Bcfg2/Client/Tools/APT.py
@@ -228,8 +228,13 @@ class APT(Bcfg2.Client.Tools.Tool):
continue
if pkg.get('version') in ['auto', 'any']:
if self._newapi:
- ipkgs.append("%s=%s" % (pkg.get('name'),
- self.pkg_cache[pkg.get('name')].candidate.version))
+ try:
+ ipkgs.append("%s=%s" % (pkg.get('name'),
+ self.pkg_cache[pkg.get('name')].candidate.version))
+ except AttributeError:
+ self.logger.error("Failed to find %s in apt package cache" %
+ pkg.get('name'))
+ continue
else:
ipkgs.append("%s=%s" % (pkg.get('name'),
self.pkg_cache[pkg.get('name')].candidateVersion))
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/base.py b/src/lib/Bcfg2/Client/Tools/POSIX/base.py
index b867fa3d8..f46875743 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/base.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/base.py
@@ -687,7 +687,7 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
if path is None:
path = entry.get("name")
cur = path
- while cur != '/':
+ while cur and cur != '/':
if not os.path.exists(cur):
created.append(cur)
cur = os.path.dirname(cur)
diff --git a/src/lib/Bcfg2/Client/Tools/Portage.py b/src/lib/Bcfg2/Client/Tools/Portage.py
index aa1254b46..32afa8cbf 100644
--- a/src/lib/Bcfg2/Client/Tools/Portage.py
+++ b/src/lib/Bcfg2/Client/Tools/Portage.py
@@ -36,7 +36,8 @@ class Portage(Bcfg2.Client.Tools.PkgTool):
return
self.logger.info('Getting list of installed packages')
self.installed = {}
- for pkg in self.cmd.run("equery -q list '*'").stdout.splitlines():
+ for pkg in self.cmd.run(["equery", "-q",
+ "list", "*"]).stdout.splitlines():
if self._pkg_pattern.match(pkg):
name = self._pkg_pattern.match(pkg).group(1)
version = self._pkg_pattern.match(pkg).group(2)
diff --git a/src/lib/Bcfg2/Client/Tools/RcUpdate.py b/src/lib/Bcfg2/Client/Tools/RcUpdate.py
index 2e58f2564..552b27842 100644
--- a/src/lib/Bcfg2/Client/Tools/RcUpdate.py
+++ b/src/lib/Bcfg2/Client/Tools/RcUpdate.py
@@ -22,8 +22,8 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool):
return True
# check if service is enabled
- cmd = '/sbin/rc-update show default | grep %s'
- is_enabled = self.cmd.run(cmd % entry.get('name')).success
+ result = self.cmd.run(["/sbin/rc-update", "show", "default"])
+ is_enabled = entry.get("name") in result.stdout
# check if init script exists
try:
@@ -34,8 +34,8 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool):
return False
# check if service is enabled
- cmd = '/etc/init.d/%s status | grep started'
- is_running = self.cmd.run(cmd % entry.attrib['name']).success
+ result = self.cmd.run(self.get_svc_command(entry, "status"))
+ is_running = "started" in result.stdout
if entry.get('status') == 'on' and not (is_enabled and is_running):
entry.set('current_status', 'off')
@@ -70,9 +70,9 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool):
def FindExtra(self):
"""Locate extra rc-update services."""
- cmd = '/bin/rc-status -s'
allsrv = [line.split()[0]
- for line in self.cmd.run(cmd).stdout.splitlines()
+ for line in self.cmd.run(['/bin/rc-status',
+ '-s']).stdout.splitlines()
if 'started' in line]
self.logger.debug('Found active services:')
self.logger.debug(allsrv)
diff --git a/src/lib/Bcfg2/Client/Tools/VCS.py b/src/lib/Bcfg2/Client/Tools/VCS.py
index fa3d22e1d..1ab867215 100644
--- a/src/lib/Bcfg2/Client/Tools/VCS.py
+++ b/src/lib/Bcfg2/Client/Tools/VCS.py
@@ -120,8 +120,9 @@ class VCS(Bcfg2.Client.Tools.Tool):
def Installsvn(self, entry):
"""Checkout contents from a svn repository"""
# pylint: disable=E1101
+ client = pysvn.Client()
try:
- client = pysvn.Client.update(entry.get('name'), recurse=True)
+ client.update(entry.get('name'), recurse=True)
except pysvn.ClientError:
self.logger.error("Failed to update repository", exc_info=1)
return False
diff --git a/src/lib/Bcfg2/Client/Tools/YUM.py b/src/lib/Bcfg2/Client/Tools/YUM.py
index 648d30d15..57ca06e77 100644
--- a/src/lib/Bcfg2/Client/Tools/YUM.py
+++ b/src/lib/Bcfg2/Client/Tools/YUM.py
@@ -125,7 +125,7 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
('Package', 'rpm'),
('Path', 'ignore')]
- __req__ = {'Package': ['name'],
+ __req__ = {'Package': ['type'],
'Path': ['type']}
conflicts = ['RPM']
@@ -290,6 +290,17 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
return self.yumbase.rpmdb.returnGPGPubkeyPackages()
return self.yumbase.rpmdb.searchNevra(name='gpg-pubkey')
+ def missing_attrs(self, entry):
+ """ Implementing from superclass to check for existence of either
+ name or group attribute for Package entry in the case of a YUM
+ group. """
+ missing = Bcfg2.Client.Tools.PkgTool.missing_attrs(self, entry)
+
+ if entry.get('name', None) == None and \
+ entry.get('group', None) == None:
+ missing += ['name', 'group']
+ return missing
+
def _verifyHelper(self, pkg_obj):
""" _verifyHelper primarly deals with a yum bug where the
pkg_obj.verify() method does not properly take into count multilib
@@ -412,8 +423,12 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
if entry.get('version', False) == 'auto':
self._fixAutoVersion(entry)
- self.logger.debug("Verifying package instances for %s" %
- entry.get('name'))
+ if entry.get('group'):
+ self.logger.debug("Verifying packages for group %s" %
+ entry.get('group'))
+ else:
+ self.logger.debug("Verifying package instances for %s" %
+ entry.get('name'))
self.verify_cache = dict() # Used for checking multilib packages
self.modlists[entry] = modlist
@@ -426,14 +441,58 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
entry.get('pkg_checks', 'true').lower() == 'true'
pkg_verify = self.pkg_verify and \
entry.get('pkg_verify', 'true').lower() == 'true'
+ yum_group = False
if entry.get('name') == 'gpg-pubkey':
all_pkg_objs = self._getGPGKeysAsPackages()
pkg_verify = False # No files here to verify
+ elif entry.get('group'):
+ entry.set('name', 'group:%s' % entry.get('group'))
+ yum_group = True
+ all_pkg_objs = []
+ instances = []
+ if self.yumbase.comps.has_group(entry.get('group')):
+ group = self.yumbase.comps.return_group(entry.get('group'))
+ group_packages = [p
+ for p, d in group.mandatory_packages.items()
+ if d]
+ group_type = entry.get('choose', 'default')
+ if group_type in ['default', 'optional', 'all']:
+ group_packages += [p
+ for p, d in
+ group.default_packages.items()
+ if d]
+ if group_type in ['optional', 'all']:
+ group_packages += [p
+ for p, d in
+ group.optional_packages.items()
+ if d]
+ if len(group_packages) == 0:
+ self.logger.error("No packages found for group %s" %
+ entry.get("group"))
+ for pkg in group_packages:
+ # create package instances for each package in yum group
+ instance = Bcfg2.Client.XML.SubElement(entry, 'Package')
+ instance.attrib['name'] = pkg
+ instance.attrib['type'] = 'yum'
+ try:
+ newest = \
+ self.yumbase.pkgSack.returnNewestByName(pkg)[0]
+ instance.attrib['version'] = newest['version']
+ instance.attrib['epoch'] = newest['epoch']
+ instance.attrib['release'] = newest['release']
+ except: # pylint: disable=W0702
+ self.logger.info("Error finding newest package "
+ "for %s" %
+ pkg)
+ instance.attrib['version'] = 'any'
+ instances.append(instance)
+ else:
+ self.logger.error("Group not found: %s" % entry.get("group"))
else:
all_pkg_objs = \
self.yumbase.rpmdb.searchNevra(name=entry.get('name'))
- if len(all_pkg_objs) == 0:
+ if len(all_pkg_objs) == 0 and yum_group != True:
# Some sort of virtual capability? Try to resolve it
all_pkg_objs = self.yumbase.rpmdb.searchProvides(entry.get('name'))
if len(all_pkg_objs) > 0:
@@ -444,7 +503,13 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
self.logger.info(" %s" % pkg)
for inst in instances:
- nevra = build_yname(entry.get('name'), inst)
+ if yum_group:
+ # the entry is not the name of the package
+ nevra = build_yname(inst.get('name'), inst)
+ all_pkg_objs = \
+ self.yumbase.rpmdb.searchNevra(name=inst.get('name'))
+ else:
+ nevra = build_yname(entry.get('name'), inst)
if nevra in pkg_cache:
continue # Ignore duplicate instances
else:
@@ -458,7 +523,10 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
stat['version_fail'] = False
stat['verify'] = {}
stat['verify_fail'] = False
- stat['pkg'] = entry
+ if yum_group:
+ stat['pkg'] = inst
+ else:
+ stat['pkg'] = entry
stat['modlist'] = modlist
if inst.get('verify_flags'):
# this splits on either space or comma
@@ -627,7 +695,9 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
else:
install_only = False
- if virt_pkg or (install_only and not self.setup['kevlar']):
+ if virt_pkg or \
+ (install_only and not self.setup['kevlar']) or \
+ yum_group:
# virtual capability supplied, we are probably dealing
# with multiple packages of different names. This check
# doesn't make a lot of since in this case.
diff --git a/src/lib/Bcfg2/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py
index 39a664df1..473d2bf09 100644
--- a/src/lib/Bcfg2/Client/Tools/__init__.py
+++ b/src/lib/Bcfg2/Client/Tools/__init__.py
@@ -130,7 +130,6 @@ class Tool(object):
raise ToolInstantiationError("%s: %s not executable" %
(self.name, filename))
-
def BundleUpdated(self, bundle): # pylint: disable=W0613
""" Callback that is invoked when a bundle has been updated.
@@ -516,8 +515,8 @@ class SvcTool(Tool):
:param service: The service entry to modify
:type service: lxml.etree._Element
- :returns: tuple - The return value from
- :class:`Bcfg2.Client.Tools.Executor.run`
+ :returns: Bcfg2.Utils.ExecutorResult - The return value from
+ :class:`Bcfg2.Utils.Executor.run`
"""
self.logger.debug('Starting service %s' % service.get('name'))
return self.cmd.run(self.get_svc_command(service, 'start'))
@@ -527,8 +526,8 @@ class SvcTool(Tool):
:param service: The service entry to modify
:type service: lxml.etree._Element
- :returns: tuple - The return value from
- :class:`Bcfg2.Client.Tools.Executor.run`
+ :returns: Bcfg2.Utils.ExecutorResult - The return value from
+ :class:`Bcfg2.Utils.Executor.run`
"""
self.logger.debug('Stopping service %s' % service.get('name'))
return self.cmd.run(self.get_svc_command(service, 'stop'))
@@ -538,8 +537,8 @@ class SvcTool(Tool):
:param service: The service entry to modify
:type service: lxml.etree._Element
- :returns: tuple - The return value from
- :class:`Bcfg2.Client.Tools.Executor.run`
+ :returns: Bcfg2.Utils.ExecutorResult - The return value from
+ :class:`Bcfg2.Utils.Executor.run`
"""
self.logger.debug('Restarting service %s' % service.get('name'))
restart_target = service.get('target', 'restart')
@@ -553,7 +552,7 @@ class SvcTool(Tool):
:returns: bool - True if the status command returned 0, False
otherwise
"""
- return self.cmd.run(self.get_svc_command(service, 'status'))
+ return bool(self.cmd.run(self.get_svc_command(service, 'status')))
def Remove(self, services):
if self.setup['servicemode'] != 'disabled':
diff --git a/src/lib/Bcfg2/Compat.py b/src/lib/Bcfg2/Compat.py
index beb534791..44c76303c 100644
--- a/src/lib/Bcfg2/Compat.py
+++ b/src/lib/Bcfg2/Compat.py
@@ -19,12 +19,13 @@ except ImportError:
# urllib imports
try:
+ from urllib import quote_plus
from urlparse import urljoin, urlparse
from urllib2 import HTTPBasicAuthHandler, \
HTTPPasswordMgrWithDefaultRealm, build_opener, install_opener, \
urlopen, HTTPError, URLError
except ImportError:
- from urllib.parse import urljoin, urlparse
+ from urllib.parse import urljoin, urlparse, quote_plus
from urllib.request import HTTPBasicAuthHandler, \
HTTPPasswordMgrWithDefaultRealm, build_opener, install_opener, urlopen
from urllib.error import HTTPError, URLError
@@ -51,7 +52,8 @@ except ImportError:
# xmlrpc imports
try:
- import xmlrpclib, SimpleXMLRPCServer
+ import xmlrpclib
+ import SimpleXMLRPCServer
except ImportError:
import xmlrpc.client as xmlrpclib
import xmlrpc.server as SimpleXMLRPCServer
@@ -73,6 +75,7 @@ try:
except NameError:
unicode = str
+
def u_str(string, encoding=None):
""" print to file compatibility """
if sys.hexversion >= 0x03000000:
diff --git a/src/lib/Bcfg2/Logger.py b/src/lib/Bcfg2/Logger.py
index c2eac1e60..5bbc9ff96 100644
--- a/src/lib/Bcfg2/Logger.py
+++ b/src/lib/Bcfg2/Logger.py
@@ -105,7 +105,11 @@ class FragmentingSysLogHandler(logging.handlers.SysLogHandler):
(self.encodePriority(self.facility, newrec.levelname.lower()),
self.format(newrec))
try:
- self.socket.send(msg.encode('ascii'))
+ try:
+ encoded = msg.encode('utf-8')
+ except UnicodeDecodeError:
+ encoded = msg
+ self.socket.send(encoded)
except socket.error:
for i in range(10): # pylint: disable=W0612
try:
@@ -139,6 +143,10 @@ def add_console_handler(level=logging.DEBUG):
console.setLevel(level)
# tell the handler to use this format
console.setFormatter(TermiosFormatter())
+ try:
+ console.set_name("console")
+ except AttributeError:
+ console.name = "console"
logging.root.addHandler(console)
@@ -153,6 +161,10 @@ def add_syslog_handler(procname, syslog_facility, level=logging.DEBUG):
syslog = FragmentingSysLogHandler(procname,
('localhost', 514),
syslog_facility)
+ try:
+ syslog.set_name("syslog")
+ except AttributeError:
+ syslog.name = "syslog"
syslog.setLevel(level)
syslog.setFormatter(
logging.Formatter('%(name)s[%(process)d]: %(message)s'))
@@ -166,6 +178,10 @@ def add_syslog_handler(procname, syslog_facility, level=logging.DEBUG):
def add_file_handler(to_file, level=logging.DEBUG):
"""Add a logging handler that logs to to_file."""
filelog = logging.FileHandler(to_file)
+ try:
+ filelog.set_name("file")
+ except AttributeError:
+ filelog.name = "file"
filelog.setLevel(level)
filelog.setFormatter(
logging.Formatter('%(asctime)s %(name)s[%(process)d]: %(message)s'))
diff --git a/src/lib/Bcfg2/Options.py b/src/lib/Bcfg2/Options.py
index 74c488b45..e5aeccf4d 100644
--- a/src/lib/Bcfg2/Options.py
+++ b/src/lib/Bcfg2/Options.py
@@ -528,6 +528,11 @@ SERVER_FAM_IGNORE = \
'SCCS', '.svn', '4913', '.gitignore'],
cf=('server', 'ignore_files'),
cook=list_split)
+SERVER_FAM_BLOCK = \
+ Option('FAM blocks on startup until all events are processed',
+ default=False,
+ cook=get_bool,
+ cf=('server', 'fam_blocking'))
SERVER_LISTEN_ALL = \
Option('Listen on all interfaces',
default=False,
@@ -630,17 +635,19 @@ WEB_CFILE = \
default="/etc/bcfg2-web.conf",
cmd='-W',
odesc='<conffile>',
- cf=('statistics', 'config'),)
+ cf=('reporting', 'config'),
+ deprecated_cf=('statistics', 'web_prefix'),)
DJANGO_TIME_ZONE = \
Option('Django timezone',
default=None,
- cf=('statistics', 'time_zone'),)
+ cf=('reporting', 'time_zone'),
+ deprecated_cf=('statistics', 'web_prefix'),)
DJANGO_DEBUG = \
Option('Django debug',
default=None,
- cf=('statistics', 'web_debug'),
+ cf=('reporting', 'web_debug'),
+ deprecated_cf=('statistics', 'web_prefix'),
cook=get_bool,)
-# Django options
DJANGO_WEB_PREFIX = \
Option('Web prefix',
default=None,
@@ -824,7 +831,7 @@ CLIENT_COMMAND_TIMEOUT = \
# bcfg2-test and bcfg2-lint options
TEST_NOSEOPTS = \
- Option('Options to pass to nosetests',
+ Option('Options to pass to nosetests. Only honored with --children 0',
default=[],
cmd='--nose-options',
odesc='<opts>',
@@ -839,6 +846,21 @@ TEST_IGNORE = \
cf=('bcfg2_test', 'ignore_entries'),
cook=list_split,
long_arg=True)
+TEST_CHILDREN = \
+ Option('Spawn this number of children for bcfg2-test (python 2.6+)',
+ default=0,
+ cmd='--children',
+ odesc='<children>',
+ cf=('bcfg2_test', 'children'),
+ cook=int,
+ long_arg=True)
+TEST_XUNIT = \
+ Option('Output an XUnit result file with --children',
+ default=None,
+ cmd='--xunit',
+ odesc='<xunit file>',
+ cf=('bcfg2_test', 'xunit'),
+ long_arg=True)
LINT_CONFIG = \
Option('Specify bcfg2-lint configuration file',
default='/etc/bcfg2-lint.conf',
@@ -1019,7 +1041,7 @@ CRYPT_STDOUT = \
cmd='--stdout',
long_arg=True)
CRYPT_PASSPHRASE = \
- Option('Encryption passphrase (name or passphrase)',
+ Option('Encryption passphrase name',
default=None,
cmd='-p',
odesc='<passphrase>')
@@ -1066,6 +1088,7 @@ SERVER_COMMON_OPTIONS = dict(repo=SERVER_REPOSITORY,
password=SERVER_PASSWORD,
filemonitor=SERVER_FILEMONITOR,
ignore=SERVER_FAM_IGNORE,
+ fam_blocking=SERVER_FAM_BLOCK,
location=SERVER_LOCATION,
key=SERVER_KEY,
cert=SERVER_CERT,
@@ -1172,6 +1195,16 @@ DATABASE_COMMON_OPTIONS = dict(web_configfile=WEB_CFILE,
REPORTING_COMMON_OPTIONS = dict(reporting_file_limit=REPORTING_FILE_LIMIT,
reporting_transport=REPORTING_TRANSPORT)
+TEST_COMMON_OPTIONS = dict(noseopts=TEST_NOSEOPTS,
+ test_ignore=TEST_IGNORE,
+ children=TEST_CHILDREN,
+ xunit=TEST_XUNIT,
+ validate=CFG_VALIDATION)
+
+INFO_COMMON_OPTIONS = dict(ppath=PARANOID_PATH,
+ max_copies=PARANOID_MAX_COPIES)
+INFO_COMMON_OPTIONS.update(CLI_COMMON_OPTIONS)
+INFO_COMMON_OPTIONS.update(SERVER_COMMON_OPTIONS)
class OptionParser(OptionSet):
""" OptionParser bootstraps option parsing, getting the value of
diff --git a/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py b/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
index bca4a9c1e..3b2c0ccfa 100644
--- a/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
+++ b/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
@@ -16,6 +16,7 @@ from Bcfg2.Reporting.Storage.base import StorageBase, StorageError
from Bcfg2.Server.Plugin.exceptions import PluginExecutionError
from django.core import management
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
+from django.db.models import FieldDoesNotExist
from django.core.cache import cache
from django.db import transaction
@@ -30,6 +31,230 @@ class DjangoORM(StorageBase):
super(DjangoORM, self).__init__(setup)
self.size_limit = setup.get('reporting_file_limit')
+ def _import_default(self, entry, state, entrytype=None, defaults=None,
+ mapping=None, boolean=None, xforms=None):
+ """ Default entry importer. Maps the entry (in state
+ ``state``) to an appropriate *Entry object; by default, this
+ is determined by the entry tag, e.g., from an Action entry an
+ ActionEntry object is created. This can be overridden with
+ ``entrytype``, which should be the class to instantiate for
+ this entry.
+
+ ``defaults`` is an optional mapping of <attribute
+ name>:<value> that will be used to set the default values for
+ various attributes.
+
+ ``mapping`` is a mapping of <field name>:<attribute name> that
+ can be used to map fields that are named differently on the
+ XML entry and in the database model.
+
+ ``boolean`` is a list of attribute names that should be
+ treated as booleans.
+
+ ``xforms`` is a dict of <attribute name>:<function>, where the
+ given function will be applied to the value of the named
+ attribute before trying to store it in the database.
+ """
+ if entrytype is None:
+ entrytype = globals()["%sEntry" % entry.tag]
+ if defaults is None:
+ defaults = dict()
+ if mapping is None:
+ mapping = dict()
+ if boolean is None:
+ boolean = []
+ if xforms is None:
+ xforms = dict()
+ mapping['exists'] = 'current_exists'
+ defaults['current_exists'] = 'true'
+ boolean.append("current_exists")
+
+ def boolean_xform(val):
+ try:
+ return val.lower() == "true"
+ except AttributeError:
+ return False
+
+ for attr in boolean + ["current_exists"]:
+ xforms[attr] = boolean_xform
+ act_dict = dict(state=state)
+ for fieldname in entrytype._meta.get_all_field_names():
+ if fieldname in ['id', 'hash_key', 'state']:
+ continue
+ try:
+ field = entrytype._meta.get_field(fieldname)
+ except FieldDoesNotExist:
+ continue
+ attrname = mapping.get(fieldname, fieldname)
+ val = entry.get(fieldname, defaults.get(attrname))
+ act_dict[fieldname] = xforms.get(attrname, lambda v: v)(val)
+ self.logger.debug("Adding %s:%s" % (entry.tag, entry.get("name")))
+ return entrytype.entry_get_or_create(act_dict)
+
+ def _import_Action(self, entry, state):
+ return self._import_default(entry, state,
+ defaults=dict(status='check', rc=-1),
+ mapping=dict(output="rc"))
+
+ def _import_Package(self, entry, state):
+ name = entry.get('name')
+ exists = entry.get('current_exists', default="true").lower() == "true"
+ act_dict = dict(name=name, state=state, exists=exists,
+ target_version=entry.get('version', default=''),
+ current_version=entry.get('current_version',
+ default=''))
+
+ # extra entries are a bit different. They can have Instance
+ # objects
+ if not act_dict['target_version']:
+ for instance in entry.findall("Instance"):
+ # FIXME - this probably only works for rpms
+ release = instance.get('release', '')
+ arch = instance.get('arch', '')
+ act_dict['current_version'] = instance.get('version')
+ if release:
+ act_dict['current_version'] += "-" + release
+ if arch:
+ act_dict['current_version'] += "." + arch
+ self.logger.debug("Adding package %s %s" %
+ (name, act_dict['current_version']))
+ return PackageEntry.entry_get_or_create(act_dict)
+ else:
+ self.logger.debug("Adding package %s %s" %
+ (name, act_dict['target_version']))
+
+ # not implemented yet
+ act_dict['verification_details'] = \
+ entry.get('verification_details', '')
+ return PackageEntry.entry_get_or_create(act_dict)
+
+ def _import_Path(self, entry, state):
+ name = entry.get('name')
+ exists = entry.get('current_exists', default="true").lower() == "true"
+ path_type = entry.get("type").lower()
+ act_dict = dict(name=name, state=state, exists=exists,
+ path_type=path_type)
+
+ target_dict = dict(
+ owner=entry.get('owner', default="root"),
+ group=entry.get('group', default="root"),
+ mode=entry.get('mode', default=entry.get('perms',
+ default=""))
+ )
+ fperm, created = FilePerms.objects.get_or_create(**target_dict)
+ act_dict['target_perms'] = fperm
+
+ current_dict = dict(
+ owner=entry.get('current_owner', default=""),
+ group=entry.get('current_group', default=""),
+ mode=entry.get('current_mode',
+ default=entry.get('current_perms', default=""))
+ )
+ fperm, created = FilePerms.objects.get_or_create(**current_dict)
+ act_dict['current_perms'] = fperm
+
+ if path_type in ('symlink', 'hardlink'):
+ act_dict['target_path'] = entry.get('to', default="")
+ act_dict['current_path'] = entry.get('current_to', default="")
+ self.logger.debug("Adding link %s" % name)
+ return LinkEntry.entry_get_or_create(act_dict)
+ elif path_type == 'device':
+ # TODO devices
+ self.logger.warn("device path types are not supported yet")
+ return
+
+ # TODO - vcs output
+ act_dict['detail_type'] = PathEntry.DETAIL_UNUSED
+ if path_type == 'directory' and entry.get('prune', 'false') == 'true':
+ unpruned_elist = [e.get('path') for e in entry.findall('Prune')]
+ if unpruned_elist:
+ act_dict['detail_type'] = PathEntry.DETAIL_PRUNED
+ act_dict['details'] = "\n".join(unpruned_elist)
+ elif entry.get('sensitive', 'false').lower() == 'true':
+ act_dict['detail_type'] = PathEntry.DETAIL_SENSITIVE
+ else:
+ cdata = None
+ if entry.get('current_bfile', None):
+ act_dict['detail_type'] = PathEntry.DETAIL_BINARY
+ cdata = entry.get('current_bfile')
+ elif entry.get('current_bdiff', None):
+ act_dict['detail_type'] = PathEntry.DETAIL_DIFF
+ cdata = b64decode(entry.get('current_bdiff'))
+ elif entry.get('current_diff', None):
+ act_dict['detail_type'] = PathEntry.DETAIL_DIFF
+ cdata = entry.get('current_bdiff')
+ if cdata:
+ if len(cdata) > self.size_limit:
+ act_dict['detail_type'] = PathEntry.DETAIL_SIZE_LIMIT
+ act_dict['details'] = md5(cdata).hexdigest()
+ else:
+ act_dict['details'] = cdata
+ self.logger.debug("Adding path %s" % name)
+ return PathEntry.entry_get_or_create(act_dict)
+ # TODO - secontext
+ # TODO - acls
+
+ def _import_Service(self, entry, state):
+ return self._import_default(entry, state,
+ defaults=dict(status='',
+ current_status=''),
+ mapping=dict(status='target_status'))
+
+ def _import_SEBoolean(self, entry, state):
+ return self._import_default(
+ entry, state,
+ xforms=dict(value=lambda v: v.lower() == "on"))
+
+ def _import_SEFcontext(self, entry, state):
+ return self._import_default(entry, state,
+ defaults=dict(filetype='all'))
+
+ def _import_SEInterface(self, entry, state):
+ return self._import_default(entry, state)
+
+ def _import_SEPort(self, entry, state):
+ return self._import_default(entry, state)
+
+ def _import_SENode(self, entry, state):
+ return self._import_default(entry, state)
+
+ def _import_SELogin(self, entry, state):
+ return self._import_default(entry, state)
+
+ def _import_SEUser(self, entry, state):
+ return self._import_default(entry, state)
+
+ def _import_SEPermissive(self, entry, state):
+ return self._import_default(entry, state)
+
+ def _import_SEModule(self, entry, state):
+ return self._import_default(entry, state,
+ defaults=dict(disabled='false'),
+ boolean=['disabled', 'current_disabled'])
+
+ def _import_POSIXUser(self, entry, state):
+ defaults = dict(group=entry.get("name"),
+ gecos=entry.get("name"),
+ shell='/bin/bash',
+ uid=entry.get("current_uid"))
+ if entry.get('name') == 'root':
+ defaults['home'] = '/root'
+ else:
+ defaults['home'] = '/home/%s' % entry.get('name')
+
+ # TODO: supplementary group membership
+ return self._import_default(entry, state, defaults=defaults)
+
+ def _import_POSIXGroup(self, entry, state):
+ return self._import_default(
+ entry, state,
+ defaults=dict(gid=entry.get("current_gid")))
+
+ def _import_unknown(self, entry, _):
+ self.logger.error("Unknown type %s not handled by reporting yet" %
+ entry.tag)
+ return None
+
@transaction.commit_on_success
def _import_interaction(self, interaction):
"""Real import function"""
@@ -46,13 +271,15 @@ class DjangoORM(StorageBase):
cache.set(hostname, client)
timestamp = datetime(*strptime(stats.get('time'))[0:6])
- if len(Interaction.objects.filter(client=client, timestamp=timestamp)) > 0:
+ if len(Interaction.objects.filter(client=client,
+ timestamp=timestamp)) > 0:
self.logger.warn("Interaction for %s at %s already exists" %
(hostname, timestamp))
return
if 'profile' in metadata:
- profile, created = Group.objects.get_or_create(name=metadata['profile'])
+ profile, created = \
+ Group.objects.get_or_create(name=metadata['profile'])
else:
profile = None
inter = Interaction(client=client,
@@ -65,10 +292,10 @@ class DjangoORM(StorageBase):
server=server,
profile=profile)
inter.save()
- self.logger.debug("Interaction for %s at %s with INSERTED in to db" %
+ self.logger.debug("Interaction for %s at %s with INSERTED in to db" %
(client.id, timestamp))
- #FIXME - this should be more efficient
+ # FIXME - this should be more efficient
for group_name in metadata['groups']:
group = cache.get("GROUP_" + group_name)
if not group:
@@ -76,12 +303,13 @@ class DjangoORM(StorageBase):
if created:
self.logger.debug("Added group %s" % group)
cache.set("GROUP_" + group_name, group)
-
+
inter.groups.add(group)
- for bundle_name in metadata['bundles']:
+ for bundle_name in metadata.get('bundles', []):
bundle = cache.get("BUNDLE_" + bundle_name)
if not bundle:
- bundle, created = Bundle.objects.get_or_create(name=bundle_name)
+ bundle, created = \
+ Bundle.objects.get_or_create(name=bundle_name)
if created:
self.logger.debug("Added bundle %s" % bundle)
cache.set("BUNDLE_" + bundle_name, bundle)
@@ -94,130 +322,26 @@ class DjangoORM(StorageBase):
pattern = [('Bad/*', TYPE_BAD),
('Extra/*', TYPE_EXTRA),
('Modified/*', TYPE_MODIFIED)]
- updates = dict(failures=[], paths=[], packages=[], actions=[], services=[])
+ updates = dict([(etype, []) for etype in Interaction.entry_types])
for (xpath, state) in pattern:
for entry in stats.findall(xpath):
counter_fields[state] = counter_fields[state] + 1
- entry_type = entry.tag
- name = entry.get('name')
- exists = entry.get('current_exists', default="true").lower() == "true"
-
# handle server failures differently
failure = entry.get('failure', '')
if failure:
- act_dict = dict(name=name, entry_type=entry_type,
- message=failure)
+ act_dict = dict(name=entry.get("name"),
+ entry_type=entry.tag,
+ message=failure)
newact = FailureEntry.entry_get_or_create(act_dict)
updates['failures'].append(newact)
continue
- act_dict = dict(name=name, state=state, exists=exists)
-
- if entry_type == 'Action':
- act_dict['status'] = entry.get('status', default="check")
- act_dict['output'] = entry.get('rc', default=-1)
- self.logger.debug("Adding action %s" % name)
- updates['actions'].append(ActionEntry.entry_get_or_create(act_dict))
- elif entry_type == 'Package':
- act_dict['target_version'] = entry.get('version', default='')
- act_dict['current_version'] = entry.get('current_version', default='')
-
- # extra entries are a bit different. They can have Instance objects
- if not act_dict['target_version']:
- for instance in entry.findall("Instance"):
- #TODO - this probably only works for rpms
- release = instance.get('release', '')
- arch = instance.get('arch', '')
- act_dict['current_version'] = instance.get('version')
- if release:
- act_dict['current_version'] += "-" + release
- if arch:
- act_dict['current_version'] += "." + arch
- self.logger.debug("Adding package %s %s" % (name, act_dict['current_version']))
- updates['packages'].append(PackageEntry.entry_get_or_create(act_dict))
- else:
-
- self.logger.debug("Adding package %s %s" % (name, act_dict['target_version']))
-
- # not implemented yet
- act_dict['verification_details'] = entry.get('verification_details', '')
- updates['packages'].append(PackageEntry.entry_get_or_create(act_dict))
-
- elif entry_type == 'Path':
- path_type = entry.get("type").lower()
- act_dict['path_type'] = path_type
-
- target_dict = dict(
- owner=entry.get('owner', default="root"),
- group=entry.get('group', default="root"),
- mode=entry.get('mode', default=entry.get('perms', default=""))
- )
- fperm, created = FilePerms.objects.get_or_create(**target_dict)
- act_dict['target_perms'] = fperm
-
- current_dict = dict(
- owner=entry.get('current_owner', default=""),
- group=entry.get('current_group', default=""),
- mode=entry.get('current_mode',
- default=entry.get('current_perms', default=""))
- )
- fperm, created = FilePerms.objects.get_or_create(**current_dict)
- act_dict['current_perms'] = fperm
-
- if path_type in ('symlink', 'hardlink'):
- act_dict['target_path'] = entry.get('to', default="")
- act_dict['current_path'] = entry.get('current_to', default="")
- self.logger.debug("Adding link %s" % name)
- updates['paths'].append(LinkEntry.entry_get_or_create(act_dict))
- continue
- elif path_type == 'device':
- #TODO devices
- self.logger.warn("device path types are not supported yet")
- continue
-
- # TODO - vcs output
- act_dict['detail_type'] = PathEntry.DETAIL_UNUSED
- if path_type == 'directory' and entry.get('prune', 'false') == 'true':
- unpruned_elist = [e.get('path') for e in entry.findall('Prune')]
- if unpruned_elist:
- act_dict['detail_type'] = PathEntry.DETAIL_PRUNED
- act_dict['details'] = "\n".join(unpruned_elist)
- elif entry.get('sensitive', 'false').lower() == 'true':
- act_dict['detail_type'] = PathEntry.DETAIL_SENSITIVE
- else:
- cdata = None
- if entry.get('current_bfile', None):
- act_dict['detail_type'] = PathEntry.DETAIL_BINARY
- cdata = entry.get('current_bfile')
- elif entry.get('current_bdiff', None):
- act_dict['detail_type'] = PathEntry.DETAIL_DIFF
- cdata = b64decode(entry.get('current_bdiff'))
- elif entry.get('current_diff', None):
- act_dict['detail_type'] = PathEntry.DETAIL_DIFF
- cdata = entry.get('current_bdiff')
- if cdata:
- if len(cdata) > self.size_limit:
- act_dict['detail_type'] = PathEntry.DETAIL_SIZE_LIMIT
- act_dict['details'] = md5(cdata).hexdigest()
- else:
- act_dict['details'] = cdata
- self.logger.debug("Adding path %s" % name)
- updates['paths'].append(PathEntry.entry_get_or_create(act_dict))
-
-
- #TODO - secontext
- #TODO - acls
-
- elif entry_type == 'Service':
- act_dict['target_status'] = entry.get('status', default='')
- act_dict['current_status'] = entry.get('current_status', default='')
- self.logger.debug("Adding service %s" % name)
- updates['services'].append(ServiceEntry.entry_get_or_create(act_dict))
- elif entry_type == 'SELinux':
- self.logger.info("SELinux not implemented yet")
- else:
- self.logger.error("Unknown type %s not handled by reporting yet" % entry_type)
+ updatetype = entry.tag.lower() + "s"
+ update = getattr(self, "_import_%s" % entry.tag,
+ self._import_unknown)(entry, state)
+ if update is not None:
+ updates[updatetype].append(update)
inter.bad_count = counter_fields[TYPE_BAD]
inter.modified_count = counter_fields[TYPE_MODIFIED]
@@ -227,15 +351,16 @@ class DjangoORM(StorageBase):
# batch this for sqlite
i = 0
while(i < len(updates[entry_type])):
- getattr(inter, entry_type).add(*updates[entry_type][i:i+100])
+ getattr(inter, entry_type).add(*updates[entry_type][i:i + 100])
i += 100
# performance metrics
for times in stats.findall('OpStamps'):
for metric, value in list(times.items()):
- Performance(interaction=inter, metric=metric, value=value).save()
+ Performance(interaction=inter,
+ metric=metric,
+ value=value).save()
-
def import_interaction(self, interaction):
"""Import the data into the backend"""
@@ -245,7 +370,6 @@ class DjangoORM(StorageBase):
self.logger.error("Failed to import interaction: %s" %
traceback.format_exc().splitlines()[-1])
-
def validate(self):
"""Validate backend storage. Should be called once when loaded"""
diff --git a/src/lib/Bcfg2/Reporting/Transport/LocalFilesystem.py b/src/lib/Bcfg2/Reporting/Transport/LocalFilesystem.py
index 30ea39263..0a0f032e5 100644
--- a/src/lib/Bcfg2/Reporting/Transport/LocalFilesystem.py
+++ b/src/lib/Bcfg2/Reporting/Transport/LocalFilesystem.py
@@ -36,7 +36,8 @@ class LocalFilesystem(TransportBase):
def set_debug(self, debug):
rv = TransportBase.set_debug(self, debug)
- self.fmon.set_debug(debug)
+ if self.fmon is not None:
+ self.fmon.set_debug(debug)
return rv
def start_monitor(self, collector):
@@ -48,7 +49,8 @@ class LocalFilesystem(TransportBase):
self.logger.error("File monitor driver %s not available; "
"forcing to default" % setup['filemonitor'])
fmon = Bcfg2.Server.FileMonitor.available['default']
-
+ if self.debug_flag:
+ self.fmon.set_debug(self.debug_flag)
try:
self.fmon = fmon(debug=self.debug_flag)
self.logger.info("Using the %s file monitor" %
diff --git a/src/lib/Bcfg2/Reporting/migrations/0005_add_selinux_entry_support.py b/src/lib/Bcfg2/Reporting/migrations/0005_add_selinux_entry_support.py
new file mode 100644
index 000000000..d5f5d801a
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/migrations/0005_add_selinux_entry_support.py
@@ -0,0 +1,485 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'SELoginEntry'
+ db.create_table('Reporting_seloginentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('selinuxuser', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_selinuxuser', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ))
+ db.send_create_signal('Reporting', ['SELoginEntry'])
+
+ # Adding model 'SEUserEntry'
+ db.create_table('Reporting_seuserentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('roles', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_roles', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ('prefix', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_prefix', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ))
+ db.send_create_signal('Reporting', ['SEUserEntry'])
+
+ # Adding model 'SEBooleanEntry'
+ db.create_table('Reporting_sebooleanentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('value', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ))
+ db.send_create_signal('Reporting', ['SEBooleanEntry'])
+
+ # Adding model 'SENodeEntry'
+ db.create_table('Reporting_senodeentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ('proto', self.gf('django.db.models.fields.CharField')(max_length=4)),
+ ))
+ db.send_create_signal('Reporting', ['SENodeEntry'])
+
+ # Adding model 'SEFcontextEntry'
+ db.create_table('Reporting_sefcontextentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ('filetype', self.gf('django.db.models.fields.CharField')(max_length=16)),
+ ))
+ db.send_create_signal('Reporting', ['SEFcontextEntry'])
+
+ # Adding model 'SEInterfaceEntry'
+ db.create_table('Reporting_seinterfaceentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ))
+ db.send_create_signal('Reporting', ['SEInterfaceEntry'])
+
+ # Adding model 'SEPermissiveEntry'
+ db.create_table('Reporting_sepermissiveentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ))
+ db.send_create_signal('Reporting', ['SEPermissiveEntry'])
+
+ # Adding model 'SEModuleEntry'
+ db.create_table('Reporting_semoduleentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('disabled', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('current_disabled', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ))
+ db.send_create_signal('Reporting', ['SEModuleEntry'])
+
+ # Adding model 'SEPortEntry'
+ db.create_table('Reporting_seportentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ))
+ db.send_create_signal('Reporting', ['SEPortEntry'])
+
+ # Adding M2M table for field sebooleans on 'Interaction'
+ db.create_table('Reporting_interaction_sebooleans', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('sebooleanentry', models.ForeignKey(orm['Reporting.sebooleanentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_sebooleans', ['interaction_id', 'sebooleanentry_id'])
+
+ # Adding M2M table for field seports on 'Interaction'
+ db.create_table('Reporting_interaction_seports', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('seportentry', models.ForeignKey(orm['Reporting.seportentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_seports', ['interaction_id', 'seportentry_id'])
+
+ # Adding M2M table for field sefcontexts on 'Interaction'
+ db.create_table('Reporting_interaction_sefcontexts', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('sefcontextentry', models.ForeignKey(orm['Reporting.sefcontextentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_sefcontexts', ['interaction_id', 'sefcontextentry_id'])
+
+ # Adding M2M table for field senodes on 'Interaction'
+ db.create_table('Reporting_interaction_senodes', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('senodeentry', models.ForeignKey(orm['Reporting.senodeentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_senodes', ['interaction_id', 'senodeentry_id'])
+
+ # Adding M2M table for field selogins on 'Interaction'
+ db.create_table('Reporting_interaction_selogins', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('seloginentry', models.ForeignKey(orm['Reporting.seloginentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_selogins', ['interaction_id', 'seloginentry_id'])
+
+ # Adding M2M table for field seusers on 'Interaction'
+ db.create_table('Reporting_interaction_seusers', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('seuserentry', models.ForeignKey(orm['Reporting.seuserentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_seusers', ['interaction_id', 'seuserentry_id'])
+
+ # Adding M2M table for field seinterfaces on 'Interaction'
+ db.create_table('Reporting_interaction_seinterfaces', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('seinterfaceentry', models.ForeignKey(orm['Reporting.seinterfaceentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_seinterfaces', ['interaction_id', 'seinterfaceentry_id'])
+
+ # Adding M2M table for field sepermissives on 'Interaction'
+ db.create_table('Reporting_interaction_sepermissives', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('sepermissiveentry', models.ForeignKey(orm['Reporting.sepermissiveentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_sepermissives', ['interaction_id', 'sepermissiveentry_id'])
+
+ # Adding M2M table for field semodules on 'Interaction'
+ db.create_table('Reporting_interaction_semodules', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('semoduleentry', models.ForeignKey(orm['Reporting.semoduleentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_semodules', ['interaction_id', 'semoduleentry_id'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'SELoginEntry'
+ db.delete_table('Reporting_seloginentry')
+
+ # Deleting model 'SEUserEntry'
+ db.delete_table('Reporting_seuserentry')
+
+ # Deleting model 'SEBooleanEntry'
+ db.delete_table('Reporting_sebooleanentry')
+
+ # Deleting model 'SENodeEntry'
+ db.delete_table('Reporting_senodeentry')
+
+ # Deleting model 'SEFcontextEntry'
+ db.delete_table('Reporting_sefcontextentry')
+
+ # Deleting model 'SEInterfaceEntry'
+ db.delete_table('Reporting_seinterfaceentry')
+
+ # Deleting model 'SEPermissiveEntry'
+ db.delete_table('Reporting_sepermissiveentry')
+
+ # Deleting model 'SEModuleEntry'
+ db.delete_table('Reporting_semoduleentry')
+
+ # Deleting model 'SEPortEntry'
+ db.delete_table('Reporting_seportentry')
+
+ # Removing M2M table for field sebooleans on 'Interaction'
+ db.delete_table('Reporting_interaction_sebooleans')
+
+ # Removing M2M table for field seports on 'Interaction'
+ db.delete_table('Reporting_interaction_seports')
+
+ # Removing M2M table for field sefcontexts on 'Interaction'
+ db.delete_table('Reporting_interaction_sefcontexts')
+
+ # Removing M2M table for field senodes on 'Interaction'
+ db.delete_table('Reporting_interaction_senodes')
+
+ # Removing M2M table for field selogins on 'Interaction'
+ db.delete_table('Reporting_interaction_selogins')
+
+ # Removing M2M table for field seusers on 'Interaction'
+ db.delete_table('Reporting_interaction_seusers')
+
+ # Removing M2M table for field seinterfaces on 'Interaction'
+ db.delete_table('Reporting_interaction_seinterfaces')
+
+ # Removing M2M table for field sepermissives on 'Interaction'
+ db.delete_table('Reporting_interaction_sepermissives')
+
+ # Removing M2M table for field semodules on 'Interaction'
+ db.delete_table('Reporting_interaction_semodules')
+
+
+ models = {
+ 'Reporting.actionentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
+ },
+ 'Reporting.bundle': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'Reporting.client': {
+ 'Meta': {'object_name': 'Client'},
+ 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
+ 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.deviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
+ 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_minor': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.failureentry': {
+ 'Meta': {'object_name': 'FailureEntry'},
+ 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'message': ('django.db.models.fields.TextField', [], {}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileacl': {
+ 'Meta': {'object_name': 'FileAcl'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileperms': {
+ 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.group': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'Reporting.interaction': {
+ 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
+ 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
+ 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
+ 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
+ 'good_count': ('django.db.models.fields.IntegerField', [], {}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
+ 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
+ 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['Reporting.Group']"}),
+ 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'sebooleans': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEBooleanEntry']", 'symmetrical': 'False'}),
+ 'sefcontexts': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEFcontextEntry']", 'symmetrical': 'False'}),
+ 'seinterfaces': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEInterfaceEntry']", 'symmetrical': 'False'}),
+ 'selogins': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SELoginEntry']", 'symmetrical': 'False'}),
+ 'semodules': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEModuleEntry']", 'symmetrical': 'False'}),
+ 'senodes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SENodeEntry']", 'symmetrical': 'False'}),
+ 'sepermissives': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPermissiveEntry']", 'symmetrical': 'False'}),
+ 'seports': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPortEntry']", 'symmetrical': 'False'}),
+ 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
+ 'seusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEUserEntry']", 'symmetrical': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'total_count': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.linkentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
+ },
+ 'Reporting.packageentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
+ 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'Reporting.pathentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
+ 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
+ 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
+ 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
+ },
+ 'Reporting.performance': {
+ 'Meta': {'object_name': 'Performance'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
+ 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
+ },
+ 'Reporting.sebooleanentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEBooleanEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'value': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'Reporting.sefcontextentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEFcontextEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seinterfaceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEInterfaceEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seloginentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SELoginEntry'},
+ 'current_selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.semoduleentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEModuleEntry'},
+ 'current_disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.senodeentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SENodeEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'proto': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.sepermissiveentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPermissiveEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seportentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPortEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.serviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
+ 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
+ },
+ 'Reporting.seuserentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEUserEntry'},
+ 'current_prefix': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'current_roles': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'prefix': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'roles': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ }
+ }
+
+ complete_apps = ['Reporting'] \ No newline at end of file
diff --git a/src/lib/Bcfg2/Reporting/migrations/0006_add_user_group_entry_support.py b/src/lib/Bcfg2/Reporting/migrations/0006_add_user_group_entry_support.py
new file mode 100644
index 000000000..d86e663d5
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/migrations/0006_add_user_group_entry_support.py
@@ -0,0 +1,340 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'POSIXGroupEntry'
+ db.create_table('Reporting_posixgroupentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('gid', self.gf('django.db.models.fields.IntegerField')(null=True)),
+ ('current_gid', self.gf('django.db.models.fields.IntegerField')(null=True)),
+ ))
+ db.send_create_signal('Reporting', ['POSIXGroupEntry'])
+
+ # Adding model 'POSIXUserEntry'
+ db.create_table('Reporting_posixuserentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('uid', self.gf('django.db.models.fields.IntegerField')(null=True)),
+ ('current_uid', self.gf('django.db.models.fields.IntegerField')(null=True)),
+ ('group', self.gf('django.db.models.fields.CharField')(max_length=64)),
+ ('current_group', self.gf('django.db.models.fields.CharField')(max_length=64, null=True)),
+ ('gecos', self.gf('django.db.models.fields.CharField')(max_length=1024)),
+ ('current_gecos', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True)),
+ ('home', self.gf('django.db.models.fields.CharField')(max_length=1024)),
+ ('current_home', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True)),
+ ('shell', self.gf('django.db.models.fields.CharField')(default='/bin/bash', max_length=1024)),
+ ('current_shell', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True)),
+ ))
+ db.send_create_signal('Reporting', ['POSIXUserEntry'])
+
+ # Adding M2M table for field posixusers on 'Interaction'
+ db.create_table('Reporting_interaction_posixusers', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('posixuserentry', models.ForeignKey(orm['Reporting.posixuserentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_posixusers', ['interaction_id', 'posixuserentry_id'])
+
+ # Adding M2M table for field posixgroups on 'Interaction'
+ db.create_table('Reporting_interaction_posixgroups', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('posixgroupentry', models.ForeignKey(orm['Reporting.posixgroupentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_posixgroups', ['interaction_id', 'posixgroupentry_id'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'POSIXGroupEntry'
+ db.delete_table('Reporting_posixgroupentry')
+
+ # Deleting model 'POSIXUserEntry'
+ db.delete_table('Reporting_posixuserentry')
+
+ # Removing M2M table for field posixusers on 'Interaction'
+ db.delete_table('Reporting_interaction_posixusers')
+
+ # Removing M2M table for field posixgroups on 'Interaction'
+ db.delete_table('Reporting_interaction_posixgroups')
+
+
+ models = {
+ 'Reporting.actionentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
+ },
+ 'Reporting.bundle': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'Reporting.client': {
+ 'Meta': {'object_name': 'Client'},
+ 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
+ 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.deviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
+ 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_minor': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.failureentry': {
+ 'Meta': {'object_name': 'FailureEntry'},
+ 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'message': ('django.db.models.fields.TextField', [], {}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileacl': {
+ 'Meta': {'object_name': 'FileAcl'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileperms': {
+ 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.group': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'Reporting.interaction': {
+ 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
+ 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
+ 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
+ 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
+ 'good_count': ('django.db.models.fields.IntegerField', [], {}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
+ 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
+ 'posixgroups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.POSIXGroupEntry']", 'symmetrical': 'False'}),
+ 'posixusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.POSIXUserEntry']", 'symmetrical': 'False'}),
+ 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['Reporting.Group']"}),
+ 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'sebooleans': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEBooleanEntry']", 'symmetrical': 'False'}),
+ 'sefcontexts': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEFcontextEntry']", 'symmetrical': 'False'}),
+ 'seinterfaces': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEInterfaceEntry']", 'symmetrical': 'False'}),
+ 'selogins': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SELoginEntry']", 'symmetrical': 'False'}),
+ 'semodules': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEModuleEntry']", 'symmetrical': 'False'}),
+ 'senodes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SENodeEntry']", 'symmetrical': 'False'}),
+ 'sepermissives': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPermissiveEntry']", 'symmetrical': 'False'}),
+ 'seports': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPortEntry']", 'symmetrical': 'False'}),
+ 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
+ 'seusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEUserEntry']", 'symmetrical': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'total_count': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.linkentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
+ },
+ 'Reporting.packageentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
+ 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'Reporting.pathentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
+ 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
+ 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
+ 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
+ },
+ 'Reporting.performance': {
+ 'Meta': {'object_name': 'Performance'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
+ 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
+ },
+ 'Reporting.posixgroupentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'POSIXGroupEntry'},
+ 'current_gid': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'gid': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.posixuserentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'POSIXUserEntry'},
+ 'current_gecos': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+ 'current_group': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+ 'current_home': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+ 'current_shell': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+ 'current_uid': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'gecos': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'home': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'shell': ('django.db.models.fields.CharField', [], {'default': "'/bin/bash'", 'max_length': '1024'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'uid': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
+ },
+ 'Reporting.sebooleanentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEBooleanEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'value': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'Reporting.sefcontextentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEFcontextEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seinterfaceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEInterfaceEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seloginentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SELoginEntry'},
+ 'current_selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.semoduleentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEModuleEntry'},
+ 'current_disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.senodeentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SENodeEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'proto': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.sepermissiveentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPermissiveEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seportentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPortEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.serviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
+ 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
+ },
+ 'Reporting.seuserentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEUserEntry'},
+ 'current_prefix': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'current_roles': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'prefix': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'roles': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ }
+ }
+
+ complete_apps = ['Reporting'] \ No newline at end of file
diff --git a/src/lib/Bcfg2/Reporting/models.py b/src/lib/Bcfg2/Reporting/models.py
index ab2dc8418..4be509f53 100644
--- a/src/lib/Bcfg2/Reporting/models.py
+++ b/src/lib/Bcfg2/Reporting/models.py
@@ -11,21 +11,9 @@ except ImproperlyConfigured:
from django.core.cache import cache
from datetime import datetime, timedelta
+from Bcfg2.Compat import cPickle
+
-try:
- import cPickle as pickle
-except:
- import pickle
-
-KIND_CHOICES = (
- #These are the kinds of config elements
- ('Package', 'Package'),
- ('Path', 'directory'),
- ('Path', 'file'),
- ('Path', 'permissions'),
- ('Path', 'symlink'),
- ('Service', 'Service'),
-)
TYPE_GOOD = 0
TYPE_BAD = 1
TYPE_MODIFIED = 2
@@ -57,8 +45,8 @@ def hash_entry(entry_dict):
for key in sorted(entry_dict.keys()):
if key in ('id', 'hash_key') or key.startswith('_'):
continue
- dataset.append( (key, entry_dict[key]) )
- return hash(pickle.dumps(dataset))
+ dataset.append((key, entry_dict[key]))
+ return hash(cPickle.dumps(dataset))
class Client(models.Model):
@@ -121,7 +109,8 @@ class InteractionManager(models.Manager):
class Interaction(models.Model):
- """Models each reconfiguration operation interaction between client and server."""
+ """ Models each reconfiguration operation interaction between
+ client and server. """
client = models.ForeignKey(Client, related_name="interactions")
timestamp = models.DateTimeField(db_index=True) # Timestamp for this record
state = models.CharField(max_length=32) # good/bad/modified/etc
@@ -137,8 +126,24 @@ class Interaction(models.Model):
packages = models.ManyToManyField("PackageEntry")
paths = models.ManyToManyField("PathEntry")
services = models.ManyToManyField("ServiceEntry")
+ sebooleans = models.ManyToManyField("SEBooleanEntry")
+ seports = models.ManyToManyField("SEPortEntry")
+ sefcontexts = models.ManyToManyField("SEFcontextEntry")
+ senodes = models.ManyToManyField("SENodeEntry")
+ selogins = models.ManyToManyField("SELoginEntry")
+ seusers = models.ManyToManyField("SEUserEntry")
+ seinterfaces = models.ManyToManyField("SEInterfaceEntry")
+ sepermissives = models.ManyToManyField("SEPermissiveEntry")
+ semodules = models.ManyToManyField("SEModuleEntry")
+ posixusers = models.ManyToManyField("POSIXUserEntry")
+ posixgroups = models.ManyToManyField("POSIXGroupEntry")
failures = models.ManyToManyField("FailureEntry")
+ entry_types = ('actions', 'packages', 'paths', 'services', 'sebooleans',
+ 'seports', 'sefcontexts', 'senodes', 'selogins', 'seusers',
+ 'seinterfaces', 'sepermissives', 'semodules', 'posixusers',
+ 'posixgroups')
+
# Formerly InteractionMetadata
profile = models.ForeignKey("Group", related_name="+", null=True)
groups = models.ManyToManyField("Group")
@@ -157,7 +162,8 @@ class Interaction(models.Model):
def percentbad(self):
if not self.total_count == 0:
- return ((self.total_count - self.good_count) / (float(self.total_count))) * 100
+ return ((self.total_count - self.good_count) /
+ (float(self.total_count))) * 100
else:
return 0
@@ -189,7 +195,8 @@ class Interaction(models.Model):
self.client.save() # save again post update
def delete(self):
- '''Override the default delete. Allows us to remove Performance items'''
+ '''Override the default delete. Allows us to remove
+ Performance items '''
pitems = list(self.performance_items.all())
super(Interaction, self).delete()
for perf in pitems:
@@ -201,19 +208,19 @@ class Interaction(models.Model):
def bad(self):
rv = []
- for entry in ('actions', 'packages', 'paths', 'services'):
+ for entry in self.entry_types:
rv.extend(getattr(self, entry).filter(state=TYPE_BAD))
return rv
def modified(self):
rv = []
- for entry in ('actions', 'packages', 'paths', 'services'):
+ for entry in self.entry_types:
rv.extend(getattr(self, entry).filter(state=TYPE_MODIFIED))
return rv
def extra(self):
rv = []
- for entry in ('actions', 'packages', 'paths', 'services'):
+ for entry in self.entry_types:
rv.extend(getattr(self, entry).filter(state=TYPE_EXTRA))
return rv
@@ -325,7 +332,6 @@ class BaseEntry(models.Model):
self.hash_key = hash_entry(self.__dict__)
super(BaseEntry, self).save(*args, **kwargs)
-
def class_name(self):
return self.__class__.__name__
@@ -333,7 +339,6 @@ class BaseEntry(models.Model):
"""todo"""
return []
-
@classmethod
def entry_from_name(cls, name):
try:
@@ -344,28 +349,26 @@ class BaseEntry(models.Model):
except KeyError:
raise ValueError("Invalid type %s" % name)
-
@classmethod
def entry_from_type(cls, etype):
- for entry_cls in (ActionEntry, PackageEntry, PathEntry, ServiceEntry):
+ for entry_cls in ENTRY_CLASSES:
if etype == entry_cls.ENTRY_TYPE:
return entry_cls
else:
raise ValueError("Invalid type %s" % etype)
-
@classmethod
def entry_get_or_create(cls, act_dict):
"""Helper to quickly lookup an object"""
cls_name = cls().__class__.__name__
act_hash = hash_entry(act_dict)
-
+
# TODO - get form cache and validate
act_key = "%s_%s" % (cls_name, act_hash)
newact = cache.get(act_key)
if newact:
return newact
-
+
acts = cls.objects.filter(hash_key=act_hash)
if len(acts) > 0:
for act in acts:
@@ -375,20 +378,18 @@ class BaseEntry(models.Model):
#match found
newact = act
break
-
+
# worst case, its new
if not newact:
newact = cls(**act_dict)
newact.save(hash_key=act_hash)
-
+
cache.set(act_key, newact, 60 * 60)
return newact
-
def is_failure(self):
return isinstance(self, FailureEntry)
-
@classmethod
def prune_orphans(cls):
'''Remove unused entries'''
@@ -397,7 +398,7 @@ class BaseEntry(models.Model):
for x in cls.objects.filter(interaction__isnull=True).values("id")]
i = 0
while i < len(cls_orphans):
- cls.objects.filter(id__in=cls_orphans[i:i+100]).delete()
+ cls.objects.filter(id__in=cls_orphans[i:i + 100]).delete()
i += 100
@@ -439,13 +440,161 @@ class FailureEntry(BaseEntry):
class ActionEntry(SuccessEntry):
- """ The new model for package information """
+ """ Action entry """
status = models.CharField(max_length=128, default="check")
output = models.IntegerField(default=0)
ENTRY_TYPE = r"Action"
+class SEBooleanEntry(SuccessEntry):
+ """ SELinux boolean """
+ value = models.BooleanField(default=True)
+
+ ENTRY_TYPE = r"SEBoolean"
+
+
+class SEPortEntry(SuccessEntry):
+ """ SELinux port """
+ selinuxtype = models.CharField(max_length=128)
+ current_selinuxtype = models.CharField(max_length=128, null=True)
+
+ ENTRY_TYPE = r"SEPort"
+
+ def selinuxtype_problem(self):
+ """Check for an selinux type problem."""
+ if not self.current_selinuxtype:
+ return True
+ return self.selinuxtype != self.current_selinuxtype
+
+ def short_list(self):
+ """Return a list of problems"""
+ rv = super(SEPortEntry, self).short_list()
+ if self.selinuxtype_problem():
+ rv.append("Wrong SELinux type")
+ return rv
+
+
+class SEFcontextEntry(SuccessEntry):
+ """ SELinux file context """
+ selinuxtype = models.CharField(max_length=128)
+ current_selinuxtype = models.CharField(max_length=128, null=True)
+ filetype = models.CharField(max_length=16)
+
+ ENTRY_TYPE = r"SEFcontext"
+
+ def selinuxtype_problem(self):
+ """Check for an selinux type problem."""
+ if not self.current_selinuxtype:
+ return True
+ return self.selinuxtype != self.current_selinuxtype
+
+ def short_list(self):
+ """Return a list of problems"""
+ rv = super(SEFcontextEntry, self).short_list()
+ if self.selinuxtype_problem():
+ rv.append("Wrong SELinux type")
+ return rv
+
+
+class SENodeEntry(SuccessEntry):
+ """ SELinux node """
+ selinuxtype = models.CharField(max_length=128)
+ current_selinuxtype = models.CharField(max_length=128, null=True)
+ proto = models.CharField(max_length=4)
+
+ ENTRY_TYPE = r"SENode"
+
+ def selinuxtype_problem(self):
+ """Check for an selinux type problem."""
+ if not self.current_selinuxtype:
+ return True
+ return self.selinuxtype != self.current_selinuxtype
+
+ def short_list(self):
+ """Return a list of problems"""
+ rv = super(SENodeEntry, self).short_list()
+ if self.selinuxtype_problem():
+ rv.append("Wrong SELinux type")
+ return rv
+
+
+class SELoginEntry(SuccessEntry):
+ """ SELinux login """
+ selinuxuser = models.CharField(max_length=128)
+ current_selinuxuser = models.CharField(max_length=128, null=True)
+
+ ENTRY_TYPE = r"SELogin"
+
+
+class SEUserEntry(SuccessEntry):
+ """ SELinux user """
+ roles = models.CharField(max_length=128)
+ current_roles = models.CharField(max_length=128, null=True)
+ prefix = models.CharField(max_length=128)
+ current_prefix = models.CharField(max_length=128, null=True)
+
+ ENTRY_TYPE = r"SEUser"
+
+
+class SEInterfaceEntry(SuccessEntry):
+ """ SELinux interface """
+ selinuxtype = models.CharField(max_length=128)
+ current_selinuxtype = models.CharField(max_length=128, null=True)
+
+ ENTRY_TYPE = r"SEInterface"
+
+ def selinuxtype_problem(self):
+ """Check for an selinux type problem."""
+ if not self.current_selinuxtype:
+ return True
+ return self.selinuxtype != self.current_selinuxtype
+
+ def short_list(self):
+ """Return a list of problems"""
+ rv = super(SEInterfaceEntry, self).short_list()
+ if self.selinuxtype_problem():
+ rv.append("Wrong SELinux type")
+ return rv
+
+
+class SEPermissiveEntry(SuccessEntry):
+ """ SELinux permissive domain """
+ ENTRY_TYPE = r"SEPermissive"
+
+
+class SEModuleEntry(SuccessEntry):
+ """ SELinux module """
+ disabled = models.BooleanField(default=False)
+ current_disabled = models.BooleanField(default=False)
+
+ ENTRY_TYPE = r"SEModule"
+
+
+class POSIXUserEntry(SuccessEntry):
+ """ POSIX user """
+ uid = models.IntegerField(null=True)
+ current_uid = models.IntegerField(null=True)
+ group = models.CharField(max_length=64)
+ current_group = models.CharField(max_length=64, null=True)
+ gecos = models.CharField(max_length=1024)
+ current_gecos = models.CharField(max_length=1024, null=True)
+ home = models.CharField(max_length=1024)
+ current_home = models.CharField(max_length=1024, null=True)
+ shell = models.CharField(max_length=1024, default='/bin/bash')
+ current_shell = models.CharField(max_length=1024, null=True)
+
+ ENTRY_TYPE = r"POSIXUser"
+
+
+class POSIXGroupEntry(SuccessEntry):
+ """ POSIX group """
+ gid = models.IntegerField(null=True)
+ current_gid = models.IntegerField(null=True)
+
+ ENTRY_TYPE = r"POSIXGroup"
+
+
class PackageEntry(SuccessEntry):
""" The new model for package information """
@@ -455,7 +604,7 @@ class PackageEntry(SuccessEntry):
verification_details = models.TextField(default="")
ENTRY_TYPE = r"Package"
- #TODO - prune
+ # TODO - prune
def version_problem(self):
"""Check for a version problem."""
@@ -612,3 +761,7 @@ class ServiceEntry(SuccessEntry):
return rv
+ENTRY_TYPES = (ActionEntry, PackageEntry, PathEntry, ServiceEntry,
+ SEBooleanEntry, SEPortEntry, SEFcontextEntry, SENodeEntry,
+ SELoginEntry, SEUserEntry, SEInterfaceEntry, SEPermissiveEntry,
+ SEModuleEntry)
diff --git a/src/lib/Bcfg2/Reporting/templates/base.html b/src/lib/Bcfg2/Reporting/templates/base.html
index 533dcc79e..c73339911 100644
--- a/src/lib/Bcfg2/Reporting/templates/base.html
+++ b/src/lib/Bcfg2/Reporting/templates/base.html
@@ -88,7 +88,7 @@
<div style='clear:both'></div>
</div><!-- document -->
<div id="footer">
- <span>Bcfg2 Version 1.3.0rc2</span>
+ <span>Bcfg2 Version 1.3.1</span>
</div>
<div id="calendar_div" style='position:absolute; visibility:hidden; background-color:white; layer-background-color:white;'></div>
diff --git a/src/lib/Bcfg2/Reporting/templates/config_items/item.html b/src/lib/Bcfg2/Reporting/templates/config_items/item.html
index 737760252..259414399 100644
--- a/src/lib/Bcfg2/Reporting/templates/config_items/item.html
+++ b/src/lib/Bcfg2/Reporting/templates/config_items/item.html
@@ -45,36 +45,47 @@ div.entry_list h3 {
{% endif %}
{# Really need a better test here #}
-{% if item.mdoe_problem or item.status_problem or item.linkentry.link_problem or item.version_problem %}
+{% if item.mode_problem or item.status_problem or item.linkentry.link_problem or item.version_problem %}
<table class='entry_list'>
<tr id='table_list_header'>
<td style='text-align: right;'>Problem Type</td><td>Expected</td><td style='border-bottom: 1px solid #98DBCC;'>Found</td></tr>
{% if item.mode_problem %}
{% if item.current_perms.owner %}
- <tr><td style='text-align: right'><b>Owner</b></td><td>{{item.target_perms.owner}}</td>
+ <tr><td style='text-align: right'><b>Owner</b></td>
+ <td>{{item.target_perms.owner}}</td>
<td>{{item.current_perms.owner}}</td></tr>
{% endif %}
{% if item.current_perms.group %}
- <tr><td style='text-align: right'><b>Group</b></td><td>{{item.target_perms.group}}</td>
+ <tr><td style='text-align: right'><b>Group</b></td>
+ <td>{{item.target_perms.group}}</td>
<td>{{item.current_perms.group}}</td></tr>
{% endif %}
{% if item.current_perms.mode%}
- <tr><td style='text-align: right'><b>Mode</b></td><td>{{item.target_perms.mode}}</td>
+ <tr><td style='text-align: right'><b>Permissions</b>
+ </td><td>{{item.target_perms.mode}}</td>
<td>{{item.current_perms.mode}}</td></tr>
{% endif %}
{% endif %}
{% if item.status_problem %}
- <tr><td style='text-align: right'><b>Status</b></td><td>{{item.target_status}}</td>
- <td>{{item.current_status}}</td></tr>
+ <tr><td style='text-align: right'><b>Status</b></td>
+ <td>{{item.target_status}}</td>
+ <td>{{item.current_status}}</td></tr>
{% endif %}
{% if item.linkentry.link_problem %}
- <tr><td style='text-align: right'><b>{{item.get_path_type_display}}</b></td><td>{{item.linkentry.target_path}}</td>
- <td>{{item.linkentry.current_path}}</td></tr>
+ <tr><td style='text-align: right'><b>{{item.get_path_type_display}}</b></td>
+ <td>{{item.linkentry.target_path}}</td>
+ <td>{{item.linkentry.current_path}}</td></tr>
{% endif %}
{% if item.version_problem %}
- <tr><td style='text-align: right'><b>Package Version</b></td><td>{{item.target_version|cut:"("|cut:")"}}</td>
+ <tr><td style='text-align: right'><b>Package Version</b></td>
+ <td>{{item.target_version|cut:"("|cut:")"}}</td>
<td>{{item.current_version|cut:"("|cut:")"}}</td></tr>
{% endif %}
+ {% if item.selinuxtype_problem %}
+ <tr><td style='text-align: right'><b>SELinux Type</b></td>
+ <td>{{item.selinuxtype}}</td>
+ <td>{{item.current_selinuxtype}}</td></tr>
+ {% endif %}
</table>
{% endif %}
@@ -92,7 +103,7 @@ div.entry_list h3 {
{{ item.details|syntaxhilight }}
</div>
{% else %}
- {{ item.details }}
+ {{ item.details }}
{% endif %}
</div>
{% endif %}
diff --git a/src/lib/Bcfg2/Reporting/templates/widgets/filter_bar.html b/src/lib/Bcfg2/Reporting/templates/widgets/filter_bar.html
index 759415507..bb4f650d1 100644
--- a/src/lib/Bcfg2/Reporting/templates/widgets/filter_bar.html
+++ b/src/lib/Bcfg2/Reporting/templates/widgets/filter_bar.html
@@ -16,7 +16,7 @@
<label for="id_group">Group filter:</label>
<select id="id_group" name="group" onchange="javascript:url=document.forms['filter_form'].group.value; if(url) { location.href=url }">
{% for group, group_url, selected in groups %}
- <option label="{{group}}" value="{{group_url}}" {% if selected %}selected {% endif %}/>
+ <option value="{{group_url}}" {% if selected %}selected {% endif %}>{{group}}</option>
{% endfor %}
</select>
{% endif %}
diff --git a/src/lib/Bcfg2/Reporting/views.py b/src/lib/Bcfg2/Reporting/views.py
index 0341a18af..6cba7bf8c 100644
--- a/src/lib/Bcfg2/Reporting/views.py
+++ b/src/lib/Bcfg2/Reporting/views.py
@@ -161,7 +161,7 @@ def config_item(request, pk, entry_type, interaction=None):
ts_end = ts_start + timedelta(days=1)
associated_list = item.interaction_set.select_related('client').filter(\
timestamp__gte=ts_start, timestamp__lt=ts_end)
-
+
if item.is_failure():
template = 'config_items/item-failure.html'
else:
@@ -184,7 +184,7 @@ def config_item_list(request, item_state, timestamp=None, **kwargs):
current_clients = [q['id'] for q in _handle_filters(current_clients, **kwargs).values('id')]
lists = []
- for etype in ActionEntry, PackageEntry, PathEntry, ServiceEntry:
+ for etype in ENTRY_TYPES:
ldata = etype.objects.filter(state=state, interaction__in=current_clients)\
.annotate(num_entries=Count('id')).select_related('linkentry', 'target_perms', 'current_perms')
if len(ldata) > 0:
@@ -218,7 +218,7 @@ def entry_status(request, entry_type, pk, timestamp=None, **kwargs):
if it.pk not in seen:
items.append((it, it.interaction_set.filter(pk__in=current_clients).order_by('client__name').select_related('client')))
seen.append(it.pk)
-
+
return render_to_response('config_items/entry_status.html',
{'entry': item,
'items': items,
@@ -254,8 +254,8 @@ def common_problems(request, timestamp=None, threshold=None, group=None):
else:
current_clients = Interaction.objects.recent_ids(timestamp)
lists = []
- for etype in ActionEntry, PackageEntry, PathEntry, ServiceEntry:
- ldata = etype.objects.exclude(state=TYPE_GOOD).filter(
+ for etype in ENTRY_TYPES:
+ ldata = etype.objects.exclude(state=TYPE_GOOD).filter(
interaction__in=current_clients).annotate(num_entries=Count('id')).filter(num_entries__gte=threshold)\
.order_by('-num_entries', 'name')
if len(ldata) > 0:
@@ -315,7 +315,8 @@ def client_detailed_list(request, timestamp=None, **kwargs):
kwargs['orderby'] = "client__name"
kwargs['sort'] = "client"
- kwargs['interaction_base'] = Interaction.objects.recent(timestamp).select_related()
+ kwargs['interaction_base'] = \
+ Interaction.objects.recent(timestamp).select_related()
kwargs['page_limit'] = 0
return render_history_view(request, 'clients/detailed-list.html', **kwargs)
@@ -330,16 +331,18 @@ def client_detail(request, hostname=None, pk=None):
inter = client.interactions.get(pk=pk)
maxdate = inter.timestamp
- etypes = { TYPE_BAD: 'bad', TYPE_MODIFIED: 'modified', TYPE_EXTRA: 'extra' }
+ etypes = {TYPE_BAD: 'bad',
+ TYPE_MODIFIED: 'modified',
+ TYPE_EXTRA: 'extra'}
edict = dict()
for label in etypes.values():
edict[label] = []
- for ekind in ('actions', 'packages', 'paths', 'services'):
+ for ekind in inter.entry_types:
for ent in getattr(inter, ekind).all():
edict[etypes[ent.state]].append(ent)
context['entry_types'] = edict
- context['interaction']=inter
+ context['interaction'] = inter
return render_history_view(request, 'clients/detail.html', page_limit=5,
client=client, maxdate=maxdate, context=context)
@@ -356,7 +359,8 @@ def client_manage(request):
client.expiration = datetime.now()
client.save()
message = "Expiration for %s set to %s." % \
- (client_name, client.expiration.strftime("%Y-%m-%d %H:%M:%S"))
+ (client_name,
+ client.expiration.strftime("%Y-%m-%d %H:%M:%S"))
elif client_action == 'unexpire':
client.expiration = None
client.save()
diff --git a/src/lib/Bcfg2/Server/Admin/Init.py b/src/lib/Bcfg2/Server/Admin/Init.py
index 724da124b..884405786 100644
--- a/src/lib/Bcfg2/Server/Admin/Init.py
+++ b/src/lib/Bcfg2/Server/Admin/Init.py
@@ -12,6 +12,7 @@ from Bcfg2.Utils import Executor
import Bcfg2.Server.Admin
import Bcfg2.Server.Plugin
import Bcfg2.Options
+import Bcfg2.Server.Plugins.Metadata
from Bcfg2.Compat import input # pylint: disable=W0622
# default config file
@@ -171,8 +172,6 @@ class Init(Bcfg2.Server.Admin.Mode):
self.data['certpath'] = os.path.join(basepath, 'bcfg2.crt')
def __call__(self, args):
- Bcfg2.Server.Admin.Mode.__call__(self, args)
-
# Parse options
setup = Bcfg2.Options.get_option_parser()
setup.add_options(dict(configfile=Bcfg2.Options.CFILE,
@@ -218,7 +217,7 @@ class Init(Bcfg2.Server.Admin.Mode):
"""Ask for the repository path."""
while True:
newrepo = safe_input("Location of Bcfg2 repository [%s]: " %
- self.data['repopath'])
+ self.data['repopath'])
if newrepo != '':
self.data['repopath'] = os.path.abspath(newrepo)
if os.path.isdir(self.data['repopath']):
@@ -296,7 +295,7 @@ class Init(Bcfg2.Server.Admin.Mode):
"created [%s]: " % self.data['keypath'])
if keypath:
self.data['keypath'] = keypath
- certpath = safe_input("Path where Bcfg2 server cert will be created"
+ certpath = safe_input("Path where Bcfg2 server cert will be created "
"[%s]: " % self.data['certpath'])
if certpath:
self.data['certpath'] = certpath
@@ -324,6 +323,16 @@ class Init(Bcfg2.Server.Admin.Mode):
def init_repo(self):
"""Setup a new repo and create the content of the
configuration file."""
+ # Create the repository
+ path = os.path.join(self.data['repopath'], 'etc')
+ try:
+ os.makedirs(path)
+ self._init_plugins()
+ print("Repository created successfuly in %s" %
+ self.data['repopath'])
+ except OSError:
+ print("Failed to create %s." % path)
+
confdata = CONFIG % (self.data['repopath'],
','.join(self.plugins),
self.data['sendmail'],
@@ -339,13 +348,3 @@ class Init(Bcfg2.Server.Admin.Mode):
create_key(self.data['shostname'], self.data['keypath'],
self.data['certpath'], self.data['country'],
self.data['state'], self.data['location'])
-
- # Create the repository
- path = os.path.join(self.data['repopath'], 'etc')
- try:
- os.makedirs(path)
- self._init_plugins()
- print("Repository created successfuly in %s" %
- self.data['repopath'])
- except OSError:
- print("Failed to create %s." % path)
diff --git a/src/lib/Bcfg2/Server/Core.py b/src/lib/Bcfg2/Server/Core.py
index 07f9e0588..c69e8b055 100644
--- a/src/lib/Bcfg2/Server/Core.py
+++ b/src/lib/Bcfg2/Server/Core.py
@@ -4,8 +4,9 @@ implementations inherit from. """
import os
import sys
import time
-import select
import atexit
+import select
+import signal
import logging
import inspect
import threading
@@ -119,13 +120,36 @@ class BaseCore(object):
#: A :class:`logging.Logger` object for use by the core
self.logger = logging.getLogger('bcfg2-server')
+ #: Log levels for the various logging handlers with debug True
+ #: and False. Each loglevel dict is a dict of ``logger name
+ #: => log level``; the logger names are set in
+ #: :mod:`Bcfg2.Logger`. The logger name ``default`` is
+ #: special, and will be used for any log handlers whose name
+ #: does not appear elsewhere in the dict. At a minimum,
+ #: ``default`` must be provided.
+ self._loglevels = {True: dict(default=logging.DEBUG),
+ False: dict(console=logging.INFO,
+ default=level)}
+
+ #: Used to keep track of the current debug state of the core.
+ self.debug_flag = False
+
+ # enable debugging on the core now. debugging is enabled on
+ # everything else later
+ if self.setup['debug']:
+ self.set_core_debug(None, setup['debug'])
+
if 'ignore' not in self.setup:
self.setup.add_option('ignore', SERVER_FAM_IGNORE)
self.setup.reparse()
+
famargs = dict(filemonitor=self.setup['filemonitor'],
debug=self.setup['debug'],
ignore=self.setup['ignore'])
- if self.setup['filemonitor'] not in Bcfg2.Server.FileMonitor.available:
+ try:
+ filemonitor = \
+ Bcfg2.Server.FileMonitor.available[setup['filemonitor']]
+ except KeyError:
self.logger.error("File monitor driver %s not available; "
"forcing to default" % self.setup['filemonitor'])
famargs['filemonitor'] = 'default'
@@ -281,6 +305,14 @@ class BaseCore(object):
#: The CA that signed the server cert
self.ca = self.setup['ca']
+ def hdlr(sig, frame): # pylint: disable=W0613
+ """ Handle SIGINT/Ctrl-C by shutting down the core and exiting
+ properly. """
+ self.shutdown()
+ os._exit(1) # pylint: disable=W0212
+
+ signal.signal(signal.SIGINT, hdlr)
+
#: The FAM :class:`threading.Thread`,
#: :func:`_file_monitor_thread`
self.fam_thread = \
@@ -295,6 +327,10 @@ class BaseCore(object):
#: metadata
self.metadata_cache = Cache()
+ if self.debug_flag:
+ # enable debugging on everything else.
+ self.plugins[plugin].set_debug(self.debug_flag)
+
def plugins_by_type(self, base_cls):
""" Return a list of loaded plugins that match the passed type.
@@ -392,6 +428,7 @@ class BaseCore(object):
def shutdown(self):
""" Perform plugin and FAM shutdown tasks. """
+ self.logger.debug("Shutting down core...")
if not self.terminate.isSet():
self.terminate.set()
self.fam.shutdown()
@@ -427,6 +464,8 @@ class BaseCore(object):
hook.
:type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
"""
+ self.logger.debug("Running %s hooks for %s" % (hook,
+ metadata.hostname))
start = time.time()
try:
for plugin in \
@@ -460,6 +499,7 @@ class BaseCore(object):
client
:type data: list of lxml.etree._Element objects
"""
+ self.logger.debug("Validating structures for %s" % metadata.hostname)
for plugin in \
self.plugins_by_type(Bcfg2.Server.Plugin.StructureValidator):
try:
@@ -486,6 +526,7 @@ class BaseCore(object):
client
:type data: list of lxml.etree._Element objects
"""
+ self.logger.debug("Validating goals for %s" % metadata.hostname)
for plugin in self.plugins_by_type(Bcfg2.Server.Plugin.GoalValidator):
try:
plugin.validate_goals(metadata, data)
@@ -506,6 +547,7 @@ class BaseCore(object):
:type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
:returns: list of :class:`lxml.etree._Element` objects
"""
+ self.logger.debug("Getting structures for %s" % metadata.hostname)
structures = list(chain(*[struct.BuildStructures(metadata)
for struct in self.structures]))
sbundles = [b.get('name') for b in structures if b.tag == 'Bundle']
@@ -528,6 +570,7 @@ class BaseCore(object):
structures to. Modified in-place.
:type config: lxml.etree._Element
"""
+ self.logger.debug("Binding structures for %s" % metadata.hostname)
for astruct in structures:
try:
self.BindStructure(astruct, metadata)
@@ -544,6 +587,9 @@ class BaseCore(object):
:param metadata: Client metadata to bind structure for
:type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
"""
+ self.logger.debug("Binding structure %s for %s" %
+ (structure.get("name", "unknown"),
+ metadata.hostname))
for entry in structure.getchildren():
if entry.tag.startswith("Bound"):
entry.tag = entry.tag[5:]
@@ -619,6 +665,7 @@ class BaseCore(object):
:type client: string
:returns: :class:`lxml.etree._Element` - A complete Bcfg2
configuration document """
+ self.logger.debug("Building configuration for %s" % client)
start = time.time()
config = lxml.etree.Element("Configuration", version='2.0',
revision=self.revision)
@@ -718,6 +765,12 @@ class BaseCore(object):
self.shutdown()
raise
+ if self.setup['fam_blocking']:
+ time.sleep(1)
+ while self.fam.pending() != 0:
+ time.sleep(1)
+
+ self.set_debug(None, self.debug_flag)
self._block()
def _daemonize(self):
@@ -746,6 +799,7 @@ class BaseCore(object):
:type mode: string
:returns: list of Decision tuples ``(<entry tag>, <entry name>)``
"""
+ self.logger.debug("Getting decision list for %s" % metadata.hostname)
result = []
for plugin in self.plugins_by_type(Bcfg2.Server.Plugin.Decision):
try:
@@ -816,6 +870,7 @@ class BaseCore(object):
else:
imd = self.metadata_cache.get(client_name, None)
if not imd:
+ self.logger.debug("Building metadata for %s" % client_name)
imd = self.metadata.get_initial_metadata(client_name)
for conn in self.connectors:
grps = conn.get_additional_groups(imd)
@@ -837,6 +892,7 @@ class BaseCore(object):
:param statistics: The statistics document to process
:type statistics: lxml.etree._Element
"""
+ self.logger.debug("Processing statistics for %s" % client_name)
meta = self.build_metadata(client_name)
state = statistics.find(".//Statistics")
if state.get('version') >= '2.0':
@@ -907,10 +963,12 @@ class BaseCore(object):
def _get_rmi(self):
""" Get a list of RMI calls exposed by plugins """
rmi = dict()
- if self.plugins:
- for pname, pinst in list(self.plugins.items()):
- for mname in pinst.__rmi__:
- rmi["%s.%s" % (pname, mname)] = getattr(pinst, mname)
+ for pname, pinst in list(self.plugins.items()):
+ for mname in pinst.__rmi__:
+ rmi["%s.%s" % (pname, mname)] = getattr(pinst, mname)
+ famname = self.fam.__class__.__name__
+ for mname in self.fam.__rmi__:
+ rmi["%s.%s" % (famname, mname)] = getattr(self.fam, mname)
return rmi
def _resolve_exposed_method(self, method_name):
@@ -964,6 +1022,7 @@ class BaseCore(object):
return func.__doc__
@exposed
+ @track_statistics()
def DeclareVersion(self, address, version):
""" Declare the client version.
@@ -974,7 +1033,9 @@ class BaseCore(object):
:returns: bool - True on success
:raises: :exc:`xmlrpclib.Fault`
"""
- client = self.resolve_client(address)[0]
+ client = self.resolve_client(address, metadata=False)[0]
+ self.logger.debug("%s is running Bcfg2 client version %s" % (client,
+ version))
try:
self.metadata.set_version(client, version)
except (Bcfg2.Server.Plugin.MetadataConsistencyError,
@@ -996,6 +1057,7 @@ class BaseCore(object):
"""
resp = lxml.etree.Element('probes')
client, metadata = self.resolve_client(address, cleanup_cache=True)
+ self.logger.debug("Getting probes for %s" % client)
try:
for plugin in self.plugins_by_type(Bcfg2.Server.Plugin.Probing):
for probe in plugin.GetProbes(metadata):
@@ -1017,6 +1079,7 @@ class BaseCore(object):
:raises: :exc:`xmlrpclib.Fault`
"""
client, metadata = self.resolve_client(address)
+ self.logger.debug("Receiving probe data from %s" % client)
if self.metadata_cache_mode == 'cautious':
# clear the metadata cache right after building the
# metadata object; that way the cache is cleared for any
@@ -1063,6 +1126,7 @@ class BaseCore(object):
:raises: :exc:`xmlrpclib.Fault`
"""
client = self.resolve_client(address, metadata=False)[0]
+ self.logger.debug("%s sets its profile to %s" % (client, profile))
try:
self.metadata.set_profile(client, profile, address)
except (Bcfg2.Server.Plugin.MetadataConsistencyError,
@@ -1171,22 +1235,35 @@ class BaseCore(object):
:type address: tuple
:returns: bool - The new debug state of the FAM
"""
- for plugin in self.plugins.values():
- plugin.toggle_debug()
- return self.toggle_fam_debug(address)
+ return self.set_debug(address, not self.debug_flag)
@exposed
- def toggle_fam_debug(self, _):
+ def toggle_core_debug(self, address):
+ """ Toggle debug status of the server core
+
+ :param address: Client (address, hostname) pair
+ :type address: tuple
+ :returns: bool - The new debug state of the FAM
+ """
+ return self.set_core_debug(address, not self.debug_flag)
+
+ @exposed
+ def toggle_fam_debug(self, address):
""" Toggle debug status of the FAM
:returns: bool - The new debug state of the FAM
"""
- return self.fam.toggle_debug()
+ self.logger.warning("Deprecated method set_fam_debug called by %s" %
+ address[0])
+ return "This method is deprecated and will be removed in a future " + \
+ "release\n%s" % self.fam.toggle_debug()
@exposed
def set_debug(self, address, debug):
""" Explicitly set debug status of the FAM and all plugins
+ :param address: Client (address, hostname) pair
+ :type address: tuple
:param debug: The new debug status. This can either be a
boolean, or a string describing the state (e.g.,
"true" or "false"; case-insensitive)
@@ -1197,10 +1274,33 @@ class BaseCore(object):
debug = debug.lower() == "true"
for plugin in self.plugins.values():
plugin.set_debug(debug)
- return self.set_fam_debug(address, debug)
+ rv = self.set_core_debug(address, debug)
+ return self.fam.set_debug(debug) and rv
+
+ @exposed
+ def set_core_debug(self, _, debug):
+ """ Explicity set debug status of the server core
+
+ :param debug: The new debug status. This can either be a
+ boolean, or a string describing the state (e.g.,
+ "true" or "false"; case-insensitive)
+ :type debug: bool or string
+ :returns: bool - The new debug state of the FAM
+ """
+ if debug not in [True, False]:
+ debug = debug.lower() == "true"
+ self.debug_flag = debug
+ self.logger.info("Core: debug = %s" % debug)
+ levels = self._loglevels[self.debug_flag]
+ for handler in logging.root.handlers:
+ level = levels.get(handler.name, levels['default'])
+ self.logger.debug("Setting %s log handler to %s" %
+ (handler.name, logging.getLevelName(level)))
+ handler.setLevel(level)
+ return self.debug_flag
@exposed
- def set_fam_debug(self, _, debug):
+ def set_fam_debug(self, address, debug):
""" Explicitly set debug status of the FAM
:param debug: The new debug status of the FAM. This can
@@ -1212,4 +1312,7 @@ class BaseCore(object):
"""
if debug not in [True, False]:
debug = debug.lower() == "true"
- return self.fam.set_debug(debug)
+ self.logger.warning("Deprecated method set_fam_debug called by %s" %
+ address[0])
+ return "This method is deprecated and will be removed in a future " + \
+ "release\n%s" % self.fam.set_debug(debug)
diff --git a/src/lib/Bcfg2/Server/FileMonitor/Inotify.py b/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
index 178a47b1a..cdd52dbb9 100644
--- a/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
+++ b/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
@@ -2,6 +2,7 @@
support. """
import os
+import errno
import logging
import pyinotify
from Bcfg2.Compat import reduce # pylint: disable=W0622
@@ -15,6 +16,8 @@ class Inotify(Pseudo, pyinotify.ProcessEvent):
""" File monitor backend with `inotify
<http://inotify.aiken.cz/>`_ support. """
+ __rmi__ = Pseudo.__rmi__ + ["list_watches", "list_paths"]
+
#: Inotify is the best FAM backend, so it gets a very high
#: priority
__priority__ = 99
@@ -182,6 +185,9 @@ class Inotify(Pseudo, pyinotify.ProcessEvent):
try:
watchdir = self.watches_by_path[watch_path]
except KeyError:
+ if not os.path.exists(watch_path):
+ raise OSError(errno.ENOENT,
+ "No such file or directory: '%s'" % path)
watchdir = self.watchmgr.add_watch(watch_path, self.mask,
quiet=False)[watch_path]
self.watches_by_path[watch_path] = watchdir
@@ -211,3 +217,20 @@ class Inotify(Pseudo, pyinotify.ProcessEvent):
if self.notifier:
self.notifier.stop()
shutdown.__doc__ = Pseudo.shutdown.__doc__
+
+ def list_watches(self):
+ """ XML-RPC that returns a list of current inotify watches for
+ debugging purposes. """
+ return list(self.watches_by_path.keys())
+
+ def list_paths(self):
+ """ XML-RPC that returns a list of paths that are handled for
+ debugging purposes. Because inotify doesn't like watching
+ files, but prefers to watch directories, this will be
+ different from
+ :func:`Bcfg2.Server.FileMonitor.Inotify.Inotify.ListWatches`. For
+ instance, if a plugin adds a monitor to
+ ``/var/lib/bcfg2/Plugin/foo.xml``, :func:`ListPaths` will
+ return ``/var/lib/bcfg2/Plugin/foo.xml``, while
+ :func:`ListWatches` will return ``/var/lib/bcfg2/Plugin``. """
+ return list(self.handles.keys())
diff --git a/src/lib/Bcfg2/Server/FileMonitor/__init__.py b/src/lib/Bcfg2/Server/FileMonitor/__init__.py
index d77f21b93..522ddb705 100644
--- a/src/lib/Bcfg2/Server/FileMonitor/__init__.py
+++ b/src/lib/Bcfg2/Server/FileMonitor/__init__.py
@@ -116,6 +116,9 @@ class FileMonitor(Debuggable):
#: should have higher priorities.
__priority__ = -1
+ #: List of names of methods to be exposed as XML-RPC functions
+ __rmi__ = Debuggable.__rmi__ + ["list_event_handlers"]
+
def __init__(self, ignore=None, debug=False):
"""
:param ignore: A list of filename globs describing events that
@@ -288,6 +291,8 @@ class FileMonitor(Debuggable):
def shutdown(self):
""" Handle any tasks required to shut down the monitor. """
+ self.debug_log("Shutting down %s file monitor" %
+ self.__class__.__name__)
self.started = False
def AddMonitor(self, path, obj, handleID=None):
@@ -310,6 +315,15 @@ class FileMonitor(Debuggable):
"""
raise NotImplementedError
+ def list_event_handlers(self):
+ """ XML-RPC that returns
+ :attr:`Bcfg2.Server.FileMonitor.FileMonitor.handles` for
+ debugging purposes. """
+ rv = dict()
+ for watch, handler in self.handles.items():
+ rv[watch] = getattr(handler, "name", handler.__class__.__name__)
+ return rv
+
#: A module-level FAM object that all plugins, etc., can use. This
#: should not be used directly, but retrieved via :func:`get_fam`.
diff --git a/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py b/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py
index 60525d5a1..497e8fac6 100644
--- a/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py
+++ b/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py
@@ -37,7 +37,7 @@ def is_octal_mode(val):
def is_username(val):
""" Return True if val is a string giving either a positive
integer uid, or a valid Unix username """
- return re.match(r'^([a-z]\w{0,30}|\d+)$', val)
+ return re.match(r'^([A-z][-_A-z0-9]{0,30}|\d+)$', val)
def is_device_mode(val):
diff --git a/src/lib/Bcfg2/Server/Plugin/base.py b/src/lib/Bcfg2/Server/Plugin/base.py
index 25a687874..f7bc08717 100644
--- a/src/lib/Bcfg2/Server/Plugin/base.py
+++ b/src/lib/Bcfg2/Server/Plugin/base.py
@@ -34,8 +34,8 @@ class Debuggable(object):
:returns: bool - The new value of the debug flag
"""
self.debug_flag = debug
- self.debug_log("%s: debug_flag = %s" % (self.__class__.__name__,
- self.debug_flag),
+ self.debug_log("%s: debug = %s" % (self.__class__.__name__,
+ self.debug_flag),
flag=True)
return debug
@@ -122,6 +122,7 @@ class Plugin(Debuggable):
""" Perform shutdown tasks for the plugin
:returns: None """
+ self.debug_log("Shutting down %s plugin" % self.name)
self.running = False
def __str__(self):
diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py
index 187c594fd..ded7dd8dc 100644
--- a/src/lib/Bcfg2/Server/Plugin/helpers.py
+++ b/src/lib/Bcfg2/Server/Plugin/helpers.py
@@ -478,6 +478,7 @@ class XMLFileBacked(FileBacked):
def Index(self):
self.xdata = lxml.etree.XML(self.data, base_url=self.name,
parser=Bcfg2.Server.XMLParser)
+ self.extras = []
self._follow_xincludes()
if self.extras:
try:
diff --git a/src/lib/Bcfg2/Server/Plugin/interfaces.py b/src/lib/Bcfg2/Server/Plugin/interfaces.py
index 3ef29775d..11a61ff9c 100644
--- a/src/lib/Bcfg2/Server/Plugin/interfaces.py
+++ b/src/lib/Bcfg2/Server/Plugin/interfaces.py
@@ -313,6 +313,7 @@ class Threaded(object):
"""
raise NotImplementedError
+
class ThreadedStatistics(Statistics, Threaded, threading.Thread):
""" ThreadedStatistics plugins process client statistics in a
separate thread. """
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py
index 5f10879be..b3781e299 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py
@@ -77,6 +77,10 @@ class CfgGenshiGenerator(CfgGenerator):
__init__.__doc__ = CfgGenerator.__init__.__doc__
def get_data(self, entry, metadata):
+ if self.template is None:
+ raise PluginExecutionError("Failed to load template %s" %
+ self.name)
+
fname = entry.get('realname', entry.get('name'))
stream = self.template.generate(
name=fname,
diff --git a/src/lib/Bcfg2/Server/Plugins/DBStats.py b/src/lib/Bcfg2/Server/Plugins/DBStats.py
index e0794f019..e6ef50fa1 100644
--- a/src/lib/Bcfg2/Server/Plugins/DBStats.py
+++ b/src/lib/Bcfg2/Server/Plugins/DBStats.py
@@ -9,7 +9,6 @@ class DBStats(Bcfg2.Server.Plugin.Plugin):
def __init__(self, core, datastore):
Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
self.logger.error("DBStats has been replaced with Reporting")
- self.logger.error("DBStats: Be sure to migrate your data "\
- "before running the report collector")
+ self.logger.error("DBStats: Be sure to migrate your data "
+ "before running the report collector")
raise Bcfg2.Server.Plugin.PluginInitError
-
diff --git a/src/lib/Bcfg2/Server/Plugins/Metadata.py b/src/lib/Bcfg2/Server/Plugins/Metadata.py
index b053e65d3..7f8db7b6d 100644
--- a/src/lib/Bcfg2/Server/Plugins/Metadata.py
+++ b/src/lib/Bcfg2/Server/Plugins/Metadata.py
@@ -138,7 +138,7 @@ class XMLMetadataConfig(Bcfg2.Server.Plugin.XMLFileBacked):
self.logger.error('Failed to parse %s' % self.basefile)
return
self.extras = []
- self.basedata = copy.copy(xdata)
+ self.basedata = copy.deepcopy(xdata)
self._follow_xincludes(xdata=xdata)
if self.extras:
try:
@@ -263,31 +263,63 @@ class ClientMetadata(object):
# pylint: disable=R0913
def __init__(self, client, profile, groups, bundles, aliases, addresses,
categories, uuid, password, version, query):
+ #: The client hostname (as a string)
self.hostname = client
+
+ #: The client profile (as a string)
self.profile = profile
+
+ #: The set of all bundles this client gets
self.bundles = bundles
+
+ #: A list of all client aliases
self.aliases = aliases
+
+ #: A list of all addresses this client is known by
self.addresses = addresses
+
+ #: A list of groups this client is a member of
self.groups = groups
+
+ #: A dict of categories of this client's groups. Keys are
+ #: category names, values are corresponding group names.
self.categories = categories
+
+ #: The UUID identifier for this client
self.uuid = uuid
+
+ #: The Bcfg2 password for this client
self.password = password
+
+ #: Connector plugins known to this client
self.connectors = []
+
+ #: The version of the Bcfg2 client this client is running, as
+ #: a string
self.version = version
try:
+ #: The version of the Bcfg2 client this client is running,
+ #: as a :class:`Bcfg2.version.Bcfg2VersionInfo` object.
self.version_info = Bcfg2VersionInfo(version)
except (ValueError, AttributeError):
self.version_info = None
+
+ #: A :class:`Bcfg2.Server.Plugins.Metadata.MetadataQuery`
+ #: object for this client.
self.query = query
# pylint: enable=R0913
def inGroup(self, group):
- """Test to see if client is a member of group."""
+ """Test to see if client is a member of group.
+
+ :returns: bool """
return group in self.groups
def group_in_category(self, category):
- """ return the group in the given category that the client is
- a member of, or the empty string """
+ """ Return the group in the given category that the client is
+ a member of, or an empty string.
+
+ :returns: string """
for grp in self.query.all_groups_in_category(category):
if grp in self.groups:
return grp
@@ -295,17 +327,59 @@ class ClientMetadata(object):
class MetadataQuery(object):
- """ object supplied to client metadata to allow client metadata
- objects to query metadata without being able to modify it """
+ """ This class provides query methods for the metadata of all
+ clients known to the Bcfg2 server, without being able to modify
+ that data.
+
+ Note that ``*by_groups()`` and ``*by_profiles()`` behave
+ differently; for a client to be included in the return value of a
+ ``*by_groups()`` method, it must be a member of *all* groups
+ listed in the argument; for a client to be included in the return
+ value of a ``*by_profiles()`` method, it must have *any* group
+ listed as its profile group. """
def __init__(self, by_name, get_clients, by_groups, by_profiles,
all_groups, all_groups_in_category):
- # resolver is set later
+ #: Get :class:`Bcfg2.Server.Plugins.Metadata.ClientMetadata`
+ #: object for the given hostname.
+ #:
+ #: :returns: Bcfg2.Server.Plugins.Metadata.ClientMetadata
self.by_name = by_name
+
+ #: Get a list of hostnames of clients that are in all given
+ #: groups.
+ #:
+ #: :param groups: The groups to check clients for membership in
+ #: :type groups: list
+ #:
+ #: :returns: list of strings
self.names_by_groups = self._warn_string(by_groups)
+
+ #: Get a list of hostnames of clients whose profile matches
+ #: any given profile group.
+ #:
+ #: :param profiles: The profiles to check clients for
+ #: membership in.
+ #: :type profiles: list
+ #: :returns: list of strings
self.names_by_profiles = self._warn_string(by_profiles)
+
+ #: Get all known client hostnames.
+ #:
+ #: :returns: list of strings
self.all_clients = get_clients
+
+ #: Get all known group names.
+ #:
+ #: :returns: list of strings
self.all_groups = all_groups
+
+ #: Get the names of all groups in the given category.
+ #:
+ #: :param category: The category to query for groups that
+ #: belong to it.
+ #: :type category: string
+ #: :returns: list of strings
self.all_groups_in_category = all_groups_in_category
def _warn_string(self, func):
@@ -326,22 +400,41 @@ class MetadataQuery(object):
return inner
def by_groups(self, groups):
- """ get a list of ClientMetadata objects that are in all given
- groups """
+ """ Get a list of
+ :class:`Bcfg2.Server.Plugins.Metadata.ClientMetadata` objects
+ that are in all given groups.
+
+ :param groups: The groups to check clients for membership in.
+ :type groups: list
+ :returns: list of Bcfg2.Server.Plugins.Metadata.ClientMetadata
+ objects
+ """
# don't need to decorate this with _warn_string because
# names_by_groups is decorated
return [self.by_name(name) for name in self.names_by_groups(groups)]
def by_profiles(self, profiles):
- """ get a list of ClientMetadata objects that are in any of
- the given profiles """
+ """ Get a list of
+ :class:`Bcfg2.Server.Plugins.Metadata.ClientMetadata` objects
+ that have any of the given groups as their profile.
+
+ :param profiles: The profiles to check clients for membership
+ in.
+ :type profiles: list
+ :returns: list of Bcfg2.Server.Plugins.Metadata.ClientMetadata
+ objects
+ """
# don't need to decorate this with _warn_string because
# names_by_profiles is decorated
return [self.by_name(name)
for name in self.names_by_profiles(profiles)]
def all(self):
- """ get a list of all ClientMetadata objects """
+ """ Get a list of all
+ :class:`Bcfg2.Server.Plugins.Metadata.ClientMetadata` objects.
+
+ :returns: list of Bcfg2.Server.Plugins.Metadata.ClientMetadata
+ """
return [self.by_name(name) for name in self.all_clients()]
@@ -1255,7 +1348,7 @@ class Metadata(Bcfg2.Server.Plugin.Metadata,
def end_statistics(self, metadata):
""" Hook to toggle clients in bootstrap mode """
if self.auth.get(metadata.hostname,
- self.core.setup('authentication')) == 'bootstrap':
+ self.core.setup['authentication']) == 'bootstrap':
self.update_client(metadata.hostname, dict(auth='cert'))
def viz(self, hosts, bundles, key, only_client, colors):
diff --git a/src/lib/Bcfg2/Server/Plugins/POSIXCompat.py b/src/lib/Bcfg2/Server/Plugins/POSIXCompat.py
index 0dd42c9cb..490ee6f20 100644
--- a/src/lib/Bcfg2/Server/Plugins/POSIXCompat.py
+++ b/src/lib/Bcfg2/Server/Plugins/POSIXCompat.py
@@ -15,6 +15,11 @@ class POSIXCompat(Bcfg2.Server.Plugin.Plugin,
def validate_goals(self, metadata, goals):
"""Verify that we are generating correct old POSIX entries."""
+ if metadata.version_info and metadata.version_info > (1, 3, 0, '', 0):
+ # do not care about a client that is _any_ 1.3.0 release
+ # (including prereleases and RCs)
+ return
+
for goal in goals:
for entry in goal.getchildren():
if entry.tag == 'Path' and 'mode' in entry.keys():
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
index 3799b1723..4535fb76d 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
@@ -313,9 +313,7 @@ class YumCollection(Collection):
@property
def __package_groups__(self):
- """ YumCollections support package groups only if
- :attr:`use_yum` is True """
- return self.use_yum
+ return True
@property
def helper(self):
@@ -663,11 +661,6 @@ class YumCollection(Collection):
In this implementation the packages may be strings or tuples.
See :ref:`yum-pkg-objects` for more information. """
- if not self.use_yum:
- self.logger.warning("Packages: Package groups are not supported "
- "by Bcfg2's internal Yum dependency generator")
- return dict()
-
if not grouplist:
return dict()
@@ -679,7 +672,15 @@ class YumCollection(Collection):
ptype = "default"
gdicts.append(dict(group=group, type=ptype))
- return self.call_helper("get_groups", inputdata=gdicts)
+ if self.use_yum:
+ return self.call_helper("get_groups", inputdata=gdicts)
+ else:
+ pkgs = dict()
+ for gdict in gdicts:
+ pkgs[gdict['group']] = Collection.get_group(self,
+ gdict['group'],
+ gdict['type'])
+ return pkgs
def _element_to_pkg(self, el, name):
""" Convert a Package or Instance element to a package tuple """
@@ -975,6 +976,7 @@ class YumSource(Source):
for x in ['global'] + self.arches])
self.needed_paths = set()
self.file_to_arch = dict()
+ self.yumgroups = dict()
__init__.__doc__ = Source.__init__.__doc__
@property
@@ -992,7 +994,8 @@ class YumSource(Source):
if not self.use_yum:
cache = open(self.cachefile, 'wb')
cPickle.dump((self.packages, self.deps, self.provides,
- self.filemap, self.url_map), cache, 2)
+ self.filemap, self.url_map,
+ self.yumgroups), cache, 2)
cache.close()
def load_state(self):
@@ -1002,7 +1005,7 @@ class YumSource(Source):
if not self.use_yum:
data = open(self.cachefile)
(self.packages, self.deps, self.provides,
- self.filemap, self.url_map) = cPickle.load(data)
+ self.filemap, self.url_map, self.yumgroups) = cPickle.load(data)
@property
def urls(self):
@@ -1057,7 +1060,7 @@ class YumSource(Source):
urls = []
for elt in xdata.findall(RPO + 'data'):
- if elt.get('type') in ['filelists', 'primary']:
+ if elt.get('type') in ['filelists', 'primary', 'group']:
floc = elt.find(RPO + 'location')
fullurl = url + floc.get('href')
urls.append(fullurl)
@@ -1074,11 +1077,14 @@ class YumSource(Source):
# we have to read primary.xml first, and filelists.xml afterwards;
primaries = list()
filelists = list()
+ groups = list()
for fname in self.files:
if fname.endswith('primary.xml.gz'):
primaries.append(fname)
elif fname.endswith('filelists.xml.gz'):
filelists.append(fname)
+ elif fname.find('comps'):
+ groups.append(fname)
for fname in primaries:
farch = self.file_to_arch[fname]
@@ -1088,6 +1094,9 @@ class YumSource(Source):
farch = self.file_to_arch[fname]
fdata = lxml.etree.parse(fname).getroot()
self.parse_filelist(fdata, farch)
+ for fname in groups:
+ fdata = lxml.etree.parse(fname).getroot()
+ self.parse_group(fdata)
# merge data
sdata = list(self.packages.values())
@@ -1151,6 +1160,35 @@ class YumSource(Source):
self.provides[arch][prov] = list()
self.provides[arch][prov].append(pkgname)
+ @Bcfg2.Server.Plugin.track_statistics()
+ def parse_group(self, data):
+ """ parse comps.xml.gz data """
+ for group in data.getchildren():
+ if not group.tag.endswith('group'):
+ continue
+ try:
+ groupid = group.xpath('id')[0].text
+ self.yumgroups[groupid] = {'mandatory': list(),
+ 'default': list(),
+ 'optional': list(),
+ 'conditional': list()}
+ except IndexError:
+ continue
+ try:
+ packagelist = group.xpath('packagelist')[0]
+ except IndexError:
+ continue
+ for pkgreq in packagelist.getchildren():
+ pkgtype = pkgreq.get('type', None)
+ if pkgtype == 'mandatory':
+ self.yumgroups[groupid]['mandatory'].append(pkgreq.text)
+ elif pkgtype == 'default':
+ self.yumgroups[groupid]['default'].append(pkgreq.text)
+ elif pkgtype == 'optional':
+ self.yumgroups[groupid]['optional'].append(pkgreq.text)
+ elif pkgtype == 'conditional':
+ self.yumgroups[groupid]['conditional'].append(pkgreq.text)
+
def is_package(self, metadata, package):
arch = [a for a in self.arches if a in metadata.groups]
if not arch:
@@ -1230,3 +1268,26 @@ class YumSource(Source):
return self.pulp_id
else:
return Source.get_repo_name(self, url_map)
+
+ def get_group(self, metadata, group, ptype=None): # pylint: disable=W0613
+ """ Get the list of packages of the given type in a package
+ group.
+
+ :param group: The name of the group to query
+ :type group: string
+ :param ptype: The type of packages to get, for backends that
+ support multiple package types in package groups
+ (e.g., "recommended," "optional," etc.)
+ :type ptype: string
+ :returns: list of strings - package names
+ """
+ try:
+ yumgroup = self.yumgroups[group]
+ except KeyError:
+ return []
+ packages = yumgroup['conditional'] + yumgroup['mandatory']
+ if ptype in ['default', 'optional', 'all']:
+ packages += yumgroup['default']
+ if ptype in ['optional', 'all']:
+ packages += yumgroup['optional']
+ return packages
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
index 5d3fbae2e..2175cf0aa 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
@@ -5,6 +5,7 @@ determine the completeness of the client configuration. """
import os
import sys
import glob
+import copy
import shutil
import lxml.etree
import Bcfg2.Logger
@@ -296,20 +297,22 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
"""
if self.disableResolver:
# Config requests no resolver
+ for struct in structures:
+ for pkg in struct.xpath('//Package | //BoundPackage'):
+ if pkg.get("group"):
+ if pkg.get("type"):
+ pkg.set("choose", pkg.get("type"))
return
if collection is None:
collection = self.get_collection(metadata)
- # base is the set of initial packages -- explicitly
- # given in the specification, from expanded package groups,
- # and essential to the distribution
- base = set()
+ initial = set()
to_remove = []
groups = []
for struct in structures:
for pkg in struct.xpath('//Package | //BoundPackage'):
if pkg.get("name"):
- base.update(collection.packages_from_entry(pkg))
+ initial.update(collection.packages_from_entry(pkg))
elif pkg.get("group"):
groups.append((pkg.get("group"),
pkg.get("type")))
@@ -321,6 +324,11 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
pkg,
xml_declaration=False).decode('UTF-8'))
+ # base is the set of initial packages explicitly given in the
+ # specification, packages from expanded package groups, and
+ # packages essential to the distribution
+ base = set(initial)
+
# remove package groups
for el in to_remove:
el.getparent().remove(el)
@@ -336,7 +344,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
if unknown:
self.logger.info("Packages: Got %d unknown entries" % len(unknown))
self.logger.info("Packages: %s" % list(unknown))
- newpkgs = collection.get_new_packages(base, packages)
+ newpkgs = collection.get_new_packages(initial, packages)
self.debug_log("Packages: %d base, %d complete, %d new" %
(len(base), len(packages), len(newpkgs)))
newpkgs.sort()
@@ -514,7 +522,8 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
:return: dict of lists of ``url_map`` data
"""
collection = self.get_collection(metadata)
- return dict(sources=collection.get_additional_data())
+ return dict(sources=collection.get_additional_data(),
+ allsources=copy.deepcopy(self.sources))
def end_client_run(self, metadata):
""" Hook to clear the cache for this client in
diff --git a/src/lib/Bcfg2/Server/Plugins/Reporting.py b/src/lib/Bcfg2/Server/Plugins/Reporting.py
index d072f1a33..a6dc2c1ef 100644
--- a/src/lib/Bcfg2/Server/Plugins/Reporting.py
+++ b/src/lib/Bcfg2/Server/Plugins/Reporting.py
@@ -65,10 +65,13 @@ class Reporting(Statistics, Threaded, PullSource, Debuggable):
(self.name, traceback.format_exc().splitlines()[-1])
self.logger.error(msg)
raise PluginInitError(msg)
+ if self.debug_flag:
+ self.transport.set_debug(self.debug_flag)
def set_debug(self, debug):
rv = Debuggable.set_debug(self, debug)
- self.transport.set_debug(debug)
+ if self.transport is not None:
+ self.transport.set_debug(debug)
return rv
def process_statistics(self, client, xdata):
diff --git a/src/lib/Bcfg2/Server/SSLServer.py b/src/lib/Bcfg2/Server/SSLServer.py
index 0d1246d85..28450aa1a 100644
--- a/src/lib/Bcfg2/Server/SSLServer.py
+++ b/src/lib/Bcfg2/Server/SSLServer.py
@@ -412,12 +412,9 @@ class XMLRPCServer(SocketServer.ThreadingMixIn, SSLServer,
name = instance.name
except AttributeError:
name = "unknown"
- if hasattr(instance, 'plugins'):
- for pname, pinst in list(instance.plugins.items()):
- for mname in pinst.__rmi__:
- xmname = "%s.%s" % (pname, mname)
- fn = getattr(pinst, mname)
- self.register_function(fn, name=xmname)
+ if hasattr(instance, '_get_rmi'):
+ for fname, func in instance._get_rmi().items():
+ self.register_function(func, name=fname)
self.logger.info("serving %s at %s" % (name, self.url))
def serve_forever(self):
diff --git a/src/lib/Bcfg2/Utils.py b/src/lib/Bcfg2/Utils.py
index 29c27257a..dd76f04d3 100644
--- a/src/lib/Bcfg2/Utils.py
+++ b/src/lib/Bcfg2/Utils.py
@@ -165,23 +165,19 @@ class Executor(object):
self.logger = logging.getLogger(self.__class__.__name__)
self.timeout = timeout
- def _timeout_callback(self, proc):
- """ Get a callback (suitable for passing to
- :class:`threading.Timer`) that kills the given process.
+ def _timeout(self, proc):
+ """ A function suitable for passing to
+ :class:`threading.Timer` that kills the given process.
:param proc: The process to kill upon timeout.
:type proc: subprocess.Popen
- :returns: function """
- def _timeout():
- """ Callback that kills ``proc`` """
- if proc.poll() == None:
- try:
- proc.kill()
- self.logger.warning("Process exceeeded timeout, killing")
- except OSError:
- pass
-
- return _timeout
+ :returns: None """
+ if proc.poll() == None:
+ try:
+ proc.kill()
+ self.logger.warning("Process exceeeded timeout, killing")
+ except OSError:
+ pass
def run(self, command, inputdata=None, timeout=None, **kwargs):
""" Run a command, given as a list, optionally giving it the
@@ -213,9 +209,7 @@ class Executor(object):
if timeout is None:
timeout = self.timeout
if timeout is not None:
- timer = threading.Timer(float(timeout),
- self._timeout_callback(proc),
- [proc])
+ timer = threading.Timer(float(timeout), self._timeout, [proc])
timer.start()
try:
if inputdata:
diff --git a/src/lib/Bcfg2/settings.py b/src/lib/Bcfg2/settings.py
index d819ee534..87f2a0df0 100644
--- a/src/lib/Bcfg2/settings.py
+++ b/src/lib/Bcfg2/settings.py
@@ -32,7 +32,7 @@ TIME_ZONE = None
DEBUG = False
TEMPLATE_DEBUG = DEBUG
-MEDIA_URL = '/site_media'
+MEDIA_URL = '/site_media/'
def _default_config():
@@ -86,14 +86,6 @@ def read_config(cfile=DEFAULT_CONFIG, repo=None):
HOST=setup['db_host'],
PORT=setup['db_port'])
- if HAS_DJANGO and django.VERSION[0] == 1 and django.VERSION[1] < 2:
- DATABASE_ENGINE = setup['db_engine']
- DATABASE_NAME = DATABASES['default']['NAME']
- DATABASE_USER = DATABASES['default']['USER']
- DATABASE_PASSWORD = DATABASES['default']['PASSWORD']
- DATABASE_HOST = DATABASES['default']['HOST']
- DATABASE_PORT = DATABASES['default']['PORT']
-
# dropping the version check. This was added in 1.1.2
TIME_ZONE = setup['time_zone']
@@ -106,7 +98,7 @@ def read_config(cfile=DEFAULT_CONFIG, repo=None):
if setup['web_prefix']:
MEDIA_URL = setup['web_prefix'].rstrip('/') + MEDIA_URL
else:
- MEDIA_URL = '/site_media'
+ MEDIA_URL = '/site_media/'
# initialize settings from /etc/bcfg2-web.conf or /etc/bcfg2.conf, or
# set up basic defaults. this lets manage.py work in all cases
@@ -144,7 +136,7 @@ MEDIA_ROOT = ''
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
-ADMIN_MEDIA_PREFIX = '/media/'
+STATIC_URL = '/media/'
#TODO - make this unique
# Make this unique, and don't share it with anybody.
@@ -159,16 +151,10 @@ else:
}
}
-if HAS_DJANGO and django.VERSION[0] == 1 and django.VERSION[1] < 2:
- TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.load_template_source',
- 'django.template.loaders.app_directories.load_template_source',
- )
-else:
- TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.Loader',
- 'django.template.loaders.app_directories.Loader',
- )
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+)
#TODO - review these. auth and sessions aren't really used
MIDDLEWARE_CLASSES = (
@@ -194,20 +180,10 @@ TEMPLATE_DIRS = (
'/usr/share/python-support/python-django/django/contrib/admin/templates/',
)
-# TODO - sanitize this
-if HAS_DJANGO and django.VERSION[0] == 1 and django.VERSION[1] < 2:
- TEMPLATE_CONTEXT_PROCESSORS = (
- 'django.core.context_processors.auth',
- 'django.core.context_processors.debug',
- 'django.core.context_processors.i18n',
- 'django.core.context_processors.media',
- 'django.core.context_processors.request'
- )
-else:
- TEMPLATE_CONTEXT_PROCESSORS = (
- 'django.contrib.auth.context_processors.auth',
- 'django.core.context_processors.debug',
- 'django.core.context_processors.i18n',
- 'django.core.context_processors.media',
- 'django.core.context_processors.request'
- )
+TEMPLATE_CONTEXT_PROCESSORS = (
+ 'django.contrib.auth.context_processors.auth',
+ 'django.core.context_processors.debug',
+ 'django.core.context_processors.i18n',
+ 'django.core.context_processors.media',
+ 'django.core.context_processors.request'
+)
diff --git a/src/lib/Bcfg2/version.py b/src/lib/Bcfg2/version.py
index 8223d7543..6f3ba3e49 100644
--- a/src/lib/Bcfg2/version.py
+++ b/src/lib/Bcfg2/version.py
@@ -2,7 +2,7 @@
import re
-__version__ = "1.3.0rc2"
+__version__ = "1.3.1"
class Bcfg2VersionInfo(tuple):