@ -5,6 +5,7 @@
from __future__ import absolute_import , division , print_function , \
unicode_literals
from collections import defaultdict
from logging import getLogger
import re
@ -39,13 +40,19 @@ class Zone(object):
# Force everyting to lowercase just to be safe
self . name = str ( name ) . lower ( ) if name else name
self . sub_zones = sub_zones
self . records = set ( )
# We're grouping by node, it allows us to efficently search for
# duplicates and detect when CNAMEs co-exist with other records
self . _records = defaultdict ( set )
# optional leading . to match empty hostname
# optional trailing . b/c some sources don't have it on their fqdn
self . _name_re = re . compile ( ' \ .?{}?$ ' . format ( name ) )
self . log . debug ( ' __init__: zone= %s , sub_zones= %s ' , self , sub_zones )
@property
def records ( self ) :
return set ( [ r for _ , node in self . _records . items ( ) for r in node ] )
def hostname_from_fqdn ( self , fqdn ) :
return self . _name_re . sub ( ' ' , fqdn )
@ -53,9 +60,6 @@ class Zone(object):
name = record . name
last = name . split ( ' . ' ) [ - 1 ]
if replace and record in self . records :
self . records . remove ( record )
if last in self . sub_zones :
if name != last :
# it's a record for something under a sub-zone
@ -67,19 +71,30 @@ class Zone(object):
raise SubzoneRecordException ( ' Record {} a managed sub-zone '
' and not of type NS '
. format ( record . fqdn ) )
# TODO: this is pretty inefficent
for existing in self . records :
if record == existing :
raise DuplicateRecordException ( ' Duplicate record {}, type {} '
. format ( record . fqdn ,
record . _type ) )
elif name == existing . name and ( record . _type == ' CNAME ' or
existing . _type == ' CNAME ' ) :
raise InvalidNodeException ( ' Invalid state, CNAME at {} '
' cannot coexist with other records '
. format ( record . fqdn ) )
self . records . add ( record )
if replace :
# will remove it if it exists
self . _records [ name ] . discard ( record )
node = self . _records [ name ]
if record in node :
# We already have a record at this node of this type
raise DuplicateRecordException ( ' Duplicate record {}, type {} '
. format ( record . fqdn ,
record . _type ) )
elif ( ( record . _type == ' CNAME ' and len ( node ) > 0 ) or
( ' CNAME ' in map ( lambda r : r . _type , node ) ) ) :
# We're adding a CNAME to existing records or adding to an existing
# CNAME
raise InvalidNodeException ( ' Invalid state, CNAME at {} cannot '
' coexist with other records '
. format ( record . fqdn ) )
node . add ( record )
def _remove_record ( self , record ) :
' Only for use in tests '
self . _records [ record . name ] . discard ( record )
def changes ( self , desired , target ) :
self . log . debug ( ' changes: zone= %s , target= %s ' , self , target )