1
# Copyright (c) 2011 Blue Pines Technologies LLC, Brad Carleton
3
# Copyright (c) 2012 42 Lines Inc., Jim Browne
6
# Permission is hereby granted, free of charge, to any person obtaining a
7
# copy of this software and associated documentation files (the
8
# "Software"), to deal in the Software without restriction, including
9
# without limitation the rights to use, copy, modify, merge, publish, dis-
10
# tribute, sublicense, and/or sell copies of the Software, and to permit
11
# persons to whom the Software is furnished to do so, subject to the fol-
14
# The above copyright notice and this permission notice shall be included
15
# in all copies or substantial portions of the Software.
17
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
19
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
20
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
28
from boto.exception import TooManyRecordsException
29
from boto.route53.record import ResourceRecordSets
30
from boto.route53.status import Status
37
:ivar Route53Connection route53connection
38
:ivar str Id: The ID of the hosted zone.
40
def __init__(self, route53connection, zone_dict):
41
self.route53connection = route53connection
44
self.id = zone_dict['Id'].replace('/hostedzone/', '')
46
self.__setattr__(key.lower(), zone_dict[key])
49
return '<Zone:%s>' % self.name
51
def _commit(self, changes):
53
Commit a set of changes and return the ChangeInfo portion of
56
:type changes: ResourceRecordSets
57
:param changes: changes to be committed
59
response = changes.commit()
60
return response['ChangeResourceRecordSetsResponse']['ChangeInfo']
62
def _new_record(self, changes, resource_type, name, value, ttl, identifier,
65
Add a CREATE change record to an existing ResourceRecordSets
67
:type changes: ResourceRecordSets
68
:param changes: change set to append to
71
:param name: The name of the resource record you want to
72
perform the action on.
74
:type resource_type: str
75
:param resource_type: The DNS record type
77
:param value: Appropriate value for resource_type
80
:param ttl: The resource record cache time to live (TTL), in seconds.
82
:type identifier: tuple
83
:param identifier: A tuple for setting WRR or LBR attributes. Valid
86
* (str, int): WRR record [e.g. ('foo',10)]
87
* (str, str): LBR record [e.g. ('foo','us-east-1')
90
:param comment: A comment that will be stored with the change.
94
if identifier is not None:
97
weight = identifier[1]
98
identifier = identifier[0]
100
region = identifier[1]
101
identifier = identifier[0]
102
change = changes.add_change("CREATE", name, resource_type, ttl,
103
identifier=identifier, weight=weight,
105
if type(value) in [list, tuple, set]:
107
change.add_value(record)
109
change.add_value(value)
111
def add_record(self, resource_type, name, value, ttl=60, identifier=None,
114
Add a new record to this Zone. See _new_record for parameter
115
documentation. Returns a Status object.
117
changes = ResourceRecordSets(self.route53connection, self.id, comment)
118
self._new_record(changes, resource_type, name, value, ttl, identifier,
120
return Status(self.route53connection, self._commit(changes))
122
def update_record(self, old_record, new_value, new_ttl=None,
123
new_identifier=None, comment=""):
125
Update an existing record in this Zone. Returns a Status object.
127
:type old_record: ResourceRecord
128
:param old_record: A ResourceRecord (e.g. returned by find_records)
130
See _new_record for additional parameter documentation.
132
new_ttl = new_ttl or default_ttl
133
record = copy.copy(old_record)
134
changes = ResourceRecordSets(self.route53connection, self.id, comment)
135
changes.add_change_record("DELETE", record)
136
self._new_record(changes, record.type, record.name,
137
new_value, new_ttl, new_identifier, comment)
138
return Status(self.route53connection, self._commit(changes))
140
def delete_record(self, record, comment=""):
142
Delete one or more records from this Zone. Returns a Status object.
144
:param record: A ResourceRecord (e.g. returned by
145
find_records) or list, tuple, or set of ResourceRecords.
148
:param comment: A comment that will be stored with the change.
150
changes = ResourceRecordSets(self.route53connection, self.id, comment)
151
if type(record) in [list, tuple, set]:
153
changes.add_change_record("DELETE", r)
155
changes.add_change_record("DELETE", record)
156
return Status(self.route53connection, self._commit(changes))
158
def add_cname(self, name, value, ttl=None, identifier=None, comment=""):
160
Add a new CNAME record to this Zone. See _new_record for
161
parameter documentation. Returns a Status object.
163
ttl = ttl or default_ttl
164
name = self.route53connection._make_qualified(name)
165
value = self.route53connection._make_qualified(value)
166
return self.add_record(resource_type='CNAME',
170
identifier=identifier,
173
def add_a(self, name, value, ttl=None, identifier=None, comment=""):
175
Add a new A record to this Zone. See _new_record for
176
parameter documentation. Returns a Status object.
178
ttl = ttl or default_ttl
179
name = self.route53connection._make_qualified(name)
180
return self.add_record(resource_type='A',
184
identifier=identifier,
187
def add_mx(self, name, records, ttl=None, identifier=None, comment=""):
189
Add a new MX record to this Zone. See _new_record for
190
parameter documentation. Returns a Status object.
192
ttl = ttl or default_ttl
193
records = self.route53connection._make_qualified(records)
194
return self.add_record(resource_type='MX',
198
identifier=identifier,
201
def find_records(self, name, type, desired=1, all=False, identifier=None):
203
Search this Zone for records that match given parameters.
204
Returns None if no results, a ResourceRecord if one result, or
205
a ResourceRecordSets if more than one result.
208
:param name: The name of the records should match this parameter
211
:param type: The type of the records should match this parameter
214
:param desired: The number of desired results. If the number of
215
matching records in the Zone exceeds the value of this parameter,
216
throw TooManyRecordsException
219
:param all: If true return all records that match name, type, and
220
identifier parameters
222
:type identifier: Tuple
223
:param identifier: A tuple specifying WRR or LBR attributes. Valid
226
* (str, int): WRR record [e.g. ('foo',10)]
227
* (str, str): LBR record [e.g. ('foo','us-east-1')
230
name = self.route53connection._make_qualified(name)
231
returned = self.route53connection.get_all_rrsets(self.id, name=name,
234
# name/type for get_all_rrsets sets the starting record; they
236
results = [r for r in returned if r.name == name and r.type == type]
240
if identifier is not None:
243
weight = identifier[1]
245
region = identifier[1]
247
if weight is not None:
248
results = [r for r in results if (r.weight == weight and
249
r.identifier == identifier[0])]
250
if region is not None:
251
results = [r for r in results if (r.region == region and
252
r.identifier == identifier[0])]
254
if ((not all) and (len(results) > desired)):
255
message = "Search: name %s type %s" % (name, type)
256
message += "\nFound: "
257
message += ", ".join(["%s %s %s" % (r.name, r.type, r.to_print())
259
raise TooManyRecordsException(message)
260
elif len(results) > 1:
262
elif len(results) == 1:
267
def get_cname(self, name, all=False):
269
Search this Zone for CNAME records that match name.
271
Returns a ResourceRecord.
273
If there is more than one match return all as a
274
ResourceRecordSets if all is True, otherwise throws
275
TooManyRecordsException.
277
return self.find_records(name, 'CNAME', all=all)
279
def get_a(self, name, all=False):
281
Search this Zone for A records that match name.
283
Returns a ResourceRecord.
285
If there is more than one match return all as a
286
ResourceRecordSets if all is True, otherwise throws
287
TooManyRecordsException.
289
return self.find_records(name, 'A', all=all)
291
def get_mx(self, name, all=False):
293
Search this Zone for MX records that match name.
295
Returns a ResourceRecord.
297
If there is more than one match return all as a
298
ResourceRecordSets if all is True, otherwise throws
299
TooManyRecordsException.
301
return self.find_records(name, 'MX', all=all)
303
def update_cname(self, name, value, ttl=None, identifier=None, comment=""):
305
Update the given CNAME record in this Zone to a new value, ttl,
306
and identifier. Returns a Status object.
308
Will throw TooManyRecordsException is name, value does not match
311
name = self.route53connection._make_qualified(name)
312
value = self.route53connection._make_qualified(value)
313
old_record = self.get_cname(name)
314
ttl = ttl or old_record.ttl
315
return self.update_record(old_record,
318
new_identifier=identifier,
321
def update_a(self, name, value, ttl=None, identifier=None, comment=""):
323
Update the given A record in this Zone to a new value, ttl,
324
and identifier. Returns a Status object.
326
Will throw TooManyRecordsException is name, value does not match
329
name = self.route53connection._make_qualified(name)
330
old_record = self.get_a(name)
331
ttl = ttl or old_record.ttl
332
return self.update_record(old_record,
335
new_identifier=identifier,
338
def update_mx(self, name, value, ttl=None, identifier=None, comment=""):
340
Update the given MX record in this Zone to a new value, ttl,
341
and identifier. Returns a Status object.
343
Will throw TooManyRecordsException is name, value does not match
346
name = self.route53connection._make_qualified(name)
347
value = self.route53connection._make_qualified(value)
348
old_record = self.get_mx(name)
349
ttl = ttl or old_record.ttl
350
return self.update_record(old_record,
353
new_identifier=identifier,
356
def delete_cname(self, name, identifier=None, all=False):
358
Delete a CNAME record matching name and identifier from
359
this Zone. Returns a Status object.
361
If there is more than one match delete all matching records if
362
all is True, otherwise throws TooManyRecordsException.
364
name = self.route53connection._make_qualified(name)
365
record = self.find_records(name, 'CNAME', identifier=identifier,
367
return self.delete_record(record)
369
def delete_a(self, name, identifier=None, all=False):
371
Delete an A record matching name and identifier from this
372
Zone. Returns a Status object.
374
If there is more than one match delete all matching records if
375
all is True, otherwise throws TooManyRecordsException.
377
name = self.route53connection._make_qualified(name)
378
record = self.find_records(name, 'A', identifier=identifier,
380
return self.delete_record(record)
382
def delete_mx(self, name, identifier=None, all=False):
384
Delete an MX record matching name and identifier from this
385
Zone. Returns a Status object.
387
If there is more than one match delete all matching records if
388
all is True, otherwise throws TooManyRecordsException.
390
name = self.route53connection._make_qualified(name)
391
record = self.find_records(name, 'MX', identifier=identifier,
393
return self.delete_record(record)
395
def get_records(self):
397
Return a ResourceRecordsSets for all of the records in this zone.
399
return self.route53connection.get_all_rrsets(self.id)
403
Request that this zone be deleted by Amazon.
405
self.route53connection.delete_hosted_zone(self.id)
407
def get_nameservers(self):
408
""" Get the list of nameservers for this zone."""
409
ns = self.find_records(self.name, 'NS')
411
ns = ns.resource_records