Merge "Add Secret Stores API support to Key Manager"

This commit is contained in:
Zuul
2025-09-15 13:28:24 +00:00
committed by Gerrit Code Review
10 changed files with 238 additions and 0 deletions

View File

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

View File

@@ -7,3 +7,4 @@ KeyManager Resources
v1/container
v1/order
v1/secret
v1/secret_store

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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