mirror of
https://github.com/ansible/awx.git
synced 2026-03-27 13:55:04 -02:30
1439 lines
50 KiB
Python
1439 lines
50 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2012 Rackspace
|
|
|
|
# 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.
|
|
|
|
from functools import wraps
|
|
import json
|
|
import re
|
|
import time
|
|
|
|
import six
|
|
|
|
import pyrax
|
|
from pyrax.client import BaseClient
|
|
from pyrax.cloudloadbalancers import CloudLoadBalancer
|
|
import pyrax.exceptions as exc
|
|
from pyrax.manager import BaseManager
|
|
from pyrax.resource import BaseResource
|
|
import pyrax.utils as utils
|
|
|
|
# How long (in seconds) to wait for a response from async operations
|
|
DEFAULT_TIMEOUT = 5
|
|
# How long (in seconds) to wait in between checks for async completion
|
|
DEFAULT_DELAY = 0.5
|
|
# How many times to retry a GET before raising an error
|
|
DEFAULT_RETRY = 3
|
|
|
|
|
|
def assure_domain(fnc):
|
|
@wraps(fnc)
|
|
def _wrapped(self, domain, *args, **kwargs):
|
|
if not isinstance(domain, CloudDNSDomain):
|
|
# Must be the ID or name. Try ID first:
|
|
try:
|
|
domain = self._manager.get(domain)
|
|
except exc.NotFound:
|
|
domain = self._manager.find(name=domain)
|
|
return fnc(self, domain, *args, **kwargs)
|
|
return _wrapped
|
|
|
|
|
|
|
|
class CloudDNSRecord(BaseResource):
|
|
"""
|
|
This class represents a domain record.
|
|
"""
|
|
GET_DETAILS = False
|
|
# Initialize the supported attributes.
|
|
type = None
|
|
name = None
|
|
data = None
|
|
priority = None
|
|
ttl = None
|
|
comment = None
|
|
|
|
|
|
def update(self, data=None, priority=None, ttl=None, comment=None):
|
|
"""
|
|
Modifies this record.
|
|
"""
|
|
return self.manager.update_record(self.domain_id, self, data=data,
|
|
priority=priority, ttl=ttl, comment=comment)
|
|
|
|
|
|
def get(self):
|
|
"""
|
|
Gets the full information for an existing record for this domain.
|
|
"""
|
|
return self.manager.get_record(self.domain_id, self)
|
|
|
|
|
|
def delete(self):
|
|
"""
|
|
Deletes an existing record for this domain.
|
|
"""
|
|
return self.manager.delete_record(self.domain_id, self)
|
|
|
|
|
|
|
|
class CloudDNSDomain(BaseResource):
|
|
"""
|
|
This class represents a DNS domain.
|
|
"""
|
|
def delete(self, delete_subdomains=False):
|
|
"""
|
|
Deletes this domain and all of its resource records. If this domain has
|
|
subdomains, each subdomain will now become a root domain. If you wish to
|
|
also delete any subdomains, pass True to 'delete_subdomains'.
|
|
"""
|
|
self.manager.delete(self, delete_subdomains=delete_subdomains)
|
|
|
|
|
|
def changes_since(self, date_or_datetime):
|
|
"""
|
|
Gets the changes for this domain since the specified date/datetime.
|
|
The date can be one of:
|
|
- a Python datetime object
|
|
- a Python date object
|
|
- a string in the format 'YYYY-MM-YY HH:MM:SS'
|
|
- a string in the format 'YYYY-MM-YY'
|
|
|
|
It returns a list of dicts, whose keys depend on the specific change
|
|
that was made. A simple example of such a change dict:
|
|
|
|
{u'accountId': 000000,
|
|
u'action': u'update',
|
|
u'changeDetails': [{u'field': u'serial_number',
|
|
u'newValue': u'1354038941',
|
|
u'originalValue': u'1354038940'},
|
|
{u'field': u'updated_at',
|
|
u'newValue': u'Tue Nov 27 17:55:41 UTC 2012',
|
|
u'originalValue': u'Tue Nov 27 17:55:40 UTC 2012'}],
|
|
u'domain': u'example.com',
|
|
u'targetId': 00000000,
|
|
u'targetType': u'Domain'}
|
|
"""
|
|
return self.manager.changes_since(self, date_or_datetime)
|
|
|
|
|
|
def export(self):
|
|
"""
|
|
Provides the BIND (Berkeley Internet Name Domain) 9 formatted contents
|
|
of the requested domain. This call is for a single domain only, and as such,
|
|
does not provide subdomain information.
|
|
|
|
Sample export:
|
|
{u'accountId': 000000,
|
|
u'contentType': u'BIND_9',
|
|
u'contents': u'example.com.\t3600\tIN\tSOA\tns.rackspace.com. '
|
|
'foo@example.com. 1354202974 21600 3600 1814400 500'
|
|
'example.com.\t3600\tIN\tNS\tdns1.stabletransit.com.'
|
|
'example.com.\t3600\tIN\tNS\tdns2.stabletransit.com.',
|
|
u'id': 1111111}
|
|
"""
|
|
return self.manager.export_domain(self)
|
|
|
|
|
|
def update(self, emailAddress=None, ttl=None, comment=None):
|
|
"""
|
|
Provides a way to modify the following attributes of a domain
|
|
entry:
|
|
- email address
|
|
- ttl setting
|
|
- comment
|
|
"""
|
|
return self.manager.update_domain(self, emailAddress=emailAddress,
|
|
ttl=ttl, comment=comment)
|
|
|
|
|
|
def list_subdomains(self, limit=None, offset=None):
|
|
"""
|
|
Returns a list of all subdomains for this domain.
|
|
"""
|
|
return self.manager.list_subdomains(self, limit=limit, offset=offset)
|
|
|
|
|
|
def list_records(self, limit=None, offset=None):
|
|
"""
|
|
Returns a list of all records configured for this domain.
|
|
"""
|
|
return self.manager.list_records(self, limit=limit, offset=offset)
|
|
|
|
|
|
def search_records(self, record_type, name=None, data=None):
|
|
"""
|
|
Returns a list of all records configured for this domain that match
|
|
the supplied search criteria.
|
|
"""
|
|
return self.manager.search_records(self, record_type=record_type,
|
|
name=name, data=data)
|
|
|
|
|
|
def find_record(self, record_type, name=None, data=None):
|
|
"""
|
|
Returns a single record for this domain that matches the supplied
|
|
search criteria.
|
|
|
|
If no record matches, a DomainRecordNotFound exception will be raised.
|
|
If more than one matches, a DomainRecordNotUnique exception will
|
|
be raised.
|
|
"""
|
|
matches = self.manager.search_records(self, record_type=record_type,
|
|
name=name, data=data)
|
|
if not matches:
|
|
raise exc.DomainRecordNotFound
|
|
elif len(matches) > 1:
|
|
raise exc.DomainRecordNotUnique
|
|
return matches[0]
|
|
|
|
|
|
def add_records(self, records):
|
|
"""
|
|
Adds the records to this domain. Each record should be a dict with the
|
|
following keys:
|
|
- type (required)
|
|
- name (required)
|
|
- data (required)
|
|
- ttl (optional)
|
|
- comment (optional)
|
|
- priority (required for MX and SRV records; forbidden otherwise)
|
|
"""
|
|
return self.manager.add_records(self, records)
|
|
|
|
# Create an alias, so that adding a single record is more intuitive
|
|
add_record = add_records
|
|
|
|
|
|
def get_record(self, record):
|
|
"""
|
|
Gets the full information for an existing record for this domain.
|
|
"""
|
|
return self.manager.get_record(self, record)
|
|
|
|
|
|
def update_record(self, record, data=None, priority=None,
|
|
ttl=None, comment=None):
|
|
"""
|
|
Modifies an existing record for this domain.
|
|
"""
|
|
return self.manager.update_record(self, record, data=data,
|
|
priority=priority, ttl=ttl, comment=comment)
|
|
|
|
|
|
def delete_record(self, record):
|
|
"""
|
|
Deletes an existing record for this domain.
|
|
"""
|
|
return self.manager.delete_record(self, record)
|
|
|
|
|
|
class CloudDNSPTRRecord(object):
|
|
"""
|
|
This represents a Cloud DNS PTR record (reverse DNS).
|
|
"""
|
|
def __init__(self, data=None, device=None):
|
|
self.type = self.id = self.data = self.name = None
|
|
self.ttl = self.comment = None
|
|
if data:
|
|
for key, val in data.items():
|
|
setattr(self, key, val)
|
|
self.device = device
|
|
|
|
|
|
def delete(self):
|
|
"""
|
|
Deletes this PTR record from its device.
|
|
"""
|
|
return pyrax.cloud_dns.delete_ptr_records(self.device, self.data)
|
|
|
|
|
|
def __repr__(self):
|
|
reprkeys = ("id", "data", "name", "ttl")
|
|
info = ", ".join("%s=%s" % (key, getattr(self, key)) for key in reprkeys)
|
|
return "<%s %s>" % (self.__class__.__name__, info)
|
|
|
|
|
|
|
|
class CloudDNSManager(BaseManager):
|
|
def __init__(self, api, resource_class=None, response_key=None,
|
|
plural_response_key=None, uri_base=None):
|
|
super(CloudDNSManager, self).__init__(api, resource_class=resource_class,
|
|
response_key=response_key, plural_response_key=plural_response_key,
|
|
uri_base=uri_base)
|
|
self._paging = {"domain": {}, "subdomain": {}, "record": {}}
|
|
self._reset_paging(service="all")
|
|
self._timeout = DEFAULT_TIMEOUT
|
|
self._delay = DEFAULT_DELAY
|
|
|
|
|
|
def _create_body(self, name, emailAddress, ttl=3600, comment=None,
|
|
subdomains=None, records=None):
|
|
"""
|
|
Creates the appropriate dict for creating a new domain.
|
|
"""
|
|
if subdomains is None:
|
|
subdomains = []
|
|
if records is None:
|
|
records = []
|
|
body = {"domains": [{
|
|
"name": name,
|
|
"emailAddress": emailAddress,
|
|
"ttl": ttl,
|
|
"comment": comment,
|
|
"subdomains": {
|
|
"domains": subdomains
|
|
},
|
|
"recordsList": {
|
|
"records": records
|
|
},
|
|
}]}
|
|
return body
|
|
|
|
|
|
def _set_timeout(self, timeout):
|
|
"""
|
|
Changes the duration for which the program will wait for a response from
|
|
the DNS system. Setting the timeout to zero will make that program wait
|
|
an indefinite amount of time.
|
|
"""
|
|
self._timeout = timeout
|
|
|
|
|
|
def _set_delay(self, delay):
|
|
"""
|
|
Changes the interval that the program will pause in between attempts to
|
|
see if a request has completed.
|
|
"""
|
|
self._delay = delay
|
|
|
|
|
|
def _reset_paging(self, service, body=None):
|
|
"""
|
|
Resets the internal attributes when there is no current paging request.
|
|
"""
|
|
if service == "all":
|
|
for svc in self._paging.keys():
|
|
svc_dct = self._paging[svc]
|
|
svc_dct["next_uri"] = svc_dct["prev_uri"] = None
|
|
svc_dct["total_entries"] = None
|
|
return
|
|
svc_dct = self._paging[service]
|
|
svc_dct["next_uri"] = svc_dct["prev_uri"] = None
|
|
svc_dct["total_entries"] = None
|
|
if not body:
|
|
return
|
|
svc_dct["total_entries"] = body.get("totalEntries")
|
|
links = body.get("links")
|
|
uri_base = self.uri_base
|
|
if links:
|
|
for link in links:
|
|
href = link["href"]
|
|
pos = href.index(uri_base)
|
|
page_uri = href[pos - 1:]
|
|
if link["rel"] == "next":
|
|
svc_dct["next_uri"] = page_uri
|
|
elif link["rel"] == "previous":
|
|
svc_dct["prev_uri"] = page_uri
|
|
|
|
|
|
def _get_pagination_qs(self, limit, offset):
|
|
pagination_items = []
|
|
if limit is not None:
|
|
pagination_items.append("limit=%s" % limit)
|
|
if offset is not None:
|
|
pagination_items.append("offset=%s" % offset)
|
|
qs = "&".join(pagination_items)
|
|
qs = "?%s" % qs if qs else ""
|
|
return qs
|
|
|
|
|
|
def list(self, limit=None, offset=None):
|
|
"""Gets a list of all domains, or optionally a page of domains."""
|
|
uri = "/%s%s" % (self.uri_base, self._get_pagination_qs(limit, offset))
|
|
return self._list(uri)
|
|
|
|
|
|
def _list(self, uri, obj_class=None, list_all=False):
|
|
"""
|
|
Handles the communication with the API when getting
|
|
a full listing of the resources managed by this class.
|
|
"""
|
|
resp, resp_body = self._retry_get(uri)
|
|
if obj_class is None:
|
|
obj_class = self.resource_class
|
|
|
|
data = resp_body[self.plural_response_key]
|
|
ret = [obj_class(self, res, loaded=False)
|
|
for res in data if res]
|
|
self._reset_paging("domain", resp_body)
|
|
if list_all:
|
|
dom_paging = self._paging.get("domain", {})
|
|
while dom_paging.get("next_uri"):
|
|
next_uri = dom_paging.get("next_uri")
|
|
ret.extend(self._list(uri=next_uri, obj_class=obj_class,
|
|
list_all=False))
|
|
return ret
|
|
|
|
|
|
def list_previous_page(self):
|
|
"""
|
|
When paging through results, this will return the previous page, using
|
|
the same limit. If there are no more results, a NoMoreResults exception
|
|
will be raised.
|
|
"""
|
|
uri = self._paging.get("domain", {}).get("prev_uri")
|
|
if uri is None:
|
|
raise exc.NoMoreResults("There are no previous pages of domains "
|
|
"to list.")
|
|
return self._list(uri)
|
|
|
|
|
|
def list_next_page(self):
|
|
"""
|
|
When paging through results, this will return the next page, using the
|
|
same limit. If there are no more results, a NoMoreResults exception
|
|
will be raised.
|
|
"""
|
|
uri = self._paging.get("domain", {}).get("next_uri")
|
|
if uri is None:
|
|
raise exc.NoMoreResults("There are no more pages of domains to "
|
|
"list.")
|
|
return self._list(uri)
|
|
|
|
|
|
def _get(self, uri):
|
|
"""
|
|
Handles the communication with the API when getting
|
|
a specific resource managed by this class.
|
|
|
|
Because DNS returns a different format for the body,
|
|
the BaseManager method must be overridden here.
|
|
"""
|
|
uri = "%s?showRecords=false&showSubdomains=false" % uri
|
|
resp, body = self._retry_get(uri)
|
|
body["records"] = []
|
|
return self.resource_class(self, body, loaded=True)
|
|
|
|
def _retry_get(self, uri):
|
|
"""
|
|
Handles GET calls to the Cloud DNS API in order to retry on empty
|
|
body responses.
|
|
"""
|
|
for i in six.moves.range(DEFAULT_RETRY):
|
|
resp, body = self.api.method_get(uri)
|
|
if body:
|
|
return resp, body
|
|
# Tried too many times
|
|
raise exc.ServiceResponseFailure("The Cloud DNS service failed to "
|
|
"respond to the request.")
|
|
|
|
def _async_call(self, uri, body=None, method="GET", error_class=None,
|
|
has_response=True, *args, **kwargs):
|
|
"""
|
|
Handles asynchronous call/responses for the DNS API.
|
|
|
|
Returns the response headers and body if the call was successful.
|
|
If an error status is returned, and the 'error_class' parameter is
|
|
specified, that class of error will be raised with the details from
|
|
the response. If no error class is specified, the response headers
|
|
and body will be returned to the calling method, which will have
|
|
to handle the result.
|
|
"""
|
|
api_methods = {
|
|
"GET": self._retry_get,
|
|
"POST": self.api.method_post,
|
|
"PUT": self.api.method_put,
|
|
"DELETE": self.api.method_delete,
|
|
}
|
|
api_method = api_methods[method]
|
|
if body is None:
|
|
resp, resp_body = api_method(uri, *args, **kwargs)
|
|
else:
|
|
resp, resp_body = api_method(uri, body=body, *args, **kwargs)
|
|
callbackURL = resp_body["callbackUrl"].split("/status/")[-1]
|
|
massagedURL = "/status/%s?showDetails=true" % callbackURL
|
|
start = time.time()
|
|
timed_out = False
|
|
while (resp_body["status"] == "RUNNING") and not timed_out:
|
|
resp_body = None
|
|
while resp_body is None and not timed_out:
|
|
resp, resp_body = self._retry_get(massagedURL)
|
|
if self._timeout:
|
|
timed_out = ((time.time() - start) > self._timeout)
|
|
time.sleep(self._delay)
|
|
|
|
if timed_out:
|
|
raise exc.DNSCallTimedOut("The API call to '%s' did not complete "
|
|
"after %s seconds." % (uri, self._timeout))
|
|
if error_class and (resp_body["status"] == "ERROR"):
|
|
# This call will handle raising the error.
|
|
self._process_async_error(resp_body, error_class)
|
|
if has_response:
|
|
ret = resp, resp_body["response"]
|
|
else:
|
|
ret = resp, resp_body
|
|
try:
|
|
resp_body = json.loads(resp_body)
|
|
except Exception:
|
|
pass
|
|
return ret
|
|
|
|
|
|
def _process_async_error(self, resp_body, error_class):
|
|
"""
|
|
The DNS API does not return a consistent format for their error
|
|
messages. This abstracts out the differences in order to present
|
|
a single unified message in the exception to be raised.
|
|
"""
|
|
def _fmt_error(err):
|
|
# Remove the cumbersome Java-esque message
|
|
details = err.get("details", "").replace("\n", " ")
|
|
if not details:
|
|
details = err.get("message", "")
|
|
return "%s (%s)" % (details, err.get("code", ""))
|
|
|
|
error = resp_body.get("error", "")
|
|
if "failedItems" in error:
|
|
# Multi-error response
|
|
faults = error.get("failedItems", {}).get("faults", [])
|
|
msgs = [_fmt_error(fault) for fault in faults]
|
|
msg = "\n".join(msgs)
|
|
else:
|
|
msg = _fmt_error(error)
|
|
raise error_class(msg)
|
|
|
|
|
|
def _create(self, uri, body, records=None, subdomains=None,
|
|
return_none=False, return_raw=False, **kwargs):
|
|
"""
|
|
Handles the communication with the API when creating a new
|
|
resource managed by this class.
|
|
|
|
Since DNS works completely differently for create() than the other
|
|
APIs, this method overrides the default BaseManager behavior.
|
|
|
|
If 'records' are supplied, they should be a list of dicts. Each
|
|
record dict should have the following format:
|
|
|
|
{"name": "example.com",
|
|
"type": "A",
|
|
"data": "192.0.2.17",
|
|
"ttl": 86400}
|
|
|
|
If 'subdomains' are supplied, they should be a list of dicts. Each
|
|
subdomain dict should have the following format:
|
|
|
|
{"name": "sub1.example.com",
|
|
"comment": "1st sample subdomain",
|
|
"emailAddress": "sample@rackspace.com"}
|
|
"""
|
|
self.run_hooks("modify_body_for_create", body, **kwargs)
|
|
resp, resp_body = self._async_call(uri, body=body, method="POST",
|
|
error_class=exc.DomainCreationFailed)
|
|
response_body = resp_body[self.response_key][0]
|
|
return self.resource_class(self, response_body)
|
|
|
|
|
|
def delete(self, domain, delete_subdomains=False):
|
|
"""
|
|
Deletes the specified domain and all of its resource records. If the
|
|
domain has subdomains, each subdomain will now become a root domain. If
|
|
you wish to also delete any subdomains, pass True to 'delete_subdomains'.
|
|
"""
|
|
uri = "/%s/%s" % (self.uri_base, utils.get_id(domain))
|
|
if delete_subdomains:
|
|
uri = "%s?deleteSubdomains=true" % uri
|
|
resp, resp_body = self._async_call(uri, method="DELETE",
|
|
error_class=exc.DomainDeletionFailed, has_response=False)
|
|
|
|
|
|
def findall(self, **kwargs):
|
|
"""
|
|
Finds all items with attributes matching ``**kwargs``.
|
|
|
|
Normally this isn't very efficient, since the default action is to
|
|
load the entire list and then filter on the Python side, but the DNS
|
|
API provides a more efficient search option when filtering on name.
|
|
So if the filter is on name, use that; otherwise, use the default.
|
|
"""
|
|
if (len(kwargs) == 1) and ("name" in kwargs):
|
|
# Filtering on name; use the more efficient method.
|
|
nm = kwargs["name"].lower()
|
|
uri = "/%s?name=%s" % (self.uri_base, nm)
|
|
matches = self._list(uri, list_all=True)
|
|
return [match for match in matches
|
|
if match.name.lower() == nm]
|
|
else:
|
|
return super(CloudDNSManager, self).findall(**kwargs)
|
|
|
|
|
|
def changes_since(self, domain, date_or_datetime):
|
|
"""
|
|
Gets the changes for a domain since the specified date/datetime.
|
|
The date can be one of:
|
|
- a Python datetime object
|
|
- a Python date object
|
|
- a string in the format 'YYYY-MM-YY HH:MM:SS'
|
|
- a string in the format 'YYYY-MM-YY'
|
|
|
|
It returns a list of dicts, whose keys depend on the specific change
|
|
that was made. A simple example of such a change dict:
|
|
|
|
{u'accountId': 000000,
|
|
u'action': u'update',
|
|
u'changeDetails': [{u'field': u'serial_number',
|
|
u'newValue': u'1354038941',
|
|
u'originalValue': u'1354038940'},
|
|
{u'field': u'updated_at',
|
|
u'newValue': u'Tue Nov 27 17:55:41 UTC 2012',
|
|
u'originalValue': u'Tue Nov 27 17:55:40 UTC 2012'}],
|
|
u'domain': u'example.com',
|
|
u'targetId': 00000000,
|
|
u'targetType': u'Domain'}
|
|
"""
|
|
domain_id = utils.get_id(domain)
|
|
dt = utils.iso_time_string(date_or_datetime, show_tzinfo=True)
|
|
uri = "/domains/%s/changes?since=%s" % (domain_id, dt)
|
|
resp, body = self._retry_get(uri)
|
|
return body.get("changes", [])
|
|
|
|
|
|
def export_domain(self, domain):
|
|
"""
|
|
Provides the BIND (Berkeley Internet Name Domain) 9 formatted contents
|
|
of the requested domain. This call is for a single domain only, and as
|
|
such, does not provide subdomain information.
|
|
|
|
Sample export:
|
|
{u'accountId': 000000,
|
|
u'contentType': u'BIND_9',
|
|
u'contents': u'example.com.\t3600\tIN\tSOA\tns.rackspace.com. '
|
|
'foo@example.com. 1354202974 21600 3600 1814400 500'
|
|
'example.com.\t3600\tIN\tNS\tdns1.stabletransit.com.'
|
|
'example.com.\t3600\tIN\tNS\tdns2.stabletransit.com.',
|
|
u'id': 1111111}
|
|
"""
|
|
uri = "/domains/%s/export" % utils.get_id(domain)
|
|
resp, resp_body = self._async_call(uri, method="GET",
|
|
error_class=exc.NotFound)
|
|
return resp_body.get("contents", "")
|
|
|
|
|
|
def import_domain(self, domain_data):
|
|
"""
|
|
Takes a string in the BIND 9 format and creates a new domain. See the
|
|
'export_domain()' method for a description of the format.
|
|
"""
|
|
uri = "/domains/import"
|
|
body = {"domains": [{
|
|
"contentType": "BIND_9",
|
|
"contents": domain_data,
|
|
}]}
|
|
resp, resp_body = self._async_call(uri, method="POST", body=body,
|
|
error_class=exc.DomainCreationFailed)
|
|
return resp_body
|
|
|
|
|
|
def update_domain(self, domain, emailAddress=None, ttl=None, comment=None):
|
|
"""
|
|
Provides a way to modify the following attributes of a domain
|
|
record:
|
|
- email address
|
|
- ttl setting
|
|
- comment
|
|
"""
|
|
if not any((emailAddress, ttl, comment)):
|
|
raise exc.MissingDNSSettings(
|
|
"No settings provided to update_domain().")
|
|
uri = "/domains/%s" % utils.get_id(domain)
|
|
body = {"comment": comment,
|
|
"ttl": ttl,
|
|
"emailAddress": emailAddress,
|
|
}
|
|
none_keys = [key for key, val in body.items()
|
|
if val is None]
|
|
for none_key in none_keys:
|
|
body.pop(none_key)
|
|
resp, resp_body = self._async_call(uri, method="PUT", body=body,
|
|
error_class=exc.DomainUpdateFailed, has_response=False)
|
|
return resp_body
|
|
|
|
|
|
def list_subdomains(self, domain, limit=None, offset=None):
|
|
"""
|
|
Returns a list of all subdomains of the specified domain.
|
|
"""
|
|
# The commented-out uri is the official API, but it is
|
|
# horribly slow.
|
|
# uri = "/domains/%s/subdomains" % utils.get_id(domain)
|
|
uri = "/domains?name=%s" % domain.name
|
|
page_qs = self._get_pagination_qs(limit, offset)
|
|
if page_qs:
|
|
uri = "%s&%s" % (uri, page_qs[1:])
|
|
return self._list_subdomains(uri, domain.id)
|
|
|
|
|
|
def _list_subdomains(self, uri, domain_id):
|
|
resp, body = self._retry_get(uri)
|
|
self._reset_paging("subdomain", body)
|
|
subdomains = body.get("domains", [])
|
|
return [CloudDNSDomain(self, subdomain, loaded=False)
|
|
for subdomain in subdomains
|
|
if subdomain["id"] != domain_id]
|
|
|
|
|
|
def list_subdomains_previous_page(self):
|
|
"""
|
|
When paging through subdomain results, this will return the previous
|
|
page, using the same limit. If there are no more results, a
|
|
NoMoreResults exception will be raised.
|
|
"""
|
|
uri = self._paging.get("subdomain", {}).get("prev_uri")
|
|
if uri is None:
|
|
raise exc.NoMoreResults("There are no previous pages of subdomains "
|
|
"to list.")
|
|
return self._list_subdomains(uri)
|
|
|
|
|
|
def list_subdomains_next_page(self):
|
|
"""
|
|
When paging through subdomain results, this will return the next page,
|
|
using the same limit. If there are no more results, a NoMoreResults
|
|
exception will be raised.
|
|
"""
|
|
uri = self._paging.get("subdomain", {}).get("next_uri")
|
|
if uri is None:
|
|
raise exc.NoMoreResults("There are no more pages of subdomains "
|
|
"to list.")
|
|
return self._list_subdomains(uri)
|
|
|
|
|
|
def list_records(self, domain, limit=None, offset=None):
|
|
"""
|
|
Returns a list of all records configured for the specified domain.
|
|
"""
|
|
uri = "/domains/%s/records%s" % (utils.get_id(domain),
|
|
self._get_pagination_qs(limit, offset))
|
|
return self._list_records(uri)
|
|
|
|
|
|
def _list_records(self, uri):
|
|
resp, body = self._retry_get(uri)
|
|
self._reset_paging("record", body)
|
|
# The domain ID will be in the URL
|
|
pat = "domains/([^/]+)/records"
|
|
mtch = re.search(pat, uri)
|
|
dom_id = mtch.groups()[0]
|
|
records = body.get("records", [])
|
|
for record in records:
|
|
record["domain_id"] = dom_id
|
|
return [CloudDNSRecord(self, record, loaded=False)
|
|
for record in records if record]
|
|
|
|
|
|
def list_records_previous_page(self):
|
|
"""
|
|
When paging through record results, this will return the previous page,
|
|
using the same limit. If there are no more results, a NoMoreResults
|
|
exception will be raised.
|
|
"""
|
|
uri = self._paging.get("record", {}).get("prev_uri")
|
|
if uri is None:
|
|
raise exc.NoMoreResults("There are no previous pages of records "
|
|
"to list.")
|
|
return self._list_records(uri)
|
|
|
|
|
|
def list_records_next_page(self):
|
|
"""
|
|
When paging through record results, this will return the next page,
|
|
using the same limit. If there are no more results, a NoMoreResults
|
|
exception will be raised.
|
|
"""
|
|
uri = self._paging.get("record", {}).get("next_uri")
|
|
if uri is None:
|
|
raise exc.NoMoreResults("There are no more pages of records to list.")
|
|
return self._list_records(uri)
|
|
|
|
|
|
def search_records(self, domain, record_type, name=None, data=None):
|
|
"""
|
|
Returns a list of all records configured for the specified domain that
|
|
match the supplied search criteria.
|
|
"""
|
|
search_params = []
|
|
if name:
|
|
search_params.append("name=%s" % name)
|
|
if data:
|
|
search_params.append("data=%s" % data)
|
|
query_string = "&".join(search_params)
|
|
dom_id = utils.get_id(domain)
|
|
uri = "/domains/%s/records?type=%s" % (dom_id, record_type)
|
|
if query_string:
|
|
uri = "%s&%s" % (uri, query_string)
|
|
resp, body = self._retry_get(uri)
|
|
records = body.get("records", [])
|
|
self._reset_paging("record", body)
|
|
rec_paging = self._paging.get("record", {})
|
|
while rec_paging.get("next_uri"):
|
|
resp, body = self._retry_get(rec_paging.get("next_uri"))
|
|
self._reset_paging("record", body)
|
|
records.extend(body.get("records", []))
|
|
for record in records:
|
|
record["domain_id"] = dom_id
|
|
return [CloudDNSRecord(self, record, loaded=False)
|
|
for record in records if record]
|
|
|
|
|
|
def add_records(self, domain, records):
|
|
"""
|
|
Adds the records to this domain. Each record should be a dict with the
|
|
following keys:
|
|
- type (required)
|
|
- name (required)
|
|
- data (required)
|
|
- ttl (optional)
|
|
- comment (optional)
|
|
- priority (required for MX and SRV records; forbidden otherwise)
|
|
"""
|
|
if isinstance(records, dict):
|
|
# Single record passed
|
|
records = [records]
|
|
dom_id = utils.get_id(domain)
|
|
uri = "/domains/%s/records" % dom_id
|
|
body = {"records": records}
|
|
resp, resp_body = self._async_call(uri, method="POST", body=body,
|
|
error_class=exc.DomainRecordAdditionFailed, has_response=False)
|
|
records = resp_body.get("response", {}).get("records", [])
|
|
for record in records:
|
|
record["domain_id"] = dom_id
|
|
return [CloudDNSRecord(self, record, loaded=False)
|
|
for record in records if record]
|
|
|
|
|
|
def get_record(self, domain, record):
|
|
"""
|
|
Gets the full information for an existing record for this domain.
|
|
"""
|
|
rec_id = utils.get_id(record)
|
|
domain_id = utils.get_id(domain)
|
|
uri = "/domains/%s/records/%s" % (domain_id, rec_id)
|
|
resp, resp_body = self._retry_get(uri)
|
|
resp_body['domain_id'] = domain_id
|
|
return CloudDNSRecord(self, resp_body, loaded=False)
|
|
|
|
|
|
def update_record(self, domain, record, data=None, priority=None,
|
|
ttl=None, comment=None):
|
|
"""
|
|
Modifies an existing record for a domain.
|
|
"""
|
|
rec_id = utils.get_id(record)
|
|
uri = "/domains/%s/records/%s" % (utils.get_id(domain), rec_id)
|
|
body = {"name": record.name}
|
|
all_opts = (("data", data), ("priority", priority), ("ttl", ttl),
|
|
("comment", comment))
|
|
opts = [(k, v) for k, v in all_opts if v is not None]
|
|
body.update(dict(opts))
|
|
resp, resp_body = self._async_call(uri, method="PUT", body=body,
|
|
error_class=exc.DomainRecordUpdateFailed, has_response=False)
|
|
return resp_body
|
|
|
|
|
|
def delete_record(self, domain, record):
|
|
"""
|
|
Deletes an existing record for a domain.
|
|
"""
|
|
uri = "/domains/%s/records/%s" % (utils.get_id(domain),
|
|
utils.get_id(record))
|
|
resp, resp_body = self._async_call(uri, method="DELETE",
|
|
error_class=exc.DomainRecordDeletionFailed, has_response=False)
|
|
return resp_body
|
|
|
|
|
|
def _get_ptr_details(self, device, device_type):
|
|
"""
|
|
Takes a device and device type and returns the corresponding HREF link
|
|
and service name for use with PTR record management.
|
|
"""
|
|
if device_type.lower().startswith("load"):
|
|
ep = pyrax._get_service_endpoint("load_balancer")
|
|
svc = "loadbalancers"
|
|
svc_name = "cloudLoadBalancers"
|
|
else:
|
|
ep = pyrax._get_service_endpoint("compute")
|
|
svc = "servers"
|
|
svc_name = "cloudServersOpenStack"
|
|
href = "%s/%s/%s" % (ep, svc, utils.get_id(device))
|
|
return (href, svc_name)
|
|
|
|
|
|
def _resolve_device_type(self, device):
|
|
"""
|
|
Given a device, determines if it is a CloudServer, a CloudLoadBalancer,
|
|
or an invalid device.
|
|
"""
|
|
try:
|
|
from tests.unit import fakes
|
|
server_types = (pyrax.CloudServer, fakes.FakeServer)
|
|
lb_types = (CloudLoadBalancer, fakes.FakeLoadBalancer,
|
|
fakes.FakeDNSDevice)
|
|
except ImportError:
|
|
# Not running with tests
|
|
server_types = (pyrax.CloudServer, )
|
|
lb_types = (CloudLoadBalancer, )
|
|
if isinstance(device, server_types):
|
|
device_type = "server"
|
|
elif isinstance(device, lb_types):
|
|
device_type = "loadbalancer"
|
|
else:
|
|
raise exc.InvalidDeviceType("The device '%s' must be a CloudServer "
|
|
"or a CloudLoadBalancer." % device)
|
|
return device_type
|
|
|
|
|
|
def list_ptr_records(self, device):
|
|
"""
|
|
Returns a list of all PTR records configured for this device.
|
|
"""
|
|
device_type = self._resolve_device_type(device)
|
|
href, svc_name = self._get_ptr_details(device, device_type)
|
|
uri = "/rdns/%s?href=%s" % (svc_name, href)
|
|
try:
|
|
resp, resp_body = self._retry_get(uri)
|
|
except exc.NotFound:
|
|
return []
|
|
records = [CloudDNSPTRRecord(rec, device)
|
|
for rec in resp_body.get("records", [])]
|
|
return records
|
|
|
|
|
|
def add_ptr_records(self, device, records):
|
|
"""
|
|
Adds one or more PTR records to the specified device.
|
|
"""
|
|
device_type = self._resolve_device_type(device)
|
|
href, svc_name = self._get_ptr_details(device, device_type)
|
|
if not isinstance(records, (list, tuple)):
|
|
records = [records]
|
|
body = {"recordsList": {
|
|
"records": records},
|
|
"link": {
|
|
"content": "",
|
|
"href": href,
|
|
"rel": svc_name,
|
|
}}
|
|
uri = "/rdns"
|
|
# This is a necessary hack, so here's why: if you attempt to add
|
|
# PTR records to device, and you don't have rights to either the device
|
|
# or the IP address, the DNS API will return a 401 - Unauthorized.
|
|
# Unfortunately, the pyrax client interprets this as a bad auth token,
|
|
# and there is no way to distinguish this from an actual authentication
|
|
# failure. The client will attempt to re-authenticate as a result, and
|
|
# will fail, due to the DNS API not having regional endpoints. The net
|
|
# result is that an EndpointNotFound exception will be raised, which
|
|
# we catch here and then raise a more meaningful exception.
|
|
# The Rackspace DNS team is working on changing this to return a 403
|
|
# instead; when that happens this kludge can go away.
|
|
try:
|
|
resp, resp_body = self._async_call(uri, body=body, method="POST",
|
|
error_class=exc.PTRRecordCreationFailed)
|
|
except exc.EndpointNotFound:
|
|
raise exc.InvalidPTRRecord("The domain/IP address information is not "
|
|
"valid for this device.")
|
|
return resp_body.get("records")
|
|
records = [CloudDNSPTRRecord(rec, device)
|
|
for rec in resp_body.get("records", [])]
|
|
return records
|
|
|
|
|
|
def update_ptr_record(self, device, record, domain_name, data=None,
|
|
ttl=None, comment=None):
|
|
"""
|
|
Updates a PTR record with the supplied values.
|
|
"""
|
|
device_type = self._resolve_device_type(device)
|
|
href, svc_name = self._get_ptr_details(device, device_type)
|
|
try:
|
|
rec_id = record.id
|
|
except AttributeError:
|
|
rec_id = record
|
|
rec = {"name": domain_name,
|
|
"id": rec_id,
|
|
"type": "PTR",
|
|
"data": data,
|
|
}
|
|
if ttl is not None:
|
|
# Minimum TTL is 300 seconds
|
|
rec["ttl"] = max(300, ttl)
|
|
if comment is not None:
|
|
# Maximum comment length is 160 chars
|
|
rec["comment"] = comment[:160]
|
|
body = {"recordsList": {
|
|
"records": [rec]},
|
|
"link": {
|
|
"content": "",
|
|
"href": href,
|
|
"rel": svc_name,
|
|
}}
|
|
uri = "/rdns"
|
|
try:
|
|
resp, resp_body = self._async_call(uri, body=body, method="PUT",
|
|
has_response=False, error_class=exc.PTRRecordUpdateFailed)
|
|
except exc.EndpointNotFound as e:
|
|
raise exc.InvalidPTRRecord("The record domain/IP address "
|
|
"information is not valid for this device.")
|
|
return resp_body.get("status") == "COMPLETED"
|
|
|
|
|
|
def delete_ptr_records(self, device, ip_address=None):
|
|
"""
|
|
Deletes the PTR records for the specified device. If 'ip_address' is
|
|
supplied, only the PTR records with that IP address will be deleted.
|
|
"""
|
|
device_type = self._resolve_device_type(device)
|
|
href, svc_name = self._get_ptr_details(device, device_type)
|
|
uri = "/rdns/%s?href=%s" % (svc_name, href)
|
|
if ip_address:
|
|
uri = "%s&ip=%s" % (uri, ip_address)
|
|
resp, resp_body = self._async_call(uri, method="DELETE",
|
|
has_response=False,
|
|
error_class=exc.PTRRecordDeletionFailed)
|
|
return resp_body.get("status") == "COMPLETED"
|
|
|
|
|
|
|
|
class CloudDNSClient(BaseClient):
|
|
"""
|
|
This is the primary class for interacting with Cloud DNS.
|
|
"""
|
|
name = "Cloud DNS"
|
|
|
|
def _configure_manager(self):
|
|
"""
|
|
Creates a manager to handle the instances, and another
|
|
to handle flavors.
|
|
"""
|
|
self._manager = CloudDNSManager(self, resource_class=CloudDNSDomain,
|
|
response_key="domains", plural_response_key="domains",
|
|
uri_base="domains")
|
|
|
|
def method_get(self, uri, **kwargs):
|
|
"""
|
|
Overload the method_get function in order to retry on empty body
|
|
responses from the Cloud DNS API
|
|
"""
|
|
for i in six.moves.range(3):
|
|
resp, body = super(CloudDNSClient, self).method_get(uri, **kwargs)
|
|
if body:
|
|
return resp, body
|
|
raise exc.ServiceResponseFailure("The Cloud DNS service failed to "
|
|
"respond to the request.")
|
|
|
|
|
|
def set_timeout(self, timeout):
|
|
"""
|
|
Sets the amount of time that calls will wait for a response from
|
|
the DNS system before timing out. Setting the timeout to zero will
|
|
cause execution to wait indefinitely until the call completes.
|
|
"""
|
|
self._manager._set_timeout(timeout)
|
|
|
|
|
|
def set_delay(self, delay):
|
|
"""
|
|
Changes the interval that the program will pause in between attempts to
|
|
see if a request has completed.
|
|
"""
|
|
self._manager._set_delay(delay)
|
|
|
|
|
|
def list(self, limit=None, offset=None):
|
|
"""Returns a list of all resources."""
|
|
return self._manager.list(limit=limit, offset=offset)
|
|
|
|
|
|
def list_previous_page(self):
|
|
"""Returns the previous page of results."""
|
|
return self._manager.list_previous_page()
|
|
|
|
|
|
def list_next_page(self):
|
|
"""Returns the next page of results."""
|
|
return self._manager.list_next_page()
|
|
|
|
|
|
def get_domain_iterator(self):
|
|
"""
|
|
Returns an iterator that will return each available domain. If there are
|
|
more than the limit of 100 domains, the iterator will continue to fetch
|
|
domains from the API until all domains have been returned.
|
|
"""
|
|
return DomainResultsIterator(self._manager)
|
|
|
|
|
|
@assure_domain
|
|
def changes_since(self, domain, date_or_datetime):
|
|
"""
|
|
Gets the changes for a domain since the specified date/datetime.
|
|
The date can be one of:
|
|
- a Python datetime object
|
|
- a Python date object
|
|
- a string in the format 'YYYY-MM-YY HH:MM:SS'
|
|
- a string in the format 'YYYY-MM-YY'
|
|
|
|
It returns a list of dicts, whose keys depend on the specific change
|
|
that was made. A simple example of such a change dict:
|
|
|
|
{u'accountId': 000000,
|
|
u'action': u'update',
|
|
u'changeDetails': [{u'field': u'serial_number',
|
|
u'newValue': u'1354038941',
|
|
u'originalValue': u'1354038940'},
|
|
{u'field': u'updated_at',
|
|
u'newValue': u'Tue Nov 27 17:55:41 UTC 2012',
|
|
u'originalValue': u'Tue Nov 27 17:55:40 UTC 2012'}],
|
|
u'domain': u'example.com',
|
|
u'targetId': 00000000,
|
|
u'targetType': u'Domain'}
|
|
"""
|
|
return domain.changes_since(date_or_datetime)
|
|
|
|
|
|
@assure_domain
|
|
def export_domain(self, domain):
|
|
"""
|
|
Provides the BIND (Berkeley Internet Name Domain) 9 formatted contents
|
|
of the requested domain. This call is for a single domain only, and as
|
|
such, does not provide subdomain information.
|
|
|
|
Sample export:
|
|
|
|
{u'accountId': 000000,
|
|
u'contentType': u'BIND_9',
|
|
u'contents': u'example.com.\t3600\tIN\tSOA\tns.rackspace.com. '
|
|
'foo@example.com. 1354202974 21600 3600 1814400 500'
|
|
'example.com.\t3600\tIN\tNS\tdns1.stabletransit.com.'
|
|
'example.com.\t3600\tIN\tNS\tdns2.stabletransit.com.',
|
|
u'id': 1111111}
|
|
"""
|
|
return domain.export()
|
|
|
|
|
|
def import_domain(self, domain_data):
|
|
"""
|
|
Takes a string in the BIND 9 format and creates a new domain. See the
|
|
'export_domain()' method for a description of the format.
|
|
"""
|
|
return self._manager.import_domain(domain_data)
|
|
|
|
|
|
@assure_domain
|
|
def update_domain(self, domain, emailAddress=None, ttl=None, comment=None):
|
|
"""
|
|
Provides a way to modify the following attributes of a domain
|
|
record:
|
|
- email address
|
|
- ttl setting
|
|
- comment
|
|
"""
|
|
return domain.update(emailAddress=emailAddress,
|
|
ttl=ttl, comment=comment)
|
|
|
|
|
|
@assure_domain
|
|
def delete(self, domain, delete_subdomains=False):
|
|
"""
|
|
Deletes the specified domain and all of its resource records. If the
|
|
domain has subdomains, each subdomain will now become a root domain. If
|
|
you wish to also delete any subdomains, pass True to 'delete_subdomains'.
|
|
"""
|
|
domain.delete(delete_subdomains=delete_subdomains)
|
|
|
|
|
|
@assure_domain
|
|
def list_subdomains(self, domain, limit=None, offset=None):
|
|
"""
|
|
Returns a list of all subdomains for the specified domain.
|
|
"""
|
|
return domain.list_subdomains(limit=limit, offset=offset)
|
|
|
|
|
|
def get_subdomain_iterator(self, domain, limit=None, offset=None):
|
|
"""
|
|
Returns an iterator that will return each available subdomain for the
|
|
specified domain. If there are more than the limit of 100 subdomains,
|
|
the iterator will continue to fetch subdomains from the API until all
|
|
subdomains have been returned.
|
|
"""
|
|
return SubdomainResultsIterator(self._manager, domain=domain)
|
|
|
|
|
|
def list_subdomains_previous_page(self):
|
|
"""Returns the previous page of subdomain results."""
|
|
return self._manager.list_subdomains_previous_page()
|
|
|
|
|
|
def list_subdomains_next_page(self):
|
|
"""Returns the next page of subdomain results."""
|
|
return self._manager.list_subdomains_next_page()
|
|
|
|
|
|
@assure_domain
|
|
def list_records(self, domain, limit=None, offset=None):
|
|
"""
|
|
Returns a list of all records configured for the specified domain.
|
|
"""
|
|
return domain.list_records(limit=limit, offset=offset)
|
|
|
|
|
|
def get_record_iterator(self, domain):
|
|
"""
|
|
Returns an iterator that will return each available DNS record for the
|
|
specified domain. If there are more than the limit of 100 records, the
|
|
iterator will continue to fetch records from the API until all records
|
|
have been returned.
|
|
"""
|
|
return RecordResultsIterator(self._manager, domain=domain)
|
|
|
|
|
|
def list_records_previous_page(self):
|
|
"""Returns the previous page of record results."""
|
|
return self._manager.list_records_previous_page()
|
|
|
|
|
|
def list_records_next_page(self):
|
|
"""Returns the next page of record results."""
|
|
return self._manager.list_records_next_page()
|
|
|
|
|
|
@assure_domain
|
|
def search_records(self, domain, record_type, name=None, data=None):
|
|
"""
|
|
Returns a list of all records configured for the specified domain
|
|
that match the supplied search criteria.
|
|
"""
|
|
return domain.search_records(record_type=record_type,
|
|
name=name, data=data)
|
|
|
|
|
|
@assure_domain
|
|
def find_record(self, domain, record_type, name=None, data=None):
|
|
"""
|
|
Returns a single record for this domain that matches the supplied
|
|
search criteria.
|
|
|
|
If no record matches, a DomainRecordNotFound exception will be raised.
|
|
If more than one matches, a DomainRecordNotUnique exception will
|
|
be raised.
|
|
"""
|
|
return domain.find_record(record_type=record_type,
|
|
name=name, data=data)
|
|
|
|
|
|
@assure_domain
|
|
def add_records(self, domain, records):
|
|
"""
|
|
Adds the records to this domain. Each record should be a dict with the
|
|
following keys:
|
|
- type (required)
|
|
- name (required)
|
|
- data (required)
|
|
- ttl (optional)
|
|
- comment (optional)
|
|
- priority (required for MX and SRV records; forbidden otherwise)
|
|
"""
|
|
return domain.add_records(records)
|
|
|
|
# Create an alias, so that adding a single record is more intuitive
|
|
add_record = add_records
|
|
|
|
|
|
@assure_domain
|
|
def get_record(self, domain, record):
|
|
"""
|
|
Gets the full information for an existing record or record ID for the
|
|
specified domain.
|
|
"""
|
|
return domain.get_record(record)
|
|
|
|
|
|
@assure_domain
|
|
def update_record(self, domain, record, data=None, priority=None,
|
|
ttl=None, comment=None):
|
|
"""
|
|
Modifies an existing record for a domain.
|
|
"""
|
|
return domain.update_record(record, data=data,
|
|
priority=priority, ttl=ttl, comment=comment)
|
|
|
|
|
|
@assure_domain
|
|
def delete_record(self, domain, record):
|
|
"""
|
|
Deletes an existing record for this domain.
|
|
"""
|
|
return domain.delete_record(record)
|
|
|
|
|
|
def list_ptr_records(self, device):
|
|
"""
|
|
Returns a list of all PTR records configured for this device.
|
|
"""
|
|
return self._manager.list_ptr_records(device)
|
|
|
|
|
|
def add_ptr_records(self, device, records):
|
|
"""
|
|
Adds one or more PTR records to the specified device.
|
|
"""
|
|
return self._manager.add_ptr_records(device, records)
|
|
|
|
|
|
def update_ptr_record(self, device, record, domain_name, data=None,
|
|
ttl=None, comment=None):
|
|
"""
|
|
Updates a PTR record with the supplied values.
|
|
"""
|
|
return self._manager.update_ptr_record(device, record, domain_name,
|
|
data=data, ttl=ttl, comment=comment)
|
|
|
|
|
|
def delete_ptr_records(self, device, ip_address=None):
|
|
"""
|
|
Deletes the PTR records for the specified device. If 'ip_address'
|
|
is supplied, only the PTR records with that IP address will be deleted.
|
|
"""
|
|
return self._manager.delete_ptr_records(device, ip_address=ip_address)
|
|
|
|
|
|
def get_absolute_limits(self):
|
|
"""
|
|
Returns a dict with the absolute limits for the current account.
|
|
"""
|
|
resp, body = self.method_get("/limits")
|
|
absolute_limits = body.get("limits", {}).get("absolute")
|
|
return absolute_limits
|
|
|
|
|
|
def get_rate_limits(self):
|
|
"""
|
|
Returns a dict with the current rate limit information for domain
|
|
and status requests.
|
|
"""
|
|
resp, body = self.method_get("/limits")
|
|
rate_limits = body.get("limits", {}).get("rate")
|
|
ret = []
|
|
for rate_limit in rate_limits:
|
|
limits = rate_limit["limit"]
|
|
uri_limits = {"uri": rate_limit["uri"],
|
|
"limits": limits}
|
|
ret.append(uri_limits)
|
|
return ret
|
|
|
|
|
|
|
|
class ResultsIterator(object):
|
|
"""
|
|
This object will iterate over all the results for a given
|
|
type of listing, no matter how many items exist.
|
|
|
|
This is an abstract class; subclasses must define the
|
|
_init_methods() method.
|
|
"""
|
|
def __init__(self, manager, domain=None):
|
|
self.manager = manager
|
|
self.domain = domain
|
|
self.domain_id = utils.get_id(domain) if domain else None
|
|
self.results = []
|
|
self.next_uri = ""
|
|
self.extra_args = tuple()
|
|
self._init_methods()
|
|
|
|
|
|
def _init_methods(self):
|
|
"""
|
|
Must be implemented in subclasses.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
|
|
def next(self):
|
|
"""
|
|
Return the next available item. If there are no more items in the
|
|
local 'results' list, check if there is a 'next_uri' value. If so,
|
|
use that to get the next page of results from the API, and return
|
|
the first item from that query.
|
|
"""
|
|
try:
|
|
return self.results.pop(0)
|
|
except IndexError:
|
|
if self.next_uri is None:
|
|
raise StopIteration()
|
|
else:
|
|
if not self.next_uri:
|
|
if self.domain:
|
|
self.results = self.list_method(self.domain)
|
|
else:
|
|
self.results = self.list_method()
|
|
else:
|
|
args = self.extra_args
|
|
self.results = self._list_method(self.next_uri, *args)
|
|
self.next_uri = self.manager._paging.get(
|
|
self.paging_service, {}).get("next_uri")
|
|
# We should have more results.
|
|
try:
|
|
return self.results.pop(0)
|
|
except IndexError:
|
|
raise StopIteration()
|
|
|
|
|
|
class DomainResultsIterator(ResultsIterator):
|
|
"""
|
|
ResultsIterator subclass for iterating over all domains.
|
|
"""
|
|
def _init_methods(self):
|
|
self.list_method = self.manager.list
|
|
self._list_method = self.manager._list
|
|
self.paging_service = "domain"
|
|
|
|
|
|
class SubdomainResultsIterator(ResultsIterator):
|
|
"""
|
|
ResultsIterator subclass for iterating over all subdomains.
|
|
"""
|
|
def _init_methods(self):
|
|
self.list_method = self.manager.list_subdomains
|
|
self._list_method = self.manager._list_subdomains
|
|
self.extra_args = (self.domain_id, )
|
|
self.paging_service = "subdomain"
|
|
|
|
|
|
class RecordResultsIterator(ResultsIterator):
|
|
"""
|
|
ResultsIterator subclass for iterating over all domain records.
|
|
"""
|
|
def _init_methods(self):
|
|
self.list_method = self.manager.list_records
|
|
self._list_method = self.manager._list_records
|
|
self.paging_service = "record"
|