refactor(sdk): extract is_tool_wrapper_provider to leaf module to break import cycle

This commit is contained in:
StylusFrost
2026-04-28 13:57:27 +02:00
parent 6715361246
commit 79f12f3617
4 changed files with 77 additions and 26 deletions
+8 -3
View File
@@ -2,10 +2,10 @@ import sys
from colorama import Fore, Style
from prowler.config.config import EXTERNAL_TOOL_PROVIDERS
from prowler.lib.check.check import parse_checks_from_file
from prowler.lib.check.compliance_models import Compliance
from prowler.lib.check.models import CheckMetadata, Severity
from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider
from prowler.lib.logger import logger
@@ -25,8 +25,13 @@ def load_checks_to_execute(
) -> set:
"""Generate the list of checks to execute based on the cloud provider and the input arguments given"""
try:
# Bypass check loading for providers that use external tools directly
if provider in EXTERNAL_TOOL_PROVIDERS:
# Bypass check loading for tool-wrapper providers — they delegate
# scanning to an external tool and have no checks to recover.
# Single source of truth across __main__, the CheckMetadata validators,
# check discovery and this loader, covering both built-in tool wrappers
# (iac/llm/image) and external plug-ins that declare
# `is_external_tool_provider = True` via the contract.
if is_tool_wrapper_provider(provider):
return set()
# Local subsets
+57
View File
@@ -0,0 +1,57 @@
"""Standalone helper for tool-wrapper provider detection.
A provider is a "tool wrapper" if it delegates scanning to an external tool
(Trivy, promptfoo, etc.) instead of running checks/services through the
standard Prowler engine. This module is the single source of truth for that
classification across the codebase.
Kept as a leaf module with no Prowler imports beyond the leaf
`external_tool_providers` so it can be referenced from `prowler.lib.check.*`
and `prowler.providers.common.provider` without forming an import cycle.
"""
import importlib.metadata
from prowler.lib.check.external_tool_providers import EXTERNAL_TOOL_PROVIDERS
# Module-level cache for entry-point classes consulted by this helper.
# Independent of `Provider._ep_providers` to keep this module leaf — the cost
# of a duplicate cache entry is negligible (one class object per external
# provider, loaded lazily on first lookup).
_ep_class_cache: dict = {}
def _load_ep_class(provider: str):
"""Return the entry-point provider class for `provider`, or None.
Caches the result in `_ep_class_cache`. Errors during entry-point loading
are swallowed (returning None) so a broken plug-in never crashes the
is-tool-wrapper check; it just falls through to "not a tool wrapper".
"""
if provider in _ep_class_cache:
return _ep_class_cache[provider]
for ep in importlib.metadata.entry_points(group="prowler.providers"):
if ep.name == provider:
try:
cls = ep.load()
except Exception:
cls = None
_ep_class_cache[provider] = cls
return cls
_ep_class_cache[provider] = None
return None
def is_tool_wrapper_provider(provider: str) -> bool:
"""Return True if the provider delegates scanning to an external tool.
Combines the built-in `EXTERNAL_TOOL_PROVIDERS` frozenset (fast path for
iac/llm/image) with the `is_external_tool_provider` class attribute of
external plug-ins registered via entry points. This is the single source
of truth consulted by `__main__`, the `CheckMetadata` validators, the
check-loading utilities, and the checks loader.
"""
if provider in EXTERNAL_TOOL_PROVIDERS:
return True
cls = _load_ep_class(provider)
return bool(cls and getattr(cls, "is_external_tool_provider", False))
+3 -10
View File
@@ -4,7 +4,7 @@ import os
import sys
from pkgutil import walk_packages
from prowler.lib.check.external_tool_providers import EXTERNAL_TOOL_PROVIDERS
from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider
from prowler.lib.logger import logger
@@ -52,9 +52,7 @@ def recover_checks_from_provider(
# Single source of truth: combines the EXTERNAL_TOOL_PROVIDERS
# frozenset (built-ins) with the per-provider `is_external_tool_provider`
# class attribute (so external plug-ins opt in via the contract).
from prowler.providers.common.provider import Provider
if Provider.is_tool_wrapper_provider(provider):
if is_tool_wrapper_provider(provider):
return []
checks = []
@@ -124,12 +122,7 @@ def recover_checks_from_service(service_list: list, provider: str) -> set:
try:
# Bypass check loading for tool-wrapper providers — symmetric with
# `recover_checks_from_provider` above, using the same source of truth.
# NOTE: master gated this on `provider in EXTERNAL_TOOL_PROVIDERS`
# (covering iac/llm/image). The PR temporarily narrowed it to `== "iac"`;
# restoring the full set via the helper.
from prowler.providers.common.provider import Provider
if Provider.is_tool_wrapper_provider(provider):
if is_tool_wrapper_provider(provider):
return set()
checks = set()
+9 -13
View File
@@ -9,10 +9,7 @@ from argparse import Namespace
from importlib import import_module
from typing import Any, Optional
from prowler.config.config import (
EXTERNAL_TOOL_PROVIDERS,
load_and_validate_config_file,
)
from prowler.config.config import load_and_validate_config_file
from prowler.lib.logger import logger
from prowler.lib.mutelist.mutelist import Mutelist
@@ -592,16 +589,15 @@ class Provider(ABC):
def is_tool_wrapper_provider(provider: str) -> bool:
"""Return True if the provider delegates scanning to an external tool.
Combines the built-in EXTERNAL_TOOL_PROVIDERS frozenset (fast path for
iac/llm/image) with the `is_external_tool_provider` class attribute of
external plug-in providers registered via entry points. This is the
single source of truth consulted by the execution flow and the
CheckMetadata validators.
Delegates to `prowler.lib.check.tool_wrapper.is_tool_wrapper_provider`,
the leaf module that holds the actual logic. Kept on `Provider` as a
convenience entry point for callers that already import `Provider`.
"""
if provider in EXTERNAL_TOOL_PROVIDERS:
return True
ep_cls = Provider._load_ep_provider(provider)
return bool(ep_cls and getattr(ep_cls, "is_external_tool_provider", False))
from prowler.lib.check.tool_wrapper import (
is_tool_wrapper_provider as _impl,
)
return _impl(provider)
@staticmethod
def is_builtin(provider: str) -> bool: