Skip to content

Commit bce5e70

Browse files
Create refresh_project_copyrights.py
New example to bulk refresh copyrights in components
1 parent 47cab2e commit bce5e70

File tree

1 file changed

+253
-0
lines changed

1 file changed

+253
-0
lines changed
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
# Iterate through components within a named project (or all) and named version (or all)
2+
# and refresh the copyrights of each - equivalent to clicking the UI refresh button
3+
# Use --debug for some feedback on progress
4+
# Ian Ashworth, May 2025
5+
#
6+
import http.client
7+
from sys import api_version
8+
import csv
9+
import datetime
10+
from blackduck import Client
11+
import argparse
12+
import logging
13+
from pprint import pprint
14+
import array as arr
15+
16+
http.client._MAXHEADERS = 1000
17+
18+
logging.basicConfig(
19+
level=logging.INFO,
20+
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
21+
)
22+
23+
def RepDebug(level, msg):
24+
if hasattr(args, 'debug') and level <= args.debug:
25+
print("dbg{" + str(level) + "} " + msg)
26+
return True
27+
return False
28+
29+
30+
# Parse command line arguments
31+
parser = argparse.ArgumentParser("Refresh copyrights for project/version components")
32+
33+
parser.add_argument("--base-url", required=True, help="BD Hub server URL e.g. https://your.blackduck.url")
34+
parser.add_argument("--token-file", dest='token_file', required=True, help="File containing your access token")
35+
36+
parser.add_argument("--dump-data", dest='dump_data', action='store_true', help="Retain analysed data")
37+
parser.add_argument("--csv-file", dest='csv_file', help="File name for dumped data formatted as CSV")
38+
39+
parser.add_argument("--project", dest='project_name', help="Project name")
40+
parser.add_argument("--version", dest='version_name', help="Version name")
41+
42+
parser.add_argument("--max-projects", dest='max_projects', type=int, help="Maximum projects to inspect else all")
43+
parser.add_argument("--max-versions-per-project", dest='max_versions_per_project', type=int, help="Maximum versions per project to inspect else all")
44+
parser.add_argument("--max-components", dest='max_components', type=int, help="Maximum components to inspect in total else all")
45+
46+
parser.add_argument("--debug", dest='debug', type=int, default=0, help="Debug verbosity (0=none)")
47+
48+
parser.add_argument("--no-verify", dest='verify', action='store_false', help="Disable TLS certificate verification")
49+
args = parser.parse_args()
50+
51+
# open the access token file
52+
with open(args.token_file, 'r') as tf:
53+
access_token = tf.readline().strip()
54+
55+
# access the Black Duck platform
56+
bd = Client(base_url=args.base_url, token=access_token, verify=args.verify)
57+
58+
# initialise
59+
all_my_comp_data = []
60+
my_statistics = {}
61+
62+
63+
# version of components API to call
64+
comp_api_version = 6
65+
66+
comp_accept_version = "application/vnd.blackducksoftware.bill-of-materials-" + str(comp_api_version) + "+json"
67+
#comp_accept_version = "application/json"
68+
69+
comp_content_type = comp_accept_version
70+
71+
# header keys
72+
comp_lc_keys = {}
73+
comp_lc_keys['accept'] = comp_accept_version
74+
comp_lc_keys['content-type'] = comp_accept_version
75+
76+
# keyword arguments to pass to API call
77+
comp_kwargs={}
78+
comp_kwargs['headers'] = comp_lc_keys
79+
80+
81+
# version of API to call
82+
refresh_api_version = 4
83+
84+
refresh_accept_version = "application/vnd.blackducksoftware.copyright-" + str(refresh_api_version) + "+json"
85+
#refresh_accept_version = "application/json"
86+
87+
refresh_content_type = refresh_accept_version
88+
89+
90+
# header keys
91+
refresh_lc_keys = {}
92+
refresh_lc_keys['accept'] = refresh_accept_version
93+
refresh_lc_keys['content-type'] = refresh_accept_version
94+
95+
# keyword arguments to pass to API call
96+
refresh_kwargs={}
97+
refresh_kwargs['headers'] = refresh_lc_keys
98+
99+
100+
# zero our main counters
101+
my_statistics['_cntProjects'] = 0
102+
my_statistics['_cntVersions'] = 0
103+
my_statistics['_cntComponents'] = 0
104+
my_statistics['_cntRefresh'] = 0
105+
106+
# record any control values
107+
if args.project_name:
108+
my_statistics['_namedProject'] = args.project_name
109+
if args.version_name:
110+
my_statistics['_namedVersion'] = args.version_name
111+
112+
if args.max_projects:
113+
my_statistics['_maxProjects'] = args.max_projects
114+
if args.max_versions_per_project:
115+
my_statistics['_maxVersionsPerProject'] = args.max_versions_per_project
116+
if args.max_components:
117+
my_statistics['_maxComponents'] = args.max_components
118+
119+
now = datetime.datetime.now()
120+
print('Started: %s' % now.strftime("%Y-%m-%d %H:%M:%S"))
121+
122+
# check named project of specific interest
123+
if args.project_name:
124+
params = {
125+
'q': [f"name:{args.project_name}"]
126+
}
127+
projects = [p for p in bd.get_resource('projects', params=params) if p['name'] == args.project_name]
128+
129+
# must exist
130+
assert len(projects) > 0, f"There should be at least one - {len(projects)} project(s) noted"
131+
else:
132+
# all projects are in scope
133+
projects = bd.get_resource('projects')
134+
135+
# loop through projects list
136+
for this_project in projects:
137+
138+
# check if we have hit any limit
139+
if args.max_components and my_statistics['_cntComponents'] >= args.max_components:
140+
break
141+
142+
if args.max_projects and my_statistics['_cntProjects'] >= args.max_projects:
143+
break
144+
145+
my_statistics['_cntProjects'] += 1
146+
RepDebug(1, '## Project %d: %s' % (my_statistics['_cntProjects'], this_project['name']))
147+
148+
if args.version_name:
149+
# note the specific project version of interest
150+
params = {
151+
'q': [f"versionName:{args.version_name}"]
152+
}
153+
versions = [v for v in bd.get_resource('versions', this_project, params=params) if v['versionName'] == args.version_name]
154+
155+
# it must exist
156+
assert len(versions) > 0, f"There should be at least one - {len(versions)} version(s) noted"
157+
else:
158+
# all versions for this project are in scope
159+
versions = bd.get_resource('versions', this_project)
160+
161+
nVersionsPerProject = 0
162+
163+
for this_version in versions:
164+
165+
# check if we have hit any limit
166+
if args.max_components and my_statistics['_cntComponents'] >= args.max_components:
167+
# exit component loop - at the limit
168+
break
169+
170+
if args.max_versions_per_project and nVersionsPerProject >= args.max_versions_per_project:
171+
# exit loop - at the version per project limit
172+
break
173+
174+
nVersionsPerProject += 1
175+
my_statistics['_cntVersions'] += 1
176+
177+
# Announce
178+
# logging.debug(f"Found {this_project['name']}:{this_version['versionName']}")
179+
RepDebug(3, ' Version: %s' % this_version['versionName'])
180+
181+
182+
# iterate through all components for this project version
183+
for this_comp_data in bd.get_resource('components', this_version, **comp_kwargs):
184+
185+
if args.max_components and my_statistics['_cntComponents'] >= args.max_components:
186+
# exit component loop - at the limit
187+
break
188+
189+
my_statistics['_cntComponents'] += 1
190+
RepDebug(4, ' Component: %s' % this_comp_data['componentName'])
191+
192+
# refresh the copyrights for this component
193+
url = this_comp_data['origins'][0]['origin']
194+
url += "/copyrights-refresh"
195+
196+
response = bd.session.put(url, data=None, **refresh_kwargs)
197+
RepDebug(5,'Refresh response %s' % response)
198+
199+
inputExternalIds = this_comp_data['inputExternalIds'][0]
200+
RepDebug(2, ' ID: %s' % inputExternalIds)
201+
202+
my_statistics['_cntRefresh'] += 1
203+
204+
# if recording the data - perhaps outputting to a CSV file
205+
if args.dump_data:
206+
my_data = {}
207+
my_data['componentName'] = this_comp_data['componentName']
208+
my_data['componentVersion'] = this_comp_data['componentVersionName']
209+
210+
if hasattr(args, 'debug') and 5 <= args.debug:
211+
pprint(my_data)
212+
213+
# add to our list
214+
all_my_comp_data.append(my_data)
215+
216+
217+
# end of processing loop
218+
219+
now = datetime.datetime.now()
220+
print('Finished: %s' % now.strftime("%Y-%m-%d %H:%M:%S"))
221+
print('Summary:')
222+
pprint(my_statistics)
223+
224+
# if dumping data
225+
if args.dump_data:
226+
# if outputting to a CSV file
227+
if args.csv_file:
228+
'''Note: See the BD API doc and in particular .../api-doc/public.html#_bom_vulnerability_endpoints
229+
for a complete list of the fields available. The below code shows a subset of them just to
230+
illustrate how to write out the data into a CSV format.
231+
'''
232+
logging.info(f"Exporting {len(all_my_comp_data)} records to CSV file {args.csv_file}")
233+
234+
with open(args.csv_file, 'w') as csv_f:
235+
field_names = [
236+
'Component',
237+
'Component Version'
238+
]
239+
240+
writer = csv.DictWriter(csv_f, fieldnames=field_names)
241+
writer.writeheader()
242+
243+
for my_comp_data in all_my_comp_data:
244+
row_data = {
245+
'Component': my_comp_data['componentName'],
246+
'Component Version': my_comp_data['componentVersion']
247+
}
248+
writer.writerow(row_data)
249+
else:
250+
# print to screen
251+
pprint(all_my_comp_data)
252+
253+
#end

0 commit comments

Comments
 (0)