From 0a19263facedc8028eeccfbd03abf62efc5b2bad Mon Sep 17 00:00:00 2001 From: Brian Curtin Date: Wed, 24 Aug 2016 08:19:39 -0400 Subject: [PATCH] Refactor Key Manager for resource2 This change updates the Key Manager service for the resource2/proxy2 refactoring. Along with making it work with the new classes, it improves usability--at least temporarily--by exposing the ID value necessary from the HREFs that the service returns. The HREF that gets returned, e.g., from a list call, is not directly usable to then pass it into a get call. A more long-term fix for this would potentially be to create a Key Manager specific base class that fiddles around with IDs and looks to see if they are an HREF and converts them to a UUID in the proper direction depending on where the data is going, but that's too much to tackle in this refactoring change. Besides updating some of the resources to match the documented attributes, one feature this does add is retrieval of the Secret payload, which is done via a separate endpoint. However, like other calls in Glance and Heat, we unify them in the proxy's `get_secret` call so the user doesn't need to know it's a separate call. This also includes some basic docs in the user guide to show how the different ID usage is currently necessary. Change-Id: I8b5753e121d8f79350b38803e8aac95d7b4d1627 --- doc/source/users/guides/key_manager.rst | 49 +++++++++- examples/key_manager/__init__.py | 0 examples/key_manager/create.py | 25 ++++++ examples/key_manager/get.py | 26 ++++++ examples/key_manager/list.py | 31 +++++++ openstack/key_manager/v1/_format.py | 39 ++++++++ openstack/key_manager/v1/_proxy.py | 4 +- openstack/key_manager/v1/container.py | 27 +++--- openstack/key_manager/v1/order.py | 41 +++++---- openstack/key_manager/v1/secret.py | 83 ++++++++++++++--- .../unit/key_manager/v1/test_container.py | 14 +-- .../tests/unit/key_manager/v1/test_order.py | 32 ++++--- .../tests/unit/key_manager/v1/test_proxy.py | 6 +- .../tests/unit/key_manager/v1/test_secret.py | 89 +++++++++++++++++-- 14 files changed, 399 insertions(+), 67 deletions(-) create mode 100644 examples/key_manager/__init__.py create mode 100644 examples/key_manager/create.py create mode 100644 examples/key_manager/get.py create mode 100644 examples/key_manager/list.py create mode 100644 openstack/key_manager/v1/_format.py diff --git a/doc/source/users/guides/key_manager.rst b/doc/source/users/guides/key_manager.rst index f49506d5..1efd12b7 100644 --- a/doc/source/users/guides/key_manager.rst +++ b/doc/source/users/guides/key_manager.rst @@ -6,4 +6,51 @@ connection to your OpenStack cloud by following the :doc:`connect` user guide. This will provide you with the ``conn`` variable used in the examples below. -.. TODO(thowe): Implement this guide +.. contents:: Table of Contents + :local: + +.. note:: Some interactions with the Key Manager service differ from that + of other services in that resources do not have a proper ``id`` parameter, + which is necessary to make some calls. Instead, resources have a separately + named id attribute, e.g., the Secret resource has ``secret_id``. + + The examples below outline when to pass in those id values. + +Create a Secret +--------------- + +The Key Manager service allows you to create new secrets by passing the +attributes of the :class:`~openstack.key_manager.v1.secret.Secret` to the +:meth:`~openstack.key_manager.v1._proxy.Proxy.create_secret` method. + +.. literalinclude:: ../examples/key_manager/create.py + :pyobject: create_secret + +List Secrets +------------ + +Once you have stored some secrets, they are available for you to list +via the :meth:`~openstack.key_manager.v1._proxy.Proxy.secrets` method. +This method returns a generator, which yields each +:class:`~openstack.key_manager.v1.secret.Secret`. + +.. literalinclude:: ../examples/key_manager/list.py + :pyobject: list_secrets + +The :meth:`~openstack.key_manager.v1._proxy.Proxy.secrets` method can +also make more advanced queries to limit the secrets that are returned. + +.. literalinclude:: ../examples/key_manager/list.py + :pyobject: list_secrets_query + +Get Secret Payload +------------------ + +Once you have received a :class:`~openstack.key_manager.v1.secret.Secret`, +you can obtain the payload for it by passing the secret's id value to +the :meth:`~openstack.key_manager.v1._proxy.Proxy.secrets` method. +Use the :data:`~openstack.key_manager.v1.secret.Secret.secret_id` attribute +when making this request. + +.. literalinclude:: ../examples/key_manager/get.py + :pyobject: get_secret_payload diff --git a/examples/key_manager/__init__.py b/examples/key_manager/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/key_manager/create.py b/examples/key_manager/create.py new file mode 100644 index 00000000..c45e7dc2 --- /dev/null +++ b/examples/key_manager/create.py @@ -0,0 +1,25 @@ +# 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. + +""" +List resources from the Key Manager service. +""" + + +def create_secret(conn): + print("Create a secret:") + + conn.key_manager.create_secret(name="My public key", + secret_type="public", + expiration="2020-02-28T23:59:59", + payload="ssh rsa...", + payload_content_type="text/plain") diff --git a/examples/key_manager/get.py b/examples/key_manager/get.py new file mode 100644 index 00000000..0025dd62 --- /dev/null +++ b/examples/key_manager/get.py @@ -0,0 +1,26 @@ +# 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. + +""" +List resources from the Key Manager service. +""" + +s = None + + +def get_secret_payload(conn): + print("Get a secret's payload:") + + # Assuming you have an object `s` which you perhaps received from + # a conn.key_manager.secrets() call... + secret = conn.key_manager.get_secret(s.secret_id) + print(secret.payload) diff --git a/examples/key_manager/list.py b/examples/key_manager/list.py new file mode 100644 index 00000000..b74e4df7 --- /dev/null +++ b/examples/key_manager/list.py @@ -0,0 +1,31 @@ +# 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. + +""" +List resources from the Key Manager service. +""" + + +def list_secrets(conn): + print("List Secrets:") + + for secret in conn.key_manager.secrets(): + print(secret) + + +def list_secrets_query(conn): + print("List Secrets:") + + for secret in conn.key_manager.secrets( + secret_type="symmetric", + expiration="gte:2020-01-01T00:00:00"): + print(secret) diff --git a/openstack/key_manager/v1/_format.py b/openstack/key_manager/v1/_format.py new file mode 100644 index 00000000..34698a38 --- /dev/null +++ b/openstack/key_manager/v1/_format.py @@ -0,0 +1,39 @@ +# 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 openstack import format + +from six.moves.urllib import parse + + +class HREFToUUID(format.Formatter): + + @classmethod + def deserialize(cls, value): + """Convert a HREF to the UUID portion""" + parts = parse.urlsplit(value) + + # Only try to proceed if we have an actual URI. + # Just check that we have a scheme, netloc, and path. + if not all(parts[:3]): + raise ValueError("Unable to convert %s to an ID" % value) + + # The UUID will be the last portion of the URI. + return parts.path.split("/")[-1] + + @classmethod + def serialize(cls, value): + # NOTE(briancurtin): If we had access to the session to get + # the endpoint we could do something smart here like take an ID + # and give back an HREF, but this will just have to be something + # that works different because Barbican does what it does... + return value diff --git a/openstack/key_manager/v1/_proxy.py b/openstack/key_manager/v1/_proxy.py index 21215bad..0c65d262 100644 --- a/openstack/key_manager/v1/_proxy.py +++ b/openstack/key_manager/v1/_proxy.py @@ -13,10 +13,10 @@ from openstack.key_manager.v1 import container as _container from openstack.key_manager.v1 import order as _order from openstack.key_manager.v1 import secret as _secret -from openstack import proxy +from openstack import proxy2 -class Proxy(proxy.BaseProxy): +class Proxy(proxy2.BaseProxy): def create_container(self, **attrs): """Create a new container from attributes diff --git a/openstack/key_manager/v1/container.py b/openstack/key_manager/v1/container.py index a11074bf..dbfcdfa5 100644 --- a/openstack/key_manager/v1/container.py +++ b/openstack/key_manager/v1/container.py @@ -11,34 +11,39 @@ # under the License. from openstack.key_manager import key_manager_service -from openstack import resource +from openstack.key_manager.v1 import _format +from openstack import resource2 -class Container(resource.Resource): - id_attribute = 'container_ref' +class Container(resource2.Resource): resources_key = 'containers' base_path = '/containers' service = key_manager_service.KeyManagerService() # capabilities allow_create = True - allow_retrieve = True + allow_get = True allow_update = True allow_delete = True allow_list = True # Properties #: A URI for this container - container_ref = resource.prop('container_ref') + container_ref = resource2.Body('container_ref') + #: The ID for this container + container_id = resource2.Body('container_ref', alternate_id=True, + type=_format.HREFToUUID) #: The timestamp when this container was created. - created_at = resource.prop('created') + created_at = resource2.Body('created') #: The name of this container - name = resource.prop('name') + name = resource2.Body('name') #: A list of references to secrets in this container - secret_refs = resource.prop('secret_refs') + secret_refs = resource2.Body('secret_refs', type=list) #: The status of this container - status = resource.prop('status') + status = resource2.Body('status') #: The type of this container - type = resource.prop('type') + type = resource2.Body('type') #: The timestamp when this container was updated. - updated_at = resource.prop('updated') + updated_at = resource2.Body('updated') + #: A party interested in this container. + consumers = resource2.Body('consumers', type=list) diff --git a/openstack/key_manager/v1/order.py b/openstack/key_manager/v1/order.py index 9d3cd3e8..b7bed065 100644 --- a/openstack/key_manager/v1/order.py +++ b/openstack/key_manager/v1/order.py @@ -11,34 +11,45 @@ # under the License. from openstack.key_manager import key_manager_service -from openstack import resource +from openstack.key_manager.v1 import _format +from openstack import resource2 -class Order(resource.Resource): +class Order(resource2.Resource): resources_key = 'orders' base_path = '/orders' service = key_manager_service.KeyManagerService() # capabilities allow_create = True - allow_retrieve = True + allow_get = True allow_update = True allow_delete = True allow_list = True - # Properties - # TODO(briancurtin): not documented - error_reason = resource.prop('error_reason') - # TODO(briancurtin): not documented - error_status_code = resource.prop('error_status_code') - #: a dictionary containing key-value parameters which specify the + #: Timestamp in ISO8601 format of when the order was created + created_at = resource2.Body('created') + #: Keystone Id of the user who created the order + creator_id = resource2.Body('creator_id') + #: A dictionary containing key-value parameters which specify the #: details of an order request - meta = resource.prop('meta') + meta = resource2.Body('meta', type=dict) #: A URI for this order - order_ref = resource.prop('order_ref') - #: TODO(briancurtin): not documented - secret_ref = resource.prop('secret_ref') + order_ref = resource2.Body('order_ref') + #: The ID of this order + order_id = resource2.Body('order_ref', alternate_id=True, + type=_format.HREFToUUID) + #: Secret href associated with the order + secret_ref = resource2.Body('secret_ref') + #: Secret ID associated with the order + secret_id = resource2.Body('secret_ref', type=_format.HREFToUUID) # The status of this order - status = resource.prop('status') + status = resource2.Body('status') + #: Metadata associated with the order + sub_status = resource2.Body('sub_status') + #: Metadata associated with the order + sub_status_message = resource2.Body('sub_status_message') # The type of order - type = resource.prop('type') + type = resource2.Body('type') + #: Timestamp in ISO8601 format of the last time the order was updated. + updated_at = resource2.Body('updated') diff --git a/openstack/key_manager/v1/secret.py b/openstack/key_manager/v1/secret.py index 2d8a5685..603b09ef 100644 --- a/openstack/key_manager/v1/secret.py +++ b/openstack/key_manager/v1/secret.py @@ -11,39 +11,96 @@ # under the License. from openstack.key_manager import key_manager_service -from openstack import resource +from openstack.key_manager.v1 import _format +from openstack import resource2 +from openstack import utils -class Secret(resource.Resource): - id_attribute = 'secret_ref' +class Secret(resource2.Resource): resources_key = 'secrets' base_path = '/secrets' service = key_manager_service.KeyManagerService() # capabilities allow_create = True - allow_retrieve = True + allow_get = True allow_update = True allow_delete = True allow_list = True + _query_mapping = resource2.QueryParameters("name", "mode", "bits", + "secret_type", "acl_only", + "created", "updated", + "expiration", "sort", + algorithm="alg") + # Properties #: Metadata provided by a user or system for informational purposes - algorithm = resource.prop('algorithm') + algorithm = resource2.Body('algorithm') #: Metadata provided by a user or system for informational purposes. #: Value must be greater than zero. - bit_length = resource.prop('bit_length') + bit_length = resource2.Body('bit_length') #: A list of content types - content_types = resource.prop('content_types') + content_types = resource2.Body('content_types', type=dict) #: Once this timestamp has past, the secret will no longer be available. - expires_at = resource.prop('expiration') + expires_at = resource2.Body('expiration') + #: Timestamp of when the secret was created. + created_at = resource2.Body('created') + #: Timestamp of when the secret was last updated. + updated_at = resource2.Body('updated') #: The type/mode of the algorithm associated with the secret information. - mode = resource.prop('mode') + mode = resource2.Body('mode') #: The name of the secret set by the user - name = resource.prop('name') + name = resource2.Body('name') #: A URI to the sercret - secret_ref = resource.prop('secret_ref') + secret_ref = resource2.Body('secret_ref') + #: The ID of the secret + # NOTE: This is not really how alternate IDs are supposed to work and + # ultimately means this has to work differently than all other services + # in all of OpenStack because of the departure from using actual IDs + # that even this service can't even use itself. + secret_id = resource2.Body('secret_ref', alternate_id=True, + type=_format.HREFToUUID) + #: Used to indicate the type of secret being stored. + secret_type = resource2.Body('secret_type') #: The status of this secret - status = resource.prop('status') + status = resource2.Body('status') #: A timestamp when this secret was updated. - updated_at = resource.prop('updated') + updated_at = resource2.Body('updated') + #: The secret's data to be stored. payload_content_type must also + #: be supplied if payload is included. (optional) + payload = resource2.Body('payload') + #: The media type for the content of the payload. + #: (required if payload is included) + payload_content_type = resource2.Body('payload_content_type') + #: The encoding used for the payload to be able to include it in + #: the JSON request. Currently only base64 is supported. + #: (required if payload is encoded) + payload_content_encoding = resource2.Body('payload_content_encoding') + + def get(self, session, requires_id=True): + request = self._prepare_request(requires_id=requires_id) + + response = session.get(request.uri, + endpoint_filter=self.service).json() + + content_type = None + if self.payload_content_type is not None: + content_type = self.payload_content_type + elif "content_types" in response: + content_type = response["content_types"]["default"] + + # Only try to get the payload if a content type has been explicitly + # specified or if one was found in the metadata response + if content_type is not None: + payload = session.get(utils.urljoin(request.uri, "payload"), + endpoint_filter=self.service, + headers={"Accept": content_type}) + response["payload"] = payload.text + + # We already have the JSON here so don't call into _translate_response + body = self._filter_component(response, self._body_mapping()) + self._body.attributes.update(body) + self._body.clean() + + return self diff --git a/openstack/tests/unit/key_manager/v1/test_container.py b/openstack/tests/unit/key_manager/v1/test_container.py index 7ab60ca2..98fe51b6 100644 --- a/openstack/tests/unit/key_manager/v1/test_container.py +++ b/openstack/tests/unit/key_manager/v1/test_container.py @@ -14,15 +14,17 @@ import testtools from openstack.key_manager.v1 import container -IDENTIFIER = 'http://localhost/containers/IDENTIFIER' +ID_VAL = "123" +IDENTIFIER = 'http://localhost/containers/%s' % ID_VAL EXAMPLE = { 'container_ref': IDENTIFIER, 'created': '2015-03-09T12:14:57.233772', 'name': '3', - 'secret_refs': '4', + 'secret_refs': ['4'], 'status': '5', 'type': '6', 'updated': '2015-03-09T12:15:57.233772', + 'consumers': ['7'] } @@ -35,14 +37,13 @@ class TestContainer(testtools.TestCase): self.assertEqual('/containers', sot.base_path) self.assertEqual('key-manager', sot.service.service_type) self.assertTrue(sot.allow_create) - self.assertTrue(sot.allow_retrieve) + self.assertTrue(sot.allow_get) self.assertTrue(sot.allow_update) self.assertTrue(sot.allow_delete) self.assertTrue(sot.allow_list) def test_make_it(self): - sot = container.Container(EXAMPLE) - self.assertEqual(EXAMPLE['container_ref'], sot.container_ref) + sot = container.Container(**EXAMPLE) self.assertEqual(EXAMPLE['created'], sot.created_at) self.assertEqual(EXAMPLE['name'], sot.name) self.assertEqual(EXAMPLE['secret_refs'], sot.secret_refs) @@ -50,3 +51,6 @@ class TestContainer(testtools.TestCase): self.assertEqual(EXAMPLE['type'], sot.type) self.assertEqual(EXAMPLE['updated'], sot.updated_at) self.assertEqual(EXAMPLE['container_ref'], sot.id) + self.assertEqual(EXAMPLE['container_ref'], sot.container_ref) + self.assertEqual(ID_VAL, sot.container_id) + self.assertEqual(EXAMPLE['consumers'], sot.consumers) diff --git a/openstack/tests/unit/key_manager/v1/test_order.py b/openstack/tests/unit/key_manager/v1/test_order.py index ae967c1e..73250052 100644 --- a/openstack/tests/unit/key_manager/v1/test_order.py +++ b/openstack/tests/unit/key_manager/v1/test_order.py @@ -14,15 +14,20 @@ import testtools from openstack.key_manager.v1 import order -IDENTIFIER = 'IDENTIFIER' +ID_VAL = "123" +SECRET_ID = "5" +IDENTIFIER = 'http://localhost/orders/%s' % ID_VAL EXAMPLE = { - 'error_reason': '1', - 'error_status_code': '2', - 'meta': '3', - 'order_ref': '4', - 'secret_ref': '5', + 'created': '1', + 'creator_id': '2', + 'meta': {'key': '3'}, + 'order_ref': IDENTIFIER, + 'secret_ref': 'http://localhost/secrets/%s' % SECRET_ID, 'status': '6', - 'type': '7', + 'sub_status': '7', + 'sub_status_message': '8', + 'type': '9', + 'updated': '10' } @@ -35,17 +40,22 @@ class TestOrder(testtools.TestCase): self.assertEqual('/orders', sot.base_path) self.assertEqual('key-manager', sot.service.service_type) self.assertTrue(sot.allow_create) - self.assertTrue(sot.allow_retrieve) + self.assertTrue(sot.allow_get) self.assertTrue(sot.allow_update) self.assertTrue(sot.allow_delete) self.assertTrue(sot.allow_list) def test_make_it(self): - sot = order.Order(EXAMPLE) - self.assertEqual(EXAMPLE['error_reason'], sot.error_reason) - self.assertEqual(EXAMPLE['error_status_code'], sot.error_status_code) + sot = order.Order(**EXAMPLE) + self.assertEqual(EXAMPLE['created'], sot.created_at) + self.assertEqual(EXAMPLE['creator_id'], sot.creator_id) self.assertEqual(EXAMPLE['meta'], sot.meta) self.assertEqual(EXAMPLE['order_ref'], sot.order_ref) + self.assertEqual(ID_VAL, sot.order_id) self.assertEqual(EXAMPLE['secret_ref'], sot.secret_ref) + self.assertEqual(SECRET_ID, sot.secret_id) self.assertEqual(EXAMPLE['status'], sot.status) + self.assertEqual(EXAMPLE['sub_status'], sot.sub_status) + self.assertEqual(EXAMPLE['sub_status_message'], sot.sub_status_message) self.assertEqual(EXAMPLE['type'], sot.type) + self.assertEqual(EXAMPLE['updated'], sot.updated_at) diff --git a/openstack/tests/unit/key_manager/v1/test_proxy.py b/openstack/tests/unit/key_manager/v1/test_proxy.py index 97d50724..e3652fb0 100644 --- a/openstack/tests/unit/key_manager/v1/test_proxy.py +++ b/openstack/tests/unit/key_manager/v1/test_proxy.py @@ -14,12 +14,12 @@ from openstack.key_manager.v1 import _proxy from openstack.key_manager.v1 import container from openstack.key_manager.v1 import order from openstack.key_manager.v1 import secret -from openstack.tests.unit import test_proxy_base +from openstack.tests.unit import test_proxy_base2 -class TestKeyManagementProxy(test_proxy_base.TestProxyBase): +class TestKeyManagerProxy(test_proxy_base2.TestProxyBase): def setUp(self): - super(TestKeyManagementProxy, self).setUp() + super(TestKeyManagerProxy, self).setUp() self.proxy = _proxy.Proxy(self.session) def test_server_create_attrs(self): diff --git a/openstack/tests/unit/key_manager/v1/test_secret.py b/openstack/tests/unit/key_manager/v1/test_secret.py index 6b09aa2d..14ff9fa0 100644 --- a/openstack/tests/unit/key_manager/v1/test_secret.py +++ b/openstack/tests/unit/key_manager/v1/test_secret.py @@ -10,21 +10,28 @@ # License for the specific language governing permissions and limitations # under the License. +import mock import testtools from openstack.key_manager.v1 import secret -IDENTIFIER = 'http://localhost:9311/v1/secrets/ID' +ID_VAL = "123" +IDENTIFIER = 'http://localhost:9311/v1/secrets/%s' % ID_VAL EXAMPLE = { 'algorithm': '1', 'bit_length': '2', - 'content_types': '3', + 'content_types': {'default': '3'}, 'expiration': '2017-03-09T12:14:57.233772', 'mode': '5', 'name': '6', 'secret_ref': IDENTIFIER, 'status': '8', - 'updated': '2015-03-09T12:15:57.233772', + 'updated': '2015-03-09T12:15:57.233773', + 'created': '2015-03-09T12:15:57.233774', + 'secret_type': '9', + 'payload': '10', + 'payload_content_type': '11', + 'payload_content_encoding': '12' } @@ -37,13 +44,25 @@ class TestSecret(testtools.TestCase): self.assertEqual('/secrets', sot.base_path) self.assertEqual('key-manager', sot.service.service_type) self.assertTrue(sot.allow_create) - self.assertTrue(sot.allow_retrieve) + self.assertTrue(sot.allow_get) self.assertTrue(sot.allow_update) self.assertTrue(sot.allow_delete) self.assertTrue(sot.allow_list) + self.assertDictEqual({"name": "name", + "mode": "mode", + "bits": "bits", + "secret_type": "secret_type", + "acl_only": "acl_only", + "created": "created", + "updated": "updated", + "expiration": "expiration", + "sort": "sort", + "algorithm": "alg"}, + sot._query_mapping._mapping) + def test_make_it(self): - sot = secret.Secret(EXAMPLE) + sot = secret.Secret(**EXAMPLE) self.assertEqual(EXAMPLE['algorithm'], sot.algorithm) self.assertEqual(EXAMPLE['bit_length'], sot.bit_length) self.assertEqual(EXAMPLE['content_types'], sot.content_types) @@ -51,6 +70,64 @@ class TestSecret(testtools.TestCase): self.assertEqual(EXAMPLE['mode'], sot.mode) self.assertEqual(EXAMPLE['name'], sot.name) self.assertEqual(EXAMPLE['secret_ref'], sot.secret_ref) + self.assertEqual(EXAMPLE['secret_ref'], sot.id) + self.assertEqual(ID_VAL, sot.secret_id) self.assertEqual(EXAMPLE['status'], sot.status) self.assertEqual(EXAMPLE['updated'], sot.updated_at) - self.assertEqual(EXAMPLE['secret_ref'], sot.id) + self.assertEqual(EXAMPLE['secret_type'], sot.secret_type) + self.assertEqual(EXAMPLE['payload'], sot.payload) + self.assertEqual(EXAMPLE['payload_content_type'], + sot.payload_content_type) + self.assertEqual(EXAMPLE['payload_content_encoding'], + sot.payload_content_encoding) + + def test_get_no_payload(self): + sot = secret.Secret(id="id") + + sess = mock.Mock() + rv = mock.Mock() + return_body = {"status": "cool"} + rv.json = mock.Mock(return_value=return_body) + sess.get = mock.Mock(return_value=rv) + + sot.get(sess) + + sess.get.assert_called_once_with("secrets/id", + endpoint_filter=sot.service) + + def _test_payload(self, sot, metadata, content_type): + content_type = "some/type" + sot = secret.Secret(id="id", payload_content_type=content_type) + + metadata_response = mock.Mock() + metadata_response.json = mock.Mock(return_value=metadata) + + payload_response = mock.Mock() + payload = "secret info" + payload_response.text = payload + + sess = mock.Mock() + sess.get = mock.Mock(side_effect=[metadata_response, payload_response]) + + rv = sot.get(sess) + + sess.get.assert_has_calls( + [mock.call("secrets/id", endpoint_filter=sot.service), + mock.call("secrets/id/payload", endpoint_filter=sot.service, + headers={"Accept": content_type})]) + + self.assertEqual(rv.payload, payload) + self.assertEqual(rv.status, metadata["status"]) + + def test_get_with_payload_from_argument(self): + metadata = {"status": "great"} + content_type = "some/type" + sot = secret.Secret(id="id", payload_content_type=content_type) + self._test_payload(sot, metadata, content_type) + + def test_get_with_payload_from_content_types(self): + content_type = "some/type" + metadata = {"status": "fine", + "content_types": {"default": content_type}} + sot = secret.Secret(id="id") + self._test_payload(sot, metadata, content_type)