def format_help(self): telemetry.set_command_details( command=self.prog[3:], extension_name=self.command_source.extension_name if self.command_source else None) telemetry.set_success(summary='show help') super(AzCliCommandParser, self).format_help()
def format_help(self): is_group = self.is_group() telemetry.set_command_details(command=self.prog[3:]) telemetry.set_success(summary='show help') _help.show_help(self.prog.split()[1:], self._actions[-1] if is_group else self, is_group) self.exit()
def format_help(self): is_group = self.is_group() telemetry.set_command_details( command=self.prog[3:], extension_name=self.command_source.extension_name if self.command_source else None) telemetry.set_success(summary='show help') _help.show_help(self.prog.split()[1:], self._actions[-1] if is_group else self, is_group) self.exit()
def format_help(self): extension_version = None try: if self.command_source: extension_version = get_extension(self.command_source.extension_name).version except Exception: # pylint: disable=broad-except pass telemetry.set_command_details( command=self.prog[3:], extension_name=self.command_source.extension_name if self.command_source else None, extension_version=extension_version) telemetry.set_success(summary='show help') super(AzCliCommandParser, self).format_help()
def execute(self, args): from knack.events import ( EVENT_INVOKER_PRE_CMD_TBL_CREATE, EVENT_INVOKER_POST_CMD_TBL_CREATE, EVENT_INVOKER_CMD_TBL_LOADED, EVENT_INVOKER_PRE_PARSE_ARGS, EVENT_INVOKER_POST_PARSE_ARGS, EVENT_INVOKER_TRANSFORM_RESULT, EVENT_INVOKER_FILTER_RESULT) from knack.util import CommandResultItem, todict from azure.cli.core.commands.events import EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE # TODO: Can't simply be invoked as an event because args are transformed args = _pre_command_table_create(self.cli_ctx, args) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_CREATE, args=args) self.commands_loader.load_command_table(args) self.cli_ctx.raise_event( EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE, load_cmd_tbl_func=self.commands_loader.load_command_table, args=args) command = self._rudimentary_get_command(args) telemetry.set_raw_command_name(command) try: self.commands_loader.command_table = { command: self.commands_loader.command_table[command] } except KeyError: # Trim down the command table to reduce the number of subparsers required to optimize the performance. # # When given a command table like this: # # network application-gateway create # network application-gateway delete # network list-usages # storage account create # storage account list # # input: az # output: network application-gateway create # storage account create # # input: az network # output: network application-gateway create # network list-usages cmd_table = {} group_names = set() for cmd_name, cmd in self.commands_loader.command_table.items(): if command and not cmd_name.startswith(command): continue cmd_stub = cmd_name[len(command):].strip() group_name = cmd_stub.split(' ', 1)[0] if group_name not in group_names: cmd_table[cmd_name] = cmd group_names.add(group_name) self.commands_loader.command_table = cmd_table self.commands_loader.command_table = self.commands_loader.command_table # update with the truncated table self.commands_loader.command_name = command self.commands_loader.load_arguments(command) self.cli_ctx.raise_event(EVENT_INVOKER_POST_CMD_TBL_CREATE, cmd_tbl=self.commands_loader.command_table) self.parser.cli_ctx = self.cli_ctx self.parser.load_command_table(self.commands_loader.command_table) self.cli_ctx.raise_event(EVENT_INVOKER_CMD_TBL_LOADED, cmd_tbl=self.commands_loader.command_table, parser=self.parser) if not args: self.cli_ctx.completion.enable_autocomplete(self.parser) subparser = self.parser.subparsers[tuple()] self.help.show_welcome(subparser) # TODO: No event in base with which to target telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return None if args[0].lower() == 'help': args[0] = '--help' self.cli_ctx.completion.enable_autocomplete(self.parser) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_PARSE_ARGS, args=args) parsed_args = self.parser.parse_args(args) self.cli_ctx.raise_event(EVENT_INVOKER_POST_PARSE_ARGS, command=parsed_args.command, args=parsed_args) # TODO: This fundamentally alters the way Knack.invocation works here. Cannot be customized # with an event. Would need to be customized via inheritance. results = [] for expanded_arg in _explode_list_args(parsed_args): cmd = expanded_arg.func if hasattr(expanded_arg, 'cmd'): expanded_arg.cmd = cmd self.cli_ctx.data['command'] = expanded_arg.command self._validation(expanded_arg) params = self._filter_params(expanded_arg) command_source = self.commands_loader.command_table[ command].command_source extension_version = None try: if command_source: extension_version = get_extension( command_source.extension_name).version except Exception: # pylint: disable=broad-except pass telemetry.set_command_details( self.cli_ctx.data['command'], self.data['output'], [(p.split('=', 1)[0] if p.startswith('--') else p[:2]) for p in args if (p.startswith('-') and len(p) > 1)], extension_name=command_source.extension_name if command_source else None, extension_version=extension_version) if command_source: self.data[ 'command_extension_name'] = command_source.extension_name try: result = cmd(params) if cmd.supports_no_wait and getattr(expanded_arg, 'no_wait', False): result = None elif cmd.no_wait_param and getattr(expanded_arg, cmd.no_wait_param, False): result = None # TODO: Not sure how to make this actually work with the TRANSFORM event... transform_op = cmd.command_kwargs.get('transform', None) if transform_op: result = transform_op(result) if _is_poller(result): result = LongRunningOperation( self.cli_ctx, 'Starting {}'.format(cmd.name))(result) elif _is_paged(result): result = list(result) result = todict(result) event_data = {'result': result} self.cli_ctx.raise_event(EVENT_INVOKER_TRANSFORM_RESULT, event_data=event_data) self.cli_ctx.raise_event(EVENT_INVOKER_FILTER_RESULT, event_data=event_data) result = event_data['result'] results.append(result) except Exception as ex: # pylint: disable=broad-except if cmd.exception_handler: cmd.exception_handler(ex) return None else: six.reraise(*sys.exc_info()) if results and len(results) == 1: results = results[0] return CommandResultItem( results, table_transformer=self.commands_loader.command_table[ parsed_args.command].table_transformer, is_query_active=self.data['query_active'])
def execute(self, unexpanded_argv): # pylint: disable=too-many-statements argv = Application._expand_file_prefixed_files(unexpanded_argv) command_table = self.configuration.get_command_table(argv) self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table) self.parser.load_command_table(command_table) self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser) if not argv: enable_autocomplete(self.parser) az_subparser = self.parser.subparsers[tuple()] _help.show_welcome(az_subparser) # TODO: Question, is this needed? telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return None if argv[0].lower() == 'help': argv[0] = '--help' # Rudimentary parsing to get the command nouns = [] for i, current in enumerate(argv): try: if current[0] == '-': break except IndexError: pass argv[i] = current.lower() nouns.append(argv[i]) command = ' '.join(nouns) if argv[-1] in ('--help', '-h') or command in command_table: self.configuration.load_params(command) self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table) self.parser.load_command_table(command_table) if self.session['completer_active']: enable_autocomplete(self.parser) self.raise_event(self.COMMAND_PARSER_PARSING, argv=argv) args = self.parser.parse_args(argv) self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args) results = [] for expanded_arg in _explode_list_args(args): self.session['command'] = expanded_arg.command try: _validate_arguments(expanded_arg) except CLIError: raise except: # pylint: disable=bare-except err = sys.exc_info()[1] getattr(expanded_arg, '_parser', self.parser).validation_error(str(err)) # Consider - we are using any args that start with an underscore (_) as 'private' # arguments and remove them from the arguments that we pass to the actual function. # This does not feel quite right. params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith('_')]) params.pop('subcommand', None) params.pop('func', None) params.pop('command', None) telemetry.set_command_details(expanded_arg.command, self.configuration.output_format, [p for p in unexpanded_argv if p.startswith('-')]) result = expanded_arg.func(params) result = todict(result) results.append(result) if len(results) == 1: results = results[0] event_data = {'result': results} self.raise_event(self.TRANSFORM_RESULT, event_data=event_data) self.raise_event(self.FILTER_RESULT, event_data=event_data) return CommandResultItem(event_data['result'], table_transformer=command_table[args.command].table_transformer, is_query_active=self.session['query_active'])
def execute(self, args): from knack.events import (EVENT_INVOKER_PRE_CMD_TBL_CREATE, EVENT_INVOKER_POST_CMD_TBL_CREATE, EVENT_INVOKER_CMD_TBL_LOADED, EVENT_INVOKER_PRE_PARSE_ARGS, EVENT_INVOKER_POST_PARSE_ARGS, EVENT_INVOKER_TRANSFORM_RESULT, EVENT_INVOKER_FILTER_RESULT) from knack.util import CommandResultItem, todict from azure.cli.core.commands.events import EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE # TODO: Can't simply be invoked as an event because args are transformed args = _pre_command_table_create(self.cli_ctx, args) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_CREATE, args=args) self.commands_loader.load_command_table(args) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE, load_cmd_tbl_func=self.commands_loader.load_command_table, args=args) command = self._rudimentary_get_command(args) telemetry.set_raw_command_name(command) try: self.commands_loader.command_table = {command: self.commands_loader.command_table[command]} except KeyError: # Trim down the command table to reduce the number of subparsers required to optimize the performance. # # When given a command table like this: # # network application-gateway create # network application-gateway delete # network list-usages # storage account create # storage account list # # input: az # output: network application-gateway create # storage account create # # input: az network # output: network application-gateway create # network list-usages cmd_table = {} group_names = set() for cmd_name, cmd in self.commands_loader.command_table.items(): if command and not cmd_name.startswith(command): continue cmd_stub = cmd_name[len(command):].strip() group_name = cmd_stub.split(' ', 1)[0] if group_name not in group_names: cmd_table[cmd_name] = cmd group_names.add(group_name) self.commands_loader.command_table = cmd_table self.commands_loader.command_table = self.commands_loader.command_table # update with the truncated table self.commands_loader.command_name = command self.commands_loader.load_arguments(command) self.cli_ctx.raise_event(EVENT_INVOKER_POST_CMD_TBL_CREATE, commands_loader=self.commands_loader) self.parser.cli_ctx = self.cli_ctx self.parser.load_command_table(self.commands_loader) self.cli_ctx.raise_event(EVENT_INVOKER_CMD_TBL_LOADED, cmd_tbl=self.commands_loader.command_table, parser=self.parser) if not args: self.parser.enable_autocomplete() subparser = self.parser.subparsers[tuple()] self.help.show_welcome(subparser) # TODO: No event in base with which to target telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return None if args[0].lower() == 'help': args[0] = '--help' self.parser.enable_autocomplete() self.cli_ctx.raise_event(EVENT_INVOKER_PRE_PARSE_ARGS, args=args) parsed_args = self.parser.parse_args(args) self.cli_ctx.raise_event(EVENT_INVOKER_POST_PARSE_ARGS, command=parsed_args.command, args=parsed_args) # TODO: This fundamentally alters the way Knack.invocation works here. Cannot be customized # with an event. Would need to be customized via inheritance. results = [] for expanded_arg in _explode_list_args(parsed_args): cmd = expanded_arg.func if hasattr(expanded_arg, 'cmd'): expanded_arg.cmd = cmd self.cli_ctx.data['command'] = expanded_arg.command self._validation(expanded_arg) params = self._filter_params(expanded_arg) command_source = self.commands_loader.command_table[command].command_source extension_version = None extension_name = None try: if isinstance(command_source, ExtensionCommandSource): extension_name = command_source.extension_name extension_version = get_extension(command_source.extension_name).version except Exception: # pylint: disable=broad-except pass telemetry.set_command_details(self.cli_ctx.data['command'], self.data['output'], [(p.split('=', 1)[0] if p.startswith('--') else p[:2]) for p in args if (p.startswith('-') and len(p) > 1)], extension_name=extension_name, extension_version=extension_version) if extension_name: self.data['command_extension_name'] = extension_name deprecations = [] + getattr(expanded_arg, '_argument_deprecations', []) if cmd.deprecate_info: deprecations.append(cmd.deprecate_info) # search for implicit deprecation path_comps = cmd.name.split()[:-1] implicit_deprecate_info = None while path_comps and not implicit_deprecate_info: implicit_deprecate_info = resolve_deprecate_info(self.cli_ctx, ' '.join(path_comps)) del path_comps[-1] if implicit_deprecate_info: deprecate_kwargs = implicit_deprecate_info.__dict__.copy() deprecate_kwargs['object_type'] = 'command' del deprecate_kwargs['_get_tag'] del deprecate_kwargs['_get_message'] deprecations.append(ImplicitDeprecated(**deprecate_kwargs)) for d in deprecations: logger.warning(d.message) try: result = cmd(params) if cmd.supports_no_wait and getattr(expanded_arg, 'no_wait', False): result = None elif cmd.no_wait_param and getattr(expanded_arg, cmd.no_wait_param, False): result = None transform_op = cmd.command_kwargs.get('transform', None) if transform_op: result = transform_op(result) if _is_poller(result): result = LongRunningOperation(self.cli_ctx, 'Starting {}'.format(cmd.name))(result) elif _is_paged(result): result = list(result) result = todict(result, AzCliCommandInvoker.remove_additional_prop_layer) event_data = {'result': result} self.cli_ctx.raise_event(EVENT_INVOKER_TRANSFORM_RESULT, event_data=event_data) result = event_data['result'] results.append(result) except Exception as ex: # pylint: disable=broad-except if cmd.exception_handler: cmd.exception_handler(ex) return None else: six.reraise(*sys.exc_info()) if results and len(results) == 1: results = results[0] event_data = {'result': results} self.cli_ctx.raise_event(EVENT_INVOKER_FILTER_RESULT, event_data=event_data) return CommandResultItem( event_data['result'], table_transformer=self.commands_loader.command_table[parsed_args.command].table_transformer, is_query_active=self.data['query_active'])
def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many-locals, too-many-statements, too-many-branches, no-member, unused-argument import os import platform import sys import subprocess import azure.cli.core.telemetry as telemetry from azure.cli.core import __version__ as local_version from azure.cli.core._environment import _ENV_AZ_INSTALLER from azure.cli.core.extension import get_extensions, WheelExtension from distutils.version import LooseVersion from knack.util import CLIError update_cli = True from azure.cli.core.util import get_latest_from_github try: latest_version = get_latest_from_github() if latest_version and LooseVersion(latest_version) <= LooseVersion( local_version): logger.warning("You already have the latest azure-cli version: %s", local_version) update_cli = False if not update_all: return except Exception as ex: # pylint: disable=broad-except logger.debug("Failed to get the latest version. %s", str(ex)) exts = [ext.name for ext in get_extensions( ext_type=WheelExtension)] if update_all else [] exit_code = 0 installer = os.getenv(_ENV_AZ_INSTALLER) or '' installer = installer.upper() if update_cli: latest_version_msg = 'It will be updated to {}.'.format(latest_version) if yes \ else 'Latest version available is {}.'.format(latest_version) logger.warning("Your current Azure CLI version is %s. %s", local_version, latest_version_msg) from knack.prompting import prompt_y_n if not yes: confirmation = prompt_y_n( "Please check the release notes first: https://docs.microsoft.com/" "cli/azure/release-notes-azure-cli\nDo you want to continue?", default='y') if not confirmation: telemetry.set_success("Upgrade stopped by user") return if installer == 'DEB': from azure.cli.core.util import in_cloud_console if in_cloud_console(): raise CLIError("az upgrade is not supported in Cloud Shell.") apt_update_cmd = 'apt-get update'.split() az_update_cmd = 'apt-get install --only-upgrade -y azure-cli'.split( ) if os.geteuid() != 0: # pylint: disable=no-member apt_update_cmd.insert(0, 'sudo') az_update_cmd.insert(0, 'sudo') exit_code = subprocess.call(apt_update_cmd) if exit_code == 0: logger.debug("Update azure cli with '%s'", " ".join(apt_update_cmd)) exit_code = subprocess.call(az_update_cmd) elif installer == 'RPM': from azure.cli.core.util import get_linux_distro distname, _ = get_linux_distro() if not distname: logger.warning(UPGRADE_MSG) else: distname = distname.lower().strip() if any(x in distname for x in ['centos', 'rhel', 'red hat', 'fedora']): update_cmd = 'yum update -y azure-cli'.split() if os.geteuid() != 0: # pylint: disable=no-member update_cmd.insert(0, 'sudo') logger.debug("Update azure cli with '%s'", " ".join(update_cmd)) exit_code = subprocess.call(update_cmd) elif any(x in distname for x in ['opensuse', 'suse', 'sles']): zypper_refresh_cmd = ['zypper', 'refresh'] az_update_cmd = 'zypper update -y azure-cli'.split() if os.geteuid() != 0: # pylint: disable=no-member zypper_refresh_cmd.insert(0, 'sudo') az_update_cmd.insert(0, 'sudo') exit_code = subprocess.call(zypper_refresh_cmd) if exit_code == 0: logger.debug("Update azure cli with '%s'", " ".join(az_update_cmd)) exit_code = subprocess.call(az_update_cmd) else: logger.warning(UPGRADE_MSG) elif installer == 'HOMEBREW': logger.warning("Update homebrew formulae") exit_code = subprocess.call(['brew', 'update']) if exit_code == 0: update_cmd = ['brew', 'upgrade', 'azure-cli'] logger.debug("Update azure cli with '%s'", " ".join(update_cmd)) exit_code = subprocess.call(update_cmd) elif installer == 'PIP': pip_args = [ sys.executable, '-m', 'pip', 'install', '--upgrade', 'azure-cli', '-vv', '--disable-pip-version-check', '--no-cache-dir' ] logger.debug("Update azure cli with '%s'", " ".join(pip_args)) exit_code = subprocess.call(pip_args, shell=platform.system() == 'Windows') elif installer == 'DOCKER': logger.warning( "Exit the container to pull latest image with 'docker pull mcr.microsoft.com/azure-cli' " "or run 'pip install --upgrade azure-cli' in this container") elif installer == 'MSI': logger.debug( "Update azure cli with MSI from https://aka.ms/installazurecliwindows" ) exit_code = subprocess.call(['powershell.exe', '-NoProfile', "Start-Process msiexec.exe -Wait -ArgumentList '/i https://aka.ms/installazurecliwindows'"]) # pylint: disable=line-too-long else: logger.warning(UPGRADE_MSG) if exit_code: telemetry.set_failure("CLI upgrade failed.") sys.exit(exit_code) # Updating Azure CLI and extension together is not supported in homebrewe package. if installer == 'HOMEBREW' and exts: logger.warning("Please rerun 'az upgrade' to update all extensions.") else: for ext_name in exts: try: logger.warning("Checking update for %s", ext_name) subprocess.call(['az', 'extension', 'update', '-n', ext_name], shell=platform.system() == 'Windows') except Exception as ex: # pylint: disable=broad-except msg = "Extension {} update failed during az upgrade. {}".format( ext_name, str(ex)) raise CLIError(msg) logger.warning("Upgrade finished.")
def run(self): """ runs the CLI """ telemetry.start() self.cli.buffers['symbols'].reset( initial_document=Document(u'%s' %shell_help) ) while True: try: document = self.cli.run(reset_current_buffer=True) text = document.text cmd = text outside = False if text.split() and text.split()[0] == 'az': cmd = ' '.join(text.split()[1:]) if self.default_command: cmd = self.default_command + " " + cmd # if self.default_params: # for param in self.default_params: # cmd += ' ' + param except AttributeError: # when the user pressed Control Q break else: if text.strip() == "quit" or text.strip() == "exit": break elif text.strip() == "clear": # clears the history, but only when you restart outside = True cmd = 'echo -n "" >' +\ os.path.join( SHELL_CONFIGURATION.get_config_dir(), SHELL_CONFIGURATION.get_history()) elif text.strip() == "help": print(help_doc.dump(shell_help)) if text: if text[0] == SELECT_SYMBOL['outside']: cmd = text[1:] outside = True # elif text.split()[0] == "az": # dumps the extra az # cmd = " ".join(text.split()[1:]) elif text[0] == SELECT_SYMBOL['exit_code']: print(self.last_exit) self.set_prompt() continue elif SELECT_SYMBOL['query'] in text: # query previous output if self.last and self.last.result: if hasattr(self.last.result, '__dict__'): input_dict = dict(self.last.result) else: input_dict = self.last.result try: result = jmespath.search( text.partition(SELECT_SYMBOL['query'])[2], input_dict) if isinstance(result, str): print(result) else: print(json.dumps(result, sort_keys=True, indent=2)) except jmespath.exceptions.ParseError: print("Invalid Query") self.set_prompt() continue elif "|" in text or ">" in text: # anything I don't parse, send off outside = True cmd = "az " + cmd elif SELECT_SYMBOL['example'] in text: global NOTIFICATIONS cmd = self.handle_example(text) if SELECT_SYMBOL['default'] in text: default = text.partition(SELECT_SYMBOL['default'])[2].split() if default[0].startswith('-'): value = self.handle_default_param(default) else: value = self.handle_default_command(default) print("defaulting: " + value) self.set_prompt() continue if SELECT_SYMBOL['undefault'] in text: value = text.partition(SELECT_SYMBOL['undefault'])[2].split() if len(value) == 0: self.default_command = "" set_default_command("", add=False) # self.default_params = [] print('undefaulting all') elif len(value) == 1 and value[0] == self.default_command: self.default_command = "" set_default_command("", add=False) print('undefaulting: ' + value[0]) # elif len(value) == 2 and ' '.join(value[:2]) in self.default_params: # self.default_params.remove(' '.join(value[:2])) # print('undefaulting: ' + ' '.join(value[:2])) self.set_prompt() continue if not text: # not input self.set_prompt() continue self.history.append(cmd) self.set_prompt() if outside: subprocess.Popen(cmd, shell=True).communicate() else: try: args = [str(command) for command in cmd.split()] azlogging.configure_logging(args) azure_folder = get_config_dir() if not os.path.exists(azure_folder): os.makedirs(azure_folder) ACCOUNT.load(os.path.join(azure_folder, 'azureProfile.json')) CONFIG.load(os.path.join(azure_folder, 'az.json')) SESSION.load(os.path.join(azure_folder, 'az.sess'), max_age=3600) config = Configuration(args) self.app.initialize(config) result = self.app.execute(args) if result and result.result is not None: from azure.cli.core._output import OutputProducer, format_json if self.output: self.output.out(result) else: formatter = OutputProducer.get_formatter( self.app.configuration.output_format) OutputProducer(formatter=formatter, file=self.input).out(result) self.last = result self.last_exit = 0 except Exception as ex: # pylint: disable=broad-except self.last_exit = handle_exception(ex) except SystemExit as ex: self.last_exit = ex.code if self.last_exit != 0: telemetry.set_failure() else: telemetry.set_success() print('Have a lovely day!!') telemetry.conclude()
def execute(self, args): from knack.events import (EVENT_INVOKER_PRE_CMD_TBL_CREATE, EVENT_INVOKER_POST_CMD_TBL_CREATE, EVENT_INVOKER_CMD_TBL_LOADED, EVENT_INVOKER_PRE_PARSE_ARGS, EVENT_INVOKER_POST_PARSE_ARGS, EVENT_INVOKER_FILTER_RESULT) from azure.cli.core.commands.events import EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE # TODO: Can't simply be invoked as an event because args are transformed args = _pre_command_table_create(self.cli_ctx, args) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_CREATE, args=args) self.commands_loader.load_command_table(args) self.cli_ctx.raise_event( EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE, load_cmd_tbl_func=self.commands_loader.load_command_table, args=args) command = self._rudimentary_get_command(args) self.cli_ctx.invocation.data['command_string'] = command telemetry.set_raw_command_name(command) try: self.commands_loader.command_table = { command: self.commands_loader.command_table[command] } except KeyError: # Trim down the command table to reduce the number of subparsers required to optimize the performance. # # When given a command table like this: # # network application-gateway create # network application-gateway delete # network list-usages # storage account create # storage account list # # input: az # output: network application-gateway create # storage account create # # input: az network # output: network application-gateway create # network list-usages cmd_table = {} group_names = set() for cmd_name, cmd in self.commands_loader.command_table.items(): if command and not cmd_name.startswith(command): continue cmd_stub = cmd_name[len(command):].strip() group_name = cmd_stub.split(' ', 1)[0] if group_name not in group_names: cmd_table[cmd_name] = cmd group_names.add(group_name) self.commands_loader.command_table = cmd_table self.commands_loader.command_table = self.commands_loader.command_table # update with the truncated table self.commands_loader.command_name = command self.commands_loader.load_arguments(command) self.cli_ctx.raise_event(EVENT_INVOKER_POST_CMD_TBL_CREATE, commands_loader=self.commands_loader) self.parser.cli_ctx = self.cli_ctx self.parser.load_command_table(self.commands_loader) self.cli_ctx.raise_event(EVENT_INVOKER_CMD_TBL_LOADED, cmd_tbl=self.commands_loader.command_table, parser=self.parser) arg_check = [a for a in args if a not in ['--debug', '--verbose']] if not arg_check: self.parser.enable_autocomplete() subparser = self.parser.subparsers[tuple()] self.help.show_welcome(subparser) # TODO: No event in base with which to target telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return CommandResultItem(None, exit_code=0) if args[0].lower() == 'help': args[0] = '--help' self.parser.enable_autocomplete() self.cli_ctx.raise_event(EVENT_INVOKER_PRE_PARSE_ARGS, args=args) parsed_args = self.parser.parse_args(args) self.cli_ctx.raise_event(EVENT_INVOKER_POST_PARSE_ARGS, command=parsed_args.command, args=parsed_args) # TODO: This fundamentally alters the way Knack.invocation works here. Cannot be customized # with an event. Would need to be customized via inheritance. cmd = parsed_args.func self.cli_ctx.data['command'] = parsed_args.command self.cli_ctx.data[ 'safe_params'] = AzCliCommandInvoker._extract_parameter_names(args) command_source = self.commands_loader.command_table[ command].command_source extension_version = None extension_name = None try: if isinstance(command_source, ExtensionCommandSource): extension_name = command_source.extension_name extension_version = get_extension( command_source.extension_name).version except Exception: # pylint: disable=broad-except pass telemetry.set_command_details(self.cli_ctx.data['command'], self.data['output'], self.cli_ctx.data['safe_params'], extension_name=extension_name, extension_version=extension_version) if extension_name: self.data['command_extension_name'] = extension_name self.resolve_warnings(cmd, parsed_args) self.resolve_confirmation(cmd, parsed_args) jobs = [] for expanded_arg in _explode_list_args(parsed_args): cmd_copy = copy.copy(cmd) cmd_copy.cli_ctx = copy.copy(cmd.cli_ctx) cmd_copy.cli_ctx.data = copy.deepcopy(cmd.cli_ctx.data) expanded_arg.cmd = cmd_copy if hasattr(expanded_arg, '_subscription'): cmd_copy.cli_ctx.data[ 'subscription_id'] = expanded_arg._subscription # pylint: disable=protected-access self._validation(expanded_arg) jobs.append((expanded_arg, cmd_copy)) ids = getattr(parsed_args, '_ids', None) or [None] * len(jobs) if self.cli_ctx.config.getboolean('core', 'disable_concurrent_ids', False) or len(ids) < 2: results, exceptions = self._run_jobs_serially(jobs, ids) else: results, exceptions = self._run_jobs_concurrently(jobs, ids) # handle exceptions if len(exceptions) == 1 and not results: ex, id_arg = exceptions[0] raise ex elif exceptions: for exception, id_arg in exceptions: logger.warning('%s: "%s"', id_arg, str(exception)) if not results: return CommandResultItem( None, exit_code=1, error=CLIError('Encountered more than one exception.')) else: logger.warning('Encountered more than one exception.') if results and len(results) == 1: results = results[0] event_data = {'result': results} self.cli_ctx.raise_event(EVENT_INVOKER_FILTER_RESULT, event_data=event_data) return CommandResultItem( event_data['result'], table_transformer=self.commands_loader.command_table[ parsed_args.command].table_transformer, is_query_active=self.data['query_active'])
def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many-locals, too-many-statements, too-many-branches, no-member, unused-argument import os import platform import sys import subprocess import azure.cli.core.telemetry as telemetry from azure.cli.core import __version__ as local_version from azure.cli.core._environment import _ENV_AZ_INSTALLER from azure.cli.core.extension import get_extensions, WheelExtension from packaging.version import parse from knack.util import CLIError update_cli = True from azure.cli.core.util import get_latest_from_github try: latest_version = get_latest_from_github() if latest_version and parse(latest_version) <= parse(local_version): logger.warning("You already have the latest azure-cli version: %s", local_version) update_cli = False if not update_all: return except Exception as ex: # pylint: disable=broad-except logger.debug("Failed to get the latest version. %s", str(ex)) exts = [ext.name for ext in get_extensions( ext_type=WheelExtension)] if update_all else [] exit_code = 0 installer = os.getenv(_ENV_AZ_INSTALLER) or '' installer = installer.upper() if update_cli: latest_version_msg = 'It will be updated to {}.'.format(latest_version) if yes \ else 'Latest version available is {}.'.format(latest_version) logger.warning("Your current Azure CLI version is %s. %s", local_version, latest_version_msg) from knack.prompting import prompt_y_n, NoTTYException if not yes: logger.warning( "Please check the release notes first: https://docs.microsoft.com/" "cli/azure/release-notes-azure-cli") try: confirmation = prompt_y_n("Do you want to continue?", default='y') except NoTTYException: from azure.cli.core.azclierror import UnclassifiedUserFault raise UnclassifiedUserFault("No tty available.", "Please run command with --yes.") if not confirmation: telemetry.set_success("Upgrade stopped by user") return if installer == 'DEB': from azure.cli.core.util import in_cloud_console if in_cloud_console(): raise CLIError("az upgrade is not supported in Cloud Shell.") apt_update_cmd = 'apt-get update'.split() az_update_cmd = 'apt-get install --only-upgrade -y azure-cli'.split( ) if os.geteuid() != 0: # pylint: disable=no-member apt_update_cmd.insert(0, 'sudo') az_update_cmd.insert(0, 'sudo') exit_code = subprocess.call(apt_update_cmd) if exit_code == 0: logger.debug("Update azure cli with '%s'", " ".join(az_update_cmd)) exit_code = subprocess.call(az_update_cmd) elif installer == 'RPM': from azure.cli.core.util import get_linux_distro distname, _ = get_linux_distro() if not distname: logger.warning(UPGRADE_MSG) else: distname = distname.lower().strip() if any(x in distname for x in ['centos', 'rhel', 'red hat', 'fedora']): update_cmd = 'yum update -y azure-cli'.split() if os.geteuid() != 0: # pylint: disable=no-member update_cmd.insert(0, 'sudo') logger.debug("Update azure cli with '%s'", " ".join(update_cmd)) exit_code = subprocess.call(update_cmd) elif any(x in distname for x in ['opensuse', 'suse', 'sles']): zypper_refresh_cmd = ['zypper', 'refresh'] az_update_cmd = 'zypper update -y azure-cli'.split() if os.geteuid() != 0: # pylint: disable=no-member zypper_refresh_cmd.insert(0, 'sudo') az_update_cmd.insert(0, 'sudo') exit_code = subprocess.call(zypper_refresh_cmd) if exit_code == 0: logger.debug("Update azure cli with '%s'", " ".join(az_update_cmd)) exit_code = subprocess.call(az_update_cmd) else: logger.warning(UPGRADE_MSG) elif installer == 'HOMEBREW': logger.debug("Update homebrew formulae") exit_code = subprocess.call(['brew', 'update']) if exit_code == 0: update_cmd = ['brew', 'upgrade', 'azure-cli'] logger.debug("Update azure cli with '%s'", " ".join(update_cmd)) exit_code = subprocess.call(update_cmd) elif installer == 'PIP': pip_args = [ sys.executable, '-m', 'pip', 'install', '--upgrade', 'azure-cli', '-vv', '--disable-pip-version-check', '--no-cache-dir' ] logger.debug("Update azure cli with '%s'", " ".join(pip_args)) exit_code = subprocess.call(pip_args, shell=platform.system() == 'Windows') elif installer == 'DOCKER': logger.warning( "Exit the container to pull latest image with 'docker pull mcr.microsoft.com/azure-cli' " "or run 'pip install --upgrade azure-cli' in this container") elif installer == 'MSI': logger.debug( "Update azure cli with MSI from https://aka.ms/installazurecliwindows" ) exit_code = subprocess.call(['powershell.exe', '-NoProfile', "Start-Process msiexec.exe -Wait -ArgumentList '/i https://aka.ms/installazurecliwindows'"]) # pylint: disable=line-too-long else: logger.warning(UPGRADE_MSG) if exit_code: err_msg = "CLI upgrade failed." logger.warning(err_msg) telemetry.set_failure(err_msg) sys.exit(exit_code) # Avoid using python modules directly as they may have been changed due to upgrade. # If you do need to use them, you may need to reload them and their dependent modules. # Otherwise you may have such issue https://github.com/Azure/azure-cli/issues/16952 import importlib import json importlib.reload(subprocess) importlib.reload(json) version_result = subprocess.check_output( ['az', 'version', '-o', 'json'], shell=platform.system() == 'Windows') version_json = json.loads(version_result) new_version = version_json['azure-cli-core'] if update_cli and new_version == local_version: err_msg = "CLI upgrade failed or aborted." logger.warning(err_msg) telemetry.set_failure(err_msg) sys.exit(1) if exts: logger.warning("Upgrading extensions") for ext_name in exts: try: logger.warning("Checking update for %s", ext_name) subprocess.call(['az', 'extension', 'update', '-n', ext_name], shell=platform.system() == 'Windows') except Exception as ex: # pylint: disable=broad-except msg = "Extension {} update failed during az upgrade. {}".format( ext_name, str(ex)) raise CLIError(msg) auto_upgrade_msg = "You can enable auto-upgrade with 'az config set auto-upgrade.enable=yes'. " \ "More details in https://docs.microsoft.com/cli/azure/update-azure-cli#automatic-update" logger.warning( "Upgrade finished.%s", "" if cmd.cli_ctx.config.getboolean( 'auto-upgrade', 'enable', False) else auto_upgrade_msg)
def set_success(summary=None): telemetry_core.set_success(summary=summary)
uuid.uuid1 = uuid.uuid4 logger = get_logger(__name__) def cli_main(cli, args): return cli.invoke(args) az_cli = get_default_cli() telemetry.set_application(az_cli, ARGCOMPLETE_ENV_NAME) try: telemetry.start() exit_code = cli_main(az_cli, sys.argv[1:]) if exit_code and exit_code != 0: telemetry.set_failure() else: telemetry.set_success() sys.exit(exit_code) except KeyboardInterrupt: telemetry.set_user_fault('keyboard interrupt') sys.exit(1) finally: telemetry.conclude()
def execute(self, unexpanded_argv): # pylint: disable=too-many-statements self.refresh_request_id() argv = Application._expand_file_prefixed_files(unexpanded_argv) command_table = self.configuration.get_command_table(argv) self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table) self.parser.load_command_table(command_table) self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser) if not argv: enable_autocomplete(self.parser) az_subparser = self.parser.subparsers[tuple()] _help.show_welcome(az_subparser) # TODO: Question, is this needed? telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return None if argv[0].lower() == 'help': argv[0] = '--help' # Rudimentary parsing to get the command nouns = [] for i, current in enumerate(argv): try: if current[0] == '-': break except IndexError: pass argv[i] = current.lower() nouns.append(argv[i]) command = ' '.join(nouns) if argv[-1] in ('--help', '-h') or command in command_table: self.configuration.load_params(command) self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table) self.parser.load_command_table(command_table) if self.session['completer_active']: enable_autocomplete(self.parser) self.raise_event(self.COMMAND_PARSER_PARSING, argv=argv) args = self.parser.parse_args(argv) self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args) results = [] for expanded_arg in _explode_list_args(args): self.session['command'] = expanded_arg.command try: _validate_arguments(expanded_arg) except CLIError: raise except: # pylint: disable=bare-except err = sys.exc_info()[1] getattr(expanded_arg, '_parser', self.parser).validation_error(str(err)) # Consider - we are using any args that start with an underscore (_) as 'private' # arguments and remove them from the arguments that we pass to the actual function. # This does not feel quite right. params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith('_')]) params.pop('subcommand', None) params.pop('func', None) params.pop('command', None) telemetry.set_command_details(expanded_arg.command, self.configuration.output_format, [p for p in unexpanded_argv if p.startswith('-')]) result = expanded_arg.func(params) result = todict(result) results.append(result) if len(results) == 1: results = results[0] event_data = {'result': results} self.raise_event(self.TRANSFORM_RESULT, event_data=event_data) self.raise_event(self.FILTER_RESULT, event_data=event_data) return CommandResultItem(event_data['result'], table_transformer=command_table[args.command].table_transformer, is_query_active=self.session['query_active'])
def execute(self, args): from knack.events import (EVENT_INVOKER_PRE_CMD_TBL_CREATE, EVENT_INVOKER_POST_CMD_TBL_CREATE, EVENT_INVOKER_CMD_TBL_LOADED, EVENT_INVOKER_PRE_PARSE_ARGS, EVENT_INVOKER_POST_PARSE_ARGS, EVENT_INVOKER_FILTER_RESULT) from azure.cli.core.commands.events import EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE # TODO: Can't simply be invoked as an event because args are transformed args = _pre_command_table_create(self.cli_ctx, args) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_CREATE, args=args) self.commands_loader.load_command_table(args) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE, load_cmd_tbl_func=self.commands_loader.load_command_table, args=args) command = self._rudimentary_get_command(args) self.cli_ctx.invocation.data['command_string'] = command telemetry.set_raw_command_name(command) try: self.commands_loader.command_table = {command: self.commands_loader.command_table[command]} except KeyError: # Trim down the command table to reduce the number of subparsers required to optimize the performance. # # When given a command table like this: # # network application-gateway create # network application-gateway delete # network list-usages # storage account create # storage account list # # input: az # output: network application-gateway create # storage account create # # input: az network # output: network application-gateway create # network list-usages cmd_table = {} group_names = set() for cmd_name, cmd in self.commands_loader.command_table.items(): if command and not cmd_name.startswith(command): continue cmd_stub = cmd_name[len(command):].strip() group_name = cmd_stub.split(' ', 1)[0] if group_name not in group_names: cmd_table[cmd_name] = cmd group_names.add(group_name) self.commands_loader.command_table = cmd_table self.commands_loader.command_table = self.commands_loader.command_table # update with the truncated table self.commands_loader.command_name = command self.commands_loader.load_arguments(command) self.cli_ctx.raise_event(EVENT_INVOKER_POST_CMD_TBL_CREATE, commands_loader=self.commands_loader) self.parser.cli_ctx = self.cli_ctx self.parser.load_command_table(self.commands_loader) self.cli_ctx.raise_event(EVENT_INVOKER_CMD_TBL_LOADED, cmd_tbl=self.commands_loader.command_table, parser=self.parser) arg_check = [a for a in args if a not in ['--debug', '--verbose']] if not arg_check: self.parser.enable_autocomplete() subparser = self.parser.subparsers[tuple()] self.help.show_welcome(subparser) # TODO: No event in base with which to target telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return CommandResultItem(None, exit_code=0) if args[0].lower() == 'help': args[0] = '--help' self.parser.enable_autocomplete() self.cli_ctx.raise_event(EVENT_INVOKER_PRE_PARSE_ARGS, args=args) parsed_args = self.parser.parse_args(args) self.cli_ctx.raise_event(EVENT_INVOKER_POST_PARSE_ARGS, command=parsed_args.command, args=parsed_args) # TODO: This fundamentally alters the way Knack.invocation works here. Cannot be customized # with an event. Would need to be customized via inheritance. cmd = parsed_args.func self.cli_ctx.data['command'] = parsed_args.command self.cli_ctx.data['safe_params'] = AzCliCommandInvoker._extract_parameter_names(args) command_source = self.commands_loader.command_table[command].command_source extension_version = None extension_name = None try: if isinstance(command_source, ExtensionCommandSource): extension_name = command_source.extension_name extension_version = get_extension(command_source.extension_name).version except Exception: # pylint: disable=broad-except pass telemetry.set_command_details(self.cli_ctx.data['command'], self.data['output'], self.cli_ctx.data['safe_params'], extension_name=extension_name, extension_version=extension_version) if extension_name: self.data['command_extension_name'] = extension_name self.resolve_warnings(cmd, parsed_args) self.resolve_confirmation(cmd, parsed_args) jobs = [] for expanded_arg in _explode_list_args(parsed_args): cmd_copy = copy.copy(cmd) cmd_copy.cli_ctx = copy.copy(cmd.cli_ctx) cmd_copy.cli_ctx.data = copy.deepcopy(cmd.cli_ctx.data) expanded_arg.cmd = expanded_arg._cmd = cmd_copy if hasattr(expanded_arg, '_subscription'): cmd_copy.cli_ctx.data['subscription_id'] = expanded_arg._subscription # pylint: disable=protected-access self._validation(expanded_arg) jobs.append((expanded_arg, cmd_copy)) ids = getattr(parsed_args, '_ids', None) or [None] * len(jobs) if self.cli_ctx.config.getboolean('core', 'disable_concurrent_ids', False) or len(ids) < 2: results, exceptions = self._run_jobs_serially(jobs, ids) else: results, exceptions = self._run_jobs_concurrently(jobs, ids) # handle exceptions if len(exceptions) == 1 and not results: ex, id_arg = exceptions[0] raise ex elif exceptions: for exception, id_arg in exceptions: logger.warning('%s: "%s"', id_arg, str(exception)) if not results: return CommandResultItem(None, exit_code=1, error=CLIError('Encountered more than one exception.')) logger.warning('Encountered more than one exception.') if results and len(results) == 1: results = results[0] event_data = {'result': results} self.cli_ctx.raise_event(EVENT_INVOKER_FILTER_RESULT, event_data=event_data) return CommandResultItem( event_data['result'], table_transformer=self.commands_loader.command_table[parsed_args.command].table_transformer, is_query_active=self.data['query_active'])