Implement functionality in order to generate report directly from the artifacts folder.
This commit is contained in:
1 parent
fb0611dd5d
commit
7b6c19d8d9
4 files changed
+239
-16
No files matched your search
@@ -1,4 +1,9 @@
|
||||
"""report_generator generate command."""
|
||||
"""The two ways to ask for a report: out of the store, or out of a folder.
|
||||
|
||||
Two commands rather than one with a mode switch, because the two need different things and a
|
||||
command that demands a database host in order to read a directory is a command nobody will use for
|
||||
that. What they share is everything after the artifacts are in hand, which is :func:`_report`.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from uuid import UUID
|
||||
@@ -7,10 +12,12 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from cc_common.api import PipelineError, context
|
||||
from cc_common.utils import print
|
||||
|
||||
# Imported for the properties model; the store half of the tool is the only part that needs it.
|
||||
from persistent_storage.shared import StorageConfiguration
|
||||
|
||||
from ..rendering import render
|
||||
from ..sources import collect
|
||||
from ..sources import ReportSources, collect, collect_from_directory
|
||||
|
||||
# Where the report goes when the caller names no path.
|
||||
DEFAULT_DIRECTORY = Path("reports")
|
||||
@@ -20,7 +27,7 @@ class GenerateProperties(BaseModel):
|
||||
"""Configuration properties for the generate CLI command.
|
||||
|
||||
Attributes:
|
||||
scan_id: The scan to report on.
|
||||
scan_id: The scan to report on. The store is keyed by it.
|
||||
path: Where to write the PDF. A directory is written into under the name
|
||||
the renderer chose; leaving it out writes into ``reports/``.
|
||||
storage_config: The store the scan's artifacts are read from.
|
||||
@@ -31,6 +38,23 @@ class GenerateProperties(BaseModel):
|
||||
storage_config: StorageConfiguration
|
||||
|
||||
|
||||
class FolderProperties(BaseModel):
|
||||
"""Configuration properties for the from-folder CLI command.
|
||||
|
||||
Attributes:
|
||||
artifacts: The folder holding the artifacts, as the pipeline exported them.
|
||||
scan_id: The scan to report under. Read out of the artifacts when not
|
||||
given -- an export names its own run.
|
||||
path: Where to write the PDF, as above.
|
||||
"""
|
||||
|
||||
artifacts: Path = Field(description="Folder of exported artifacts to build the report from.")
|
||||
scan_id: UUID | None = Field(
|
||||
default=None, description="The scan to report under. Read from the artifacts when omitted."
|
||||
)
|
||||
path: Path | None = Field(default=None, description="Where to write the PDF report.")
|
||||
|
||||
|
||||
class GeneratedReport(BaseModel):
|
||||
"""The report that was written.
|
||||
|
||||
@@ -51,10 +75,10 @@ class GeneratedReport(BaseModel):
|
||||
|
||||
|
||||
def generate(properties: GenerateProperties) -> GeneratedReport:
|
||||
"""Build the PDF report of one scan and write it.
|
||||
"""Build the report of a scan held in the store.
|
||||
|
||||
Args:
|
||||
properties: Parsed command properties naming the scan and the destination.
|
||||
properties: The scan, the store and the destination.
|
||||
|
||||
Returns:
|
||||
The report that was written.
|
||||
@@ -63,9 +87,45 @@ def generate(properties: GenerateProperties) -> GeneratedReport:
|
||||
PipelineError: The scan cannot be read, is missing an input the report
|
||||
needs, or the destination cannot be written.
|
||||
"""
|
||||
sources = collect(properties.storage_config, properties.scan_id)
|
||||
return _report(collect(properties.storage_config, properties.scan_id), properties.path)
|
||||
|
||||
|
||||
def from_folder(properties: FolderProperties) -> GeneratedReport:
|
||||
"""Build the report of a scan that was exported to a folder.
|
||||
|
||||
Neither Postgres nor the bucket is touched. The same report, from a directory somebody was sent.
|
||||
|
||||
Args:
|
||||
properties: The folder, optionally the scan to report under, and the destination.
|
||||
|
||||
Returns:
|
||||
The report that was written.
|
||||
|
||||
Raises:
|
||||
PipelineError: The folder cannot be read, names no scan, is missing an
|
||||
input the report needs, or the destination cannot be written.
|
||||
"""
|
||||
return _report(collect_from_directory(properties.artifacts, properties.scan_id), properties.path)
|
||||
|
||||
|
||||
def _report(sources: ReportSources, path: Path | None) -> GeneratedReport:
|
||||
"""Render the collected artifacts and write the result.
|
||||
|
||||
Everything after the artifacts are in hand, which is the same whichever of the two collected
|
||||
them -- and the reason the two commands are two callbacks rather than two pipelines.
|
||||
|
||||
Args:
|
||||
sources: The scan and its files.
|
||||
path: Where the caller asked for the PDF, if anywhere in particular.
|
||||
|
||||
Returns:
|
||||
The report that was written.
|
||||
|
||||
Raises:
|
||||
PipelineError: The report cannot be built or the destination not written.
|
||||
"""
|
||||
name, content = render(sources)
|
||||
destination = _destination(properties.path, name)
|
||||
destination = _destination(path, name)
|
||||
|
||||
try:
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
Reference in new issue
Block a user