|
| 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