mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-14 23:44:15 +00:00
fix: fix the batch writing when launching the CLI
This commit is contained in:
1 parent
79aded5aa3
commit
97d55d7aa7
11 files changed
+47
-21
No files matched your search
@@ -255,6 +255,7 @@ def generate_outputs(scan_id: str, provider_id: str, tenant_id: str):
|
||||
findings=finding_outputs,
|
||||
file_path=output_directory,
|
||||
file_extension=config["suffix"],
|
||||
from_cli=False,
|
||||
)
|
||||
writer.close_file = is_last_batch
|
||||
output_writers[writer_class] = writer
|
||||
|
||||
@@ -33,6 +33,7 @@ class ComplianceOutput(Output):
|
||||
file_extension: str = "",
|
||||
) -> None:
|
||||
self._data = []
|
||||
self.file_descriptor = None
|
||||
|
||||
if not file_extension and file_path:
|
||||
self._file_extension = "".join(Path(file_path).suffixes)
|
||||
|
||||
@@ -102,7 +102,7 @@ class CSV(Output):
|
||||
csv_writer.writeheader()
|
||||
for finding in self._data:
|
||||
csv_writer.writerow(finding)
|
||||
if self.close_file:
|
||||
if self.close_file or self._from_cli:
|
||||
self._file_descriptor.close()
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
|
||||
@@ -78,7 +78,7 @@ class HTML(Output):
|
||||
HTML.write_header(self._file_descriptor, provider, stats)
|
||||
for finding in self._data:
|
||||
self._file_descriptor.write(finding)
|
||||
if self.close_file:
|
||||
if self.close_file or self._from_cli:
|
||||
HTML.write_footer(self._file_descriptor)
|
||||
self._file_descriptor.close()
|
||||
except Exception as error:
|
||||
|
||||
@@ -212,7 +212,7 @@ class OCSF(Output):
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
if self.close_file:
|
||||
if self.close_file or self._from_cli:
|
||||
if self._file_descriptor.tell() != 1:
|
||||
self._file_descriptor.seek(
|
||||
self._file_descriptor.tell() - 1, os.SEEK_SET
|
||||
|
||||
@@ -34,11 +34,14 @@ class Output(ABC):
|
||||
findings: List[Finding],
|
||||
file_path: str = None,
|
||||
file_extension: str = "",
|
||||
from_cli: bool = True,
|
||||
) -> None:
|
||||
self._data = []
|
||||
self.close_file = False
|
||||
self.file_path = file_path
|
||||
self.file_descriptor = None
|
||||
self._file_descriptor = None
|
||||
# This parameter is to avoid refactoring more code, the CLI does not write in batches, the API does
|
||||
self._from_cli = from_cli
|
||||
|
||||
if not file_extension and file_path:
|
||||
self._file_extension = "".join(Path(file_path).suffixes)
|
||||
@@ -48,7 +51,7 @@ class Output(ABC):
|
||||
|
||||
if findings:
|
||||
self.transform(findings)
|
||||
if not self.file_descriptor and file_path:
|
||||
if not self._file_descriptor and file_path:
|
||||
self.create_file_descriptor(self.file_path)
|
||||
|
||||
@property
|
||||
|
||||
@@ -577,7 +577,7 @@ class TestASFF:
|
||||
assert loads(content) == expected_asff
|
||||
|
||||
def test_batch_write_data_to_file_without_findings(self):
|
||||
assert not hasattr(ASFF([]), "_file_descriptor")
|
||||
assert not ASFF([])._file_descriptor
|
||||
|
||||
def test_asff_generate_status(self):
|
||||
assert ASFF.generate_status("PASS") == "PASSED"
|
||||
|
||||
@@ -119,7 +119,7 @@ class TestCSV:
|
||||
assert content == expected_csv
|
||||
|
||||
def test_batch_write_data_to_file_without_findings(self):
|
||||
assert not hasattr(CSV([]), "_file_descriptor")
|
||||
assert not CSV([])._file_descriptor
|
||||
|
||||
@pytest.fixture
|
||||
def mock_output_class(self):
|
||||
|
||||
@@ -61,6 +61,7 @@ class DummyTag:
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
|
||||
class DummyTags:
|
||||
def __init__(self, tags):
|
||||
self._tags = tags
|
||||
@@ -68,6 +69,7 @@ class DummyTags:
|
||||
def all(self):
|
||||
return self._tags
|
||||
|
||||
|
||||
class DummyResource:
|
||||
def __init__(self, uid, name, region, tags):
|
||||
self.uid = uid
|
||||
@@ -75,27 +77,33 @@ class DummyResource:
|
||||
self.region = region
|
||||
self.tags = DummyTags(tags)
|
||||
|
||||
|
||||
class DummyResources:
|
||||
"""Simulate a collection with a first() method."""
|
||||
|
||||
def __init__(self, resource):
|
||||
self._resource = resource
|
||||
|
||||
def first(self):
|
||||
return self._resource
|
||||
|
||||
|
||||
class DummyProvider:
|
||||
def __init__(self, uid):
|
||||
self.uid = uid
|
||||
|
||||
|
||||
class DummyScan:
|
||||
def __init__(self, provider):
|
||||
self.provider = provider
|
||||
|
||||
|
||||
class DummyAPIFinding:
|
||||
"""
|
||||
A dummy API finding model to simulate the database model.
|
||||
Attributes will be added dynamically.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -518,7 +526,9 @@ class TestFinding:
|
||||
|
||||
# Create a dummy resource with one tag
|
||||
tag = DummyTag("env", "prod")
|
||||
resource = DummyResource(uid="res-uid-1", name="ResourceName1", region="us-east-1", tags=[tag])
|
||||
resource = DummyResource(
|
||||
uid="res-uid-1", name="ResourceName1", region="us-east-1", tags=[tag]
|
||||
)
|
||||
resources = DummyResources(resource)
|
||||
|
||||
# Create a dummy check_metadata dict with all required fields
|
||||
@@ -535,10 +545,7 @@ class TestFinding:
|
||||
"risk": "High risk",
|
||||
"relatedurl": "http://example.com",
|
||||
"remediation": {
|
||||
"recommendation": {
|
||||
"text": "Fix it",
|
||||
"url": "http://fix.com"
|
||||
},
|
||||
"recommendation": {"text": "Fix it", "url": "http://fix.com"},
|
||||
"code": {
|
||||
"nativeiac": "iac_code",
|
||||
"terraform": "terraform_code",
|
||||
@@ -618,10 +625,18 @@ class TestFinding:
|
||||
# Create a dummy scan object with a unique_resource_count
|
||||
dummy_scan = SimpleNamespace(unique_resource_count=10)
|
||||
# Build summaries covering each severity branch.
|
||||
ss1 = SimpleNamespace(_pass=1, fail=2, total=3, muted=2, severity="critical", scan=dummy_scan)
|
||||
ss2 = SimpleNamespace(_pass=2, fail=0, total=2, muted=0, severity="high", scan=dummy_scan)
|
||||
ss3 = SimpleNamespace(_pass=2, fail=3, total=5, muted=3, severity="medium", scan=dummy_scan)
|
||||
ss4 = SimpleNamespace(_pass=3, fail=0, total=3, muted=0, severity="low", scan=dummy_scan)
|
||||
ss1 = SimpleNamespace(
|
||||
_pass=1, fail=2, total=3, muted=2, severity="critical", scan=dummy_scan
|
||||
)
|
||||
ss2 = SimpleNamespace(
|
||||
_pass=2, fail=0, total=2, muted=0, severity="high", scan=dummy_scan
|
||||
)
|
||||
ss3 = SimpleNamespace(
|
||||
_pass=2, fail=3, total=5, muted=3, severity="medium", scan=dummy_scan
|
||||
)
|
||||
ss4 = SimpleNamespace(
|
||||
_pass=3, fail=0, total=3, muted=0, severity="low", scan=dummy_scan
|
||||
)
|
||||
|
||||
summaries = [ss1, ss2, ss3, ss4]
|
||||
stats = Finding._transform_findings_stats(summaries)
|
||||
@@ -657,8 +672,12 @@ class TestFinding:
|
||||
"""
|
||||
dummy_scan = SimpleNamespace(unique_resource_count=5)
|
||||
# Build summaries: one summary has fail > 0 but muted == 0
|
||||
ss1 = SimpleNamespace(_pass=1, fail=2, total=3, muted=0, severity="critical", scan=dummy_scan)
|
||||
ss2 = SimpleNamespace(_pass=2, fail=1, total=3, muted=1, severity="high", scan=dummy_scan)
|
||||
ss1 = SimpleNamespace(
|
||||
_pass=1, fail=2, total=3, muted=0, severity="critical", scan=dummy_scan
|
||||
)
|
||||
ss2 = SimpleNamespace(
|
||||
_pass=2, fail=1, total=3, muted=1, severity="high", scan=dummy_scan
|
||||
)
|
||||
summaries = [ss1, ss2]
|
||||
stats = Finding._transform_findings_stats(summaries)
|
||||
|
||||
@@ -705,7 +724,9 @@ class TestFinding:
|
||||
dummy_finding.check_metadata = {}
|
||||
# Provide a dummy resources with a minimal resource
|
||||
tag = DummyTag("env", "prod")
|
||||
resource = DummyResource(uid="res-uid-1", name="ResourceName1", region="us-east-1", tags=[tag])
|
||||
resource = DummyResource(
|
||||
uid="res-uid-1", name="ResourceName1", region="us-east-1", tags=[tag]
|
||||
)
|
||||
dummy_finding.resources = DummyResources(resource)
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
|
||||
@@ -492,7 +492,7 @@ class TestHTML:
|
||||
assert content == get_aws_html_header(args) + pass_html_finding + html_footer
|
||||
|
||||
def test_batch_write_data_to_file_without_findings(self):
|
||||
assert not hasattr(HTML([]), "_file_descriptor")
|
||||
assert not HTML([])._file_descriptor
|
||||
|
||||
def test_write_header(self):
|
||||
mock_file = StringIO()
|
||||
|
||||
@@ -258,7 +258,7 @@ class TestOCSF:
|
||||
assert json.loads(content) == expected_json_output
|
||||
|
||||
def test_batch_write_data_to_file_without_findings(self):
|
||||
assert not hasattr(OCSF([]), "_file_descriptor")
|
||||
assert not OCSF([])._file_descriptor
|
||||
|
||||
def test_finding_output_cloud_pass_low_muted(self):
|
||||
finding_output = generate_finding_output(
|
||||
|
||||
Reference in new issue
Block a user