Skip to content

Commit 8692e52

Browse files
Merge pull request #1207 from allmightyspiff/ATGE-issue1195
Fix #1195 slcli vs dns-sync --ptr fails
2 parents 3d5e025 + 6333248 commit 8692e52

File tree

7 files changed

+336
-123
lines changed

7 files changed

+336
-123
lines changed

SoftLayer/CLI/hardware/dns.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Sync DNS records."""
2+
# :license: MIT, see LICENSE for more details.
3+
# pylint: disable=duplicate-code
4+
5+
import click
6+
7+
import SoftLayer
8+
from SoftLayer.CLI import environment
9+
from SoftLayer.CLI import exceptions
10+
from SoftLayer.CLI import formatting
11+
from SoftLayer.CLI import helpers
12+
13+
14+
@click.command(epilog="""If you don't specify any
15+
arguments, it will attempt to update both the A and PTR records. If you don't
16+
want to update both records, you may use the -a or --ptr arguments to limit
17+
the records updated.""")
18+
@click.argument('identifier')
19+
@click.option('--a-record', '-a', is_flag=True, help="Sync the A record for the host")
20+
@click.option('--aaaa-record', is_flag=True, help="Sync the AAAA record for the host")
21+
@click.option('--ptr', is_flag=True, help="Sync the PTR record for the host")
22+
@click.option('--ttl', default=7200, show_default=True, type=click.INT,
23+
help="Sets the TTL for the A and/or PTR records")
24+
@environment.pass_env
25+
def cli(env, identifier, a_record, aaaa_record, ptr, ttl):
26+
"""Sync DNS records."""
27+
28+
mask = """mask[id, globalIdentifier, fullyQualifiedDomainName, hostname, domain,
29+
primaryBackendIpAddress,primaryIpAddress,
30+
primaryNetworkComponent[id,primaryIpAddress,primaryVersion6IpAddressRecord[ipAddress]]]"""
31+
dns = SoftLayer.DNSManager(env.client)
32+
server = SoftLayer.HardwareManager(env.client)
33+
34+
server_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS')
35+
instance = server.get_hardware(server_id, mask=mask)
36+
zone_id = helpers.resolve_id(dns.resolve_ids, instance['domain'], name='zone')
37+
38+
if not instance['primaryIpAddress']:
39+
raise exceptions.CLIAbort('No primary IP address associated with this hardware')
40+
41+
go_for_it = env.skip_confirmations or formatting.confirm(
42+
"Attempt to update DNS records for %s" % instance['fullyQualifiedDomainName'])
43+
44+
if not go_for_it:
45+
raise exceptions.CLIAbort("Aborting DNS sync")
46+
47+
# both will be true only if no options are passed in, basically.
48+
both = (not ptr) and (not a_record) and (not aaaa_record)
49+
50+
if both or a_record:
51+
dns.sync_host_record(zone_id, instance['hostname'], instance['primaryIpAddress'], 'a', ttl)
52+
53+
if both or ptr:
54+
# getReverseDomainRecords returns a list of 1 element, so just get the top.
55+
ptr_domains = env.client['Hardware_Server'].getReverseDomainRecords(id=instance['id']).pop()
56+
dns.sync_ptr_record(ptr_domains, instance['primaryIpAddress'], instance['fullyQualifiedDomainName'], ttl)
57+
58+
if aaaa_record:
59+
try:
60+
# done this way to stay within 80 character lines
61+
ipv6 = instance['primaryNetworkComponent']['primaryVersion6IpAddressRecord']['ipAddress']
62+
dns.sync_host_record(zone_id, instance['hostname'], ipv6, 'aaaa', ttl)
63+
except KeyError:
64+
raise exceptions.CLIAbort("%s does not have an ipv6 address" % instance['fullyQualifiedDomainName'])

SoftLayer/CLI/routes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@
236236
('hardware:rescue', 'SoftLayer.CLI.hardware.power:rescue'),
237237
('hardware:ready', 'SoftLayer.CLI.hardware.ready:cli'),
238238
('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'),
239+
('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'),
239240

240241
('securitygroup', 'SoftLayer.CLI.securitygroup'),
241242
('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'),

SoftLayer/CLI/virt/dns.py

Lines changed: 25 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Sync DNS records."""
22
# :license: MIT, see LICENSE for more details.
3+
# pylint: disable=duplicate-code
34

45
import click
56

@@ -15,138 +16,49 @@
1516
want to update both records, you may use the -a or --ptr arguments to limit
1617
the records updated.""")
1718
@click.argument('identifier')
18-
@click.option('--a-record', '-a',
19-
is_flag=True,
20-
help="Sync the A record for the host")
21-
@click.option('--aaaa-record',
22-
is_flag=True,
23-
help="Sync the AAAA record for the host")
19+
@click.option('--a-record', '-a', is_flag=True, help="Sync the A record for the host")
20+
@click.option('--aaaa-record', is_flag=True, help="Sync the AAAA record for the host")
2421
@click.option('--ptr', is_flag=True, help="Sync the PTR record for the host")
25-
@click.option('--ttl',
26-
default=7200,
27-
show_default=True,
28-
type=click.INT,
22+
@click.option('--ttl', default=7200, show_default=True, type=click.INT,
2923
help="Sets the TTL for the A and/or PTR records")
3024
@environment.pass_env
3125
def cli(env, identifier, a_record, aaaa_record, ptr, ttl):
3226
"""Sync DNS records."""
3327

34-
items = ['id',
35-
'globalIdentifier',
36-
'fullyQualifiedDomainName',
37-
'hostname',
38-
'domain',
39-
'primaryBackendIpAddress',
40-
'primaryIpAddress',
41-
'''primaryNetworkComponent[
42-
id, primaryIpAddress,
43-
primaryVersion6IpAddressRecord[ipAddress]
44-
]''']
45-
mask = "mask[%s]" % ','.join(items)
28+
mask = """mask[id, globalIdentifier, fullyQualifiedDomainName, hostname, domain,
29+
primaryBackendIpAddress,primaryIpAddress,
30+
primaryNetworkComponent[id,primaryIpAddress,primaryVersion6IpAddressRecord[ipAddress]]]"""
4631
dns = SoftLayer.DNSManager(env.client)
47-
vsi = SoftLayer.VSManager(env.client)
32+
server = SoftLayer.VSManager(env.client)
4833

49-
vs_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS')
50-
instance = vsi.get_instance(vs_id, mask=mask)
51-
zone_id = helpers.resolve_id(dns.resolve_ids,
52-
instance['domain'],
53-
name='zone')
54-
55-
def sync_a_record():
56-
"""Sync A record."""
57-
records = dns.get_records(zone_id,
58-
host=instance['hostname'],
59-
record_type='a')
60-
if not records:
61-
# don't have a record, lets add one to the base zone
62-
dns.create_record(zone['id'],
63-
instance['hostname'],
64-
'a',
65-
instance['primaryIpAddress'],
66-
ttl=ttl)
67-
else:
68-
if len(records) != 1:
69-
raise exceptions.CLIAbort("Aborting A record sync, found "
70-
"%d A record exists!" % len(records))
71-
rec = records[0]
72-
rec['data'] = instance['primaryIpAddress']
73-
rec['ttl'] = ttl
74-
dns.edit_record(rec)
75-
76-
def sync_aaaa_record():
77-
"""Sync AAAA record."""
78-
records = dns.get_records(zone_id,
79-
host=instance['hostname'],
80-
record_type='aaaa')
81-
try:
82-
# done this way to stay within 80 character lines
83-
component = instance['primaryNetworkComponent']
84-
record = component['primaryVersion6IpAddressRecord']
85-
ip_address = record['ipAddress']
86-
except KeyError:
87-
raise exceptions.CLIAbort("%s does not have an ipv6 address"
88-
% instance['fullyQualifiedDomainName'])
89-
90-
if not records:
91-
# don't have a record, lets add one to the base zone
92-
dns.create_record(zone['id'],
93-
instance['hostname'],
94-
'aaaa',
95-
ip_address,
96-
ttl=ttl)
97-
else:
98-
if len(records) != 1:
99-
raise exceptions.CLIAbort("Aborting A record sync, found "
100-
"%d A record exists!" % len(records))
101-
rec = records[0]
102-
rec['data'] = ip_address
103-
rec['ttl'] = ttl
104-
dns.edit_record(rec)
105-
106-
def sync_ptr_record():
107-
"""Sync PTR record."""
108-
host_rec = instance['primaryIpAddress'].split('.')[-1]
109-
ptr_domains = (env.client['Virtual_Guest']
110-
.getReverseDomainRecords(id=instance['id'])[0])
111-
edit_ptr = None
112-
for ptr in ptr_domains['resourceRecords']:
113-
if ptr['host'] == host_rec:
114-
ptr['ttl'] = ttl
115-
edit_ptr = ptr
116-
break
117-
118-
if edit_ptr:
119-
edit_ptr['data'] = instance['fullyQualifiedDomainName']
120-
dns.edit_record(edit_ptr)
121-
else:
122-
dns.create_record(ptr_domains['id'],
123-
host_rec,
124-
'ptr',
125-
instance['fullyQualifiedDomainName'],
126-
ttl=ttl)
34+
server_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS')
35+
instance = server.get_instance(server_id, mask=mask)
36+
zone_id = helpers.resolve_id(dns.resolve_ids, instance['domain'], name='zone')
12737

12838
if not instance['primaryIpAddress']:
129-
raise exceptions.CLIAbort('No primary IP address associated with '
130-
'this VS')
131-
132-
zone = dns.get_zone(zone_id)
39+
raise exceptions.CLIAbort('No primary IP address associated with this VS')
13340

13441
go_for_it = env.skip_confirmations or formatting.confirm(
135-
"Attempt to update DNS records for %s"
136-
% instance['fullyQualifiedDomainName'])
42+
"Attempt to update DNS records for %s" % instance['fullyQualifiedDomainName'])
13743

13844
if not go_for_it:
13945
raise exceptions.CLIAbort("Aborting DNS sync")
14046

141-
both = False
142-
if not ptr and not a_record and not aaaa_record:
143-
both = True
47+
# both will be true only if no options are passed in, basically.
48+
both = (not ptr) and (not a_record) and (not aaaa_record)
14449

14550
if both or a_record:
146-
sync_a_record()
51+
dns.sync_host_record(zone_id, instance['hostname'], instance['primaryIpAddress'], 'a', ttl)
14752

14853
if both or ptr:
149-
sync_ptr_record()
54+
# getReverseDomainRecords returns a list of 1 element, so just get the top.
55+
ptr_domains = env.client['Virtual_Guest'].getReverseDomainRecords(id=instance['id']).pop()
56+
dns.sync_ptr_record(ptr_domains, instance['primaryIpAddress'], instance['fullyQualifiedDomainName'], ttl)
15057

15158
if aaaa_record:
152-
sync_aaaa_record()
59+
try:
60+
# done this way to stay within 80 character lines
61+
ipv6 = instance['primaryNetworkComponent']['primaryVersion6IpAddressRecord']['ipAddress']
62+
dns.sync_host_record(zone_id, instance['hostname'], ipv6, 'aaaa', ttl)
63+
except KeyError:
64+
raise exceptions.CLIAbort("%s does not have an ipv6 address" % instance['fullyQualifiedDomainName'])

SoftLayer/managers/dns.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""
88
import time
99

10+
from SoftLayer import exceptions
1011
from SoftLayer import utils
1112

1213

@@ -205,13 +206,11 @@ def get_records(self, zone_id, ttl=None, data=None, host=None,
205206
_filter['resourceRecords']['data'] = utils.query_filter(data)
206207

207208
if record_type:
208-
_filter['resourceRecords']['type'] = utils.query_filter(
209-
record_type.lower())
209+
_filter['resourceRecords']['type'] = utils.query_filter(record_type.lower())
210210

211211
results = self.service.getResourceRecords(
212212
id=zone_id,
213-
mask='id,expire,domainId,host,minimum,refresh,retry,'
214-
'mxPriority,ttl,type,data,responsiblePerson',
213+
mask='id,expire,domainId,host,minimum,refresh,retry,mxPriority,ttl,type,data,responsiblePerson',
215214
filter=_filter.to_dict(),
216215
)
217216

@@ -226,6 +225,7 @@ def edit_record(self, record):
226225
:param dict record: the record to update
227226
228227
"""
228+
record.pop('isGatewayAddress', None)
229229
self.record.editObject(record, id=record['id'])
230230

231231
def dump_zone(self, zone_id):
@@ -235,3 +235,47 @@ def dump_zone(self, zone_id):
235235
236236
"""
237237
return self.service.getZoneFileContents(id=zone_id)
238+
239+
def sync_host_record(self, zone_id, hostname, ip_address, record_type='a', ttl=7200):
240+
"""For a given zone_id, will set hostname's A record to ip_address
241+
242+
:param integer zone_id: The zone id for the domain
243+
:param string hostname: host part of the record
244+
:param string ip_address: data part of the record
245+
:param integer ttl: TTL for the record
246+
:param string record_type: 'a' or 'aaaa'
247+
"""
248+
records = self.get_records(zone_id, host=hostname, record_type=record_type)
249+
if not records:
250+
# don't have a record, lets add one to the base zone
251+
self.create_record(zone_id, hostname, record_type, ip_address, ttl=ttl)
252+
else:
253+
if len(records) != 1:
254+
raise exceptions.SoftLayerError("Aborting record sync, found %d records!" % len(records))
255+
rec = records[0]
256+
rec['data'] = ip_address
257+
rec['ttl'] = ttl
258+
self.edit_record(rec)
259+
260+
def sync_ptr_record(self, ptr_domains, ip_address, fqdn, ttl=7200):
261+
"""Sync PTR record.
262+
263+
:param dict ptr_domains: result from SoftLayer_Virtual_Guest.getReverseDomainRecords or
264+
SoftLayer_Hardware_Server.getReverseDomainRecords
265+
:param string ip_address: ip address to sync with
266+
:param string fqdn: Fully Qualified Domain Name
267+
:param integer ttl: TTL for the record
268+
"""
269+
host_rec = ip_address.split('.')[-1]
270+
edit_ptr = None
271+
for ptr in ptr_domains['resourceRecords']:
272+
if ptr.get('host', '') == host_rec:
273+
ptr['ttl'] = ttl
274+
edit_ptr = ptr
275+
break
276+
277+
if edit_ptr:
278+
edit_ptr['data'] = fqdn
279+
self.edit_record(edit_ptr)
280+
else:
281+
self.create_record(ptr_domains['id'], host_rec, 'ptr', fqdn, ttl=ttl)

docs/cli/hardware.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,6 @@ This function updates the firmware of a server. If already at the latest version
9494
:prog: hw ready
9595
:show-nested:
9696

97+
.. click:: SoftLayer.CLI.hardware.dns-sync:cli
98+
:prog: hw dns-sync
99+
:show-nested:

0 commit comments

Comments
 (0)