def test_no_exts_in_index(self): name = 'myext' with IndexPatch( {}), self.assertRaises(NoExtensionCandidatesError) as err: resolve_from_index(name) self.assertEqual(str(err.exception), "No extension found with name '{}'".format(name))
def test_filter_platform_wrong_pyver(self): name = 'myext' # Should raise as not py2.py3 with IndexPatch({ 'myext': [mock_ext('myext-0.0.1-py3-none-any.whl', '0.0.1')] }), self.assertRaises(NoExtensionCandidatesError): resolve_from_index(name)
def test_filter_platform_wrong_platform(self): name = 'myext' # Should raise as we don't support platform specific whls in index with IndexPatch({ 'myext': [mock_ext('myext-0.0.1-py2.py3-none-x86_64.whl', '0.0.1')] }), self.assertRaises(NoExtensionCandidatesError): resolve_from_index(name)
def test_ext_resolved(self): name = 'myext' index_data = { name: [mock_ext('myext-0.0.1-py2.py3-none-any.whl', '0.0.1')] } with IndexPatch(index_data): self.assertEqual( resolve_from_index(name)[0], index_data[name][0]['downloadUrl'])
def test_filter_platform(self): name = 'myext' index_data = { 'myext': [ mock_ext('myext-0.0.1-py2.py3-none-any.whl', '0.0.1'), mock_ext('myext-0.0.2-py3-none-any.whl', '0.0.2') ] } with IndexPatch(index_data): # Should choose the first one as it's not a platform specific whl. self.assertEqual( resolve_from_index(name)[0], index_data[name][0]['downloadUrl'])
def test_filter_platform(self): name = 'myext' index_data = { 'myext': [ mock_ext('myext-0.0.1-py2.py3-none-any.whl', '0.0.1'), mock_ext('myext-0.0.2-py3-none-any.whl', '0.0.2') ] } with IndexPatch(index_data): # Should choose the second one as py version is not considered platform specific. self.assertEqual( resolve_from_index(name)[0], index_data[name][1]['downloadUrl'])
def test_filter_target_version(self): ext_name = 'hello' index_data = { ext_name: [ mock_ext('hello-0.1.0-py3-none-any.whl', '0.1.0'), mock_ext('hello-0.2.0-py3-none-any.whl', '0.2.0') ] } # resolve okay with IndexPatch(index_data): self.assertEqual( resolve_from_index(ext_name, target_version='0.1.0')[0], index_data[ext_name][0]['downloadUrl']) self.assertEqual( resolve_from_index(ext_name, target_version='0.2.0')[0], index_data[ext_name][1]['downloadUrl']) with IndexPatch(index_data): with self.assertRaisesRegex( NoExtensionCandidatesError, 'Extension with version 0.3.0 not found'): resolve_from_index(ext_name, target_version='0.3.0')
def _check_value_in_extensions(cli_ctx, parser, args, no_prompt): # pylint: disable=too-many-statements, too-many-locals """Check if the command args can be found in extension commands. Exit command if the error is caused by an extension not installed. Otherwise return. """ # Check if the command is from an extension from azure.cli.core.util import roughly_parse_command from azure.cli.core.azclierror import NoTTYError exit_code = 2 command_str = roughly_parse_command(args[1:]) allow_prefix_match = args[-1] == '-h' or args[-1] == '--help' ext_name = _search_in_extension_commands(cli_ctx, command_str, allow_prefix_match=allow_prefix_match) # ext_name is a list if the input command matches the prefix of one or more extension commands, # for instance: `az blueprint` when running `az blueprint -h` # ext_name is a str if the input command matches a complete command of an extension, # for instance: `az blueprint create` if isinstance(ext_name, list): if len(ext_name) > 1: from knack.prompting import prompt_choice_list, NoTTYException prompt_msg = "The command requires the latest version of one of the following " \ "extensions. You need to pick one to install:" try: choice_idx = prompt_choice_list(prompt_msg, ext_name) ext_name = ext_name[choice_idx] no_prompt = True except NoTTYException: tty_err_msg = "{}{}\nUnable to prompt for selection as no tty available. Please update or " \ "install the extension with 'az extension add --upgrade -n <extension-name>'." \ .format(prompt_msg, ext_name) az_error = NoTTYError(tty_err_msg) az_error.print_error() az_error.send_telemetry() parser.exit(exit_code) else: ext_name = ext_name[0] if not ext_name: return # If a valid command has parser error, it may be caused by CLI running on a profile that is # not 'latest' and the command is not supported in that profile. If this command exists in an extension, # CLI will try to download the extension and rerun the command. But the parser will fail again and try to # install the extension and rerun the command infinitely. So we need to check if the latest version of the # extension is already installed and return if yes as the error is not caused by extension not installed. from azure.cli.core.extension import get_extension, ExtensionNotInstalledException from azure.cli.core.extension._resolve import resolve_from_index, NoExtensionCandidatesError try: ext = get_extension(ext_name) except ExtensionNotInstalledException: pass else: try: resolve_from_index(ext_name, cur_version=ext.version, cli_ctx=cli_ctx) except NoExtensionCandidatesError: return telemetry.set_command_details(command_str, parameters=AzCliCommandInvoker._extract_parameter_names(args), # pylint: disable=protected-access extension_name=ext_name) run_after_extension_installed = _get_extension_run_after_dynamic_install_config(cli_ctx) prompt_info = "" if no_prompt: logger.warning('The command requires the extension %s. It will be installed first.', ext_name) install_ext = True else: # yes_prompt from knack.prompting import prompt_y_n, NoTTYException prompt_msg = 'The command requires the extension {}. Do you want to install it now?'.format(ext_name) if run_after_extension_installed: prompt_msg = '{} The command will continue to run after the extension is installed.' \ .format(prompt_msg) NO_PROMPT_CONFIG_MSG = "Run 'az config set extension.use_dynamic_install=" \ "yes_without_prompt' to allow installing extensions without prompt." try: install_ext = prompt_y_n(prompt_msg, default='y') if install_ext: prompt_info = " with prompt" logger.warning(NO_PROMPT_CONFIG_MSG) except NoTTYException: tty_err_msg = "The command requires the extension {}. " \ "Unable to prompt for extension install confirmation as no tty " \ "available. {}".format(ext_name, NO_PROMPT_CONFIG_MSG) az_error = NoTTYError(tty_err_msg) az_error.print_error() az_error.send_telemetry() parser.exit(exit_code) print_error = True if install_ext: from azure.cli.core.extension.operations import add_extension add_extension(cli_ctx=cli_ctx, extension_name=ext_name, upgrade=True) if run_after_extension_installed: import subprocess import platform exit_code = subprocess.call(args, shell=platform.system() == 'Windows') # In this case, error msg is for telemetry recording purpose only. # From UX perspective, the command will rerun in subprocess. Whether it succeeds or fails, # mesages will be shown from the subprocess and this process should not print more message to # interrupt that. print_error = False error_msg = ("Extension {} dynamically installed{} and commands will be " "rerun automatically.").format(ext_name, prompt_info) else: error_msg = 'Extension {} installed{}. Please rerun your command.' \ .format(ext_name, prompt_info) else: error_msg = "The command requires the latest version of extension {ext_name}. " \ "To install, run 'az extension add --upgrade -n {ext_name}'.".format( ext_name=ext_name) az_error = CommandNotFoundError(error_msg) if print_error: az_error.print_error() az_error.send_telemetry() parser.exit(exit_code)