Skip to content

Commit

Permalink
Merge ccc760f into 240d784
Browse files Browse the repository at this point in the history
  • Loading branch information
thedodd committed Aug 3, 2015
2 parents 240d784 + ccc760f commit a6920c0
Show file tree
Hide file tree
Showing 6 changed files with 416 additions and 12 deletions.
256 changes: 256 additions & 0 deletions objectrocket/acls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
"""ACL operations and objects."""
import copy
import json
import logging

import requests

from objectrocket import bases
from objectrocket import util

logger = logging.getLogger(__name__)


class Acls(bases.BaseOperationsLayer):
"""ACL operations.
:param objectrocket.client.Client base_client: An instance of objectrocket.client.Client.
"""

def __init__(self, base_client):
super(Acls, self).__init__(base_client=base_client)

#####################
# Public interface. #
#####################
@util.token_auto_auth
def all(self, instance):
"""Get all ACLs associated with the instance specified by name.
:param str instance: The name of the instance from which to fetch ACLs.
:returns: A list of :py:class:`Acl` objects associated with the specified instance.
:rtype: list
"""
url = self._url.format(instance)
response = requests.get(url, **self._default_request_kwargs)
data = self._get_response_data(response)
return self._concrete_acl_list(data)

@util.token_auto_auth
def create(self, instance, cidr_mask, description, **kwargs):
"""Create an ACL entry for the specified instance.
:param str instance: The name of the instance to associate the new ACL entry with.
:param str cidr_mask: The IPv4 CIDR mask for the new ACL entry.
:param str description: A short description for the new ACL entry.
:param collector kwargs: (optional) Additional key=value pairs to be supplied to the
creation payload. **Caution:** fields unrecognized by the API will cause this request
to fail with a 400 from the API.
"""
# Build up request data.
url = self._url.format(instance)
request_data = {
'cidr_mask': cidr_mask,
'description': description
}
request_data.update(kwargs)

# Call to create an instance.
response = requests.post(
url,
data=json.dumps(request_data),
**self._default_request_kwargs
)

# Log outcome of instance creation request.
if response.status_code == 200:
logger.info('Successfully created a new ACL for instance {} with: {}.'
.format(instance, request_data))
else:
logger.info('Failed to create a new ACL for instance {} with: {}.'
.format(instance, request_data))

data = self._get_response_data(response)
return self._concrete_acl(data)

@util.token_auto_auth
def get(self, instance, acl):
"""Get an ACL by ID belonging to the instance specified by name.
:param str instance: The name of the instance from which to fetch the ACL.
:param str acl: The ID of the ACL to fetch.
:returns: An :py:class:`Acl` object, or None if ACL does not exist.
:rtype: :py:class:`Acl`
"""
url = self._url.format(instance)
response = requests.get(url, **self._default_request_kwargs)
data = self._get_response_data(response)
return self._concrete_acl(data)

######################
# Private interface. #
######################
def _concrete_acl(self, acl_doc):
"""Concretize an ACL document.
:param dict acl_doc: A document describing an ACL entry. Should come from the API.
:returns: An :py:class:`Acl`, or None.
:rtype: :py:class:`bases.BaseInstance`
"""
if not isinstance(acl_doc, dict):
return None

# Attempt to instantiate an Acl object with the given dict.
try:
return Acl(document=acl_doc, acls=self)

# If service is not recognized, log a warning and return None.
except Exception as ex:
logger.exception(ex)
logger.error('Could not instantiate ACL document. You probably need to upgrade to a '
'recent version of the client. Document which caused this error: {}'
.format(acl_doc))
return None

def _concrete_acl_list(self, acl_docs):
"""Concretize a list of ACL documents.
:param list acl_docs: A list of ACL documents. Should come from the API.
:returns: A list of :py:class:`ACL` objects.
:rtype: list
"""
if not acl_docs:
return []

return list(filter(None, [self._concrete_acl(acl_doc=doc) for doc in acl_docs]))

@property
def _default_request_kwargs(self):
"""The default request keyword arguments to be passed to the requests library."""
defaults = copy.deepcopy(super(Acls, self)._default_request_kwargs)
defaults.setdefault('headers', {}).update({
'X-Auth-Token': self._client.auth._token
})
return defaults

@property
def _url(self):
"""The base URL for ACL operations."""
base_url = self._client._url.rstrip('/')
return '{}/instances/{{instance}}/acls/'.format(base_url)


class Acl(object):
"""An Access Control List entry object.
:param document dict: The dict representing this object.
:param Acls acls: The Acls operations layer instance from which this object came.
"""

def __init__(self, document, acls):
self.__client = acls._client
self.__acls = acls
self.__document = document

# Bind required pseudo private attributes from API response document.
self._cidr_mask = document['cidr_mask']
self._description = document['description']
self._id = document['id']
self._instance_name = document['instance']
self._login = document['login']
self._port = document['port']

# Bind attributes which may be present in API response document.
self._date_created = document.get('date_created', None)
self._instance = document.get('instance_id', None)
self._instance_type = document.get('instance_type', None)
self._metadata = document.get('metadata', {})
self._service_type = document.get('service_type', None)

def __repr__(self):
"""Represent this object as a string."""
_id = hex(id(self))
rep = (
'<{!s} cidr={!s} port={!s} instance={!s} id={!s} at {!s}>'
.format(self.__class__.__name__, self.cidr_mask, self.port,
self.instance_name, self.id, _id)
)
return rep

@property
def cidr_mask(self):
"""This ACL entry's CIDR mask."""
return self._cidr_mask

@property
def date_created(self):
"""The date which this ACL entry was created on."""
return self._date_created

@property
def description(self):
"""This ACL entry's description."""
return self._description

@property
def _document(self):
"""This ACL entry's document."""
return self.__document

@property
def id(self):
"""This ACL entry's ID."""
return self._id

@property
def instance(self):
"""The ID of the instance to which this ACL entry is associated."""
return self._instance

@property
def instance_name(self):
"""The name of the instance to which this ACL entry is associated."""
return self._instance_name

@property
def instance_type(self):
"""The type of the instance to which this ACL entry is associated."""
return self._instance_type

@property
def login(self):
"""The login of the user to which this ACL entry belongs."""
return self._login

@property
def metadata(self):
"""This ACL entry's metadata."""
return self._metadata

@property
def port(self):
"""This ACL entry's port number."""
return self._port

@property
def service_type(self):
"""The service of the instance to which this ACL entry is associated."""
return self._service_type

def to_dict(self):
"""Render this object as a dictionary."""
return self._document

######################
# Private interface. #
######################
@property
def _client(self):
"""An instance of the objectrocket.client.Client."""
return self.__client

@property
def _url(self):
"""The URL of this ACL object."""
base_url = self._client._url.rstrip('/')
return '{}/instances/{}/acls/{}/'.format(base_url, self.instance_name, self.id)
16 changes: 16 additions & 0 deletions objectrocket/bases.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Base classes used throughout the library."""
import abc
import logging

import six

Expand All @@ -8,6 +9,8 @@

from stevedore.extension import ExtensionManager

log = logging.getLogger(__name__)


@six.add_metaclass(abc.ABCMeta)
class BaseOperationsLayer(object):
Expand Down Expand Up @@ -37,6 +40,19 @@ def _default_request_kwargs(self):
}
return default_kwargs

def _get_response_data(self, response):
"""Return the data from a ``requests.Response`` object.
:param requests.Response response: The ``Response`` object from which to get the data.
"""
try:
_json = response.json()
data = _json.get('data')
return data
except ValueError as ex:
log.exception(ex)
return None

@abc.abstractproperty
def _url(self):
"""The URL this operations layer is to interface with."""
Expand Down
2 changes: 2 additions & 0 deletions objectrocket/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""ObjectRocket Python client."""
from objectrocket import acls
from objectrocket import auth
from objectrocket import bases
from objectrocket import constants
Expand All @@ -16,6 +17,7 @@ def __init__(self, base_url=constants.OR_DEFAULT_API_URL):
self.__url = base_url

# Public interface attributes.
self.acls = acls.Acls(base_client=self)
self.auth = auth.Auth(base_client=self)
self.instances = instances.Instances(base_client=self)

Expand Down
12 changes: 0 additions & 12 deletions objectrocket/instances/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,18 +132,6 @@ def _concrete_instance_list(self, instance_docs):

return filter(None, [self._concrete_instance(instance_doc=doc) for doc in instance_docs])

def _get_response_data(self, response):
"""Return the data from a ``requests.Response`` object.
:param requests.Response response: The ``Response`` object from which to get the data.
"""
try:
_json = response.json()
data = _json.get('data')
return data
except ValueError:
return None

@property
def _default_request_kwargs(self):
"""The default request keyword arguments to be passed to the requests library."""
Expand Down
34 changes: 34 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import requests

from objectrocket.client import Client
from objectrocket import acls
from objectrocket import instances
from objectrocket import constants

Expand Down Expand Up @@ -57,6 +58,35 @@ class Obj(object):
return Obj()


##########################
# ACLs related fixtures. #
##########################
@pytest.fixture
def acl_doc():
now = datetime.datetime.utcnow()
doc = {
'id': uuid.uuid4().hex,

'cidr_mask': '0.0.0.0/1',
'description': 'testing',
'instance': 'testinstance',
'login': 'testuser',
'port': 27017,

'date_created': datetime.datetime.strftime(now, constants.TIME_FORMAT),
'instance_id': uuid.uuid4().hex,
'instance_type': 'mongodb_sharded',
'metadata': {},
'service_type': 'mongodb'
}
return doc


@pytest.fixture
def acl(acl_doc, client):
return acls.Acl(document=acl_doc, acls=client.acls)


##############################
# Instance related fixtures. #
##############################
Expand Down Expand Up @@ -125,6 +155,10 @@ def patched_requests_map(request):
"""
patches = {}

mocked = mock.patch('objectrocket.acls.requests', autospec=True)
request.addfinalizer(mocked.stop)
patches['acls'] = mocked.start()

mocked = mock.patch('objectrocket.auth.requests', autospec=True)
request.addfinalizer(mocked.stop)
patches['auth'] = mocked.start()
Expand Down

0 comments on commit a6920c0

Please sign in to comment.