Skip to content

Commit 9b63b78

Browse files
committed
Functional requirement satisfied
1 parent 96feab6 commit 9b63b78

File tree

1 file changed

+66
-12
lines changed

1 file changed

+66
-12
lines changed

examples/client/file_hierarchy_report.py

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
'''
2929

3030
import argparse
31+
import csv
3132
import logging
3233
import sys
3334
import io
@@ -47,15 +48,8 @@
4748
'''
4849

4950
# BD report general
50-
BLACKDUCK_REPORT_MEDIATYPE = "application/vnd.blackducksoftware.report-4+json"
51-
blackduck_report_download_api = "/api/projects/{projectId}/versions/{projectVersionId}/reports/{reportId}/download"
52-
# BD version details report
53-
blackduck_create_version_report_api = "/api/versions/{projectVersionId}/reports"
54-
blackduck_version_report_filename = "./blackduck_version_report_for_{projectVersionId}.zip"
55-
# Consolidated report
5651
BLACKDUCK_VERSION_MEDIATYPE = "application/vnd.blackducksoftware.status-4+json"
5752
BLACKDUCK_VERSION_API = "/api/current-version"
58-
REPORT_DIR = "./blackduck_component_source_report"
5953
# Retries to wait for BD report creation. RETRY_LIMIT can be overwritten by the script parameter.
6054
RETRY_LIMIT = 30
6155
RETRY_TIMER = 30
@@ -122,7 +116,7 @@ def create_version_details_report(bd, version):
122116
assert location, "Hmm, this does not make sense. If we successfully created a report then there needs to be a location where we can get it from"
123117
return location
124118

125-
def download_report(bd, location, retries):
119+
def download_report(bd, location, retries, timeout):
126120
report_id = location.split("/")[-1]
127121
logging.debug(f"Report location {location}")
128122
url_data = location.split('/')
@@ -142,10 +136,10 @@ def download_report(bd, location, retries):
142136
logging.error("Ruh-roh, not sure what happened here")
143137
return None
144138
else:
145-
logging.debug(f"Report status request {response.status_code} {report_status} ,waiting {retries} seconds then retrying...")
146-
time.sleep(60)
139+
logging.debug(f"Report status request {response.status_code} {report_status} ,waiting {timeout} seconds then retrying...")
140+
time.sleep(timeout)
147141
retries -= 1
148-
return download_report(bd, location, retries)
142+
return download_report(bd, location, retries, timeout)
149143
else:
150144
logging.debug(f"Failed to retrieve report {report_id} after multiple retries")
151145
return None
@@ -158,6 +152,47 @@ def get_blackduck_version(hub_client):
158152
else:
159153
sys.exit(f"Get BlackDuck version failed with status {res.status_code}")
160154

155+
def reduce(path_set):
156+
path_set.sort()
157+
for path in path_set:
158+
if len(path) < 3:
159+
continue
160+
index = path_set.index(path)
161+
while index + 1 < len(path_set) and path in path_set[index+1]:
162+
logging.debug(f"{path} is in {path_set[index+1]} deleting the sub-path from the list")
163+
path_set.pop(index+1)
164+
return path_set
165+
166+
def trim_version_report(version_report, reduced_path_set):
167+
file_bom_entries = version_report['detailedFileBomViewEntries']
168+
aggregate_bom_view_entries = version_report['aggregateBomViewEntries']
169+
170+
reduced_file_bom_entries = [e for e in file_bom_entries if f"{e.get('archiveContext', "")}!{e['path']}" in reduced_path_set]
171+
version_report['detailedFileBomViewEntries'] = reduced_file_bom_entries
172+
173+
component_identifiers = [f"{e['projectId']}:{e['versionId']}" for e in reduced_file_bom_entries]
174+
deduplicated = list(dict.fromkeys(component_identifiers))
175+
176+
reduced_aggregate_bom_view_entries = [e for e in aggregate_bom_view_entries if f"{e['producerProject']['id']}:{e['producerReleases'][0]['id']}" in deduplicated]
177+
version_report['aggregateBomViewEntries'] = reduced_aggregate_bom_view_entries
178+
179+
def write_output_file(version_report, output_file):
180+
if output_file.lower().endswith(".csv"):
181+
logging.info(f"Writing CSV output into {output_file}")
182+
field_names = list(version_report['aggregateBomViewEntries'][0].keys())
183+
with open(output_file, "w") as f:
184+
writer = csv.DictWriter(f, fieldnames = field_names)
185+
writer.writeheader()
186+
writer.writerows(version_report['aggregateBomViewEntries'])
187+
188+
return
189+
# If it's neither, then .json
190+
if not output_file.lower().endswith(".json"):
191+
output_file += ".json"
192+
logging.info(f"Writing JSON output into {output_file}")
193+
with open(output_file,"w") as f:
194+
json.dump(version_report, f)
195+
161196
def parse_command_args():
162197
parser = argparse.ArgumentParser(description=program_description, formatter_class=argparse.RawTextHelpFormatter)
163198
parser.add_argument("-u", "--base-url", required=True, help="Hub server URL e.g. https://your.blackduck.url")
@@ -166,8 +201,10 @@ def parse_command_args():
166201
parser.add_argument("-d", "--debug", action='store_true', help="Set debug output on")
167202
parser.add_argument("-pn", "--project-name", required=True, help="Project Name")
168203
parser.add_argument("-pv", "--project-version-name", required=True, help="Project Version Name")
204+
parser.add_argument("-o", "--output-file", required=False, help="File name to write output. File extension determines format .json and .csv, json is the default.")
169205
parser.add_argument("-kh", "--keep_hierarchy", action='store_true', help="Set to keep all entries in the sources report. Will not remove components found under others.")
170206
parser.add_argument("--report-retries", metavar="", type=int, default=RETRY_LIMIT, help="Retries for receiving the generated BlackDuck report. Generating copyright report tends to take longer minutes.")
207+
parser.add_argument("--report-timeout", metavar="", type=int, default=RETRY_TIMER, help="Wait time between subsequent download attempts.")
171208
parser.add_argument("--timeout", metavar="", type=int, default=60, help="Timeout for REST-API. Some API may take longer than the default 60 seconds")
172209
parser.add_argument("--retries", metavar="", type=int, default=4, help="Retries for REST-API. Some API may need more retries than the default 4 times")
173210
return parser.parse_args()
@@ -176,6 +213,9 @@ def main():
176213
args = parse_command_args()
177214
with open(args.token_file, 'r') as tf:
178215
token = tf.readline().strip()
216+
output_file = args.output_file
217+
if not args.output_file:
218+
output_file = f"{args.project_name}-{args.project_version_name}.json".replace(" ","_")
179219
try:
180220
log_config(args.debug)
181221
hub_client = Client(token=token,
@@ -187,7 +227,7 @@ def main():
187227
project = find_project_by_name(hub_client, args.project_name)
188228
version = find_project_version_by_name(hub_client, project, args.project_version_name)
189229
location = create_version_details_report(hub_client, version)
190-
report_zip = download_report(hub_client, location, args.report_retries)
230+
report_zip = download_report(hub_client, location, args.report_retries, args.report_timeout)
191231
logging.debug(f"Deleting report from Black Duck {hub_client.session.delete(location)}")
192232
zip=ZipFile(io.BytesIO(report_zip), "r")
193233
pprint(zip.namelist())
@@ -198,10 +238,24 @@ def main():
198238
json.dump(version_report, f)
199239
# TODO items
200240
# Process file section of report data to identify primary paths
241+
path_set = [f"{entry.get('archiveContext', "")}!{entry['path']}" for entry in version_report['detailedFileBomViewEntries']]
242+
reduced_path_set = reduce(path_set.copy())
243+
logging.info(f"{len(path_set)-len(reduced_path_set)} path entries were scrubbed from the dataset.")
244+
245+
# Remove component entries that correspond to removed path entries.
246+
247+
logging.info(f"Original dataset contains {len(version_report['aggregateBomViewEntries'])} bom entries and {len(version_report['detailedFileBomViewEntries'])} file view entries")
248+
if not args.keep_hierarchy:
249+
trim_version_report(version_report, reduced_path_set)
250+
logging.info(f"Truncated dataset contains {len(version_report['aggregateBomViewEntries'])} bom entries and {len(version_report['detailedFileBomViewEntries'])} file view entries")
251+
252+
write_output_file(version_report, output_file)
253+
201254
# Combine component data with selected file data
202255
# Output result with CSV anf JSON as options.
203256

204257

258+
205259
except (Exception, BaseException) as err:
206260
logging.error(f"Exception by {str(err)}. See the stack trace")
207261
traceback.print_exc()

0 commit comments

Comments
 (0)