2020)
2121from macaron .config .defaults import create_defaults , load_defaults
2222from macaron .config .global_config import global_config
23+ from macaron .console import RichConsoleHandler , access_handler
2324from macaron .errors import ConfigurationError
2425from macaron .output_reporter .reporter import HTMLReporter , JSONReporter , PolicyReporter
2526from macaron .policy_engine .policy_engine import run_policy_engine , show_prelude
@@ -63,7 +64,8 @@ def analyze_slsa_levels_single(analyzer_single_args: argparse.Namespace) -> None
6364 if analyzer_single_args .provenance_expectation is not None :
6465 if not os .path .exists (analyzer_single_args .provenance_expectation ):
6566 logger .critical (
66- 'The provenance expectation file "%s" does not exist.' , analyzer_single_args .provenance_expectation
67+ 'The provenance expectation file "%s" does not exist.' ,
68+ analyzer_single_args .provenance_expectation ,
6769 )
6870 sys .exit (os .EX_OSFILE )
6971 global_config .load_expectation_files (analyzer_single_args .provenance_expectation )
@@ -72,7 +74,8 @@ def analyze_slsa_levels_single(analyzer_single_args: argparse.Namespace) -> None
7274 if analyzer_single_args .python_venv is not None :
7375 if not os .path .exists (analyzer_single_args .python_venv ):
7476 logger .critical (
75- 'The Python virtual environment path "%s" does not exist.' , analyzer_single_args .python_venv
77+ 'The Python virtual environment path "%s" does not exist.' ,
78+ analyzer_single_args .python_venv ,
7679 )
7780 sys .exit (os .EX_OSFILE )
7881 global_config .load_python_venv (analyzer_single_args .python_venv )
@@ -95,7 +98,10 @@ def analyze_slsa_levels_single(analyzer_single_args: argparse.Namespace) -> None
9598 else :
9699 user_provided_local_maven_repo = analyzer_single_args .local_maven_repo
97100 if not os .path .isdir (user_provided_local_maven_repo ):
98- logger .error ("The user provided local Maven repo at %s is not valid." , user_provided_local_maven_repo )
101+ logger .error (
102+ "The user provided local Maven repo at %s is not valid." ,
103+ user_provided_local_maven_repo ,
104+ )
99105 sys .exit (os .EX_USAGE )
100106
101107 global_config .local_maven_repo = user_provided_local_maven_repo
@@ -111,7 +117,8 @@ def analyze_slsa_levels_single(analyzer_single_args: argparse.Namespace) -> None
111117 lstrip_blocks = True ,
112118 )
113119 html_reporter = HTMLReporter (
114- env = custom_jinja_env , target_template = os .path .basename (analyzer_single_args .template_path )
120+ env = custom_jinja_env ,
121+ target_template = os .path .basename (analyzer_single_args .template_path ),
115122 )
116123 if not html_reporter .template :
117124 logger .error ("Exiting because the custom template cannot be found." )
@@ -207,8 +214,11 @@ def verify_policy(verify_policy_args: argparse.Namespace) -> int:
207214
208215 result = run_policy_engine (verify_policy_args .database , policy_content )
209216 vsa = generate_vsa (policy_content = policy_content , policy_result = result )
217+ # Retrieve the console handler previously configured via the access_handler.
218+ rich_handler = access_handler .get_handler ()
210219 if vsa is not None :
211220 vsa_filepath = os .path .join (global_config .output_path , "vsa.intoto.jsonl" )
221+ rich_handler .update_vsa (os .path .relpath (vsa_filepath , os .getcwd ()))
212222 logger .info (
213223 "Generating the Verification Summary Attestation (VSA) to %s." ,
214224 os .path .relpath (vsa_filepath , os .getcwd ()),
@@ -222,8 +232,12 @@ def verify_policy(verify_policy_args: argparse.Namespace) -> int:
222232 file .write (json .dumps (vsa ))
223233 except OSError as err :
224234 logger .error (
225- "Could not generate the VSA to %s. Error: %s" , os .path .relpath (vsa_filepath , os .getcwd ()), err
235+ "Could not generate the VSA to %s. Error: %s" ,
236+ os .path .relpath (vsa_filepath , os .getcwd ()),
237+ err ,
226238 )
239+ else :
240+ rich_handler .update_vsa ("No VSA generated." )
227241
228242 policy_reporter = PolicyReporter ()
229243 policy_reporter .generate (global_config .output_path , result )
@@ -290,16 +304,23 @@ def find_source(find_args: argparse.Namespace) -> int:
290304
291305def perform_action (action_args : argparse .Namespace ) -> None :
292306 """Perform the indicated action of Macaron."""
307+ rich_handler = access_handler .get_handler ()
293308 match action_args .action :
294309 case "dump-defaults" :
310+ if not action_args .disable_rich_output :
311+ rich_handler .start ("dump-defaults" )
295312 # Create the defaults.ini file in the output dir and exit.
296313 create_defaults (action_args .output_dir , os .getcwd ())
297314 sys .exit (os .EX_OK )
298315
299316 case "verify-policy" :
317+ if not action_args .disable_rich_output :
318+ rich_handler .start ("verify-policy" )
300319 sys .exit (verify_policy (action_args ))
301320
302321 case "analyze" :
322+ if not action_args .disable_rich_output :
323+ rich_handler .start ("analyze" )
303324 if not global_config .gh_token :
304325 logger .error ("GitHub access token not set." )
305326 sys .exit (os .EX_USAGE )
@@ -317,6 +338,8 @@ def perform_action(action_args: argparse.Namespace) -> None:
317338 analyze_slsa_levels_single (action_args )
318339
319340 case "find-source" :
341+ if not action_args .disable_rich_output :
342+ rich_handler .start ("find-source" )
320343 try :
321344 for git_service in GIT_SERVICES :
322345 git_service .load_defaults ()
@@ -329,6 +352,8 @@ def perform_action(action_args: argparse.Namespace) -> None:
329352 find_source (action_args )
330353
331354 case "gen-build-spec" :
355+ if not action_args .disable_rich_output :
356+ rich_handler .start ("gen-build-spec" )
332357 sys .exit (gen_build_spec (action_args ))
333358
334359 case _:
@@ -393,6 +418,13 @@ def main(argv: list[str] | None = None) -> None:
393418 action = "store_true" ,
394419 )
395420
421+ main_parser .add_argument (
422+ "--disable-rich-output" ,
423+ default = False ,
424+ help = "Disable Rich UI output" ,
425+ action = "store_true" ,
426+ )
427+
396428 main_parser .add_argument (
397429 "-o" ,
398430 "--output-dir" ,
@@ -531,7 +563,10 @@ def main(argv: list[str] | None = None) -> None:
531563 )
532564
533565 # Dump the default values.
534- sub_parser .add_parser (name = "dump-defaults" , description = "Dumps the defaults.ini file to the output directory." )
566+ sub_parser .add_parser (
567+ name = "dump-defaults" ,
568+ description = "Dumps the defaults.ini file to the output directory." ,
569+ )
535570
536571 # Verify the Datalog policy.
537572 vp_parser = sub_parser .add_parser (name = "verify-policy" )
@@ -593,65 +628,98 @@ def main(argv: list[str] | None = None) -> None:
593628 main_parser .print_help ()
594629 sys .exit (os .EX_USAGE )
595630
596- if args .verbose :
597- log_level = logging .DEBUG
598- log_format = "%(asctime)s [%(name)s:%(funcName)s:%(lineno)d] [%(levelname)s] %(message)s"
599- else :
600- log_level = logging .INFO
601- log_format = "%(asctime)s [%(levelname)s] %(message)s"
602-
603631 # Set global logging config. We need the stream handler for the initial
604632 # output directory checking log messages.
605- st_handler = logging .StreamHandler (sys .stdout )
606- logging .basicConfig (format = log_format , handlers = [st_handler ], force = True , level = log_level )
633+ st_handler : logging .StreamHandler = logging .StreamHandler (sys .stdout )
634+ rich_handler : RichConsoleHandler = access_handler .set_handler (args .verbose )
635+ if args .disable_rich_output :
636+ if args .verbose :
637+ log_level = logging .DEBUG
638+ log_format = "%(asctime)s [%(name)s:%(funcName)s:%(lineno)d] [%(levelname)s] %(message)s"
639+ else :
640+ log_level = logging .INFO
641+ log_format = "%(asctime)s [%(levelname)s] %(message)s"
642+ st_handler = logging .StreamHandler (sys .stdout )
643+ logging .basicConfig (format = log_format , handlers = [st_handler ], force = True , level = log_level )
644+ else :
645+ if args .verbose :
646+ log_level = logging .DEBUG
647+ log_format = "%(asctime)s [%(name)s:%(funcName)s:%(lineno)d] %(message)s"
648+ else :
649+ log_level = logging .INFO
650+ log_format = "%(asctime)s %(message)s"
651+ rich_handler = access_handler .set_handler (args .verbose )
652+ logging .basicConfig (format = log_format , handlers = [rich_handler ], force = True , level = log_level )
607653
608- # Set the output directory.
609- if not args .output_dir :
610- logger .error ("The output path cannot be empty. Exiting ..." )
611- sys .exit (os .EX_USAGE )
654+ try :
655+ # Set the output directory.
656+ if not args .output_dir :
657+ logger .error ("The output path cannot be empty. Exiting ..." )
658+ sys .exit (os .EX_USAGE )
612659
613- if os .path .isfile (args .output_dir ):
614- logger .error ("The output directory already exists. Exiting ..." )
615- sys .exit (os .EX_USAGE )
660+ if os .path .isfile (args .output_dir ):
661+ logger .error ("The output directory already exists. Exiting ..." )
662+ sys .exit (os .EX_USAGE )
616663
617- if os .path .isdir (args .output_dir ):
618- logger .info ("Setting the output directory to %s" , os .path .relpath (args .output_dir , os .getcwd ()))
619- else :
620- logger .info ("No directory at %s. Creating one ..." , os .path .relpath (args .output_dir , os .getcwd ()))
621- os .makedirs (args .output_dir )
622-
623- # Add file handler to the root logger. Remove stream handler from the
624- # root logger to prevent dependencies printing logs to stdout.
625- debug_log_path = os .path .join (args .output_dir , "debug.log" )
626- log_file_handler = logging .FileHandler (debug_log_path , "w" )
627- log_file_handler .setFormatter (logging .Formatter (log_format ))
628- logging .getLogger ().removeHandler (st_handler )
629- logging .getLogger ().addHandler (log_file_handler )
630-
631- # Add StreamHandler to the Macaron logger only.
632- mcn_logger = logging .getLogger ("macaron" )
633- mcn_logger .addHandler (st_handler )
634-
635- logger .info ("The logs will be stored in debug.log" )
636-
637- # Set Macaron's global configuration.
638- # The path to provenance expectation files will be updated if
639- # set through analyze sub-command.
640- global_config .load (
641- macaron_path = macaron .MACARON_PATH ,
642- output_path = args .output_dir ,
643- build_log_path = os .path .join (args .output_dir , "build_log" ),
644- debug_level = log_level ,
645- local_repos_path = args .local_repos_path ,
646- resources_path = os .path .join (macaron .MACARON_PATH , "resources" ),
647- )
664+ if os .path .isdir (args .output_dir ):
665+ logger .info (
666+ "Setting the output directory to %s" ,
667+ os .path .relpath (args .output_dir , os .getcwd ()),
668+ )
669+ else :
670+ logger .info (
671+ "No directory at %s. Creating one ..." ,
672+ os .path .relpath (args .output_dir , os .getcwd ()),
673+ )
674+ os .makedirs (args .output_dir )
675+
676+ # Add file handler to the root logger. Remove stream handler from the
677+ # root logger to prevent dependencies printing logs to stdout.
678+ debug_log_path = os .path .join (args .output_dir , "debug.log" )
679+ log_file_handler = logging .FileHandler (debug_log_path , "w" )
680+ log_file_handler .setFormatter (logging .Formatter (log_format ))
681+ if args .disable_rich_output :
682+ logging .getLogger ().removeHandler (st_handler )
683+ else :
684+ logging .getLogger ().removeHandler (rich_handler )
685+ logging .getLogger ().addHandler (log_file_handler )
686+
687+ # Add StreamHandler to the Macaron logger only.
688+ mcn_logger = logging .getLogger ("macaron" )
689+ if args .disable_rich_output :
690+ mcn_logger .addHandler (st_handler )
691+ else :
692+ mcn_logger .addHandler (rich_handler )
693+
694+ logger .info ("The logs will be stored in debug.log" )
695+
696+ # Set Macaron's global configuration.
697+ # The path to provenance expectation files will be updated if
698+ # set through analyze sub-command.
699+ global_config .load (
700+ macaron_path = macaron .MACARON_PATH ,
701+ output_path = args .output_dir ,
702+ build_log_path = os .path .join (args .output_dir , "build_log" ),
703+ debug_level = log_level ,
704+ local_repos_path = args .local_repos_path ,
705+ resources_path = os .path .join (macaron .MACARON_PATH , "resources" ),
706+ )
648707
649- # Load the default values from defaults.ini files.
650- if not load_defaults (args .defaults_path ):
651- logger .error ("Exiting because the defaults configuration could not be loaded." )
652- sys .exit (os .EX_NOINPUT )
708+ # Load the default values from defaults.ini files.
709+ if not load_defaults (args .defaults_path ):
710+ logger .error ("Exiting because the defaults configuration could not be loaded." )
711+ sys .exit (os .EX_NOINPUT )
653712
654- perform_action (args )
713+ perform_action (args )
714+ except KeyboardInterrupt :
715+ if not args .disable_rich_output :
716+ rich_handler .error ("Macaron failed: Interrupted by user" )
717+ sys .exit (os .EX_SOFTWARE )
718+ finally :
719+ if args .disable_rich_output :
720+ st_handler .close ()
721+ else :
722+ rich_handler .close ()
655723
656724
657725def _get_token_from_dict_or_env (token : str , token_dict : dict [str , str ]) -> str :
0 commit comments