Skip to content

Commit a7ad4be

Browse files
committed
0.5 prototype
1 parent 4e35214 commit a7ad4be

File tree

1 file changed

+94
-134
lines changed

1 file changed

+94
-134
lines changed

examples/client/file_hierarchy_report.py

Lines changed: 94 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import argparse
3131
import logging
3232
import sys
33+
import io
3334
import os
3435
import re
3536
import time
@@ -40,10 +41,11 @@
4041
import ijson
4142
from blackduck import Client
4243
from zipfile import ZipFile
44+
from pprint import pprint
4345

4446
program_description = \
4547
'''Generate version detail reports (source and components) and consolidate information on source matches, with license
46-
and component matched. Removes matches found underneith other matched components in the source tree (configurable).
48+
and component matched. Removes matches found underneath other matched components in the source tree (configurable).
4749
4850
This script assumes a project version exists and has scans associated with it (i.e. the project is not scanned as part of this process).
4951
@@ -83,39 +85,21 @@ def log_config(debug):
8385
logging.getLogger("urllib3").setLevel(logging.WARNING)
8486
logging.getLogger("blackduck").setLevel(logging.WARNING)
8587

86-
def parse_parameter():
87-
parser = argparse.ArgumentParser(description=program_description, formatter_class=argparse.RawTextHelpFormatter)
88-
parser.add_argument("project",
89-
metavar="project",
90-
type=str,
91-
help="Provide the BlackDuck project name.")
92-
parser.add_argument("version",
93-
metavar="version",
94-
type=str,
95-
help="Provide the BlackDuck project version name.")
96-
parser.add_argument("-kh",
97-
"--keep_hierarchy",
98-
action='store_true',
99-
help="Set to keep all entries in the sources report. Will not remove components found under others.")
100-
parser.add_argument("-rr",
101-
"--report_retries",
102-
metavar="",
103-
type=int,
104-
default=RETRY_LIMIT,
105-
help="Retries for receiving the generated BlackDuck report. Generating copyright report tends to take longer minutes.")
106-
parser.add_argument("-t",
107-
"--timeout",
108-
metavar="",
109-
type=int,
110-
default=15,
111-
help="Timeout for REST-API. Some API may take longer than the default 15 seconds")
112-
parser.add_argument("-r",
113-
"--retries",
114-
metavar="",
115-
type=int,
116-
default=3,
117-
help="Retries for REST-API. Some API may need more retries than the default 3 times")
118-
return parser.parse_args()
88+
def find_project_by_name(bd, project_name):
89+
params = {
90+
'q': [f"name:{project_name}"]
91+
}
92+
projects = [p for p in bd.get_resource('projects', params=params) if p['name'] == project_name]
93+
assert len(projects) == 1, f"Project {project_name} not found."
94+
return projects[0]
95+
96+
def find_project_version_by_name(bd, project, version_name):
97+
params = {
98+
'q': [f"versionName:{version_name}"]
99+
}
100+
versions = [v for v in bd.get_resource('versions', project, params=params) if v['versionName'] == version_name]
101+
assert len(versions) == 1, f"Project version {version_name} for project {project['name']} not found"
102+
return versions[0]
119103

120104
def get_bd_project_data(hub_client, project_name, version_name):
121105
""" Get and return project ID, version ID. """
@@ -136,55 +120,53 @@ def get_bd_project_data(hub_client, project_name, version_name):
136120

137121
return project_id, version_id
138122

139-
def report_create(hub_client, url, body):
140-
"""
141-
Request BlackDuck to create report. Requested report is included in the request payload.
142-
"""
143-
res = hub_client.session.post(url, headers={'Content-Type': BLACKDUCK_REPORT_MEDIATYPE}, json=body)
144-
if res.status_code != 201:
145-
sys.exit(f"BlackDuck report creation failed with status {res.status_code}!")
146-
return res.headers['Location'] # return report_url
147-
148-
def report_download(hub_client, report_url, project_id, version_id, retries):
149-
"""
150-
Download the generated report after the report completion. We will retry until reaching the retry-limit.
151-
"""
152-
while retries:
153-
res = hub_client.session.get(report_url, headers={'Accept': BLACKDUCK_REPORT_MEDIATYPE})
154-
if res.status_code == 200 and (json.loads(res.content))['status'] == "COMPLETED":
155-
report_id = report_url.split("reports/", 1)[1]
156-
download_url = (((blackduck_report_download_api.replace("{projectId}", project_id))
157-
.replace("{projectVersionId}", version_id))
158-
.replace("{reportId}", report_id))
159-
res = hub_client.session.get(download_url,
160-
headers={'Content-Type': 'application/zip', 'Accept':'application/zip'})
161-
if res.status_code != 200:
162-
sys.exit(f"BlackDuck report download failed with status {res.status_code} for {download_url}!")
163-
return res.content
164-
elif res.status_code != 200:
165-
sys.exit(f"BlackDuck report creation not completed successfully with status {res.status_code}")
166-
else:
167-
retries -= 1
168-
logging.info(f"Waiting for the report generation for {report_url} with the remaining retries {retries} times.")
169-
time.sleep(RETRY_TIMER)
170-
sys.exit(f"BlackDuck report for {report_url} was not generated after retries {RETRY_TIMER} sec * {retries} times!")
171-
172-
def get_version_detail_report(hub_client, project_id, version_id, retries):
173-
""" Create and get BOM component and BOM source file report in json. """
174-
create_version_url = blackduck_create_version_report_api.replace("{projectVersionId}", version_id)
175-
body = {
123+
def create_version_details_report(bd, version):
124+
version_reports_url = bd.list_resources(version).get('versionReport')
125+
post_data = {
176126
'reportFormat' : 'JSON',
177127
'locale' : 'en_US',
178-
'versionId' : f'{version_id}',
128+
'versionId': version['_meta']['href'].split("/")[-1],
179129
'categories' : [ 'COMPONENTS', 'FILES' ] # Generating "project version" report including components and files
180130
}
181-
report_url = report_create(hub_client, create_version_url, body)
182-
# Zipped report content is received and write the content to a local zip file
183-
content = report_download(hub_client, report_url, project_id, version_id, retries)
184-
output_file = blackduck_version_report_filename.replace("{projectVersionId}", version_id)
185-
with open(output_file, "wb") as f:
186-
f.write(content)
187-
return output_file
131+
132+
bd.session.headers["Content-Type"] = "application/vnd.blackducksoftware.report-4+json"
133+
r = bd.session.post(version_reports_url, json=post_data)
134+
if (r.status_code == 403):
135+
logging.debug("Authorization Error - Please ensure the token you are using has write permissions!")
136+
r.raise_for_status()
137+
pprint(r.headers)
138+
location = r.headers.get('Location')
139+
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"
140+
return location
141+
142+
def download_report(bd, location, retries):
143+
report_id = location.split("/")[-1]
144+
print (location)
145+
url_data = location.split('/')
146+
url_data.pop(4)
147+
url_data.pop(4)
148+
download_link = '/'.join(url_data)
149+
print(download_link)
150+
if retries:
151+
logging.debug(f"Retrieving generated report from {location}")
152+
response = bd.session.get(location)
153+
report_status = response.json().get('status', 'Not Ready')
154+
if response.status_code == 200 and report_status == 'COMPLETED':
155+
response = bd.session.get(download_link, headers={'Content-Type': 'application/zip', 'Accept':'application/zip'})
156+
pprint(response)
157+
if response.status_code == 200:
158+
return response.content
159+
else:
160+
logging.error("Ruh-roh, not sure what happened here")
161+
return None
162+
else:
163+
logging.debug(f"Report status request {response.status_code} {report_status} ,waiting {retries} seconds then retrying...")
164+
time.sleep(60)
165+
retries -= 1
166+
return download_report(bd, location, retries)
167+
else:
168+
logging.debug(f"Failed to retrieve report {report_id} after multiple retries")
169+
return None
188170

189171
def get_blackduck_version(hub_client):
190172
url = hub_client.base_url + BLACKDUCK_VERSION_API
@@ -194,68 +176,46 @@ def get_blackduck_version(hub_client):
194176
else:
195177
sys.exit(f"Get BlackDuck version failed with status {res.status_code}")
196178

197-
def generate_file_report(hub_client, project_id, version_id, keep_hierarchy, retries):
198-
"""
199-
Create a consolidated file report from BlackDuck project version source and components reports.
200-
Remarks:
201-
"""
202-
if not os.path.exists(REPORT_DIR):
203-
os.makedirs(REPORT_DIR)
204-
205-
# Report body - Component BOM, file BOM with Discoveries data
206-
version_report_zip = get_version_detail_report(hub_client, project_id, version_id, retries)
207-
with ZipFile(f"./{version_report_zip}", "r") as vzf:
208-
vzf.extractall()
209-
for i, unzipped_version in enumerate(vzf.namelist()):
210-
if re.search(r"\bversion.+json\b", unzipped_version) is not None:
211-
break
212-
if i + 1 >= len(vzf.namelist()):
213-
sys.exit(f"Version detail file not found in the downloaded report: {version_report_zip}!")
214-
215-
# Report body - Component BOM report
216-
with open(f"./{unzipped_version}", "r") as uvf:
217-
for i, comp_bom in enumerate(ijson.items(uvf, 'aggregateBomViewEntries.item')):
218-
logging.info(f"{comp_bom['componentName']}")
219-
logging.info(f"Number of the reported components {i+1}")
220-
179+
def parse_command_args():
180+
parser = argparse.ArgumentParser(description=program_description, formatter_class=argparse.RawTextHelpFormatter)
181+
parser.add_argument("-u", "--base-url", required=True, help="Hub server URL e.g. https://your.blackduck.url")
182+
parser.add_argument("-t", "--token-file", required=True, help="File containing access token")
183+
parser.add_argument("-nv", "--no-verify", action='store_false', help="Disable TLS certificate verification")
184+
parser.add_argument("-d", "--debug", action='store_true', help="Set debug output on")
185+
parser.add_argument("-pn", "--project-name", required=True, help="Project Name")
186+
parser.add_argument("-pv", "--project-version-name", required=True, help="Project Version Name")
187+
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.")
188+
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.")
189+
parser.add_argument("--timeout", metavar="", type=int, default=60, help="Timeout for REST-API. Some API may take longer than the default 60 seconds")
190+
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")
191+
return parser.parse_args()
221192

222193
def main():
223-
args = parse_parameter()
224-
debug = 0
194+
args = parse_command_args()
195+
with open(args.token_file, 'r') as tf:
196+
token = tf.readline().strip()
225197
try:
226-
if args.project == "":
227-
sys.exit("Please set BlackDuck project name!")
228-
if args.version == "":
229-
sys.exit("Please set BlackDuck project version name!")
230-
231-
with open(".restconfig.json", "r") as f:
232-
config = json.load(f)
233-
# Remove last slash if there is, otherwise REST API may fail.
234-
if re.search(r".+/$", config['baseurl']):
235-
bd_url = config['baseurl'][:-1]
236-
else:
237-
bd_url = config['baseurl']
238-
bd_token = config['api_token']
239-
bd_insecure = not config['insecure']
240-
if config['debug']:
241-
debug = 1
242-
243-
log_config(debug)
244-
245-
hub_client = Client(token=bd_token,
246-
base_url=bd_url,
247-
verify=bd_insecure,
198+
log_config(args.debug)
199+
hub_client = Client(token=token,
200+
base_url=args.base_url,
201+
verify=args.no_verify,
248202
timeout=args.timeout,
249203
retries=args.retries)
250204

251-
project_id, version_id = get_bd_project_data(hub_client, args.project, args.version)
205+
project = find_project_by_name(hub_client, args.project_name)
206+
version = find_project_version_by_name(hub_client, project, args.project_version_name)
207+
pprint(version)
208+
location = create_version_details_report(hub_client, version)
209+
pprint(location)
210+
report_zip = download_report(hub_client, location, args.report_retries)
211+
pprint(report_zip)
212+
logging.debug(f"Deleting report from Black Duck {hub_client.session.delete(location)}")
213+
zip=ZipFile(io.BytesIO(report_zip), "r")
214+
pprint(zip.namelist())
215+
report_data = {name: zip.read(name) for name in zip.namelist()}
216+
filename = [i for i in report_data.keys() if i.endswith(".json")][0]
217+
pprint(json.loads(report_data[filename]))
252218

253-
generate_file_report(hub_client,
254-
project_id,
255-
version_id,
256-
args.keep_hierarchy,
257-
args.report_retries
258-
)
259219

260220
except (Exception, BaseException) as err:
261221
logging.error(f"Exception by {str(err)}. See the stack trace")

0 commit comments

Comments
 (0)