diff options
Diffstat (limited to 'lib/dnspython/examples')
-rwxr-xr-x | lib/dnspython/examples/ddns.py | 2 | ||||
-rwxr-xr-x | lib/dnspython/examples/zonediff.py | 270 |
2 files changed, 271 insertions, 1 deletions
diff --git a/lib/dnspython/examples/ddns.py b/lib/dnspython/examples/ddns.py index 27a5b932f4..84814b73cf 100755 --- a/lib/dnspython/examples/ddns.py +++ b/lib/dnspython/examples/ddns.py @@ -16,7 +16,7 @@ # # DEVICE=$1 # -# if [ "X${DEVICE}" = "Xeth0" ]; then +# if [ "X${DEVICE}" == "Xeth0" ]; then # IPADDR=`LANG= LC_ALL= ifconfig ${DEVICE} | grep 'inet addr' | # awk -F: '{ print $2 } ' | awk '{ print $1 }'` # /usr/local/sbin/ddns.py $IPADDR diff --git a/lib/dnspython/examples/zonediff.py b/lib/dnspython/examples/zonediff.py new file mode 100755 index 0000000000..ad81fb1d2d --- /dev/null +++ b/lib/dnspython/examples/zonediff.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python +# +# Small library and commandline tool to do logical diffs of zonefiles +# ./zonediff -h gives you help output +# +# Requires dnspython to do all the heavy lifting +# +# (c)2009 Dennis Kaarsemaker <dennis@kaarsemaker.net> +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +"""See diff_zones.__doc__ for more information""" + +__all__ = ['diff_zones', 'format_changes_plain', 'format_changes_html'] + +try: + import dns.zone +except ImportError: + import sys + sys.stderr.write("Please install dnspython") + sys.exit(1) + +def diff_zones(zone1, zone2, ignore_ttl=False, ignore_soa=False): + """diff_zones(zone1, zone2, ignore_ttl=False, ignore_soa=False) -> changes + Compares two dns.zone.Zone objects and returns a list of all changes + in the format (name, oldnode, newnode). + + If ignore_ttl is true, a node will not be added to this list if the + only change is its TTL. + + If ignore_soa is true, a node will not be added to this list if the + only changes is a change in a SOA Rdata set. + + The returned nodes do include all Rdata sets, including unchanged ones. + """ + + changes = [] + for name in zone1: + name = str(name) + n1 = zone1.get_node(name) + n2 = zone2.get_node(name) + if not n2: + changes.append((str(name), n1, n2)) + elif _nodes_differ(n1, n2, ignore_ttl, ignore_soa): + changes.append((str(name), n1, n2)) + + for name in zone2: + n1 = zone1.get_node(name) + if not n1: + n2 = zone2.get_node(name) + changes.append((str(name), n1, n2)) + return changes + +def _nodes_differ(n1, n2, ignore_ttl, ignore_soa): + if ignore_soa or not ignore_ttl: + # Compare datasets directly + for r in n1.rdatasets: + if ignore_soa and r.rdtype == dns.rdatatype.SOA: + continue + if r not in n2.rdatasets: + return True + if not ignore_ttl: + return r.ttl != n2.find_rdataset(r.rdclass, r.rdtype).ttl + + for r in n2.rdatasets: + if ignore_soa and r.rdtype == dns.rdatatype.SOA: + continue + if r not in n1.rdatasets: + return True + else: + return n1 != n2 + +def format_changes_plain(oldf, newf, changes, ignore_ttl=False): + """format_changes(oldfile, newfile, changes, ignore_ttl=False) -> str + Given 2 filenames and a list of changes from diff_zones, produce diff-like + output. If ignore_ttl is True, TTL-only changes are not displayed""" + + ret = "--- %s\n+++ %s\n" % (oldf, newf) + for name, old, new in changes: + ret += "@ %s\n" % name + if not old: + for r in new.rdatasets: + ret += "+ %s\n" % str(r).replace('\n','\n+ ') + elif not new: + for r in old.rdatasets: + ret += "- %s\n" % str(r).replace('\n','\n+ ') + else: + for r in old.rdatasets: + if r not in new.rdatasets or (r.ttl != new.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl): + ret += "- %s\n" % str(r).replace('\n','\n+ ') + for r in new.rdatasets: + if r not in old.rdatasets or (r.ttl != old.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl): + ret += "+ %s\n" % str(r).replace('\n','\n+ ') + return ret + +def format_changes_html(oldf, newf, changes, ignore_ttl=False): + """format_changes(oldfile, newfile, changes, ignore_ttl=False) -> str + Given 2 filenames and a list of changes from diff_zones, produce nice html + output. If ignore_ttl is True, TTL-only changes are not displayed""" + + ret = '''<table class="zonediff"> + <thead> + <tr> + <th> </th> + <th class="old">%s</th> + <th class="new">%s</th> + </tr> + </thead> + <tbody>\n''' % (oldf, newf) + + for name, old, new in changes: + ret += ' <tr class="rdata">\n <td class="rdname">%s</td>\n' % name + if not old: + for r in new.rdatasets: + ret += ' <td class="old"> </td>\n <td class="new">%s</td>\n' % str(r).replace('\n','<br />') + elif not new: + for r in old.rdatasets: + ret += ' <td class="old">%s</td>\n <td class="new"> </td>\n' % str(r).replace('\n','<br />') + else: + ret += ' <td class="old">' + for r in old.rdatasets: + if r not in new.rdatasets or (r.ttl != new.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl): + ret += str(r).replace('\n','<br />') + ret += '</td>\n' + ret += ' <td class="new">' + for r in new.rdatasets: + if r not in old.rdatasets or (r.ttl != old.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl): + ret += str(r).replace('\n','<br />') + ret += '</td>\n' + ret += ' </tr>\n' + return ret + ' </tbody>\n</table>' + +# Make this module usable as a script too. +if __name__ == '__main__': + import optparse + import subprocess + import sys + import traceback + + usage = """%prog zonefile1 zonefile2 - Show differences between zones in a diff-like format +%prog [--git|--bzr|--rcs] zonefile rev1 [rev2] - Show differences between two revisions of a zonefile + +The differences shown will be logical differences, not textual differences. +""" + p = optparse.OptionParser(usage=usage) + p.add_option('-s', '--ignore-soa', action="store_true", default=False, dest="ignore_soa", + help="Ignore SOA-only changes to records") + p.add_option('-t', '--ignore-ttl', action="store_true", default=False, dest="ignore_ttl", + help="Ignore TTL-only changes to Rdata") + p.add_option('-T', '--traceback', action="store_true", default=False, dest="tracebacks", + help="Show python tracebacks when errors occur") + p.add_option('-H', '--html', action="store_true", default=False, dest="html", + help="Print HTML output") + p.add_option('-g', '--git', action="store_true", default=False, dest="use_git", + help="Use git revisions instead of real files") + p.add_option('-b', '--bzr', action="store_true", default=False, dest="use_bzr", + help="Use bzr revisions instead of real files") + p.add_option('-r', '--rcs', action="store_true", default=False, dest="use_rcs", + help="Use rcs revisions instead of real files") + opts, args = p.parse_args() + opts.use_vc = opts.use_git or opts.use_bzr or opts.use_rcs + + def _open(what, err): + if isinstance(what, basestring): + # Open as normal file + try: + return open(what, 'rb') + except: + sys.stderr.write(err + "\n") + if opts.tracebacks: + traceback.print_exc() + else: + # Must be a list, open subprocess + try: + proc = subprocess.Popen(what, stdout=subprocess.PIPE) + proc.wait() + if proc.returncode == 0: + return proc.stdout + sys.stderr.write(err + "\n") + except: + sys.stderr.write(err + "\n") + if opts.tracebacks: + traceback.print_exc() + + if not opts.use_vc and len(args) != 2: + p.print_help() + sys.exit(64) + if opts.use_vc and len(args) not in (2,3): + p.print_help() + sys.exit(64) + + # Open file desriptors + if not opts.use_vc: + oldn, newn = args + else: + if len(args) == 3: + filename, oldr, newr = args + oldn = "%s:%s" % (oldr, filename) + newn = "%s:%s" % (newr, filename) + else: + filename, oldr = args + newr = None + oldn = "%s:%s" % (oldr, filename) + newn = filename + + + old, new = None, None + oldz, newz = None, None + if opts.use_bzr: + old = _open(["bzr", "cat", "-r" + oldr, filename], + "Unable to retrieve revision %s of %s" % (oldr, filename)) + if newr != None: + new = _open(["bzr", "cat", "-r" + newr, filename], + "Unable to retrieve revision %s of %s" % (newr, filename)) + elif opts.use_git: + old = _open(["git", "show", oldn], + "Unable to retrieve revision %s of %s" % (oldr, filename)) + if newr != None: + new = _open(["git", "show", newn], + "Unable to retrieve revision %s of %s" % (newr, filename)) + elif opts.use_rcs: + old = _open(["co", "-q", "-p", "-r" + oldr, filename], + "Unable to retrieve revision %s of %s" % (oldr, filename)) + if newr != None: + new = _open(["co", "-q", "-p", "-r" + newr, filename], + "Unable to retrieve revision %s of %s" % (newr, filename)) + if not opts.use_vc: + old = _open(oldn, "Unable to open %s" % oldn) + if not opts.use_vc or newr == None: + new = _open(newn, "Unable to open %s" % newn) + + if not old or not new: + sys.exit(65) + + # Parse the zones + try: + oldz = dns.zone.from_file(old, origin = '.', check_origin=False) + except dns.exception.DNSException: + sys.stderr.write("Incorrect zonefile: %s\n", old) + if opts.tracebacks: + traceback.print_exc() + try: + newz = dns.zone.from_file(new, origin = '.', check_origin=False) + except dns.exception.DNSException: + sys.stderr.write("Incorrect zonefile: %s\n" % new) + if opts.tracebacks: + traceback.print_exc() + if not oldz or not newz: + sys.exit(65) + + changes = diff_zones(oldz, newz, opts.ignore_ttl, opts.ignore_soa) + changes.sort() + + if not changes: + sys.exit(0) + if opts.html: + print format_changes_html(oldn, newn, changes, opts.ignore_ttl) + else: + print format_changes_plain(oldn, newn, changes, opts.ignore_ttl) + sys.exit(1) |