dns zone enhancement and return curated value (#50740)

This commit is contained in:
Yuwei Zhou 2019-01-11 16:38:29 +08:00 committed by Yunge Zhu
parent 9487815a26
commit 0f6252baf3
8 changed files with 445 additions and 118 deletions

View file

@ -220,7 +220,7 @@ AZURE_PKG_VERSIONS = {
}, },
'DnsManagementClient': { 'DnsManagementClient': {
'package_name': 'dns', 'package_name': 'dns',
'expected_version': '1.2.0' 'expected_version': '2.1.0'
}, },
'WebSiteManagementClient': { 'WebSiteManagementClient': {
'package_name': 'web', 'package_name': 'web',
@ -839,9 +839,15 @@ class AzureRMModuleBase(object):
self.log('Getting dns client') self.log('Getting dns client')
if not self._dns_client: if not self._dns_client:
self._dns_client = self.get_mgmt_svc_client(DnsManagementClient, 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 return self._dns_client
@property
def dns_models(self):
self.log("Getting dns models...")
return DnsManagementClient.models('2018-05-01')
@property @property
def web_client(self): def web_client(self):
self.log('Getting web client') self.log('Getting web client')

View file

@ -48,6 +48,8 @@ options:
- SRV - SRV
- TXT - TXT
- PTR - PTR
- CAA
- SOA
required: true required: true
record_mode: record_mode:
description: description:
@ -179,7 +181,6 @@ from ansible.module_utils.azure_rm_common import AzureRMModuleBase, HAS_AZURE
try: try:
from msrestazure.azure_exceptions import CloudError 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: except ImportError:
# This is handled in azure_rm_common # This is handled in azure_rm_common
pass pass
@ -214,18 +215,34 @@ RECORD_ARGSPECS = dict(
TXT=dict( TXT=dict(
value=dict(type='list', required=True, aliases=['entry']) 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) # 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( RECORDSET_VALUE_MAP = dict(
A=dict(attrname='arecords', classobj=ARecord, is_list=True), A=dict(attrname='arecords', classobj='ARecord', is_list=True),
AAAA=dict(attrname='aaaa_records', classobj=AaaaRecord, is_list=True), AAAA=dict(attrname='aaaa_records', classobj='AaaaRecord', is_list=True),
CNAME=dict(attrname='cname_record', classobj=CnameRecord, is_list=False), CNAME=dict(attrname='cname_record', classobj='CnameRecord', is_list=False),
MX=dict(attrname='mx_records', classobj=MxRecord, is_list=True), MX=dict(attrname='mx_records', classobj='MxRecord', is_list=True),
NS=dict(attrname='ns_records', classobj=NsRecord, is_list=True), NS=dict(attrname='ns_records', classobj='NsRecord', is_list=True),
PTR=dict(attrname='ptr_records', classobj=PtrRecord, is_list=True), PTR=dict(attrname='ptr_records', classobj='PtrRecord', is_list=True),
SRV=dict(attrname='srv_records', classobj=SrvRecord, is_list=True), SRV=dict(attrname='srv_records', classobj='SrvRecord', is_list=True),
TXT=dict(attrname='txt_records', classobj=TxtRecord, 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 # 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 {} ) if HAS_AZURE else {}
@ -261,14 +278,18 @@ class AzureRMRecordSet(AzureRMModuleBase):
# look up the right subspec and metadata # look up the right subspec and metadata
record_subspec = RECORD_ARGSPECS.get(self.module.params['record_type']) 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 # patch the right record shape onto the argspec
self.module_arg_spec['records']['options'] = record_subspec self.module_arg_spec['records']['options'] = record_subspec
# monkeypatch __hash__ on SDK model objects so we can safely use them in sets self.resource_group = None
for rvm in RECORDSET_VALUE_MAP.values(): self.relative_name = None
rvm['classobj'].__hash__ = gethash 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 # 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) super(AzureRMRecordSet, self).__init__(self.module_arg_spec, required_if=required_if, supports_check_mode=True)
@ -286,24 +307,27 @@ class AzureRMRecordSet(AzureRMModuleBase):
try: try:
self.log('Fetching Record Set {0}'.format(self.relative_name)) 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) 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 record_set = None
# FUTURE: fail on anything other than ResourceNotFound # FUTURE: fail on anything other than ResourceNotFound
record_type_metadata = RECORDSET_VALUE_MAP.get(self.record_type)
# FUTURE: implement diff mode # FUTURE: implement diff mode
if self.state == 'present': if self.state == 'present':
# convert the input records to SDK objects # 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: if not record_set:
changed = True changed = True
else: else:
# and use it to get the type-specific records # 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 # 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 # also check top-level recordset properties
changed |= record_set.ttl != self.time_to_live changed |= record_set.ttl != self.time_to_live
@ -325,18 +349,11 @@ class AzureRMRecordSet(AzureRMModuleBase):
ttl=self.time_to_live ttl=self.time_to_live
) )
if not self.record_type_metadata['is_list']: record_set_args[record_type_metadata['attrname']] = self.input_sdk_records if record_type_metadata['is_list'] else self.input_sdk_records[0]
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[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) self.results['state'] = self.create_or_update(record_set)
rsout = self.dns_client.record_sets.create_or_update(self.resource_group, self.zone_name, self.relative_name, self.record_type, record_set)
elif self.state == 'absent': elif self.state == 'absent':
# delete record set # delete record set
@ -344,45 +361,56 @@ class AzureRMRecordSet(AzureRMModuleBase):
return self.results 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): def delete_record_set(self):
try: try:
# delete the record set # 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: 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 return None
def create_sdk_records(self, input_records): def create_sdk_records(self, input_records, record_type):
record_sdk_class = self.record_type_metadata['classobj'] record = RECORDSET_VALUE_MAP.get(record_type)
record_argspec = inspect.getargspec(record_sdk_class.__init__) if not record:
return [record_sdk_class(**dict([(k, v) for k, v in iteritems(x) if k in record_argspec.args])) for x in input_records] 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): def records_changed(self, input_records, server_records):
# ensure we're always comparing a list, even for the single-valued types # ensure we're always comparing a list, even for the single-valued types
if not isinstance(server_records, list): if not isinstance(server_records, list):
server_records = [server_records] server_records = [server_records]
input_set = set(input_records) input_set = set([self.module.jsonify(x.as_dict()) for x in input_records])
server_set = set(server_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 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 # 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 recordset_to_dict(self, recordset):
def gethash(self): result = recordset.as_dict()
if not getattr(self, '_cachedhash', None): result['type'] = result['type'].strip('Microsoft.Network/dnszones/')
spec = inspect.getargspec(self.__init__) return result
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 main(): def main():

View file

@ -38,7 +38,7 @@ options:
top: top:
description: description:
- Limit the maximum number of record sets to return - Limit the maximum number of record sets to return
default: 100 type: int
extends_documentation_fragment: extends_documentation_fragment:
- azure - azure
@ -91,6 +91,41 @@ azure_dnsrecordset:
"type": "Microsoft.Network/dnszones/A" "type": "Microsoft.Network/dnszones/A"
} }
] ]
dnsrecordsets:
description: List of record set dicts, which shares the same hierarchy as azure_rm_dnsrecordset module's parameter.
returned: always
type: list
contains:
id:
description: ID of the dns recordset.
sample: "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/testing/providers/Microsoft.Network/dnszones/newzone.com/A/servera"
relative_name:
description: Name of the dns recordset.
sample: servera
record_type:
description:
- The type of the record set.
- Can be C(A), C(AAAA), C(CNAME), C(MX), C(NS), C(SRV), C(TXT), C(PTR).
sample: A
time_to_live:
description: Time to live of the record set in seconds.
sample: 12900
records:
description: List of records depending on the type of recordset.
sample: [
{
"ipv4Address": "10.4.5.7"
},
{
"ipv4Address": "2.4.5.8"
}
]
provisioning_state:
description: Provision state of the resource.
sample: Successed
fqdn:
description: Fully qualified domain name of the record set.
sample: www.newzone.com
''' '''
from ansible.module_utils.azure_rm_common import AzureRMModuleBase from ansible.module_utils.azure_rm_common import AzureRMModuleBase
@ -105,6 +140,21 @@ except Exception:
AZURE_OBJECT_CLASS = 'RecordSet' AZURE_OBJECT_CLASS = 'RecordSet'
RECORDSET_VALUE_MAP = dict(
A='arecords',
AAAA='aaaa_records',
CNAME='cname_record',
MX='mx_records',
NS='ns_records',
PTR='ptr_records',
SRV='srv_records',
TXT='txt_records',
SOA='soa_record',
CAA='caa_records'
# 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
)
class AzureRMRecordSetFacts(AzureRMModuleBase): class AzureRMRecordSetFacts(AzureRMModuleBase):
def __init__(self): def __init__(self):
@ -115,7 +165,7 @@ class AzureRMRecordSetFacts(AzureRMModuleBase):
resource_group=dict(type='str'), resource_group=dict(type='str'),
zone_name=dict(type='str'), zone_name=dict(type='str'),
record_type=dict(type='str'), record_type=dict(type='str'),
top=dict(type='str', default='100') top=dict(type='int')
) )
# store the results of the module operation # store the results of the module operation
@ -137,22 +187,29 @@ class AzureRMRecordSetFacts(AzureRMModuleBase):
for key in self.module_arg_spec: for key in self.module_arg_spec:
setattr(self, key, kwargs[key]) setattr(self, key, kwargs[key])
if not self.top or self.top <= 0:
self.top = None
# create conditionals to catch errors when calling record facts # create conditionals to catch errors when calling record facts
if self.relative_name and not self.resource_group: if self.relative_name and not self.resource_group:
self.fail("Parameter error: resource group required when filtering by name or record type.") self.fail("Parameter error: resource group required when filtering by name or record type.")
if self.relative_name and not self.zone_name: if self.relative_name and not self.zone_name:
self.fail("Parameter error: DNS Zone required when filtering by name or record type.") self.fail("Parameter error: DNS Zone required when filtering by name or record type.")
results = []
# list the conditions for what to return based on input # list the conditions for what to return based on input
if self.relative_name is not None: if self.relative_name is not None:
# if there is a name listed, they want only facts about that specific Record Set itself # if there is a name listed, they want only facts about that specific Record Set itself
self.results['ansible_facts']['azure_dnsrecordset'] = self.get_item() results = self.get_item()
elif self.record_type: elif self.record_type:
# else, they just want all the record sets of a specific type # else, they just want all the record sets of a specific type
self.results['ansible_facts']['azure_dnsrecordset'] = self.list_type() results = self.list_type()
elif self.zone_name: elif self.zone_name:
# if there is a zone name listed, then they want all the record sets in a zone # if there is a zone name listed, then they want all the record sets in a zone
self.results['ansible_facts']['azure_dnsrecordset'] = self.list_zone() results = self.list_zone()
self.results['ansible_facts']['azure_dnsrecordset'] = self.serialize_list(results)
self.results['dnsrecordsets'] = self.curated_list(results)
return self.results return self.results
def get_item(self): def get_item(self):
@ -166,33 +223,54 @@ class AzureRMRecordSetFacts(AzureRMModuleBase):
except CloudError: except CloudError:
pass pass
results = [self.serialize_obj(item, AZURE_OBJECT_CLASS)] results = [item]
return results return results
def list_type(self): def list_type(self):
self.log('Lists the record sets of a specified type in a DNS zone') self.log('Lists the record sets of a specified type in a DNS zone')
try: try:
response = self.dns_client.record_sets.list_by_type(self.resource_group, self.zone_name, self.record_type, top=int(self.top)) response = self.dns_client.record_sets.list_by_type(self.resource_group, self.zone_name, self.record_type, top=self.top)
except AzureHttpError as exc: except AzureHttpError as exc:
self.fail("Failed to list for record type {0} - {1}".format(self.record_type, str(exc))) self.fail("Failed to list for record type {0} - {1}".format(self.record_type, str(exc)))
results = [] results = []
for item in response: for item in response:
results.append(self.serialize_obj(item, AZURE_OBJECT_CLASS)) results.append(item)
return results return results
def list_zone(self): def list_zone(self):
self.log('Lists all record sets in a DNS zone') self.log('Lists all record sets in a DNS zone')
try: try:
response = self.dns_client.record_sets.list_by_dns_zone(self.resource_group, self.zone_name, top=int(self.top)) response = self.dns_client.record_sets.list_by_dns_zone(self.resource_group, self.zone_name, top=self.top)
except AzureHttpError as exc: except AzureHttpError as exc:
self.fail("Failed to list for zone {0} - {1}".format(self.zone_name, str(exc))) self.fail("Failed to list for zone {0} - {1}".format(self.zone_name, str(exc)))
results = [] results = []
for item in response: for item in response:
results.append(self.serialize_obj(item, AZURE_OBJECT_CLASS)) results.append(item)
return results return results
def serialize_list(self, raws):
return [self.serialize_obj(item, AZURE_OBJECT_CLASS) for item in raws] if raws else []
def curated_list(self, raws):
return [self.record_to_dict(item) for item in raws] if raws else []
def record_to_dict(self, record):
record_type = record.type[len('Microsoft.Network/dnszones/'):]
records = getattr(record, RECORDSET_VALUE_MAP.get(record_type))
if not isinstance(records, list):
records = [records]
return dict(
id=record.id,
relative_name=record.name,
record_type=record_type,
records=[x.as_dict() for x in records],
time_to_live=record.ttl,
fqdn=record.fqdn,
provisioning_state=record.provisioning_state
)
def main(): def main():
AzureRMRecordSetFacts() AzureRMRecordSetFacts()

View file

@ -53,6 +53,27 @@ options:
choices: choices:
- absent - absent
- present - present
type:
description:
- The type of this DNS zone (public or private)
choices:
- public
- private
version_added: 2.8
registration_virtual_networks:
description:
- A list of references to virtual networks that register hostnames in this DNS zone.
- This is a only when I(type) is C(private).
- Each element can be the name or resource id, or a dict contains C(name), C(resource_group) information of the virtual network.
version_added: 2.8
type: list
resolution_virtual_networks:
description:
- A list of references to virtual networks that resolve records in this DNS zone.
- This is a only when I(type) is C(private).
- Each element can be the name or resource id, or a dict contains C(name), C(resource_group) information of the virtual network.
version_added: 2.8
type: list
extends_documentation_fragment: extends_documentation_fragment:
- azure - azure
@ -92,16 +113,18 @@ state:
"ns3-07.azure-dns.org.", "ns3-07.azure-dns.org.",
"ns4-07.azure-dns.info." "ns4-07.azure-dns.info."
], ],
"number_of_record_sets": 2 "number_of_record_sets": 2,
"type": "private",
"resolution_virtual_networks": ["/subscriptions/XXXX/resourceGroups/Testing/providers/Microsoft.Network/virtualNetworks/foo"]
} }
''' '''
from ansible.module_utils.azure_rm_common import AzureRMModuleBase from ansible.module_utils.azure_rm_common import AzureRMModuleBase, format_resource_id
from ansible.module_utils._text import to_native
try: try:
from msrestazure.azure_exceptions import CloudError from msrestazure.azure_exceptions import CloudError
from azure.mgmt.dns.models import Zone
except ImportError: except ImportError:
# This is handled in azure_rm_common # This is handled in azure_rm_common
pass pass
@ -115,7 +138,10 @@ class AzureRMDNSZone(AzureRMModuleBase):
self.module_arg_spec = dict( self.module_arg_spec = dict(
resource_group=dict(type='str', required=True), resource_group=dict(type='str', required=True),
name=dict(type='str', required=True), name=dict(type='str', required=True),
state=dict(choices=['present', 'absent'], default='present', type='str') state=dict(choices=['present', 'absent'], default='present', type='str'),
type=dict(type='str', choices=['private', 'public']),
registration_virtual_networks=dict(type='list', elements='raw'),
resolution_virtual_networks=dict(type='list', elements='raw')
) )
# store the results of the module operation # store the results of the module operation
@ -128,6 +154,9 @@ class AzureRMDNSZone(AzureRMModuleBase):
self.name = None self.name = None
self.state = None self.state = None
self.tags = None self.tags = None
self.type = None
self.registration_virtual_networks = None
self.resolution_virtual_networks = None
super(AzureRMDNSZone, self).__init__(self.module_arg_spec, super(AzureRMDNSZone, self).__init__(self.module_arg_spec,
supports_check_mode=True, supports_check_mode=True,
@ -140,6 +169,9 @@ class AzureRMDNSZone(AzureRMModuleBase):
for key in list(self.module_arg_spec.keys()) + ['tags']: for key in list(self.module_arg_spec.keys()) + ['tags']:
setattr(self, key, kwargs[key]) setattr(self, key, kwargs[key])
self.registration_virtual_networks = self.preprocess_vn_list(self.registration_virtual_networks)
self.resolution_virtual_networks = self.preprocess_vn_list(self.resolution_virtual_networks)
self.results['check_mode'] = self.check_mode self.results['check_mode'] = self.check_mode
# retrieve resource group to make sure it exists # retrieve resource group to make sure it exists
@ -162,7 +194,22 @@ class AzureRMDNSZone(AzureRMModuleBase):
update_tags, results['tags'] = self.update_tags(results['tags']) update_tags, results['tags'] = self.update_tags(results['tags'])
if update_tags: if update_tags:
changed = True changed = True
if self.type and results['type'] != self.type:
changed = True
results['type'] = self.type
if self.resolution_virtual_networks:
if set(self.resolution_virtual_networks) != set(results['resolution_virtual_networks'] or []):
changed = True
results['resolution_virtual_networks'] = self.resolution_virtual_networks
else:
# this property should not be changed
self.resolution_virtual_networks = results['resolution_virtual_networks']
if self.registration_virtual_networks:
if set(self.registration_virtual_networks) != set(results['registration_virtual_networks'] or []):
changed = True
results['registration_virtual_networks'] = self.registration_virtual_networks
else:
self.registration_virtual_networks = results['registration_virtual_networks']
elif self.state == 'absent': elif self.state == 'absent':
changed = True changed = True
@ -183,16 +230,13 @@ class AzureRMDNSZone(AzureRMModuleBase):
if changed: if changed:
if self.state == 'present': if self.state == 'present':
if not zone: zone = self.dns_models.Zone(zone_type=str.capitalize(self.type) if self.type else None,
# create new zone tags=self.tags,
self.log('Creating zone {0}'.format(self.name)) location='global')
zone = Zone(location='global', tags=self.tags) if self.resolution_virtual_networks:
else: zone.resolution_virtual_networks = self.construct_subresource_list(self.resolution_virtual_networks)
# update zone if self.registration_virtual_networks:
zone = Zone( zone.registration_virtual_networks = self.construct_subresource_list(self.registration_virtual_networks)
location='global',
tags=results['tags']
)
self.results['state'] = self.create_or_update_zone(zone) self.results['state'] = self.create_or_update_zone(zone)
elif self.state == 'absent': elif self.state == 'absent':
# delete zone # delete zone
@ -208,7 +252,7 @@ class AzureRMDNSZone(AzureRMModuleBase):
# create or update the new Zone object we created # create or update the new Zone object we created
new_zone = self.dns_client.zones.create_or_update(self.resource_group, self.name, zone) new_zone = self.dns_client.zones.create_or_update(self.resource_group, self.name, zone)
except Exception as exc: except Exception as exc:
self.fail("Error creating or updating zone {0} - {1}".format(self.name, str(exc))) self.fail("Error creating or updating zone {0} - {1}".format(self.name, exc.message or str(exc)))
return zone_to_dict(new_zone) return zone_to_dict(new_zone)
def delete_zone(self): def delete_zone(self):
@ -217,9 +261,23 @@ class AzureRMDNSZone(AzureRMModuleBase):
poller = self.dns_client.zones.delete(self.resource_group, self.name) poller = self.dns_client.zones.delete(self.resource_group, self.name)
result = self.get_poller_result(poller) result = self.get_poller_result(poller)
except Exception as exc: except Exception as exc:
self.fail("Error deleting zone {0} - {1}".format(self.name, str(exc))) self.fail("Error deleting zone {0} - {1}".format(self.name, exc.message or str(exc)))
return result return result
def preprocess_vn_list(self, vn_list):
return [self.parse_vn_id(x) for x in vn_list] if vn_list else None
def parse_vn_id(self, vn):
vn_dict = self.parse_resource_to_dict(vn) if not isinstance(vn, dict) else vn
return format_resource_id(val=vn_dict['name'],
subscription_id=vn_dict.get('subscription') or self.subscription_id,
namespace='Microsoft.Network',
types='virtualNetworks',
resource_group=vn_dict.get('resource_group') or self.resource_group)
def construct_subresource_list(self, raw):
return [self.dns_models.SubResource(id=x) for x in raw] if raw else None
def zone_to_dict(zone): def zone_to_dict(zone):
# turn Zone object into a dictionary (serialization) # turn Zone object into a dictionary (serialization)
@ -228,7 +286,10 @@ def zone_to_dict(zone):
name=zone.name, name=zone.name,
number_of_record_sets=zone.number_of_record_sets, number_of_record_sets=zone.number_of_record_sets,
name_servers=zone.name_servers, name_servers=zone.name_servers,
tags=zone.tags tags=zone.tags,
type=zone.zone_type.value.lower(),
registration_virtual_networks=[to_native(x.id) for x in zone.registration_virtual_networks] if zone.registration_virtual_networks else None,
resolution_virtual_networks=[to_native(x.id) for x in zone.resolution_virtual_networks] if zone.resolution_virtual_networks else None
) )
return result return result

View file

@ -73,9 +73,52 @@ azure_dnszones:
}, },
"tags": {} "tags": {}
}] }]
dnszones:
description: List of zone dicts, which share the same layout as azure_rm_dnszone module parameter.
returned: always
type: list
contains:
id:
description:
- id of the DNS Zone.
sample: "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/foo/providers/Microsoft.Network/dnszones/azure.com"
name:
description:
- name of the DNS Zone.
sample: azure.com
type:
description:
- The type of this DNS zone (public or private)
sample: private
registration_virtual_networks:
description:
- A list of references to virtual networks that register hostnames in this DNS zone.
sample: ["/subscriptions/XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/foo/providers/Microsoft.Network/virtualNetworks/bar"]
resolution_virtual_networks:
description:
- A list of references to virtual networks that resolve records in this DNS zone.
sample: ["/subscriptions/XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/foo/providers/Microsoft.Network/virtualNetworks/deadbeef"]
number_of_record_sets:
description:
- The current number of record sets in this DNS zone.
sample: 2
max_number_of_record_sets:
description:
- The maximum number of record sets that can be created in this DNS zone.
sample: 5000
name_servers:
description:
- The name servers for this DNS zone.
sample: [
"ns1-03.azure-dns.com.",
"ns2-03.azure-dns.net.",
"ns3-03.azure-dns.org.",
"ns4-03.azure-dns.info."
]
''' '''
from ansible.module_utils.azure_rm_common import AzureRMModuleBase from ansible.module_utils.azure_rm_common import AzureRMModuleBase
from ansible.module_utils._text import to_native
try: try:
from msrestazure.azure_exceptions import CloudError from msrestazure.azure_exceptions import CloudError
@ -118,17 +161,20 @@ class AzureRMDNSZoneFacts(AzureRMModuleBase):
if self.name and not self.resource_group: if self.name and not self.resource_group:
self.fail("Parameter error: resource group required when filtering by name.") self.fail("Parameter error: resource group required when filtering by name.")
results = []
# list the conditions and what to return based on user input # list the conditions and what to return based on user input
if self.name is not None: if self.name is not None:
# if there is a name, facts about that specific zone # if there is a name, facts about that specific zone
self.results['ansible_facts']['azure_dnszones'] = self.get_item() results = self.get_item()
elif self.resource_group: elif self.resource_group:
# all the zones listed in that specific resource group # all the zones listed in that specific resource group
self.results['ansible_facts']['azure_dnszones'] = self.list_resource_group() results = self.list_resource_group()
else: else:
# all the zones in a subscription # all the zones in a subscription
self.results['ansible_facts']['azure_dnszones'] = self.list_items() results = self.list_items()
self.results['ansible_facts']['azure_dnszones'] = self.serialize_items(results)
self.results['dnszones'] = self.curated_items(results)
return self.results return self.results
@ -144,7 +190,7 @@ class AzureRMDNSZoneFacts(AzureRMModuleBase):
# serialize result # serialize result
if item and self.has_tags(item.tags, self.tags): if item and self.has_tags(item.tags, self.tags):
results = [self.serialize_obj(item, AZURE_OBJECT_CLASS)] results = [item]
return results return results
def list_resource_group(self): def list_resource_group(self):
@ -157,7 +203,7 @@ class AzureRMDNSZoneFacts(AzureRMModuleBase):
results = [] results = []
for item in response: for item in response:
if self.has_tags(item.tags, self.tags): if self.has_tags(item.tags, self.tags):
results.append(self.serialize_obj(item, AZURE_OBJECT_CLASS)) results.append(item)
return results return results
def list_items(self): def list_items(self):
@ -170,9 +216,28 @@ class AzureRMDNSZoneFacts(AzureRMModuleBase):
results = [] results = []
for item in response: for item in response:
if self.has_tags(item.tags, self.tags): if self.has_tags(item.tags, self.tags):
results.append(self.serialize_obj(item, AZURE_OBJECT_CLASS)) results.append(item)
return results return results
def serialize_items(self, raws):
return [self.serialize_obj(item, AZURE_OBJECT_CLASS) for item in raws] if raws else []
def curated_items(self, raws):
return [self.zone_to_dict(item) for item in raws] if raws else []
def zone_to_dict(self, zone):
return dict(
id=zone.id,
name=zone.name,
number_of_record_sets=zone.number_of_record_sets,
max_number_of_record_sets=zone.max_number_of_record_sets,
name_servers=zone.name_servers,
tags=zone.tags,
type=zone.zone_type.value.lower(),
registration_virtual_networks=[to_native(x.id) for x in zone.registration_virtual_networks] if zone.registration_virtual_networks else None,
resolution_virtual_networks=[to_native(x.id) for x in zone.resolution_virtual_networks] if zone.resolution_virtual_networks else None
)
def main(): def main():
AzureRMDNSZoneFacts() AzureRMDNSZoneFacts()

View file

@ -9,7 +9,7 @@ azure-mgmt-compute==4.3.1
azure-mgmt-containerinstance==0.4.0 azure-mgmt-containerinstance==0.4.0
azure-mgmt-containerregistry==2.0.0 azure-mgmt-containerregistry==2.0.0
azure-mgmt-containerservice==4.2.2 azure-mgmt-containerservice==4.2.2
azure-mgmt-dns==1.2.0 azure-mgmt-dns==2.1.0
azure-mgmt-keyvault==0.40.0 azure-mgmt-keyvault==0.40.0
azure-mgmt-marketplaceordering==0.1.0 azure-mgmt-marketplaceordering==0.1.0
azure-mgmt-monitor==0.5.2 azure-mgmt-monitor==0.5.2

View file

@ -2,11 +2,20 @@
set_fact: set_fact:
domain_name: "{{ resource_group | hash('md5') | truncate(16, True, '') + (65535 | random | string) }}" domain_name: "{{ resource_group | hash('md5') | truncate(16, True, '') + (65535 | random | string) }}"
- name: Create a DNS zone (check mode)
azure_rm_dnszone:
resource_group: "{{ resource_group }}"
name: "{{ domain_name }}.com"
register: results
check_mode: yes
- assert:
that: results.changed
- name: Create a DNS zone - name: Create a DNS zone
azure_rm_dnszone: azure_rm_dnszone:
resource_group: "{{ resource_group }}" resource_group: "{{ resource_group }}"
name: "{{ domain_name }}.com" name: "{{ domain_name }}.com"
state: present
register: results register: results
- assert: - assert:
@ -16,7 +25,6 @@
azure_rm_dnszone: azure_rm_dnszone:
resource_group: "{{ resource_group }}" resource_group: "{{ resource_group }}"
name: "{{ domain_name }}.com" name: "{{ domain_name }}.com"
state: present
tags: tags:
test: modified test: modified
register: results register: results
@ -26,33 +34,79 @@
- results.changed - results.changed
- results.state.tags.test == 'modified' - results.state.tags.test == 'modified'
- name: Test check_mode
azure_rm_dnszone:
resource_group: "{{ resource_group }}"
name: "{{ domain_name }}.com"
state: present
tags:
test: new_modified
check_mode: yes
register: results
- assert:
that:
- results.changed
- results.state.tags.test == 'new_modified'
- results.check_mode == true
- name: Retrieve DNS Zone Facts - name: Retrieve DNS Zone Facts
azure_rm_dnszone_facts: azure_rm_dnszone_facts:
resource_group: "{{ resource_group }}" resource_group: "{{ resource_group }}"
name: "{{ domain_name }}.com" name: "{{ domain_name }}.com"
register: results register: zones
- name: Assert that facts module returned result - name: Assert that facts module returned result
assert: assert:
that: that:
- not results.changed - azure_dnszones[0].tags.test == 'modified'
- results.ansible_facts.azure_dnszones[0].tags.test == 'modified' - zones.dnszones[0].type == 'public'
- name: Create virtual network
azure_rm_virtualnetwork:
resource_group: "{{ resource_group }}"
name: "{{ item }}"
address_prefixes_cidr:
- 10.1.0.0/16
- 172.100.0.0/16
with_items:
- "{{ domain_name }}registration1"
- "{{ domain_name }}resolution1"
- "{{ domain_name }}registration2"
- "{{ domain_name }}resolution2"
- name: Create private dns zone
azure_rm_dnszone:
name: "{{ domain_name }}.private"
resource_group: "{{ resource_group }}"
type: private
registration_virtual_networks:
- name: "{{ domain_name }}registration1"
resolution_virtual_networks:
- name: "{{ domain_name }}resolution1"
- name: "{{ domain_name }}resolution2"
register: results
- assert:
that:
- "results.state.registration_virtual_networks | length == 1"
- "results.state.resolution_virtual_networks | length == 2"
- results.state.type == 'private'
- name: Update private dns zone
azure_rm_dnszone:
name: "{{ domain_name }}.private"
resource_group: "{{ resource_group }}"
type: private
registration_virtual_networks:
- name: "{{ domain_name }}registration1"
resolution_virtual_networks:
- name: "{{ domain_name }}resolution1"
register: results
- assert:
that:
- "results.state.registration_virtual_networks | length == 1"
- "results.state.resolution_virtual_networks | length == 1"
- results.state.type == 'private'
- name: Test idempotent
azure_rm_dnszone:
name: "{{ item }}"
resource_group: "{{ resource_group }}"
with_items:
- "{{ domain_name }}.com"
- "{{ domain_name }}.private"
register: results
- assert:
that:
- "not {{ item.changed }}"
with_items: "{{ results.results }}"
# #
# azure_rm_dnsrecordset test # azure_rm_dnsrecordset test
@ -72,7 +126,9 @@
- name: Assert that A record set was created - name: Assert that A record set was created
assert: assert:
that: results.changed that:
- results.changed
- 'results.state.arecords | length == 3'
- name: re-run "A" record with same values - name: re-run "A" record with same values
azure_rm_dnsrecordset: azure_rm_dnsrecordset:
@ -105,6 +161,7 @@
assert: assert:
that: that:
- results.changed - results.changed
- 'results.state.arecords | length == 4'
- name: re-update "A" record set with additional record - name: re-update "A" record set with additional record
azure_rm_dnsrecordset: azure_rm_dnsrecordset:
@ -138,6 +195,7 @@
assert: assert:
that: that:
- results.changed - results.changed
- 'results.state.arecords | length == 3'
- name: Check_mode test - name: Check_mode test
azure_rm_dnsrecordset: azure_rm_dnsrecordset:
@ -213,38 +271,56 @@
assert: assert:
that: that:
- not results.changed - not results.changed
- results.ansible_facts.azure_dnsrecordset[0].name == 'www' - azure_dnsrecordset[0].name == 'www'
- results.dnsrecordsets[0].relative_name == 'www'
- 'results.dnsrecordsets[0].records | length == 3'
- results.dnsrecordsets[0].record_type == 'A'
- name: Retrieve DNS Record Set Facts for all Record Sets - name: Retrieve DNS Record Set Facts for all Record Sets
azure_rm_dnsrecordset_facts: azure_rm_dnsrecordset_facts:
resource_group: "{{ resource_group }}" resource_group: "{{ resource_group }}"
zone_name: "{{ domain_name }}.com" zone_name: "{{ domain_name }}.com"
register: results register: facts
- name: Assert that facts module returned result for all Record Sets - name: Assert that facts module returned result for all Record Sets
assert: assert:
that: that:
- not results.changed - not facts.changed
- results.ansible_facts.azure_dnsrecordset[0].name == '@' - facts.ansible_facts.azure_dnsrecordset[0].name == '@'
- results.ansible_facts.azure_dnsrecordset[1].name == '@' - facts.ansible_facts.azure_dnsrecordset[1].name == '@'
- results.ansible_facts.azure_dnsrecordset[4].name == 'www' - facts.ansible_facts.azure_dnsrecordset[4].name == 'www'
# #
# azure_rm_dnsrecordset cleanup # azure_rm_dnsrecordset cleanup
# #
- name: delete a record set - name: delete all record sets except for @
azure_rm_dnsrecordset: azure_rm_dnsrecordset:
resource_group: "{{ resource_group }}" resource_group: "{{ resource_group }}"
relative_name: www relative_name: "{{ item.relative_name }}"
zone_name: "{{ domain_name }}.com" zone_name: "{{ domain_name }}.com"
record_type: A record_type: "{{ item.record_type }}"
state: absent state: absent
with_items: "{{ facts.dnsrecordsets }}"
when:
- item.relative_name != '@'
register: results register: results
- name: Assert that record set deleted - name: Assert that record set deleted
assert: assert:
that: results.changed that: results.changed
- name: Retrieve DNS Record Set Facts for all Record Sets
azure_rm_dnsrecordset_facts:
resource_group: "{{ resource_group }}"
zone_name: "{{ domain_name }}.com"
register: facts
- name: Assert all record set deleted
assert:
that:
- item.relative_name == '@'
with_items: "{{ facts.dnsrecordsets }}"
- name: (idempotence test) re-run record set absent - name: (idempotence test) re-run record set absent
azure_rm_dnsrecordset: azure_rm_dnsrecordset:
resource_group: "{{ resource_group }}" resource_group: "{{ resource_group }}"
@ -262,7 +338,20 @@
# azure_rm_dnszone cleanup # azure_rm_dnszone cleanup
# #
- name: Delete DNS zone - name: Delete DNS zone
azure_rm_dnszone:
resource_group: "{{ resource_group }}"
name: "{{ item }}"
state: absent
with_items:
- "{{ domain_name }}.com"
- "{{ domain_name }}.private"
- name: Delete DNS zone (idempotent)
azure_rm_dnszone: azure_rm_dnszone:
resource_group: "{{ resource_group }}" resource_group: "{{ resource_group }}"
name: "{{ domain_name }}.com" name: "{{ domain_name }}.com"
state: absent state: absent
register: results
- assert:
that: not results.changed

View file

@ -9,7 +9,7 @@ azure-mgmt-compute==4.3.1
azure-mgmt-containerinstance==0.4.0 azure-mgmt-containerinstance==0.4.0
azure-mgmt-containerregistry==2.0.0 azure-mgmt-containerregistry==2.0.0
azure-mgmt-containerservice==4.2.2 azure-mgmt-containerservice==4.2.2
azure-mgmt-dns==1.2.0 azure-mgmt-dns==2.1.0
azure-mgmt-keyvault==0.40.0 azure-mgmt-keyvault==0.40.0
azure-mgmt-marketplaceordering==0.1.0 azure-mgmt-marketplaceordering==0.1.0
azure-mgmt-monitor==0.5.2 azure-mgmt-monitor==0.5.2