From 99860844eb7c92ec919ceb7ce2f2a14434b2f6f6 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 19 Dec 2019 19:24:55 -0400 Subject: [PATCH 1/7] #1195 Remove isGatewayAddress property of record template --- SoftLayer/managers/dns.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index a3fc322af..99545944f 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -226,6 +226,7 @@ def edit_record(self, record): :param dict record: the record to update """ + record.pop('isGatewayAddress', None) self.record.editObject(record, id=record['id']) def dump_zone(self, zone_id): From b51572a92d848482072026dd9cf95f634ca52656 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 20 Dec 2019 18:36:01 -0400 Subject: [PATCH 2/7] #1195 Added hardware dns-sync command --- SoftLayer/CLI/hardware/dns.py | 152 ++++++++++++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + 2 files changed, 153 insertions(+) create mode 100644 SoftLayer/CLI/hardware/dns.py diff --git a/SoftLayer/CLI/hardware/dns.py b/SoftLayer/CLI/hardware/dns.py new file mode 100644 index 000000000..19e611818 --- /dev/null +++ b/SoftLayer/CLI/hardware/dns.py @@ -0,0 +1,152 @@ +"""Sync DNS records.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command(epilog="""If you don't specify any +arguments, it will attempt to update both the A and PTR records. If you don't +want to update both records, you may use the -a or --ptr arguments to limit +the records updated.""") +@click.argument('identifier') +@click.option('--a-record', '-a', + is_flag=True, + help="Sync the A record for the host") +@click.option('--aaaa-record', + is_flag=True, + help="Sync the AAAA record for the host") +@click.option('--ptr', is_flag=True, help="Sync the PTR record for the host") +@click.option('--ttl', + default=7200, + show_default=True, + type=click.INT, + help="Sets the TTL for the A and/or PTR records") +@environment.pass_env +def cli(env, identifier, a_record, aaaa_record, ptr, ttl): + """Sync DNS records.""" + + items = ['id', + 'globalIdentifier', + 'fullyQualifiedDomainName', + 'hostname', + 'domain', + 'primaryBackendIpAddress', + 'primaryIpAddress', + '''primaryNetworkComponent[ + id, primaryIpAddress, + primaryVersion6IpAddressRecord[ipAddress] + ]'''] + mask = "mask[%s]" % ','.join(items) + dns = SoftLayer.DNSManager(env.client) + server = SoftLayer.HardwareManager(env.client) + + hw_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS') + instance = server.get_hardware(hw_id, mask=mask) + zone_id = helpers.resolve_id(dns.resolve_ids, + instance['domain'], + name='zone') + + def sync_a_record(): + """Sync A record.""" + records = dns.get_records(zone_id, + host=instance['hostname'], + record_type='a') + if not records: + # don't have a record, lets add one to the base zone + dns.create_record(zone['id'], + instance['hostname'], + 'a', + instance['primaryIpAddress'], + ttl=ttl) + else: + if len(records) != 1: + raise exceptions.CLIAbort("Aborting A record sync, found " + "%d A record exists!" % len(records)) + rec = records[0] + rec['data'] = instance['primaryIpAddress'] + rec['ttl'] = ttl + dns.edit_record(rec) + + def sync_aaaa_record(): + """Sync AAAA record.""" + records = dns.get_records(zone_id, + host=instance['hostname'], + record_type='aaaa') + try: + # done this way to stay within 80 character lines + component = instance['primaryNetworkComponent'] + record = component['primaryVersion6IpAddressRecord'] + ip_address = record['ipAddress'] + except KeyError: + raise exceptions.CLIAbort("%s does not have an ipv6 address" + % instance['fullyQualifiedDomainName']) + + if not records: + # don't have a record, lets add one to the base zone + dns.create_record(zone['id'], + instance['hostname'], + 'aaaa', + ip_address, + ttl=ttl) + else: + if len(records) != 1: + raise exceptions.CLIAbort("Aborting A record sync, found " + "%d A record exists!" % len(records)) + rec = records[0] + rec['data'] = ip_address + rec['ttl'] = ttl + dns.edit_record(rec) + + def sync_ptr_record(): + """Sync PTR record.""" + host_rec = instance['primaryIpAddress'].split('.')[-1] + ptr_domains = (env.client['Hardware_Server'] + .getReverseDomainRecords(id=instance['id'])[0]) + edit_ptr = None + for ptr in ptr_domains['resourceRecords']: + if ptr['host'] == host_rec: + ptr['ttl'] = ttl + edit_ptr = ptr + break + + if edit_ptr: + edit_ptr['data'] = instance['fullyQualifiedDomainName'] + dns.edit_record(edit_ptr) + else: + dns.create_record(ptr_domains['id'], + host_rec, + 'ptr', + instance['fullyQualifiedDomainName'], + ttl=ttl) + + if not instance['primaryIpAddress']: + raise exceptions.CLIAbort('No primary IP address associated with ' + 'this VS') + + zone = dns.get_zone(zone_id) + + go_for_it = env.skip_confirmations or formatting.confirm( + "Attempt to update DNS records for %s" + % instance['fullyQualifiedDomainName']) + + if not go_for_it: + raise exceptions.CLIAbort("Aborting DNS sync") + + both = False + if not ptr and not a_record and not aaaa_record: + both = True + + if both or a_record: + sync_a_record() + + if both or ptr: + sync_ptr_record() + + if aaaa_record: + sync_aaaa_record() diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 5c37541df..97f004bcd 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -234,6 +234,7 @@ ('hardware:rescue', 'SoftLayer.CLI.hardware.power:rescue'), ('hardware:ready', 'SoftLayer.CLI.hardware.ready:cli'), ('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'), + ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), From 65237ea57c1551d3be27ea843a3222665386ffc0 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 20 Dec 2019 18:36:58 -0400 Subject: [PATCH 3/7] #1195 Added hardware dns-sync command unittests --- tests/CLI/modules/server_tests.py | 188 ++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 29ec65d40..7f000c8eb 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -638,3 +638,191 @@ def test_bandwidth_hw_quite(self): self.assertEqual(output_summary[1]['Max Date'], date) self.assertEqual(output_summary[2]['Max GB'], 0.1172) self.assertEqual(output_summary[3]['Sum GB'], 0.0009) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_dns_sync_both(self, confirm_mock): + confirm_mock.return_value = True + getReverseDomainRecords = self.set_mock('SoftLayer_Hardware_Server', + 'getReverseDomainRecords') + getReverseDomainRecords.return_value = [{ + 'networkAddress': '172.16.1.100', + 'name': '2.240.16.172.in-addr.arpa', + 'resourceRecords': [{'data': 'test.softlayer.com.', + 'id': 100, + 'host': '12'}], + 'updateDate': '2013-09-11T14:36:57-07:00', + 'serial': 1234665663, + 'id': 123456, + }] + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', + 'getResourceRecords') + getResourceRecords.return_value = [] + createAargs = ({ + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 98765, + 'data': '172.16.1.100', + 'ttl': 7200 + },) + createPTRargs = ({ + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) + + result = self.run_command(['hw', 'dns-sync', '1000']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Dns_Domain', 'getResourceRecords') + self.assert_called_with('SoftLayer_Hardware_Server', + 'getReverseDomainRecords') + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'createObject', + args=createAargs) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'createObject', + args=createPTRargs) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_dns_sync_v6(self, confirm_mock): + confirm_mock.return_value = True + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', + 'getResourceRecords') + getResourceRecords.return_value = [] + server = self.set_mock('SoftLayer_Hardware_Server', 'getObject') + test_server = { + 'id': 1000, + 'hostname': 'hardware-test1', + 'domain': 'sftlyr.ws', + 'primaryIpAddress': '172.16.1.100', + 'fullyQualifiedDomainName': 'hw-test1.sftlyr.ws', + "primaryNetworkComponent": {} + } + server.return_value = test_server + + result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + test_server['primaryNetworkComponent'] = { + 'primaryVersion6IpAddressRecord': { + 'ipAddress': '2607:f0d0:1b01:0023:0000:0000:0000:0004' + } + } + createV6args = ({ + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 98765, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) + server.return_value = test_server + result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'createObject', + args=createV6args) + + v6Record = { + 'id': 1, + 'ttl': 7200, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'host': 'hardware-test1', + 'type': 'aaaa' + } + + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', + 'getResourceRecords') + getResourceRecords.return_value = [v6Record] + editArgs = (v6Record,) + result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'editObject', + args=editArgs) + + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', + 'getResourceRecords') + getResourceRecords.return_value = [v6Record, v6Record] + result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_dns_sync_edit_a(self, confirm_mock): + confirm_mock.return_value = True + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', + 'getResourceRecords') + getResourceRecords.return_value = [ + {'id': 1, 'ttl': 7200, 'data': '1.1.1.1', + 'host': 'hardware-test1', 'type': 'a'} + ] + editArgs = ( + {'type': 'a', 'host': 'hardware-test1', 'data': '172.16.1.100', + 'id': 1, 'ttl': 7200}, + ) + result = self.run_command(['hw', 'dns-sync', '-a', '1000']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'editObject', + args=editArgs) + + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', + 'getResourceRecords') + getResourceRecords.return_value = [ + {'id': 1, 'ttl': 7200, 'data': '1.1.1.1', + 'host': 'hardware-test1', 'type': 'a'}, + {'id': 2, 'ttl': 7200, 'data': '1.1.1.1', + 'host': 'hardware-test1', 'type': 'a'} + ] + result = self.run_command(['hw', 'dns-sync', '-a', '1000']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_dns_sync_edit_ptr(self, confirm_mock): + confirm_mock.return_value = True + getReverseDomainRecords = self.set_mock('SoftLayer_Hardware_Server', + 'getReverseDomainRecords') + getReverseDomainRecords.return_value = [{ + 'networkAddress': '172.16.1.100', + 'name': '2.240.16.172.in-addr.arpa', + 'resourceRecords': [{'data': 'test.softlayer.com.', + 'id': 123, + 'host': '100'}], + 'updateDate': '2013-09-11T14:36:57-07:00', + 'serial': 1234665663, + 'id': 123456, + }] + editArgs = ({'host': '100', 'data': 'hardware-test1.test.sftlyr.ws', + 'id': 123, 'ttl': 7200},) + result = self.run_command(['hw', 'dns-sync', '--ptr', '1000']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'editObject', + args=editArgs) + + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_dns_sync_misc_exception(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'dns-sync', '-a', '1000']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + guest = self.set_mock('SoftLayer_Hardware_Server', 'getObject') + test_guest = { + 'id': 1000, + 'primaryIpAddress': '', + 'hostname': 'hardware-test1', + 'domain': 'sftlyr.ws', + 'fullyQualifiedDomainName': 'hardware-test1.sftlyr.ws', + "primaryNetworkComponent": {} + } + guest.return_value = test_guest + result = self.run_command(['hw', 'dns-sync', '-a', '1000']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) From 00b12f0b79869bb368b5c041801da2fe85812798 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 20 Dec 2019 19:40:45 -0400 Subject: [PATCH 4/7] #1195 Fix tox E303 too many blank lines --- tests/CLI/modules/server_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 7f000c8eb..1d2995004 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -805,7 +805,6 @@ def test_dns_sync_edit_ptr(self, confirm_mock): 'editObject', args=editArgs) - @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_misc_exception(self, confirm_mock): confirm_mock.return_value = False From 27a1d3da719467da15345acf513859642e09497e Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 30 Dec 2019 17:34:50 -0600 Subject: [PATCH 5/7] #1205 refactored the dns_sync sub-functions for vs and hw dns-sync --- SoftLayer/CLI/hardware/dns.py | 136 ++++++--------------------------- SoftLayer/CLI/virt/dns.py | 138 ++++++---------------------------- SoftLayer/managers/dns.py | 51 ++++++++++++- 3 files changed, 96 insertions(+), 229 deletions(-) diff --git a/SoftLayer/CLI/hardware/dns.py b/SoftLayer/CLI/hardware/dns.py index 19e611818..d698265b5 100644 --- a/SoftLayer/CLI/hardware/dns.py +++ b/SoftLayer/CLI/hardware/dns.py @@ -1,5 +1,6 @@ """Sync DNS records.""" # :license: MIT, see LICENSE for more details. +# pylint: disable=duplicate-code import click @@ -15,138 +16,49 @@ want to update both records, you may use the -a or --ptr arguments to limit the records updated.""") @click.argument('identifier') -@click.option('--a-record', '-a', - is_flag=True, - help="Sync the A record for the host") -@click.option('--aaaa-record', - is_flag=True, - help="Sync the AAAA record for the host") +@click.option('--a-record', '-a', is_flag=True, help="Sync the A record for the host") +@click.option('--aaaa-record', is_flag=True, help="Sync the AAAA record for the host") @click.option('--ptr', is_flag=True, help="Sync the PTR record for the host") -@click.option('--ttl', - default=7200, - show_default=True, - type=click.INT, +@click.option('--ttl', default=7200, show_default=True, type=click.INT, help="Sets the TTL for the A and/or PTR records") @environment.pass_env def cli(env, identifier, a_record, aaaa_record, ptr, ttl): """Sync DNS records.""" - items = ['id', - 'globalIdentifier', - 'fullyQualifiedDomainName', - 'hostname', - 'domain', - 'primaryBackendIpAddress', - 'primaryIpAddress', - '''primaryNetworkComponent[ - id, primaryIpAddress, - primaryVersion6IpAddressRecord[ipAddress] - ]'''] - mask = "mask[%s]" % ','.join(items) + mask = """mask[id, globalIdentifier, fullyQualifiedDomainName, hostname, domain, + primaryBackendIpAddress,primaryIpAddress, + primaryNetworkComponent[id,primaryIpAddress,primaryVersion6IpAddressRecord[ipAddress]]]""" dns = SoftLayer.DNSManager(env.client) server = SoftLayer.HardwareManager(env.client) - hw_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS') - instance = server.get_hardware(hw_id, mask=mask) - zone_id = helpers.resolve_id(dns.resolve_ids, - instance['domain'], - name='zone') - - def sync_a_record(): - """Sync A record.""" - records = dns.get_records(zone_id, - host=instance['hostname'], - record_type='a') - if not records: - # don't have a record, lets add one to the base zone - dns.create_record(zone['id'], - instance['hostname'], - 'a', - instance['primaryIpAddress'], - ttl=ttl) - else: - if len(records) != 1: - raise exceptions.CLIAbort("Aborting A record sync, found " - "%d A record exists!" % len(records)) - rec = records[0] - rec['data'] = instance['primaryIpAddress'] - rec['ttl'] = ttl - dns.edit_record(rec) - - def sync_aaaa_record(): - """Sync AAAA record.""" - records = dns.get_records(zone_id, - host=instance['hostname'], - record_type='aaaa') - try: - # done this way to stay within 80 character lines - component = instance['primaryNetworkComponent'] - record = component['primaryVersion6IpAddressRecord'] - ip_address = record['ipAddress'] - except KeyError: - raise exceptions.CLIAbort("%s does not have an ipv6 address" - % instance['fullyQualifiedDomainName']) - - if not records: - # don't have a record, lets add one to the base zone - dns.create_record(zone['id'], - instance['hostname'], - 'aaaa', - ip_address, - ttl=ttl) - else: - if len(records) != 1: - raise exceptions.CLIAbort("Aborting A record sync, found " - "%d A record exists!" % len(records)) - rec = records[0] - rec['data'] = ip_address - rec['ttl'] = ttl - dns.edit_record(rec) - - def sync_ptr_record(): - """Sync PTR record.""" - host_rec = instance['primaryIpAddress'].split('.')[-1] - ptr_domains = (env.client['Hardware_Server'] - .getReverseDomainRecords(id=instance['id'])[0]) - edit_ptr = None - for ptr in ptr_domains['resourceRecords']: - if ptr['host'] == host_rec: - ptr['ttl'] = ttl - edit_ptr = ptr - break - - if edit_ptr: - edit_ptr['data'] = instance['fullyQualifiedDomainName'] - dns.edit_record(edit_ptr) - else: - dns.create_record(ptr_domains['id'], - host_rec, - 'ptr', - instance['fullyQualifiedDomainName'], - ttl=ttl) + server_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS') + instance = server.get_hardware(server_id, mask=mask) + zone_id = helpers.resolve_id(dns.resolve_ids, instance['domain'], name='zone') if not instance['primaryIpAddress']: - raise exceptions.CLIAbort('No primary IP address associated with ' - 'this VS') - - zone = dns.get_zone(zone_id) + raise exceptions.CLIAbort('No primary IP address associated with this hardware') go_for_it = env.skip_confirmations or formatting.confirm( - "Attempt to update DNS records for %s" - % instance['fullyQualifiedDomainName']) + "Attempt to update DNS records for %s" % instance['fullyQualifiedDomainName']) if not go_for_it: raise exceptions.CLIAbort("Aborting DNS sync") - both = False - if not ptr and not a_record and not aaaa_record: - both = True + # both will be true only if no options are passed in, basically. + both = (not ptr) and (not a_record) and (not aaaa_record) if both or a_record: - sync_a_record() + dns.sync_host_record(zone_id, instance['hostname'], instance['primaryIpAddress'], 'a', ttl) if both or ptr: - sync_ptr_record() + # getReverseDomainRecords returns a list of 1 element, so just get the top. + ptr_domains = env.client['Virtual_Guest'].getReverseDomainRecords(id=instance['id']).pop() + dns.sync_ptr_record(ptr_domains, instance['primaryIpAddress'], instance['fullyQualifiedDomainName'], ttl) if aaaa_record: - sync_aaaa_record() + try: + # done this way to stay within 80 character lines + ipv6 = instance['primaryNetworkComponent']['primaryVersion6IpAddressRecord']['ipAddress'] + dns.sync_host_record(zone_id, instance['hostname'], ipv6, 'aaaa', ttl) + except KeyError: + raise exceptions.CLIAbort("%s does not have an ipv6 address" % instance['fullyQualifiedDomainName']) diff --git a/SoftLayer/CLI/virt/dns.py b/SoftLayer/CLI/virt/dns.py index ca600465d..26b4904cd 100644 --- a/SoftLayer/CLI/virt/dns.py +++ b/SoftLayer/CLI/virt/dns.py @@ -1,5 +1,6 @@ """Sync DNS records.""" # :license: MIT, see LICENSE for more details. +# pylint: disable=duplicate-code import click @@ -15,138 +16,49 @@ want to update both records, you may use the -a or --ptr arguments to limit the records updated.""") @click.argument('identifier') -@click.option('--a-record', '-a', - is_flag=True, - help="Sync the A record for the host") -@click.option('--aaaa-record', - is_flag=True, - help="Sync the AAAA record for the host") +@click.option('--a-record', '-a', is_flag=True, help="Sync the A record for the host") +@click.option('--aaaa-record', is_flag=True, help="Sync the AAAA record for the host") @click.option('--ptr', is_flag=True, help="Sync the PTR record for the host") -@click.option('--ttl', - default=7200, - show_default=True, - type=click.INT, +@click.option('--ttl', default=7200, show_default=True, type=click.INT, help="Sets the TTL for the A and/or PTR records") @environment.pass_env def cli(env, identifier, a_record, aaaa_record, ptr, ttl): """Sync DNS records.""" - items = ['id', - 'globalIdentifier', - 'fullyQualifiedDomainName', - 'hostname', - 'domain', - 'primaryBackendIpAddress', - 'primaryIpAddress', - '''primaryNetworkComponent[ - id, primaryIpAddress, - primaryVersion6IpAddressRecord[ipAddress] - ]'''] - mask = "mask[%s]" % ','.join(items) + mask = """mask[id, globalIdentifier, fullyQualifiedDomainName, hostname, domain, + primaryBackendIpAddress,primaryIpAddress, + primaryNetworkComponent[id,primaryIpAddress,primaryVersion6IpAddressRecord[ipAddress]]]""" dns = SoftLayer.DNSManager(env.client) - vsi = SoftLayer.VSManager(env.client) + server = SoftLayer.VSManager(env.client) - vs_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') - instance = vsi.get_instance(vs_id, mask=mask) - zone_id = helpers.resolve_id(dns.resolve_ids, - instance['domain'], - name='zone') - - def sync_a_record(): - """Sync A record.""" - records = dns.get_records(zone_id, - host=instance['hostname'], - record_type='a') - if not records: - # don't have a record, lets add one to the base zone - dns.create_record(zone['id'], - instance['hostname'], - 'a', - instance['primaryIpAddress'], - ttl=ttl) - else: - if len(records) != 1: - raise exceptions.CLIAbort("Aborting A record sync, found " - "%d A record exists!" % len(records)) - rec = records[0] - rec['data'] = instance['primaryIpAddress'] - rec['ttl'] = ttl - dns.edit_record(rec) - - def sync_aaaa_record(): - """Sync AAAA record.""" - records = dns.get_records(zone_id, - host=instance['hostname'], - record_type='aaaa') - try: - # done this way to stay within 80 character lines - component = instance['primaryNetworkComponent'] - record = component['primaryVersion6IpAddressRecord'] - ip_address = record['ipAddress'] - except KeyError: - raise exceptions.CLIAbort("%s does not have an ipv6 address" - % instance['fullyQualifiedDomainName']) - - if not records: - # don't have a record, lets add one to the base zone - dns.create_record(zone['id'], - instance['hostname'], - 'aaaa', - ip_address, - ttl=ttl) - else: - if len(records) != 1: - raise exceptions.CLIAbort("Aborting A record sync, found " - "%d A record exists!" % len(records)) - rec = records[0] - rec['data'] = ip_address - rec['ttl'] = ttl - dns.edit_record(rec) - - def sync_ptr_record(): - """Sync PTR record.""" - host_rec = instance['primaryIpAddress'].split('.')[-1] - ptr_domains = (env.client['Virtual_Guest'] - .getReverseDomainRecords(id=instance['id'])[0]) - edit_ptr = None - for ptr in ptr_domains['resourceRecords']: - if ptr['host'] == host_rec: - ptr['ttl'] = ttl - edit_ptr = ptr - break - - if edit_ptr: - edit_ptr['data'] = instance['fullyQualifiedDomainName'] - dns.edit_record(edit_ptr) - else: - dns.create_record(ptr_domains['id'], - host_rec, - 'ptr', - instance['fullyQualifiedDomainName'], - ttl=ttl) + server_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS') + instance = server.get_instance(server_id, mask=mask) + zone_id = helpers.resolve_id(dns.resolve_ids, instance['domain'], name='zone') if not instance['primaryIpAddress']: - raise exceptions.CLIAbort('No primary IP address associated with ' - 'this VS') - - zone = dns.get_zone(zone_id) + raise exceptions.CLIAbort('No primary IP address associated with this VS') go_for_it = env.skip_confirmations or formatting.confirm( - "Attempt to update DNS records for %s" - % instance['fullyQualifiedDomainName']) + "Attempt to update DNS records for %s" % instance['fullyQualifiedDomainName']) if not go_for_it: raise exceptions.CLIAbort("Aborting DNS sync") - both = False - if not ptr and not a_record and not aaaa_record: - both = True + # both will be true only if no options are passed in, basically. + both = (not ptr) and (not a_record) and (not aaaa_record) if both or a_record: - sync_a_record() + dns.sync_host_record(zone_id, instance['hostname'], instance['primaryIpAddress'], 'a', ttl) if both or ptr: - sync_ptr_record() + # getReverseDomainRecords returns a list of 1 element, so just get the top. + ptr_domains = env.client['Virtual_Guest'].getReverseDomainRecords(id=instance['id']).pop() + dns.sync_ptr_record(ptr_domains, instance['primaryIpAddress'], instance['fullyQualifiedDomainName'], ttl) if aaaa_record: - sync_aaaa_record() + try: + # done this way to stay within 80 character lines + ipv6 = instance['primaryNetworkComponent']['primaryVersion6IpAddressRecord']['ipAddress'] + dns.sync_host_record(zone_id, instance['hostname'], ipv6, 'aaaa', ttl) + except KeyError: + raise exceptions.CLIAbort("%s does not have an ipv6 address" % instance['fullyQualifiedDomainName']) diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index 99545944f..7635a195d 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -7,6 +7,7 @@ """ import time +from SoftLayer import exceptions from SoftLayer import utils @@ -205,13 +206,11 @@ def get_records(self, zone_id, ttl=None, data=None, host=None, _filter['resourceRecords']['data'] = utils.query_filter(data) if record_type: - _filter['resourceRecords']['type'] = utils.query_filter( - record_type.lower()) + _filter['resourceRecords']['type'] = utils.query_filter(record_type.lower()) results = self.service.getResourceRecords( id=zone_id, - mask='id,expire,domainId,host,minimum,refresh,retry,' - 'mxPriority,ttl,type,data,responsiblePerson', + mask='id,expire,domainId,host,minimum,refresh,retry,mxPriority,ttl,type,data,responsiblePerson', filter=_filter.to_dict(), ) @@ -236,3 +235,47 @@ def dump_zone(self, zone_id): """ return self.service.getZoneFileContents(id=zone_id) + + def sync_host_record(self, zone_id, hostname, ip_address, record_type='a', ttl=7200): + """For a given zone_id, will set hostname's A record to ip_address + + :param integer zone_id: The zone id for the domain + :param string hostname: host part of the record + :param string ip_address: data part of the record + :param integer ttl: TTL for the record + :param string record_type: 'a' or 'aaaa' + """ + records = self.get_records(zone_id, host=hostname, record_type=record_type) + if not records: + # don't have a record, lets add one to the base zone + self.create_record(zone_id, hostname, record_type, ip_address, ttl=ttl) + else: + if len(records) != 1: + raise exceptions.SoftLayerError("Aborting record sync, found %d records!" % len(records)) + rec = records[0] + rec['data'] = ip_address + rec['ttl'] = ttl + self.edit_record(rec) + + def sync_ptr_record(self, ptr_domains, ip_address, fqdn, ttl=7200): + """Sync PTR record. + + :param dict ptr_domains: result from SoftLayer_Virtual_Guest.getReverseDomainRecords or + SoftLayer_Hardware_Server.getReverseDomainRecords + :param string ip_address: ip address to sync with + :param string fqdn: Fully Qualified Domain Name + :param integer ttl: TTL for the record + """ + host_rec = ip_address.split('.')[-1] + edit_ptr = None + for ptr in ptr_domains['resourceRecords']: + if ptr['host'] == host_rec: + ptr['ttl'] = ttl + edit_ptr = ptr + break + + if edit_ptr: + edit_ptr['data'] = fqdn + self.edit_record(edit_ptr) + else: + self.create_record(ptr_domains['id'], host_rec, 'ptr', fqdn, ttl=ttl) From 47417cc86466bf4b592403dbc69f3222e3a872ef Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 2 Jan 2020 15:24:56 -0600 Subject: [PATCH 6/7] #1195 refactored the vs/hw dns-sync commands, moved most of the logic to the managers/dns.py file. Fixed up unit tests to take account of the changes --- SoftLayer/CLI/hardware/dns.py | 2 +- SoftLayer/managers/dns.py | 2 +- tests/CLI/modules/server_tests.py | 15 ++++++++------- tests/CLI/modules/vs/vs_tests.py | 13 +++++++------ 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/SoftLayer/CLI/hardware/dns.py b/SoftLayer/CLI/hardware/dns.py index d698265b5..3b7458003 100644 --- a/SoftLayer/CLI/hardware/dns.py +++ b/SoftLayer/CLI/hardware/dns.py @@ -52,7 +52,7 @@ def cli(env, identifier, a_record, aaaa_record, ptr, ttl): if both or ptr: # getReverseDomainRecords returns a list of 1 element, so just get the top. - ptr_domains = env.client['Virtual_Guest'].getReverseDomainRecords(id=instance['id']).pop() + ptr_domains = env.client['Hardware_Server'].getReverseDomainRecords(id=instance['id']).pop() dns.sync_ptr_record(ptr_domains, instance['primaryIpAddress'], instance['fullyQualifiedDomainName'], ttl) if aaaa_record: diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index 7635a195d..3a2ea9147 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -269,7 +269,7 @@ def sync_ptr_record(self, ptr_domains, ip_address, fqdn, ttl=7200): host_rec = ip_address.split('.')[-1] edit_ptr = None for ptr in ptr_domains['resourceRecords']: - if ptr['host'] == host_rec: + if ptr.get('host', '') == host_rec: ptr['ttl'] = ttl edit_ptr = ptr break diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 1d2995004..14f8e9201 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -12,6 +12,7 @@ import sys from SoftLayer.CLI import exceptions +from SoftLayer import SoftLayerError from SoftLayer import testing import json @@ -660,7 +661,7 @@ def test_dns_sync_both(self, confirm_mock): createAargs = ({ 'type': 'a', 'host': 'hardware-test1', - 'domainId': 98765, + 'domainId': 12345, # from SoftLayer_Account::getDomains 'data': '172.16.1.100', 'ttl': 7200 },) @@ -715,7 +716,7 @@ def test_dns_sync_v6(self, confirm_mock): createV6args = ({ 'type': 'aaaa', 'host': 'hardware-test1', - 'domainId': 98765, + 'domainId': 12345, # from SoftLayer_Account::getDomains 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', 'ttl': 7200 },) @@ -748,8 +749,8 @@ def test_dns_sync_v6(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [v6Record, v6Record] result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) + self.assertEqual(result.exit_code, 1) + self.assertIsInstance(result.exception, SoftLayerError) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_edit_a(self, confirm_mock): @@ -779,8 +780,8 @@ def test_dns_sync_edit_a(self, confirm_mock): 'host': 'hardware-test1', 'type': 'a'} ] result = self.run_command(['hw', 'dns-sync', '-a', '1000']) - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) + self.assertEqual(result.exit_code, 1) + self.assertIsInstance(result.exception, SoftLayerError) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_edit_ptr(self, confirm_mock): @@ -789,7 +790,7 @@ def test_dns_sync_edit_ptr(self, confirm_mock): 'getReverseDomainRecords') getReverseDomainRecords.return_value = [{ 'networkAddress': '172.16.1.100', - 'name': '2.240.16.172.in-addr.arpa', + 'name': '100.1.16.172.in-addr.arpa', 'resourceRecords': [{'data': 'test.softlayer.com.', 'id': 123, 'host': '100'}], diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 203230913..3e57cb209 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -12,6 +12,7 @@ from SoftLayer.CLI import exceptions from SoftLayer.fixtures import SoftLayer_Virtual_Guest as SoftLayer_Virtual_Guest from SoftLayer import SoftLayerAPIError +from SoftLayer import SoftLayerError from SoftLayer import testing @@ -310,7 +311,7 @@ def test_dns_sync_both(self, confirm_mock): createAargs = ({ 'type': 'a', 'host': 'vs-test1', - 'domainId': 98765, + 'domainId': 12345, # from SoftLayer_Account::getDomains 'data': '172.16.240.2', 'ttl': 7200 },) @@ -365,7 +366,7 @@ def test_dns_sync_v6(self, confirm_mock): createV6args = ({ 'type': 'aaaa', 'host': 'vs-test1', - 'domainId': 98765, + 'domainId': 12345, 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', 'ttl': 7200 },) @@ -398,8 +399,8 @@ def test_dns_sync_v6(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [v6Record, v6Record] result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) + self.assertEqual(result.exit_code, 1) + self.assertIsInstance(result.exception, SoftLayerError) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_edit_a(self, confirm_mock): @@ -429,8 +430,8 @@ def test_dns_sync_edit_a(self, confirm_mock): 'host': 'vs-test1', 'type': 'a'} ] result = self.run_command(['vs', 'dns-sync', '-a', '100']) - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) + self.assertEqual(result.exit_code, 1) + self.assertIsInstance(result.exception, SoftLayerError) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_edit_ptr(self, confirm_mock): From 633324840b237bdb7f8363a183204795dc8cd5f4 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 6 Jan 2020 17:12:00 -0600 Subject: [PATCH 7/7] added docs for slcli hw dns-sync --- docs/cli/hardware.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 3e7eeaf4c..08fc273d6 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -94,3 +94,6 @@ This function updates the firmware of a server. If already at the latest version :prog: hw ready :show-nested: +.. click:: SoftLayer.CLI.hardware.dns-sync:cli + :prog: hw dns-sync + :show-nested: