| OLD | NEW |
| (Empty) | |
| 1 # Copyright (c) 2010 Chris Moyer http://coredumped.org/ |
| 2 # Copyright (c) 2012 Mitch Garnaat http://garnaat.org/ |
| 3 # Copyright (c) 2012 Amazon.com, Inc. or its affiliates. |
| 4 # All rights reserved. |
| 5 # |
| 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- |
| 12 # lowing conditions: |
| 13 # |
| 14 # The above copyright notice and this permission notice shall be included |
| 15 # in all copies or substantial portions of the Software. |
| 16 # |
| 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 |
| 23 # IN THE SOFTWARE. |
| 24 |
| 25 RECORD_TYPES = ['A', 'AAAA', 'TXT', 'CNAME', 'MX', 'PTR', 'SRV', 'SPF'] |
| 26 |
| 27 from boto.resultset import ResultSet |
| 28 class ResourceRecordSets(ResultSet): |
| 29 """ |
| 30 A list of resource records. |
| 31 |
| 32 :ivar hosted_zone_id: The ID of the hosted zone. |
| 33 :ivar comment: A comment that will be stored with the change. |
| 34 :ivar changes: A list of changes. |
| 35 """ |
| 36 |
| 37 ChangeResourceRecordSetsBody = """<?xml version="1.0" encoding="UTF-8"?> |
| 38 <ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/20
12-02-29/"> |
| 39 <ChangeBatch> |
| 40 <Comment>%(comment)s</Comment> |
| 41 <Changes>%(changes)s</Changes> |
| 42 </ChangeBatch> |
| 43 </ChangeResourceRecordSetsRequest>""" |
| 44 |
| 45 ChangeXML = """<Change> |
| 46 <Action>%(action)s</Action> |
| 47 %(record)s |
| 48 </Change>""" |
| 49 |
| 50 def __init__(self, connection=None, hosted_zone_id=None, comment=None): |
| 51 self.connection = connection |
| 52 self.hosted_zone_id = hosted_zone_id |
| 53 self.comment = comment |
| 54 self.changes = [] |
| 55 self.next_record_name = None |
| 56 self.next_record_type = None |
| 57 ResultSet.__init__(self, [('ResourceRecordSet', Record)]) |
| 58 |
| 59 def __repr__(self): |
| 60 return '<ResourceRecordSets: %s>' % self.hosted_zone_id |
| 61 |
| 62 def add_change(self, action, name, type, ttl=600, |
| 63 alias_hosted_zone_id=None, alias_dns_name=None, identifier=None, |
| 64 weight=None, region=None): |
| 65 """ |
| 66 Add a change request to the set. |
| 67 |
| 68 :type action: str |
| 69 :param action: The action to perform ('CREATE'|'DELETE') |
| 70 |
| 71 :type name: str |
| 72 :param name: The name of the domain you want to perform the action on. |
| 73 |
| 74 :type type: str |
| 75 :param type: The DNS record type. Valid values are: |
| 76 |
| 77 * A |
| 78 * AAAA |
| 79 * CNAME |
| 80 * MX |
| 81 * NS |
| 82 * PTR |
| 83 * SOA |
| 84 * SPF |
| 85 * SRV |
| 86 * TXT |
| 87 |
| 88 :type ttl: int |
| 89 :param ttl: The resource record cache time to live (TTL), in seconds. |
| 90 |
| 91 :type alias_hosted_zone_id: str |
| 92 :param alias_dns_name: *Alias resource record sets only* The value |
| 93 of the hosted zone ID, CanonicalHostedZoneNameId, for |
| 94 the LoadBalancer. |
| 95 |
| 96 :type alias_dns_name: str |
| 97 :param alias_hosted_zone_id: *Alias resource record sets only* |
| 98 Information about the domain to which you are redirecting traffic. |
| 99 |
| 100 :type identifier: str |
| 101 :param identifier: *Weighted and latency-based resource record sets |
| 102 only* An identifier that differentiates among multiple resource |
| 103 record sets that have the same combination of DNS name and type. |
| 104 |
| 105 :type weight: int |
| 106 :param weight: *Weighted resource record sets only* Among resource |
| 107 record sets that have the same combination of DNS name and type, |
| 108 a value that determines what portion of traffic for the current |
| 109 resource record set is routed to the associated location |
| 110 |
| 111 :type region: str |
| 112 :param region: *Latency-based resource record sets only* Among resource |
| 113 record sets that have the same combination of DNS name and type, |
| 114 a value that determines which region this should be associated with |
| 115 for the latency-based routing |
| 116 """ |
| 117 change = Record(name, type, ttl, |
| 118 alias_hosted_zone_id=alias_hosted_zone_id, |
| 119 alias_dns_name=alias_dns_name, identifier=identifier, |
| 120 weight=weight, region=region) |
| 121 self.changes.append([action, change]) |
| 122 return change |
| 123 |
| 124 def to_xml(self): |
| 125 """Convert this ResourceRecordSet into XML |
| 126 to be saved via the ChangeResourceRecordSetsRequest""" |
| 127 changesXML = "" |
| 128 for change in self.changes: |
| 129 changeParams = {"action": change[0], "record": change[1].to_xml()} |
| 130 changesXML += self.ChangeXML % changeParams |
| 131 params = {"comment": self.comment, "changes": changesXML} |
| 132 return self.ChangeResourceRecordSetsBody % params |
| 133 |
| 134 def commit(self): |
| 135 """Commit this change""" |
| 136 if not self.connection: |
| 137 import boto |
| 138 self.connection = boto.connect_route53() |
| 139 return self.connection.change_rrsets(self.hosted_zone_id, self.to_xml()) |
| 140 |
| 141 def endElement(self, name, value, connection): |
| 142 """Overwritten to also add the NextRecordName and |
| 143 NextRecordType to the base object""" |
| 144 if name == 'NextRecordName': |
| 145 self.next_record_name = value |
| 146 elif name == 'NextRecordType': |
| 147 self.next_record_type = value |
| 148 else: |
| 149 return ResultSet.endElement(self, name, value, connection) |
| 150 |
| 151 def __iter__(self): |
| 152 """Override the next function to support paging""" |
| 153 results = ResultSet.__iter__(self) |
| 154 while results: |
| 155 for obj in results: |
| 156 yield obj |
| 157 if self.is_truncated: |
| 158 self.is_truncated = False |
| 159 results = self.connection.get_all_rrsets(self.hosted_zone_id, na
me=self.next_record_name, type=self.next_record_type) |
| 160 else: |
| 161 results = None |
| 162 |
| 163 |
| 164 |
| 165 class Record(object): |
| 166 """An individual ResourceRecordSet""" |
| 167 |
| 168 XMLBody = """<ResourceRecordSet> |
| 169 <Name>%(name)s</Name> |
| 170 <Type>%(type)s</Type> |
| 171 %(weight)s |
| 172 %(body)s |
| 173 </ResourceRecordSet>""" |
| 174 |
| 175 WRRBody = """ |
| 176 <SetIdentifier>%(identifier)s</SetIdentifier> |
| 177 <Weight>%(weight)s</Weight> |
| 178 """ |
| 179 |
| 180 RRRBody = """ |
| 181 <SetIdentifier>%(identifier)s</SetIdentifier> |
| 182 <Region>%(region)s</Region> |
| 183 """ |
| 184 |
| 185 ResourceRecordsBody = """ |
| 186 <TTL>%(ttl)s</TTL> |
| 187 <ResourceRecords> |
| 188 %(records)s |
| 189 </ResourceRecords>""" |
| 190 |
| 191 ResourceRecordBody = """<ResourceRecord> |
| 192 <Value>%s</Value> |
| 193 </ResourceRecord>""" |
| 194 |
| 195 AliasBody = """<AliasTarget> |
| 196 <HostedZoneId>%s</HostedZoneId> |
| 197 <DNSName>%s</DNSName> |
| 198 </AliasTarget>""" |
| 199 |
| 200 |
| 201 |
| 202 def __init__(self, name=None, type=None, ttl=600, resource_records=None, |
| 203 alias_hosted_zone_id=None, alias_dns_name=None, identifier=None, |
| 204 weight=None, region=None): |
| 205 self.name = name |
| 206 self.type = type |
| 207 self.ttl = ttl |
| 208 if resource_records == None: |
| 209 resource_records = [] |
| 210 self.resource_records = resource_records |
| 211 self.alias_hosted_zone_id = alias_hosted_zone_id |
| 212 self.alias_dns_name = alias_dns_name |
| 213 self.identifier = identifier |
| 214 self.weight = weight |
| 215 self.region = region |
| 216 |
| 217 def add_value(self, value): |
| 218 """Add a resource record value""" |
| 219 self.resource_records.append(value) |
| 220 |
| 221 def set_alias(self, alias_hosted_zone_id, alias_dns_name): |
| 222 """Make this an alias resource record set""" |
| 223 self.alias_hosted_zone_id = alias_hosted_zone_id |
| 224 self.alias_dns_name = alias_dns_name |
| 225 |
| 226 def to_xml(self): |
| 227 """Spit this resource record set out as XML""" |
| 228 if self.alias_hosted_zone_id != None and self.alias_dns_name != None: |
| 229 # Use alias |
| 230 body = self.AliasBody % (self.alias_hosted_zone_id, self.alias_dns_n
ame) |
| 231 else: |
| 232 # Use resource record(s) |
| 233 records = "" |
| 234 for r in self.resource_records: |
| 235 records += self.ResourceRecordBody % r |
| 236 body = self.ResourceRecordsBody % { |
| 237 "ttl": self.ttl, |
| 238 "records": records, |
| 239 } |
| 240 weight = "" |
| 241 if self.identifier != None and self.weight != None: |
| 242 weight = self.WRRBody % {"identifier": self.identifier, "weight": |
| 243 self.weight} |
| 244 elif self.identifier != None and self.region != None: |
| 245 weight = self.RRRBody % {"identifier": self.identifier, "region": |
| 246 self.region} |
| 247 |
| 248 params = { |
| 249 "name": self.name, |
| 250 "type": self.type, |
| 251 "weight": weight, |
| 252 "body": body, |
| 253 } |
| 254 return self.XMLBody % params |
| 255 |
| 256 def to_print(self): |
| 257 rr = "" |
| 258 if self.alias_hosted_zone_id != None and self.alias_dns_name != None: |
| 259 # Show alias |
| 260 rr = 'ALIAS ' + self.alias_hosted_zone_id + ' ' + self.alias_dns_nam
e |
| 261 else: |
| 262 # Show resource record(s) |
| 263 rr = ",".join(self.resource_records) |
| 264 |
| 265 if self.identifier != None and self.weight != None: |
| 266 rr += ' (WRR id=%s, w=%s)' % (self.identifier, self.weight) |
| 267 elif self.identifier != None and self.region != None: |
| 268 rr += ' (LBR id=%s, region=%s)' % (self.identifier, self.region) |
| 269 |
| 270 return rr |
| 271 |
| 272 def endElement(self, name, value, connection): |
| 273 if name == 'Name': |
| 274 self.name = value |
| 275 elif name == 'Type': |
| 276 self.type = value |
| 277 elif name == 'TTL': |
| 278 self.ttl = value |
| 279 elif name == 'Value': |
| 280 self.resource_records.append(value) |
| 281 elif name == 'HostedZoneId': |
| 282 self.alias_hosted_zone_id = value |
| 283 elif name == 'DNSName': |
| 284 self.alias_dns_name = value |
| 285 elif name == 'SetIdentifier': |
| 286 self.identifier = value |
| 287 elif name == 'Weight': |
| 288 self.weight = value |
| 289 elif name == 'Region': |
| 290 self.region = value |
| 291 |
| 292 def startElement(self, name, attrs, connection): |
| 293 return None |
| OLD | NEW |