Merge "Refactor Key Manager for resource2"

This commit is contained in:
Jenkins 2016-08-24 23:13:51 +00:00 committed by Gerrit Code Review
commit a4ac7f437a
14 changed files with 399 additions and 67 deletions

View File

@ -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

View File

View File

@ -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")

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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)