Skip to content

Commit 6376f6b

Browse files
author
Glenn Snyder
authored
Merge pull request #68 from blackducksoftware/gsnyder/various-fixes
refactoring to use public APIs whenever possible
2 parents dbdd960 + b8dc761 commit 6376f6b

File tree

5 files changed

+110
-67
lines changed

5 files changed

+110
-67
lines changed

blackduck/HubRestApi.py

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -472,10 +472,32 @@ def get_vulnerabilities(self, vulnerability, parameters={}):
472472
return response.json()
473473

474474
def get_vulnerability_affected_projects(self, vulnerability):
475-
url = self.config['baseurl'] + "/api/v1/composite/vulnerability"+ "/{}".format(vulnerability)
475+
url = self._get_vulnerabilities_url() + "/{}/affected-projects".format(vulnerability)
476+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.vulnerability-4+json'}
477+
response = self.execute_get(url, custom_headers=custom_headers)
478+
return response.json()
479+
480+
# TODO: Refactor this, i.e. use get_link method?
481+
def get_vulnerable_bom_components(self, version_obj, limit=9999):
482+
url = "{}/vulnerable-bom-components".format(version_obj['_meta']['href'])
483+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.bill-of-materials-6+json'}
484+
param_string = self._get_parameter_string({'limit': limit})
485+
url = "{}{}".format(url, param_string)
486+
response = self.execute_get(url, custom_headers=custom_headers)
487+
return response.json()
488+
489+
# TODO: Remove or refactor this
490+
def get_component_remediation(self, bom_component):
491+
url = "{}/remediating".format(bom_component['componentVersion'])
492+
logging.debug("Url for getting remediation info is : {}".format(url))
476493
response = self.execute_get(url)
477494
return response.json()
478495

496+
##
497+
#
498+
# Lookup Black Duck (Hub) KB info given Protex KB info
499+
#
500+
##
479501
def find_component_info_for_protex_component(self, protex_component_id, protex_component_release_id):
480502
'''Will return the Hub component corresponding to the protex_component_id, and if a release (version) id
481503
is given, the response will also include the component-version. Returns an empty list if there were
@@ -495,24 +517,6 @@ def find_component_info_for_protex_component(self, protex_component_id, protex_c
495517
component_list_d = response.json()
496518
return response.json()
497519

498-
def get_vulnerable_bom_components(self, version_obj, limit=9999):
499-
url = "{}/vulnerable-bom-components".format(version_obj['_meta']['href'])
500-
custom_headers = {'Content-Type': 'application/vnd.blackducksoftware.bill-of-materials-4+json'}
501-
param_string = self._get_parameter_string({'limit': limit})
502-
url = "{}{}".format(url, param_string)
503-
response = self.execute_get(url, custom_headers=custom_headers)
504-
if response.status_code == 200:
505-
vulnerable_bom_components = response.json()
506-
return vulnerable_bom_components
507-
else:
508-
logging.warning("Failed to retrieve vulnerable bom components for project {}, status code {}".format(
509-
version_obj, response.status_code))
510-
511-
def get_component_remediation(self, bom_component):
512-
url = "{}/remediating".format(bom_component['componentVersion'])
513-
logging.debug("Url for getting remediation info is : {}".format(url))
514-
response = self.execute_get(url)
515-
return response.json()
516520

517521
##
518522
#
@@ -1146,6 +1150,25 @@ def get_project_roles(self):
11461150
all_project_roles = self.get_roles(parameters={"filter":"scope:project"})
11471151
return all_project_roles['items']
11481152

1153+
def get_version_scan_info(self, version_obj):
1154+
url = self.get_link(version_obj, "codelocations")
1155+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.project-detail-5+json'}
1156+
response = self.execute_get(url, custom_headers=custom_headers)
1157+
code_locations = response.json().get('items', [])
1158+
if code_locations:
1159+
scan_info = {
1160+
'most_recent_scan': max([cl['updatedAt'] for cl in code_locations]),
1161+
'oldest_scan': min([cl['createdAt'] for cl in code_locations]),
1162+
'number_scans': len(code_locations)
1163+
}
1164+
else:
1165+
scan_info = {
1166+
'most_recent_scan': None,
1167+
'oldest_scan': None,
1168+
'number_scans': None
1169+
}
1170+
return scan_info
1171+
11491172
###
11501173
#
11511174
# Add project version as a component to another project
@@ -1237,18 +1260,7 @@ def get_codelocations(self, limit=100, unmapped=False, parameters={}):
12371260
url = self.get_apibase() + "/codelocations" + paramstring
12381261
headers['Accept'] = 'application/vnd.blackducksoftware.scan-4+json'
12391262
response = requests.get(url, headers=headers, verify = not self.config['insecure'])
1240-
if response.status_code == 200:
1241-
jsondata = response.json()
1242-
if unmapped:
1243-
jsondata['items'] = [s for s in jsondata['items'] if 'mappedProjectVersion' not in s]
1244-
jsondata['totalCount'] = len(jsondata['items'])
1245-
return jsondata
1246-
elif response.status_code == 403:
1247-
logging.warning("Failed to retrieve code locations (aka scans) probably due to lack of permissions, status code {}".format(
1248-
response.status_code))
1249-
else:
1250-
logging.error("Failed to retrieve code locations (aka scans), status code {}".format(
1251-
response.status_code))
1263+
return response.json()
12521264

12531265
def get_codelocation_scan_summaries(self, code_location_id = None, code_location_obj = None, limit=100):
12541266
'''Retrieve the scans (aka scan summaries) for the given location. You can give either
@@ -1269,7 +1281,7 @@ def get_codelocation_scan_summaries(self, code_location_id = None, code_location
12691281
return jsondata
12701282

12711283
def delete_unmapped_codelocations(self, limit=1000):
1272-
code_locations = self.get_codelocations(limit, True).get('items', [])
1284+
code_locations = self.get_codelocations(limit=limit, unmapped=True).get('items', [])
12731285

12741286
for c in code_locations:
12751287
scan_summaries = self.get_codelocation_scan_summaries(code_location_obj = c).get('items', [])
@@ -1509,6 +1521,18 @@ def get_health_checks(self):
15091521
url = self.get_urlbase() + "/api/health-checks/liveness"
15101522
return self.execute_get(url)
15111523

1524+
##
1525+
#
1526+
# Jobs
1527+
#
1528+
##
1529+
def get_jobs(self, parameters={}):
1530+
url = self.get_apibase() + "/jobs"
1531+
url = url + self._get_parameter_string(parameters)
1532+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.status-4+json'}
1533+
response = self.execute_get(url, custom_headers=custom_headers)
1534+
return response.json()
1535+
15121536
##
15131537
#
15141538
# Job Statistics

blackduck/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
VERSION = (0, 0, 36)
1+
VERSION = (0, 0, 37)
22

33
__version__ = '.'.join(map(str, VERSION))

examples/check_scan_jobs_status.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,7 @@ def initialize(cls):
3434

3535
if not cls.jobs:
3636
logging.debug("retrieving jobs")
37-
cls.jobs = cls._get_all_jobs()
38-
39-
@classmethod
40-
def _get_all_jobs(cls):
41-
# See https://jira.dc1.lan/browse/HUB-14263 - Rest API for Job status
42-
# The below is using a private endpoint that could change on any release and break this code
43-
jobs_url = cls.hub.get_urlbase() + "/api/v1/jobs?limit={}".format(10000)
44-
response = cls.hub.execute_get(jobs_url)
45-
jobs = []
46-
if response.status_code == 200:
47-
return response.json().get('items', [])
48-
else:
49-
raise Exception("Failed to retrieve jobs, status code: {}".format(response.status_code))
37+
cls.jobs = cls.hub.get_jobs().get('items', [])
5038

5139
def __init__(self, code_location_obj):
5240
self.initialize()

examples/get_scans_or_codelocations.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,24 @@
99

1010
import argparse
1111
import json
12+
import logging
1213
from pprint import pprint
14+
import sys
1315

14-
from blackduck.HubRestApi import HubInstance
16+
from blackduck.HubRestApi import HubInstance, object_id
1517

1618
parser = argparse.ArgumentParser("Retrieve scans (aka code locations) from the Black Duck system")
17-
parser.add_argument("--name", type=str, default=None, help="Filter by name")
19+
parser.add_argument("-n", "--name", type=str, default=None, help="Filter by name")
1820
parser.add_argument("--unmapped", action='store_true', help="Set this to see any scans (aka code locations) that are not mapped to any project-version")
21+
parser.add_argument("-s", "--scan_summaries", action='store_true', help="Set this option to include scan summaries")
22+
parser.add_argument("-d", "--scan_details", action='store_true')
1923

2024
args = parser.parse_args()
2125

26+
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', stream=sys.stderr, level=logging.DEBUG)
27+
logging.getLogger("requests").setLevel(logging.WARNING)
28+
logging.getLogger("urllib3").setLevel(logging.WARNING)
29+
2230
hub = HubInstance()
2331

2432
if args.name:
@@ -27,8 +35,24 @@
2735
parameters={}
2836

2937
if args.unmapped:
30-
scans = hub.get_codelocations(limit=10000, unmapped=True, parameters=parameters)
38+
code_locations = hub.get_codelocations(limit=10000, unmapped=True, parameters=parameters)
3139
else:
32-
scans = hub.get_codelocations(limit=10000, parameters=parameters)
33-
34-
print(json.dumps(scans))
40+
code_locations = hub.get_codelocations(limit=10000, parameters=parameters)
41+
42+
code_locations = code_locations.get('items', [])
43+
44+
if args.scan_summaries:
45+
for code_location in code_locations:
46+
scan_summaries = hub.get_codelocation_scan_summaries(code_location_obj=code_location).get('items', [])
47+
code_location['scan_summaries'] = scan_summaries
48+
if args.scan_details:
49+
for scan in scan_summaries:
50+
scan_id = object_id(scan)
51+
# This uses a private API endpoint that can, and probably will, break in the future
52+
# HUB-15330 is the (internal) JIRA ticket # asking that the information in this endpoint
53+
# be made part of the public API
54+
url = hub.get_apibase() + "/v1/scans/{}".format(scan_id)
55+
scan_details = hub.execute_get(url).json()
56+
scan['scan_details'] = scan_details
57+
58+
print(json.dumps(code_locations))

examples/print_vulnerability_affected_projects.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,29 @@
1919
parser.add_argument("vulnerability", help="A CVE or BDSA number, e.g. CVE-2016-4009")
2020
args = parser.parse_args()
2121

22-
affected_projects = hub.get_vulnerability_affected_projects(args.vulnerability.upper())
22+
affected_projects = hub.get_vulnerability_affected_projects(args.vulnerability.upper()).get('items', [])
2323

24-
if 'totalCount' in affected_projects and affected_projects['totalCount'] > 0:
24+
if affected_projects:
2525
ttable = [[
2626
"project-name",
2727
"version",
2828
"phase",
2929
"distribution",
30-
"last-bom-update",
30+
"scan-info",
3131
"Owner Name",
3232
"Owner email"]]
3333

34-
for affected_project in affected_projects['items']:
34+
for affected_project in affected_projects:
3535
# Get the Owner info for the project
36-
project_json = hub.get_project_by_id(affected_project['project']['id'])
37-
if 'projectOwner' in project_json:
38-
owner_response = hub.execute_get(project_json['projectOwner'])
36+
# project_json = hub.get_project_by_id(affected_project['project']['id'])
37+
project_url = affected_project['project']
38+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.project-detail-4+json'}
39+
project_details = hub.execute_get(project_url, custom_headers=custom_headers).json()
40+
if 'projectOwner' in project_details:
41+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.user-4+json'}
42+
owner_response = hub.execute_get(project_details['projectOwner'], custom_headers=custom_headers)
3943
owner_json = owner_response.json()
44+
4045
if 'firstName' in owner_json and 'lastName' in owner_json:
4146
owner_name = owner_json['firstName'] + ' ' + owner_json['lastName']
4247
else:
@@ -48,26 +53,28 @@
4853
else:
4954
owner_name = owner_email = "None supplied"
5055

51-
project_name = affected_project['project']['name']
52-
version = affected_project['release']['version']
56+
project_name = affected_project['projectName']
57+
version = affected_project['projectVersionName']
5358

5459
# Development phase does not appear to be in the payload returned by the affected projects
5560
# endpoint so we need to fetch it from the project-version endpoint
56-
project_id = affected_project['project']['id']
57-
version_id = affected_project['release']['id']
58-
59-
project_version_info = hub.get_version_by_id(project_id, version_id)
61+
version_url = affected_project['projectVersion']
62+
custom_headers = {'Accept': 'application/vnd.blackducksoftware.project-detail-5+json'}
63+
project_version_info = hub.execute_get(version_url, custom_headers=custom_headers).json()
6064

6165
phase = project_version_info['phase']
6266
distribution = project_version_info['distribution']
63-
last_bom_update = project_version_info['lastBomUpdateDate']
67+
scan_info = hub.get_version_scan_info(project_version_info)
68+
separator = "\n"
69+
scan_info_str = separator.join("{}: {}".format(k,v) for k,v in scan_info.items())
70+
# last_bom_update = project_version_info['lastBomUpdateDate']
6471

6572
ttable.append([
6673
project_name,
6774
version,
6875
phase,
6976
distribution,
70-
last_bom_update,
77+
scan_info_str,
7178
owner_name,
7279
owner_email])
7380
print(AsciiTable(ttable).table)

0 commit comments

Comments
 (0)