Skip to content

Commit 3ac7084

Browse files
committed
refactor: show dependency analysis with rich ui
Signed-off-by: Demolus13 <parth.govale@oracle.com>
1 parent dab8198 commit 3ac7084

File tree

4 files changed

+237
-5
lines changed

4 files changed

+237
-5
lines changed

src/macaron/console.py

Lines changed: 225 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,174 @@
1717
from rich.table import Table
1818

1919

20+
class Dependency:
21+
"""A class to manage the display of dependency analysis in the console."""
22+
23+
def __init__(self) -> None:
24+
"""Initialize the Dependency instance with default values and tables."""
25+
self.description_table = Table(show_header=False, box=None)
26+
self.description_table_content: dict[str, str | Status] = {
27+
"Package URL:": Status("[green]Processing[/]"),
28+
"Local Cloned Path:": Status("[green]Processing[/]"),
29+
"Remote Path:": Status("[green]Processing[/]"),
30+
"Branch:": Status("[green]Processing[/]"),
31+
"Commit Hash:": Status("[green]Processing[/]"),
32+
"Commit Date:": Status("[green]Processing[/]"),
33+
"CI Services:": Status("[green]Processing[/]"),
34+
"Build Tools:": Status("[green]Processing[/]"),
35+
}
36+
self.progress = Progress(
37+
TextColumn(" RUNNING ANALYSIS"),
38+
BarColumn(bar_width=None, complete_style="green"),
39+
MofNCompleteColumn(),
40+
)
41+
self.task_id: TaskID
42+
self.progress_table = Table(show_header=False, box=None)
43+
self.checks: dict[str, str] = {}
44+
self.failed_checks_table = Table(show_header=False, box=None)
45+
self.summary_table = Table(show_header=False, box=None)
46+
47+
def add_description_table_content(self, key: str, value: str | Status) -> None:
48+
"""
49+
Add or update a key-value pair in the description table.
50+
51+
Parameters
52+
----------
53+
key : str
54+
The key to be added or updated.
55+
value : str or Status
56+
The value associated with the key.
57+
"""
58+
self.description_table_content[key] = value
59+
description_table = Table(show_header=False, box=None)
60+
description_table.add_column("Details", justify="left")
61+
description_table.add_column("Value", justify="left")
62+
for field, content in self.description_table_content.items():
63+
description_table.add_row(field, content)
64+
65+
self.description_table = description_table
66+
67+
def no_of_checks(self, value: int) -> None:
68+
"""
69+
Initialize the progress bar with the total number of checks.
70+
71+
Parameters
72+
----------
73+
value : int
74+
The total number of checks to be performed.
75+
"""
76+
self.task_id = self.progress.add_task("analyzing", total=value)
77+
78+
def remove_progress_bar(self) -> None:
79+
"""Remove the progress bar from the display."""
80+
self.progress.remove_task(self.task_id)
81+
82+
def update_checks(self, check_id: str, status: str = "RUNNING") -> None:
83+
"""
84+
Update the status of a specific check and refresh the progress table.
85+
86+
Parameters
87+
----------
88+
check_id : str
89+
The identifier of the check to be updated.
90+
status : str, optional
91+
The new status of the check, by default "RUNNING"
92+
"""
93+
self.checks[check_id] = status
94+
95+
progress_table = Table(show_header=False, box=None)
96+
progress_table.add_column("Status", justify="left")
97+
progress_table.add_column("Check", justify="left")
98+
99+
for check_name, check_status in self.checks.items():
100+
if check_status == "RUNNING":
101+
progress_table.add_row(Status("[bold green]RUNNING[/]"), check_name)
102+
self.progress_table = progress_table
103+
104+
if self.task_id is not None and status != "RUNNING":
105+
self.progress.update(self.task_id, advance=1)
106+
107+
def update_checks_summary(self, checks_summary: dict, total_checks: int) -> None:
108+
"""
109+
Update the summary tables with the results of the checks.
110+
111+
Parameters
112+
----------
113+
checks_summary : dict
114+
Dictionary containing lists of checks categorized by their results.
115+
total_checks : int
116+
The total number of checks.
117+
"""
118+
failed_checks_table = Table(show_header=False, box=None)
119+
failed_checks_table.add_column("Status", justify="left")
120+
failed_checks_table.add_column("Check ID", justify="left")
121+
failed_checks_table.add_column("Description", justify="left")
122+
123+
failed_checks = checks_summary["FAILED"]
124+
for check in failed_checks:
125+
failed_checks_table.add_row(
126+
"[bold red]FAILED[/]",
127+
check.check.check_id,
128+
check.check.check_description,
129+
)
130+
131+
self.failed_checks_table = failed_checks_table
132+
133+
summary_table = Table(show_header=False, box=None)
134+
summary_table.add_column("Check Result Type", justify="left")
135+
summary_table.add_column("Count", justify="left")
136+
summary_table.add_row("Total Checks", str(total_checks), style="white")
137+
138+
for check_result_type, checks in checks_summary.items():
139+
if check_result_type == "PASSED":
140+
summary_table.add_row("PASSED", str(len(checks)), style="green")
141+
if check_result_type == "FAILED":
142+
summary_table.add_row("FAILED", str(len(checks)), style="red")
143+
if check_result_type == "SKIPPED":
144+
summary_table.add_row("SKIPPED", str(len(checks)), style="yellow")
145+
if check_result_type == "DISABLED":
146+
summary_table.add_row("DISABLED", str(len(checks)), style="bright_blue")
147+
if check_result_type == "UNKNOWN":
148+
summary_table.add_row("UNKNOWN", str(len(checks)), style="white")
149+
150+
self.summary_table = summary_table
151+
152+
def make_layout(self) -> list[RenderableType]:
153+
"""
154+
Create the layout for the live console display.
155+
156+
Returns
157+
-------
158+
list[RenderableType]
159+
A list of rich RenderableType objects containing the layout for the live console display.
160+
"""
161+
layout: list[RenderableType] = []
162+
if self.description_table.row_count > 0:
163+
layout = layout + [
164+
"",
165+
self.description_table,
166+
]
167+
if self.progress_table.row_count > 0:
168+
layout = layout + ["", self.progress, "", self.progress_table]
169+
if self.failed_checks_table.row_count > 0:
170+
layout = layout + [
171+
"",
172+
Rule(" SUMMARY", align="left"),
173+
"",
174+
self.failed_checks_table,
175+
]
176+
if self.summary_table.row_count > 0:
177+
layout = layout + ["", self.summary_table]
178+
elif self.summary_table.row_count > 0:
179+
layout = layout + [
180+
"",
181+
Rule(" SUMMARY", align="left"),
182+
"",
183+
self.summary_table,
184+
]
185+
return layout
186+
187+
20188
class RichConsoleHandler(RichHandler):
21189
"""A rich console handler for logging with rich formatting and live updates."""
22190

@@ -67,6 +235,8 @@ def __init__(self, *args: Any, verbose: bool = False, **kwargs: Any) -> None:
67235
"Dependencies Report": "Not Generated",
68236
"JSON Report": "Not Generated",
69237
}
238+
self.if_dependency: bool = False
239+
self.dependency_analysis_list: list[Dependency] = []
70240
self.components_violates_table = Table(box=None)
71241
self.components_satisfy_table = Table(box=None)
72242
self.policy_summary_table = Table(show_header=False, box=None)
@@ -138,6 +308,11 @@ def add_description_table_content(self, key: str, value: str | Status) -> None:
138308
value : str or Status
139309
The value associated with the key.
140310
"""
311+
if self.if_dependency and self.dependency_analysis_list:
312+
dependency = self.dependency_analysis_list[-1]
313+
dependency.add_description_table_content(key, value)
314+
return
315+
141316
self.description_table_content[key] = value
142317
description_table = Table(show_header=False, box=None)
143318
description_table.add_column("Details", justify="left")
@@ -156,8 +331,20 @@ def no_of_checks(self, value: int) -> None:
156331
value : int
157332
The total number of checks to be performed.
158333
"""
334+
if self.if_dependency and self.dependency_analysis_list:
335+
dependency = self.dependency_analysis_list[-1]
336+
dependency.no_of_checks(value)
337+
return
159338
self.task_id = self.progress.add_task("analyzing", total=value)
160339

340+
def remove_progress_bar(self) -> None:
341+
"""Remove the progress bar from the display."""
342+
if self.if_dependency and self.dependency_analysis_list:
343+
dependency = self.dependency_analysis_list[-1]
344+
dependency.remove_progress_bar()
345+
return
346+
self.progress.remove_task(self.task_id)
347+
161348
def update_checks(self, check_id: str, status: str = "RUNNING") -> None:
162349
"""
163350
Update the status of a specific check and refresh the progress table.
@@ -169,6 +356,10 @@ def update_checks(self, check_id: str, status: str = "RUNNING") -> None:
169356
status : str, optional
170357
The new status of the check, by default "RUNNING"
171358
"""
359+
if self.if_dependency and self.dependency_analysis_list:
360+
dependency = self.dependency_analysis_list[-1]
361+
dependency.update_checks(check_id, status)
362+
return
172363
self.checks[check_id] = status
173364

174365
progress_table = Table(show_header=False, box=None)
@@ -194,6 +385,10 @@ def update_checks_summary(self, checks_summary: dict, total_checks: int) -> None
194385
total_checks : int
195386
The total number of checks.
196387
"""
388+
if self.if_dependency and self.dependency_analysis_list:
389+
dependency = self.dependency_analysis_list[-1]
390+
dependency.update_checks_summary(checks_summary, total_checks)
391+
return
197392
failed_checks_table = Table(show_header=False, box=None)
198393
failed_checks_table.add_column("Status", justify="left")
199394
failed_checks_table.add_column("Check ID", justify="left")
@@ -249,6 +444,20 @@ def update_report_table(self, report_type: str, report_path: str) -> None:
249444

250445
self.report_table = report_table
251446

447+
def is_dependency(self, value: bool) -> None:
448+
"""
449+
Update the flag indicating whether the analyzed package is a dependency.
450+
451+
Parameters
452+
----------
453+
value : bool
454+
True if the package is a dependency, False otherwise.
455+
"""
456+
self.if_dependency = value
457+
if self.if_dependency:
458+
dependency = Dependency()
459+
self.dependency_analysis_list.append(dependency)
460+
252461
def generate_policy_summary_table(self) -> None:
253462
"""Generate the policy summary table based on the current policy summary data."""
254463
policy_summary_table = Table(show_header=False, box=None)
@@ -398,7 +607,11 @@ def make_layout(self) -> Group:
398607
layout = layout + [error_log_panel]
399608
if self.command == "analyze":
400609
if self.description_table.row_count > 0:
401-
layout = layout + [Rule(" DESCRIPTION", align="left"), "", self.description_table]
610+
layout = layout + [
611+
Rule(" DESCRIPTION", align="left"),
612+
"",
613+
self.description_table,
614+
]
402615
if self.progress_table.row_count > 0:
403616
layout = layout + ["", self.progress, "", self.progress_table]
404617
if self.failed_checks_table.row_count > 0:
@@ -425,6 +638,17 @@ def make_layout(self) -> Group:
425638
layout = layout + [
426639
self.report_table,
427640
]
641+
if self.if_dependency and self.dependency_analysis_list:
642+
for idx, dependency in enumerate(self.dependency_analysis_list, start=1):
643+
dependency_layout = dependency.make_layout()
644+
layout = (
645+
layout
646+
+ [
647+
"",
648+
Rule(f" DEPENDENCY {idx}", align="left"),
649+
]
650+
+ dependency_layout
651+
)
428652
elif self.command == "verify-policy":
429653
if self.policy_summary_table.row_count > 0:
430654
if self.components_satisfy_table.row_count > 0:

src/macaron/output_reporter/results.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from typing import Generic, TypedDict, TypeVar
1010

1111
from macaron.config.target_config import Configuration
12-
from macaron.console import access_handler
1312
from macaron.output_reporter.scm import SCMStatus
1413
from macaron.slsa_analyzer.analyze_context import AnalyzeContext
1514
from macaron.slsa_analyzer.checks.check_result import CheckResultType
@@ -200,7 +199,6 @@ def __init__(self, root_record: Record) -> None:
200199
self.record_mapping: dict[str, Record] = {}
201200
if root_record.context:
202201
self.record_mapping[root_record.record_id] = root_record
203-
self.rich_handler = access_handler.get_handler()
204202

205203
def get_records(self) -> Iterable[Record]:
206204
"""Get the generator for all records in the report.
@@ -299,7 +297,6 @@ def __str__(self) -> str:
299297
"""Return the string representation of the Report instance."""
300298
ctx_list = list(self.get_ctxs())
301299
main_ctx: AnalyzeContext = ctx_list.pop(0)
302-
self.rich_handler = access_handler.get_handler()
303300

304301
output = "".join(
305302
[
@@ -309,7 +306,6 @@ def __str__(self) -> str:
309306
"\nSLSA REQUIREMENT RESULTS:\n",
310307
]
311308
)
312-
self.rich_handler.update_checks_summary(main_ctx.get_check_summary(), len(main_ctx.check_results))
313309

314310
slsa_req_mesg: dict[SLSALevels, list[str]] = {level: [] for level in SLSALevels if level != SLSALevels.LEVEL0}
315311
for req_name, req_status in main_ctx.ctx_data.items():

src/macaron/slsa_analyzer/analyzer.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ def run(
240240

241241
# Create a report instance with the record of the main repo.
242242
report = Report(main_record)
243+
self.rich_handler.update_checks_summary(
244+
main_record.context.get_check_summary(),
245+
len(main_record.context.check_results),
246+
)
243247

244248
duplicated_scm_records: list[Record] = []
245249

@@ -257,8 +261,14 @@ def run(
257261
report.add_dep_record(dep_record)
258262
duplicated_scm_records.append(dep_record)
259263
continue
264+
self.rich_handler.is_dependency(True)
260265
dep_record = self.run_single(config, analysis, report.record_mapping)
261266
report.add_dep_record(dep_record)
267+
if dep_record.context:
268+
self.rich_handler.update_checks_summary(
269+
dep_record.context.get_check_summary(),
270+
len(dep_record.context.check_results),
271+
)
262272
else:
263273
logger.info("Found no dependencies to analyze.")
264274

src/macaron/slsa_analyzer/registry.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,8 @@ def scan(self, target: AnalyzeContext) -> dict[str, CheckResult]:
519519
logger.info("Check %s has failed.", check_id)
520520
return results
521521

522+
self.rich_handler.remove_progress_bar()
523+
522524
return results
523525

524526
def prepare(self) -> bool:

0 commit comments

Comments
 (0)