From 0f6252baf3903c8cd5629b0dc24ea2e61ea4efe7 Mon Sep 17 00:00:00 2001 From: Yuwei Zhou Date: Fri, 11 Jan 2019 16:38:29 +0800 Subject: [PATCH] dns zone enhancement and return curated value (#50740) --- lib/ansible/module_utils/azure_rm_common.py | 10 +- .../cloud/azure/azure_rm_dnsrecordset.py | 124 ++++++++------ .../azure/azure_rm_dnsrecordset_facts.py | 98 ++++++++++-- .../modules/cloud/azure/azure_rm_dnszone.py | 97 ++++++++--- .../cloud/azure/azure_rm_dnszone_facts.py | 79 ++++++++- packaging/requirements/requirements-azure.txt | 2 +- .../targets/azure_rm_dnszone/tasks/main.yml | 151 ++++++++++++++---- .../requirements/integration.cloud.azure.txt | 2 +- 8 files changed, 445 insertions(+), 118 deletions(-) diff --git a/lib/ansible/module_utils/azure_rm_common.py b/lib/ansible/module_utils/azure_rm_common.py index bce6515072..0bb79fd95d 100644 --- a/lib/ansible/module_utils/azure_rm_common.py +++ b/lib/ansible/module_utils/azure_rm_common.py @@ -220,7 +220,7 @@ AZURE_PKG_VERSIONS = { }, 'DnsManagementClient': { 'package_name': 'dns', - 'expected_version': '1.2.0' + 'expected_version': '2.1.0' }, 'WebSiteManagementClient': { 'package_name': 'web', @@ -839,9 +839,15 @@ class AzureRMModuleBase(object): 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') diff --git a/lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset.py b/lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset.py index 8da678a30a..0ddb8252ee 100644 --- a/lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset.py +++ b/lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset.py @@ -48,6 +48,8 @@ options: - SRV - TXT - PTR + - CAA + - SOA required: true record_mode: description: @@ -179,7 +181,6 @@ from ansible.module_utils.azure_rm_common import AzureRMModuleBase, HAS_AZURE 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 @@ -214,18 +215,34 @@ RECORD_ARGSPECS = dict( 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 {} @@ -261,14 +278,18 @@ class AzureRMRecordSet(AzureRMModuleBase): # 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) @@ -286,24 +307,27 @@ class AzureRMRecordSet(AzureRMModuleBase): 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 @@ -325,18 +349,11 @@ class AzureRMRecordSet(AzureRMModuleBase): 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 @@ -344,45 +361,56 @@ class AzureRMRecordSet(AzureRMModuleBase): 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(): diff --git a/lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset_facts.py b/lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset_facts.py index 3127b3b375..22046f557b 100644 --- a/lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset_facts.py +++ b/lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset_facts.py @@ -38,7 +38,7 @@ options: top: description: - Limit the maximum number of record sets to return - default: 100 + type: int extends_documentation_fragment: - azure @@ -91,6 +91,41 @@ azure_dnsrecordset: "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 @@ -105,6 +140,21 @@ except Exception: 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): def __init__(self): @@ -115,7 +165,7 @@ class AzureRMRecordSetFacts(AzureRMModuleBase): resource_group=dict(type='str'), zone_name=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 @@ -137,22 +187,29 @@ class AzureRMRecordSetFacts(AzureRMModuleBase): for key in self.module_arg_spec: 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 if self.relative_name and not self.resource_group: self.fail("Parameter error: resource group required when filtering by name or record type.") if self.relative_name and not self.zone_name: 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 if self.relative_name is not None: # 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: # 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: # 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 def get_item(self): @@ -166,33 +223,54 @@ class AzureRMRecordSetFacts(AzureRMModuleBase): except CloudError: pass - results = [self.serialize_obj(item, AZURE_OBJECT_CLASS)] + results = [item] return results def list_type(self): self.log('Lists the record sets of a specified type in a DNS zone') 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: self.fail("Failed to list for record type {0} - {1}".format(self.record_type, str(exc))) results = [] for item in response: - results.append(self.serialize_obj(item, AZURE_OBJECT_CLASS)) + results.append(item) return results def list_zone(self): self.log('Lists all record sets in a DNS zone') 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: self.fail("Failed to list for zone {0} - {1}".format(self.zone_name, str(exc))) results = [] for item in response: - results.append(self.serialize_obj(item, AZURE_OBJECT_CLASS)) + results.append(item) 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(): AzureRMRecordSetFacts() diff --git a/lib/ansible/modules/cloud/azure/azure_rm_dnszone.py b/lib/ansible/modules/cloud/azure/azure_rm_dnszone.py index a63f22c451..d65e27b046 100644 --- a/lib/ansible/modules/cloud/azure/azure_rm_dnszone.py +++ b/lib/ansible/modules/cloud/azure/azure_rm_dnszone.py @@ -53,6 +53,27 @@ options: choices: - absent - 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: - azure @@ -92,16 +113,18 @@ state: "ns3-07.azure-dns.org.", "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: from msrestazure.azure_exceptions import CloudError - from azure.mgmt.dns.models import Zone except ImportError: # This is handled in azure_rm_common pass @@ -115,7 +138,10 @@ class AzureRMDNSZone(AzureRMModuleBase): self.module_arg_spec = dict( resource_group=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 @@ -128,6 +154,9 @@ class AzureRMDNSZone(AzureRMModuleBase): self.name = None self.state = 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, supports_check_mode=True, @@ -140,6 +169,9 @@ class AzureRMDNSZone(AzureRMModuleBase): for key in list(self.module_arg_spec.keys()) + ['tags']: 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 # retrieve resource group to make sure it exists @@ -162,7 +194,22 @@ class AzureRMDNSZone(AzureRMModuleBase): update_tags, results['tags'] = self.update_tags(results['tags']) if update_tags: 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': changed = True @@ -183,16 +230,13 @@ class AzureRMDNSZone(AzureRMModuleBase): if changed: if self.state == 'present': - if not zone: - # create new zone - self.log('Creating zone {0}'.format(self.name)) - zone = Zone(location='global', tags=self.tags) - else: - # update zone - zone = Zone( - location='global', - tags=results['tags'] - ) + zone = self.dns_models.Zone(zone_type=str.capitalize(self.type) if self.type else None, + tags=self.tags, + location='global') + if self.resolution_virtual_networks: + zone.resolution_virtual_networks = self.construct_subresource_list(self.resolution_virtual_networks) + if self.registration_virtual_networks: + zone.registration_virtual_networks = self.construct_subresource_list(self.registration_virtual_networks) self.results['state'] = self.create_or_update_zone(zone) elif self.state == 'absent': # delete zone @@ -208,7 +252,7 @@ class AzureRMDNSZone(AzureRMModuleBase): # create or update the new Zone object we created new_zone = self.dns_client.zones.create_or_update(self.resource_group, self.name, zone) 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) def delete_zone(self): @@ -217,9 +261,23 @@ class AzureRMDNSZone(AzureRMModuleBase): poller = self.dns_client.zones.delete(self.resource_group, self.name) result = self.get_poller_result(poller) 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 + 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): # turn Zone object into a dictionary (serialization) @@ -228,7 +286,10 @@ def zone_to_dict(zone): name=zone.name, number_of_record_sets=zone.number_of_record_sets, 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 diff --git a/lib/ansible/modules/cloud/azure/azure_rm_dnszone_facts.py b/lib/ansible/modules/cloud/azure/azure_rm_dnszone_facts.py index aa034c7d87..8cf767ecf6 100644 --- a/lib/ansible/modules/cloud/azure/azure_rm_dnszone_facts.py +++ b/lib/ansible/modules/cloud/azure/azure_rm_dnszone_facts.py @@ -73,9 +73,52 @@ azure_dnszones: }, "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._text import to_native try: from msrestazure.azure_exceptions import CloudError @@ -118,17 +161,20 @@ class AzureRMDNSZoneFacts(AzureRMModuleBase): if self.name and not self.resource_group: self.fail("Parameter error: resource group required when filtering by name.") + results = [] # list the conditions and what to return based on user input - if self.name is not None: # 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: # 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: # 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 @@ -144,7 +190,7 @@ class AzureRMDNSZoneFacts(AzureRMModuleBase): # serialize result if item and self.has_tags(item.tags, self.tags): - results = [self.serialize_obj(item, AZURE_OBJECT_CLASS)] + results = [item] return results def list_resource_group(self): @@ -157,7 +203,7 @@ class AzureRMDNSZoneFacts(AzureRMModuleBase): results = [] for item in response: if self.has_tags(item.tags, self.tags): - results.append(self.serialize_obj(item, AZURE_OBJECT_CLASS)) + results.append(item) return results def list_items(self): @@ -170,9 +216,28 @@ class AzureRMDNSZoneFacts(AzureRMModuleBase): results = [] for item in response: if self.has_tags(item.tags, self.tags): - results.append(self.serialize_obj(item, AZURE_OBJECT_CLASS)) + results.append(item) 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(): AzureRMDNSZoneFacts() diff --git a/packaging/requirements/requirements-azure.txt b/packaging/requirements/requirements-azure.txt index 67b0adac00..e344815526 100644 --- a/packaging/requirements/requirements-azure.txt +++ b/packaging/requirements/requirements-azure.txt @@ -9,7 +9,7 @@ azure-mgmt-compute==4.3.1 azure-mgmt-containerinstance==0.4.0 azure-mgmt-containerregistry==2.0.0 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-marketplaceordering==0.1.0 azure-mgmt-monitor==0.5.2 diff --git a/test/integration/targets/azure_rm_dnszone/tasks/main.yml b/test/integration/targets/azure_rm_dnszone/tasks/main.yml index 635837ad49..a115a030c0 100644 --- a/test/integration/targets/azure_rm_dnszone/tasks/main.yml +++ b/test/integration/targets/azure_rm_dnszone/tasks/main.yml @@ -2,11 +2,20 @@ set_fact: 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 azure_rm_dnszone: resource_group: "{{ resource_group }}" name: "{{ domain_name }}.com" - state: present register: results - assert: @@ -16,7 +25,6 @@ azure_rm_dnszone: resource_group: "{{ resource_group }}" name: "{{ domain_name }}.com" - state: present tags: test: modified register: results @@ -26,33 +34,79 @@ - results.changed - 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 azure_rm_dnszone_facts: resource_group: "{{ resource_group }}" name: "{{ domain_name }}.com" - register: results + register: zones - name: Assert that facts module returned result assert: that: - - not results.changed - - results.ansible_facts.azure_dnszones[0].tags.test == 'modified' + - 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 @@ -72,7 +126,9 @@ - name: Assert that A record set was created assert: - that: results.changed + that: + - results.changed + - 'results.state.arecords | length == 3' - name: re-run "A" record with same values azure_rm_dnsrecordset: @@ -105,6 +161,7 @@ assert: that: - results.changed + - 'results.state.arecords | length == 4' - name: re-update "A" record set with additional record azure_rm_dnsrecordset: @@ -138,6 +195,7 @@ assert: that: - results.changed + - 'results.state.arecords | length == 3' - name: Check_mode test azure_rm_dnsrecordset: @@ -213,38 +271,56 @@ assert: that: - 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 azure_rm_dnsrecordset_facts: resource_group: "{{ resource_group }}" zone_name: "{{ domain_name }}.com" - register: results + register: facts - name: Assert that facts module returned result for all Record Sets assert: that: - - not results.changed - - results.ansible_facts.azure_dnsrecordset[0].name == '@' - - results.ansible_facts.azure_dnsrecordset[1].name == '@' - - results.ansible_facts.azure_dnsrecordset[4].name == 'www' + - not facts.changed + - facts.ansible_facts.azure_dnsrecordset[0].name == '@' + - facts.ansible_facts.azure_dnsrecordset[1].name == '@' + - facts.ansible_facts.azure_dnsrecordset[4].name == 'www' # # azure_rm_dnsrecordset cleanup # -- name: delete a record set +- name: delete all record sets except for @ azure_rm_dnsrecordset: resource_group: "{{ resource_group }}" - relative_name: www + relative_name: "{{ item.relative_name }}" zone_name: "{{ domain_name }}.com" - record_type: A + record_type: "{{ item.record_type }}" state: absent + with_items: "{{ facts.dnsrecordsets }}" + when: + - item.relative_name != '@' register: results - name: Assert that record set deleted assert: 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 azure_rm_dnsrecordset: resource_group: "{{ resource_group }}" @@ -262,7 +338,20 @@ # azure_rm_dnszone cleanup # - 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: resource_group: "{{ resource_group }}" name: "{{ domain_name }}.com" state: absent + register: results + +- assert: + that: not results.changed \ No newline at end of file diff --git a/test/runner/requirements/integration.cloud.azure.txt b/test/runner/requirements/integration.cloud.azure.txt index 67b0adac00..e344815526 100644 --- a/test/runner/requirements/integration.cloud.azure.txt +++ b/test/runner/requirements/integration.cloud.azure.txt @@ -9,7 +9,7 @@ azure-mgmt-compute==4.3.1 azure-mgmt-containerinstance==0.4.0 azure-mgmt-containerregistry==2.0.0 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-marketplaceordering==0.1.0 azure-mgmt-monitor==0.5.2