diff --git a/doc/source/user/proxies/key_manager.rst b/doc/source/user/proxies/key_manager.rst index 2b611e9dc..f5d7309ac 100644 --- a/doc/source/user/proxies/key_manager.rst +++ b/doc/source/user/proxies/key_manager.rst @@ -37,3 +37,11 @@ Order Operations :noindex: :members: create_order, update_order, delete_order, get_order, find_order, orders + +Secret Store Operations +^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: openstack.key_manager.v1._proxy.Proxy + :noindex: + :members: secret_stores, get_global_default_secret_store, + get_preferred_secret_store diff --git a/doc/source/user/resources/key_manager/index.rst b/doc/source/user/resources/key_manager/index.rst index 76b6659c6..5f881b458 100644 --- a/doc/source/user/resources/key_manager/index.rst +++ b/doc/source/user/resources/key_manager/index.rst @@ -7,3 +7,4 @@ KeyManager Resources v1/container v1/order v1/secret + v1/secret_store diff --git a/doc/source/user/resources/key_manager/v1/secret_store.rst b/doc/source/user/resources/key_manager/v1/secret_store.rst new file mode 100644 index 000000000..f40902b50 --- /dev/null +++ b/doc/source/user/resources/key_manager/v1/secret_store.rst @@ -0,0 +1,12 @@ +openstack.key_manager.v1.secret_store +===================================== + +.. automodule:: openstack.key_manager.v1.secret_store + +The SecretStore Class +--------------------- + +The ``SecretStore`` class inherits from :class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.key_manager.v1.secret_store.SecretStore + :members: diff --git a/openstack/key_manager/v1/_proxy.py b/openstack/key_manager/v1/_proxy.py index 05f3f347a..c7e1addea 100644 --- a/openstack/key_manager/v1/_proxy.py +++ b/openstack/key_manager/v1/_proxy.py @@ -15,6 +15,7 @@ import typing as ty 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.key_manager.v1 import secret_store as _secret_store from openstack import proxy from openstack import resource @@ -24,6 +25,7 @@ class Proxy(proxy.Proxy): "container": _container.Container, "order": _order.Order, "secret": _secret.Secret, + "secret_store": _secret_store.SecretStore, } def create_container(self, **attrs): @@ -270,6 +272,47 @@ class Proxy(proxy.Proxy): """ return self._update(_secret.Secret, secret, **attrs) + # ========== Secret Store Operations ========== + + def secret_stores(self, **query): + """Return a generator of secret stores + + :param kwargs query: Optional query parameters to be sent to limit + the resources being returned. + + :returns: A generator of secret store objects + :rtype: :class:`~openstack.key_manager.v1.secret_store.SecretStore` + """ + return self._list(_secret_store.SecretStore, **query) + + def get_global_default_secret_store(self): + """Get the global default secret store + + :returns: One :class:`~openstack.key_manager.v1.secret_store.SecretStore` + :raises: :class:`~openstack.exceptions.NotFoundException` + when no resource can be found. + """ + return self._get( + _secret_store.SecretStore, + None, + requires_id=False, + base_path='/secret-stores/global-default', + ) + + def get_preferred_secret_store(self): + """Get the preferred secret store for the current project + + :returns: One :class:`~openstack.key_manager.v1.secret_store.SecretStore` + :raises: :class:`~openstack.exceptions.NotFoundException` + when no resource can be found. + """ + return self._get( + _secret_store.SecretStore, + None, + requires_id=False, + base_path='/secret-stores/preferred', + ) + # ========== Utilities ========== def wait_for_status( diff --git a/openstack/key_manager/v1/secret_store.py b/openstack/key_manager/v1/secret_store.py new file mode 100644 index 000000000..f6798d96c --- /dev/null +++ b/openstack/key_manager/v1/secret_store.py @@ -0,0 +1,58 @@ +# 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.key_manager.v1 import _format +from openstack import resource + + +class SecretStore(resource.Resource): + resources_key = 'secret_stores' + base_path = '/secret-stores' + + # capabilities + allow_create = False + allow_fetch = True + allow_commit = False + allow_delete = False + allow_list = True + + _query_mapping = resource.QueryParameters( + "name", + "status", + "global_default", + "crypto_plugin", + "secret_store_plugin", + "created", + "updated", + ) + + # Properties + #: The name of the secret store + name = resource.Body('name') + #: The status of the secret store + status = resource.Body('status') + #: Timestamp of when the secret store was created + created_at = resource.Body('created') + #: Timestamp of when the secret store was last updated + updated_at = resource.Body('updated') + #: A URI to the secret store + secret_store_ref = resource.Body('secret_store_ref') + #: The ID of the secret store + secret_store_id = resource.Body( + 'secret_store_ref', alternate_id=True, type=_format.HREFToUUID + ) + #: Flag indicating if this secret store is global default + global_default = resource.Body('global_default', type=bool) + #: The crypto plugin name + crypto_plugin = resource.Body('crypto_plugin') + #: The secret store plugin name + secret_store_plugin = resource.Body('secret_store_plugin') diff --git a/openstack/tests/functional/key_manager/__init__.py b/openstack/tests/functional/key_manager/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openstack/tests/functional/key_manager/v1/__init__.py b/openstack/tests/functional/key_manager/v1/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openstack/tests/functional/key_manager/v1/test_secret_store.py b/openstack/tests/functional/key_manager/v1/test_secret_store.py new file mode 100644 index 000000000..8c1b5b0f8 --- /dev/null +++ b/openstack/tests/functional/key_manager/v1/test_secret_store.py @@ -0,0 +1,55 @@ +# 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.key_manager.v1 import secret_store as _secret_store +from openstack.tests.functional import base + + +class TestSecretStore(base.BaseFunctionalTest): + def setUp(self): + super().setUp() + self.require_service('key-manager') + + def test_secret_store(self): + """Test Secret Store operations""" + key_manager = self.operator_cloud.key_manager + + # Test list secret stores + secret_stores = list(key_manager.secret_stores()) + self.assertIsInstance(secret_stores, list) + + for store in secret_stores: + self.assertIsInstance(store, _secret_store.SecretStore) + self.assertIsNotNone(store.name) + self.assertIsNotNone(store.status) + + # Test list secret stores with filters + global_default_stores = list( + key_manager.secret_stores(global_default=True) + ) + self.assertIsInstance(global_default_stores, list) + + active_stores = list(key_manager.secret_stores(status="ACTIVE")) + self.assertIsInstance(active_stores, list) + + # Test get global default secret store + if global_default_stores: + default_store = key_manager.get_global_default_secret_store() + self.assertIsInstance(default_store, _secret_store.SecretStore) + self.assertIsNotNone(default_store.name) + self.assertTrue(default_store.global_default) + + # Test get preferred secret store + if secret_stores: + preferred_store = key_manager.get_preferred_secret_store() + self.assertIsInstance(preferred_store, _secret_store.SecretStore) + self.assertIsNotNone(preferred_store.name) diff --git a/openstack/tests/unit/key_manager/v1/test_proxy.py b/openstack/tests/unit/key_manager/v1/test_proxy.py index f770f11d6..4ff6ceb44 100644 --- a/openstack/tests/unit/key_manager/v1/test_proxy.py +++ b/openstack/tests/unit/key_manager/v1/test_proxy.py @@ -14,6 +14,7 @@ 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.key_manager.v1 import secret_store from openstack.tests.unit import test_proxy_base @@ -97,3 +98,8 @@ class TestKeyManagerSecret(TestKeyManagerProxy): def test_secret_update(self): self.verify_update(self.proxy.update_secret, secret.Secret) + + +class TestKeyManagerSecretStore(TestKeyManagerProxy): + def test_secret_stores(self): + self.verify_list(self.proxy.secret_stores, secret_store.SecretStore) diff --git a/openstack/tests/unit/key_manager/v1/test_secret_store.py b/openstack/tests/unit/key_manager/v1/test_secret_store.py new file mode 100644 index 000000000..08fee411d --- /dev/null +++ b/openstack/tests/unit/key_manager/v1/test_secret_store.py @@ -0,0 +1,55 @@ +# 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.key_manager.v1 import secret_store +from openstack.tests.unit import base + + +EXAMPLE = { + "status": "ACTIVE", + "updated": "2016-08-22T23:46:45.114283", + "name": "PKCS11 HSM", + "created": "2016-08-22T23:46:45.114283", + "secret_store_ref": "http://localhost:9311/v1/secret-stores/4d27b7a7-b82f-491d-88c0-746bd67dadc8", + "global_default": True, + "crypto_plugin": "p11_crypto", + "secret_store_plugin": "store_crypto", +} + + +class TestSecretStore(base.TestCase): + def test_basic(self): + sot = secret_store.SecretStore() + self.assertEqual('secret_stores', sot.resources_key) + self.assertEqual('/secret-stores', sot.base_path) + self.assertFalse(sot.allow_create) + self.assertTrue(sot.allow_fetch) + self.assertFalse(sot.allow_commit) + self.assertFalse(sot.allow_delete) + self.assertTrue(sot.allow_list) + + def test_make_it(self): + sot = secret_store.SecretStore(**EXAMPLE) + self.assertEqual(EXAMPLE['name'], sot.name) + self.assertEqual(EXAMPLE['status'], sot.status) + self.assertEqual(EXAMPLE['created'], sot.created_at) + self.assertEqual(EXAMPLE['updated'], sot.updated_at) + self.assertEqual(EXAMPLE['secret_store_ref'], sot.secret_store_ref) + self.assertEqual(EXAMPLE['global_default'], sot.global_default) + self.assertEqual(EXAMPLE['crypto_plugin'], sot.crypto_plugin) + self.assertEqual( + EXAMPLE['secret_store_plugin'], sot.secret_store_plugin + ) + # Test the alternate_id extraction + self.assertEqual( + '4d27b7a7-b82f-491d-88c0-746bd67dadc8', sot.secret_store_id + )