-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Upgrade] Add az upgrade
command
#14803
Changes from all commits
230457a
2937636
443ae5e
70314d0
cd2aec4
db9cde6
e467049
332df8b
82da615
ee9c40f
7f27969
937cd9b
fb00bef
139c1f5
28ddccd
5fbcfc5
8ad8ffd
5f0e199
ad2f965
f36f9de
d2fa204
8857ae0
99767fa
dcae003
545db0b
b9fa5f9
c2d7b87
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,16 +37,6 @@ | |
|
||
_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' | ||
|
||
|
@@ -171,25 +161,56 @@ 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how to accommodate air-gapped cloud? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we are going to support There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it necessary to call it out in az upgrade or cloud endpoint discovery design spec? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, will add to cloud endpoint discovery design spec. |
||
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 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): | ||
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) | ||
versions, success = _update_latest_from_github(versions) | ||
if success: | ||
VERSIONS['versions'] = versions | ||
VERSIONS[_VERSION_UPDATE_TIME] = str(datetime.datetime.now()) | ||
|
@@ -286,12 +307,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): | ||
|
@@ -314,34 +332,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.') | ||
|
@@ -1036,3 +1027,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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,6 +71,43 @@ def cli_main(cli, args): | |
except NameError: | ||
pass | ||
|
||
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'): | ||
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') | ||
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.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A warning should be ok right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A warning will not impact the command result for use in script. |
||
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 prompt: | ||
exit_code = subprocess.call(cmd, shell=platform.system() == 'Windows') | ||
else: | ||
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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this error out or silently swallow? will this func be used other than az upgrade?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is not used in
az upgrade
now as we always update to the latest extension version. This error happens when users type the wrong extension name.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. I saw it used by list-available-version, so error out is ok. My original point is that az upgrade internal failure should not error out which might impact user in silent upgrading scenario.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In silent upgrading scenario (auto-upgrade with no prompt), the exit code and
stdout
of a command will not be affected, but warnings and errors fromaz upgrade
will be shown instderr
.