From 230457ab392b53a42ae0415e7f6ba757d79cee73 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Thu, 30 Jul 2020 15:03:01 +0800 Subject: [PATCH 01/24] add az upgrade --- .../azure/cli/command_modules/util/_help.py | 5 ++++ .../cli/command_modules/util/commands.py | 3 +++ .../azure/cli/command_modules/util/custom.py | 26 +++++++++++++++++++ .../cli/command_modules/util/upgrade.ps1 | 11 ++++++++ 4 files changed, 45 insertions(+) create mode 100644 src/azure-cli/azure/cli/command_modules/util/upgrade.ps1 diff --git a/src/azure-cli/azure/cli/command_modules/util/_help.py b/src/azure-cli/azure/cli/command_modules/util/_help.py index 045a49d649a..98bb68e16aa 100644 --- a/src/azure-cli/azure/cli/command_modules/util/_help.py +++ b/src/azure-cli/azure/cli/command_modules/util/_help.py @@ -40,3 +40,8 @@ type: command short-summary: Show the versions of Azure CLI modules and extensions in JSON format by default or format configured by --output """ + +helps['upgrade'] = """ +type: command +short-summary: Upgrade to the latest Azure CLI version +""" diff --git a/src/azure-cli/azure/cli/command_modules/util/commands.py b/src/azure-cli/azure/cli/command_modules/util/commands.py index cf4ea6ac7d4..d2ebfe65b3e 100644 --- a/src/azure-cli/azure/cli/command_modules/util/commands.py +++ b/src/azure-cli/azure/cli/command_modules/util/commands.py @@ -11,3 +11,6 @@ def load_command_table(self, _): with self.command_group('') as g: g.custom_command('version', 'show_version') + + with self.command_group('') as g: + g.custom_command('upgrade', 'upgrade_version', is_experimental=True) diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index 8051264ef11..7c553205363 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -27,3 +27,29 @@ def show_version(cmd): # pylint: disable=unused-argument from azure.cli.core.util import get_az_version_json versions = get_az_version_json() return versions + + +def upgrade_version(cmd): + from azure.cli.core._environment import _ENV_AZ_INSTALLER + import subprocess + # TODO check whether it's already the latest version + try: + installer = os.getenv(_ENV_AZ_INSTALLER) + if installer == 'DEB': + subprocess.call('sudo apt-get update && sudo apt-get install --only-upgrade -y azure-cli', shell=True) + elif installer == 'RPM': + subprocess.call('sudo yum update -y azure-cli', shell=True) + elif installer == 'HOMEBREW': + subprocess.call('brew update && brew upgrade -y azure-cli', shell=True) + elif installer == 'PIP': + subprocess.call('pip install --upgrade azure-cli', shell=True) + elif installer == 'DOCKER': + logger.warning('Exit the container to pull latest image with docker pull mcr.microsoft.com/azure-cli or pip install --upgrade azure-cli in this container') + elif installer == 'MSI': + # TODO put the script in a storage account, download it and store in a tmp dir + subprocess.call('powershell.exe "C:\\upgrade.ps1"', shell=True) + # logger.warning('Update with the latest MSI https://aka.ms/installazurecliwindows') + else: + logger.warning('Not able to upgrade automatically. Instructions can be found at https://docs.microsoft.com/en-us/cli/azure/install-azure-cli') + except Exception: # pylint: disable=broad-except + pass diff --git a/src/azure-cli/azure/cli/command_modules/util/upgrade.ps1 b/src/azure-cli/azure/cli/command_modules/util/upgrade.ps1 new file mode 100644 index 00000000000..42928c25422 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/util/upgrade.ps1 @@ -0,0 +1,11 @@ +If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) +{ + $arguments = "& '" + $myinvocation.mycommand.definition + "'" + Start-Process powershell -Verb runAs -ArgumentList $arguments + Break +} + +Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; +Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; +Remove-Item .\AzureCLI.msi +Write-Output "Upgrade Complete!" From 443ae5ee9680be7378e9aa35126e5faf5fc4b424 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Thu, 6 Aug 2020 13:08:55 +0800 Subject: [PATCH 02/24] upgrade extensions --- .../azure/cli/core/extension/_resolve.py | 2 +- .../azure/cli/core/extension/operations.py | 16 ++++--- .../cli/command_modules/extension/custom.py | 4 +- .../azure/cli/command_modules/util/_params.py | 5 ++- .../azure/cli/command_modules/util/custom.py | 45 ++++++++++++++++--- 5 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/extension/_resolve.py b/src/azure-cli-core/azure/cli/core/extension/_resolve.py index 350fad76bab..8093b64d0b6 100644 --- a/src/azure-cli-core/azure/cli/core/extension/_resolve.py +++ b/src/azure-cli-core/azure/cli/core/extension/_resolve.py @@ -74,7 +74,6 @@ def resolve_from_index(extension_name, cur_version=None, index_url=None, target_ candidates_sorted = sorted(candidates, key=lambda c: parse_version(c['metadata']['version']), reverse=True) logger.debug("Candidates %s", [c['filename'] for c in candidates_sorted]) - logger.debug("Choosing the latest of the remaining candidates.") if target_version: try: @@ -82,6 +81,7 @@ def resolve_from_index(extension_name, cur_version=None, index_url=None, target_ except IndexError: raise NoExtensionCandidatesError('Extension with version {} not found'.format(target_version)) else: + logger.debug("Choosing the latest of the remaining candidates.") chosen = candidates_sorted[0] logger.debug("Chosen %s", chosen) diff --git a/src/azure-cli-core/azure/cli/core/extension/operations.py b/src/azure-cli-core/azure/cli/core/extension/operations.py index 126d3aad9ad..b19a4c1a21e 100644 --- a/src/azure-cli-core/azure/cli/core/extension/operations.py +++ b/src/azure-cli-core/azure/cli/core/extension/operations.py @@ -289,16 +289,18 @@ def show_extension(extension_name): raise CLIError(e) -def update_extension(cmd=None, extension_name=None, index_url=None, pip_extra_index_urls=None, pip_proxy=None, cli_ctx=None): +def update_extension(cmd=None, source=None, extension_name=None, index_url=None, pip_extra_index_urls=None, pip_proxy=None, cli_ctx=None): try: + ext_sha256 = None cmd_cli_ctx = cli_ctx or cmd.cli_ctx ext = get_extension(extension_name, ext_type=WheelExtension) cur_version = ext.get_version() - try: - download_url, ext_sha256 = resolve_from_index(extension_name, cur_version=cur_version, index_url=index_url) - except NoExtensionCandidatesError as err: - logger.debug(err) - raise CLIError("No updates available for '{}'. Use --debug for more information.".format(extension_name)) + if not source: + try: + source, ext_sha256 = resolve_from_index(extension_name, cur_version=cur_version, index_url=index_url) + except NoExtensionCandidatesError as err: + logger.debug(err) + raise CLIError("No updates available for '{}'. Use --debug for more information.".format(extension_name)) # Copy current version of extension to tmp directory in case we need to restore it after a failed install. backup_dir = os.path.join(tempfile.mkdtemp(), extension_name) extension_path = ext.path @@ -308,7 +310,7 @@ def update_extension(cmd=None, extension_name=None, index_url=None, pip_extra_in shutil.rmtree(extension_path) # Install newer version try: - _add_whl_ext(cli_ctx=cmd_cli_ctx, source=download_url, ext_sha256=ext_sha256, + _add_whl_ext(cli_ctx=cmd_cli_ctx, source=source, ext_sha256=ext_sha256, pip_extra_index_urls=pip_extra_index_urls, pip_proxy=pip_proxy) logger.debug('Deleting backup of old extension at %s', backup_dir) shutil.rmtree(backup_dir) diff --git a/src/azure-cli/azure/cli/command_modules/extension/custom.py b/src/azure-cli/azure/cli/command_modules/extension/custom.py index 96e708e0f84..8fb36179a92 100644 --- a/src/azure-cli/azure/cli/command_modules/extension/custom.py +++ b/src/azure-cli/azure/cli/command_modules/extension/custom.py @@ -12,9 +12,9 @@ def add_extension_cmd(cmd, source=None, extension_name=None, index_url=None, yes=None, - pip_extra_index_urls=None, pip_proxy=None, system=None): + pip_extra_index_urls=None, pip_proxy=None, system=None, version=None): return add_extension(cli_ctx=cmd.cli_ctx, source=source, extension_name=extension_name, index_url=index_url, - yes=yes, pip_extra_index_urls=pip_extra_index_urls, pip_proxy=pip_proxy, system=system) + yes=yes, pip_extra_index_urls=pip_extra_index_urls, pip_proxy=pip_proxy, system=system, version=version) def remove_extension_cmd(extension_name): diff --git a/src/azure-cli/azure/cli/command_modules/util/_params.py b/src/azure-cli/azure/cli/command_modules/util/_params.py index 1a6922c8850..c9ec8e9730b 100644 --- a/src/azure-cli/azure/cli/command_modules/util/_params.py +++ b/src/azure-cli/azure/cli/command_modules/util/_params.py @@ -6,7 +6,7 @@ # pylint: disable=line-too-long def load_arguments(self, _): - from azure.cli.core.commands.parameters import get_enum_type + from azure.cli.core.commands.parameters import get_enum_type, get_three_state_flag with self.argument_context('rest') as c: c.argument('method', options_list=['--method', '-m'], @@ -33,3 +33,6 @@ def load_arguments(self, _): 'the service. The token will be placed in the Authorization header. By default, ' 'CLI can figure this out based on --url argument, unless you use ones not in the list ' 'of "az cloud show --query endpoints"') + + with self.argument_context('upgrade') as c: + c.argument('all', arg_type=get_three_state_flag(), help='Enable updating extensions as well.', default='true') diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index 7c553205363..0218319ad84 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -29,11 +29,42 @@ def show_version(cmd): # pylint: disable=unused-argument return versions -def upgrade_version(cmd): - from azure.cli.core._environment import _ENV_AZ_INSTALLER +def upgrade_version(cmd, all=None): import subprocess - # TODO check whether it's already the latest version - try: + from azure.cli.core._environment import _ENV_AZ_INSTALLER + from azure.cli.core.util import CLI_PACKAGE_NAME, _get_local_versions, _update_latest_from_pypi + from distutils.version import LooseVersion + from knack.util import CLIError + + update_cli = True + versions = _get_local_versions() + local = versions[CLI_PACKAGE_NAME]['local'] + versions, success = _update_latest_from_pypi(versions) + if not success: + raise CLIError("Failed to fetch the latest version. Please check your network connectivity.") + + pypi = versions[CLI_PACKAGE_NAME].get('pypi', None) + latest_version = pypi + if pypi and LooseVersion(pypi) <= LooseVersion(local): + logger.warning("You already have the latest %s: %s", CLI_PACKAGE_NAME, local) + update_cli = False + if not all: + return + ext_sources = [] + if all: + from azure.cli.core.extension import get_extensions, WheelExtension + from azure.cli.core.extension._resolve import resolve_from_index, NoExtensionCandidatesError + from azure.cli.core.extension.operations import update_extension + extensions = get_extensions(ext_type=WheelExtension) + if extensions: + for ext in extensions: + try: + download_url, _ = resolve_from_index(ext.name, cur_version=ext.version) + ext_sources.append((ext.name, download_url)) + except NoExtensionCandidatesError: + pass + if update_cli: + import os installer = os.getenv(_ENV_AZ_INSTALLER) if installer == 'DEB': subprocess.call('sudo apt-get update && sudo apt-get install --only-upgrade -y azure-cli', shell=True) @@ -51,5 +82,7 @@ def upgrade_version(cmd): # logger.warning('Update with the latest MSI https://aka.ms/installazurecliwindows') else: logger.warning('Not able to upgrade automatically. Instructions can be found at https://docs.microsoft.com/en-us/cli/azure/install-azure-cli') - except Exception: # pylint: disable=broad-except - pass + + for name, download_url in ext_sources: + logger.warning("Update extension: {}".format(name)) + update_extension(cli_ctx=cmd.cli_ctx, extension_name=name, source=download_url) From cd2aec4638069772c01c288942fc9b416450e006 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Fri, 7 Aug 2020 11:10:23 +0800 Subject: [PATCH 03/24] add extension list-versions --- .../azure/cli/core/extension/operations.py | 39 +++++++++++++++++++ .../cli/command_modules/extension/__init__.py | 1 + .../cli/command_modules/extension/custom.py | 6 ++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/azure-cli-core/azure/cli/core/extension/operations.py b/src/azure-cli-core/azure/cli/core/extension/operations.py index b19a4c1a21e..7f204f25e55 100644 --- a/src/azure-cli-core/azure/cli/core/extension/operations.py +++ b/src/azure-cli-core/azure/cli/core/extension/operations.py @@ -358,6 +358,45 @@ def list_available_extensions(index_url=None, show_details=False): return results +def list_versions(extension_name, index_url=None): + index_data = get_index_extensions(index_url=index_url) + + try: + exts = index_data[extension_name] + except Exception: + raise CLIError('Extension {} not found.'.format(extension_name)) + + try: + installed_ext = get_extension(extension_name, ext_type=WheelExtension) + except ExtensionNotInstalledException: + installed_ext = None + + results = [] + latest_compatible_version = '0.0.0' + + for ext in sorted(exts, key=lambda c: parse_version(c['metadata']['version']), reverse=True): + compatible = ext_compat_with_cli(ext['metadata'])[0] + ext_version = ext['metadata']['version'] + if compatible and latest_compatible_version == '0.0.0': + latest_compatible_version = ext_version + installed = ext_version == installed_ext.version if installed_ext else False + if installed and parse_version(latest_compatible_version) > parse_version(installed_ext.version): + installed = str(True) + ' (upgrade available)' + version = ext['metadata']['version'] + if latest_compatible_version == ext_version: + version = version + ' (max compatible version)' + results.append({ + 'name': extension_name, + 'version': version, + 'preview': ext['metadata'].get(EXT_METADATA_ISPREVIEW, False), + 'experimental': ext['metadata'].get(EXT_METADATA_ISEXPERIMENTAL, False), + 'installed': installed, + 'compatible': compatible + }) + results.reverse() + return results + + def reload_extension(extension_name, extension_module=None): return reload_module(extension_module if extension_module else get_extension_modname(ext_name=extension_name)) diff --git a/src/azure-cli/azure/cli/command_modules/extension/__init__.py b/src/azure-cli/azure/cli/command_modules/extension/__init__.py index 85f8b9b8896..0ad7fd51b33 100644 --- a/src/azure-cli/azure/cli/command_modules/extension/__init__.py +++ b/src/azure-cli/azure/cli/command_modules/extension/__init__.py @@ -45,6 +45,7 @@ def validate_extension_add(namespace): g.show_command('show', 'show_extension_cmd') g.command('list-available', 'list_available_extensions_cmd', table_transformer=transform_extension_list_available) g.command('update', 'update_extension_cmd') + g.command('list-versions', 'list_versions_cmd') return self.command_table diff --git a/src/azure-cli/azure/cli/command_modules/extension/custom.py b/src/azure-cli/azure/cli/command_modules/extension/custom.py index 471c73e96f3..acf0b19638f 100644 --- a/src/azure-cli/azure/cli/command_modules/extension/custom.py +++ b/src/azure-cli/azure/cli/command_modules/extension/custom.py @@ -6,7 +6,7 @@ from azure.cli.core.extension.operations import ( add_extension, remove_extension, list_extensions, show_extension, - list_available_extensions, update_extension) + list_available_extensions, update_extension, list_versions) logger = get_logger(__name__) @@ -37,3 +37,7 @@ def update_extension_cmd(cmd, extension_name, index_url=None, pip_extra_index_ur def list_available_extensions_cmd(index_url=None, show_details=False): return list_available_extensions(index_url=index_url, show_details=show_details) + + +def list_versions_cmd(extension_name, index_url=None): + return list_versions(extension_name, index_url=index_url) From db9cde6bd14fe1efc60d4e39fec10109d0c5fb34 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Fri, 7 Aug 2020 15:44:09 +0800 Subject: [PATCH 04/24] fix style --- .../azure/cli/command_modules/util/_params.py | 2 +- .../azure/cli/command_modules/util/custom.py | 32 ++++++++++++------- .../cli/command_modules/util/upgrade.ps1 | 4 +-- src/azure-cli/setup.py | 3 ++ 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/util/_params.py b/src/azure-cli/azure/cli/command_modules/util/_params.py index c9ec8e9730b..76cce9e8c71 100644 --- a/src/azure-cli/azure/cli/command_modules/util/_params.py +++ b/src/azure-cli/azure/cli/command_modules/util/_params.py @@ -35,4 +35,4 @@ def load_arguments(self, _): 'of "az cloud show --query endpoints"') with self.argument_context('upgrade') as c: - c.argument('all', arg_type=get_three_state_flag(), help='Enable updating extensions as well.', default='true') + c.argument('_all', options_list=['--all'], arg_type=get_three_state_flag(), help='Enable updating extensions as well.', default='true') diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index 0218319ad84..16fdde31e82 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -29,7 +29,7 @@ def show_version(cmd): # pylint: disable=unused-argument return versions -def upgrade_version(cmd, all=None): +def upgrade_version(cmd, _all=None): # pylint: disable=too-many-locals, too-many-statements import subprocess from azure.cli.core._environment import _ENV_AZ_INSTALLER from azure.cli.core.util import CLI_PACKAGE_NAME, _get_local_versions, _update_latest_from_pypi @@ -44,14 +44,13 @@ def upgrade_version(cmd, all=None): raise CLIError("Failed to fetch the latest version. Please check your network connectivity.") pypi = versions[CLI_PACKAGE_NAME].get('pypi', None) - latest_version = pypi if pypi and LooseVersion(pypi) <= LooseVersion(local): logger.warning("You already have the latest %s: %s", CLI_PACKAGE_NAME, local) update_cli = False - if not all: + if not _all: return ext_sources = [] - if all: + if _all: from azure.cli.core.extension import get_extensions, WheelExtension from azure.cli.core.extension._resolve import resolve_from_index, NoExtensionCandidatesError from azure.cli.core.extension.operations import update_extension @@ -66,6 +65,7 @@ def upgrade_version(cmd, all=None): if update_cli: import os installer = os.getenv(_ENV_AZ_INSTALLER) + logger.warning("Update Azure CLI to %s", pypi) if installer == 'DEB': subprocess.call('sudo apt-get update && sudo apt-get install --only-upgrade -y azure-cli', shell=True) elif installer == 'RPM': @@ -74,15 +74,25 @@ def upgrade_version(cmd, all=None): subprocess.call('brew update && brew upgrade -y azure-cli', shell=True) elif installer == 'PIP': subprocess.call('pip install --upgrade azure-cli', shell=True) + pip_args = ['install', '--upgrade', 'azure-cli'] + logger.debug('Executing pip with args: %s', pip_args) + from azure.cli.core.extension._homebrew_patch import HomebrewPipPatch + with HomebrewPipPatch(): + from azure.cli.core.extension.operations import _run_pip + pip_status_code = _run_pip(pip_args) + if pip_status_code > 0: + raise CLIError('An error occurred. Pip failed with status code {}. ' + 'Use --debug for more information.'.format(pip_status_code)) elif installer == 'DOCKER': - logger.warning('Exit the container to pull latest image with docker pull mcr.microsoft.com/azure-cli or pip install --upgrade azure-cli in this container') + logger.warning('Exit the container to pull latest image with docker pull mcr.microsoft.com/azure-cli ' + 'or pip install --upgrade azure-cli in this container') elif installer == 'MSI': - # TODO put the script in a storage account, download it and store in a tmp dir - subprocess.call('powershell.exe "C:\\upgrade.ps1"', shell=True) - # logger.warning('Update with the latest MSI https://aka.ms/installazurecliwindows') + logger.warning('Update with the latest MSI https://aka.ms/installazurecliwindows') + ps_path = os.path.abspath(os.path.join(os.path.abspath(__file__), '../upgrade.ps1')) + subprocess.call('powershell.exe "{}"'.format(ps_path.replace('\\', '\\\\')), shell=True) else: - logger.warning('Not able to upgrade automatically. Instructions can be found at https://docs.microsoft.com/en-us/cli/azure/install-azure-cli') - + logger.warning('Not able to upgrade automatically. Instructions can be found at ' + 'https://docs.microsoft.com/en-us/cli/azure/install-azure-cli') for name, download_url in ext_sources: - logger.warning("Update extension: {}".format(name)) + logger.warning("Update extension: %s", name) update_extension(cli_ctx=cmd.cli_ctx, extension_name=name, source=download_url) diff --git a/src/azure-cli/azure/cli/command_modules/util/upgrade.ps1 b/src/azure-cli/azure/cli/command_modules/util/upgrade.ps1 index 42928c25422..8b32dfbe135 100644 --- a/src/azure-cli/azure/cli/command_modules/util/upgrade.ps1 +++ b/src/azure-cli/azure/cli/command_modules/util/upgrade.ps1 @@ -2,10 +2,10 @@ If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdenti { $arguments = "& '" + $myinvocation.mycommand.definition + "'" Start-Process powershell -Verb runAs -ArgumentList $arguments - Break + # Break } Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; Remove-Item .\AzureCLI.msi -Write-Output "Upgrade Complete!" +Write-Output "Update completed for Azure CLI!" diff --git a/src/azure-cli/setup.py b/src/azure-cli/setup.py index cbea91a1363..98f3c240d6d 100644 --- a/src/azure-cli/setup.py +++ b/src/azure-cli/setup.py @@ -183,6 +183,9 @@ 'resources/WindowsFunctionsStacks.json', 'resources/LinuxFunctionsStacks.json', 'resources/WebappRuntimeStacks.json' + ], + 'azure.cli.command_modules.util': [ + 'upgrade.ps1' ] }, cmdclass=cmdclass From e4670494b4c1a1be02deace60b8cef343667e9f8 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Tue, 11 Aug 2020 17:07:02 +0800 Subject: [PATCH 05/24] only sudo for non-root user --- .../azure/cli/command_modules/util/_params.py | 1 + .../azure/cli/command_modules/util/custom.py | 104 ++++++++++++------ 2 files changed, 71 insertions(+), 34 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/util/_params.py b/src/azure-cli/azure/cli/command_modules/util/_params.py index 76cce9e8c71..1e5f7afe190 100644 --- a/src/azure-cli/azure/cli/command_modules/util/_params.py +++ b/src/azure-cli/azure/cli/command_modules/util/_params.py @@ -36,3 +36,4 @@ def load_arguments(self, _): with self.argument_context('upgrade') as c: c.argument('_all', options_list=['--all'], arg_type=get_three_state_flag(), help='Enable updating extensions as well.', default='true') + c.argument('yes', options_list=['--yes', '-y'], action='store_true', help='Do not prompt for checking release notes.') diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index 16fdde31e82..bb0ae4c6631 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -7,6 +7,9 @@ logger = get_logger(__name__) +UPGRADE_MSG = 'Not able to upgrade automatically. Instructions can be found at ' \ + 'https://docs.microsoft.com/cli/azure/install-azure-cli' + def rest_call(cmd, url, method=None, headers=None, uri_parameters=None, body=None, skip_authorization_header=False, resource=None, output_file=None): @@ -29,12 +32,13 @@ def show_version(cmd): # pylint: disable=unused-argument return versions -def upgrade_version(cmd, _all=None): # pylint: disable=too-many-locals, too-many-statements +def upgrade_version(cmd, _all=None, yes=None): # pylint: disable=too-many-locals, too-many-statements, too-many-branches, no-member import subprocess from azure.cli.core._environment import _ENV_AZ_INSTALLER from azure.cli.core.util import CLI_PACKAGE_NAME, _get_local_versions, _update_latest_from_pypi from distutils.version import LooseVersion from knack.util import CLIError + import azure.cli.core.telemetry as telemetry update_cli = True versions = _get_local_versions() @@ -42,57 +46,89 @@ def upgrade_version(cmd, _all=None): # pylint: disable=too-many-locals, too-man versions, success = _update_latest_from_pypi(versions) if not success: raise CLIError("Failed to fetch the latest version. Please check your network connectivity.") - pypi = versions[CLI_PACKAGE_NAME].get('pypi', None) if pypi and LooseVersion(pypi) <= LooseVersion(local): logger.warning("You already have the latest %s: %s", CLI_PACKAGE_NAME, local) update_cli = False if not _all: return - ext_sources = [] - if _all: - from azure.cli.core.extension import get_extensions, WheelExtension - from azure.cli.core.extension._resolve import resolve_from_index, NoExtensionCandidatesError - from azure.cli.core.extension.operations import update_extension - extensions = get_extensions(ext_type=WheelExtension) - if extensions: - for ext in extensions: - try: - download_url, _ = resolve_from_index(ext.name, cur_version=ext.version) - ext_sources.append((ext.name, download_url)) - except NoExtensionCandidatesError: - pass + if update_cli: + logger.warning("Your current Azure CLI version is %s. Latest version is %s.", local, pypi) + 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\nWould you like to proceed?", default='y') + if not confirmation: + telemetry.set_success("Upgrade stopped by user") + return import os + import platform installer = os.getenv(_ENV_AZ_INSTALLER) - logger.warning("Update Azure CLI to %s", pypi) + shell = platform.system() == 'Windows' + exit_code = 0 if installer == 'DEB': - subprocess.call('sudo apt-get update && sudo apt-get install --only-upgrade -y azure-cli', shell=True) + apt_update = 'apt-get install --only-upgrade -y azure-cli' + update_cmd = 'apt-get update && {}'.format(apt_update) if os.geteuid() == 0 else 'sudo apt-get update && sudo {}'.format(apt_update) # pylint: disable=no-member, line-too-long + exit_code = subprocess.call(update_cmd, shell=shell) elif installer == 'RPM': - subprocess.call('sudo yum update -y azure-cli', shell=True) + 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' + if os.geteuid() != 0: # pylint: disable=no-member + update_cmd = 'sudo {}'.format(update_cmd) + exit_code = subprocess.call(update_cmd, shell=shell) + elif any(x in distname for x in ['opensuse', 'suse', 'sles']): + update_cmd = 'zypper update -y azure-cli' + if os.geteuid() != 0: # pylint: disable=no-member + update_cmd = 'sudo {}'.format(update_cmd) + exit_code = subprocess.call(update_cmd, shell=shell) + else: + logger.warning(UPGRADE_MSG) elif installer == 'HOMEBREW': - subprocess.call('brew update && brew upgrade -y azure-cli', shell=True) + exit_code = subprocess.call('brew update && brew upgrade -y azure-cli', shell=shell) elif installer == 'PIP': - subprocess.call('pip install --upgrade azure-cli', shell=True) - pip_args = ['install', '--upgrade', 'azure-cli'] + import sys + pip_args = [sys.executable, '-m', 'pip', 'install', '--upgrade', 'azure-cli', '-vv', + '--disable-pip-version-check', '--no-cache-dir'] logger.debug('Executing pip with args: %s', pip_args) - from azure.cli.core.extension._homebrew_patch import HomebrewPipPatch - with HomebrewPipPatch(): - from azure.cli.core.extension.operations import _run_pip - pip_status_code = _run_pip(pip_args) - if pip_status_code > 0: - raise CLIError('An error occurred. Pip failed with status code {}. ' - 'Use --debug for more information.'.format(pip_status_code)) + exit_code = subprocess.call(pip_args, shell=shell) + return elif installer == 'DOCKER': logger.warning('Exit the container to pull latest image with docker pull mcr.microsoft.com/azure-cli ' 'or pip install --upgrade azure-cli in this container') elif installer == 'MSI': logger.warning('Update with the latest MSI https://aka.ms/installazurecliwindows') ps_path = os.path.abspath(os.path.join(os.path.abspath(__file__), '../upgrade.ps1')) - subprocess.call('powershell.exe "{}"'.format(ps_path.replace('\\', '\\\\')), shell=True) + exit_code = subprocess.call('powershell.exe "{}"'.format(ps_path.replace('\\', '\\\\')), shell=shell) else: - logger.warning('Not able to upgrade automatically. Instructions can be found at ' - 'https://docs.microsoft.com/en-us/cli/azure/install-azure-cli') - for name, download_url in ext_sources: - logger.warning("Update extension: %s", name) - update_extension(cli_ctx=cmd.cli_ctx, extension_name=name, source=download_url) + logger.warning(UPGRADE_MSG) + if exit_code: + telemetry.set_failure("CLI upgrade failed.") + sys.exit(exit_code) + + ext_sources = [] + if _all: + from azure.cli.core.extension import get_extensions, WheelExtension + from azure.cli.core.extension._resolve import resolve_from_index, NoExtensionCandidatesError + from azure.cli.core.extension.operations import update_extension + extensions = get_extensions(ext_type=WheelExtension) + if extensions: + for ext in extensions: + try: + download_url, _ = resolve_from_index(ext.name, cur_version=ext.version) + ext_sources.append((ext.name, download_url)) + except NoExtensionCandidatesError: + pass + try: + for name, download_url in ext_sources: + logger.warning("Update extension: %s", name) + update_extension(cli_ctx=cmd.cli_ctx, extension_name=name, source=download_url) + except CLIError as ex: + telemetry.set_failure("Extension update failed during upgrade. {}".format(str(ex))) + raise ex From 332df8b3b6844926bab5c4e0a62c5d7664f52a69 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Wed, 12 Aug 2020 13:42:58 +0800 Subject: [PATCH 06/24] fix subprocess call --- .../azure/cli/command_modules/util/_params.py | 2 +- .../azure/cli/command_modules/util/custom.py | 47 ++++++++++--------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/util/_params.py b/src/azure-cli/azure/cli/command_modules/util/_params.py index 1e5f7afe190..481d63f1ac0 100644 --- a/src/azure-cli/azure/cli/command_modules/util/_params.py +++ b/src/azure-cli/azure/cli/command_modules/util/_params.py @@ -35,5 +35,5 @@ def load_arguments(self, _): 'of "az cloud show --query endpoints"') with self.argument_context('upgrade') as c: - c.argument('_all', options_list=['--all'], arg_type=get_three_state_flag(), help='Enable updating extensions as well.', default='true') + c.argument('update_all', options_list=['--all'], arg_type=get_three_state_flag(), help='Enable updating extensions as well.', default='true') c.argument('yes', options_list=['--yes', '-y'], action='store_true', help='Do not prompt for checking release notes.') diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index bb0ae4c6631..0545eab1160 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -32,14 +32,13 @@ def show_version(cmd): # pylint: disable=unused-argument return versions -def upgrade_version(cmd, _all=None, yes=None): # pylint: disable=too-many-locals, too-many-statements, too-many-branches, no-member +def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many-locals, too-many-statements, too-many-branches, no-member import subprocess from azure.cli.core._environment import _ENV_AZ_INSTALLER from azure.cli.core.util import CLI_PACKAGE_NAME, _get_local_versions, _update_latest_from_pypi from distutils.version import LooseVersion from knack.util import CLIError import azure.cli.core.telemetry as telemetry - update_cli = True versions = _get_local_versions() local = versions[CLI_PACKAGE_NAME]['local'] @@ -52,7 +51,7 @@ def upgrade_version(cmd, _all=None, yes=None): # pylint: disable=too-many-local update_cli = False if not _all: return - + exit_code = 0 if update_cli: logger.warning("Your current Azure CLI version is %s. Latest version is %s.", local, pypi) from knack.prompting import prompt_y_n @@ -65,12 +64,17 @@ def upgrade_version(cmd, _all=None, yes=None): # pylint: disable=too-many-local import os import platform installer = os.getenv(_ENV_AZ_INSTALLER) - shell = platform.system() == 'Windows' - exit_code = 0 if installer == 'DEB': - apt_update = 'apt-get install --only-upgrade -y azure-cli' - update_cmd = 'apt-get update && {}'.format(apt_update) if os.geteuid() == 0 else 'sudo apt-get update && sudo {}'.format(apt_update) # pylint: disable=no-member, line-too-long - exit_code = subprocess.call(update_cmd, shell=shell) + 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') + subprocess.call(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() @@ -79,41 +83,38 @@ def upgrade_version(cmd, _all=None, yes=None): # pylint: disable=too-many-local 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' + update_cmd = 'yum update -y azure-cli'.split() if os.geteuid() != 0: # pylint: disable=no-member - update_cmd = 'sudo {}'.format(update_cmd) - exit_code = subprocess.call(update_cmd, shell=shell) + update_cmd.insert(0, 'sudo') + exit_code = subprocess.call(update_cmd) elif any(x in distname for x in ['opensuse', 'suse', 'sles']): - update_cmd = 'zypper update -y azure-cli' + update_cmd = 'zypper update -y azure-cli'.split() if os.geteuid() != 0: # pylint: disable=no-member - update_cmd = 'sudo {}'.format(update_cmd) - exit_code = subprocess.call(update_cmd, shell=shell) + update_cmd.insert(0, 'sudo') + exit_code = subprocess.call(update_cmd) else: logger.warning(UPGRADE_MSG) elif installer == 'HOMEBREW': - exit_code = subprocess.call('brew update && brew upgrade -y azure-cli', shell=shell) + exit_code = subprocess.call('brew update && brew upgrade -y azure-cli'.split()) elif installer == 'PIP': import sys pip_args = [sys.executable, '-m', 'pip', 'install', '--upgrade', 'azure-cli', '-vv', '--disable-pip-version-check', '--no-cache-dir'] logger.debug('Executing pip with args: %s', pip_args) - exit_code = subprocess.call(pip_args, shell=shell) + exit_code = subprocess.call(pip_args, shell=platform.system() == 'Windows') return elif installer == 'DOCKER': - logger.warning('Exit the container to pull latest image with docker pull mcr.microsoft.com/azure-cli ' - 'or pip install --upgrade azure-cli in this container') + logger.warning("Exit the container to pull latest image with 'docker pull mcr.microsoft.com/azure-cli' " + "or 'pip install --upgrade azure-cli' in this container") elif installer == 'MSI': - logger.warning('Update with the latest MSI https://aka.ms/installazurecliwindows') - ps_path = os.path.abspath(os.path.join(os.path.abspath(__file__), '../upgrade.ps1')) - exit_code = subprocess.call('powershell.exe "{}"'.format(ps_path.replace('\\', '\\\\')), shell=shell) + exit_code = subprocess.call(['powershell.exe', 'Start-Process powershell -Verb runAs -ArgumentList "Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi;Start-Process msiexec.exe -Wait -ArgumentList \'/I AzureCLI.msi\';Remove-Item .\AzureCLI.msi"']) else: logger.warning(UPGRADE_MSG) if exit_code: telemetry.set_failure("CLI upgrade failed.") sys.exit(exit_code) - ext_sources = [] - if _all: + if update_all: from azure.cli.core.extension import get_extensions, WheelExtension from azure.cli.core.extension._resolve import resolve_from_index, NoExtensionCandidatesError from azure.cli.core.extension.operations import update_extension From 82da615a2daaf889e258fecbe708433df9b952e4 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Wed, 12 Aug 2020 14:17:19 +0800 Subject: [PATCH 07/24] fix style --- .../azure/cli/command_modules/util/custom.py | 6 +++--- .../azure/cli/command_modules/util/upgrade.ps1 | 11 ----------- 2 files changed, 3 insertions(+), 14 deletions(-) delete mode 100644 src/azure-cli/azure/cli/command_modules/util/upgrade.ps1 diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index 0545eab1160..abf07050467 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -49,7 +49,7 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many if pypi and LooseVersion(pypi) <= LooseVersion(local): logger.warning("You already have the latest %s: %s", CLI_PACKAGE_NAME, local) update_cli = False - if not _all: + if not update_all: return exit_code = 0 if update_cli: @@ -63,7 +63,7 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many return import os import platform - installer = os.getenv(_ENV_AZ_INSTALLER) + installer = 'MSI' # os.getenv(_ENV_AZ_INSTALLER) if installer == 'DEB': from azure.cli.core.util import in_cloud_console if in_cloud_console(): @@ -107,7 +107,7 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many logger.warning("Exit the container to pull latest image with 'docker pull mcr.microsoft.com/azure-cli' " "or 'pip install --upgrade azure-cli' in this container") elif installer == 'MSI': - exit_code = subprocess.call(['powershell.exe', 'Start-Process powershell -Verb runAs -ArgumentList "Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi;Start-Process msiexec.exe -Wait -ArgumentList \'/I AzureCLI.msi\';Remove-Item .\AzureCLI.msi"']) + exit_code = subprocess.call(['powershell.exe', 'Start-Process powershell -Verb runAs -ArgumentList "Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile AzureCLI.msi;Start-Process msiexec.exe -Wait -ArgumentList \'/I AzureCLI.msi\';Remove-Item AzureCLI.msi"']) # pylint: disable=line-too-long else: logger.warning(UPGRADE_MSG) if exit_code: diff --git a/src/azure-cli/azure/cli/command_modules/util/upgrade.ps1 b/src/azure-cli/azure/cli/command_modules/util/upgrade.ps1 deleted file mode 100644 index 8b32dfbe135..00000000000 --- a/src/azure-cli/azure/cli/command_modules/util/upgrade.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) -{ - $arguments = "& '" + $myinvocation.mycommand.definition + "'" - Start-Process powershell -Verb runAs -ArgumentList $arguments - # Break -} - -Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; -Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; -Remove-Item .\AzureCLI.msi -Write-Output "Update completed for Azure CLI!" From ee9c40f01fc5418a0c1b729f6677b1f1069f9653 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Wed, 12 Aug 2020 15:11:25 +0800 Subject: [PATCH 08/24] fix homebrew --- .../azure/cli/command_modules/util/custom.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index abf07050467..fc7f017a081 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -33,12 +33,16 @@ def show_version(cmd): # pylint: disable=unused-argument def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many-locals, too-many-statements, too-many-branches, no-member + import os + import platform + import sys import subprocess + import azure.cli.core.telemetry as telemetry from azure.cli.core._environment import _ENV_AZ_INSTALLER from azure.cli.core.util import CLI_PACKAGE_NAME, _get_local_versions, _update_latest_from_pypi from distutils.version import LooseVersion from knack.util import CLIError - import azure.cli.core.telemetry as telemetry + update_cli = True versions = _get_local_versions() local = versions[CLI_PACKAGE_NAME]['local'] @@ -61,9 +65,7 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many if not confirmation: telemetry.set_success("Upgrade stopped by user") return - import os - import platform - installer = 'MSI' # os.getenv(_ENV_AZ_INSTALLER) + installer = os.getenv(_ENV_AZ_INSTALLER) if installer == 'DEB': from azure.cli.core.util import in_cloud_console if in_cloud_console(): @@ -95,9 +97,9 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many else: logger.warning(UPGRADE_MSG) elif installer == 'HOMEBREW': - exit_code = subprocess.call('brew update && brew upgrade -y azure-cli'.split()) + subprocess.call(['brew', 'update']) + exit_code = subprocess.call(['brew', 'upgrade', 'azure-cli']) elif installer == 'PIP': - import sys pip_args = [sys.executable, '-m', 'pip', 'install', '--upgrade', 'azure-cli', '-vv', '--disable-pip-version-check', '--no-cache-dir'] logger.debug('Executing pip with args: %s', pip_args) From 7f279694828ace4ba9bba25524a0cb85883ff005 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Wed, 12 Aug 2020 15:11:25 +0800 Subject: [PATCH 09/24] fix homebrew --- .../azure/cli/command_modules/util/custom.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index abf07050467..cb6abbc6da2 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -33,12 +33,19 @@ def show_version(cmd): # pylint: disable=unused-argument def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many-locals, too-many-statements, too-many-branches, no-member + import os + import platform + import sys import subprocess + import azure.cli.core.telemetry as telemetry from azure.cli.core._environment import _ENV_AZ_INSTALLER from azure.cli.core.util import CLI_PACKAGE_NAME, _get_local_versions, _update_latest_from_pypi + from azure.cli.core.extension import get_extensions, WheelExtension + from azure.cli.core.extension._resolve import resolve_from_index, NoExtensionCandidatesError + from azure.cli.core.extension.operations import update_extension from distutils.version import LooseVersion from knack.util import CLIError - import azure.cli.core.telemetry as telemetry + update_cli = True versions = _get_local_versions() local = versions[CLI_PACKAGE_NAME]['local'] @@ -61,9 +68,7 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many if not confirmation: telemetry.set_success("Upgrade stopped by user") return - import os - import platform - installer = 'MSI' # os.getenv(_ENV_AZ_INSTALLER) + installer = os.getenv(_ENV_AZ_INSTALLER) if installer == 'DEB': from azure.cli.core.util import in_cloud_console if in_cloud_console(): @@ -95,9 +100,9 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many else: logger.warning(UPGRADE_MSG) elif installer == 'HOMEBREW': - exit_code = subprocess.call('brew update && brew upgrade -y azure-cli'.split()) + subprocess.call(['brew', 'update']) + exit_code = subprocess.call(['brew', 'upgrade', 'azure-cli']) elif installer == 'PIP': - import sys pip_args = [sys.executable, '-m', 'pip', 'install', '--upgrade', 'azure-cli', '-vv', '--disable-pip-version-check', '--no-cache-dir'] logger.debug('Executing pip with args: %s', pip_args) @@ -115,9 +120,6 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many sys.exit(exit_code) ext_sources = [] if update_all: - from azure.cli.core.extension import get_extensions, WheelExtension - from azure.cli.core.extension._resolve import resolve_from_index, NoExtensionCandidatesError - from azure.cli.core.extension.operations import update_extension extensions = get_extensions(ext_type=WheelExtension) if extensions: for ext in extensions: From fb00befb453da9ac91797f5129811a1813708091 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Wed, 12 Aug 2020 17:39:57 +0800 Subject: [PATCH 10/24] fix extension update --- .../azure/cli/core/extension/operations.py | 5 ++-- .../azure/cli/command_modules/util/custom.py | 28 ++++++++----------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/extension/operations.py b/src/azure-cli-core/azure/cli/core/extension/operations.py index 7f204f25e55..7efa989bc4c 100644 --- a/src/azure-cli-core/azure/cli/core/extension/operations.py +++ b/src/azure-cli-core/azure/cli/core/extension/operations.py @@ -289,7 +289,7 @@ def show_extension(extension_name): raise CLIError(e) -def update_extension(cmd=None, source=None, extension_name=None, index_url=None, pip_extra_index_urls=None, pip_proxy=None, cli_ctx=None): +def update_extension(cmd=None, extension_name=None, index_url=None, pip_extra_index_urls=None, pip_proxy=None, cli_ctx=None, source=None): try: ext_sha256 = None cmd_cli_ctx = cli_ctx or cmd.cli_ctx @@ -300,7 +300,8 @@ def update_extension(cmd=None, source=None, extension_name=None, index_url=None, source, ext_sha256 = resolve_from_index(extension_name, cur_version=cur_version, index_url=index_url) except NoExtensionCandidatesError as err: logger.debug(err) - raise CLIError("No updates available for '{}'. Use --debug for more information.".format(extension_name)) + logger.warning("No updates available for '{}'. Use --debug for more information.".format(extension_name)) + return # Copy current version of extension to tmp directory in case we need to restore it after a failed install. backup_dir = os.path.join(tempfile.mkdtemp(), extension_name) extension_path = ext.path diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index cb6abbc6da2..e8da5730a6c 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -58,6 +58,9 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many update_cli = False if not update_all: return + + exts = [ext.name for ext in get_extensions(ext_type=WheelExtension)] if update_all else [] + exit_code = 0 if update_cli: logger.warning("Your current Azure CLI version is %s. Latest version is %s.", local, pypi) @@ -118,20 +121,11 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many if exit_code: telemetry.set_failure("CLI upgrade failed.") sys.exit(exit_code) - ext_sources = [] - if update_all: - extensions = get_extensions(ext_type=WheelExtension) - if extensions: - for ext in extensions: - try: - download_url, _ = resolve_from_index(ext.name, cur_version=ext.version) - ext_sources.append((ext.name, download_url)) - except NoExtensionCandidatesError: - pass - try: - for name, download_url in ext_sources: - logger.warning("Update extension: %s", name) - update_extension(cli_ctx=cmd.cli_ctx, extension_name=name, source=download_url) - except CLIError as ex: - telemetry.set_failure("Extension update failed during upgrade. {}".format(str(ex))) - raise ex + + 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 CLIError as ex: + telemetry.set_failure("Extension update failed during az upgrade. {}".format(str(ex))) + raise ex From 139c1f5799373609682e2ef01531772b4ea7ceab Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Thu, 13 Aug 2020 14:29:25 +0800 Subject: [PATCH 11/24] fetch latest version from github --- .../azure/cli/command_modules/util/custom.py | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index e8da5730a6c..3c4f10b6d43 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -32,42 +32,42 @@ def show_version(cmd): # pylint: disable=unused-argument return versions -def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many-locals, too-many-statements, too-many-branches, no-member +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 requests 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.util import CLI_PACKAGE_NAME, _get_local_versions, _update_latest_from_pypi from azure.cli.core.extension import get_extensions, WheelExtension - from azure.cli.core.extension._resolve import resolve_from_index, NoExtensionCandidatesError - from azure.cli.core.extension.operations import update_extension from distutils.version import LooseVersion from knack.util import CLIError update_cli = True - versions = _get_local_versions() - local = versions[CLI_PACKAGE_NAME]['local'] - versions, success = _update_latest_from_pypi(versions) - if not success: - raise CLIError("Failed to fetch the latest version. Please check your network connectivity.") - pypi = versions[CLI_PACKAGE_NAME].get('pypi', None) - if pypi and LooseVersion(pypi) <= LooseVersion(local): - logger.warning("You already have the latest %s: %s", CLI_PACKAGE_NAME, local) + git_release_url = "https://api.github.com/repos/Azure/azure-cli/releases/latest" + response = requests.get(git_release_url) + if response.status_code != 200: + raise CLIError("Failed to fetch the latest version from '{}' with status code '{}' and reason '{}'".format( + git_release_url, response.status_code, response.reason)) + latest_version = response.json()['tag_name'].replace('azure-cli-', '') + + 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 - exts = [ext.name for ext in get_extensions(ext_type=WheelExtension)] if update_all else [] - + exit_code = 0 if update_cli: - logger.warning("Your current Azure CLI version is %s. Latest version is %s.", local, pypi) + logger.warning("Your current Azure CLI version is %s. Latest version available is %s.", + local_version, latest_version) 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\nWould you like to proceed?", default='y') + "cli/azure/release-notes-azure-cli\nDo you wnat to continue?", default='y') if not confirmation: telemetry.set_success("Upgrade stopped by user") return @@ -96,13 +96,17 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many update_cmd.insert(0, 'sudo') exit_code = subprocess.call(update_cmd) elif any(x in distname for x in ['opensuse', 'suse', 'sles']): - update_cmd = 'zypper update -y azure-cli'.split() + zypper_refresh_cmd = ['zypper', 'refresh'] + az_update_cmd = 'zypper update -y azure-cli'.split() if os.geteuid() != 0: # pylint: disable=no-member - update_cmd.insert(0, 'sudo') - exit_code = subprocess.call(update_cmd) + zypper_refresh_cmd.insert(0, 'sudo') + az_update_cmd.insert(0, 'sudo') + subprocess.call(zypper_refresh_cmd) + exit_code = subprocess.call(az_update_cmd) else: logger.warning(UPGRADE_MSG) elif installer == 'HOMEBREW': + logger.warning("Update homebrew formulae") subprocess.call(['brew', 'update']) exit_code = subprocess.call(['brew', 'upgrade', 'azure-cli']) elif installer == 'PIP': @@ -110,7 +114,6 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many '--disable-pip-version-check', '--no-cache-dir'] logger.debug('Executing pip with args: %s', pip_args) exit_code = subprocess.call(pip_args, shell=platform.system() == 'Windows') - return elif installer == 'DOCKER': logger.warning("Exit the container to pull latest image with 'docker pull mcr.microsoft.com/azure-cli' " "or 'pip install --upgrade azure-cli' in this container") From 28ddccd0733b0acb8671ed897f304e1bf890d772 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Thu, 13 Aug 2020 18:15:17 +0800 Subject: [PATCH 12/24] add auto upgrade --- src/azure-cli-core/azure/cli/core/__init__.py | 5 +++- .../azure/cli/core/extension/operations.py | 2 +- src/azure-cli-core/azure/cli/core/util.py | 23 ++++++++++++++----- src/azure-cli/azure/cli/__main__.py | 22 ++++++++++++++++++ .../azure/cli/command_modules/util/custom.py | 2 +- 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/__init__.py b/src/azure-cli-core/azure/cli/core/__init__.py index e624f3a096c..64464e6f563 100644 --- a/src/azure-cli-core/azure/cli/core/__init__.py +++ b/src/azure-cli-core/azure/cli/core/__init__.py @@ -48,7 +48,8 @@ def __init__(self, **kwargs): register_ids_argument, register_global_subscription_argument) from azure.cli.core.cloud import get_active_cloud from azure.cli.core.commands.transform import register_global_transforms - from azure.cli.core._session import ACCOUNT, CONFIG, SESSION, INDEX + from azure.cli.core._session import ACCOUNT, CONFIG, SESSION, INDEX, VERSIONS + from azure.cli.core.util import handle_version_update from knack.util import ensure_dir @@ -64,6 +65,8 @@ def __init__(self, **kwargs): CONFIG.load(os.path.join(azure_folder, 'az.json')) SESSION.load(os.path.join(azure_folder, 'az.sess'), max_age=3600) INDEX.load(os.path.join(azure_folder, 'commandIndex.json')) + VERSIONS.load(os.path.join(azure_folder, 'versionCheck.json')) + handle_version_update() self.cloud = get_active_cloud(self) logger.debug('Current cloud config:\n%s', str(self.cloud.name)) diff --git a/src/azure-cli-core/azure/cli/core/extension/operations.py b/src/azure-cli-core/azure/cli/core/extension/operations.py index 7efa989bc4c..da53b7ab513 100644 --- a/src/azure-cli-core/azure/cli/core/extension/operations.py +++ b/src/azure-cli-core/azure/cli/core/extension/operations.py @@ -300,7 +300,7 @@ def update_extension(cmd=None, extension_name=None, index_url=None, pip_extra_in source, ext_sha256 = resolve_from_index(extension_name, cur_version=cur_version, index_url=index_url) except NoExtensionCandidatesError as err: logger.debug(err) - logger.warning("No updates available for '{}'. Use --debug for more information.".format(extension_name)) + logger.warning("No updates available for '%s'. Use --debug for more information.", extension_name) return # Copy current version of extension to tmp directory in case we need to restore it after a failed install. backup_dir = os.path.join(tempfile.mkdtemp(), extension_name) diff --git a/src/azure-cli-core/azure/cli/core/util.py b/src/azure-cli-core/azure/cli/core/util.py index 141ca271eee..357d71645b1 100644 --- a/src/azure-cli-core/azure/cli/core/util.py +++ b/src/azure-cli-core/azure/cli/core/util.py @@ -173,15 +173,12 @@ def _update_latest_from_pypi(versions): def get_cached_latest_versions(versions=None): """ Get the latest versions from a cached file""" - import os import datetime - from azure.cli.core._environment import get_config_dir from azure.cli.core._session import VERSIONS if not versions: versions = _get_local_versions() - VERSIONS.load(os.path.join(get_config_dir(), 'versionCheck.json')) if VERSIONS[_VERSION_UPDATE_TIME]: version_update_time = datetime.datetime.strptime(VERSIONS[_VERSION_UPDATE_TIME], '%Y-%m-%d %H:%M:%S.%f') if datetime.datetime.now() < version_update_time + datetime.timedelta(days=1): @@ -286,12 +283,9 @@ def get_az_version_json(): def show_updates_available(new_line_before=False, new_line_after=False): - import os from azure.cli.core._session import VERSIONS import datetime - from azure.cli.core._environment import get_config_dir - VERSIONS.load(os.path.join(get_config_dir(), 'versionCheck.json')) if VERSIONS[_VERSION_CHECK_TIME]: version_check_time = datetime.datetime.strptime(VERSIONS[_VERSION_CHECK_TIME], '%Y-%m-%d %H:%M:%S.%f') if datetime.datetime.now() < version_check_time + datetime.timedelta(days=7): @@ -1036,3 +1030,20 @@ def is_guid(guid): return True except ValueError: return False + + +def handle_version_update(): + """Clean up information in local file that may be invalidated + because of a version update of Azure CLI + """ + try: + from azure.cli.core._session import VERSIONS + from distutils.version import LooseVersion # pylint: disable=import-error,no-name-in-module + from azure.cli.core import __version__ + if not VERSIONS['versions']: + get_cached_latest_versions() + elif LooseVersion(VERSIONS['versions']['core']['local']) != LooseVersion(__version__): + VERSIONS['versions'] = {} + VERSIONS['update_time'] = '' + except Exception as ex: # pylint: disable=broad-except + logger.warning(ex) diff --git a/src/azure-cli/azure/cli/__main__.py b/src/azure-cli/azure/cli/__main__.py index 06eef18cc6f..9f197533c46 100644 --- a/src/azure-cli/azure/cli/__main__.py +++ b/src/azure-cli/azure/cli/__main__.py @@ -74,3 +74,25 @@ def cli_main(cli, args): telemetry.set_init_time_elapsed("{:.6f}".format(init_finish_time - start_time)) telemetry.set_invoke_time_elapsed("{:.6f}".format(invoke_finish_time - init_finish_time)) telemetry.conclude() + try: + if sys.argv[1] != 'upgrade' and az_cli.config.getboolean('auto-upgrade', 'enable', False): + from azure.cli.core._session import VERSIONS + from azure.cli.core.util import get_cached_latest_versions, _VERSION_UPDATE_TIME + if VERSIONS[_VERSION_UPDATE_TIME]: + import datetime + version_update_time = datetime.datetime.strptime(VERSIONS[_VERSION_UPDATE_TIME], '%Y-%m-%d %H:%M:%S.%f') + if datetime.datetime.now() > version_update_time + datetime.timedelta(days=10): + get_cached_latest_versions() + from distutils.version import LooseVersion + if LooseVersion(VERSIONS['versions']['core']['local']) < LooseVersion(VERSIONS['versions']['core']['pypi']): # pylint: disable=line-too-long + import subprocess + import platform + logger.warning("New Azure CLI version available. Running 'az upgrade' to update automatically.") + update_all = az_cli.config.getboolean('auto-upgrade', 'all', True) + prompt = az_cli.config.getboolean('auto-upgrade', 'prompt', True) + cmd = ['az', 'upgrade', '--all', str(update_all)] + if not prompt: + cmd.append('-y') + subprocess.call(cmd, shell=platform.system() == 'Windows') + except Exception: # pylint: disable=broad-except + pass diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index 3c4f10b6d43..113caa0f153 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -67,7 +67,7 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many 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 wnat to continue?", default='y') + "cli/azure/release-notes-azure-cli\nDo you want to continue?", default='y') if not confirmation: telemetry.set_success("Upgrade stopped by user") return From 5fbcfc51d7bc426e40ffac39c42d9564aadf6ed7 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Fri, 14 Aug 2020 11:02:46 +0800 Subject: [PATCH 13/24] add wait in start-process --- src/azure-cli/azure/cli/command_modules/util/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index 113caa0f153..5f7a917f8ba 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -118,7 +118,7 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many logger.warning("Exit the container to pull latest image with 'docker pull mcr.microsoft.com/azure-cli' " "or 'pip install --upgrade azure-cli' in this container") elif installer == 'MSI': - exit_code = subprocess.call(['powershell.exe', 'Start-Process powershell -Verb runAs -ArgumentList "Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile AzureCLI.msi;Start-Process msiexec.exe -Wait -ArgumentList \'/I AzureCLI.msi\';Remove-Item AzureCLI.msi"']) # pylint: disable=line-too-long + exit_code = subprocess.call(['powershell.exe', 'Start-Process powershell -Wait -Verb runAs -ArgumentList "Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile AzureCLI.msi;Start-Process msiexec.exe -Wait -ArgumentList \'/I AzureCLI.msi\';Remove-Item AzureCLI.msi"']) # pylint: disable=line-too-long else: logger.warning(UPGRADE_MSG) if exit_code: From 8ad8ffd0d2c5b8c109686cef917c43cff5ded674 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Fri, 14 Aug 2020 15:05:44 +0800 Subject: [PATCH 14/24] modify upgrade message --- .../azure/cli/core/extension/operations.py | 20 +++++----- src/azure-cli-core/azure/cli/core/util.py | 39 +------------------ .../azure/cli/command_modules/util/_help.py | 2 +- .../azure/cli/command_modules/util/custom.py | 28 ++++++------- 4 files changed, 26 insertions(+), 63 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/extension/operations.py b/src/azure-cli-core/azure/cli/core/extension/operations.py index da53b7ab513..18e09535f1e 100644 --- a/src/azure-cli-core/azure/cli/core/extension/operations.py +++ b/src/azure-cli-core/azure/cli/core/extension/operations.py @@ -289,19 +289,17 @@ def show_extension(extension_name): raise CLIError(e) -def update_extension(cmd=None, extension_name=None, index_url=None, pip_extra_index_urls=None, pip_proxy=None, cli_ctx=None, source=None): +def update_extension(cmd=None, extension_name=None, index_url=None, pip_extra_index_urls=None, pip_proxy=None, cli_ctx=None): try: - ext_sha256 = None cmd_cli_ctx = cli_ctx or cmd.cli_ctx ext = get_extension(extension_name, ext_type=WheelExtension) cur_version = ext.get_version() - if not source: - try: - source, ext_sha256 = resolve_from_index(extension_name, cur_version=cur_version, index_url=index_url) - except NoExtensionCandidatesError as err: - logger.debug(err) - logger.warning("No updates available for '%s'. Use --debug for more information.", extension_name) - return + try: + source, ext_sha256 = resolve_from_index(extension_name, cur_version=cur_version, index_url=index_url) + except NoExtensionCandidatesError as err: + logger.debug(err) + logger.warning("No updates available for '%s'. Use --debug for more information.", extension_name) + return # Copy current version of extension to tmp directory in case we need to restore it after a failed install. backup_dir = os.path.join(tempfile.mkdtemp(), extension_name) extension_path = ext.path @@ -373,12 +371,12 @@ def list_versions(extension_name, index_url=None): installed_ext = None results = [] - latest_compatible_version = '0.0.0' + latest_compatible_version = None for ext in sorted(exts, key=lambda c: parse_version(c['metadata']['version']), reverse=True): compatible = ext_compat_with_cli(ext['metadata'])[0] ext_version = ext['metadata']['version'] - if compatible and latest_compatible_version == '0.0.0': + if latest_compatible_version is None and compatible: latest_compatible_version = ext_version installed = ext_version == installed_ext.version if installed_ext else False if installed and parse_version(latest_compatible_version) > parse_version(installed_ext.version): diff --git a/src/azure-cli-core/azure/cli/core/util.py b/src/azure-cli-core/azure/cli/core/util.py index 357d71645b1..3ccfc0c8b01 100644 --- a/src/azure-cli-core/azure/cli/core/util.py +++ b/src/azure-cli-core/azure/cli/core/util.py @@ -37,16 +37,6 @@ _CHILDREN_RE = re.compile('(?i)/(?P[^/]*)/(?P[^/]*)') -_PACKAGE_UPGRADE_INSTRUCTIONS = {"YUM": ("sudo yum update -y azure-cli", "https://aka.ms/doc/UpdateAzureCliYum"), - "ZYPPER": ("sudo zypper refresh && sudo zypper update -y azure-cli", "https://aka.ms/doc/UpdateAzureCliZypper"), - "DEB": ("sudo apt-get update && sudo apt-get install --only-upgrade -y azure-cli", "https://aka.ms/doc/UpdateAzureCliApt"), - "HOMEBREW": ("brew update && brew upgrade azure-cli", "https://aka.ms/doc/UpdateAzureCliHomebrew"), - "PIP": ("curl -L https://aka.ms/InstallAzureCli | bash", "https://aka.ms/doc/UpdateAzureCliLinux"), - "MSI": ("https://aka.ms/installazurecliwindows", "https://aka.ms/doc/UpdateAzureCliMsi"), - "DOCKER": ("docker pull mcr.microsoft.com/azure-cli", "https://aka.ms/doc/UpdateAzureCliDocker")} - -_GENERAL_UPGRADE_INSTRUCTION = 'Instructions can be found at https://aka.ms/doc/InstallAzureCli' - _VERSION_CHECK_TIME = 'check_time' _VERSION_UPDATE_TIME = 'update_time' @@ -308,34 +298,7 @@ def show_updates(updates_available): if in_cloud_console(): warning_msg = 'You have %i updates available. They will be updated with the next build of Cloud Shell.' else: - warning_msg = 'You have %i updates available. Consider updating your CLI installation' - from azure.cli.core._environment import _ENV_AZ_INSTALLER - import os - installer = os.getenv(_ENV_AZ_INSTALLER) - instruction_msg = '' - if installer in _PACKAGE_UPGRADE_INSTRUCTIONS: - if installer == 'RPM': - distname, _ = get_linux_distro() - if not distname: - instruction_msg = '. {}'.format(_GENERAL_UPGRADE_INSTRUCTION) - else: - distname = distname.lower().strip() - if any(x in distname for x in ['centos', 'rhel', 'red hat', 'fedora']): - installer = 'YUM' - elif any(x in distname for x in ['opensuse', 'suse', 'sles']): - installer = 'ZYPPER' - else: - instruction_msg = '. {}'.format(_GENERAL_UPGRADE_INSTRUCTION) - elif installer == 'PIP': - system = platform.system() - alternative_command = " or '{}' if you used our script for installation. Detailed instructions can be found at {}".format(_PACKAGE_UPGRADE_INSTRUCTIONS[installer][0], _PACKAGE_UPGRADE_INSTRUCTIONS[installer][1]) if system != 'Windows' else '' - instruction_msg = " with 'pip install --upgrade azure-cli'{}".format(alternative_command) - if instruction_msg: - warning_msg += instruction_msg - else: - warning_msg += " with '{}'. Detailed instructions can be found at {}".format(_PACKAGE_UPGRADE_INSTRUCTIONS[installer][0], _PACKAGE_UPGRADE_INSTRUCTIONS[installer][1]) - else: - warning_msg += '. {}'.format(_GENERAL_UPGRADE_INSTRUCTION) + warning_msg = "You have %i updates available. Consider updating your CLI installation with 'az upgrade'" logger.warning(warning_msg, updates_available) else: print('Your CLI is up-to-date.') diff --git a/src/azure-cli/azure/cli/command_modules/util/_help.py b/src/azure-cli/azure/cli/command_modules/util/_help.py index 98bb68e16aa..62dc572c457 100644 --- a/src/azure-cli/azure/cli/command_modules/util/_help.py +++ b/src/azure-cli/azure/cli/command_modules/util/_help.py @@ -43,5 +43,5 @@ helps['upgrade'] = """ type: command -short-summary: Upgrade to the latest Azure CLI version +short-summary: Upgrade Azure CLI and extensions """ diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index 5f7a917f8ba..7cfb6f26338 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -7,8 +7,7 @@ logger = get_logger(__name__) -UPGRADE_MSG = 'Not able to upgrade automatically. Instructions can be found at ' \ - 'https://docs.microsoft.com/cli/azure/install-azure-cli' +UPGRADE_MSG = 'Not able to upgrade automatically. Instructions can be found at https://aka.ms/doc/InstallAzureCli' def rest_call(cmd, url, method=None, headers=None, uri_parameters=None, @@ -46,18 +45,21 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many from knack.util import CLIError update_cli = True - git_release_url = "https://api.github.com/repos/Azure/azure-cli/releases/latest" - response = requests.get(git_release_url) - if response.status_code != 200: - raise CLIError("Failed to fetch the latest version from '{}' with status code '{}' and reason '{}'".format( - git_release_url, response.status_code, response.reason)) - latest_version = response.json()['tag_name'].replace('azure-cli-', '') + try: + git_release_url = "https://api.github.com/repos/Azure/azure-cli/releases/latest" + response = requests.get(git_release_url) + if response.status_code != 200: + raise CLIError("Failed to fetch the latest version from '{}' with status code '{}' and reason '{}'".format( + git_release_url, response.status_code, response.reason)) + latest_version = response.json()['tag_name'].replace('azure-cli-', '') - 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 + 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.warning('Failed to check against the latest version, will try to upgrade directly.') exts = [ext.name for ext in get_extensions(ext_type=WheelExtension)] if update_all else [] exit_code = 0 From 5f0e199c2e73a53d76b3249a3d6ae25c479bf62e Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Sun, 16 Aug 2020 12:37:18 +0800 Subject: [PATCH 15/24] get version from github --- src/azure-cli-core/azure/cli/core/util.py | 36 ++++++++++++++++++- .../azure/cli/command_modules/util/custom.py | 12 ++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/util.py b/src/azure-cli-core/azure/cli/core/util.py index 3ccfc0c8b01..37bb88cd0e6 100644 --- a/src/azure-cli-core/azure/cli/core/util.py +++ b/src/azure-cli-core/azure/cli/core/util.py @@ -161,6 +161,40 @@ def _update_latest_from_pypi(versions): return versions, success +def get_latest_from_github(package_path='azure-cli'): + try: + import requests + git_url = "https://raw.githubusercontent.com/Azure/azure-cli/master/src/{}/setup.py".format(package_path) + response = requests.get(git_url, timeout=10) + if response.status_code != 200: + logger.info("Failed to fetch the latest version from '%s' with status code '%s' and reason '%s'", + git_url, response.status_code, response.reason) + return None + for line in response.iter_lines(): + txt = line.decode('utf-8', errors='ignore') + if txt.startswith('VERSION'): + match = re.search(r'VERSION = \"(.*)\"$', txt) + if match: + return match.group(1) + except Exception as ex: # pylint: disable=broad-except + logger.info("Failed to get the latest version from '%s'. %s", git_url, str(ex)) + return None + + +def _update_latest_from_github(versions): + if not check_connectivity(max_retries=0): + return versions, False + success = True + for pkg in ['azure-cli-core', 'azure-cli-telemetry']: + version = get_latest_from_github(pkg) + if not version: + success = False + else: + versions[pkg.replace(COMPONENT_PREFIX, '')]['pypi'] = version + versions[CLI_PACKAGE_NAME]['pypi'] = versions['core']['pypi'] + return versions, success + + def get_cached_latest_versions(versions=None): """ Get the latest versions from a cached file""" import datetime @@ -176,7 +210,7 @@ def get_cached_latest_versions(versions=None): if cache_versions and cache_versions['azure-cli']['local'] == versions['azure-cli']['local']: return cache_versions.copy(), True - versions, success = _update_latest_from_pypi(versions) + versions, success = _update_latest_from_github(versions) if success: VERSIONS['versions'] = versions VERSIONS[_VERSION_UPDATE_TIME] = str(datetime.datetime.now()) diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index 7cfb6f26338..54923978a7d 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -34,7 +34,6 @@ def show_version(cmd): # pylint: disable=unused-argument 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 requests import sys import subprocess import azure.cli.core.telemetry as telemetry @@ -45,21 +44,16 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many from knack.util import CLIError update_cli = True + from azure.cli.core.util import get_latest_from_github try: - git_release_url = "https://api.github.com/repos/Azure/azure-cli/releases/latest" - response = requests.get(git_release_url) - if response.status_code != 200: - raise CLIError("Failed to fetch the latest version from '{}' with status code '{}' and reason '{}'".format( - git_release_url, response.status_code, response.reason)) - latest_version = response.json()['tag_name'].replace('azure-cli-', '') - + 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.warning('Failed to check against the latest version, will try to upgrade directly.') + pass exts = [ext.name for ext in get_extensions(ext_type=WheelExtension)] if update_all else [] exit_code = 0 From ad2f965f18b4f5a858e09645f482668520c53a46 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Sun, 16 Aug 2020 14:04:40 +0800 Subject: [PATCH 16/24] fix tests --- .../azure/cli/core/extension/operations.py | 3 ++- .../tests/latest/test_extension_commands.py | 13 +++++++++---- .../azure/cli/command_modules/util/custom.py | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/extension/operations.py b/src/azure-cli-core/azure/cli/core/extension/operations.py index 18e09535f1e..41686f2f00a 100644 --- a/src/azure-cli-core/azure/cli/core/extension/operations.py +++ b/src/azure-cli-core/azure/cli/core/extension/operations.py @@ -298,7 +298,8 @@ def update_extension(cmd=None, extension_name=None, index_url=None, pip_extra_in source, ext_sha256 = resolve_from_index(extension_name, cur_version=cur_version, index_url=index_url) except NoExtensionCandidatesError as err: logger.debug(err) - logger.warning("No updates available for '%s'. Use --debug for more information.", extension_name) + msg = "No updates available for '{}'. Use --debug for more information.".format(extension_name) + logger.warning(msg) return # Copy current version of extension to tmp directory in case we need to restore it after a failed install. backup_dir = os.path.join(tempfile.mkdtemp(), extension_name) diff --git a/src/azure-cli-core/azure/cli/core/extension/tests/latest/test_extension_commands.py b/src/azure-cli-core/azure/cli/core/extension/tests/latest/test_extension_commands.py index c53d506cb08..da4667ba268 100644 --- a/src/azure-cli-core/azure/cli/core/extension/tests/latest/test_extension_commands.py +++ b/src/azure-cli-core/azure/cli/core/extension/tests/latest/test_extension_commands.py @@ -304,13 +304,18 @@ def test_update_extension_not_found(self): self.assertEqual(str(err.exception), 'The extension {} is not installed.'.format(MY_EXT_NAME)) def test_update_extension_no_updates(self): + logger_msgs = [] + + def mock_log_warning(_, msg): + logger_msgs.append(msg) + add_extension(cmd=self.cmd, source=MY_EXT_SOURCE) ext = show_extension(MY_EXT_NAME) self.assertEqual(ext[OUT_KEY_VERSION], '0.0.3+dev') - with mock.patch('azure.cli.core.extension.operations.resolve_from_index', side_effect=NoExtensionCandidatesError()): - with self.assertRaises(CLIError) as err: - update_extension(self.cmd, MY_EXT_NAME) - self.assertTrue("No updates available for '{}'.".format(MY_EXT_NAME) in str(err.exception)) + with mock.patch('azure.cli.core.extension.operations.resolve_from_index', side_effect=NoExtensionCandidatesError()), \ + mock.patch('logging.Logger.warning', mock_log_warning): + update_extension(self.cmd, MY_EXT_NAME) + self.assertTrue("No updates available for '{}'.".format(MY_EXT_NAME) in logger_msgs[0]) def test_update_extension_exception_in_update_and_rolled_back(self): add_extension(cmd=self.cmd, source=MY_EXT_SOURCE) diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index 54923978a7d..13ad738e881 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -53,7 +53,7 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many if not update_all: return except Exception as ex: # pylint: disable=broad-except - pass + 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 From f36f9de9692ce5fdcca6bd7cd115add444bf50c4 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Mon, 17 Aug 2020 12:50:22 +0800 Subject: [PATCH 17/24] add debug --- .../azure/cli/core/extension/operations.py | 4 ++-- src/azure-cli/azure/cli/__main__.py | 4 ++-- .../azure/cli/command_modules/extension/_help.py | 8 ++++++++ .../azure/cli/command_modules/util/custom.py | 12 +++++++++--- src/azure-cli/setup.py | 3 --- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/extension/operations.py b/src/azure-cli-core/azure/cli/core/extension/operations.py index 41686f2f00a..687305eb8c5 100644 --- a/src/azure-cli-core/azure/cli/core/extension/operations.py +++ b/src/azure-cli-core/azure/cli/core/extension/operations.py @@ -295,7 +295,7 @@ def update_extension(cmd=None, extension_name=None, index_url=None, pip_extra_in ext = get_extension(extension_name, ext_type=WheelExtension) cur_version = ext.get_version() try: - source, ext_sha256 = resolve_from_index(extension_name, cur_version=cur_version, index_url=index_url) + download_url, ext_sha256 = resolve_from_index(extension_name, cur_version=cur_version, index_url=index_url) except NoExtensionCandidatesError as err: logger.debug(err) msg = "No updates available for '{}'. Use --debug for more information.".format(extension_name) @@ -310,7 +310,7 @@ def update_extension(cmd=None, extension_name=None, index_url=None, pip_extra_in shutil.rmtree(extension_path) # Install newer version try: - _add_whl_ext(cli_ctx=cmd_cli_ctx, source=source, ext_sha256=ext_sha256, + _add_whl_ext(cli_ctx=cmd_cli_ctx, source=download_url, ext_sha256=ext_sha256, pip_extra_index_urls=pip_extra_index_urls, pip_proxy=pip_proxy) logger.debug('Deleting backup of old extension at %s', backup_dir) shutil.rmtree(backup_dir) diff --git a/src/azure-cli/azure/cli/__main__.py b/src/azure-cli/azure/cli/__main__.py index 9f197533c46..1279a78f531 100644 --- a/src/azure-cli/azure/cli/__main__.py +++ b/src/azure-cli/azure/cli/__main__.py @@ -76,8 +76,8 @@ def cli_main(cli, args): telemetry.conclude() try: if sys.argv[1] != 'upgrade' and az_cli.config.getboolean('auto-upgrade', 'enable', False): - from azure.cli.core._session import VERSIONS - from azure.cli.core.util import get_cached_latest_versions, _VERSION_UPDATE_TIME + from azure.cli.core._session import VERSIONS # pylint: disable=ungrouped-imports + from azure.cli.core.util import get_cached_latest_versions, _VERSION_UPDATE_TIME # pylint: disable=ungrouped-imports if VERSIONS[_VERSION_UPDATE_TIME]: import datetime version_update_time = datetime.datetime.strptime(VERSIONS[_VERSION_UPDATE_TIME], '%Y-%m-%d %H:%M:%S.%f') diff --git a/src/azure-cli/azure/cli/command_modules/extension/_help.py b/src/azure-cli/azure/cli/command_modules/extension/_help.py index 1c9284d969b..fe7c41eb114 100644 --- a/src/azure-cli/azure/cli/command_modules/extension/_help.py +++ b/src/azure-cli/azure/cli/command_modules/extension/_help.py @@ -75,3 +75,11 @@ - name: Update an extension by name and use pip proxy for dependencies text: az extension update --name anextension --pip-proxy https://user:pass@proxy.server:8080 """ + +helps['extension list-versions'] = """ +type: command +short-summary: List available versions for an extension. +examples: + - name: List available versions for an extension + text: az extension list-versions --name anextension +""" diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index 13ad738e881..d1231a07bbb 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -78,6 +78,7 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many apt_update_cmd.insert(0, 'sudo') az_update_cmd.insert(0, 'sudo') subprocess.call(apt_update_cmd) + 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 @@ -90,6 +91,7 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many 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'] @@ -98,22 +100,26 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many zypper_refresh_cmd.insert(0, 'sudo') az_update_cmd.insert(0, 'sudo') subprocess.call(zypper_refresh_cmd) + 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") subprocess.call(['brew', 'update']) - exit_code = subprocess.call(['brew', 'upgrade', 'azure-cli']) + 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('Executing pip with args: %s', pip_args) + 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 'pip install --upgrade azure-cli' in this container") + "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', 'Start-Process powershell -Wait -Verb runAs -ArgumentList "Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile AzureCLI.msi;Start-Process msiexec.exe -Wait -ArgumentList \'/I AzureCLI.msi\';Remove-Item AzureCLI.msi"']) # pylint: disable=line-too-long else: logger.warning(UPGRADE_MSG) diff --git a/src/azure-cli/setup.py b/src/azure-cli/setup.py index 98f3c240d6d..cbea91a1363 100644 --- a/src/azure-cli/setup.py +++ b/src/azure-cli/setup.py @@ -183,9 +183,6 @@ 'resources/WindowsFunctionsStacks.json', 'resources/LinuxFunctionsStacks.json', 'resources/WebappRuntimeStacks.json' - ], - 'azure.cli.command_modules.util': [ - 'upgrade.ps1' ] }, cmdclass=cmdclass From d2fa204346df8ee3e855ccdc000481eda67c542e Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Thu, 20 Aug 2020 12:44:16 +0800 Subject: [PATCH 18/24] error handling --- src/azure-cli/azure/cli/__main__.py | 7 +++- .../azure/cli/command_modules/util/custom.py | 41 +++++++++++-------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/azure-cli/azure/cli/__main__.py b/src/azure-cli/azure/cli/__main__.py index 1279a78f531..dc4275a87c8 100644 --- a/src/azure-cli/azure/cli/__main__.py +++ b/src/azure-cli/azure/cli/__main__.py @@ -93,6 +93,11 @@ def cli_main(cli, args): cmd = ['az', 'upgrade', '--all', str(update_all)] if not prompt: cmd.append('-y') - subprocess.call(cmd, shell=platform.system() == 'Windows') + import os + devnull = open(os.devnull, 'w') + if prompt: + subprocess.call(cmd, shell=platform.system() == 'Windows') + else: + subprocess.call(cmd, shell=platform.system() == 'Windows', stdout=devnull) except Exception: # pylint: disable=broad-except pass diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index d1231a07bbb..f298818ff70 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -58,8 +58,9 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many exit_code = 0 if update_cli: - logger.warning("Your current Azure CLI version is %s. Latest version available is %s.", - local_version, latest_version) + 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/" @@ -77,9 +78,10 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many if os.geteuid() != 0: # pylint: disable=no-member apt_update_cmd.insert(0, 'sudo') az_update_cmd.insert(0, 'sudo') - subprocess.call(apt_update_cmd) - logger.debug("Update azure cli with '%s'", " ".join(apt_update_cmd)) - exit_code = subprocess.call(az_update_cmd) + 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() @@ -99,17 +101,19 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many if os.geteuid() != 0: # pylint: disable=no-member zypper_refresh_cmd.insert(0, 'sudo') az_update_cmd.insert(0, 'sudo') - subprocess.call(zypper_refresh_cmd) - logger.debug("Update azure cli with '%s'", " ".join(az_update_cmd)) - exit_code = subprocess.call(az_update_cmd) + 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") - subprocess.call(['brew', 'update']) - update_cmd = ['brew', 'upgrade', 'azure-cli'] - logger.debug("Update azure cli with '%s'", " ".join(update_cmd)) - exit_code = subprocess.call(update_cmd) + 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'] @@ -128,9 +132,10 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many sys.exit(exit_code) 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 CLIError as ex: - telemetry.set_failure("Extension update failed during az upgrade. {}".format(str(ex))) - raise ex + logger.warning("Checking update for %s", ext_name) + exit_code = subprocess.call(['az', 'extension', 'update', '-n', ext_name], shell=platform.system() == 'Windows') + if exit_code: + msg = "Extension {} update failed during az upgrade. Exit code {}.".format(ext_name, exit_code) + raise CLIError(msg) + + logger.warning("Upgrade finished.") From 8857ae07d022f6d1f86ddbcf8742b23acab946b3 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Thu, 20 Aug 2020 14:11:11 +0800 Subject: [PATCH 19/24] telemetry error in auto-upgrade --- src/azure-cli/azure/cli/__main__.py | 32 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/azure-cli/azure/cli/__main__.py b/src/azure-cli/azure/cli/__main__.py index dc4275a87c8..6cfd37e5709 100644 --- a/src/azure-cli/azure/cli/__main__.py +++ b/src/azure-cli/azure/cli/__main__.py @@ -71,11 +71,10 @@ def cli_main(cli, args): except NameError: pass - telemetry.set_init_time_elapsed("{:.6f}".format(init_finish_time - start_time)) - telemetry.set_invoke_time_elapsed("{:.6f}".format(invoke_finish_time - init_finish_time)) - telemetry.conclude() try: - if sys.argv[1] != 'upgrade' and az_cli.config.getboolean('auto-upgrade', 'enable', False): + # check for new version auto-upgrade + if az_cli.config.getboolean('auto-upgrade', 'enable', False) and \ + sys.argv[1] != 'upgrade' and (sys.argv[1] != 'extension' and sys.argv[2] != 'update'): from azure.cli.core._session import VERSIONS # pylint: disable=ungrouped-imports from azure.cli.core.util import get_cached_latest_versions, _VERSION_UPDATE_TIME # pylint: disable=ungrouped-imports if VERSIONS[_VERSION_UPDATE_TIME]: @@ -91,13 +90,24 @@ def cli_main(cli, args): update_all = az_cli.config.getboolean('auto-upgrade', 'all', True) prompt = az_cli.config.getboolean('auto-upgrade', 'prompt', True) cmd = ['az', 'upgrade', '--all', str(update_all)] - if not prompt: - cmd.append('-y') - import os - devnull = open(os.devnull, 'w') if prompt: - subprocess.call(cmd, shell=platform.system() == 'Windows') + exit_code = subprocess.call(cmd, shell=platform.system() == 'Windows') else: - subprocess.call(cmd, shell=platform.system() == 'Windows', stdout=devnull) - except Exception: # pylint: disable=broad-except + import os + devnull = open(os.devnull, 'w') + cmd.append('-y') + exit_code = subprocess.call(cmd, shell=platform.system() == 'Windows', stdout=devnull) + if exit_code != 0: + from knack.util import CLIError + err_msg = "Auto upgrade failed with exit code {}".format(exit_code) + logger.warning(err_msg) + telemetry.set_exception(CLIError(err_msg), fault_type='auto-upgrade-failed') + except IndexError: pass + except Exception as ex: # pylint: disable=broad-except + logger.warning("Auto upgrade failed. %s", str(ex)) + telemetry.set_exception(ex, fault_type='auto-upgrade-failed') + + telemetry.set_init_time_elapsed("{:.6f}".format(init_finish_time - start_time)) + telemetry.set_invoke_time_elapsed("{:.6f}".format(invoke_finish_time - init_finish_time)) + telemetry.conclude() From 99767fa17a9a06ea47c40cc6cd834ea0d0ed4403 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Thu, 20 Aug 2020 15:23:04 +0800 Subject: [PATCH 20/24] fix style --- src/azure-cli/azure/cli/__main__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/azure-cli/azure/cli/__main__.py b/src/azure-cli/azure/cli/__main__.py index 6cfd37e5709..26ea353f334 100644 --- a/src/azure-cli/azure/cli/__main__.py +++ b/src/azure-cli/azure/cli/__main__.py @@ -74,7 +74,7 @@ def cli_main(cli, args): try: # check for new version auto-upgrade if az_cli.config.getboolean('auto-upgrade', 'enable', False) and \ - sys.argv[1] != 'upgrade' and (sys.argv[1] != 'extension' and sys.argv[2] != 'update'): + sys.argv[1] != 'upgrade' and (sys.argv[1] != 'extension' and sys.argv[2] != 'update'): from azure.cli.core._session import VERSIONS # pylint: disable=ungrouped-imports from azure.cli.core.util import get_cached_latest_versions, _VERSION_UPDATE_TIME # pylint: disable=ungrouped-imports if VERSIONS[_VERSION_UPDATE_TIME]: @@ -101,12 +101,12 @@ def cli_main(cli, args): from knack.util import CLIError err_msg = "Auto upgrade failed with exit code {}".format(exit_code) logger.warning(err_msg) - telemetry.set_exception(CLIError(err_msg), fault_type='auto-upgrade-failed') + telemetry.set_exception(CLIError(err_msg), fault_type='auto-upgrade-failed') except IndexError: pass except Exception as ex: # pylint: disable=broad-except logger.warning("Auto upgrade failed. %s", str(ex)) - telemetry.set_exception(ex, fault_type='auto-upgrade-failed') + telemetry.set_exception(ex, fault_type='auto-upgrade-failed') telemetry.set_init_time_elapsed("{:.6f}".format(init_finish_time - start_time)) telemetry.set_invoke_time_elapsed("{:.6f}".format(invoke_finish_time - init_finish_time)) From dcae003147a33e763dd872c35cd5ddcd6543f77c Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Thu, 20 Aug 2020 15:43:21 +0800 Subject: [PATCH 21/24] fix summary in set_exception --- src/azure-cli-core/azure/cli/core/telemetry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/telemetry.py b/src/azure-cli-core/azure/cli/core/telemetry.py index cc10885e88b..2251af42c84 100644 --- a/src/azure-cli-core/azure/cli/core/telemetry.py +++ b/src/azure-cli-core/azure/cli/core/telemetry.py @@ -280,8 +280,8 @@ def set_custom_properties(prop, name, value): @decorators.suppress_all_exceptions() def set_exception(exception, fault_type, summary=None): - if not summary: - _session.result_summary = summary + if not _session.result_summary: + _session.result_summary = _remove_cmd_chars(summary) _session.add_exception(exception, fault_type=fault_type, description=summary) From 545db0bc88a137fc2329e07cf15f32d546c615f9 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Thu, 20 Aug 2020 15:57:20 +0800 Subject: [PATCH 22/24] add rerun message for homebrew --- .../azure/cli/command_modules/util/custom.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index f298818ff70..eebcc7dd50a 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -130,12 +130,16 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many if exit_code: telemetry.set_failure("CLI upgrade failed.") sys.exit(exit_code) - - for ext_name in exts: - logger.warning("Checking update for %s", ext_name) - exit_code = subprocess.call(['az', 'extension', 'update', '-n', ext_name], shell=platform.system() == 'Windows') - if exit_code: - msg = "Extension {} update failed during az upgrade. Exit code {}.".format(ext_name, exit_code) - raise CLIError(msg) + # 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: + logger.warning("Checking update for %s", ext_name) + exit_code = subprocess.call(['az', 'extension', 'update', '-n', ext_name], + shell=platform.system() == 'Windows') + if exit_code: + msg = "Extension {} update failed during az upgrade. Exit code {}.".format(ext_name, exit_code) + raise CLIError(msg) logger.warning("Upgrade finished.") From b9fa5f9deb1828ab2c038c7ff0d64aa2b5a94c35 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Fri, 21 Aug 2020 00:36:02 +0800 Subject: [PATCH 23/24] fix installer --- .../azure/cli/command_modules/util/custom.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index eebcc7dd50a..e61c353251d 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -57,6 +57,8 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many 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) @@ -68,7 +70,7 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many if not confirmation: telemetry.set_success("Upgrade stopped by user") return - installer = os.getenv(_ENV_AZ_INSTALLER) + if installer == 'DEB': from azure.cli.core.util import in_cloud_console if in_cloud_console(): @@ -135,11 +137,12 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many logger.warning("Please rerun 'az upgrade' to update all extensions.") else: for ext_name in exts: - logger.warning("Checking update for %s", ext_name) - exit_code = subprocess.call(['az', 'extension', 'update', '-n', ext_name], - shell=platform.system() == 'Windows') - if exit_code: - msg = "Extension {} update failed during az upgrade. Exit code {}.".format(ext_name, exit_code) + 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.") From c2d7b876d619aa44aa81c7ca97b63a4f1619dd66 Mon Sep 17 00:00:00 2001 From: Feng Zhou Date: Fri, 21 Aug 2020 01:56:19 +0800 Subject: [PATCH 24/24] simplify msi update --- src/azure-cli/azure/cli/command_modules/util/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/util/custom.py b/src/azure-cli/azure/cli/command_modules/util/custom.py index e61c353251d..c90d9689356 100644 --- a/src/azure-cli/azure/cli/command_modules/util/custom.py +++ b/src/azure-cli/azure/cli/command_modules/util/custom.py @@ -126,7 +126,7 @@ def upgrade_version(cmd, update_all=None, yes=None): # pylint: disable=too-many "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', 'Start-Process powershell -Wait -Verb runAs -ArgumentList "Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile AzureCLI.msi;Start-Process msiexec.exe -Wait -ArgumentList \'/I AzureCLI.msi\';Remove-Item AzureCLI.msi"']) # pylint: disable=line-too-long + exit_code = subprocess.call(['powershell.exe', "Start-Process msiexec.exe -Wait -ArgumentList '/i https://aka.ms/installazurecliwindows'"]) # pylint: disable=line-too-long else: logger.warning(UPGRADE_MSG) if exit_code: