Skip to content

Commit

Permalink
[Output] Show update instruction in find, feedback and --help (#13345)
Browse files Browse the repository at this point in the history
  • Loading branch information
fengzhou-msft authored May 14, 2020
1 parent 73ee5ce commit e6e437a
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 52 deletions.
49 changes: 3 additions & 46 deletions src/azure-cli-core/azure/cli/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,6 @@
'content_version', 'kwargs', 'client', 'no_wait']
EVENT_FAILED_EXTENSION_LOAD = 'MainLoader.OnFailedExtensionLoad'

_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'


class AzCli(CLI):

Expand Down Expand Up @@ -98,47 +88,14 @@ def get_cli_version(self):
return __version__

def show_version(self):
from azure.cli.core.util import get_az_version_string
from azure.cli.core.util import get_az_version_string, show_updates
from azure.cli.core.commands.constants import (SURVEY_PROMPT, SURVEY_PROMPT_COLOR,
UX_SURVEY_PROMPT, UX_SURVEY_PROMPT_COLOR)

ver_string, updates_available = get_az_version_string()
print(ver_string)
if updates_available == -1:
logger.warning('Unable to check if your CLI is up-to-date. Check your internet connection.')
elif updates_available:
warning_msg = 'You have %i updates available. Consider updating your CLI installation'
from azure.cli.core._environment import _ENV_AZ_INSTALLER
installer = os.getenv(_ENV_AZ_INSTALLER)
instruction_msg = ''
if installer in _PACKAGE_UPGRADE_INSTRUCTIONS:
if installer == 'RPM':
from azure.cli.core.util import get_linux_distro
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':
import platform
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)
logger.warning(warning_msg, updates_available)
else:
print('Your CLI is up-to-date.')
show_updates(updates_available)

show_link = self.config.getboolean('output', 'show_survey_link', True)
if show_link:
print('\n' + (SURVEY_PROMPT_COLOR if self.enable_color else SURVEY_PROMPT))
Expand Down
2 changes: 2 additions & 0 deletions src/azure-cli-core/azure/cli/core/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ def show_help(self, cli_name, nouns, parser, is_group):
else:
AzCliHelp.update_examples(help_file)
self._print_detailed_help(cli_name, help_file)
from azure.cli.core.util import show_updates_available
show_updates_available(new_line_after=True)
show_link = self.cli_ctx.config.getboolean('output', 'show_survey_link', True)
if show_link:
print(SURVEY_PROMPT_COLOR if self.cli_ctx.enable_color else SURVEY_PROMPT)
Expand Down
6 changes: 6 additions & 0 deletions src/azure-cli-core/azure/cli/core/_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,9 @@ def __len__(self):

# SESSION provides read-write session variables
SESSION = Session()

# VERSIONS provides local versions and pypi versions.
# DO NOT USE it to get the current version of azure-cli,
# it could be lagged behind and can be used to check whether
# an upgrade of azure-cli happens
VERSIONS = Session()
117 changes: 111 additions & 6 deletions src/azure-cli-core/azure/cli/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
# pylint: disable=too-many-lines

from __future__ import print_function
import sys
Expand Down Expand Up @@ -40,6 +41,19 @@

_CHILDREN_RE = re.compile('(?i)/(?P<child_type>[^/]*)/(?P<child_name>[^/]*)')

_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'


def handle_exception(ex): # pylint: disable=too-many-return-statements
# For error code, follow guidelines at https://docs.python.org/2/library/sys.html#sys.exit,
Expand Down Expand Up @@ -150,22 +164,51 @@ def _update_latest_from_pypi(versions):
return versions, success


def get_az_version_string(): # pylint: disable=too-many-statements
from azure.cli.core.extension import get_extensions, EXTENSIONS_DIR, DEV_EXTENSION_SOURCES, EXTENSIONS_SYS_DIR
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

output = six.StringIO()
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):
cache_versions = VERSIONS['versions']
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)
if success:
VERSIONS['versions'] = versions
VERSIONS[_VERSION_UPDATE_TIME] = str(datetime.datetime.now())
return versions.copy(), success


def _get_local_versions():
# get locally installed versions
versions = {}
for dist in get_installed_cli_distributions():
if dist.key == CLI_PACKAGE_NAME:
versions[CLI_PACKAGE_NAME] = {'local': dist.version}
elif dist.key.startswith(COMPONENT_PREFIX):
comp_name = dist.key.replace(COMPONENT_PREFIX, '')
versions[comp_name] = {'local': dist.version}
return versions


def get_az_version_string(use_cache=False): # pylint: disable=too-many-statements
from azure.cli.core.extension import get_extensions, EXTENSIONS_DIR, DEV_EXTENSION_SOURCES, EXTENSIONS_SYS_DIR

output = six.StringIO()
versions = _get_local_versions()

# get the versions from pypi
versions, success = _update_latest_from_pypi(versions)
versions, success = get_cached_latest_versions(versions) if use_cache else _update_latest_from_pypi(versions)
updates_available = 0

def _print(val=''):
Expand Down Expand Up @@ -195,7 +238,7 @@ def _get_version_string(name, version_dict):
_print('Extensions:')
for ext in extensions:
if ext.ext_type == 'dev':
_print(ext.name.ljust(20) + ext.version.rjust(20) + ' (dev) ' + ext.path)
_print(ext.name.ljust(20) + (ext.version or 'Unknown').rjust(20) + ' (dev) ' + ext.path)
else:
_print(ext.name.ljust(20) + (ext.version or 'Unknown').rjust(20))
_print()
Expand Down Expand Up @@ -235,6 +278,68 @@ def get_az_version_json():
return versions


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):
return

_, updates_available = get_az_version_string(use_cache=True)
if updates_available > 0:
if new_line_before:
logger.warning("")
show_updates(updates_available)
if new_line_after:
logger.warning("")
VERSIONS[_VERSION_CHECK_TIME] = str(datetime.datetime.now())


def show_updates(updates_available):
if updates_available == -1:
logger.warning('Unable to check if your CLI is up-to-date. Check your internet connection.')
elif updates_available: # pylint: disable=too-many-nested-blocks
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)
logger.warning(warning_msg, updates_available)
else:
print('Your CLI is up-to-date.')


def get_json_object(json_string):
""" Loads a JSON string as an object and converts all keys to snake case """

Expand Down
2 changes: 2 additions & 0 deletions src/azure-cli/azure/cli/command_modules/feedback/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,8 @@ def handle_feedback(cmd):

if res:
print(_MSG_THNK)
from azure.cli.core.util import show_updates_available
show_updates_available(new_line_before=True)
return
except NoTTYException:
raise CLIError('This command is interactive, however no tty is available.')
Expand Down
2 changes: 2 additions & 0 deletions src/azure-cli/azure/cli/command_modules/find/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def process_query(cli_term):
if has_pruned_answer:
print(style_message("More commands and examples are available in the latest version of the CLI. "
"Please update for the best experience.\n"))
from azure.cli.core.util import show_updates_available
show_updates_available(new_line_after=True)
print(SURVEY_PROMPT)


Expand Down

0 comments on commit e6e437a

Please sign in to comment.