From c2925f81307c29fe125c2f0d6f39fbda672f7afa Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 21 Sep 2015 12:00:50 -0400 Subject: [PATCH 1/4] Change 'list_resources' -> 'list_resource_record_sets' in usage docs. --- docs/dns-usage.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/dns-usage.rst b/docs/dns-usage.rst index 78ec078c4c1e..4a674ce08d30 100644 --- a/docs/dns-usage.rst +++ b/docs/dns-usage.rst @@ -104,23 +104,23 @@ Each managed zone exposes a read-only set of resource records: >>> from gcloud import dns >>> client = dns.Client(project='PROJECT_ID') >>> zone = client.zone('acme-co') - >>> records, page_token = zone.list_resources() # API request + >>> records, page_token = zone.list_resource_record_sets() # API request >>> [(record.name, record.type, record.ttl, record.rrdatas) for record in records] [('example.com.', 'SOA', 21600, ['ns-cloud1.googlecomains.com dns-admin.google.com 1 21600 3600 1209600 300'])] .. note:: - The ``page_token`` returned from ``zone.list_resources()`` will be - an opaque string if there are more resources than can be returned in a + The ``page_token`` returned from ``zone.list_resource_record_sets()`` will + be an opaque string if there are more resources than can be returned in a single request. To enumerate them all, repeat calling - ``zone.list_resources()``, passing the ``page_token``, until the token - is ``None``. E.g. + ``zone.list_resource_record_sets()``, passing the ``page_token``, until + the token is ``None``. E.g. .. doctest:: - >>> records, page_token = zone.list_resources() # API request + >>> records, page_token = zone.list_resource_record_sets() # API request >>> while page_token is not None: - ... next_batch, page_token = zone.list_resources( + ... next_batch, page_token = zone.list_resource_record_sets( ... page_token=page_token) # API request ... records.extend(next_batch) From 90c9aadf554b89cf058735d3e322bc5b0c834281 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 21 Sep 2015 12:24:53 -0400 Subject: [PATCH 2/4] Add 'ResourceRecordSet' class. --- gcloud/dns/resource_record_set.py | 67 ++++++++++++++++++ gcloud/dns/test_resource_record_set.py | 94 ++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 gcloud/dns/resource_record_set.py create mode 100644 gcloud/dns/test_resource_record_set.py diff --git a/gcloud/dns/resource_record_set.py b/gcloud/dns/resource_record_set.py new file mode 100644 index 000000000000..8b4db9588f32 --- /dev/null +++ b/gcloud/dns/resource_record_set.py @@ -0,0 +1,67 @@ +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Define API ResourceRecordSets.""" + + +class ResourceRecordSet(object): + """ResourceRecordSets are DNS resource records. + + RRS are contained wihin a :class:`gcloud.dns.zone.ManagedZone` instance. + + See: + https://cloud.google.com/dns/api/v1/resourceRecordSets + + :type name: string + :param name: the name of the record set + + :type record_type: string + :param record_type: the RR type of the zone + + :type ttl: integer + :param ttl: TTL (in seconds) for caching the record sets + + :type rrdatas: list of string + :param rrdatas: one or more lines containing the resource data + + :type zone: :class:`gcloud.dns.zone.ManagedZone` + :param zone: A zone which holds one or more record sets. + """ + + def __init__(self, name, record_type, ttl, rrdatas, zone): + self.name = name + self.record_type = record_type + self.ttl = ttl + self.rrdatas = rrdatas + self.zone = zone + + @classmethod + def from_api_repr(cls, resource, zone): + """Factory: construct a zone given its API representation + + :type resource: dict + :param resource: zone resource representation returned from the API + + + :type zone: :class:`gcloud.dns.zone.ManagedZone` + :param zone: A zone which holds one or more record sets. + + :rtype: :class:`gcloud.dns.zone.ResourceRecordSets` + :returns: RRS parsed from ``resource``. + """ + name = resource['name'] + record_type = resource['type'] + ttl = int(resource['ttl']) + rrdatas = resource['rrdatas'] + return cls(name, record_type, ttl, rrdatas, zone=zone) diff --git a/gcloud/dns/test_resource_record_set.py b/gcloud/dns/test_resource_record_set.py new file mode 100644 index 000000000000..8f4bc98bd61b --- /dev/null +++ b/gcloud/dns/test_resource_record_set.py @@ -0,0 +1,94 @@ +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest2 + + +class TestResourceRecordSet(unittest2.TestCase): + + def _getTargetClass(self): + from gcloud.dns.resource_record_set import ResourceRecordSet + return ResourceRecordSet + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_ctor(self): + zone = _Zone() + + rrs = self._makeOne('test.example.com', 'CNAME', 3600, + ['www.example.com'], zone) + + self.assertEqual(rrs.name, 'test.example.com') + self.assertEqual(rrs.record_type, 'CNAME') + self.assertEqual(rrs.ttl, 3600) + self.assertEqual(rrs.rrdatas, ['www.example.com']) + self.assertTrue(rrs.zone is zone) + + def test_from_api_repr_missing_rrdatas(self): + zone = _Zone() + klass = self._getTargetClass() + + with self.assertRaises(KeyError): + klass.from_api_repr({'name': 'test.example.com', + 'type': 'CNAME', + 'ttl': 3600}, zone=zone) + + def test_from_api_repr_missing_ttl(self): + zone = _Zone() + klass = self._getTargetClass() + + with self.assertRaises(KeyError): + klass.from_api_repr({'name': 'test.example.com', + 'type': 'CNAME', + 'rrdatas': ['www.example.com']}, zone=zone) + + def test_from_api_repr_missing_type(self): + zone = _Zone() + klass = self._getTargetClass() + + with self.assertRaises(KeyError): + klass.from_api_repr({'name': 'test.example.com', + 'ttl': 3600, + 'rrdatas': ['www.example.com']}, zone=zone) + + def test_from_api_repr_missing_name(self): + zone = _Zone() + klass = self._getTargetClass() + + with self.assertRaises(KeyError): + klass.from_api_repr({'type': 'CNAME', + 'ttl': 3600, + 'rrdatas': ['www.example.com']}, zone=zone) + + def test_from_api_repr_bare(self): + zone = _Zone() + RESOURCE = { + 'kind': 'dns#resourceRecordSet', + 'name': 'test.example.com', + 'type': 'CNAME', + 'ttl': '3600', + 'rrdatas': ['www.example.com'], + } + klass = self._getTargetClass() + rrs = klass.from_api_repr(RESOURCE, zone=zone) + self.assertEqual(rrs.name, 'test.example.com') + self.assertEqual(rrs.record_type, 'CNAME') + self.assertEqual(rrs.ttl, 3600) + self.assertEqual(rrs.rrdatas, ['www.example.com']) + self.assertTrue(rrs.zone is zone) + + +class _Zone(object): + pass From 5cec5714f91b2397a9cc3bad7887dc64f0a802fa Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 21 Sep 2015 12:47:51 -0400 Subject: [PATCH 3/4] Add 'ManagedZone.list_resource_record_sets()'. --- gcloud/dns/test_zone.py | 101 ++++++++++++++++++++++++++++++++++++++++ gcloud/dns/zone.py | 45 ++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/gcloud/dns/test_zone.py b/gcloud/dns/test_zone.py index a96daa868ef7..760bc372b3df 100644 --- a/gcloud/dns/test_zone.py +++ b/gcloud/dns/test_zone.py @@ -314,6 +314,107 @@ def test_delete_w_alternate_client(self): self.assertEqual(req['method'], 'DELETE') self.assertEqual(req['path'], '/%s' % PATH) + def test_list_resource_record_sets_defaults(self): + from gcloud.dns.resource_record_set import ResourceRecordSet + PATH = 'projects/%s/managedZones/%s/rrsets' % ( + self.PROJECT, self.ZONE_NAME) + TOKEN = 'TOKEN' + NAME_1 = 'www.example.com' + TYPE_1 = 'A' + TTL_1 = '86400' + RRDATAS_1 = ['123.45.67.89'] + NAME_2 = 'alias.example.com' + TYPE_2 = 'CNAME' + TTL_2 = '3600' + RRDATAS_2 = ['www.example.com'] + DATA = { + 'nextPageToken': TOKEN, + 'rrsets': [ + {'kind': 'dns#resourceRecordSet', + 'name': NAME_1, + 'type': TYPE_1, + 'ttl': TTL_1, + 'rrdatas': RRDATAS_1}, + {'kind': 'dns#resourceRecordSet', + 'name': NAME_2, + 'type': TYPE_2, + 'ttl': TTL_2, + 'rrdatas': RRDATAS_2}, + ] + } + conn = _Connection(DATA) + client = _Client(project=self.PROJECT, connection=conn) + zone = self._makeOne(self.ZONE_NAME, self.DNS_NAME, client) + + rrsets, token = zone.list_resource_record_sets() + + self.assertEqual(len(rrsets), len(DATA['rrsets'])) + for found, expected in zip(rrsets, DATA['rrsets']): + self.assertTrue(isinstance(found, ResourceRecordSet)) + self.assertEqual(found.name, expected['name']) + self.assertEqual(found.record_type, expected['type']) + self.assertEqual(found.ttl, int(expected['ttl'])) + self.assertEqual(found.rrdatas, expected['rrdatas']) + self.assertEqual(token, TOKEN) + + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '/%s' % PATH) + + def test_list_resource_record_sets_explicit(self): + from gcloud.dns.resource_record_set import ResourceRecordSet + PATH = 'projects/%s/managedZones/%s/rrsets' % ( + self.PROJECT, self.ZONE_NAME) + TOKEN = 'TOKEN' + NAME_1 = 'www.example.com' + TYPE_1 = 'A' + TTL_1 = '86400' + RRDATAS_1 = ['123.45.67.89'] + NAME_2 = 'alias.example.com' + TYPE_2 = 'CNAME' + TTL_2 = '3600' + RRDATAS_2 = ['www.example.com'] + DATA = { + 'rrsets': [ + {'kind': 'dns#resourceRecordSet', + 'name': NAME_1, + 'type': TYPE_1, + 'ttl': TTL_1, + 'rrdatas': RRDATAS_1}, + {'kind': 'dns#resourceRecordSet', + 'name': NAME_2, + 'type': TYPE_2, + 'ttl': TTL_2, + 'rrdatas': RRDATAS_2}, + ] + } + conn1 = _Connection() + client1 = _Client(project=self.PROJECT, connection=conn1) + conn2 = _Connection(DATA) + client2 = _Client(project=self.PROJECT, connection=conn2) + zone = self._makeOne(self.ZONE_NAME, self.DNS_NAME, client1) + + rrsets, token = zone.list_resource_record_sets( + max_results=3, page_token=TOKEN, client=client2) + + self.assertEqual(len(rrsets), len(DATA['rrsets'])) + for found, expected in zip(rrsets, DATA['rrsets']): + self.assertTrue(isinstance(found, ResourceRecordSet)) + self.assertEqual(found.name, expected['name']) + self.assertEqual(found.record_type, expected['type']) + self.assertEqual(found.ttl, int(expected['ttl'])) + self.assertEqual(found.rrdatas, expected['rrdatas']) + self.assertEqual(token, None) + + self.assertEqual(len(conn1._requested), 0) + self.assertEqual(len(conn2._requested), 1) + req = conn2._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '/%s' % PATH) + self.assertEqual(req['query_params'], + {'maxResults': 3, 'pageToken': TOKEN}) + class _Client(object): diff --git a/gcloud/dns/zone.py b/gcloud/dns/zone.py index 8a56fb13348a..4ed9eea65ac2 100644 --- a/gcloud/dns/zone.py +++ b/gcloud/dns/zone.py @@ -17,6 +17,7 @@ from gcloud._helpers import _datetime_from_microseconds from gcloud.exceptions import NotFound +from gcloud.dns.resource_record_set import ResourceRecordSet class ManagedZone(object): @@ -267,3 +268,47 @@ def delete(self, client=None): """ client = self._require_client(client) client.connection.api_request(method='DELETE', path=self.path) + + def list_resource_record_sets(self, max_results=None, page_token=None, + client=None): + """List resource record sets for this zone. + + See: + https://cloud.google.com/dns/api/v1/resourceRecordSets/list + + :type max_results: int + :param max_results: maximum number of zones to return, If not + passed, defaults to a value set by the API. + + :type page_token: string + :param page_token: opaque marker for the next "page" of zones. If + not passed, the API will return the first page of + zones. + + :type client: :class:`gcloud.dns.client.Client` or ``NoneType`` + :param client: the client to use. If not passed, falls back to the + ``client`` stored on the current zone. + + :rtype: tuple, (list, str) + :returns: list of + :class:`gcloud.dns.resource_record_set.ResourceRecordSet`, + plus a "next page token" string: if the token is not None, + indicates that more zones can be retrieved with another + call (pass that value as ``page_token``). + """ + params = {} + + if max_results is not None: + params['maxResults'] = max_results + + if page_token is not None: + params['pageToken'] = page_token + + path = '/projects/%s/managedZones/%s/rrsets' % ( + self.project, self.name) + client = self._require_client(client) + conn = client.connection + resp = conn.api_request(method='GET', path=path, query_params=params) + zones = [ResourceRecordSet.from_api_repr(resource, self) + for resource in resp['rrsets']] + return zones, resp.get('nextPageToken') From faee1ece3b80f9abeb53f54a7cbe4b9c6a673b4d Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 21 Sep 2015 13:42:36 -0400 Subject: [PATCH 4/4] Docstring typos. --- gcloud/dns/resource_record_set.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gcloud/dns/resource_record_set.py b/gcloud/dns/resource_record_set.py index 8b4db9588f32..b53fe8ceebea 100644 --- a/gcloud/dns/resource_record_set.py +++ b/gcloud/dns/resource_record_set.py @@ -48,16 +48,15 @@ def __init__(self, name, record_type, ttl, rrdatas, zone): @classmethod def from_api_repr(cls, resource, zone): - """Factory: construct a zone given its API representation + """Factory: construct a record set given its API representation :type resource: dict - :param resource: zone resource representation returned from the API - + :param resource: record sets representation returned from the API :type zone: :class:`gcloud.dns.zone.ManagedZone` :param zone: A zone which holds one or more record sets. - :rtype: :class:`gcloud.dns.zone.ResourceRecordSets` + :rtype: :class:`gcloud.dns.zone.ResourceRecordSet` :returns: RRS parsed from ``resource``. """ name = resource['name']