Added secret consumers list functionality. Unit, smoke
and functional tests were also added. Change-Id: I093475833cdc6d68ff2d4735a0d4a8d0eb143a53
This commit is contained in:
parent
7f6b3cf790
commit
da03fc5cf0
|
@ -252,3 +252,27 @@ class DeleteConsumer(command.Command):
|
|||
args.service_type_name,
|
||||
args.resource_type,
|
||||
args.resource_id)
|
||||
|
||||
|
||||
class ListConsumer(lister.Lister):
|
||||
"""List consumers of a secret."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListConsumer, self).get_parser(prog_name)
|
||||
parser.add_argument('URI', help='The URI reference for the secret')
|
||||
parser.add_argument('--limit', '-l', default=10,
|
||||
help='specify the limit to the number of items '
|
||||
'to list per page (default: %(default)s; '
|
||||
'maximum: 100)',
|
||||
type=int)
|
||||
parser.add_argument('--offset', '-o', default=0,
|
||||
help='specify the page offset '
|
||||
'(default: %(default)s)',
|
||||
type=int)
|
||||
return parser
|
||||
|
||||
def take_action(self, args):
|
||||
obj_list = self.app.client_manager.key_manager.secrets.list_consumers(
|
||||
secret_ref=args.URI, limit=args.limit, offset=args.offset)
|
||||
|
||||
return secrets.SecretConsumers._list_objects(obj_list)
|
||||
|
|
|
@ -127,3 +127,35 @@ class WhenTestingConsumers(test_client.BaseEntityResource):
|
|||
|
||||
def test_should_delete_from_manager_without_consumers_and_force(self):
|
||||
self._delete_from_manager(self.entity_href, force=True)
|
||||
|
||||
def _list_consumers(self, secret_ref, consumers=[]):
|
||||
mock_get_secret_for_client(self.client, consumers)
|
||||
return self.manager.list_consumers(secret_ref)
|
||||
|
||||
def test_list_consumers_from_secret_without_consumers(self):
|
||||
consumer_list = self._list_consumers(self.entity_href)
|
||||
self.assertTrue(len(consumer_list) == 0)
|
||||
|
||||
def test_list_consumers_from_secret_with_consumers(self):
|
||||
consumers = [{'service': 'service_test1',
|
||||
'resource_type': 'type_test1',
|
||||
'resource_id': 'id_test1'},
|
||||
{'service': 'service_test2',
|
||||
'resource_type': 'type_test2',
|
||||
'resource_id': 'id_test2'}]
|
||||
consumer_list = self._list_consumers(self.entity_href, consumers)
|
||||
|
||||
for elem in range(len(consumers)):
|
||||
self.assertTrue(
|
||||
consumer_list[elem].service ==
|
||||
consumers[elem]['service'])
|
||||
self.assertTrue(
|
||||
consumer_list[elem].resource_type ==
|
||||
consumers[elem]['resource_type'])
|
||||
self.assertTrue(
|
||||
consumer_list[elem].resource_id ==
|
||||
consumers[elem]['resource_id'])
|
||||
|
||||
def test_should_fail_list_consumers_invalid_secret(self):
|
||||
self.assertRaises(ValueError, self.manager.list_consumers,
|
||||
**{'secret_ref': '12345'})
|
||||
|
|
|
@ -44,6 +44,49 @@ def immutable_after_save(func):
|
|||
return wrapper
|
||||
|
||||
|
||||
class SecretConsumersFormatter(formatter.EntityFormatter):
|
||||
|
||||
columns = ("Service",
|
||||
"Resource type",
|
||||
"Resource id",
|
||||
"Created"
|
||||
)
|
||||
|
||||
def _get_formatted_data(self):
|
||||
data = (self.service,
|
||||
self.resource_type,
|
||||
self.resource_id,
|
||||
self.created
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
class SecretConsumers(SecretConsumersFormatter):
|
||||
"""Secrets consumers managed by Barbican
|
||||
|
||||
Secrets might or might not have consumers.
|
||||
"""
|
||||
|
||||
def __init__(self, secret_ref, service, resource_type, resource_id,
|
||||
created=None, updated=None, status=None):
|
||||
|
||||
self.secret_ref = secret_ref
|
||||
self.service = service
|
||||
self.resource_type = resource_type
|
||||
self.resource_id = resource_id
|
||||
self.created = created
|
||||
self.updated = updated
|
||||
self.status = status
|
||||
|
||||
def __repr__(self):
|
||||
return ('SecretConsumers(secret_ref="{0}", service="{1}", '
|
||||
'resource_type="{2}", resource_id="{3}", '
|
||||
'created="{4}", updated="{5}", status="{6}")'
|
||||
.format(self.secret_ref, self.service,
|
||||
self.resource_type, self.resource_id,
|
||||
self.created, self.updated, self.status))
|
||||
|
||||
|
||||
class SecretFormatter(formatter.EntityFormatter):
|
||||
|
||||
columns = ("Secret href",
|
||||
|
@ -689,3 +732,27 @@ class SecretManager(base.BaseEntityManager):
|
|||
}
|
||||
|
||||
self._api.delete(href, json=consumer_dict)
|
||||
|
||||
def list_consumers(self, secret_ref, limit=10, offset=0):
|
||||
"""List consumers of the secret
|
||||
|
||||
:param secret_ref: Full HATEOAS reference to a secret, or a UUID
|
||||
:param limit: Max number of consumers returned
|
||||
:param offset: Offset secrets to begin list
|
||||
:raises barbicanclient.exceptions.HTTPAuthError: 401 Responses
|
||||
:raises barbicanclient.exceptions.HTTPClientError: 4xx Responses
|
||||
:raises barbicanclient.exceptions.HTTPServerError: 5xx Responses
|
||||
"""
|
||||
LOG.debug('Listing consumers of secret {0}'.format(secret_ref))
|
||||
self._enforce_microversion()
|
||||
secret_uuid = base.validate_ref_and_return_uuid(
|
||||
secret_ref, 'secret')
|
||||
href = '{0}/{1}/consumers'.format(self._entity, secret_uuid)
|
||||
|
||||
params = {'limit': limit, 'offset': offset}
|
||||
response = self._api.get(href, params=params)
|
||||
|
||||
return [
|
||||
SecretConsumers(secret_ref=secret_ref, **s)
|
||||
for s in response.get('consumers', [])
|
||||
]
|
||||
|
|
|
@ -46,3 +46,17 @@ class ConsumerBehaviors(base_behaviors.BaseBehaviors):
|
|||
argv.extend([secret_href])
|
||||
|
||||
stdout, stderr = self.issue_barbican_command(argv)
|
||||
|
||||
def list_consumers(self, secret_href):
|
||||
argv = ['secret', 'consumer', 'list']
|
||||
self.add_auth_and_endpoint(argv)
|
||||
|
||||
argv.extend([secret_href])
|
||||
|
||||
stdout, stderr = self.issue_barbican_command(argv)
|
||||
|
||||
if len(stderr) > 0 or stdout == '\n':
|
||||
return []
|
||||
else:
|
||||
consumers = self._prettytable_to_list(stdout)
|
||||
return consumers
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
# Copyright 2022 Red Hat Inc.
|
||||
#
|
||||
# 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 functionaltests.cli.base import CmdLineTestCase
|
||||
from functionaltests.cli.v1.behaviors.consumer_behaviors import (
|
||||
ConsumerBehaviors)
|
||||
from functionaltests.cli.v1.behaviors.secret_behaviors import SecretBehaviors
|
||||
from functionaltests import utils
|
||||
from testtools import testcase
|
||||
|
||||
|
||||
@utils.parameterized_test_case
|
||||
class ConsumerTestCase(CmdLineTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ConsumerTestCase, self).setUp()
|
||||
self.consumer_behaviors = ConsumerBehaviors()
|
||||
self.secret_behaviors = SecretBehaviors()
|
||||
|
||||
def tearDown(self):
|
||||
super(ConsumerTestCase, self).tearDown()
|
||||
self.secret_behaviors.delete_all_created_secrets()
|
||||
|
||||
def _create_secret(self):
|
||||
secret_href = self.secret_behaviors.store_secret()
|
||||
secret = self.secret_behaviors.get_secret(secret_href)
|
||||
return secret['Secret href']
|
||||
|
||||
def _create_secret_with_consumer(self, consumer):
|
||||
secret_href = self._create_secret()
|
||||
self.consumer_behaviors.register_consumer(
|
||||
secret_href, consumer["service"], consumer["resource_type"],
|
||||
consumer["resource_id"])
|
||||
return secret_href
|
||||
|
||||
def _register_consumer(self, secret_href, consumer):
|
||||
self.consumer_behaviors.register_consumer(
|
||||
secret_href, consumer["service"], consumer["resource_type"],
|
||||
consumer["resource_id"])
|
||||
|
||||
@testcase.attr('positive')
|
||||
def test_register_consumer_on_empty_secret(self):
|
||||
consumer = {
|
||||
'service': 'service', 'resource_type': 'type',
|
||||
'resource_id': 'id', 'created': 'created'
|
||||
}
|
||||
secret_href = self._create_secret_with_consumer(consumer)
|
||||
|
||||
secret = self.consumer_behaviors.list_consumers(secret_href)
|
||||
# Because "created" is non-deterministic, we need to assign
|
||||
# its value before running the loop below.
|
||||
secret[0]['Created'] = consumer['created']
|
||||
# The CLI's output is slighted different in terms of headers.
|
||||
# So, we have to rename their keys to the consumer dictionary's keys.
|
||||
for (k1, v1), (k2, v2) in zip(list(secret[0].items()),
|
||||
consumer.items()):
|
||||
secret[0][k2] = secret[0].pop(k1)
|
||||
self.assertDictEqual(consumer, secret[0])
|
||||
|
||||
@testcase.attr('positive')
|
||||
def test_register_duplicated_service_name_consumer(self):
|
||||
consumer = {
|
||||
'service': 'service', 'resource_type': 'type', 'resource_id': 'id'
|
||||
}
|
||||
secret_href = self._create_secret_with_consumer(consumer)
|
||||
second_consumer = {
|
||||
'service': 'service', 'resource_type': 'type2',
|
||||
'resource_id': 'id2'
|
||||
}
|
||||
self._register_consumer(secret_href, second_consumer)
|
||||
self._register_consumer(secret_href, second_consumer)
|
||||
consumers_list = self.consumer_behaviors.list_consumers(secret_href)
|
||||
self.assertEqual(2, len(consumers_list))
|
||||
|
||||
@testcase.attr('positive')
|
||||
def test_register_duplicated_resource_type_consumer(self):
|
||||
consumer = {
|
||||
'service': 'service', 'resource_type': 'type',
|
||||
'resource_id': 'id'
|
||||
}
|
||||
secret_href = self._create_secret_with_consumer(consumer)
|
||||
second_consumer = {
|
||||
'service': 'service2', 'resource_type': 'type',
|
||||
'resource_id': 'id2'
|
||||
}
|
||||
self._register_consumer(secret_href, second_consumer)
|
||||
consumers_list = self.consumer_behaviors.list_consumers(secret_href)
|
||||
self.assertEqual(2, len(consumers_list))
|
||||
|
||||
@testcase.attr('positive')
|
||||
def test_register_duplicated_resource_id_consumer(self):
|
||||
consumer = {
|
||||
'service': 'service', 'resource_type': 'type', 'resource_id': 'id'
|
||||
}
|
||||
secret_href = self._create_secret_with_consumer(consumer)
|
||||
second_consumer = {
|
||||
'service': 'service2', 'resource_type': 'type2',
|
||||
'resource_id': 'id'
|
||||
}
|
||||
self._register_consumer(secret_href, second_consumer)
|
||||
consumers_list = self.consumer_behaviors.list_consumers(secret_href)
|
||||
self.assertEqual(2, len(consumers_list))
|
||||
|
||||
@testcase.attr('positive')
|
||||
def test_remove_consumer(self):
|
||||
consumer = {
|
||||
'service': 'service', 'resource_type': 'type',
|
||||
'resource_id': 'id'
|
||||
}
|
||||
secret_href = self._create_secret_with_consumer(consumer)
|
||||
|
||||
self.consumer_behaviors.remove_consumer(
|
||||
secret_href, consumer["service"], consumer["resource_type"],
|
||||
consumer["resource_id"])
|
||||
|
||||
consumers = self.consumer_behaviors.list_consumers(secret_href)
|
||||
self.assertEqual(0, len(consumers))
|
||||
|
||||
@testcase.attr('positive')
|
||||
def test_list_consumer_secret_with_multiple_consumers(self):
|
||||
first_consumer = {
|
||||
'service': 'service1', 'resource_type': 'type1',
|
||||
'resource_id': 'id1'}
|
||||
secret_href = self._create_secret_with_consumer(first_consumer)
|
||||
|
||||
second_consumer = {
|
||||
'service': 'service2', 'resource_type': 'type2',
|
||||
'resource_id': 'id2'}
|
||||
self._register_consumer(secret_href, second_consumer)
|
||||
|
||||
consumers = self.consumer_behaviors.list_consumers(secret_href)
|
||||
self.assertEqual(2, len(consumers))
|
|
@ -69,14 +69,20 @@ class SecretsTestCase(base.TestCase):
|
|||
self.cleanup.delete_all_entities()
|
||||
super(SecretsTestCase, self).tearDown()
|
||||
|
||||
def _create_test_secret(self):
|
||||
"""Helper module to create a secret withouth consumers"""
|
||||
new_secret = self.barbicanclient.secrets.create(
|
||||
**secret_create_defaults_data)
|
||||
|
||||
secret_ref = self.cleanup.add_entity(new_secret)
|
||||
self.assertIsNotNone(secret_ref)
|
||||
|
||||
return secret_ref
|
||||
|
||||
@testcase.attr('positive')
|
||||
def test_secret_create_defaults_check_content_types(self):
|
||||
"""Check that set content-type attribute is retained in metadata."""
|
||||
secret = self.barbicanclient.secrets.create(
|
||||
**secret_create_defaults_data)
|
||||
|
||||
secret_ref = self.cleanup.add_entity(secret)
|
||||
self.assertIsNotNone(secret_ref)
|
||||
secret_ref = self._create_test_secret()
|
||||
|
||||
resp = self.barbicanclient.secrets.get(secret_ref)
|
||||
content_types = resp.content_types
|
||||
|
@ -106,11 +112,7 @@ class SecretsTestCase(base.TestCase):
|
|||
|
||||
By default, 'read' ACL settings are there for a secret.
|
||||
"""
|
||||
test_model = self.barbicanclient.secrets.create(
|
||||
**secret_create_defaults_data)
|
||||
|
||||
secret_ref = self.cleanup.add_entity(test_model)
|
||||
self.assertIsNotNone(secret_ref)
|
||||
secret_ref = self._create_test_secret()
|
||||
|
||||
secret_entity = self.barbicanclient.secrets.get(secret_ref)
|
||||
self.assertIsNotNone(secret_entity.acls)
|
||||
|
@ -179,11 +181,7 @@ class SecretsTestCase(base.TestCase):
|
|||
in the register_consumers list, then remove each consumer
|
||||
in the remove_consumers list.
|
||||
"""
|
||||
new_secret = self.barbicanclient.secrets.create(
|
||||
**secret_create_defaults_data)
|
||||
|
||||
secret_ref = self.cleanup.add_entity(new_secret)
|
||||
self.assertIsNotNone(secret_ref)
|
||||
secret_ref = self._create_test_secret()
|
||||
|
||||
for consumer in register_consumers:
|
||||
secret = self.barbicanclient.secrets.register_consumer(
|
||||
|
@ -231,11 +229,7 @@ class SecretsTestCase(base.TestCase):
|
|||
providing all of the required positional arguments
|
||||
(service, resource_type, resource_id).
|
||||
"""
|
||||
new_secret = self.barbicanclient.secrets.create(
|
||||
**secret_create_defaults_data)
|
||||
|
||||
secret_ref = self.cleanup.add_entity(new_secret)
|
||||
self.assertIsNotNone(secret_ref)
|
||||
secret_ref = self._create_test_secret()
|
||||
|
||||
for consumer in register_consumers:
|
||||
e = self.assertRaises(
|
||||
|
@ -267,11 +261,7 @@ class SecretsTestCase(base.TestCase):
|
|||
providing all of the required positional arguments
|
||||
(service, resource_type, resource_id).
|
||||
"""
|
||||
new_secret = self.barbicanclient.secrets.create(
|
||||
**secret_create_defaults_data)
|
||||
|
||||
secret_ref = self.cleanup.add_entity(new_secret)
|
||||
self.assertIsNotNone(secret_ref)
|
||||
secret_ref = self._create_test_secret()
|
||||
|
||||
secret = self.barbicanclient.secrets.register_consumer(
|
||||
secret_ref,
|
||||
|
@ -290,10 +280,7 @@ class SecretsTestCase(base.TestCase):
|
|||
|
||||
@testcase.attr('positive')
|
||||
def test_secret_delete_without_consumers_no_force(self):
|
||||
new_secret = self.barbicanclient.secrets.create(
|
||||
**secret_create_defaults_data)
|
||||
|
||||
secret_ref = self.cleanup.add_entity(new_secret)
|
||||
secret_ref = self._create_test_secret()
|
||||
|
||||
self.barbicanclient.secrets.delete(secret_ref, force=False)
|
||||
resp = self.barbicanclient.secrets.get(secret_ref)
|
||||
|
@ -302,10 +289,7 @@ class SecretsTestCase(base.TestCase):
|
|||
|
||||
@testcase.attr('positive')
|
||||
def test_secret_delete_without_consumers_with_force(self):
|
||||
new_secret = self.barbicanclient.secrets.create(
|
||||
**secret_create_defaults_data)
|
||||
|
||||
secret_ref = self.cleanup.add_entity(new_secret)
|
||||
secret_ref = self._create_test_secret()
|
||||
|
||||
self.barbicanclient.secrets.delete(secret_ref, force=True)
|
||||
resp = self.barbicanclient.secrets.get(secret_ref)
|
||||
|
@ -319,11 +303,7 @@ class SecretsTestCase(base.TestCase):
|
|||
Tries to delete a secret with consumers, but
|
||||
without providing the 'force' parameter.
|
||||
"""
|
||||
new_secret = self.barbicanclient.secrets.create(
|
||||
**secret_create_defaults_data)
|
||||
|
||||
secret_ref = self.cleanup.add_entity(new_secret)
|
||||
self.assertIsNotNone(secret_ref)
|
||||
secret_ref = self._create_test_secret()
|
||||
|
||||
secret = self.barbicanclient.secrets.register_consumer(
|
||||
secret_ref,
|
||||
|
@ -346,11 +326,7 @@ class SecretsTestCase(base.TestCase):
|
|||
Tries to delete a secret with consumers,
|
||||
making the 'force' parameter equals True.
|
||||
"""
|
||||
new_secret = self.barbicanclient.secrets.create(
|
||||
**secret_create_defaults_data)
|
||||
|
||||
secret_ref = self.cleanup.add_entity(new_secret)
|
||||
self.assertIsNotNone(secret_ref)
|
||||
secret_ref = self._create_test_secret()
|
||||
|
||||
secret = self.barbicanclient.secrets.register_consumer(
|
||||
secret_ref,
|
||||
|
@ -365,6 +341,83 @@ class SecretsTestCase(base.TestCase):
|
|||
self.assertRaises(exceptions.HTTPClientError, getattr, resp, "name")
|
||||
self.cleanup.delete_entity(secret_ref)
|
||||
|
||||
@testcase.attr('positive')
|
||||
def test_consumers_list_secret_without_consumers(self):
|
||||
"""Lists consumers from a secret without consumers"""
|
||||
secret_ref = self._create_test_secret()
|
||||
|
||||
consumers_list = self.barbicanclient.secrets.list_consumers(
|
||||
secret_ref)
|
||||
self.assertTrue(len(consumers_list) == 0)
|
||||
|
||||
self.cleanup.delete_entity(secret_ref)
|
||||
self.barbicanclient.secrets.delete(secret_ref, True)
|
||||
|
||||
@testcase.attr('positive')
|
||||
def test_consumers_list_secret_with_consumers(self):
|
||||
"""Lists consumers from a secret with consumers"""
|
||||
secret_ref = self._create_test_secret()
|
||||
|
||||
consumers = [{
|
||||
'service': 'service1',
|
||||
'resource_type': 'type1',
|
||||
'resource_id': 'id1'}, {
|
||||
'service': 'service2',
|
||||
'resource_type': 'type2',
|
||||
'resource_id': 'id2'}]
|
||||
|
||||
for consumer in consumers:
|
||||
_ = self.barbicanclient.secrets.register_consumer(
|
||||
secret_ref,
|
||||
service=consumer['service'],
|
||||
resource_type=consumer['resource_type'],
|
||||
resource_id=consumer['resource_id']
|
||||
)
|
||||
|
||||
consumers_list = self.barbicanclient.secrets.list_consumers(
|
||||
secret_ref)
|
||||
|
||||
for elem in range(len(consumers)):
|
||||
self.assertTrue(
|
||||
consumers_list[elem].service ==
|
||||
consumers[elem]['service'])
|
||||
self.assertTrue(
|
||||
consumers_list[elem].resource_type ==
|
||||
consumers[elem]['resource_type'])
|
||||
self.assertTrue(
|
||||
consumers_list[elem].resource_id ==
|
||||
consumers[elem]['resource_id'])
|
||||
|
||||
self.cleanup.delete_entity(secret_ref)
|
||||
self.barbicanclient.secrets.delete(secret_ref, True)
|
||||
|
||||
@testcase.attr('negative')
|
||||
def test_consumers_list_secret_doesnt_exist(self):
|
||||
"""Tries to list consumers from a non-existent secret"""
|
||||
e = self.assertRaises(exceptions.HTTPClientError,
|
||||
self.barbicanclient.secrets.list_consumers,
|
||||
'9999999f-f99f-49f9-9fff-f99f999ff9ff')
|
||||
|
||||
self.assertIn("Secret not found", str(e))
|
||||
|
||||
@testcase.attr('negative')
|
||||
def test_consumers_list_secret_invalid_uuid(self):
|
||||
"""Tries to list consumers providing an invalid secret UUID"""
|
||||
e = self.assertRaises(exceptions.HTTPClientError,
|
||||
self.barbicanclient.secrets.list_consumers,
|
||||
'9999999f-ffff-ffff-9fff-f99f999ff9ff')
|
||||
|
||||
self.assertIn("Provided secret id is invalid.", str(e))
|
||||
|
||||
@testcase.attr('negative')
|
||||
def test_consumers_list_invalid_secret(self):
|
||||
"""Tries to list consumers providing an invalid secret"""
|
||||
e = self.assertRaises(ValueError,
|
||||
self.barbicanclient.secrets.list_consumers,
|
||||
'abcde')
|
||||
|
||||
self.assertIn("secret incorrectly specified.", str(e))
|
||||
|
||||
@testcase.attr('negative')
|
||||
def test_secret_delete_doesnt_exist(self):
|
||||
"""Deletes a non-existent secret.
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
features: >
|
||||
The Barbican API has been extended to allow secrets to have one or
|
||||
more consumers. This extension has been documented here:
|
||||
https://docs.openstack.org/barbican/latest/api/reference/secret_consumers.html
|
||||
|
||||
This functionality has now been exposed in the barbican client.
|
||||
Users may add, remove or delete consumers by calling new mechods on the
|
||||
SecretManager. In addition, new CLI options have been provided to add, remove
|
||||
and list consumers. See the documentation for details.
|
|
@ -51,6 +51,7 @@ openstack.key_manager.v1 =
|
|||
|
||||
secret_consumer_create = barbicanclient.barbican_cli.v1.secrets:CreateConsumer
|
||||
secret_consumer_delete = barbicanclient.barbican_cli.v1.secrets:DeleteConsumer
|
||||
secret_consumer_list = barbicanclient.barbican_cli.v1.secrets:ListConsumer
|
||||
|
||||
ca_get = barbicanclient.barbican_cli.v1.cas:GetCA
|
||||
ca_list = barbicanclient.barbican_cli.v1.cas:ListCA
|
||||
|
|
Loading…
Reference in New Issue