diff options
-rw-r--r-- | schemas/bundle.xsd | 14 | ||||
-rw-r--r-- | schemas/rules.xsd | 7 | ||||
-rw-r--r-- | schemas/types.xsd | 46 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/Debconf.py | 130 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/__init__.py | 5 | ||||
-rw-r--r-- | src/lib/Bcfg2/Reporting/Storage/DjangoORM.py | 9 | ||||
-rw-r--r-- | src/lib/Bcfg2/Reporting/migrations/0009_add_conf_entry.py | 35 | ||||
-rw-r--r-- | src/lib/Bcfg2/Reporting/models.py | 32 | ||||
-rw-r--r-- | src/lib/Bcfg2/Reporting/south_migrations/0009_add_conf_entry.py | 321 | ||||
-rw-r--r-- | src/lib/Bcfg2/Reporting/templates/config_items/item.html | 7 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin.py | 1 |
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, |