diff options
Diffstat (limited to 'src/lib/Bcfg2/Server/Admin/Compare.py')
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Compare.py | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/src/lib/Bcfg2/Server/Admin/Compare.py b/src/lib/Bcfg2/Server/Admin/Compare.py new file mode 100644 index 000000000..050dd69f8 --- /dev/null +++ b/src/lib/Bcfg2/Server/Admin/Compare.py @@ -0,0 +1,151 @@ +import lxml.etree +import os + +import Bcfg2.Server.Admin + + +class Compare(Bcfg2.Server.Admin.Mode): + __shorthelp__ = ("Determine differences between files or " + "directories of client specification instances") + __longhelp__ = (__shorthelp__ + "\n\nbcfg2-admin compare <file1> <file2>" + "\nbcfg2-admin compare -r <dir1> <dir2>") + __usage__ = ("bcfg2-admin compare <old> <new>\n\n" + " -r\trecursive") + + def __init__(self, setup): + Bcfg2.Server.Admin.Mode.__init__(self, setup) + self.important = {'Path': ['name', 'type', 'owner', 'group', 'perms', + 'important', 'paranoid', 'sensitive', + 'dev_type', 'major', 'minor', 'prune', + 'encoding', 'empty', 'to', 'recursive', + 'vcstype', 'sourceurl', 'revision'], + 'Package': ['name', 'type', 'version', 'simplefile', + 'verify'], + 'Service': ['name', 'type', 'status', 'mode', + 'target', 'sequence', 'parameters'], + 'Action': ['name', 'timing', 'when', 'status', + 'command'], + 'PostInstall': ['name'] + } + + def compareStructures(self, new, old): + if new.tag == 'Independent': + bundle = 'Base' + else: + bundle = new.get('name') + + identical = True + + for child in new.getchildren(): + if child.tag not in self.important: + print(" %s in (new) bundle %s:\n tag type not handled!" % + (child.tag, bundle)) + continue + equiv = old.xpath('%s[@name="%s"]' % + (child.tag, child.get('name'))) + if len(equiv) == 0: + print(" %s %s in bundle %s:\n only in new configuration" % + (child.tag, child.get('name'), bundle)) + identical = False + continue + diff = [] + if child.tag == 'Path' and child.get('type') == 'file' and \ + child.text != equiv[0].text: + diff.append('contents') + attrdiff = [field for field in self.important[child.tag] if \ + child.get(field) != equiv[0].get(field)] + if attrdiff: + diff.append('attributes (%s)' % ', '.join(attrdiff)) + if diff: + print(" %s %s in bundle %s:\n %s differ" % (child.tag, \ + child.get('name'), bundle, ' and '.join(diff))) + identical = False + + for child in old.getchildren(): + if child.tag not in self.important: + print(" %s in (old) bundle %s:\n tag type not handled!" % + (child.tag, bundle)) + elif len(new.xpath('%s[@name="%s"]' % + (child.tag, child.get('name')))) == 0: + print(" %s %s in bundle %s:\n only in old configuration" % + (child.tag, child.get('name'), bundle)) + identical = False + + return identical + + def compareSpecifications(self, path1, path2): + try: + new = lxml.etree.parse(path1).getroot() + except IOError: + print("Failed to read %s" % (path1)) + raise SystemExit(1) + + try: + old = lxml.etree.parse(path2).getroot() + except IOError: + print("Failed to read %s" % (path2)) + raise SystemExit(1) + + for src in [new, old]: + for bundle in src.findall('./Bundle'): + if bundle.get('name')[-4:] == '.xml': + bundle.set('name', bundle.get('name')[:-4]) + + identical = True + + for bundle in old.findall('./Bundle'): + if len(new.xpath('Bundle[@name="%s"]' % (bundle.get('name')))) == 0: + print(" Bundle %s only in old configuration" % + bundle.get('name')) + identical = False + for bundle in new.findall('./Bundle'): + equiv = old.xpath('Bundle[@name="%s"]' % (bundle.get('name'))) + if len(equiv) == 0: + print(" Bundle %s only in new configuration" % + bundle.get('name')) + identical = False + elif not self.compareStructures(bundle, equiv[0]): + identical = False + + i1 = lxml.etree.Element('Independent') + i2 = lxml.etree.Element('Independent') + i1.extend(new.findall('./Independent/*')) + i2.extend(old.findall('./Independent/*')) + if not self.compareStructures(i1, i2): + identical = False + + return identical + + def __call__(self, args): + Bcfg2.Server.Admin.Mode.__call__(self, args) + if len(args) == 0: + self.errExit("No argument specified.\n" + "Please see bcfg2-admin compare help for usage.") + if '-r' in args: + args = list(args) + args.remove('-r') + (oldd, newd) = args + (old, new) = [os.listdir(spot) for spot in args] + old_extra = [] + for item in old: + if item not in new: + old_extra.append(item) + continue + print("File: %s" % item) + state = self.__call__([oldd + '/' + item, newd + '/' + item]) + new.remove(item) + if state: + print("File %s is good" % item) + else: + print("File %s is bad" % item) + if new: + print("%s has extra files: %s" % (newd, ', '.join(new))) + if old_extra: + print("%s has extra files: %s" % (oldd, ', '.join(old_extra))) + return + try: + (old, new) = args + return self.compareSpecifications(new, old) + except IndexError: + print(self.__call__.__doc__) + raise SystemExit(1) |