|
| 1 | +""" |
| 2 | +generate_source_report_for_sub_projects_recursive |
| 3 | +
|
| 4 | +Created on February 15, 2021 |
| 5 | +
|
| 6 | +Adapted from generate_source_report_for_sub_projects.py written by AMacDonald |
| 7 | +
|
| 8 | +@author: DNicholls |
| 9 | +
|
| 10 | +Script designed to generate and collate Source reports for the master project and it's sub-projects recursively. |
| 11 | +
|
| 12 | +If a project has source but also sub projects with source that also have sub projects with source this utility will |
| 13 | +generate source reports for each level of project and combine to a consolidated source report. It will also generate |
| 14 | +security and components reports for the master project (these automatically include subprojects). It will output the |
| 15 | +results to a ./results folder and uses ./temp for work in progress. These can be modified in the below checkdirs, unzip |
| 16 | +and concat functions. If you are finding the reports are not generated in time by default it will wait 5 seconds and |
| 17 | +retry 10 times, if this is not long enough as your projects are large then increase the retries_per_download variable. |
| 18 | +
|
| 19 | +To run this script, you will need to pass arguments for the master project name and the master project version. Once |
| 20 | +they are specified, the script will investigate to see if the master project contains sub-projects and will generate |
| 21 | +reports for all sub-projects it discovers. Finally, it will combine them into a single report, saving it to a "results" |
| 22 | +sub-directory. |
| 23 | +
|
| 24 | +For this script to run, the hub-rest-api-python (blackduck) library and pandas library will need to be installed. |
| 25 | +
|
| 26 | +""" |
| 27 | + |
| 28 | +import argparse |
| 29 | +from blackduck.HubRestApi import HubInstance |
| 30 | +import time |
| 31 | +from zipfile import ZipFile |
| 32 | +import shutil |
| 33 | +import os |
| 34 | +import glob |
| 35 | +import pandas |
| 36 | + |
| 37 | +parser = argparse.ArgumentParser("A program to create consolidated Source report for sub projects") |
| 38 | +parser.add_argument("project_name") |
| 39 | +parser.add_argument("version_name") |
| 40 | +args = parser.parse_args() |
| 41 | +hub = HubInstance() |
| 42 | +csv_list = [] |
| 43 | +timestamp = time.strftime('%m_%d_%Y_%H_%M') |
| 44 | +retries_per_download=10 |
| 45 | +file_out = (args.project_name + '_' + "Consolidated_src_report-" + timestamp) |
| 46 | +file_out = (file_out + ".csv") |
| 47 | + |
| 48 | + |
| 49 | +class FailedReportDownload(Exception): |
| 50 | + pass |
| 51 | + |
| 52 | + |
| 53 | +def download_report(location, filename, retries=retries_per_download): |
| 54 | + report_id = location.split("/")[-1] |
| 55 | + |
| 56 | + if retries: |
| 57 | + print("Retrieving generated report from {}".format(location)) |
| 58 | + response = hub.download_report(report_id) |
| 59 | + if response.status_code == 200: |
| 60 | + with open(filename, "wb") as f: |
| 61 | + f.write(response.content) |
| 62 | + |
| 63 | + print("Successfully downloaded zip file to {} for report {}".format(filename, report_id)) |
| 64 | + else: |
| 65 | + print("Failed to retrieve report {}".format(report_id)) |
| 66 | + print("Probably not ready yet, waiting 5 seconds then retrying...") |
| 67 | + time.sleep(5) |
| 68 | + retries -= 1 |
| 69 | + download_report(location, filename, retries) |
| 70 | + else: |
| 71 | + raise FailedReportDownload("Failed to retrieve report {} after multiple retries".format(report_id)) |
| 72 | + |
| 73 | +def genreportsforversion(projectname,versionname,reportlist): |
| 74 | + print("Generating source report for project {} version {}".format(projectname, versionname)) |
| 75 | + |
| 76 | + projversion = hub.get_project_version_by_name(projectname, versionname) |
| 77 | + components = hub.get_version_components(projversion) |
| 78 | + |
| 79 | + subname = (projectname + '_' + versionname + '.zip') |
| 80 | + ## CURRENTLY COMBINES ALL REPORTS TOGETHER. |
| 81 | + ## SWITCH TO GENERATE COMPONENTS AND SECURITY AFTER SEPARATELY OR EXCLUDE NON SOURCE FROM CONCAT |
| 82 | + result = hub.create_version_reports(version=projversion, report_list=reportlist, format="CSV") |
| 83 | + # Generates reports in the main project for SECURITY, COMPONENTS and FILES (source) report, |
| 84 | + # Using the version object (line 21) to say which reports are needed |
| 85 | + # prints out success/error code. |
| 86 | + if result.status_code == 201: |
| 87 | + print("Successfully created reports ({}) for project {} and version {}".format( |
| 88 | + reportlist, projectname, versionname)) |
| 89 | + location = result.headers['Location'] |
| 90 | + download_report(location, subname) |
| 91 | + else: |
| 92 | + print("Failed to create reports for project {} version {}, status code returned {}".format( |
| 93 | + projectname, versionname, result.status_code)) |
| 94 | + |
| 95 | + for component in components['items']: |
| 96 | + subname = (component['componentName'] + '_' + component['componentVersionName'] + '.zip') |
| 97 | + subname = (subname.replace(" ", "")) |
| 98 | + # Above step is to generate the output from the get_version_components and specifically look at the activityData |
| 99 | + # portion to indicate whether a component is a KB component, or a subproject. |
| 100 | + if len(component['activityData']) == 0: |
| 101 | + # Above checks length of output from activityData is >0. If equals 0, is sub-project. |
| 102 | + print("activityData is empty, {} is subproject version {}".format(component['componentName'],component['componentVersionName'])) |
| 103 | + genreportsforversion(component['componentName'],component['componentVersionName'],reportlist=['FILES']) |
| 104 | + elif len(component['activityData']) != 0: |
| 105 | + print('is OSS component, no report to download') |
| 106 | + |
| 107 | + |
| 108 | +def checkdirs(): |
| 109 | + if os.path.isdir('./temp') == False: |
| 110 | + os.makedirs('./temp') |
| 111 | + print('made temp directory') |
| 112 | + else: |
| 113 | + print('temp directory already exists') |
| 114 | + if os.path.isdir('./results') == False: |
| 115 | + os.makedirs('./results') |
| 116 | + print('made results directory') |
| 117 | + else: |
| 118 | + print('results directory already exists') |
| 119 | + |
| 120 | + |
| 121 | +def unzip(): |
| 122 | + for filename in os.listdir("."): |
| 123 | + if filename.endswith(".zip"): |
| 124 | + shutil.move(filename, './temp/') |
| 125 | + curdir = (os.getcwd() + './temp/') |
| 126 | + os.chdir(curdir) |
| 127 | + for zipfile in os.listdir(curdir): |
| 128 | + with ZipFile(zipfile, 'r') as zipObj: |
| 129 | + zipObj.extractall() |
| 130 | + |
| 131 | + |
| 132 | +def concat(): |
| 133 | + for csv in glob.iglob('**/source*.csv'): |
| 134 | + csv_list.append(csv) |
| 135 | + consolidated = pandas.concat([pandas.read_csv(csv) for csv in csv_list]) |
| 136 | + consolidated.to_csv(file_out, index=False, encoding="utf-8") |
| 137 | + shutil.move(file_out, '../results/') |
| 138 | + |
| 139 | + # If you do not want the original source reports for each project exclude this. |
| 140 | + files = glob.iglob('**/*.csv') |
| 141 | + for csv in files: |
| 142 | + shutil.move(csv, '../results/') |
| 143 | + |
| 144 | + # Clean up after |
| 145 | + shutil.rmtree('../temp', ignore_errors=True) |
| 146 | + |
| 147 | + |
| 148 | +def main(): |
| 149 | + checkdirs() |
| 150 | + genreportsforversion(args.project_name, args.version_name,reportlist=['FILES','COMPONENTS','SECURITY']) |
| 151 | + unzip() |
| 152 | + concat() |
| 153 | + |
| 154 | + |
| 155 | +main() |
0 commit comments