Merge "Add allowlist and denylist for action providers and actions"
This commit is contained in:
commit
ffef2f4a79
@ -99,7 +99,17 @@ class LegacyActionProvider(ml_actions.ActionProvider):
|
||||
invoke_on_load=False
|
||||
)
|
||||
|
||||
allowlist = CONF.legacy_action_provider.allowlist
|
||||
denylist = CONF.legacy_action_provider.denylist
|
||||
|
||||
for action_name in ext_mgr.names():
|
||||
if allowlist:
|
||||
if action_name not in allowlist:
|
||||
continue
|
||||
elif denylist:
|
||||
if action_name in denylist:
|
||||
continue
|
||||
|
||||
action_cls = ext_mgr[action_name].plugin
|
||||
|
||||
if CONF.legacy_action_provider.only_builtin_actions:
|
||||
|
@ -64,6 +64,29 @@ auth_type_opt = cfg.StrOpt(
|
||||
help=_('Authentication type (valid options: keystone, keycloak-oidc)')
|
||||
)
|
||||
|
||||
action_providers_opts = [
|
||||
cfg.ListOpt(
|
||||
'allowlist',
|
||||
default=[],
|
||||
help=_(
|
||||
'Allowlist with action providers that is allowed to be '
|
||||
'loaded from the entry point "mistral.action.providers", '
|
||||
'if empty all action providers will be allowed unless '
|
||||
'denylist is set.'
|
||||
),
|
||||
),
|
||||
cfg.ListOpt(
|
||||
'denylist',
|
||||
default=[],
|
||||
help=_(
|
||||
'Denylist with action providers that is not allowed to '
|
||||
'be loaded from the entry point "mistral.action.providers", '
|
||||
'allowlist takes precendence, if empty all action providers '
|
||||
'will be allowed.'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
legacy_action_provider_opts = [
|
||||
cfg.BoolOpt(
|
||||
'load_action_plugins',
|
||||
@ -91,6 +114,25 @@ legacy_action_provider_opts = [
|
||||
'This property is needed mostly for testing.'
|
||||
)
|
||||
),
|
||||
cfg.ListOpt(
|
||||
'allowlist',
|
||||
default=[],
|
||||
help=_(
|
||||
'Allowlist with actions that is allowed to be '
|
||||
'loaded from the entry point "mistral.actions", '
|
||||
'if empty all actions will be allowed.'
|
||||
),
|
||||
),
|
||||
cfg.ListOpt(
|
||||
'denylist',
|
||||
default=[],
|
||||
help=_(
|
||||
'Denylist with actions that is not allowed to '
|
||||
'be loaded from the entry point "mistral.actions", '
|
||||
'allowlist takes precedence, if empty all actions '
|
||||
'will be allowed.'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
api_opts = [
|
||||
@ -755,6 +797,7 @@ healthcheck_opts = [
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
ACTION_PROVIDERS_GROUP = 'action_providers'
|
||||
LEGACY_ACTION_PROVIDER_GROUP = 'legacy_action_provider'
|
||||
API_GROUP = 'api'
|
||||
ENGINE_GROUP = 'engine'
|
||||
@ -785,6 +828,7 @@ CONF.register_opt(oslo_rpc_executor)
|
||||
CONF.register_opt(expiration_token_duration)
|
||||
CONF.register_opts(service_opts.service_opts)
|
||||
|
||||
CONF.register_opts(action_providers_opts, group=ACTION_PROVIDERS_GROUP)
|
||||
CONF.register_opts(
|
||||
legacy_action_provider_opts,
|
||||
group=LEGACY_ACTION_PROVIDER_GROUP
|
||||
@ -842,6 +886,7 @@ _DEFAULT_LOG_LEVELS = [
|
||||
|
||||
def list_opts():
|
||||
return [
|
||||
(ACTION_PROVIDERS_GROUP, action_providers_opts),
|
||||
(API_GROUP, api_opts),
|
||||
(ENGINE_GROUP, engine_opts),
|
||||
(EXECUTOR_GROUP, executor_opts),
|
||||
|
@ -18,6 +18,7 @@ collection of functions for accessing information about actions
|
||||
available in the system.
|
||||
"""
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from stevedore import extension
|
||||
|
||||
@ -39,7 +40,17 @@ def _get_registered_providers():
|
||||
invoke_on_load=False
|
||||
)
|
||||
|
||||
allowlist = cfg.CONF.action_providers.allowlist
|
||||
denylist = cfg.CONF.action_providers.denylist
|
||||
|
||||
for provider_name in mgr.names():
|
||||
if allowlist:
|
||||
if provider_name not in allowlist:
|
||||
continue
|
||||
elif denylist:
|
||||
if provider_name in denylist:
|
||||
continue
|
||||
|
||||
provider_cls = mgr[provider_name].plugin
|
||||
|
||||
try:
|
||||
|
@ -202,3 +202,61 @@ class LegacyActionProviderTest(base.BaseTest):
|
||||
)
|
||||
|
||||
self.assertEqual('Goodbye, Lieutenant Dan!', goodbye_action.run(None))
|
||||
|
||||
def test_allowlist(self):
|
||||
self.override_config(
|
||||
'load_action_generators',
|
||||
False,
|
||||
'legacy_action_provider'
|
||||
)
|
||||
self.override_config(
|
||||
'allowlist', ['std.noop'],
|
||||
'legacy_action_provider'
|
||||
)
|
||||
|
||||
provider = legacy.LegacyActionProvider()
|
||||
action_descs = provider.find_all()
|
||||
self.assertEqual(1, len(action_descs))
|
||||
self.assertIsNotNone(provider.find('std.noop'))
|
||||
|
||||
def test_denylist(self):
|
||||
self.override_config(
|
||||
'load_action_generators',
|
||||
False,
|
||||
'legacy_action_provider'
|
||||
)
|
||||
self.override_config(
|
||||
'denylist', ['std.noop'],
|
||||
'legacy_action_provider'
|
||||
)
|
||||
|
||||
provider = legacy.LegacyActionProvider()
|
||||
action_descs = provider.find_all()
|
||||
self.assertTrue(
|
||||
all(
|
||||
[
|
||||
a_d.name != 'std.noop'
|
||||
for a_d in action_descs
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
def test_allowlist_and_denylist(self):
|
||||
self.override_config(
|
||||
'load_action_generators',
|
||||
False,
|
||||
'legacy_action_provider'
|
||||
)
|
||||
self.override_config(
|
||||
'allowlist', ['std.noop'],
|
||||
'legacy_action_provider'
|
||||
)
|
||||
self.override_config(
|
||||
'denylist', ['std.fail'],
|
||||
'legacy_action_provider'
|
||||
)
|
||||
|
||||
provider = legacy.LegacyActionProvider()
|
||||
action_descs = provider.find_all()
|
||||
self.assertEqual(1, len(action_descs))
|
||||
self.assertIsNotNone(provider.find('std.noop'))
|
||||
|
71
mistral/tests/unit/services/test_actions.py
Normal file
71
mistral/tests/unit/services/test_actions.py
Normal file
@ -0,0 +1,71 @@
|
||||
# Copyright 2025 Binero.
|
||||
#
|
||||
# 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 unittest import mock
|
||||
|
||||
from mistral import config
|
||||
from mistral.services.actions import _get_registered_providers
|
||||
from mistral.tests.unit import base
|
||||
|
||||
|
||||
class FakeExtManager:
|
||||
def __init__(self):
|
||||
self.plugins = ['adhoc', 'dynamic', 'legacy']
|
||||
|
||||
def names(self):
|
||||
return self.plugins
|
||||
|
||||
|
||||
class ActionsTest(base.DbTestCase):
|
||||
def setUp(self):
|
||||
super(ActionsTest, self).setUp()
|
||||
|
||||
@mock.patch('stevedore.enabled.ExtensionManager')
|
||||
def test_get_registered_providers(self, mock_ext_mgr):
|
||||
mock_ext_mgr.return_value = FakeExtManager()
|
||||
providers = _get_registered_providers()
|
||||
self.assertEqual(3, len(providers))
|
||||
self.assertEqual('adhoc', providers[0].name)
|
||||
self.assertEqual('dynamic', providers[1].name)
|
||||
self.assertEqual('legacy', providers[2].name)
|
||||
|
||||
@mock.patch('stevedore.enabled.ExtensionManager')
|
||||
def test_get_registered_providers_allowlist(self, mock_ext_mgr):
|
||||
self.override_config(
|
||||
'allowlist', ['dynamic'], config.ACTION_PROVIDERS_GROUP)
|
||||
mock_ext_mgr.return_value = FakeExtManager()
|
||||
providers = _get_registered_providers()
|
||||
self.assertEqual(1, len(providers))
|
||||
self.assertEqual('dynamic', providers[0].name)
|
||||
|
||||
@mock.patch('stevedore.enabled.ExtensionManager')
|
||||
def test_get_registered_providers_denylist(self, mock_ext_mgr):
|
||||
self.override_config(
|
||||
'denylist', ['legacy'], config.ACTION_PROVIDERS_GROUP)
|
||||
mock_ext_mgr.return_value = FakeExtManager()
|
||||
providers = _get_registered_providers()
|
||||
self.assertEqual(2, len(providers))
|
||||
self.assertEqual('adhoc', providers[0].name)
|
||||
self.assertEqual('dynamic', providers[1].name)
|
||||
|
||||
@mock.patch('stevedore.enabled.ExtensionManager')
|
||||
def test_get_registered_providers_allow_and_deny(self, mock_ext_mgr):
|
||||
self.override_config(
|
||||
'allowlist', ['adhoc'], group=config.ACTION_PROVIDERS_GROUP)
|
||||
self.override_config(
|
||||
'denylist', ['dynamic'], group=config.ACTION_PROVIDERS_GROUP)
|
||||
mock_ext_mgr.return_value = FakeExtManager()
|
||||
providers = _get_registered_providers()
|
||||
self.assertEqual(1, len(providers))
|
||||
self.assertEqual('adhoc', providers[0].name)
|
@ -0,0 +1,10 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added mutually exclusive config options ``[action_providers]/allowlist``
|
||||
and ``[action_providers]/denylist`` that can be used to filter what action
|
||||
providers is loaded.
|
||||
- |
|
||||
Added mutually exclusive config options ``[legacy_action_provider]/allowlist``
|
||||
and ``[legacy_action_provider]/denylist`` that can be used to filter what
|
||||
actions is loaded by the legacy action provider.
|
Loading…
x
Reference in New Issue
Block a user