comparison env/lib/python3.7/site-packages/boto/route53/connection.py @ 5:9b1c78e6ba9c draft default tip

"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author shellac
date Mon, 01 Jun 2020 08:59:25 -0400
parents 79f47841a781
children
comparison
equal deleted inserted replaced
4:79f47841a781 5:9b1c78e6ba9c
1 # Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
2 # Copyright (c) 2010, Eucalyptus Systems, Inc.
3 # Copyright (c) 2011 Blue Pines Technologies LLC, Brad Carleton
4 # www.bluepines.org
5 # Copyright (c) 2012 42 Lines Inc., Jim Browne
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a
8 # copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish, dis-
11 # tribute, sublicense, and/or sell copies of the Software, and to permit
12 # persons to whom the Software is furnished to do so, subject to the fol-
13 # lowing conditions:
14 #
15 # The above copyright notice and this permission notice shall be included
16 # in all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
20 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 # IN THE SOFTWARE.
25 #
26
27 from boto.route53 import exception
28 import random
29 import uuid
30 import xml.sax
31
32 import boto
33 from boto.connection import AWSAuthConnection
34 from boto import handler
35 import boto.jsonresponse
36 from boto.route53.record import ResourceRecordSets
37 from boto.route53.zone import Zone
38 from boto.compat import six, urllib
39
40
41 HZXML = """<?xml version="1.0" encoding="UTF-8"?>
42 <CreateHostedZoneRequest xmlns="%(xmlns)s">
43 <Name>%(name)s</Name>
44 <CallerReference>%(caller_ref)s</CallerReference>
45 <HostedZoneConfig>
46 <Comment>%(comment)s</Comment>
47 </HostedZoneConfig>
48 </CreateHostedZoneRequest>"""
49
50 HZPXML = """<?xml version="1.0" encoding="UTF-8"?>
51 <CreateHostedZoneRequest xmlns="%(xmlns)s">
52 <Name>%(name)s</Name>
53 <VPC>
54 <VPCId>%(vpc_id)s</VPCId>
55 <VPCRegion>%(vpc_region)s</VPCRegion>
56 </VPC>
57 <CallerReference>%(caller_ref)s</CallerReference>
58 <HostedZoneConfig>
59 <Comment>%(comment)s</Comment>
60 </HostedZoneConfig>
61 </CreateHostedZoneRequest>"""
62
63 # boto.set_stream_logger('dns')
64
65
66 class Route53Connection(AWSAuthConnection):
67 DefaultHost = 'route53.amazonaws.com'
68 """The default Route53 API endpoint to connect to."""
69
70 Version = '2013-04-01'
71 """Route53 API version."""
72
73 XMLNameSpace = 'https://route53.amazonaws.com/doc/2013-04-01/'
74 """XML schema for this Route53 API version."""
75
76 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
77 port=None, proxy=None, proxy_port=None,
78 host=DefaultHost, debug=0, security_token=None,
79 validate_certs=True, https_connection_factory=None,
80 profile_name=None):
81 super(Route53Connection, self).__init__(
82 host,
83 aws_access_key_id, aws_secret_access_key,
84 True, port, proxy, proxy_port, debug=debug,
85 security_token=security_token,
86 validate_certs=validate_certs,
87 https_connection_factory=https_connection_factory,
88 profile_name=profile_name)
89
90 def _required_auth_capability(self):
91 return ['route53']
92
93 def make_request(self, action, path, headers=None, data='', params=None):
94 if params:
95 pairs = []
96 for key, val in six.iteritems(params):
97 if val is None:
98 continue
99 pairs.append(key + '=' + urllib.parse.quote(str(val)))
100 path += '?' + '&'.join(pairs)
101 return super(Route53Connection, self).make_request(
102 action, path, headers, data,
103 retry_handler=self._retry_handler)
104
105 # Hosted Zones
106
107 def get_all_hosted_zones(self, start_marker=None, zone_list=None):
108 """
109 Returns a Python data structure with information about all
110 Hosted Zones defined for the AWS account.
111
112 :param int start_marker: start marker to pass when fetching additional
113 results after a truncated list
114 :param list zone_list: a HostedZones list to prepend to results
115 """
116 params = {}
117 if start_marker:
118 params = {'marker': start_marker}
119 response = self.make_request('GET', '/%s/hostedzone' % self.Version,
120 params=params)
121 body = response.read()
122 boto.log.debug(body)
123 if response.status >= 300:
124 raise exception.DNSServerError(response.status,
125 response.reason,
126 body)
127 e = boto.jsonresponse.Element(list_marker='HostedZones',
128 item_marker=('HostedZone',))
129 h = boto.jsonresponse.XmlHandler(e, None)
130 h.parse(body)
131 if zone_list:
132 e['ListHostedZonesResponse']['HostedZones'].extend(zone_list)
133 while 'NextMarker' in e['ListHostedZonesResponse']:
134 next_marker = e['ListHostedZonesResponse']['NextMarker']
135 zone_list = e['ListHostedZonesResponse']['HostedZones']
136 e = self.get_all_hosted_zones(next_marker, zone_list)
137 return e
138
139 def get_hosted_zone(self, hosted_zone_id):
140 """
141 Get detailed information about a particular Hosted Zone.
142
143 :type hosted_zone_id: str
144 :param hosted_zone_id: The unique identifier for the Hosted Zone
145
146 """
147 uri = '/%s/hostedzone/%s' % (self.Version, hosted_zone_id)
148 response = self.make_request('GET', uri)
149 body = response.read()
150 boto.log.debug(body)
151 if response.status >= 300:
152 raise exception.DNSServerError(response.status,
153 response.reason,
154 body)
155 e = boto.jsonresponse.Element(list_marker='NameServers',
156 item_marker=('NameServer',))
157 h = boto.jsonresponse.XmlHandler(e, None)
158 h.parse(body)
159 return e
160
161 def get_hosted_zone_by_name(self, hosted_zone_name):
162 """
163 Get detailed information about a particular Hosted Zone.
164
165 :type hosted_zone_name: str
166 :param hosted_zone_name: The fully qualified domain name for the Hosted
167 Zone
168
169 """
170 if hosted_zone_name[-1] != '.':
171 hosted_zone_name += '.'
172 all_hosted_zones = self.get_all_hosted_zones()
173 for zone in all_hosted_zones['ListHostedZonesResponse']['HostedZones']:
174 # check that they gave us the FQDN for their zone
175 if zone['Name'] == hosted_zone_name:
176 return self.get_hosted_zone(zone['Id'].split('/')[-1])
177
178 def create_hosted_zone(self, domain_name, caller_ref=None, comment='',
179 private_zone=False, vpc_id=None, vpc_region=None):
180 """
181 Create a new Hosted Zone. Returns a Python data structure with
182 information about the newly created Hosted Zone.
183
184 :type domain_name: str
185 :param domain_name: The name of the domain. This should be a
186 fully-specified domain, and should end with a final period
187 as the last label indication. If you omit the final period,
188 Amazon Route 53 assumes the domain is relative to the root.
189 This is the name you have registered with your DNS registrar.
190 It is also the name you will delegate from your registrar to
191 the Amazon Route 53 delegation servers returned in
192 response to this request.A list of strings with the image
193 IDs wanted.
194
195 :type caller_ref: str
196 :param caller_ref: A unique string that identifies the request
197 and that allows failed CreateHostedZone requests to be retried
198 without the risk of executing the operation twice. If you don't
199 provide a value for this, boto will generate a Type 4 UUID and
200 use that.
201
202 :type comment: str
203 :param comment: Any comments you want to include about the hosted
204 zone.
205
206 :type private_zone: bool
207 :param private_zone: Set True if creating a private hosted zone.
208
209 :type vpc_id: str
210 :param vpc_id: When creating a private hosted zone, the VPC Id to
211 associate to is required.
212
213 :type vpc_region: str
214 :param vpc_region: When creating a private hosted zone, the region
215 of the associated VPC is required.
216
217 """
218 if caller_ref is None:
219 caller_ref = str(uuid.uuid4())
220 if private_zone:
221 params = {'name': domain_name,
222 'caller_ref': caller_ref,
223 'comment': comment,
224 'vpc_id': vpc_id,
225 'vpc_region': vpc_region,
226 'xmlns': self.XMLNameSpace}
227 xml_body = HZPXML % params
228 else:
229 params = {'name': domain_name,
230 'caller_ref': caller_ref,
231 'comment': comment,
232 'xmlns': self.XMLNameSpace}
233 xml_body = HZXML % params
234 uri = '/%s/hostedzone' % self.Version
235 response = self.make_request('POST', uri,
236 {'Content-Type': 'text/xml'}, xml_body)
237 body = response.read()
238 boto.log.debug(body)
239 if response.status == 201:
240 e = boto.jsonresponse.Element(list_marker='NameServers',
241 item_marker=('NameServer',))
242 h = boto.jsonresponse.XmlHandler(e, None)
243 h.parse(body)
244 return e
245 else:
246 raise exception.DNSServerError(response.status,
247 response.reason,
248 body)
249
250 def delete_hosted_zone(self, hosted_zone_id):
251 """
252 Delete the hosted zone specified by the given id.
253
254 :type hosted_zone_id: str
255 :param hosted_zone_id: The hosted zone's id
256
257 """
258 uri = '/%s/hostedzone/%s' % (self.Version, hosted_zone_id)
259 response = self.make_request('DELETE', uri)
260 body = response.read()
261 boto.log.debug(body)
262 if response.status not in (200, 204):
263 raise exception.DNSServerError(response.status,
264 response.reason,
265 body)
266 e = boto.jsonresponse.Element()
267 h = boto.jsonresponse.XmlHandler(e, None)
268 h.parse(body)
269 return e
270
271 # Health checks
272
273 POSTHCXMLBody = """<CreateHealthCheckRequest xmlns="%(xmlns)s">
274 <CallerReference>%(caller_ref)s</CallerReference>
275 %(health_check)s
276 </CreateHealthCheckRequest>"""
277
278 def create_health_check(self, health_check, caller_ref=None):
279 """
280 Create a new Health Check
281
282 :type health_check: HealthCheck
283 :param health_check: HealthCheck object
284
285 :type caller_ref: str
286 :param caller_ref: A unique string that identifies the request
287 and that allows failed CreateHealthCheckRequest requests to be retried
288 without the risk of executing the operation twice. If you don't
289 provide a value for this, boto will generate a Type 4 UUID and
290 use that.
291
292 """
293 if caller_ref is None:
294 caller_ref = str(uuid.uuid4())
295 uri = '/%s/healthcheck' % self.Version
296 params = {'xmlns': self.XMLNameSpace,
297 'caller_ref': caller_ref,
298 'health_check': health_check.to_xml()
299 }
300 xml_body = self.POSTHCXMLBody % params
301 response = self.make_request('POST', uri, {'Content-Type': 'text/xml'}, xml_body)
302 body = response.read()
303 boto.log.debug(body)
304 if response.status == 201:
305 e = boto.jsonresponse.Element()
306 h = boto.jsonresponse.XmlHandler(e, None)
307 h.parse(body)
308 return e
309 else:
310 raise exception.DNSServerError(response.status, response.reason, body)
311
312 def get_list_health_checks(self, maxitems=None, marker=None):
313 """
314 Return a list of health checks
315
316 :type maxitems: int
317 :param maxitems: Maximum number of items to return
318
319 :type marker: str
320 :param marker: marker to get next set of items to list
321
322 """
323
324 params = {}
325 if maxitems is not None:
326 params['maxitems'] = maxitems
327 if marker is not None:
328 params['marker'] = marker
329
330 uri = '/%s/healthcheck' % (self.Version, )
331 response = self.make_request('GET', uri, params=params)
332 body = response.read()
333 boto.log.debug(body)
334 if response.status >= 300:
335 raise exception.DNSServerError(response.status,
336 response.reason,
337 body)
338 e = boto.jsonresponse.Element(list_marker='HealthChecks',
339 item_marker=('HealthCheck',))
340 h = boto.jsonresponse.XmlHandler(e, None)
341 h.parse(body)
342 return e
343
344 def get_checker_ip_ranges(self):
345 """
346 Return a list of Route53 healthcheck IP ranges
347 """
348 uri = '/%s/checkeripranges' % self.Version
349 response = self.make_request('GET', uri)
350 body = response.read()
351 boto.log.debug(body)
352 if response.status >= 300:
353 raise exception.DNSServerError(response.status,
354 response.reason,
355 body)
356 e = boto.jsonresponse.Element(list_marker='CheckerIpRanges', item_marker=('member',))
357 h = boto.jsonresponse.XmlHandler(e, None)
358 h.parse(body)
359 return e
360
361 def delete_health_check(self, health_check_id):
362 """
363 Delete a health check
364
365 :type health_check_id: str
366 :param health_check_id: ID of the health check to delete
367
368 """
369 uri = '/%s/healthcheck/%s' % (self.Version, health_check_id)
370 response = self.make_request('DELETE', uri)
371 body = response.read()
372 boto.log.debug(body)
373 if response.status not in (200, 204):
374 raise exception.DNSServerError(response.status,
375 response.reason,
376 body)
377 e = boto.jsonresponse.Element()
378 h = boto.jsonresponse.XmlHandler(e, None)
379 h.parse(body)
380 return e
381
382 # Resource Record Sets
383
384 def get_all_rrsets(self, hosted_zone_id, type=None,
385 name=None, identifier=None, maxitems=None):
386 """
387 Retrieve the Resource Record Sets defined for this Hosted Zone.
388 Returns the raw XML data returned by the Route53 call.
389
390 :type hosted_zone_id: str
391 :param hosted_zone_id: The unique identifier for the Hosted Zone
392
393 :type type: str
394 :param type: The type of resource record set to begin the record
395 listing from. Valid choices are:
396
397 * A
398 * AAAA
399 * CNAME
400 * MX
401 * NS
402 * PTR
403 * SOA
404 * SPF
405 * SRV
406 * TXT
407
408 Valid values for weighted resource record sets:
409
410 * A
411 * AAAA
412 * CNAME
413 * TXT
414
415 Valid values for Zone Apex Aliases:
416
417 * A
418 * AAAA
419
420 :type name: str
421 :param name: The first name in the lexicographic ordering of domain
422 names to be retrieved
423
424 :type identifier: str
425 :param identifier: In a hosted zone that includes weighted resource
426 record sets (multiple resource record sets with the same DNS
427 name and type that are differentiated only by SetIdentifier),
428 if results were truncated for a given DNS name and type,
429 the value of SetIdentifier for the next resource record
430 set that has the current DNS name and type
431
432 :type maxitems: int
433 :param maxitems: The maximum number of records
434
435 """
436 params = {'type': type, 'name': name,
437 'identifier': identifier, 'maxitems': maxitems}
438 uri = '/%s/hostedzone/%s/rrset' % (self.Version, hosted_zone_id)
439 response = self.make_request('GET', uri, params=params)
440 body = response.read()
441 boto.log.debug(body)
442 if response.status >= 300:
443 raise exception.DNSServerError(response.status,
444 response.reason,
445 body)
446 rs = ResourceRecordSets(connection=self, hosted_zone_id=hosted_zone_id)
447 h = handler.XmlHandler(rs, self)
448 xml.sax.parseString(body, h)
449 return rs
450
451 def change_rrsets(self, hosted_zone_id, xml_body):
452 """
453 Create or change the authoritative DNS information for this
454 Hosted Zone.
455 Returns a Python data structure with information about the set of
456 changes, including the Change ID.
457
458 :type hosted_zone_id: str
459 :param hosted_zone_id: The unique identifier for the Hosted Zone
460
461 :type xml_body: str
462 :param xml_body: The list of changes to be made, defined in the
463 XML schema defined by the Route53 service.
464
465 """
466 uri = '/%s/hostedzone/%s/rrset' % (self.Version, hosted_zone_id)
467 response = self.make_request('POST', uri,
468 {'Content-Type': 'text/xml'},
469 xml_body)
470 body = response.read()
471 boto.log.debug(body)
472 if response.status >= 300:
473 raise exception.DNSServerError(response.status,
474 response.reason,
475 body)
476 e = boto.jsonresponse.Element()
477 h = boto.jsonresponse.XmlHandler(e, None)
478 h.parse(body)
479 return e
480
481 def get_change(self, change_id):
482 """
483 Get information about a proposed set of changes, as submitted
484 by the change_rrsets method.
485 Returns a Python data structure with status information about the
486 changes.
487
488 :type change_id: str
489 :param change_id: The unique identifier for the set of changes.
490 This ID is returned in the response to the change_rrsets method.
491
492 """
493 uri = '/%s/change/%s' % (self.Version, change_id)
494 response = self.make_request('GET', uri)
495 body = response.read()
496 boto.log.debug(body)
497 if response.status >= 300:
498 raise exception.DNSServerError(response.status,
499 response.reason,
500 body)
501 e = boto.jsonresponse.Element()
502 h = boto.jsonresponse.XmlHandler(e, None)
503 h.parse(body)
504 return e
505
506 def create_zone(self, name, private_zone=False,
507 vpc_id=None, vpc_region=None):
508 """
509 Create a new Hosted Zone. Returns a Zone object for the newly
510 created Hosted Zone.
511
512 :type name: str
513 :param name: The name of the domain. This should be a
514 fully-specified domain, and should end with a final period
515 as the last label indication. If you omit the final period,
516 Amazon Route 53 assumes the domain is relative to the root.
517 This is the name you have registered with your DNS registrar.
518 It is also the name you will delegate from your registrar to
519 the Amazon Route 53 delegation servers returned in
520 response to this request.
521
522 :type private_zone: bool
523 :param private_zone: Set True if creating a private hosted zone.
524
525 :type vpc_id: str
526 :param vpc_id: When creating a private hosted zone, the VPC Id to
527 associate to is required.
528
529 :type vpc_region: str
530 :param vpc_region: When creating a private hosted zone, the region
531 of the associated VPC is required.
532 """
533 zone = self.create_hosted_zone(name, private_zone=private_zone,
534 vpc_id=vpc_id, vpc_region=vpc_region)
535 return Zone(self, zone['CreateHostedZoneResponse']['HostedZone'])
536
537 def get_zone(self, name):
538 """
539 Returns a Zone object for the specified Hosted Zone.
540
541 :param name: The name of the domain. This should be a
542 fully-specified domain, and should end with a final period
543 as the last label indication.
544 """
545 name = self._make_qualified(name)
546 for zone in self.get_zones():
547 if name == zone.name:
548 return zone
549
550 def get_zones(self):
551 """
552 Returns a list of Zone objects, one for each of the Hosted
553 Zones defined for the AWS account.
554
555 :rtype: list
556 :returns: A list of Zone objects.
557
558 """
559 zones = self.get_all_hosted_zones()
560 return [Zone(self, zone) for zone in
561 zones['ListHostedZonesResponse']['HostedZones']]
562
563 def _make_qualified(self, value):
564 """
565 Ensure passed domain names end in a period (.) character.
566 This will usually make a domain fully qualified.
567 """
568 if type(value) in [list, tuple, set]:
569 new_list = []
570 for record in value:
571 if record and not record[-1] == '.':
572 new_list.append("%s." % record)
573 else:
574 new_list.append(record)
575 return new_list
576 else:
577 value = value.strip()
578 if value and not value[-1] == '.':
579 value = "%s." % value
580 return value
581
582 def _retry_handler(self, response, i, next_sleep):
583 status = None
584 boto.log.debug("Saw HTTP status: %s" % response.status)
585
586 if response.status == 400:
587 body = response.read()
588
589 # We need to parse the error first
590 err = exception.DNSServerError(
591 response.status,
592 response.reason,
593 body)
594 if err.error_code:
595 # This is a case where we need to ignore a 400 error, as
596 # Route53 returns this. See
597 # http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html
598 if not err.error_code in (
599 'PriorRequestNotComplete',
600 'Throttling',
601 'ServiceUnavailable',
602 'RequestExpired'):
603 return status
604 msg = "%s, retry attempt %s" % (
605 err.error_code,
606 i
607 )
608 next_sleep = min(random.random() * (2 ** i),
609 boto.config.get('Boto', 'max_retry_delay', 60))
610 i += 1
611 status = (msg, i, next_sleep)
612
613 return status