Skip to content

Commit

Permalink
Preview extension notation & Simple default view for extension list-a… (
Browse files Browse the repository at this point in the history
Azure#5882)

* Preview extension notation & Simple default view for extension list-available

- Show message for extensions marked as preview on -h

* Update HISTORY.rst

* Add more tests

* fix style
  • Loading branch information
derekbekoe authored Mar 22, 2018
1 parent 28ede75 commit e4339a7
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 18 deletions.
5 changes: 5 additions & 0 deletions doc/extensions/metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,9 @@ Type: `string`

Example: `"azext.maxCliCoreVersion": "2.0.15"`

### azext.isPreview
Description: Indicate that the extension is in preview.

Type: `boolean`

Example: `"azext.isPreview": true`
2 changes: 1 addition & 1 deletion src/azure-cli-core/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Release History

2.0.30
++++++
* Minor fixes
* Show message for extensions marked as preview on -h.

2.0.29
++++++
Expand Down
6 changes: 4 additions & 2 deletions src/azure-cli-core/azure/cli/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ def _handle_extension_suppressions(extensions):
if extensions:
logger.debug("Found %s extensions: %s", len(extensions), [e.name for e in extensions])
allowed_extensions = _handle_extension_suppressions(extensions)
for ext_name in [e.name for e in allowed_extensions]:
for ext in allowed_extensions:
ext_name = ext.name
ext_dir = get_extension_path(ext_name)
sys.path.append(ext_dir)
try:
Expand All @@ -177,7 +178,8 @@ def _handle_extension_suppressions(extensions):
for cmd_name, cmd in extension_command_table.items():
cmd.command_source = ExtensionCommandSource(
extension_name=ext_name,
overrides_command=cmd_name in cmd_to_mod_map)
overrides_command=cmd_name in cmd_to_mod_map,
preview=ext.preview)

self.command_table.update(extension_command_table)
elapsed_time = timeit.default_timer() - start_time
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 @@ -56,6 +56,8 @@ def _print_extensions_msg(help_file):
return
if help_file.command_source and isinstance(help_file.command_source, ExtensionCommandSource):
logger.warning(help_file.command_source.get_command_warn_msg())
if help_file.command_source.preview:
logger.warning(help_file.command_source.get_preview_warn_msg())

@classmethod
def print_detailed_help(cls, cli_name, help_file):
Expand Down
8 changes: 7 additions & 1 deletion src/azure-cli-core/azure/cli/core/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,11 +568,12 @@ def _load_module_command_loader(loader, args, mod):
class ExtensionCommandSource(object):
""" Class for commands contributed by an extension """

def __init__(self, overrides_command=False, extension_name=None):
def __init__(self, overrides_command=False, extension_name=None, preview=False):
super(ExtensionCommandSource, self).__init__()
# True if the command overrides a CLI command
self.overrides_command = overrides_command
self.extension_name = extension_name
self.preview = preview

def get_command_warn_msg(self):
if self.overrides_command:
Expand All @@ -585,6 +586,11 @@ def get_command_warn_msg(self):
return "This command is from the following extension: {}".format(self.extension_name)
return "This command is from an extension."

def get_preview_warn_msg(self):
if self.preview:
return "The extension is in preview"
return None


def _load_client_exception_class():
# Since loading msrest is expensive, we avoid it until we have to
Expand Down
15 changes: 15 additions & 0 deletions src/azure-cli-core/azure/cli/core/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

EXT_METADATA_MINCLICOREVERSION = 'azext.minCliCoreVersion'
EXT_METADATA_MAXCLICOREVERSION = 'azext.maxCliCoreVersion'
EXT_METADATA_ISPREVIEW = 'azext.isPreview'

logger = get_logger(__name__)

Expand All @@ -42,6 +43,7 @@ def __init__(self, name, ext_type):
self.ext_type = ext_type
self._version = None
self._metadata = None
self._preview = None

@property
def version(self):
Expand All @@ -67,6 +69,19 @@ def metadata(self):
logger.debug("Unable to get extension metadata: %s", traceback.format_exc())
return self._metadata

@property
def preview(self):
"""
Lazy load preview status.
Returns the preview status of the extension.
"""
try:
if not isinstance(self._preview, bool):
self._preview = bool(self.metadata.get(EXT_METADATA_ISPREVIEW))
except Exception: # pylint: disable=broad-except
logger.debug("Unable to get extension preview status: %s", traceback.format_exc())
return self._preview

def get_version(self):
raise NotImplementedError()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ def _mock_extension_modname(ext_name, ext_dir):
return ext_name

def _mock_get_extensions():
MockExtension = namedtuple('Extension', ['name'])
return [MockExtension(name=__name__ + '.ExtCommandsLoader'),
MockExtension(name=__name__ + '.Ext2CommandsLoader')]
MockExtension = namedtuple('Extension', ['name', 'preview'])
return [MockExtension(name=__name__ + '.ExtCommandsLoader', preview=False),
MockExtension(name=__name__ + '.Ext2CommandsLoader', preview=False)]

def _mock_load_command_loader(loader, args, name, prefix):

Expand Down
4 changes: 3 additions & 1 deletion src/command_modules/azure-cli-extension/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ Release History

0.0.11
++++++
* Minor fixes
* Preview extensions: Show message on `az extension add` if extension is in preview
* BC: `az extension list-available` - The full extension data is now available with `--show-details`
* `az extension list-available` - A simplified view of the extensions available is now shown by default

0.0.10
+++++++
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ def ext_add_has_confirmed(command_args):
return bool(not command_args.get('source') or prompt_y_n('Are you sure you want to install this extension?'))

def transform_extension_list_available(results):
return [OrderedDict([('Name', r)]) for r in results]
if isinstance(results, dict):
# For --show-details, transform the table
return [OrderedDict([('Name', r)]) for r in results]
return results

def validate_extension_add(namespace):
if (namespace.extension_name and namespace.source) or (not namespace.extension_name and not namespace.source):
Expand Down Expand Up @@ -67,5 +70,8 @@ def load_arguments(self, command):
c.argument('source', options_list=['--source', '-s'], help='Filepath or URL to an extension', completer=FilesCompleter())
c.argument('yes', options_list=['--yes', '-y'], action='store_true', help='Do not prompt for confirmation.')

with self.argument_context('extension list-available') as c:
c.argument('show_details', options_list=['--show-details', '-d'], action='store_true', help='Show the raw data from the extension index.')


COMMAND_LOADER_CLS = ExtensionCommandsLoader
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@
import hashlib
from subprocess import check_output, STDOUT, CalledProcessError
from six.moves.urllib.parse import urlparse # pylint: disable=import-error
from collections import OrderedDict

import requests
from wheel.install import WHEEL_INFO_RE
from pkg_resources import parse_version

from knack.log import get_logger

from azure.cli.core.util import CLIError
from azure.cli.core.extension import (extension_exists, get_extension_path, get_extensions,
get_extension, ext_compat_with_cli,
get_extension, ext_compat_with_cli, EXT_METADATA_ISPREVIEW,
WheelExtension, ExtensionNotInstalledException)
from azure.cli.core.telemetry import set_extension_management_detail

Expand Down Expand Up @@ -200,6 +203,11 @@ def add_extension(source=None, extension_name=None, index_url=None, yes=None, #
raise CLIError("No matching extensions for '{}'. Use --debug for more information.".format(extension_name))
_add_whl_ext(source, ext_sha256=ext_sha256, pip_extra_index_urls=pip_extra_index_urls, pip_proxy=pip_proxy)
_augment_telemetry_with_ext_info(extension_name)
try:
if extension_name and get_extension(extension_name).preview:
logger.warning("The installed extension '%s' is in preview.", extension_name)
except ExtensionNotInstalledException:
pass


def remove_extension(extension_name):
Expand Down Expand Up @@ -263,8 +271,28 @@ def update_extension(extension_name, index_url=None, pip_extra_index_urls=None,
raise CLIError(e)


def list_available_extensions(index_url=None):
return get_index_extensions(index_url=index_url)
def list_available_extensions(index_url=None, show_details=False):
index_data = get_index_extensions(index_url=index_url)
if show_details:
return index_data
installed_extensions = get_extensions()
installed_extension_names = [e.name for e in installed_extensions]
results = []
for name, items in OrderedDict(sorted(index_data.items())).items():
latest = sorted(items, key=lambda c: parse_version(c['metadata']['version']), reverse=True)[0]
installed = False
if name in installed_extension_names:
installed = True
if parse_version(latest['metadata']['version']) > parse_version(get_extension(name).version):
installed = str(True) + ' (upgrade available)'
results.append({
'name': name,
'version': latest['metadata']['version'],
'summary': latest['metadata']['summary'],
'preview': latest['metadata'].get(EXT_METADATA_ISPREVIEW, False),
'installed': installed
})
return results


def get_lsb_release():
Expand Down Expand Up @@ -296,14 +324,14 @@ def check_distro_consistency():
except Exception as err: # pylint: disable=broad-except
current_linux_dist_name = None
stored_linux_dist_name = None
logger.debug('Linux distro check: An error occurred while checking \
linux distribution version source list consistency.')
logger.debug('Linux distro check: An error occurred while checking '
'linux distribution version source list consistency.')
logger.debug(err)

if current_linux_dist_name != stored_linux_dist_name:
logger.warning("Linux distro check: Mismatch distribution \
name in %s file", LIST_FILE_PATH)
logger.warning("Linux distro check: If command fails, install the appropriate package \
for your distribution or change the above file accordingly.")
logger.warning("Linux distro check: Mismatch distribution "
"name in %s file", LIST_FILE_PATH)
logger.warning("Linux distro check: If command fails, install the appropriate package "
"for your distribution or change the above file accordingly.")
logger.warning("Linux distro check: %s has '%s', current distro is '%s'",
LIST_FILE_PATH, stored_linux_dist_name, current_linux_dist_name)
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,29 @@ def test_list_available_extensions_custom_index_url(self):
list_available_extensions(index_url=index_url)
c.assert_called_once_with(index_url)

def test_list_available_extensions_show_details(self):
with mock.patch('azure.cli.command_modules.extension.custom.get_index_extensions', autospec=True) as c:
list_available_extensions(show_details=True)
c.assert_called_once_with(None)

def test_list_available_extensions_no_show_details(self):
sample_index_extensions = {
'test_sample_extension1': [{
'metadata': {
'name': 'test_sample_extension1',
'summary': 'my summary',
'version': '0.1.0'
}}]
}
with mock.patch('azure.cli.command_modules.extension.custom.get_index_extensions', return_value=sample_index_extensions):
res = list_available_extensions()
self.assertIsInstance(res, list)
self.assertEqual(len(res), len(sample_index_extensions))
self.assertEqual(res[0]['name'], 'test_sample_extension1')
self.assertEqual(res[0]['summary'], 'my summary')
self.assertEqual(res[0]['version'], '0.1.0')
self.assertEqual(res[0]['preview'], False)

def test_add_list_show_remove_extension_extra_index_url(self):
"""
Tests extension addition while specifying --extra-index-url parameter.
Expand Down

0 comments on commit e4339a7

Please sign in to comment.