Skip to content

Commit

Permalink
Merge pull request #1144 from tseaver/dns-resource_record_sets
Browse files Browse the repository at this point in the history
Add support for resource record sets
  • Loading branch information
tseaver committed Sep 21, 2015
2 parents b590330 + faee1ec commit c4602c3
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 7 deletions.
14 changes: 7 additions & 7 deletions docs/dns-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
66 changes: 66 additions & 0 deletions gcloud/dns/resource_record_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# 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 record set given its API representation
:type resource: dict
: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.ResourceRecordSet`
: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)
94 changes: 94 additions & 0 deletions gcloud/dns/test_resource_record_set.py
Original file line number Diff line number Diff line change
@@ -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
101 changes: 101 additions & 0 deletions gcloud/dns/test_zone.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down
45 changes: 45 additions & 0 deletions gcloud/dns/zone.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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')

0 comments on commit c4602c3

Please sign in to comment.