From 5e926fea8856386746c9a0fc4aa4dfb785dfba5d Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Fri, 7 Sep 2018 18:01:55 -0400 Subject: [PATCH 1/4] Added new methods on dns manager for mx/ptr/srv creation, CLI supports creation of all kind of records, including ptr --- SoftLayer/CLI/dns/record_add.py | 79 ++++++++++++++++++++++++++++++--- SoftLayer/managers/dns.py | 76 ++++++++++++++++++++++++++++--- tests/CLI/modules/dns_tests.py | 6 +-- tests/managers/dns_tests.py | 67 ++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/dns/record_add.py b/SoftLayer/CLI/dns/record_add.py index fbf8213b2..faabfd65c 100644 --- a/SoftLayer/CLI/dns/record_add.py +++ b/SoftLayer/CLI/dns/record_add.py @@ -5,24 +5,89 @@ import SoftLayer from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import helpers # pylint: disable=redefined-builtin @click.command() -@click.argument('zone') @click.argument('record') @click.argument('type') @click.argument('data') +@click.option('--zone', + help="Zone name or identifier that the resource record will be associated with.\n" + "Required for all record types except PTR") @click.option('--ttl', - type=click.INT, - default=7200, + default=900, show_default=True, help='TTL value in seconds, such as 86400') +@click.option('--priority', + default=10, + show_default=True, + help='The priority of the target host. (MX or SRV type only)') +@click.option('--protocol', + type=click.Choice(['tcp', 'udp', 'tls']), + default='tcp', + show_default=True, + help='The protocol of the service, usually either TCP or UDP. (SRV type only)') +@click.option('--port', + type=click.INT, + help='The TCP/UDP/TLS port on which the service is to be found. (SRV type only)') +@click.option('--service', + help='The symbolic name of the desired service. (SRV type only)') +@click.option('--weight', + default=5, + show_default=True, + help='Relative weight for records with same priority. (SRV type only)') @environment.pass_env -def cli(env, zone, record, type, data, ttl): - """Add resource record.""" +def cli(env, record, type, data, zone, ttl, priority, protocol, port, service, weight): + """Add resource record. + + Each resource record contains a RECORD and DATA property, defining a resource's name and it's target data. + Domains contain multiple types of resource records so it can take one of the following values: A, AAAA, CNAME, + MX, SPF, SRV, and PTR. + + About reverse records (PTR), the RECORD value must to be the public Ip Address of device you would like to manage + reverse DNS. + + slcli dns record-add 10.10.8.21 PTR myhost.com --ttl=900 + + Examples: + + slcli dns record-add myhost.com A 192.168.1.10 --zone=foobar.com --ttl=900 + + slcli dns record-add myhost.com AAAA 2001:DB8::1 --zone=foobar.com + + slcli dns record-add 192.168.1.2 MX 192.168.1.10 --zone=foobar.com --priority=11 --ttl=1800 + + slcli dns record-add myhost.com TXT "txt-verification=rXOxyZounZs87oacJSKvbUSIQ" --zone=2223334 + + slcli dns record-add myhost.com SPF "v=spf1 include:_spf.google.com ~all" --zone=2223334 + + slcli dns record-add myhost.com SRV 192.168.1.10 --zone=2223334 --service=foobar --port=80 --protocol=TCP + + """ manager = SoftLayer.DNSManager(env.client) - zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') - manager.create_record(zone_id, record, type, data, ttl=ttl) + type = type.upper() + + if zone and type != 'PTR': + zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') + + if type == 'MX': + result = manager.create_record_mx(zone_id, record, data, ttl=ttl, priority=priority) + elif type == 'SRV': + result = manager.create_record_srv(zone_id, record, data, protocol, port, service, + ttl=ttl, priority=priority, weight=weight) + else: + result = manager.create_record(zone_id, record, type, data, ttl=ttl) + + elif type == 'PTR': + result = manager.create_record_ptr(record, data, ttl=ttl) + else: + raise exceptions.CLIAbort("%s isn't a valid record type or zone is missing" % (type)) + + if result: + click.secho("%s record added successfully" % (type), fg='green') + else: + click.secho("Failed to add %s record" % (type), fg='red') diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index c1b7b3b60..a3fc322af 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -89,17 +89,81 @@ def create_record(self, zone_id, record, record_type, data, ttl=60): :param integer id: the zone's ID :param record: the name of the record to add - :param record_type: the type of record (A, AAAA, CNAME, MX, TXT, etc.) + :param record_type: the type of record (A, AAAA, CNAME, TXT, etc.) :param data: the record's value :param integer ttl: the TTL or time-to-live value (default: 60) """ - return self.record.createObject({ - 'domainId': zone_id, - 'ttl': ttl, + resource_record = self._generate_create_dict(record, record_type, data, + ttl, domainId=zone_id) + return self.record.createObject(resource_record) + + def create_record_mx(self, zone_id, record, data, ttl=60, priority=10): + """Create a mx resource record on a domain. + + :param integer id: the zone's ID + :param record: the name of the record to add + :param data: the record's value + :param integer ttl: the TTL or time-to-live value (default: 60) + :param integer priority: the priority of the target host + + """ + resource_record = self._generate_create_dict(record, 'MX', data, ttl, + domainId=zone_id, mxPriority=priority) + return self.record.createObject(resource_record) + + def create_record_srv(self, zone_id, record, data, protocol, port, service, + ttl=60, priority=20, weight=10): + """Create a resource record on a domain. + + :param integer id: the zone's ID + :param record: the name of the record to add + :param data: the record's value + :param string protocol: the protocol of the service, usually either TCP or UDP. + :param integer port: the TCP or UDP port on which the service is to be found. + :param string service: the symbolic name of the desired service. + :param integer ttl: the TTL or time-to-live value (default: 60) + :param integer priority: the priority of the target host (default: 20) + :param integer weight: relative weight for records with same priority (default: 10) + + """ + resource_record = self._generate_create_dict(record, 'SRV', data, ttl, domainId=zone_id, + priority=priority, protocol=protocol, port=port, + service=service, weight=weight) + + # The createObject won't creates SRV records unless we send the following complexType. + resource_record['complexType'] = 'SoftLayer_Dns_Domain_ResourceRecord_SrvType' + + return self.record.createObject(resource_record) + + def create_record_ptr(self, record, data, ttl=60): + """Create a reverse record. + + :param record: the public ip address of device for which you would like to manage reverse DNS. + :param data: the record's value + :param integer ttl: the TTL or time-to-live value (default: 60) + + """ + resource_record = self._generate_create_dict(record, 'PTR', data, ttl) + + return self.record.createObject(resource_record) + + @staticmethod + def _generate_create_dict(record, record_type, data, ttl, **kwargs): + """Returns a dict appropriate to pass into Dns_Domain_ResourceRecord::createObject""" + + # Basic dns record structure + resource_record = { 'host': record, - 'type': record_type, - 'data': data}) + 'data': data, + 'ttl': ttl, + 'type': record_type + } + + for (key, value) in kwargs.items(): + resource_record.setdefault(key, value) + + return resource_record def delete_record(self, record_id): """Delete a resource record by its ID. diff --git a/tests/CLI/modules/dns_tests.py b/tests/CLI/modules/dns_tests.py index 836da74a9..890e513cf 100644 --- a/tests/CLI/modules/dns_tests.py +++ b/tests/CLI/modules/dns_tests.py @@ -72,11 +72,11 @@ def test_list_records(self): 'ttl': 7200}) def test_add_record(self): - result = self.run_command(['dns', 'record-add', '1234', 'hostname', - 'A', 'd', '--ttl=100']) + result = self.run_command(['dns', 'record-add', 'hostname', 'A', + 'data', '--zone=1234', '--ttl=100']) self.assert_no_fail(result) - self.assertEqual(result.output, "") + self.assertEqual(str(result.output), 'A record added successfully\n') @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_delete_record(self, no_going_back_mock): diff --git a/tests/managers/dns_tests.py b/tests/managers/dns_tests.py index 8cd83a3a2..070eed707 100644 --- a/tests/managers/dns_tests.py +++ b/tests/managers/dns_tests.py @@ -91,6 +91,73 @@ def test_create_record(self): },)) self.assertEqual(res, {'name': 'example.com'}) + def test_create_record_mx(self): + res = self.dns_client.create_record_mx(1, 'test', 'testing', ttl=1200, priority=21) + + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'createObject', + args=({ + 'domainId': 1, + 'ttl': 1200, + 'host': 'test', + 'type': 'MX', + 'data': 'testing', + 'mxPriority': 21 + },)) + self.assertEqual(res, {'name': 'example.com'}) + + def test_create_record_srv(self): + res = self.dns_client.create_record_srv(1, 'record', 'test_data', 'SLS', 8080, 'foobar', + ttl=1200, priority=21, weight=15) + + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'createObject', + args=({ + 'complexType': 'SoftLayer_Dns_Domain_ResourceRecord_SrvType', + 'domainId': 1, + 'ttl': 1200, + 'host': 'record', + 'type': 'SRV', + 'data': 'test_data', + 'priority': 21, + 'weight': 15, + 'service': 'foobar', + 'port': 8080, + 'protocol': 'SLS' + },)) + self.assertEqual(res, {'name': 'example.com'}) + + def test_create_record_ptr(self): + res = self.dns_client.create_record_ptr('test', 'testing', ttl=1200) + + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'createObject', + args=({ + 'ttl': 1200, + 'host': 'test', + 'type': 'PTR', + 'data': 'testing' + },)) + self.assertEqual(res, {'name': 'example.com'}) + + def test_generate_create_dict(self): + data = self.dns_client._generate_create_dict('foo', 'pmx', 'bar', 60, domainId=1234, + mxPriority=18, port=80, protocol='TCP', weight=25) + + assert_data = { + 'host': 'foo', + 'data': 'bar', + 'ttl': 60, + 'type': 'pmx', + 'domainId': 1234, + 'mxPriority': 18, + 'port': 80, + 'protocol': 'TCP', + 'weight': 25 + } + + self.assertEqual(data, assert_data) + def test_delete_record(self): self.dns_client.delete_record(1) From 4b1fa0e00dd134155190d7735f19d2f476c772d2 Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Tue, 11 Sep 2018 16:29:02 -0400 Subject: [PATCH 2/4] More unittests were added to increase the coverage, an else conditional was removed since it will never be executed --- SoftLayer/CLI/dns/record_add.py | 15 ++++++--------- tests/CLI/modules/dns_tests.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/dns/record_add.py b/SoftLayer/CLI/dns/record_add.py index faabfd65c..dbeca03b8 100644 --- a/SoftLayer/CLI/dns/record_add.py +++ b/SoftLayer/CLI/dns/record_add.py @@ -75,19 +75,16 @@ def cli(env, record, type, data, zone, ttl, priority, protocol, port, service, w zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') if type == 'MX': - result = manager.create_record_mx(zone_id, record, data, ttl=ttl, priority=priority) + manager.create_record_mx(zone_id, record, data, ttl=ttl, priority=priority) elif type == 'SRV': - result = manager.create_record_srv(zone_id, record, data, protocol, port, service, - ttl=ttl, priority=priority, weight=weight) + manager.create_record_srv(zone_id, record, data, protocol, port, service, + ttl=ttl, priority=priority, weight=weight) else: - result = manager.create_record(zone_id, record, type, data, ttl=ttl) + manager.create_record(zone_id, record, type, data, ttl=ttl) elif type == 'PTR': - result = manager.create_record_ptr(record, data, ttl=ttl) + manager.create_record_ptr(record, data, ttl=ttl) else: raise exceptions.CLIAbort("%s isn't a valid record type or zone is missing" % (type)) - if result: - click.secho("%s record added successfully" % (type), fg='green') - else: - click.secho("Failed to add %s record" % (type), fg='red') + click.secho("%s record added successfully" % (type), fg='green') diff --git a/tests/CLI/modules/dns_tests.py b/tests/CLI/modules/dns_tests.py index 890e513cf..3c1329b39 100644 --- a/tests/CLI/modules/dns_tests.py +++ b/tests/CLI/modules/dns_tests.py @@ -78,6 +78,36 @@ def test_add_record(self): self.assert_no_fail(result) self.assertEqual(str(result.output), 'A record added successfully\n') + def test_add_record_mx(self): + result = self.run_command(['dns', 'record-add', 'hostname', 'MX', + 'data', '--zone=1234', '--ttl=100', '--priority=25']) + + self.assert_no_fail(result) + self.assertEqual(str(result.output), 'MX record added successfully\n') + + def test_add_record_srv(self): + result = self.run_command(['dns', 'record-add', 'hostname', 'SRV', + 'data', '--zone=1234', '--protocol=udp', + '--port=88', '--ttl=100', '--weight=5']) + + self.assert_no_fail(result) + self.assertEqual(str(result.output), 'SRV record added successfully\n') + + def test_add_record_ptr(self): + result = self.run_command(['dns', 'record-add', '192.168.1.1', 'PTR', + 'hostname', '--ttl=100']) + + self.assert_no_fail(result) + self.assertEqual(str(result.output), 'PTR record added successfully\n') + + def test_add_record_abort(self): + result = self.run_command(['dns', 'record-add', 'hostname', 'A', + 'data', '--ttl=100']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + self.assertEqual(result.exception.message, "A isn't a valid record type or zone is missing") + @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_delete_record(self, no_going_back_mock): no_going_back_mock.return_value = True From cc549d18ec711906fa19a3a4d47ce7992c4605e2 Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Thu, 13 Sep 2018 18:40:43 -0400 Subject: [PATCH 3/4] Use record_type instead of only type since this is a built --- SoftLayer/CLI/dns/record_add.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/dns/record_add.py b/SoftLayer/CLI/dns/record_add.py index dbeca03b8..b0cc174bf 100644 --- a/SoftLayer/CLI/dns/record_add.py +++ b/SoftLayer/CLI/dns/record_add.py @@ -69,22 +69,22 @@ def cli(env, record, type, data, zone, ttl, priority, protocol, port, service, w """ manager = SoftLayer.DNSManager(env.client) - type = type.upper() + record_type = type.upper() - if zone and type != 'PTR': + if zone and record_type != 'PTR': zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') - if type == 'MX': + if record_type == 'MX': manager.create_record_mx(zone_id, record, data, ttl=ttl, priority=priority) - elif type == 'SRV': + elif record_type == 'SRV': manager.create_record_srv(zone_id, record, data, protocol, port, service, ttl=ttl, priority=priority, weight=weight) else: - manager.create_record(zone_id, record, type, data, ttl=ttl) + manager.create_record(zone_id, record, record_type, data, ttl=ttl) - elif type == 'PTR': + elif record_type == 'PTR': manager.create_record_ptr(record, data, ttl=ttl) else: - raise exceptions.CLIAbort("%s isn't a valid record type or zone is missing" % (type)) + raise exceptions.CLIAbort("%s isn't a valid record type or zone is missing" % record_type) - click.secho("%s record added successfully" % (type), fg='green') + click.secho("%s record added successfully" % record_type, fg='green') From 6fe950cdb8510f80f9068261c9c49f5c8f11f4f3 Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Thu, 13 Sep 2018 18:45:57 -0400 Subject: [PATCH 4/4] Use record_type instead of only type since this is a built --- SoftLayer/CLI/dns/record_add.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/dns/record_add.py b/SoftLayer/CLI/dns/record_add.py index b0cc174bf..03caf8ff2 100644 --- a/SoftLayer/CLI/dns/record_add.py +++ b/SoftLayer/CLI/dns/record_add.py @@ -12,7 +12,7 @@ @click.command() @click.argument('record') -@click.argument('type') +@click.argument('record_type') @click.argument('data') @click.option('--zone', help="Zone name or identifier that the resource record will be associated with.\n" @@ -40,7 +40,7 @@ show_default=True, help='Relative weight for records with same priority. (SRV type only)') @environment.pass_env -def cli(env, record, type, data, zone, ttl, priority, protocol, port, service, weight): +def cli(env, record, record_type, data, zone, ttl, priority, protocol, port, service, weight): """Add resource record. Each resource record contains a RECORD and DATA property, defining a resource's name and it's target data. @@ -69,7 +69,7 @@ def cli(env, record, type, data, zone, ttl, priority, protocol, port, service, w """ manager = SoftLayer.DNSManager(env.client) - record_type = type.upper() + record_type = record_type.upper() if zone and record_type != 'PTR': zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone')