4
4
# Ian Ashworth, May 2025
5
5
#
6
6
import http .client
7
+ import signal
7
8
from sys import api_version
8
9
import sys
9
10
import csv
18
19
19
20
http .client ._MAXHEADERS = 1000
20
21
22
+ job_status = 0
23
+
21
24
logging .basicConfig (
22
25
level = logging .INFO ,
23
26
format = "[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
24
27
)
25
28
29
+ # initialise
30
+ all_my_comp_data = []
31
+ my_statistics = {}
32
+
33
+
26
34
def RepDebug (level , msg ):
27
35
if hasattr (args , 'debug' ) and level <= args .debug :
28
36
print ("dbg{" + str (level ) + "} " + msg )
@@ -33,6 +41,59 @@ def RepWarning(msg):
33
41
print ("WARNING: " + msg )
34
42
return True
35
43
44
+ def CompleteTask (job_status ):
45
+ now = datetime .datetime .now ()
46
+ my_statistics ['_jobStatus' ] = job_status
47
+
48
+ print ('Finished: %s' % now .strftime ("%Y-%m-%d %H:%M:%S" ))
49
+ print ('Summary:' )
50
+ pprint (my_statistics )
51
+
52
+ # if dumping data
53
+ if args .dump_data :
54
+ # if outputting to a CSV file
55
+ if args .csv_file :
56
+ '''Note: See the BD API doc and in particular .../api-doc/public.html#_bom_vulnerability_endpoints
57
+ for a complete list of the fields available. The below code shows a subset of them just to
58
+ illustrate how to write out the data into a CSV format.
59
+ '''
60
+ logging .info (f"Exporting { len (all_my_comp_data )} records to CSV file { args .csv_file } " )
61
+
62
+ with open (args .csv_file , 'w' ) as csv_f :
63
+ field_names = [
64
+ 'Component' ,
65
+ 'Component Version' ,
66
+ 'Status' ,
67
+ 'Url'
68
+ ]
69
+
70
+ writer = csv .DictWriter (csv_f , fieldnames = field_names )
71
+ writer .writeheader ()
72
+
73
+ for my_comp_data in all_my_comp_data :
74
+ row_data = {
75
+ 'Component' : my_comp_data ['componentName' ],
76
+ 'Component Version' : my_comp_data ['componentVersion' ],
77
+ 'Status' : my_comp_data ['status' ],
78
+ 'Url' : my_comp_data ['url' ]
79
+ }
80
+ writer .writerow (row_data )
81
+ else :
82
+ # print to screen
83
+ pprint (all_my_comp_data )
84
+
85
+ def SignalHandler (sig , frame ):
86
+ # Complete the work
87
+ print ("Ctrl+C detected!" )
88
+
89
+ # tidy up and complete the job
90
+ CompleteTask (1 )
91
+ sys .exit (job_status )
92
+
93
+ # ------------------------------------------------------------------------------
94
+ # register the signal handler
95
+ signal .signal (signal .SIGINT , SignalHandler )
96
+
36
97
37
98
# Parse command line arguments
38
99
parser = argparse .ArgumentParser ("Refresh copyrights for project/version components" )
@@ -46,10 +107,12 @@ def RepWarning(msg):
46
107
parser .add_argument ("--project" , dest = 'project_name' , help = "Project name" )
47
108
parser .add_argument ("--version" , dest = 'version_name' , help = "Version name" )
48
109
49
- parser .add_argument ("--max-projects" , dest = 'max_projects' , type = int , help = "Maximum projects to inspect else all" )
110
+ parser .add_argument ("--max-projects" , dest = 'max_projects' , type = int , help = "Maximum number of projects to inspect else all" )
50
111
parser .add_argument ("--max-versions-per-project" , dest = 'max_versions_per_project' , type = int , help = "Maximum versions per project to inspect else all" )
51
112
parser .add_argument ("--max-components" , dest = 'max_components' , type = int , help = "Maximum components to inspect in total else all" )
52
113
114
+ parser .add_argument ("--skip-projects" , dest = 'skip_projects' , type = int , help = "Skip first 'n' projects to inspect" )
115
+
53
116
parser .add_argument ("--debug" , dest = 'debug' , type = int , default = 0 , help = "Debug verbosity (0=none 'n'=level)" )
54
117
parser .add_argument ("--dryrun" , dest = 'dry_run' , type = int , default = 0 , help = "Dry run test (0=no 1=yes)" )
55
118
@@ -72,11 +135,10 @@ def RepWarning(msg):
72
135
retries = args .retries ,
73
136
)
74
137
75
- # initialise
76
- all_my_comp_data = []
77
- my_statistics = {}
78
138
79
139
140
+ str_unknown = "n/a"
141
+
80
142
# version of components API to call
81
143
comp_api_version = 6
82
144
@@ -118,10 +180,13 @@ def RepWarning(msg):
118
180
my_statistics ['_cntProjects' ] = 0
119
181
my_statistics ['_cntVersions' ] = 0
120
182
my_statistics ['_cntComponents' ] = 0
183
+ my_statistics ['_cntOrigins' ] = 0
184
+
121
185
my_statistics ['_cntRefresh' ] = 0
122
186
my_statistics ['_cntNoOrigins' ] = 0
123
187
my_statistics ['_cntNoIDs' ] = 0
124
-
188
+ my_statistics ['_cntSkippedProjects' ] = 0
189
+ my_statistics ['_jobStatus' ] = 0
125
190
126
191
# record any control values
127
192
if args .project_name :
@@ -152,18 +217,33 @@ def RepWarning(msg):
152
217
# all projects are in scope
153
218
projects = bd .get_resource ('projects' )
154
219
220
+
221
+ cnt_project = 0
222
+ cnt_call = 0
223
+
155
224
# loop through projects list
156
225
for this_project in projects :
157
226
227
+ cnt_project += 1
228
+
229
+ # check if we are skipping over this project
230
+ if args .skip_projects and cnt_project <= args .skip_projects :
231
+ my_statistics ['_cntSkippedProjects' ] += 1
232
+ RepDebug (1 , 'Skipping project [%d] [%s]' % (cnt_project , this_project ['name' ]))
233
+ continue
234
+
158
235
# check if we have hit any limit
159
236
if args .max_components and my_statistics ['_cntComponents' ] >= args .max_components :
237
+ RepDebug (1 , 'Reached component limit [%d]' % args .max_components )
160
238
break
161
239
162
240
if args .max_projects and my_statistics ['_cntProjects' ] >= args .max_projects :
241
+ RepDebug (1 , 'Reached project limit [%d]' % args .max_projects )
163
242
break
164
243
244
+ # process this project
165
245
my_statistics ['_cntProjects' ] += 1
166
- RepDebug (1 , '## Project %d: %s ' % (my_statistics [ '_cntProjects' ] , this_project ['name' ]))
246
+ RepDebug (1 , '## Project: [%d] [%s] ' % (cnt_project , this_project ['name' ]))
167
247
168
248
if args .version_name :
169
249
# note the specific project version of interest
@@ -184,60 +264,80 @@ def RepWarning(msg):
184
264
185
265
# check if we have hit any limit
186
266
if args .max_components and my_statistics ['_cntComponents' ] >= args .max_components :
187
- # exit component loop - at the limit
267
+ RepDebug ( 1 , 'Reached component limit [%d]' % args . max_components )
188
268
break
189
269
190
270
if args .max_versions_per_project and nVersionsPerProject >= args .max_versions_per_project :
191
- # exit loop - at the version per project limit
271
+ RepDebug ( 1 , 'Reached versions per project limit [%d]' % args . max_versions_per_project )
192
272
break
193
273
194
274
nVersionsPerProject += 1
195
275
my_statistics ['_cntVersions' ] += 1
196
276
197
277
# Announce
198
278
# logging.debug(f"Found {this_project['name']}:{this_version['versionName']}")
199
- RepDebug (3 , ' Version: %s ' % this_version ['versionName' ])
279
+ RepDebug (3 , ' Version: [%s] ' % this_version ['versionName' ])
200
280
201
281
202
282
# iterate through all components for this project version
203
283
for this_comp_data in bd .get_resource ('components' , this_version , ** comp_kwargs ):
204
284
205
285
if args .max_components and my_statistics ['_cntComponents' ] >= args .max_components :
206
- # exit component loop - at the limit
207
286
break
208
287
209
288
my_statistics ['_cntComponents' ] += 1
210
- comp_label = "{} ({})" .format (this_comp_data ['componentName' ], this_comp_data ['componentVersionName' ])
211
289
212
- RepDebug (4 , ' Component: %s' % comp_label )
290
+ if this_comp_data .get ("componentName" ):
291
+ comp_name = this_comp_data ['componentName' ]
292
+ else :
293
+ comp_name = str_unknown
294
+
295
+ if this_comp_data .get ("componentVersionName" ):
296
+ comp_version_name = this_comp_data ['componentVersionName' ]
297
+ else :
298
+ comp_version_name = str_unknown
299
+
300
+ comp_label = "{} ({})" .format (comp_name , comp_version_name )
301
+
302
+ RepDebug (4 , ' Component: [%s]' % comp_label )
213
303
214
304
if this_comp_data ['inputExternalIds' ].__len__ () > 0 :
215
305
inputExternalIds = this_comp_data ['inputExternalIds' ][0 ]
216
306
else :
217
307
my_statistics ['_cntNoIDs' ] += 1
218
- inputExternalIds = "n/a"
219
- RepDebug (2 , ' ID: %s ' % inputExternalIds )
308
+ inputExternalIds = str_unknown
309
+ RepDebug (2 , ' ID: [%s] ' % inputExternalIds )
220
310
221
311
222
- # refresh the copyrights for this component
312
+ # refresh the copyrights for this component-origin
223
313
if this_comp_data ['origins' ].__len__ () > 0 :
224
314
225
315
n_origin = 0
226
316
227
317
for this_origin in this_comp_data ['origins' ]:
228
318
229
319
n_origin += 1
230
- origin_id = this_origin ['externalId' ]
320
+ my_statistics ['_cntOrigins' ] += 1
321
+
322
+ if this_origin .get ('externalId' ):
323
+ origin_id = this_origin ['externalId' ]
324
+ else :
325
+ origin_id = str_unknown
326
+
231
327
url = this_origin ['origin' ]
232
328
233
329
# refresh with end point
234
330
url += "/copyrights-refresh"
235
331
236
332
status = - 1
333
+ cnt_call += 1
334
+ call_id = "{}.{}" .format (cnt_project , cnt_call )
237
335
238
336
if args .dry_run != 0 :
239
- RepDebug (1 , " DryRun: no=%d origin=%s url=%s" % (n_origin , origin_id , url ))
337
+ RepDebug (2 , ' DryRun: %s - origin - no [%d] id [%s] url [%s]' % (call_id , n_origin , origin_id , url ))
240
338
else :
339
+ RepDebug (3 ,
340
+ ' Origin: %s - origin - no [%d] id [%s] url [%s]' % (call_id , n_origin , origin_id , url ))
241
341
try :
242
342
response = bd .session .put (url , data = None , ** refresh_kwargs )
243
343
RepDebug (5 ,'Refresh response: origin [%s] [%s]' % (this_origin , response ))
@@ -274,8 +374,8 @@ def RepWarning(msg):
274
374
# if recording the data
275
375
if args .dump_data :
276
376
my_data = {}
277
- my_data ['componentName' ] = this_comp_data [ 'componentName' ]
278
- my_data ['componentVersion' ] = this_comp_data [ 'componentVersionName' ]
377
+ my_data ['componentName' ] = comp_name
378
+ my_data ['componentVersion' ] = comp_version_name
279
379
my_data ['status' ] = status
280
380
my_data ['url' ] = url
281
381
@@ -287,42 +387,6 @@ def RepWarning(msg):
287
387
288
388
# end of processing loop
289
389
290
- now = datetime .datetime .now ()
291
- print ('Finished: %s' % now .strftime ("%Y-%m-%d %H:%M:%S" ))
292
- print ('Summary:' )
293
- pprint (my_statistics )
294
-
295
- # if dumping data
296
- if args .dump_data :
297
- # if outputting to a CSV file
298
- if args .csv_file :
299
- '''Note: See the BD API doc and in particular .../api-doc/public.html#_bom_vulnerability_endpoints
300
- for a complete list of the fields available. The below code shows a subset of them just to
301
- illustrate how to write out the data into a CSV format.
302
- '''
303
- logging .info (f"Exporting { len (all_my_comp_data )} records to CSV file { args .csv_file } " )
304
-
305
- with open (args .csv_file , 'w' ) as csv_f :
306
- field_names = [
307
- 'Component' ,
308
- 'Component Version' ,
309
- 'Status' ,
310
- 'Url'
311
- ]
312
-
313
- writer = csv .DictWriter (csv_f , fieldnames = field_names )
314
- writer .writeheader ()
315
-
316
- for my_comp_data in all_my_comp_data :
317
- row_data = {
318
- 'Component' : my_comp_data ['componentName' ],
319
- 'Component Version' : my_comp_data ['componentVersion' ],
320
- 'Status' : my_comp_data ['status' ],
321
- 'Url' : my_comp_data ['url' ]
322
- }
323
- writer .writerow (row_data )
324
- else :
325
- # print to screen
326
- pprint (all_my_comp_data )
390
+ CompleteTask (0 )
327
391
328
392
#end
0 commit comments