|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +import sys |
| 4 | +import os |
| 5 | +import time |
| 6 | +import argparse |
| 7 | +from http import HTTPStatus |
| 8 | +try: |
| 9 | + import requests |
| 10 | +except ImportError: |
| 11 | + print("requests module is not installed. script will fail to execute") |
| 12 | + |
| 13 | +#debug print level |
| 14 | +PRINT_LEVEL_ERROR = "err" |
| 15 | +PRINT_LEVEL_WARN = "warn" |
| 16 | +PRINT_LEVEL_INFO = "info" |
| 17 | +PRINT_LEVEL_VERBOSE = "verbose" |
| 18 | + |
| 19 | +PRINT_LEVEL_LUT = {PRINT_LEVEL_ERROR : 1, |
| 20 | + PRINT_LEVEL_WARN : 2, |
| 21 | + PRINT_LEVEL_INFO : 3, |
| 22 | + PRINT_LEVEL_VERBOSE : 4 } |
| 23 | + |
| 24 | +#return code |
| 25 | +RET_CODE_SUCCESS = 0 |
| 26 | +RET_CODE_CANNOT_CREATE_FILE = -1 |
| 27 | +RET_CODE_CANNOT_OPEN_FILE = -2 |
| 28 | +RET_CODE_HTTP_SERVER_ERROR = -3 |
| 29 | +RET_CODE_CANNOT_WRITE_FILE = -4 |
| 30 | + |
| 31 | +#constants |
| 32 | +RESOURCES_FILE_NAME = 'versions-web' |
| 33 | +EXCLUDE_DIRECTORES = ['fsroot', 'target'] |
| 34 | +HASH_SEPARATOR = '-' |
| 35 | +DEFAULT_INVALID_INPUT = 'none' |
| 36 | + |
| 37 | +# global variables |
| 38 | +g_current_print_level = PRINT_LEVEL_INFO |
| 39 | + |
| 40 | +#Script debug features (disabled by default) |
| 41 | +g_delete_resources_in_cache = True |
| 42 | + |
| 43 | + |
| 44 | +# global Classes |
| 45 | +class Resource: |
| 46 | + def __init__(self, line, file): |
| 47 | + self.file = file |
| 48 | + temp=line.split("==") |
| 49 | + assert(2==len(temp)) |
| 50 | + self.url=temp[0].strip() |
| 51 | + self.hash=temp[1].strip() |
| 52 | + temp=self.url.split("/") |
| 53 | + assert(len(temp)>0) |
| 54 | + self.name=temp[len(temp)-1] |
| 55 | + #handle special scenarios |
| 56 | + if 0 != self.name.count('?') == True: |
| 57 | + temp = self.name.split("?") |
| 58 | + self.name = temp[0] |
| 59 | + |
| 60 | + def get_unique_name(self): |
| 61 | + return self.name + HASH_SEPARATOR + self.hash |
| 62 | + |
| 63 | + def get_url(self): |
| 64 | + return self.url |
| 65 | + |
| 66 | + def __str__(self): |
| 67 | + ret_val = "Resource name: " + self.name + "\n" |
| 68 | + ret_val += "File: " + self.file + "\n" |
| 69 | + ret_val += "Hash: " + self.hash + "\n" |
| 70 | + ret_val += "Full URL: " + self.url |
| 71 | + return ret_val |
| 72 | + |
| 73 | +# Helper functions |
| 74 | + |
| 75 | +def print_msg(print_level, msg, print_in_place=False): |
| 76 | + if PRINT_LEVEL_LUT[g_current_print_level] >= PRINT_LEVEL_LUT[print_level]: |
| 77 | + if True == print_in_place: |
| 78 | + print(msg, end='\r') |
| 79 | + else: |
| 80 | + print(msg) |
| 81 | + |
| 82 | +def create_dir_if_not_exist(dir): |
| 83 | + if not os.path.exists(dir): |
| 84 | + try: |
| 85 | + os.makedirs(dir) |
| 86 | + except: |
| 87 | + print_msg(PRINT_LEVEL_WARN, "Cannot create directory " + dir) |
| 88 | + |
| 89 | +def delete_file_if_exist(file): |
| 90 | + if os.path.exists(file): |
| 91 | + try: |
| 92 | + os.remove(file) |
| 93 | + except: |
| 94 | + print_msg(PRINT_LEVEL_WARN, "Cannot delete " + file) |
| 95 | + |
| 96 | +# Logic functions |
| 97 | + |
| 98 | +def generate_output_file(resources, dest_url_valid, dest_url, output_file_name): |
| 99 | + try: |
| 100 | + with open(output_file_name, 'w') as f: |
| 101 | + for unique_name in resources.keys(): |
| 102 | + resource = resources[unique_name] |
| 103 | + if True == dest_url_valid: |
| 104 | + line = dest_url |
| 105 | + else: |
| 106 | + line = resource.get_url() |
| 107 | + if line[-1] != '/': |
| 108 | + line += '/' |
| 109 | + line += resource.name + "==" + resource.hash |
| 110 | + f.write(line + '\n') |
| 111 | + except: |
| 112 | + print_msg(PRINT_LEVEL_WARN, output_file_name + " cannot be created") |
| 113 | + return RET_CODE_CANNOT_CREATE_FILE |
| 114 | + |
| 115 | + return RET_CODE_SUCCESS |
| 116 | + |
| 117 | +def upload_resource_to_server(resource_path, resource_name, user, key, server_url): |
| 118 | + url_full_path = server_url + "/" + resource_name |
| 119 | + |
| 120 | + try: |
| 121 | + f = open(resource_path, 'rb') |
| 122 | + except: |
| 123 | + err_print("Cannot open " + resource_path) |
| 124 | + return RET_CODE_CANNOT_OPEN_FILE |
| 125 | + |
| 126 | + headers = {'Content-type': 'application', 'Slug': resource_name} |
| 127 | + response = requests.put(url_full_path, data=f, |
| 128 | + headers=headers, auth=(user, key)) |
| 129 | + |
| 130 | + f.close() |
| 131 | + |
| 132 | + if response.status_code != HTTPStatus.CREATED.value: |
| 133 | + err_print(f"HTTP request returned status code {response.status_code}, expected {HTTPStatus.CREATED.value}") |
| 134 | + return RET_CODE_HTTP_SERVER_ERROR |
| 135 | + |
| 136 | + # JSON response empty only when status code is 204 |
| 137 | + reported_md5 = response.json().get('checksums', {}).get('md5') |
| 138 | + file_md5 = resource_name.split(HASH_SEPARATOR)[-1] |
| 139 | + |
| 140 | + # Check if server reports checksum, if so compare reported sum and the one |
| 141 | + # specified in filename |
| 142 | + if reported_md5 != None and reported_md5 != file_md5: |
| 143 | + print_msg(PRINT_LEVEL_WARN, f"Server reported file's chsum {reported_md5}, expected {file_md5}") |
| 144 | + |
| 145 | + |
| 146 | + return RET_CODE_SUCCESS |
| 147 | + |
| 148 | +def download_external_resouce(resource, cache_path): |
| 149 | + resource_path_in_cache = cache_path + os.sep + resource.get_unique_name() |
| 150 | + |
| 151 | + r = requests.get(resource.get_url(), allow_redirects=True) |
| 152 | + |
| 153 | + try: |
| 154 | + f = open(resource_path_in_cache, 'wb') |
| 155 | + f.write(r.content) |
| 156 | + f.close() |
| 157 | + except: |
| 158 | + print_msg(PRINT_LEVEL_ERROR, "Cannot write " + resource_path_in_cache + " to cache") |
| 159 | + resource_path_in_cache = "" #report error |
| 160 | + |
| 161 | + return resource_path_in_cache |
| 162 | + |
| 163 | +def get_resources_list(resource_files_list): |
| 164 | + resource_list = list() |
| 165 | + |
| 166 | + for file_name in resource_files_list: |
| 167 | + try: |
| 168 | + with open(file_name, 'r') as f: |
| 169 | + for line in f: |
| 170 | + resource_list.append(Resource(line, file_name)) |
| 171 | + except: |
| 172 | + print_msg(PRINT_LEVEL_WARN, file_name + " cannot be opened") |
| 173 | + |
| 174 | + return resource_list |
| 175 | + |
| 176 | +def filter_out_dir(subdir): |
| 177 | + ret_val = False |
| 178 | + |
| 179 | + for exclude in EXCLUDE_DIRECTORES: |
| 180 | + if exclude in subdir.split(os.sep): |
| 181 | + ret_val = True |
| 182 | + break |
| 183 | + |
| 184 | + return ret_val |
| 185 | + |
| 186 | +def get_resource_files_list(serach_path): |
| 187 | + resource_files_list = list() |
| 188 | + |
| 189 | + for subdir, dirs, files in os.walk(serach_path): |
| 190 | + for file in files: |
| 191 | + if False == filter_out_dir(subdir) and RESOURCES_FILE_NAME == file: |
| 192 | + file_full_path = os.path.join(subdir, file) |
| 193 | + print_msg(PRINT_LEVEL_VERBOSE, "Found resource file :" + file_full_path) |
| 194 | + resource_files_list.append(file_full_path) |
| 195 | + |
| 196 | + return resource_files_list |
| 197 | + |
| 198 | +def parse_args(): |
| 199 | + parser = argparse.ArgumentParser(description='Various pre-steps for build compilation') |
| 200 | + |
| 201 | + parser.add_argument('-s', '--source', default=".", |
| 202 | + help='Search path for ' + RESOURCES_FILE_NAME + ' files') |
| 203 | + |
| 204 | + parser.add_argument('-c', '--cache', default="." + os.sep + "tmp", |
| 205 | + help='Path to cache for storing content before uploading to server') |
| 206 | + |
| 207 | + parser.add_argument('-p', '--print', default=PRINT_LEVEL_INFO, |
| 208 | + choices=[PRINT_LEVEL_ERROR, PRINT_LEVEL_WARN, PRINT_LEVEL_INFO, PRINT_LEVEL_VERBOSE], |
| 209 | + help='Print level verbosity') |
| 210 | + |
| 211 | + parser.add_argument('-o', '--output', default=DEFAULT_INVALID_INPUT, |
| 212 | + help='Output file name to hold the list of packages') |
| 213 | + |
| 214 | + parser.add_argument('-u', '--user', default=DEFAULT_INVALID_INPUT, |
| 215 | + help='User for server authentication') |
| 216 | + |
| 217 | + parser.add_argument('-k', '--key', default=DEFAULT_INVALID_INPUT, |
| 218 | + help='API key server authentication') |
| 219 | + |
| 220 | + parser.add_argument('-d', '--dest', default=DEFAULT_INVALID_INPUT, |
| 221 | + help='URL for destination web file server') |
| 222 | + |
| 223 | + return parser.parse_args() |
| 224 | + |
| 225 | +def main(): |
| 226 | + global g_current_print_level |
| 227 | + ret_val = RET_CODE_SUCCESS |
| 228 | + resource_counter = 0.0 |
| 229 | + resource_dict = dict() |
| 230 | + |
| 231 | + args = parse_args() |
| 232 | + |
| 233 | + g_current_print_level = args.print |
| 234 | + |
| 235 | + resource_files_list = get_resource_files_list(args.source) |
| 236 | + |
| 237 | + resource_list = get_resources_list(resource_files_list) |
| 238 | + |
| 239 | + #remove duplications |
| 240 | + for resource in resource_list: |
| 241 | + unique_name = resource.get_unique_name() |
| 242 | + if not unique_name in resource_dict.keys(): |
| 243 | + resource_dict[unique_name] = resource |
| 244 | + |
| 245 | + print_msg(PRINT_LEVEL_INFO, "Found " + str(len(resource_files_list)) + " version files and " + str(len(resource_dict.keys())) + " unique resources") |
| 246 | + |
| 247 | + if args.dest != DEFAULT_INVALID_INPUT: |
| 248 | + upload_files_to_server = True |
| 249 | + print_msg(PRINT_LEVEL_INFO, "Upload files to URL - " + args.dest) |
| 250 | + else: |
| 251 | + upload_files_to_server = False |
| 252 | + print_msg(PRINT_LEVEL_INFO, "Skipping files upload to server") |
| 253 | + |
| 254 | + #create cache directory if not exist |
| 255 | + create_dir_if_not_exist(args.cache) |
| 256 | + |
| 257 | + #download content to cache and then upload to web server |
| 258 | + for unique_name in resource_dict.keys(): |
| 259 | + |
| 260 | + resource = resource_dict[unique_name] |
| 261 | + |
| 262 | + print_msg(PRINT_LEVEL_VERBOSE, resource) |
| 263 | + |
| 264 | + resource_counter += 1.0 |
| 265 | + |
| 266 | + #download content to cache |
| 267 | + file_in_cache = download_external_resouce(resource, args.cache) |
| 268 | + |
| 269 | + if "" == file_in_cache: |
| 270 | + return RET_CODE_CANNOT_WRITE_FILE |
| 271 | + |
| 272 | + if True == upload_files_to_server: |
| 273 | + #upload content to web server |
| 274 | + ret_val = upload_resource_to_server(file_in_cache, unique_name, args.user, args.key, args.dest) |
| 275 | + if ret_val != RET_CODE_SUCCESS: |
| 276 | + return ret_val |
| 277 | + |
| 278 | + if True == g_delete_resources_in_cache: |
| 279 | + delete_file_if_exist(file_in_cache) |
| 280 | + |
| 281 | + print_msg(PRINT_LEVEL_INFO, "Downloading Data. Progress " + str(int(100.0*resource_counter/len(resource_dict.keys()))) + "%", True) #print progress bar |
| 282 | + |
| 283 | + # generate version output file as needed |
| 284 | + if args.output != DEFAULT_INVALID_INPUT: |
| 285 | + ret_val = generate_output_file(resource_dict, upload_files_to_server, args.dest, args.output) |
| 286 | + print_msg(PRINT_LEVEL_INFO, "Generate output file " + args.output) |
| 287 | + |
| 288 | + return ret_val |
| 289 | + |
| 290 | +# Entry function |
| 291 | +if __name__ == '__main__': |
| 292 | + |
| 293 | + ret_val = main() |
| 294 | + |
| 295 | + sys.exit(ret_val) |
0 commit comments