scripts/backCompatChecker.py (109 lines of code) (raw):

# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import re import os import subprocess oldArguments = [] newArguments = [] allowedMissingArguments = {} allowedMissingArguments['devops extension uninstall'] = ['--extension-name', '--publisher-name'] allowedMissingArguments['devops extension install'] = ['--extension-name', '--publisher-name'] allowedMissingArguments['devops extension enable'] = ['--extension-name', '--publisher-name'] allowedMissingArguments['devops extension disable'] = ['--extension-name', '--publisher-name'] allowedMissingArguments['devops extension show'] = ['--extension-name', '--publisher-name'] allowedNewMandatoryArguments = {} allowedNewMandatoryArguments['devops extension uninstall'] = ['--extension-id', '--publisher-id'] allowedNewMandatoryArguments['devops extension install'] = ['--extension-id', '--publisher-id'] allowedNewMandatoryArguments['devops extension enable'] = ['--extension-id', '--publisher-id'] allowedNewMandatoryArguments['devops extension disable'] = ['--extension-id', '--publisher-id'] allowedNewMandatoryArguments['devops extension show'] = ['--extension-id', '--publisher-id'] # Do not compare these commands ignoreCommands = [] class Arguments(dict): def __init__(self, command, name, isRequired): self.command = command self.name = name self.isRequired = isRequired dict.__init__(self,command = command, name = name, isRequired = isRequired) def extractArgumentsFromCommand(command): print('running extractArgumentsFromCommand for ' + command) argumentList = [] commandExtended = 'az ' + command + ' -h' help_text = subprocess.run(commandExtended.split(' '), shell=True, stdout=subprocess.PIPE) print('help text for ' + command) print(help_text) if " is in preview" in str(help_text): return argumentList regexReesult = re.search('Arguments(.*)Global Arguments',str(help_text)) result = regexReesult.group(1) argumentLines = result.split('\\r\\n') for argumentLine in argumentLines: argumentLineSplits = argumentLine.split(" : ") if len(argumentLineSplits) > 1 and ' : ' in argumentLine: isRequired = False if '[Required]' in argumentLineSplits[0]: isRequired = True names = argumentLineSplits[0].replace('[Required]','').strip().split(' ') for name in names: argument = Arguments(command, name, isRequired) argumentList.append(argument) return argumentList # remove azure-devops extension from index (if installed) subprocess.run(['az', 'extension', 'remove', '-n', 'azure-devops'], shell=True) # install extension from index subprocess.run(['az', 'extension', 'add', '-n', 'azure-devops'], shell=True) # Check the installed extensions subprocess.run(['az', 'extension', 'list'], shell=True) # add extension path to sys.path so that we can get all the commands import sys from azure.cli.core.extension import get_extension_path # Make sure that the extension install directory is on sys.path so that dependencies can be found. extensionPath = get_extension_path('azure-devops') sys.path.append(extensionPath) # loading commands from code from azure.cli.core.mock import DummyCli from azext_devops import DevCommandsLoader cli_ctx = DummyCli() loader = DevCommandsLoader(cli_ctx) loader.load_command_table(None) for command in loader.command_table: oldArguments.extend(extractArgumentsFromCommand(command)) print('Unload extension (loaded from index).') # uninstall extension loaded from index subprocess.run(['az', 'extension', 'remove', '-n', 'azure-devops'], shell=True, stdout=subprocess.PIPE) # search and install extension from given path def findExtension(): for p, d, f in os.walk('.'): for file in f: if file.endswith('.whl'): return os.path.join(p, file) newExtensionLocation = findExtension() print('Install extension (loaded from current code). Wheel path - {}'.format(newExtensionLocation)) subprocess.run(['az', 'extension', 'add', '--source', newExtensionLocation, '-y'], shell=True, stdout=subprocess.PIPE) # Check the installed extensions subprocess.run(['az', 'extension', 'list'], shell=True) # get a set of old commands, we are not reusing the set from ext because we want to keep this clean oldCommands = [] for oldArgument in oldArguments: if oldArgument.command not in ignoreCommands: if not (oldArgument.command in oldCommands): oldCommands.append(oldArgument.command) else: print('Ignoring command.. ' + oldArgument.command) # prepare argument set from new extension for oldCommand in oldCommands: print("Running extract for command: {}".format(oldCommand)) newArguments.extend(extractArgumentsFromCommand(oldCommand)) errorList = [] # make sure no new argument is mandatory for newArgument in newArguments: if newArgument.isRequired is True: isNewMandatory = True for oldArgument in oldArguments: if oldArgument.command == newArgument.command and oldArgument.name == newArgument.name and oldArgument.isRequired is True: isNewMandatory = False break if isNewMandatory is True: allowedNewMandatoryArgumentsForCommand = allowedNewMandatoryArguments.get(newArgument.command, []) if not newArgument.name in allowedNewMandatoryArgumentsForCommand: errorList.append('\n' + 'New Mandatory argument found for command ' + newArgument.command + ' argument ' + newArgument.name) # make sure no argument is removed for oldArgument in oldArguments: if oldArgument.command not in ignoreCommands: isArgumentMissing = True for newArgument in newArguments: if oldArgument.name == newArgument.name and oldArgument.command == newArgument.command: isArgumentMissing = False break if isArgumentMissing is True: allowedMissingArgumetsForCommand = allowedMissingArguments.get(oldArgument.command, []) if not oldArgument.name in allowedMissingArgumetsForCommand: errorList.append('\n' + 'Argument missing for command ' + oldArgument.command + ' argument ' + oldArgument.name) if len(errorList) > 0: import sys sys.stderr.write(' '.join(errorList)) raise Exception('Something is not correct')