summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Sulfrian <asulfrian@zedat.fu-berlin.de>2022-01-30 05:03:51 +0100
committerAlexander Sulfrian <asulfrian@zedat.fu-berlin.de>2022-01-30 05:03:51 +0100
commit44dba9dc7eb82c6c7073ca4a01128ad779ac0f4b (patch)
treebbbad6988088f69214a48a13e237132ee64f6e03
parentba1a18e060a8614b3dcb41b94a7ad37e89f1dfdf (diff)
parentc11f4f790d710721dcc99f87cfcbafb49e9a4715 (diff)
downloadbcfg2-44dba9dc7eb82c6c7073ca4a01128ad779ac0f4b.tar.gz
bcfg2-44dba9dc7eb82c6c7073ca4a01128ad779ac0f4b.tar.bz2
bcfg2-44dba9dc7eb82c6c7073ca4a01128ad779ac0f4b.zip
Merge branch 'debconf'
-rw-r--r--schemas/bundle.xsd14
-rw-r--r--schemas/rules.xsd7
-rw-r--r--schemas/types.xsd46
-rw-r--r--src/lib/Bcfg2/Client/Tools/Debconf.py130
-rw-r--r--src/lib/Bcfg2/Client/__init__.py5
-rw-r--r--src/lib/Bcfg2/Reporting/Storage/DjangoORM.py9
-rw-r--r--src/lib/Bcfg2/Reporting/migrations/0009_add_conf_entry.py35
-rw-r--r--src/lib/Bcfg2/Reporting/models.py32
-rw-r--r--src/lib/Bcfg2/Reporting/south_migrations/0009_add_conf_entry.py321
-rw-r--r--src/lib/Bcfg2/Reporting/templates/config_items/item.html7
-rw-r--r--src/lib/Bcfg2/Server/Admin.py1
11 files changed, 601 insertions, 6 deletions
diff --git a/schemas/bundle.xsd b/schemas/bundle.xsd
index 4a11a1d1b..7d7a141b7 100644
--- a/schemas/bundle.xsd
+++ b/schemas/bundle.xsd
@@ -69,6 +69,13 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
+ <xsd:element name='Conf' type='StructureEntry'>
+ <xsd:annotation>
+ <xsd:documentation>
+ Abstract description of a Conf entry.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
<xsd:element name='SEBoolean' type='SELinuxStructure'>
<xsd:annotation>
<xsd:documentation>
@@ -238,6 +245,13 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
+ <xsd:element name='BoundConf' type='ConfType'>
+ <xsd:annotation>
+ <xsd:documentation>
+ Fully bound description of a Conf entry.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
<xsd:element name='Group' type='BundlerGroupType'>
<xsd:annotation>
<xsd:documentation>
diff --git a/schemas/rules.xsd b/schemas/rules.xsd
index fb41ad9d4..7afc0f85e 100644
--- a/schemas/rules.xsd
+++ b/schemas/rules.xsd
@@ -122,6 +122,13 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
+ <xsd:element name='Conf' type='ConfType'>
+ <xsd:annotation>
+ <xsd:documentation>
+ Fully bound description of a Conf entry.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
<xsd:element name='Group' type='RContainerType'>
<xsd:annotation>
<xsd:documentation>
diff --git a/schemas/types.xsd b/schemas/types.xsd
index 5165d186b..59e9149e2 100644
--- a/schemas/types.xsd
+++ b/schemas/types.xsd
@@ -106,6 +106,12 @@
</xsd:restriction>
</xsd:simpleType>
+ <xsd:simpleType name="ConfTypeEnum">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="debconf"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
<xsd:complexType name='ActionType'>
<xsd:annotation>
<xsd:documentation>
@@ -536,4 +542,44 @@
</xsd:attribute>
<xsd:attributeGroup ref="py:genshiAttrs"/>
</xsd:complexType>
+
+ <xsd:complexType name="ConfType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The Conf tag allows you to set configurations options client
+ machines (f.e. debconf).
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute type="xsd:token" name="name" use="required">
+ <xsd:annotation>
+ <xsd:documentation>
+ Name of the configuration setting.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute type="xsd:string" name="value">
+ <xsd:annotation>
+ <xsd:documentation>
+ The value of the configuration setting.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="type" type="ConfTypeEnum">
+ <xsd:annotation>
+ <xsd:documentation>
+ Driver to use on the client to manage this configuration.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute type="xsd:boolean" name="ignore" default="false">
+ <xsd:annotation>
+ <xsd:documentation>
+ If you set this to "true" the configuration setting will be ignored
+ and not updated. This is usefull to remove a setting from the list of
+ extra entries.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attributeGroup ref="py:genshiAttrs"/>
+ </xsd:complexType>
</xsd:schema>
diff --git a/src/lib/Bcfg2/Client/Tools/Debconf.py b/src/lib/Bcfg2/Client/Tools/Debconf.py
new file mode 100644
index 000000000..784f7e9bc
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/Debconf.py
@@ -0,0 +1,130 @@
+"""Debconf Support for Bcfg2"""
+
+import subprocess
+import Bcfg2.Options
+import Bcfg2.Client.Tools
+
+
+class Debconf(Bcfg2.Client.Tools.Tool):
+ """Debconf Support for Bcfg2."""
+ name = 'Debconf'
+ __execs__ = ['/usr/bin/debconf-communicate', '/usr/bin/debconf-show']
+ __handles__ = [('Conf', 'debconf')]
+ __req__ = {'Conf': ['name']}
+
+ def __init__(self, config):
+ Bcfg2.Client.Tools.Tool.__init__(self, config)
+
+ #: This is the referrence to the Popen object of the
+ #: running debconf-communicate process. If this is None,
+ #: no process is runnning.
+ self.debconf = None
+
+ def _start_debconf(self):
+ if self.debconf is None:
+ self.debconf = subprocess.Popen(
+ ['/usr/bin/debconf-communicate'],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+
+ def _stop_debconf(self):
+ if self.debconf is not None:
+ self.debconf.stdin.close()
+ self.debconf.stdout.close()
+ self.debconf = None
+
+ def _debconf_reply(self, msg):
+ if self.debconf is None:
+ self._start_debconf()
+
+ self.logger.debug('Debconf: %s' % msg.strip())
+ self.debconf.stdin.write(msg)
+ line = self.debconf.stdout.readline().rstrip('\n')
+ self.logger.debug('< %s' % line)
+ reply = line.split(' ', 1)
+
+ result = None
+ if len(reply) > 1:
+ result = reply[1]
+ return (reply[0] == '0', result)
+
+ def debconf_get(self, key):
+ (success, value) = self._debconf_reply('GET %s\n' % key)
+ if not success:
+ return (False, None)
+
+ (_, seen) = self._debconf_reply('FGET %s seen\n' % key)
+ return (seen == 'true', value)
+
+ def debconf_set(self, key, value):
+ (success, _) = self._debconf_reply('SET %s %s\n' % (key, value))
+ if success:
+ self._debconf_reply('FSET %s seen true\n' % key)
+
+ return success
+
+ def debconf_reset(self, key):
+ (success, _) = self._debconf_reply('RESET %s\n' % key)
+ return success
+
+ def VerifyConf(self, entry, _modlist):
+ """ Verify the given Debconf entry. """
+ if entry.get('ignore', 'false').lower() == 'true':
+ return True
+
+ (seen, current_value) = self.debconf_get(entry.get('name'))
+ if not seen:
+ current_value = '%s (not seen)' % current_value
+ entry.set('current_value', current_value)
+
+ return seen and current_value == entry.get('value')
+
+ def InstallConf(self, entry):
+ """ Install the given Debconf entry. """
+ return self.debconf_set(entry.get('name'), entry.get('value'))
+
+ def Inventory(self, structures=None):
+ try:
+ result = Bcfg2.Client.Tools.Tool.Inventory(self, structures)
+ finally:
+ self._stop_debconf()
+
+ return result
+ Inventory.__doc__ = Bcfg2.Client.Tools.Tool.Inventory.__doc__
+
+ def Install(self, entries):
+ try:
+ result = Bcfg2.Client.Tools.Tool.Install(self, entries)
+ finally:
+ self._stop_debconf()
+
+ return result
+ Install.__doc__ = Bcfg2.Client.Tools.Tool.Install.__doc__
+
+ def Remove(self, entries):
+ try:
+ for entry in entries:
+ self.debconf_reset(entry.get('name'))
+ self.modified += entry
+ finally:
+ self._stop_debconf()
+ self.extra = self.FindExtra()
+ Remove.__doc__ = Bcfg2.Client.Tools.Tool.Remove.__doc__
+
+ def FindExtra(self):
+ specified = [entry.get('name')
+ for entry in self.getSupportedEntries()]
+ extra = dict()
+ listowners = self.cmd.run(['/usr/bin/debconf-show', '--listowners'])
+ if listowners.success:
+ owners = listowners.stdout.splitlines()
+
+ values = self.cmd.run(['/usr/bin/debconf-show'] + owners)
+ for line in values.stdout.splitlines():
+ if len(line) > 2 and line[0] == '*':
+ (name, current_value) = line[2:].split(':', 2)
+ if name not in specified and name not in extra:
+ extra[name] = Bcfg2.Client.XML.Element(
+ 'Conf', name=name, type='debconf',
+ current_value=current_value[1:])
+ return extra.values()
+ FindExtra.__doc__ = Bcfg2.Client.Tools.Tool.FindExtra.__doc__
diff --git a/src/lib/Bcfg2/Client/__init__.py b/src/lib/Bcfg2/Client/__init__.py
index 157cc7f65..a7e0dade5 100644
--- a/src/lib/Bcfg2/Client/__init__.py
+++ b/src/lib/Bcfg2/Client/__init__.py
@@ -110,7 +110,7 @@ class Client(object):
help='Only verify the given bundle(s)'),
Bcfg2.Options.Option(
'-r', '--remove',
- choices=['all', 'services', 'packages', 'users'],
+ choices=['all', 'services', 'packages', 'users', 'conf'],
help='Force removal of additional configuration items')),
Bcfg2.Options.ExclusiveOptionGroup(
Bcfg2.Options.PathOption(
@@ -640,6 +640,9 @@ class Client(object):
elif Bcfg2.Options.setup.remove == 'users':
self.removal = [entry for entry in self.extra
if entry.tag in ['POSIXUser', 'POSIXGroup']]
+ elif Bcfg2.Options.setup.remove == 'conf':
+ self.removal = [entry for entry in self.extra
+ if entry.tag == 'Conf']
candidates = [entry for entry in self.states
if not self.states[entry]]
diff --git a/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py b/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
index a8c8ce243..e0566a51b 100644
--- a/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
+++ b/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
@@ -30,7 +30,7 @@ def load_django_models():
FailureEntry, Performance, BaseEntry, ServiceEntry, ActionEntry, \
POSIXGroupEntry, POSIXUserEntry, SEBooleanEntry, SEFcontextEntry, \
SEInterfaceEntry, SELoginEntry, SEModuleEntry, SENodeEntry, \
- SEPermissiveEntry, SEPortEntry, SEUserEntry
+ SEPermissiveEntry, SEPortEntry, SEUserEntry, ConfEntry
# pylint: enable=W0602
from Bcfg2.Reporting.models import \
@@ -39,7 +39,7 @@ def load_django_models():
FailureEntry, Performance, BaseEntry, ServiceEntry, ActionEntry, \
POSIXGroupEntry, POSIXUserEntry, SEBooleanEntry, SEFcontextEntry, \
SEInterfaceEntry, SELoginEntry, SEModuleEntry, SENodeEntry, \
- SEPermissiveEntry, SEPortEntry, SEUserEntry
+ SEPermissiveEntry, SEPortEntry, SEUserEntry, ConfEntry
def get_all_field_names(model):
@@ -127,6 +127,11 @@ class DjangoORM(StorageBase):
defaults=dict(status='check', rc=-1),
mapping=dict(output="rc"))
+ def _import_Conf(self, entry, state):
+ return self._import_default(entry, state,
+ defaults=dict(),
+ mapping=dict())
+
def _import_Package(self, entry, state):
name = entry.get('name')
exists = entry.get('current_exists', default="true").lower() == "true"
diff --git a/src/lib/Bcfg2/Reporting/migrations/0009_add_conf_entry.py b/src/lib/Bcfg2/Reporting/migrations/0009_add_conf_entry.py
new file mode 100644
index 000000000..527a7bbe9
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/migrations/0009_add_conf_entry.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('Reporting', '0008_add_ready_flag_interaction'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ConfEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.BigIntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('value', models.TextField(null=True)),
+ ('current_value', models.TextField(null=True)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='confs',
+ field=models.ManyToManyField(to='Reporting.ConfEntry'),
+ ),
+ ]
diff --git a/src/lib/Bcfg2/Reporting/models.py b/src/lib/Bcfg2/Reporting/models.py
index c28571ded..7bff0ab3b 100644
--- a/src/lib/Bcfg2/Reporting/models.py
+++ b/src/lib/Bcfg2/Reporting/models.py
@@ -153,6 +153,7 @@ class Interaction(models.Model):
only_important = models.BooleanField(default=False)
actions = models.ManyToManyField("ActionEntry")
+ confs = models.ManyToManyField("ConfEntry")
packages = models.ManyToManyField("PackageEntry")
paths = models.ManyToManyField("PathEntry")
services = models.ManyToManyField("ServiceEntry")
@@ -174,7 +175,7 @@ class Interaction(models.Model):
'seports', 'sefcontexts', 'senodes',
'selogins', 'seusers', 'seinterfaces',
'sepermissives', 'semodules', 'posixusers',
- 'posixgroups')
+ 'posixgroups', 'confs')
# Formerly InteractionMetadata
profile = models.ForeignKey("Group", related_name="+", null=True)
@@ -489,6 +490,33 @@ class ActionEntry(SuccessEntry):
ENTRY_TYPE = r"Action"
+class ConfEntry(SuccessEntry):
+ """ Conf entry """
+ value = models.TextField(null=True)
+ current_value = models.TextField(null=True)
+
+ ENTRY_TYPE = r"Conf"
+
+ def conf_problem(self):
+ """Check for a conf problem."""
+ if not self.current_value:
+ return True
+ return self.value != self.current_value
+
+ def short_list(self):
+ """Return a list of problems"""
+ rv = super(ConfEntry, self).short_list()
+ if self.is_extra():
+ return rv
+ if not self.conf_problem() or not self.exists:
+ return rv
+ if not self.current_value:
+ rv.append("Missing")
+ else:
+ rv.append("Wrong value")
+ return rv
+
+
class SEBooleanEntry(SuccessEntry):
""" SELinux boolean """
value = models.BooleanField(default=True)
@@ -800,7 +828,7 @@ class ServiceEntry(SuccessEntry):
return rv
-ENTRY_TYPES = (ActionEntry, PackageEntry, PathEntry, ServiceEntry,
+ENTRY_TYPES = (ActionEntry, ConfEntry, PackageEntry, PathEntry, ServiceEntry,
SEBooleanEntry, SEPortEntry, SEFcontextEntry, SENodeEntry,
SELoginEntry, SEUserEntry, SEInterfaceEntry, SEPermissiveEntry,
SEModuleEntry)
diff --git a/src/lib/Bcfg2/Reporting/south_migrations/0009_add_conf_entry.py b/src/lib/Bcfg2/Reporting/south_migrations/0009_add_conf_entry.py
new file mode 100644
index 000000000..cbbad4d59
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/south_migrations/0009_add_conf_entry.py
@@ -0,0 +1,321 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as 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 'ConfEntry'
+ db.create_table(u'Reporting_confentry', (
+ (u'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.TextField')(null=True)),
+ ('current_value', self.gf('django.db.models.fields.TextField')(null=True)),
+ ))
+ db.send_create_signal(u'Reporting', ['ConfEntry'])
+
+ # Adding M2M table for field confs on 'Interaction'
+ m2m_table_name = db.shorten_name(u'Reporting_interaction_confs')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm[u'Reporting.interaction'], null=False)),
+ ('confentry', models.ForeignKey(orm[u'Reporting.confentry'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['interaction_id', 'confentry_id'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'ConfEntry'
+ db.delete_table(u'Reporting_confentry')
+
+ # Removing M2M table for field confs on 'Interaction'
+ db.delete_table(db.shorten_name(u'Reporting_interaction_confs'))
+
+
+ models = {
+ u'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'}),
+ u'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'})
+ },
+ u'Reporting.bundle': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ u'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': u"orm['Reporting.Interaction']"}),
+ 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ u'Reporting.confentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ConfEntry'},
+ 'current_value': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ u'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.TextField', [], {'null': 'True'})
+ },
+ u'Reporting.deviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': [u'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'}),
+ u'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_minor': ('django.db.models.fields.IntegerField', [], {})
+ },
+ u'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'}),
+ u'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'})
+ },
+ u'Reporting.fileacl': {
+ 'Meta': {'object_name': 'FileAcl'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ u'Reporting.fileperms': {
+ 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ u'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'})
+ },
+ u'Reporting.group': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"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': u"orm['Reporting.Group']", 'symmetrical': 'False'}),
+ u'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'})
+ },
+ u'Reporting.interaction': {
+ 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
+ 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
+ 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': u"orm['Reporting.Client']"}),
+ 'confs': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.ConfEntry']", 'symmetrical': 'False'}),
+ 'dry_run': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
+ 'good_count': ('django.db.models.fields.IntegerField', [], {}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.Group']", 'symmetrical': 'False'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'only_important': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
+ 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
+ 'posixgroups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.POSIXGroupEntry']", 'symmetrical': 'False'}),
+ 'posixusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.POSIXUserEntry']", 'symmetrical': 'False'}),
+ 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': u"orm['Reporting.Group']"}),
+ 'ready': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'sebooleans': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.SEBooleanEntry']", 'symmetrical': 'False'}),
+ 'sefcontexts': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.SEFcontextEntry']", 'symmetrical': 'False'}),
+ 'seinterfaces': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.SEInterfaceEntry']", 'symmetrical': 'False'}),
+ 'selogins': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.SELoginEntry']", 'symmetrical': 'False'}),
+ 'semodules': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.SEModuleEntry']", 'symmetrical': 'False'}),
+ 'senodes': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.SENodeEntry']", 'symmetrical': 'False'}),
+ 'sepermissives': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.SEPermissiveEntry']", 'symmetrical': 'False'}),
+ 'seports': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.SEPortEntry']", 'symmetrical': 'False'}),
+ 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
+ 'seusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"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', [], {})
+ },
+ u'Reporting.linkentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': [u'Reporting.PathEntry']},
+ 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ u'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
+ },
+ u'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'}),
+ u'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': "''"})
+ },
+ u'Reporting.pathentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
+ 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
+ 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': u"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'}),
+ u'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': u"orm['Reporting.FilePerms']"})
+ },
+ u'Reporting.performance': {
+ 'Meta': {'object_name': 'Performance'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': u"orm['Reporting.Interaction']"}),
+ 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
+ },
+ u'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'}),
+ u'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', [], {})
+ },
+ u'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'}),
+ u'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'})
+ },
+ u'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'}),
+ u'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'})
+ },
+ u'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'}),
+ u'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', [], {})
+ },
+ u'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'}),
+ u'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', [], {})
+ },
+ u'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'}),
+ u'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', [], {})
+ },
+ u'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'}),
+ u'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', [], {})
+ },
+ u'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'}),
+ u'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', [], {})
+ },
+ u'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'}),
+ u'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', [], {})
+ },
+ u'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'}),
+ u'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', [], {})
+ },
+ u'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'}),
+ u'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'})
+ },
+ u'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'}),
+ u'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']
diff --git a/src/lib/Bcfg2/Reporting/templates/config_items/item.html b/src/lib/Bcfg2/Reporting/templates/config_items/item.html
index 91c368bd7..9a103bc72 100644
--- a/src/lib/Bcfg2/Reporting/templates/config_items/item.html
+++ b/src/lib/Bcfg2/Reporting/templates/config_items/item.html
@@ -45,7 +45,7 @@ div.entry_list h3 {
{% endif %}
{# Really need a better test here #}
-{% if item.mode_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 or item.conf_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>
@@ -86,6 +86,11 @@ div.entry_list h3 {
<td>{{item.selinuxtype}}</td>
<td>{{item.current_selinuxtype}}</td></tr>
{% endif %}
+ {% if item.conf_problem %}
+ <tr><td style='text-align: right'><b>Conf Value</b></td>
+ <td>{{item.value|default:""}}</td>
+ <td>{{item.current_value|default:""}}</td></tr>
+ {% endif %}
</table>
{% endif %}
diff --git a/src/lib/Bcfg2/Server/Admin.py b/src/lib/Bcfg2/Server/Admin.py
index 77bca88eb..0accd0361 100644
--- a/src/lib/Bcfg2/Server/Admin.py
+++ b/src/lib/Bcfg2/Server/Admin.py
@@ -884,6 +884,7 @@ class _ReportsCmd(AdminCmd): # pylint: disable=W0223
Bcfg2.Reporting.models.Bundle,
Bcfg2.Reporting.models.FailureEntry,
Bcfg2.Reporting.models.ActionEntry,
+ Bcfg2.Reporting.models.ConfEntry,
Bcfg2.Reporting.models.PathEntry,
Bcfg2.Reporting.models.PackageEntry,
Bcfg2.Reporting.models.PathEntry,