mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-25 04:44:20 +00:00
fix(compliance): merge frameworks from multiple dynamic packages per provider
_get_ep_compliance_dirs collapsed compliance directories into a dict keyed by provider, so when two or more dynamically-registered packages shipped compliance for the same provider, only one survived and the others were silently dropped from discovery, default output modes and the stdout table. Accumulate the directories per provider and iterate them all in get_available_compliance_frameworks, matching the behaviour Compliance.get_bulk already had. Built-in precedence and filename-stem dedup are preserved.
This commit is contained in:
1 parent
d25f8a4fe9
commit
204870f48a
2 files changed
+43
-8
No files matched your search
@@ -87,15 +87,22 @@ actual_directory = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
|
||||
def _get_ep_compliance_dirs() -> dict:
|
||||
"""Discover compliance directories from entry points. Returns {provider: path}."""
|
||||
"""Discover compliance directories from entry points. Returns {provider: [paths]}.
|
||||
|
||||
A provider may be contributed by several packages, so accumulate every
|
||||
directory instead of overwriting.
|
||||
"""
|
||||
dirs = {}
|
||||
for ep in importlib.metadata.entry_points(group="prowler.compliance"):
|
||||
try:
|
||||
module = ep.load()
|
||||
if hasattr(module, "__path__"):
|
||||
dirs[ep.name] = module.__path__[0]
|
||||
path = module.__path__[0]
|
||||
elif hasattr(module, "__file__"):
|
||||
dirs[ep.name] = os.path.dirname(module.__file__)
|
||||
path = os.path.dirname(module.__file__)
|
||||
else:
|
||||
continue
|
||||
dirs.setdefault(ep.name, []).append(path)
|
||||
except Exception as error:
|
||||
logger.warning(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
@@ -144,13 +151,15 @@ def get_available_compliance_frameworks(provider=None):
|
||||
continue
|
||||
if name not in available_compliance_frameworks:
|
||||
available_compliance_frameworks.append(name)
|
||||
# External compliance via entry points.
|
||||
# Multi-provider support for external plug-ins is tracked in PROWLER-1444.
|
||||
# External compliance via entry points; a provider may be served by
|
||||
# several packages, so iterate every directory it contributes.
|
||||
ep_dirs = _get_ep_compliance_dirs()
|
||||
for prov, path in ep_dirs.items():
|
||||
for prov, paths in ep_dirs.items():
|
||||
if provider and prov != provider:
|
||||
continue
|
||||
if os.path.isdir(path):
|
||||
for path in paths:
|
||||
if not os.path.isdir(path):
|
||||
continue
|
||||
for file in os.scandir(path):
|
||||
if file.is_file() and file.name.endswith(".json"):
|
||||
name = file.name.removesuffix(".json")
|
||||
|
||||
@@ -488,7 +488,7 @@ class Test_Config:
|
||||
with open(json_path, "w") as f:
|
||||
json.dump({"Framework": "CIS", "Provider": "aws"}, f)
|
||||
|
||||
mock_dirs.return_value = {"aws": tmpdir}
|
||||
mock_dirs.return_value = {"aws": [tmpdir]}
|
||||
|
||||
frameworks = get_available_compliance_frameworks("aws")
|
||||
|
||||
@@ -497,6 +497,32 @@ class Test_Config:
|
||||
f"{frameworks.count('cis_2.0_aws')} occurrences in: {frameworks}"
|
||||
)
|
||||
|
||||
@mock.patch("prowler.config.config._get_ep_compliance_dirs")
|
||||
def test_get_available_compliance_frameworks_merges_multiple_ep_dirs_same_provider(
|
||||
self, mock_dirs
|
||||
):
|
||||
"""Frameworks from every package contributing the same provider must
|
||||
surface, not just the last directory discovered."""
|
||||
import json
|
||||
import tempfile
|
||||
|
||||
with (
|
||||
tempfile.TemporaryDirectory() as pkg_a,
|
||||
tempfile.TemporaryDirectory() as pkg_b,
|
||||
):
|
||||
with open(os.path.join(pkg_a, "cis_1.0_template.json"), "w") as f:
|
||||
json.dump({"Framework": "CIS", "Provider": "template"}, f)
|
||||
with open(os.path.join(pkg_b, "nis2_1.0_template.json"), "w") as f:
|
||||
json.dump({"Framework": "NIS2", "Provider": "template"}, f)
|
||||
|
||||
# Two packages register `prowler.compliance` with the same name.
|
||||
mock_dirs.return_value = {"template": [pkg_a, pkg_b]}
|
||||
|
||||
frameworks = get_available_compliance_frameworks("template")
|
||||
|
||||
assert "cis_1.0_template" in frameworks
|
||||
assert "nis2_1.0_template" in frameworks
|
||||
|
||||
def test_load_and_validate_config_file_aws(self):
|
||||
path = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
|
||||
config_test_file = f"{path}/fixtures/config.yaml"
|
||||
|
||||
Reference in new issue
Block a user