scripts/update_excel_openpyxl.py (232 lines of code) (raw):

###################################################################### # # This script reads the checklist items from the latest checklist file # in Github (or from a local file) and populates an Excel spreadsheet # with the contents. # # Example usage: # python3 ./scripts/update_excel_openpyxl.py \ # --checklist-file=./checklists/aks_checklist.en.json \ # --find-all \ # --excel-file="./spreadsheet/macrofree/review_checklist_empty.xlsx" \ # --output-name-is-input-name \ # --output-path="./spreadsheet/macrofree/" # # Last updated: March 2022 # ###################################################################### import json import argparse import sys import os import requests import glob from openpyxl import load_workbook from openpyxl.worksheet.datavalidation import DataValidation # Get input arguments parser = argparse.ArgumentParser(description='Update a checklist spreadsheet with JSON-formatted Azure Resource Graph results') parser.add_argument('--checklist-file', dest='checklist_file', action='store', help='You can optionally supply a JSON file containing the checklist you want to dump to the Excel spreadsheet. Otherwise it will take the latest file from Github') parser.add_argument('--only-english', dest='only_english', action='store_true', default=False, help='if checklist files are specified, ignore the non-English ones and only generate a spreadsheet for the English version (default: False)') parser.add_argument('--find-all', dest='find_all', action='store_true', default=False, help='if checklist files are specified, find all the languages for the given checklists (default: False)') parser.add_argument('--technology', dest='technology', action='store', help='If you do not supply a JSON file with the checklist, you need to specify the technology from which the latest checklist will be downloaded from Github') parser.add_argument('--excel-file', dest='excel_file', action='store', help='You need to supply an Excel file where the checklist will be written') parser.add_argument('--output-excel-file', dest='output_excel_file', action='store', help='You can optionally supply an Excel file where the checklist will be saved, otherwise it will be updated in-place') parser.add_argument('--output-path', dest='output_path', action='store', help='If using --output-name-is-input-name, folder where to store the results') parser.add_argument('--output-name-is-input-name', dest='output_name_is_input_name', action='store_true', default=False, help='Save the output in a file with the same filename as the JSON input, but with xlsx extension') parser.add_argument('--verbose', dest='verbose', action='store_true', default=False, help='run in verbose mode (default: False)') args = parser.parse_args() checklist_file = args.checklist_file excel_file = args.excel_file technology = args.technology # Constants worksheet_checklist_name = 'Checklist' row1 = 8 # First row after which the Excel spreadsheet will be updated col_checklist_name = "A" row_checklist_name = "4" guid_column_index = "L" comment_column_index = "G" sample_cell_index = 'A4' col_area = "A" col_subarea = "B" col_check = "C" col_desc = "D" col_sev = "E" col_status = "F" col_comment = "G" col_link = "H" col_training = "I" col_arg_success = "J" col_arg_failure = "K" col_guid = "L" info_link_text = 'More info' training_link_text = 'Training' worksheet_values_name = 'Values' values_row1 = 2 col_values_severity = "A" col_values_status = "B" col_values_area = "C" col_values_description = "H" # Main function def update_excel_file(input_excel_file, output_excel_file, checklist_data): # Load workbook try: wb = load_workbook(filename = input_excel_file) if args.verbose: print("DEBUG: workbook", input_excel_file, "opened successfully") except Exception as e: print("ERROR: Error when opening Excel file", input_excel_file, "-", str(e)) sys.exit(1) # Get worksheet try: ws = wb[worksheet_checklist_name] if args.verbose: print("DEBUG: worksheet", worksheet_checklist_name, "selected successfully") except Exception as e: print("ERROR: Error when selecting worksheet", worksheet_checklist_name, "-", str(e)) sys.exit(1) # Set checklist name try: if 'metadata' in checklist_data: if 'name' in checklist_data['metadata']: ws[col_checklist_name + row_checklist_name] = checklist_data["metadata"]["name"] else: ws[col_checklist_name + row_checklist_name] = "Azure Review Checklist" else: ws[col_checklist_name + row_checklist_name] = "Azure Review Checklist" if args.verbose: print("DEBUG: starting filling the Excel spreadsheet with the values of checklist '{0}'".format(checklist_data["metadata"]["name"])) except Exception as e: print("ERROR: Error when filling in worksheet", worksheet_checklist_name, "-", str(e)) sys.exit(1) # Get default status from the JSON, default to "Not verified" try: status_list = checklist_data.get("status") default_status = status_list[0].get("name") if args.verbose: print ("DEBUG: default status retrieved from checklist: '{0}'".format(default_status)) except: default_status = "Not verified" if args.verbose: print ("DEBUG: Using default status 'Not verified'") pass # For each checklist item, add a row to spreadsheet row_counter = row1 for item in checklist_data.get("items"): # Read variables from JSON guid = item.get("guid") category = item.get("category") subcategory = item.get("subcategory") text = item.get("text") description = item.get("description") severity = item.get("severity") link = item.get("link") training = item.get("training") status = default_status graph_query_success = item.get("graph_success") graph_query_failure = item.get("graph_failure") # Update Excel ws[col_area + str(row_counter)].value = category ws[col_subarea + str(row_counter)].value = subcategory ws[col_check + str(row_counter)].value = text ws[col_desc + str(row_counter)].value = description ws[col_sev + str(row_counter)].value = severity ws[col_status + str(row_counter)].value = status ws[col_link + str(row_counter)].value = link # if link != None: # link_elements = link.split('#') # link_address = link_elements[0] # if len(link_elements) > 1: # link_subaddress = link_elements[1] # else: # link_subaddress = "" # ws.api.Hyperlinks.Add (Anchor=ws[col_link + str(row_counter)].api, Address=link_address, SubAddress=link_subaddress, ScreenTip="", TextToDisplay=info_link_text) ws[col_training + str(row_counter)].value = training # if training != None: # training_elements = training.split('#') # training_address = training_elements[0] # if len(training_elements) > 1: # training_subaddress = training_elements[1] # else: # training_subaddress = "" # ws.api.Hyperlinks.Add (Anchor=ws[col_training + str(row_counter)].api, Address=training_address, SubAddress=training_subaddress, ScreenTip="", TextToDisplay=training_link_text) # GUID and ARG queries ws[col_arg_success + str(row_counter)].value = graph_query_success ws[col_arg_failure + str(row_counter)].value = graph_query_failure ws[col_guid + str(row_counter)].value = guid # Next row row_counter += 1 # Display summary if args.verbose: number_of_checks = row_counter - row1 print("DEBUG:", str(number_of_checks), "checks added to Excel spreadsheet") # Get worksheet try: wsv = wb[worksheet_values_name] if args.verbose: print("DEBUG: worksheet", worksheet_values_name, "selected successfully") except Exception as e: print("ERROR: Error when selecting worksheet", worksheet_values_name, "-", str(e)) sys.exit(1) # Update categories row_counter = values_row1 for item in checklist_data.get("categories"): area = item.get("name") wsv[col_values_area + str(row_counter)].value = area row_counter += 1 # Display summary if args.verbose: print("DEBUG:", str(row_counter - values_row1), "categories added to Excel spreadsheet") # Update status if 'status' in checklist_data: row_counter = values_row1 for item in checklist_data.get("status"): status = item.get("name") description = item.get("description") wsv[col_values_status + str(row_counter)].value = status wsv[col_values_description + str(row_counter)].value = description row_counter += 1 else: print('ERROR: no "status" information in the checklist.') # Display summary if args.verbose: print("DEBUG:", str(row_counter - values_row1), "statuses added to Excel spreadsheet") # Update severities if 'severities' in checklist_data: row_counter = values_row1 for item in checklist_data.get("severities"): severity = item.get("name") wsv[col_values_severity + str(row_counter)].value = severity row_counter += 1 # Display summary if args.verbose: print("DEBUG:", str(row_counter - values_row1), "severities added to Excel spreadsheet") # Data validation # dv = DataValidation(type="list", formula1='=Values!$B$2:$B$6', allow_blank=True, showDropDown=True) dv = DataValidation(type="list", formula1='=Values!$B$2:$B$6', allow_blank=True) rangevar = col_status + str(row1) +':' + col_status + str(row1 + number_of_checks) if args.verbose: print("DEBUG: adding data validation to range", rangevar) dv.add(rangevar) ws.add_data_validation(dv) # Close book if args.verbose: print("DEBUG: saving workbook", output_excel_file) try: wb.save(output_excel_file) except Exception as e: print("ERROR: Error when saving Excel file to", output_excel_file, "-", str(e)) sys.exit(1) ######## # Main # ######## # Download checklist if checklist_file: checklist_file_list = checklist_file.split(" ") # If --only-english parameter was supplied, take only the English version and remove duplicates if args.only_english: checklist_file_list = [file[:-8] + '.en.json' for file in checklist_file_list] checklist_file_list = list(set(checklist_file_list)) if args.verbose: print("DEBUG: new checklist file list:", str(checklist_file_list)) # If --find-all parameter was supplied, find all the languages for the checklist if args.find_all: new_file_list = [] for checklist_file in checklist_file_list: filedir = os.path.dirname(checklist_file) filebase = os.path.basename(checklist_file) filebase_noext = filebase[:-8] # Remove '.en.json' file_match_list = glob.glob(os.path.join(filedir, filebase_noext + '.*.json')) for checklist_match in file_match_list: # new_file_list.append(os.path.join(filedir, checklist_match)) new_file_list.append(checklist_match) checklist_file_list = list(set(new_file_list)) if args.verbose: print("DEBUG: new checklist file list:", str(checklist_file_list)) # Go over the list for checklist_file in checklist_file_list: if args.verbose: print("DEBUG: Opening checklist file", checklist_file) # Get JSON try: with open(checklist_file) as f: checklist_data = json.load(f) except Exception as e: print("ERROR: Error when processing JSON file", checklist_file, "-", str(e)) sys.exit(0) # Set input and output files input_excel_file = excel_file if args.output_excel_file: output_excel_file = args.output_excel_file elif args.output_name_is_input_name: if args.output_path: # Get filename without path and extension output_excel_file = os.path.splitext(os.path.basename(checklist_file))[0] + '.xlsx' output_excel_file = os.path.join(args.output_path, output_excel_file) else: # Just change the extension output_excel_file = os.path.splitext(checklist_file)[0] + '.xlsx' # Update spreadsheet update_excel_file(input_excel_file, output_excel_file, checklist_data) else: if technology: checklist_url = "https://raw.githubusercontent.com/Azure/review-checklists/main/checklists/" + technology + "_checklist.en.json" else: checklist_url = "https://raw.githubusercontent.com/Azure/review-checklists/main/checklists/alz_checklist.en.json" if args.verbose: print("DEBUG: Downloading checklist file from", checklist_url) response = requests.get(checklist_url) # If download was successful if response.status_code == 200: if args.verbose: print ("DEBUG: File {0} downloaded successfully".format(checklist_url)) try: # Deserialize JSON to object variable checklist_data = json.loads(response.text) except Exception as e: print("Error deserializing JSON content: {0}".format(str(e))) sys.exit(1) # Upload spreadsheet if args.output_excel_file: output_excel_file = args.output_excel_file else: output_excel_file = excel_file update_excel_file(excel_file, output_excel_file, checklist_data)