diff --git a/octodns/provider/azuredns.py b/octodns/provider/azuredns.py index d1ec333..a1ef6fe 100644 --- a/octodns/provider/azuredns.py +++ b/octodns/provider/azuredns.py @@ -28,6 +28,25 @@ def unescape_semicolon(s): return s.replace('\\;', ';') +def azure_chunked_value(val): + CHUNK_SIZE = 255 + val_replace = val.replace('"', '\\"') + value = unescape_semicolon(val_replace) + if len(val) > CHUNK_SIZE: + vs = [value[i:i + CHUNK_SIZE] + for i in range(0, len(value), CHUNK_SIZE)] + else: + vs = value + return vs + + +def azure_chunked_values(s): + values = [] + for v in s: + values.append(azure_chunked_value(v)) + return values + + class _AzureRecord(object): '''Wrapper for OctoDNS record for AzureProvider to make dns_client calls. @@ -72,6 +91,8 @@ class _AzureRecord(object): :type return: _AzureRecord ''' + self.log = logging.getLogger('AzureRecord') + self.resource_group = resource_group self.zone_name = record.zone.name[:len(record.zone.name) - 1] self.relative_record_set_name = record.name or '@' @@ -162,11 +183,19 @@ class _AzureRecord(object): return {key_name: [azure_class(ptrdname=v) for v in values]} def _params_for_TXT(self, data, key_name, azure_class): + + params = [] try: # API for TxtRecord has list of str, even for singleton - values = [unescape_semicolon(v) for v in data['values']] + values = [v for v in azure_chunked_values(data['values'])] except KeyError: - values = [unescape_semicolon(data['value'])] - return {key_name: [azure_class(value=[v]) for v in values]} + values = [azure_chunked_value(data['value'])] + + for v in values: + if isinstance(v, list): + params.append(azure_class(value=v)) + else: + params.append(azure_class(value=[v])) + return {key_name: params} def _equals(self, b): '''Checks whether two records are equal by comparing all fields. @@ -234,6 +263,13 @@ def _parse_azure_type(string): return string.split('/')[len(string.split('/')) - 1] +def _check_for_alias(azrecord): + if (azrecord.target_resource.id and not azrecord.arecords and not + azrecord.cname_record): + return True + return False + + class AzureProvider(BaseProvider): ''' Azure DNS Provider @@ -387,11 +423,20 @@ class AzureProvider(BaseProvider): for azrecord in _records: record_name = azrecord.name if azrecord.name != '@' else '' typ = _parse_azure_type(azrecord.type) + + if typ in ['A', 'CNAME']: + if _check_for_alias(azrecord): + self.log.debug( + 'Skipping - ALIAS. zone=%s record=%s, type=%s', + zone_name, record_name, typ) # pragma: no cover + continue # pragma: no cover + data = getattr(self, '_data_for_{}'.format(typ)) data = data(azrecord) data['type'] = typ data['ttl'] = azrecord.ttl record = Record.new(zone, record_name, data, source=self) + zone.add_record(record, lenient=lenient) self.log.info('populate: found %s records, exists=%s', diff --git a/tests/test_octodns_provider_azuredns.py b/tests/test_octodns_provider_azuredns.py index 3b008e5..2b2c1e7 100644 --- a/tests/test_octodns_provider_azuredns.py +++ b/tests/test_octodns_provider_azuredns.py @@ -7,13 +7,13 @@ from __future__ import absolute_import, division, print_function, \ from octodns.record import Create, Delete, Record from octodns.provider.azuredns import _AzureRecord, AzureProvider, \ - _check_endswith_dot, _parse_azure_type + _check_endswith_dot, _parse_azure_type, _check_for_alias from octodns.zone import Zone from octodns.provider.base import Plan from azure.mgmt.dns.models import ARecord, AaaaRecord, CaaRecord, \ CnameRecord, MxRecord, SrvRecord, NsRecord, PtrRecord, TxtRecord, \ - RecordSet, SoaRecord, Zone as AzureZone + RecordSet, SoaRecord, SubResource, Zone as AzureZone from msrestazure.azure_exceptions import CloudError from unittest import TestCase @@ -134,6 +134,18 @@ octo_records.append(Record.new(zone, 'txt2', { 'type': 'TXT', 'values': ['txt multiple test', 'txt multiple test 2']})) +long_txt = "v=spf1 ip4:10.10.0.0/24 ip4:10.10.1.0/24 ip4:10.10.2.0/24" +long_txt += " ip4:10.10.3.0/24 ip4:10.10.4.0/24 ip4:10.10.5.0/24 " +long_txt += " 10.6.0/24 ip4:10.10.7.0/24 ip4:10.10.8.0/24 " +long_txt += " ip4:10.10.10.0/24 ip4:10.10.11.0/24 ip4:10.10.12.0/24" +long_txt += " ip4:10.10.13.0/24 ip4:10.10.14.0/24 ip4:10.10.15.0/24" +long_txt += " ip4:10.10.16.0/24 ip4:10.10.17.0/24 ip4:10.10.18.0/24" +long_txt += " ip4:10.10.19.0/24 ip4:10.10.20.0/24 ~all" +octo_records.append(Record.new(zone, 'txt3', { + 'ttl': 10, + 'type': 'TXT', + 'values': ['txt multiple test', long_txt]})) + azure_records = [] _base0 = _AzureRecord('TestAzure', octo_records[0]) _base0.zone_name = 'unit.tests' @@ -306,6 +318,22 @@ _base17.params['txt_records'] = [TxtRecord(value=['txt multiple test']), TxtRecord(value=['txt multiple test 2'])] azure_records.append(_base17) +long_txt_az1 = "v=spf1 ip4:10.10.0.0/24 ip4:10.10.1.0/24 ip4:10.10.2.0/24" +long_txt_az1 += " ip4:10.10.3.0/24 ip4:10.10.4.0/24 ip4:10.10.5.0/24 " +long_txt_az1 += " 10.6.0/24 ip4:10.10.7.0/24 ip4:10.10.8.0/24 " +long_txt_az1 += " ip4:10.10.10.0/24 ip4:10.10.11.0/24 ip4:10.10.12.0/24" +long_txt_az1 += " ip4:10.10.13.0/24 ip4:10.10.14.0/24 ip4:10.10." +long_txt_az2 = "15.0/24 ip4:10.10.16.0/24 ip4:10.10.17.0/24 ip4:10.10.18.0/24" +long_txt_az2 += " ip4:10.10.19.0/24 ip4:10.10.20.0/24 ~all" +_base18 = _AzureRecord('TestAzure', octo_records[18]) +_base18.zone_name = 'unit.tests' +_base18.relative_record_set_name = 'txt3' +_base18.record_type = 'TXT' +_base18.params['ttl'] = 10 +_base18.params['txt_records'] = [TxtRecord(value=['txt multiple test']), + TxtRecord(value=[long_txt_az1, long_txt_az2])] +azure_records.append(_base18) + class Test_AzureRecord(TestCase): def test_azure_record(self): @@ -333,6 +361,17 @@ class Test_CheckEndswithDot(TestCase): self.assertEquals(expected, _check_endswith_dot(test)) +class Test_CheckAzureAlias(TestCase): + def test_check_for_alias(self): + alias_record = type('C', (object,), {}) + alias_record.target_resource = type('C', (object,), {}) + alias_record.target_resource.id = "/subscriptions/x/resourceGroups/y/z" + alias_record.arecords = None + alias_record.cname_record = None + + self.assertEquals(_check_for_alias(alias_record), True) + + class TestAzureDnsProvider(TestCase): def _provider(self): return self._get_provider('mock_spc', 'mock_dns_client') @@ -358,19 +397,23 @@ class TestAzureDnsProvider(TestCase): rs = [] recordSet = RecordSet(arecords=[ARecord(ipv4_address='1.1.1.1')]) recordSet.name, recordSet.ttl, recordSet.type = 'a1', 0, 'A' + recordSet.target_resource = SubResource() rs.append(recordSet) recordSet = RecordSet(arecords=[ARecord(ipv4_address='1.1.1.1'), ARecord(ipv4_address='2.2.2.2')]) recordSet.name, recordSet.ttl, recordSet.type = 'a2', 1, 'A' + recordSet.target_resource = SubResource() rs.append(recordSet) aaaa1 = AaaaRecord(ipv6_address='1:1ec:1::1') recordSet = RecordSet(aaaa_records=[aaaa1]) recordSet.name, recordSet.ttl, recordSet.type = 'aaaa1', 2, 'AAAA' + recordSet.target_resource = SubResource() rs.append(recordSet) aaaa2 = AaaaRecord(ipv6_address='1:1ec:1::2') recordSet = RecordSet(aaaa_records=[aaaa1, aaaa2]) recordSet.name, recordSet.ttl, recordSet.type = 'aaaa2', 3, 'AAAA' + recordSet.target_resource = SubResource() rs.append(recordSet) recordSet = RecordSet(caa_records=[CaaRecord(flags=0, tag='issue', @@ -388,6 +431,7 @@ class TestAzureDnsProvider(TestCase): cname1 = CnameRecord(cname='cname.unit.test.') recordSet = RecordSet(cname_record=cname1) recordSet.name, recordSet.ttl, recordSet.type = 'cname1', 5, 'CNAME' + recordSet.target_resource = SubResource() rs.append(recordSet) recordSet = RecordSet(mx_records=[MxRecord(preference=10, exchange='mx1.unit.test.')]) @@ -428,22 +472,35 @@ class TestAzureDnsProvider(TestCase): rs.append(recordSet) recordSet = RecordSet(txt_records=[TxtRecord(value='sample text1')]) recordSet.name, recordSet.ttl, recordSet.type = 'txt1', 15, 'TXT' + recordSet.target_resource = SubResource() rs.append(recordSet) recordSet = RecordSet(txt_records=[TxtRecord(value='sample text1'), TxtRecord(value='sample text2')]) recordSet.name, recordSet.ttl, recordSet.type = 'txt2', 16, 'TXT' + recordSet.target_resource = SubResource() rs.append(recordSet) recordSet = RecordSet(soa_record=[SoaRecord()]) recordSet.name, recordSet.ttl, recordSet.type = '', 17, 'SOA' rs.append(recordSet) + long_txt = "v=spf1 ip4:10.10.0.0/24 ip4:10.10.1.0/24 ip4:10.10.2.0/24" + long_txt += " ip4:10.10.3.0/24 ip4:10.10.4.0/24 ip4:10.10.5.0/24 " + long_txt += " 10.6.0/24 ip4:10.10.7.0/24 ip4:10.10.8.0/24 " + long_txt += " ip4:10.10.10.0/24 ip4:10.10.11.0/24 ip4:10.10.12.0/24" + long_txt += " ip4:10.10.13.0/24 ip4:10.10.14.0/24 ip4:10.10.15.0/24" + long_txt += " ip4:10.10.16.0/24 ip4:10.10.17.0/24 ip4:10.10.18.0/24" + long_txt += " ip4:10.10.19.0/24 ip4:10.10.20.0/24 ~all" + recordSet = RecordSet(txt_records=[TxtRecord(value='sample value1'), + TxtRecord(value=long_txt)]) + recordSet.name, recordSet.ttl, recordSet.type = 'txt3', 18, 'TXT' + recordSet.target_resource = SubResource() + rs.append(recordSet) record_list = provider._dns_client.record_sets.list_by_dns_zone record_list.return_value = rs exists = provider.populate(zone) self.assertTrue(exists) - - self.assertEquals(len(zone.records), 16) + self.assertEquals(len(zone.records), 17) def test_populate_zone(self): provider = self._get_provider() @@ -477,9 +534,9 @@ class TestAzureDnsProvider(TestCase): changes.append(Create(i)) deletes.append(Delete(i)) - self.assertEquals(18, provider.apply(Plan(None, zone, + self.assertEquals(19, provider.apply(Plan(None, zone, changes, True))) - self.assertEquals(18, provider.apply(Plan(zone, zone, + self.assertEquals(19, provider.apply(Plan(zone, zone, deletes, True))) def test_create_zone(self): @@ -495,7 +552,7 @@ class TestAzureDnsProvider(TestCase): _get = provider._dns_client.zones.get _get.side_effect = CloudError(Mock(status=404), err_msg) - self.assertEquals(18, provider.apply(Plan(None, desired, changes, + self.assertEquals(19, provider.apply(Plan(None, desired, changes, True))) def test_check_zone_no_create(self):