6161import sys
6262import arrow
6363
64+ from io import StringIO
65+
6466from blackduck import Client
6567from pprint import pprint ,pformat
6668
@@ -75,12 +77,48 @@ def __init__(self, args):
7577 with open (args .token_file , 'r' ) as tf :
7678 self .access_token = tf .readline ().strip ()
7779 self .no_verify = args .no_verify
80+ self .reprocess_run_file = args .reprocess_run_file
7881 self .connect ()
79- self .init_project_data (args )
82+ if self .reprocess_run_file :
83+ self .load_project_data ()
84+ else :
85+ self .init_project_data (args )
8086
8187 def connect (self ):
8288 self .client = Client (base_url = self .base_url , token = self .access_token , verify = self .no_verify , timeout = 60.0 , retries = 4 )
83-
89+
90+ def load_project_data (self ):
91+ with open (self .reprocess_run_file , "r" ) as f :
92+ data = json .load (f )
93+ self .project_data = data
94+ self .project_data .pop ('log' , None )
95+ discard = list ()
96+ for name , subproject in self .project_data ['subprojects' ].items ():
97+ if not self .has_errors (subproject ):
98+ discard .append (name )
99+ else :
100+ self .project_data ['subprojects' ][name ].pop ('log' , None )
101+ self .project_data ['subprojects' ][name ].pop ('status' , None )
102+ self .project_data ['subprojects' ][name ].pop ('scan_results' ,None )
103+ for name in discard :
104+ del self .project_data ['subprojects' ][name ]
105+
106+ def has_errors (self , subproject ):
107+ structure = False
108+ runtime = False
109+ if subproject ['status' ] != 'PRESENT' :
110+ structure = True
111+ if not subproject .get ('scan_results' , None ):
112+ runtime = True
113+ else :
114+ rcodes = [r ['scan_results' ]['returncode' ] for r in subproject ['scan_results' ] if r .get ('scan_results' , None )]
115+ if sum (rcodes ) > 0 :
116+ runtime = True
117+ if structure or runtime :
118+ return True
119+ else :
120+ return False
121+
84122 def init_project_data (self ,args ):
85123 self .project_data = dict ()
86124 self .project_data ['project_name' ] = args .project_name
@@ -435,15 +473,51 @@ def proceed(self):
435473 self .validate_project_structure ()
436474 self .scan_container_images ()
437475
476+ def write_failure_report (data , output_file_name ):
477+ s = StringIO ()
478+ subprojects = data ['subprojects' ]
479+ for subproject_name , subproject in subprojects .items ():
480+ structure = False
481+ runtime = False
482+ if subproject ['status' ] != 'PRESENT' :
483+ structure = True
484+ if not subproject .get ('scan_results' , None ):
485+ runtime = True
486+ else :
487+ rcodes = [r ['scan_results' ]['returncode' ] for r in subproject ['scan_results' ] if r .get ('scan_results' , None )]
488+ if sum (rcodes ) > 0 :
489+ runtime = True
490+ if structure or runtime :
491+ print (f"\n Status for { subproject ['project_name' ]} { subproject ['version_name' ]} " , file = s )
492+ print (f"\t Structural failures present { structure } " , file = s )
493+ print (f"\t Runtime failures present { runtime } \n " , file = s )
494+
495+ if subproject ['status' ] != 'PRESENT' :
496+ for line in subproject ['log' ]:
497+ print ('\t ' , line , file = s )
498+ scan_results = subproject .get ('scan_results' ,[])
499+ if len (scan_results ) == 0 :
500+ print ("No scans were performed" , file = s )
501+ else :
502+ for invocation in scan_results :
503+ returncode = invocation ['scan_results' ]['returncode' ]
504+ if returncode > 0 :
505+ print (f"\n \t Scan for { invocation ['name' ]} failed with returncode { returncode } \n " , file = s )
506+ stdout = invocation ['scan_results' ]['stdout' ].split ('\n ' )
507+ for line in stdout :
508+ if 'ERROR' in line and 'certificates' not in line :
509+ print ('\t ' , line , file = s )
510+ with open (output_file_name , "w" ) as f :
511+ f .write (s .getvalue ())
438512
439513def parse_command_args ():
440514
441515 parser = argparse .ArgumentParser (description = program_description , formatter_class = argparse .RawTextHelpFormatter )
442516 parser .add_argument ("-u" , "--base-url" , required = True , help = "Hub server URL e.g. https://your.blackduck.url" )
443517 parser .add_argument ("-t" , "--token-file" , required = True , help = "File containing access token" )
444518 parser .add_argument ("-pg" , "--project_group" , required = False , default = 'Multi-Image' , help = "Project Group to be used" )
445- parser .add_argument ("-p" , "--project-name" , required = True , help = "Project Name" )
446- parser .add_argument ("-pv" , "--version-name" , required = True , help = "Project Version Name" )
519+ parser .add_argument ("-p" , "--project-name" , required = False , help = "Project Name" )
520+ parser .add_argument ("-pv" , "--version-name" , required = False , help = "Project Version Name" )
447521 group = parser .add_mutually_exclusive_group ()
448522 group .add_argument ("-sp" , "--subproject-list" , required = False , help = "List of subprojects to generate with subproject:container:tag" )
449523 group .add_argument ("-ssf" , "--subproject-spec-file" , required = False , help = "Excel or txt file containing subproject specification" )
@@ -456,7 +530,13 @@ def parse_command_args():
456530 parser .add_argument ("--strict" , action = 'store_true' , help = "Fail if existing (sub)project versions already exist" )
457531 parser .add_argument ("--binary" , action = 'store_true' , help = "Use binary scan for analysis" )
458532 parser .add_argument ("-ifm" , "--individual-file-matching" , action = 'store_true' , help = "Turn Individual file matching on" )
459- return parser .parse_args ()
533+ parser .add_argument ("--reprocess-run-file" , help = "Reprocess Failures from previous run report." )
534+ args = parser .parse_args ()
535+ if not args .reprocess_run_file and not (args .project_name and args .version_name ):
536+ parser .error ("[ -p/--project-name and -pv/--version-name ] or --reprocess-run-file are required" )
537+ if args .reprocess_run_file and (args .project_name or args .version_name ):
538+ parser .error ("[ -p/--project-name and -pv/--version-name ] or --reprocess-run-file are required" )
539+ return args
460540
461541def main ():
462542 from datetime import datetime
@@ -467,28 +547,14 @@ def main():
467547 mipm .proceed ()
468548
469549 if not args .remove :
470- filename_complete = f"{ args .project_name } -{ args .version_name } -{ timestamp } -full.json"
471- filename_failures = f"{ args .project_name } -{ args .version_name } -{ timestamp } -failures.json"
550+ filename_base = f"{ mipm .project_data ['project_name' ]} -{ mipm .project_data ['version_name' ]} "
551+ filename_complete = f"{ filename_base } -{ timestamp } -full.json"
552+ filename_failure_report = f"{ filename_base } -{ timestamp } -failures.txt"
472553 # write full processing log
473554 with open (filename_complete , "w" ) as f :
474555 json .dump (mipm .project_data , f , indent = 2 )
475556
476- failures = list ()
477- for sname , sub in mipm .project_data ['subprojects' ].items ():
478- structure = False
479- runtime = False
480- if sub ['status' ] != 'PRESENT' :
481- structure = True
482- if not sub .get ('scan_results' , None ):
483- runtime = True
484- else :
485- rcodes = [r ['scan_results' ]['returncode' ] for r in sub ['scan_results' ] if r .get ('scan_results' , None )]
486- if sum (rcodes ) > 0 :
487- runtime = True
488- if structure or runtime :
489- failures .append (sub )
490- with open (filename_failures , "w" ) as f :
491- json .dump (failures , f , indent = 2 )
557+ write_failure_report (mipm .project_data , filename_failure_report )
492558
493559if __name__ == "__main__" :
494560 sys .exit (main ())
0 commit comments