|
| 1 | +''' |
| 2 | +Created on Jan 22, 2024 |
| 3 | +
|
| 4 | +@author: pedapati |
| 5 | +
|
| 6 | +Update component version info for BOM Components with Unknown Versions based on matched filename in a given project version |
| 7 | +
|
| 8 | +''' |
| 9 | + |
| 10 | +from blackduck import Client |
| 11 | + |
| 12 | +import requests |
| 13 | +import argparse |
| 14 | +import json |
| 15 | +import logging |
| 16 | +import sys |
| 17 | +import time |
| 18 | +from pprint import pprint |
| 19 | + |
| 20 | +import urllib3 |
| 21 | +import urllib.parse |
| 22 | + |
| 23 | +NAME = 'update_component_version.py' |
| 24 | +VERSION = '2024-01-22' |
| 25 | + |
| 26 | +print(f'{NAME} ({VERSION}). Copyright (c) 2023 Synopsys, Inc.') |
| 27 | + |
| 28 | + |
| 29 | +logging.basicConfig( |
| 30 | + level=logging.DEBUG, |
| 31 | + format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s" |
| 32 | +) |
| 33 | + |
| 34 | +parser = argparse.ArgumentParser(sys.argv[0]) |
| 35 | +parser.add_argument("-u", "--bd-url", help="Hub server URL e.g. https://your.blackduck.url") |
| 36 | +parser.add_argument("-t", "--token-file", help="File name of a file containing access token") |
| 37 | +parser.add_argument("-nv", '--no-verify', dest='verify', action='store_false', help="disable TLS certificate verification") |
| 38 | +parser.add_argument("project_name") |
| 39 | +parser.add_argument("version_name") |
| 40 | + |
| 41 | +args = parser.parse_args() |
| 42 | + |
| 43 | +logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', stream=sys.stderr, level=logging.DEBUG) |
| 44 | +logging.getLogger("requests").setLevel(logging.WARNING) |
| 45 | +logging.getLogger("urllib3").setLevel(logging.WARNING) |
| 46 | +logging.getLogger("blackduck").setLevel(logging.WARNING) |
| 47 | + |
| 48 | +with open(args.token_file, 'r') as tf: |
| 49 | +access_token = tf.readline().strip() |
| 50 | + |
| 51 | +bd = Client(base_url=args.bd_url, token=access_token, verify=args.verify) |
| 52 | + |
| 53 | +params = { |
| 54 | + 'q': [f"name:{args.project_name}"] |
| 55 | +} |
| 56 | +projects = [p for p in bd.get_resource('projects', params=params) if p['name'] == args.project_name] |
| 57 | +assert len(projects) == 1, f"There should be one, and only one project named {args.project_name}. We found {len(projects)}" |
| 58 | +project = projects[0] |
| 59 | +project_id = project["_meta"]["href"].split("/")[-1] |
| 60 | +print("Project ID: " + project_id) |
| 61 | + |
| 62 | +params = { |
| 63 | + 'q': [f"versionName:{args.version_name}"] |
| 64 | +} |
| 65 | +versions = [v for v in bd.get_resource('versions', project, params=params) if v['versionName'] == args.version_name] |
| 66 | +assert len(versions) == 1, f"There should be one, and only one version named {args.version_name}. We found {len(versions)}" |
| 67 | +version = versions[0] |
| 68 | +version_id = version["_meta"]["href"].split("/")[-1] |
| 69 | +print("Version ID: " + version_id) |
| 70 | + |
| 71 | +logging.debug(f"Found {project['name']}:{version['versionName']}") |
| 72 | + |
| 73 | +def update_bom_unknown_versions(bd, project_id, version_id): |
| 74 | +limit = 1000 |
| 75 | +offset = 0 |
| 76 | +paginated_url = f"{bd.base_url}/api/projects/{project_id}/versions/{version_id}/components?limit={limit}&offset={offset}&filter=unknownVersion:true" |
| 77 | +print("Looking for BOM Components with Unknown Versions: " + paginated_url) |
| 78 | +print() |
| 79 | +components_json = bd.session.get(paginated_url).json() |
| 80 | +total = str(components_json["totalCount"]) |
| 81 | +print("Found " + total + " components with unknown versions") |
| 82 | +print() |
| 83 | +for component in components_json["items"]: |
| 84 | +comp_name =component["componentName"] |
| 85 | +print("Processing Component: " + comp_name) |
| 86 | +comp_url = component["component"] |
| 87 | +comp_bom_url = component["_meta"]["href"] |
| 88 | +matched_files_url = component["_meta"]["href"] + "/matched-files" |
| 89 | +matched_file_json = bd.session.get(matched_files_url).json() |
| 90 | +archivecontext = matched_file_json["items"][0]["filePath"]["archiveContext"] |
| 91 | +filename = matched_file_json["items"][0]["filePath"]["fileName"] |
| 92 | +## Extract Component Name and Version from archivecontext to do a KB lookup |
| 93 | +archive_strip = archivecontext.strip("/,!") |
| 94 | +archive_partition = archive_strip.rpartition("-") |
| 95 | +archive_final_list = archive_partition[0].rpartition("-") |
| 96 | +kb_file_lookup_name = archive_final_list[0] |
| 97 | +kb_comp_lookup_version = archive_final_list[2] |
| 98 | +print("Processing Component Version: " + kb_comp_lookup_version) |
| 99 | +## KB Lookup via Component Name |
| 100 | +components_url = bd.base_url + "/api/components/autocomplete" |
| 101 | +query = { "q": comp_name, |
| 102 | + "filter": "componentType:kb_component" |
| 103 | + } |
| 104 | +url = f"{components_url}?{urllib.parse.urlencode(query)}" |
| 105 | +headers = {'Accept': '*/*'} |
| 106 | +name_match = bd.session.get(url, headers=headers).json() |
| 107 | +# Filtering results for exact name match |
| 108 | +exact_name_match = [x for x in name_match['items'] if x['name']==comp_name] |
| 109 | +if len(exact_name_match) == 0 : |
| 110 | +logging.debug(f"Component {comp_name} is not found in the KB") |
| 111 | +return |
| 112 | +else: |
| 113 | +logging.debug(f"Component {comp_name} is found in the KB") |
| 114 | +if kb_comp_lookup_version: |
| 115 | +first_match_successful = False |
| 116 | +# second_match_successful = False |
| 117 | +for match in exact_name_match: # handling OSS components that share same name |
| 118 | +url = match['_meta']['href']+'/versions?q=versionName:' + kb_comp_lookup_version |
| 119 | +headers = {'Accept': 'application/vnd.blackducksoftware.summary-1+json'} |
| 120 | +# Producing version matches |
| 121 | +version_match = bd.session.get(url, headers=headers).json() |
| 122 | +if version_match['totalCount'] > 0: |
| 123 | +print(version_match["items"][0]["versionName"]) |
| 124 | +print("Found version: " + kb_comp_lookup_version + " in the KB for component " + comp_name) |
| 125 | +print("Updating component version for " + comp_name + " to " + kb_comp_lookup_version ) |
| 126 | +# component_url = version_match[] |
| 127 | +# print(version_match) |
| 128 | +component_version_url = version_match['items'][0]['_meta']['href'] |
| 129 | +component_url = component_version_url[:component_version_url.index("versions")-1] |
| 130 | +# print(component_url) |
| 131 | +post_data = {"component": component_url, "componentVersion": component_version_url} |
| 132 | +headers = {'Accept': 'application/vnd.blackducksoftware.bill-of-materials-6+json', 'Content-Type': 'application/vnd.blackducksoftware.bill-of-materials-6+json'} |
| 133 | +response = bd.session.put(comp_bom_url, headers=headers, data=json.dumps(post_data)) |
| 134 | +# print(response) |
| 135 | +if response.status_code == 200: |
| 136 | +message = f"{response}" |
| 137 | +print("Successfully updated " + comp_name + " with version " + kb_comp_lookup_version) |
| 138 | +else: |
| 139 | +message = f"{response.json()}" |
| 140 | +logging.debug(f"Updating BOM component {comp_name} {kb_comp_lookup_version} failed with: {message}") |
| 141 | +first_match_successful = True |
| 142 | +print("### Proceeding to next component") |
| 143 | +print() |
| 144 | +break |
| 145 | +else: |
| 146 | +print("No matching version " + kb_comp_lookup_version + " found for " + comp_name) |
| 147 | +if not first_match_successful: |
| 148 | +## Trying to locate component name using source archive name |
| 149 | +print("Proceeding to KB lookup via matched file name: " + kb_file_lookup_name) |
| 150 | +components_url = bd.base_url + "/api/components/autocomplete" |
| 151 | +query = { "q": kb_file_lookup_name, |
| 152 | +"filter": "componentType:kb_component" |
| 153 | +} |
| 154 | +url = f"{components_url}?{urllib.parse.urlencode(query)}" |
| 155 | +headers = {'Accept': '*/*'} |
| 156 | +name_match = bd.session.get(url, headers=headers).json() |
| 157 | +# Filtering results for exact name match |
| 158 | +exact_name_match = [x for x in name_match['items'] if x['name']==kb_file_lookup_name] |
| 159 | +if len(exact_name_match) == 0 : |
| 160 | +logging.debug(f"File Match KB Component {kb_file_lookup_name} is not found in the KB") |
| 161 | +print("### Proceeding to next component") |
| 162 | +print() |
| 163 | +continue |
| 164 | +else: |
| 165 | +logging.debug(f"File Match KB Component {kb_file_lookup_name} is found in the KB") |
| 166 | +if kb_comp_lookup_version: |
| 167 | +for match in exact_name_match: # handling OSS components that share same name |
| 168 | +url = match['_meta']['href']+'/versions?q=versionName:' + kb_comp_lookup_version |
| 169 | +headers = {'Accept': 'application/vnd.blackducksoftware.summary-1+json'} |
| 170 | +# Producing version matches |
| 171 | +version_match = bd.session.get(url, headers=headers).json() |
| 172 | +if version_match['totalCount'] > 0: |
| 173 | +print(version_match["items"][0]["versionName"]) |
| 174 | +print("Found version: " + kb_comp_lookup_version + " in the KB for component " + kb_file_lookup_name) |
| 175 | +print("Updating component version for " + kb_file_lookup_name + " to " + kb_comp_lookup_version ) |
| 176 | +# component_url = version_match[] |
| 177 | +# print(version_match) |
| 178 | +component_version_url = version_match['items'][0]['_meta']['href'] |
| 179 | +component_url = component_version_url[:component_version_url.index("versions")-1] |
| 180 | +# print(component_url) |
| 181 | +post_data = {"component": component_url, "componentVersion": component_version_url} |
| 182 | +headers = {'Accept': 'application/vnd.blackducksoftware.bill-of-materials-6+json', 'Content-Type': 'application/vnd.blackducksoftware.bill-of-materials-6+json'} |
| 183 | +response = bd.session.put(comp_bom_url, headers=headers, data=json.dumps(post_data)) |
| 184 | +# print(response) |
| 185 | +if response.status_code == 200: |
| 186 | +message = f"{response}" |
| 187 | +print("Successfully updated " + kb_file_lookup_name + " with version " + kb_comp_lookup_version) |
| 188 | +# second_match_successful = True |
| 189 | +else: |
| 190 | +message = f"{response.json()}" |
| 191 | +logging.debug(f"Updating BOM component {kb_file_lookup_name} {kb_comp_lookup_version} failed with: {message}") |
| 192 | +print("### Proceeding to next component") |
| 193 | +print() |
| 194 | +break |
| 195 | +else: |
| 196 | +print("No matching version " + kb_comp_lookup_version + " found for " + kb_file_lookup_name) |
| 197 | +print("### Proceeding to next component") |
| 198 | +print() |
| 199 | + |
| 200 | + |
| 201 | + |
| 202 | +bom = update_bom_unknown_versions(bd, project_id, version_id) |
| 203 | + |
| 204 | + |
| 205 | + |
0 commit comments