Skip to content

Commit

Permalink
dns zone enhancement and return curated value (ansible#50740)
Browse files Browse the repository at this point in the history
  • Loading branch information
yuwzho authored and yungezz committed Jan 11, 2019
1 parent 9487815 commit 0f6252b
Show file tree
Hide file tree
Showing 8 changed files with 442 additions and 115 deletions.
10 changes: 8 additions & 2 deletions lib/ansible/module_utils/azure_rm_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ def normalize_location_name(name):
},
'DnsManagementClient': {
'package_name': 'dns',
'expected_version': '1.2.0'
'expected_version': '2.1.0'
},
'WebSiteManagementClient': {
'package_name': 'web',
Expand Down Expand Up @@ -839,9 +839,15 @@ def dns_client(self):
self.log('Getting dns client')
if not self._dns_client:
self._dns_client = self.get_mgmt_svc_client(DnsManagementClient,
base_url=self._cloud_environment.endpoints.resource_manager)
base_url=self._cloud_environment.endpoints.resource_manager,
api_version='2018-05-01')
return self._dns_client

@property
def dns_models(self):
self.log("Getting dns models...")
return DnsManagementClient.models('2018-05-01')

@property
def web_client(self):
self.log('Getting web client')
Expand Down
124 changes: 76 additions & 48 deletions lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
- SRV
- TXT
- PTR
- CAA
- SOA
required: true
record_mode:
description:
Expand Down Expand Up @@ -179,7 +181,6 @@

try:
from msrestazure.azure_exceptions import CloudError
from azure.mgmt.dns.models import Zone, RecordSet, ARecord, AaaaRecord, MxRecord, NsRecord, PtrRecord, SrvRecord, TxtRecord, CnameRecord, SoaRecord
except ImportError:
# This is handled in azure_rm_common
pass
Expand Down Expand Up @@ -214,18 +215,34 @@
TXT=dict(
value=dict(type='list', required=True, aliases=['entry'])
),
SOA=dict(
host=dict(type='str', aliases=['entry']),
email=dict(type='str'),
serial_number=dict(type='long'),
refresh_time=dict(type='long'),
retry_time=dict(type='long'),
expire_time=dict(type='long'),
minimum_ttl=dict(type='long')
),
CAA=dict(
value=dict(type='str', aliases=['entry']),
flags=dict(type='int'),
tag=dict(type='str')
)
# FUTURE: ensure all record types are supported (see https://github.com/Azure/azure-sdk-for-python/tree/master/azure-mgmt-dns/azure/mgmt/dns/models)
)

RECORDSET_VALUE_MAP = dict(
A=dict(attrname='arecords', classobj=ARecord, is_list=True),
AAAA=dict(attrname='aaaa_records', classobj=AaaaRecord, is_list=True),
CNAME=dict(attrname='cname_record', classobj=CnameRecord, is_list=False),
MX=dict(attrname='mx_records', classobj=MxRecord, is_list=True),
NS=dict(attrname='ns_records', classobj=NsRecord, is_list=True),
PTR=dict(attrname='ptr_records', classobj=PtrRecord, is_list=True),
SRV=dict(attrname='srv_records', classobj=SrvRecord, is_list=True),
TXT=dict(attrname='txt_records', classobj=TxtRecord, is_list=True),
A=dict(attrname='arecords', classobj='ARecord', is_list=True),
AAAA=dict(attrname='aaaa_records', classobj='AaaaRecord', is_list=True),
CNAME=dict(attrname='cname_record', classobj='CnameRecord', is_list=False),
MX=dict(attrname='mx_records', classobj='MxRecord', is_list=True),
NS=dict(attrname='ns_records', classobj='NsRecord', is_list=True),
PTR=dict(attrname='ptr_records', classobj='PtrRecord', is_list=True),
SRV=dict(attrname='srv_records', classobj='SrvRecord', is_list=True),
TXT=dict(attrname='txt_records', classobj='TxtRecord', is_list=True),
SOA=dict(attrname='soa_record', classobj='SoaRecord', is_list=False),
CAA=dict(attrname='caa_records', classobj='CaaRecord', is_list=True)
# FUTURE: add missing record types from https://github.com/Azure/azure-sdk-for-python/blob/master/azure-mgmt-dns/azure/mgmt/dns/models/record_set.py
) if HAS_AZURE else {}

Expand Down Expand Up @@ -261,14 +278,18 @@ def __init__(self):

# look up the right subspec and metadata
record_subspec = RECORD_ARGSPECS.get(self.module.params['record_type'])
self.record_type_metadata = RECORDSET_VALUE_MAP.get(self.module.params['record_type'])

# patch the right record shape onto the argspec
self.module_arg_spec['records']['options'] = record_subspec

# monkeypatch __hash__ on SDK model objects so we can safely use them in sets
for rvm in RECORDSET_VALUE_MAP.values():
rvm['classobj'].__hash__ = gethash
self.resource_group = None
self.relative_name = None
self.zone_name = None
self.record_type = None
self.record_mode = None
self.state = None
self.time_to_live = None
self.records = None

# rerun validation and actually run the module this time
super(AzureRMRecordSet, self).__init__(self.module_arg_spec, required_if=required_if, supports_check_mode=True)
Expand All @@ -286,24 +307,27 @@ def exec_module(self, **kwargs):
try:
self.log('Fetching Record Set {0}'.format(self.relative_name))
record_set = self.dns_client.record_sets.get(self.resource_group, self.zone_name, self.relative_name, self.record_type)
except CloudError as ce:
self.results['state'] = self.recordset_to_dict(record_set)
except CloudError:
record_set = None
# FUTURE: fail on anything other than ResourceNotFound

record_type_metadata = RECORDSET_VALUE_MAP.get(self.record_type)

# FUTURE: implement diff mode

if self.state == 'present':
# convert the input records to SDK objects
self.input_sdk_records = self.create_sdk_records(self.records)
self.input_sdk_records = self.create_sdk_records(self.records, self.record_type)

if not record_set:
changed = True
else:
# and use it to get the type-specific records
server_records = getattr(record_set, self.record_type_metadata['attrname'])
server_records = getattr(record_set, record_type_metadata.get('attrname'))

# compare the input records to the server records
changed = self.records_changed(self.input_sdk_records, server_records)
self.input_sdk_records, changed = self.records_changed(self.input_sdk_records, server_records)

# also check top-level recordset properties
changed |= record_set.ttl != self.time_to_live
Expand All @@ -325,64 +349,68 @@ def exec_module(self, **kwargs):
ttl=self.time_to_live
)

if not self.record_type_metadata['is_list']:
records_to_create_or_update = self.input_sdk_records[0]
elif self.record_mode == 'append' and record_set: # append mode, merge with existing values before update
records_to_create_or_update = set(self.input_sdk_records).union(set(server_records))
else:
records_to_create_or_update = self.input_sdk_records
record_set_args[record_type_metadata['attrname']] = self.input_sdk_records if record_type_metadata['is_list'] else self.input_sdk_records[0]

record_set_args[self.record_type_metadata['attrname']] = records_to_create_or_update
record_set = self.dns_models.RecordSet(**record_set_args)

record_set = RecordSet(**record_set_args)

rsout = self.dns_client.record_sets.create_or_update(self.resource_group, self.zone_name, self.relative_name, self.record_type, record_set)
self.results['state'] = self.create_or_update(record_set)

elif self.state == 'absent':
# delete record set
self.delete_record_set()

return self.results

def create_or_update(self, record_set):
try:
record_set = self.dns_client.record_sets.create_or_update(resource_group_name=self.resource_group,
zone_name=self.zone_name,
relative_record_set_name=self.relative_name,
record_type=self.record_type,
parameters=record_set)
return self.recordset_to_dict(record_set)
except Exception as exc:
self.fail("Error creating or updating dns record {0} - {1}".format(self.relative_name, exc.message or str(exc)))

def delete_record_set(self):
try:
# delete the record set
self.dns_client.record_sets.delete(self.resource_group, self.zone_name, self.relative_name, self.record_type)
self.dns_client.record_sets.delete(resource_group_name=self.resource_group,
zone_name=self.zone_name,
relative_record_set_name=self.relative_name,
record_type=self.record_type)
except Exception as exc:
self.fail("Error deleting record set {0} - {1}".format(self.relative_name, str(exc)))
self.fail("Error deleting record set {0} - {1}".format(self.relative_name, exc.message or str(exc)))
return None

def create_sdk_records(self, input_records):
record_sdk_class = self.record_type_metadata['classobj']
record_argspec = inspect.getargspec(record_sdk_class.__init__)
return [record_sdk_class(**dict([(k, v) for k, v in iteritems(x) if k in record_argspec.args])) for x in input_records]
def create_sdk_records(self, input_records, record_type):
record = RECORDSET_VALUE_MAP.get(record_type)
if not record:
self.fail('record type {0} is not supported now'.format(record_type))
record_sdk_class = getattr(self.dns_models, record.get('classobj'))
return [record_sdk_class(**x) for x in input_records]

def records_changed(self, input_records, server_records):
# ensure we're always comparing a list, even for the single-valued types
if not isinstance(server_records, list):
server_records = [server_records]

input_set = set(input_records)
server_set = set(server_records)
input_set = set([self.module.jsonify(x.as_dict()) for x in input_records])
server_set = set([self.module.jsonify(x.as_dict()) for x in server_records])

if self.record_mode == 'append': # only a difference if the server set is missing something from the input set
return len(input_set.difference(server_set)) > 0
input_set = server_set.union(input_set)

# non-append mode; any difference in the sets is a change
return input_set != server_set
changed = input_set != server_set

records = [self.module.from_json(x) for x in input_set]
return self.create_sdk_records(records, self.record_type), changed

# Quick 'n dirty hash impl suitable for monkeypatching onto SDK model objects (so we can use set comparisons)
def gethash(self):
if not getattr(self, '_cachedhash', None):
spec = inspect.getargspec(self.__init__)
valuetuple = tuple(
map(lambda v: v if not isinstance(v, list) else str(v), [
getattr(self, x, None) for x in spec.args if x != 'self'
])
)
self._cachedhash = hash(valuetuple)
return self._cachedhash
def recordset_to_dict(self, recordset):
result = recordset.as_dict()
result['type'] = result['type'].strip('Microsoft.Network/dnszones/')
return result


def main():
Expand Down
Loading

0 comments on commit 0f6252b

Please sign in to comment.