Alias 0.5.0 (#131)
* Alias 0.5.0 * Fix CI Error * Fix CI Error * Incorporate positional argument feature from core * Address PR comments
This commit is contained in:
Родитель
482dadd0d1
Коммит
70d1715872
|
@ -3,6 +3,8 @@
|
|||
# Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
from argcomplete.completers import FilesCompleter # pylint: disable=import-error
|
||||
|
||||
from azure.cli.core import AzCommandsLoader
|
||||
from azure.cli.core.decorators import Completer
|
||||
from azure.cli.core.commands.events import EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE, EVENT_INVOKER_ON_TAB_COMPLETION
|
||||
|
@ -10,6 +12,13 @@ from azure.cli.command_modules.interactive.events import (
|
|||
EVENT_INTERACTIVE_PRE_COMPLETER_TEXT_PARSING,
|
||||
EVENT_INTERACTIVE_POST_SUB_TREE_CREATE
|
||||
)
|
||||
|
||||
from azext_alias.util import get_alias_table
|
||||
from azext_alias._validators import (
|
||||
process_alias_create_namespace,
|
||||
process_alias_import_namespace,
|
||||
process_alias_export_namespace
|
||||
)
|
||||
from azext_alias import _help # pylint: disable=unused-import
|
||||
from azext_alias.hooks import (
|
||||
alias_event_handler,
|
||||
|
@ -17,8 +26,6 @@ from azext_alias.hooks import (
|
|||
transform_cur_commands_interactive,
|
||||
enable_aliases_autocomplete_interactive
|
||||
)
|
||||
from azext_alias.util import get_alias_table
|
||||
from azext_alias._validators import process_alias_create_namespace
|
||||
|
||||
|
||||
# We don't have access to load_cmd_tbl_func in custom.py (need the entire command table
|
||||
|
@ -41,10 +48,15 @@ class AliasExtCommandLoader(AzCommandsLoader):
|
|||
self.cli_ctx.register_event(EVENT_INTERACTIVE_POST_SUB_TREE_CREATE, enable_aliases_autocomplete_interactive)
|
||||
|
||||
def load_command_table(self, _):
|
||||
|
||||
with self.command_group('alias') as g:
|
||||
g.custom_command('create', 'create_alias', validator=process_alias_create_namespace)
|
||||
g.custom_command('export', 'export_aliases', validator=process_alias_export_namespace)
|
||||
g.custom_command('import', 'import_aliases', validator=process_alias_import_namespace)
|
||||
g.custom_command('list', 'list_alias')
|
||||
g.custom_command('remove', 'remove_alias')
|
||||
g.custom_command('remove-all', 'remove_all_aliases',
|
||||
confirmation='Are you sure you want to remove all registered aliases?')
|
||||
|
||||
return self.command_table
|
||||
|
||||
|
@ -53,9 +65,19 @@ class AliasExtCommandLoader(AzCommandsLoader):
|
|||
c.argument('alias_name', options_list=['--name', '-n'], help='The name of the alias.')
|
||||
c.argument('alias_command', options_list=['--command', '-c'], help='The command that the alias points to.')
|
||||
|
||||
with self.argument_context('alias export') as c:
|
||||
c.argument('export_path', options_list=['--path', '-p'],
|
||||
help='The path of the alias configuration file to export to', completer=FilesCompleter())
|
||||
c.argument('exclusions', options_list=['--exclude', '-e'],
|
||||
help='Space-separated aliases excluded from export', completer=get_alias_completer, nargs='*')
|
||||
|
||||
with self.argument_context('alias import') as c:
|
||||
c.argument('alias_source', options_list=['--source', '-s'],
|
||||
help='The source of the aliases to import from.', completer=FilesCompleter())
|
||||
|
||||
with self.argument_context('alias remove') as c:
|
||||
c.argument('alias_name', options_list=['--name', '-n'], help='The name of the alias.',
|
||||
completer=get_alias_completer)
|
||||
c.argument('alias_names', options_list=['--name', '-n'], help='Space-separated aliases',
|
||||
completer=get_alias_completer, nargs='*')
|
||||
|
||||
|
||||
@Completer
|
||||
|
|
|
@ -16,13 +16,13 @@ GLOBAL_ALIAS_TAB_COMP_TABLE_PATH = os.path.join(GLOBAL_CONFIG_DIR, ALIAS_TAB_COM
|
|||
COLLISION_CHECK_LEVEL_DEPTH = 5
|
||||
|
||||
INSUFFICIENT_POS_ARG_ERROR = 'alias: "{}" takes exactly {} positional argument{} ({} given)'
|
||||
CONFIG_PARSING_ERROR = 'alias: Error parsing the configuration file - %s. Please fix the problem manually.'
|
||||
CONFIG_PARSING_ERROR = 'alias: Please ensure you have a valid alias configuration file. Error detail: %s'
|
||||
DEBUG_MSG = 'Alias Manager: Transforming "%s" to "%s"'
|
||||
DEBUG_MSG_WITH_TIMING = 'Alias Manager: Transformed args to %s in %.3fms'
|
||||
POS_ARG_DEBUG_MSG = 'Alias Manager: Transforming "%s" to "%s", with the following positional arguments: %s'
|
||||
DUPLICATED_PLACEHOLDER_ERROR = 'alias: Duplicated placeholders found when transforming "{}"'
|
||||
RENDER_TEMPLATE_ERROR = 'alias: Encounted the following error when injecting positional arguments to "{}" - {}'
|
||||
PLACEHOLDER_EVAL_ERROR = 'alias: Encounted the following error when evaluating "{}" - {}'
|
||||
RENDER_TEMPLATE_ERROR = 'alias: Encounted error when injecting positional arguments to "{}". Error detail: {}'
|
||||
PLACEHOLDER_EVAL_ERROR = 'alias: Encounted error when evaluating "{}". Error detail: {}'
|
||||
PLACEHOLDER_BRACKETS_ERROR = 'alias: Brackets in "{}" are not enclosed properly'
|
||||
ALIAS_NOT_FOUND_ERROR = 'alias: "{}" alias not found'
|
||||
INVALID_ALIAS_COMMAND_ERROR = 'alias: Invalid Azure CLI command "{}"'
|
||||
|
@ -30,3 +30,8 @@ EMPTY_ALIAS_ERROR = 'alias: Empty alias name or command is invalid'
|
|||
INVALID_STARTING_CHAR_ERROR = 'alias: Alias name should not start with "{}"'
|
||||
INCONSISTENT_ARG_ERROR = 'alias: Positional argument{} {} {} not in both alias name and alias command'
|
||||
COMMAND_LVL_ERROR = 'alias: "{}" is a reserved command and cannot be used to represent "{}"'
|
||||
ALIAS_FILE_NOT_FOUND_ERROR = 'alias: File not found'
|
||||
ALIAS_FILE_DIR_ERROR = 'alias: {} is a directory'
|
||||
ALIAS_FILE_URL_ERROR = 'alias: Encounted error when retrieving alias file from {}. Error detail: {}'
|
||||
POST_EXPORT_ALIAS_MSG = 'alias: Exported alias configuration file to %s.'
|
||||
FILE_ALREADY_EXISTS_ERROR = 'alias: {} already exists.'
|
||||
|
|
|
@ -39,6 +39,18 @@ helps['alias create'] = """
|
|||
"""
|
||||
|
||||
|
||||
helps['alias export'] = """
|
||||
type: command
|
||||
short-summary: Export all registered aliases to a given path, as an INI configuration file.
|
||||
"""
|
||||
|
||||
|
||||
helps['alias import'] = """
|
||||
type: command
|
||||
short-summary: Import aliases from an INI configuration file or an URL.
|
||||
"""
|
||||
|
||||
|
||||
helps['alias list'] = """
|
||||
type: command
|
||||
short-summary: List the registered aliases.
|
||||
|
@ -47,5 +59,11 @@ helps['alias list'] = """
|
|||
|
||||
helps['alias remove'] = """
|
||||
type: command
|
||||
short-summary: Remove an alias.
|
||||
short-summary: Remove one or more aliases. Aliases to be removed are space-delimited.
|
||||
"""
|
||||
|
||||
|
||||
helps['alias remove-all'] = """
|
||||
type: command
|
||||
short-summary: Remove all registered aliases.
|
||||
"""
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
|
||||
|
@ -10,13 +11,25 @@ from knack.util import CLIError
|
|||
|
||||
import azext_alias
|
||||
from azext_alias.argument import get_placeholders
|
||||
from azext_alias.util import (
|
||||
get_config_parser,
|
||||
is_url,
|
||||
reduce_alias_table,
|
||||
filter_alias_create_namespace,
|
||||
retrieve_file_from_url
|
||||
)
|
||||
from azext_alias._const import (
|
||||
COLLISION_CHECK_LEVEL_DEPTH,
|
||||
INVALID_ALIAS_COMMAND_ERROR,
|
||||
EMPTY_ALIAS_ERROR,
|
||||
INVALID_STARTING_CHAR_ERROR,
|
||||
INCONSISTENT_ARG_ERROR,
|
||||
COMMAND_LVL_ERROR
|
||||
COMMAND_LVL_ERROR,
|
||||
CONFIG_PARSING_ERROR,
|
||||
ALIAS_FILE_NOT_FOUND_ERROR,
|
||||
ALIAS_FILE_DIR_ERROR,
|
||||
FILE_ALREADY_EXISTS_ERROR,
|
||||
ALIAS_FILE_NAME
|
||||
)
|
||||
from azext_alias.alias import AliasManager
|
||||
|
||||
|
@ -28,12 +41,49 @@ def process_alias_create_namespace(namespace):
|
|||
Args:
|
||||
namespace: argparse namespace object.
|
||||
"""
|
||||
namespace = filter_alias_create_namespace(namespace)
|
||||
_validate_alias_name(namespace.alias_name)
|
||||
_validate_alias_command(namespace.alias_command)
|
||||
_validate_alias_command_level(namespace.alias_name, namespace.alias_command)
|
||||
_validate_pos_args_syntax(namespace.alias_name, namespace.alias_command)
|
||||
|
||||
|
||||
def process_alias_import_namespace(namespace):
|
||||
"""
|
||||
Validate input arguments when the user invokes 'az alias import'.
|
||||
|
||||
Args:
|
||||
namespace: argparse namespace object.
|
||||
"""
|
||||
if is_url(namespace.alias_source):
|
||||
alias_source = retrieve_file_from_url(namespace.alias_source)
|
||||
|
||||
_validate_alias_file_content(alias_source, url=namespace.alias_source)
|
||||
else:
|
||||
namespace.alias_source = os.path.abspath(namespace.alias_source)
|
||||
_validate_alias_file_path(namespace.alias_source)
|
||||
_validate_alias_file_content(namespace.alias_source)
|
||||
|
||||
|
||||
def process_alias_export_namespace(namespace):
|
||||
"""
|
||||
Validate input arguments when the user invokes 'az alias export'.
|
||||
|
||||
Args:
|
||||
namespace: argparse namespace object.
|
||||
"""
|
||||
namespace.export_path = os.path.abspath(namespace.export_path)
|
||||
if os.path.isfile(namespace.export_path):
|
||||
raise CLIError(FILE_ALREADY_EXISTS_ERROR.format(namespace.export_path))
|
||||
|
||||
export_path_dir = os.path.dirname(namespace.export_path)
|
||||
if not os.path.isdir(export_path_dir):
|
||||
os.makedirs(export_path_dir)
|
||||
|
||||
if os.path.isdir(namespace.export_path):
|
||||
namespace.export_path = os.path.join(namespace.export_path, ALIAS_FILE_NAME)
|
||||
|
||||
|
||||
def _validate_alias_name(alias_name):
|
||||
"""
|
||||
Check if the alias name is valid.
|
||||
|
@ -58,7 +108,6 @@ def _validate_alias_command(alias_command):
|
|||
if not alias_command:
|
||||
raise CLIError(EMPTY_ALIAS_ERROR)
|
||||
|
||||
# Boundary index is the index at which named argument or positional argument starts
|
||||
split_command = shlex.split(alias_command)
|
||||
boundary_index = len(split_command)
|
||||
for i, subcommand in enumerate(split_command):
|
||||
|
@ -72,7 +121,7 @@ def _validate_alias_command(alias_command):
|
|||
if re.match(r'([a-z\-]*\s)*{}($|\s)'.format(command_to_validate), command):
|
||||
return
|
||||
|
||||
raise CLIError(INVALID_ALIAS_COMMAND_ERROR.format(command_to_validate if command_to_validate else alias_command))
|
||||
_validate_positional_arguments(shlex.split(alias_command))
|
||||
|
||||
|
||||
def _validate_pos_args_syntax(alias_name, alias_command):
|
||||
|
@ -120,3 +169,71 @@ def _validate_alias_command_level(alias, command):
|
|||
# Check if there is a command level conflict
|
||||
if set(alias_collision_levels) & set(command_collision_levels):
|
||||
raise CLIError(COMMAND_LVL_ERROR.format(alias, command))
|
||||
|
||||
|
||||
def _validate_alias_file_path(alias_file_path):
|
||||
"""
|
||||
Make sure the alias file path is neither non-existant nor a directory
|
||||
|
||||
Args:
|
||||
The alias file path to import aliases from.
|
||||
"""
|
||||
if not os.path.exists(alias_file_path):
|
||||
raise CLIError(ALIAS_FILE_NOT_FOUND_ERROR)
|
||||
|
||||
if os.path.isdir(alias_file_path):
|
||||
raise CLIError(ALIAS_FILE_DIR_ERROR.format(alias_file_path))
|
||||
|
||||
|
||||
def _validate_alias_file_content(alias_file_path, url=''):
|
||||
"""
|
||||
Make sure the alias name and alias command in the alias file is in valid format.
|
||||
|
||||
Args:
|
||||
The alias file path to import aliases from.
|
||||
"""
|
||||
alias_table = get_config_parser()
|
||||
try:
|
||||
alias_table.read(alias_file_path)
|
||||
for alias_name, alias_command in reduce_alias_table(alias_table):
|
||||
_validate_alias_name(alias_name)
|
||||
_validate_alias_command(alias_command)
|
||||
_validate_alias_command_level(alias_name, alias_command)
|
||||
_validate_pos_args_syntax(alias_name, alias_command)
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
error_msg = CONFIG_PARSING_ERROR % AliasManager.process_exception_message(exception)
|
||||
error_msg = error_msg.replace(alias_file_path, url or alias_file_path)
|
||||
raise CLIError(error_msg)
|
||||
|
||||
|
||||
def _validate_positional_arguments(args):
|
||||
"""
|
||||
To validate the positional argument feature - https://github.com/Azure/azure-cli/pull/6055.
|
||||
Assuming that unknown commands are positional arguments immediately
|
||||
led by words that only appear at the end of the commands
|
||||
|
||||
Slight modification of
|
||||
https://github.com/Azure/azure-cli/blob/dev/src/azure-cli-core/azure/cli/core/commands/__init__.py#L356-L373
|
||||
|
||||
Args:
|
||||
args: The arguments that the user inputs in the terminal.
|
||||
|
||||
Returns:
|
||||
Rudimentary parsed arguments.
|
||||
"""
|
||||
nouns = []
|
||||
for arg in args:
|
||||
if not arg.startswith('-') or not arg.startswith('{{'):
|
||||
nouns.append(arg)
|
||||
else:
|
||||
break
|
||||
|
||||
while nouns:
|
||||
search = ' '.join(nouns)
|
||||
# Since the command name may be immediately followed by a positional arg, strip those off
|
||||
if not next((x for x in azext_alias.cached_reserved_commands if x.endswith(search)), False):
|
||||
del nouns[-1]
|
||||
else:
|
||||
return
|
||||
|
||||
raise CLIError(INVALID_ALIAS_COMMAND_ERROR.format(' '.join(args)))
|
||||
|
|
|
@ -26,7 +26,7 @@ from azext_alias._const import (
|
|||
)
|
||||
from azext_alias.argument import build_pos_args_table, render_template
|
||||
from azext_alias.util import (
|
||||
is_alias_create_command,
|
||||
is_alias_command,
|
||||
cache_reserved_commands,
|
||||
get_config_parser,
|
||||
build_tab_completion_table
|
||||
|
@ -139,7 +139,8 @@ class AliasManager(object):
|
|||
# index - 2 because alias_iter starts counting at index 1
|
||||
is_named_arg = alias_index > 1 and args[alias_index - 2].startswith('-')
|
||||
is_named_arg_flag = alias.startswith('-')
|
||||
if not alias or is_collided_alias or is_named_arg or is_named_arg_flag:
|
||||
excluded_commands = is_alias_command(['remove', 'export'], transformed_commands)
|
||||
if not alias or is_collided_alias or is_named_arg or is_named_arg_flag or excluded_commands:
|
||||
transformed_commands.append(alias)
|
||||
continue
|
||||
|
||||
|
@ -202,7 +203,7 @@ class AliasManager(object):
|
|||
post_transform_commands = []
|
||||
for i, arg in enumerate(args):
|
||||
# Do not translate environment variables for command argument
|
||||
if is_alias_create_command(args) and i > 0 and args[i - 1] in ['-c', '--command']:
|
||||
if is_alias_command(['create'], args) and i > 0 and args[i - 1] in ['-c', '--command']:
|
||||
post_transform_commands.append(arg)
|
||||
else:
|
||||
post_transform_commands.append(os.path.expandvars(arg))
|
||||
|
|
|
@ -3,13 +3,23 @@
|
|||
# Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
|
||||
from knack.util import CLIError
|
||||
from knack.log import get_logger
|
||||
|
||||
from azext_alias._const import ALIAS_NOT_FOUND_ERROR
|
||||
from azext_alias._const import ALIAS_NOT_FOUND_ERROR, POST_EXPORT_ALIAS_MSG, ALIAS_FILE_NAME
|
||||
from azext_alias.alias import GLOBAL_ALIAS_PATH, AliasManager
|
||||
from azext_alias.util import get_alias_table, build_tab_completion_table
|
||||
from azext_alias.util import (
|
||||
get_alias_table,
|
||||
is_url,
|
||||
build_tab_completion_table,
|
||||
get_config_parser,
|
||||
retrieve_file_from_url
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def create_alias(alias_name, alias_command):
|
||||
|
@ -29,6 +39,41 @@ def create_alias(alias_name, alias_command):
|
|||
_commit_change(alias_table)
|
||||
|
||||
|
||||
def export_aliases(export_path=os.path.abspath(ALIAS_FILE_NAME), exclusions=None):
|
||||
"""
|
||||
Export all registered aliases to a given path, as an INI configuration file.
|
||||
|
||||
Args:
|
||||
export_path: The path of the alias configuration file to export to.
|
||||
exclusions: Space-separated aliases excluded from export.
|
||||
"""
|
||||
alias_table = get_alias_table()
|
||||
for exclusion in exclusions or []:
|
||||
if exclusion not in alias_table.sections():
|
||||
raise CLIError(ALIAS_NOT_FOUND_ERROR.format(exclusion))
|
||||
alias_table.remove_section(exclusion)
|
||||
|
||||
_commit_change(alias_table, export_path=export_path, post_commit=False)
|
||||
logger.warning(POST_EXPORT_ALIAS_MSG, export_path) # pylint: disable=superfluous-parens
|
||||
|
||||
|
||||
def import_aliases(alias_source):
|
||||
"""
|
||||
Import aliases from a file or an URL.
|
||||
|
||||
Args:
|
||||
alias_source: The source of the alias. It can be a filepath or an URL.
|
||||
"""
|
||||
alias_table = get_alias_table()
|
||||
if is_url(alias_source):
|
||||
alias_source = retrieve_file_from_url(alias_source)
|
||||
alias_table.read(alias_source)
|
||||
os.remove(alias_source)
|
||||
else:
|
||||
alias_table.read(alias_source)
|
||||
_commit_change(alias_table)
|
||||
|
||||
|
||||
def list_alias():
|
||||
"""
|
||||
List all registered aliases.
|
||||
|
@ -49,7 +94,7 @@ def list_alias():
|
|||
return output
|
||||
|
||||
|
||||
def remove_alias(alias_name):
|
||||
def remove_alias(alias_names):
|
||||
"""
|
||||
Remove an alias.
|
||||
|
||||
|
@ -57,26 +102,36 @@ def remove_alias(alias_name):
|
|||
alias_name: The name of the alias to be removed.
|
||||
"""
|
||||
alias_table = get_alias_table()
|
||||
if alias_name not in alias_table.sections():
|
||||
raise CLIError(ALIAS_NOT_FOUND_ERROR.format(alias_name))
|
||||
alias_table.remove_section(alias_name)
|
||||
for alias_name in alias_names:
|
||||
if alias_name not in alias_table.sections():
|
||||
raise CLIError(ALIAS_NOT_FOUND_ERROR.format(alias_name))
|
||||
alias_table.remove_section(alias_name)
|
||||
_commit_change(alias_table)
|
||||
|
||||
|
||||
def _commit_change(alias_table):
|
||||
def remove_all_aliases():
|
||||
"""
|
||||
Remove all registered aliases.
|
||||
"""
|
||||
_commit_change(get_config_parser())
|
||||
|
||||
|
||||
def _commit_change(alias_table, export_path=None, post_commit=True):
|
||||
"""
|
||||
Record changes to the alias table.
|
||||
Also write new alias config hash and collided alias, if any.
|
||||
|
||||
Args:
|
||||
alias_table: The alias table to commit.
|
||||
export_path: The path to export the aliases to. Default: GLOBAL_ALIAS_PATH.
|
||||
post_commit: True if we want to perform some extra actions after writing alias to file.
|
||||
"""
|
||||
with open(GLOBAL_ALIAS_PATH, 'w+') as alias_config_file:
|
||||
with open(export_path or GLOBAL_ALIAS_PATH, 'w+') as alias_config_file:
|
||||
alias_table.write(alias_config_file)
|
||||
alias_config_file.seek(0)
|
||||
alias_config_hash = hashlib.sha1(alias_config_file.read().encode('utf-8')).hexdigest()
|
||||
AliasManager.write_alias_config_hash(alias_config_hash)
|
||||
|
||||
collided_alias = AliasManager.build_collision_table(alias_table.sections())
|
||||
AliasManager.write_collided_alias(collided_alias)
|
||||
build_tab_completion_table(alias_table)
|
||||
if post_commit:
|
||||
alias_config_file.seek(0)
|
||||
alias_config_hash = hashlib.sha1(alias_config_file.read().encode('utf-8')).hexdigest()
|
||||
AliasManager.write_alias_config_hash(alias_config_hash)
|
||||
collided_alias = AliasManager.build_collision_table(alias_table.sections())
|
||||
AliasManager.write_collided_alias(collided_alias)
|
||||
build_tab_completion_table(alias_table)
|
||||
|
|
|
@ -12,7 +12,7 @@ from azure.cli.command_modules.interactive.azclishell.command_tree import Comman
|
|||
from azext_alias import telemetry
|
||||
from azext_alias.alias import AliasManager
|
||||
from azext_alias.util import (
|
||||
is_alias_create_command,
|
||||
is_alias_command,
|
||||
cache_reserved_commands,
|
||||
get_alias_table,
|
||||
filter_aliases
|
||||
|
@ -36,7 +36,7 @@ def alias_event_handler(_, **kwargs):
|
|||
# [:] will keep the reference of the original args
|
||||
args[:] = alias_manager.transform(args)
|
||||
|
||||
if is_alias_create_command(args):
|
||||
if is_alias_command(['create', 'import'], args):
|
||||
load_cmd_tbl_func = kwargs.get('load_cmd_tbl_func', lambda _: {})
|
||||
cache_reserved_commands(load_cmd_tbl_func)
|
||||
|
||||
|
|
|
@ -84,4 +84,5 @@ aodfgojadofgjaojdfog
|
|||
|
||||
TEST_RESERVED_COMMANDS = ['account list-locations',
|
||||
'network dns',
|
||||
'storage account create']
|
||||
'storage account create',
|
||||
'group delete']
|
||||
|
|
|
@ -9,7 +9,7 @@ import os
|
|||
import sys
|
||||
import shlex
|
||||
import unittest
|
||||
from mock import Mock
|
||||
from mock import Mock, patch
|
||||
from six.moves import configparser
|
||||
|
||||
from knack.util import CLIError
|
||||
|
@ -91,7 +91,7 @@ def test_transform_alias(self, test_case):
|
|||
|
||||
|
||||
def test_transform_collided_alias(self, test_case):
|
||||
alias_manager = self.get_alias_manager(COLLISION_MOCK_ALIAS_STRING, TEST_RESERVED_COMMANDS)
|
||||
alias_manager = self.get_alias_manager(COLLISION_MOCK_ALIAS_STRING)
|
||||
alias_manager.collided_alias = azext_alias.alias.AliasManager.build_collision_table(alias_manager.alias_table.sections())
|
||||
self.assertEqual(shlex.split(test_case[1]), alias_manager.transform(shlex.split(test_case[0])))
|
||||
|
||||
|
@ -147,18 +147,22 @@ TEST_FN = {
|
|||
|
||||
class TestAlias(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUp(cls):
|
||||
def setUp(self):
|
||||
azext_alias.alias.AliasManager.write_alias_config_hash = Mock()
|
||||
azext_alias.alias.AliasManager.write_collided_alias = Mock()
|
||||
self.patcher = patch('azext_alias.cached_reserved_commands', TEST_RESERVED_COMMANDS)
|
||||
self.patcher.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.patcher.stop()
|
||||
|
||||
def test_build_empty_collision_table(self):
|
||||
alias_manager = self.get_alias_manager(DEFAULT_MOCK_ALIAS_STRING, TEST_RESERVED_COMMANDS)
|
||||
alias_manager = self.get_alias_manager(DEFAULT_MOCK_ALIAS_STRING)
|
||||
test_case = azext_alias.alias.AliasManager.build_collision_table(alias_manager.alias_table.sections())
|
||||
self.assertDictEqual(dict(), test_case)
|
||||
|
||||
def test_build_non_empty_collision_table(self):
|
||||
alias_manager = self.get_alias_manager(COLLISION_MOCK_ALIAS_STRING, TEST_RESERVED_COMMANDS)
|
||||
alias_manager = self.get_alias_manager(COLLISION_MOCK_ALIAS_STRING)
|
||||
test_case = azext_alias.alias.AliasManager.build_collision_table(alias_manager.alias_table.sections(), levels=2)
|
||||
self.assertDictEqual({'account': [1, 2], 'dns': [2], 'list-locations': [2]}, test_case)
|
||||
|
||||
|
@ -179,9 +183,8 @@ class TestAlias(unittest.TestCase):
|
|||
"""
|
||||
Helper functions
|
||||
"""
|
||||
def get_alias_manager(self, mock_alias_str=DEFAULT_MOCK_ALIAS_STRING, reserved_commands=None):
|
||||
def get_alias_manager(self, mock_alias_str=DEFAULT_MOCK_ALIAS_STRING):
|
||||
alias_manager = MockAliasManager(mock_alias_str=mock_alias_str)
|
||||
azext_alias.cached_reserved_commands = reserved_commands if reserved_commands else []
|
||||
return alias_manager
|
||||
|
||||
def assertAlias(self, value):
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
# pylint: disable=line-too-long,too-many-public-methods
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
@ -32,6 +32,7 @@ class AliasTests(ScenarioTest):
|
|||
self.patchers.append(mock.patch('azext_alias.alias.GLOBAL_COLLIDED_ALIAS_PATH', os.path.join(self.mock_config_dir, COLLIDED_ALIAS_FILE_NAME)))
|
||||
self.patchers.append(mock.patch('azext_alias.util.GLOBAL_ALIAS_TAB_COMP_TABLE_PATH', os.path.join(self.mock_config_dir, ALIAS_TAB_COMP_TABLE_FILE_NAME)))
|
||||
self.patchers.append(mock.patch('azext_alias.custom.GLOBAL_ALIAS_PATH', os.path.join(self.mock_config_dir, ALIAS_FILE_NAME)))
|
||||
os.makedirs(os.path.join(self.mock_config_dir, 'export'))
|
||||
for patcher in self.patchers:
|
||||
patcher.start()
|
||||
|
||||
|
@ -78,6 +79,25 @@ class AliasTests(ScenarioTest):
|
|||
self.check('length(@)', 0)
|
||||
])
|
||||
|
||||
def test_remove_multiple_aliases(self):
|
||||
self.kwargs.update({
|
||||
'alias_name': 'c',
|
||||
'alias_command': 'create'
|
||||
})
|
||||
self.cmd('az alias create -n \'{alias_name}\' -c \'{alias_command}\'')
|
||||
self.kwargs.update({
|
||||
'alias_name': 'storage-ls {{ url }}',
|
||||
'alias_command': 'storage blob list --account-name {{ url.replace("https://", "").split(".")[0] }} --container-name {{ url.replace("https://", "").split("/")[1] }}'
|
||||
})
|
||||
self.cmd('az alias create -n \'{alias_name}\' -c \'{alias_command}\'')
|
||||
self.cmd('az alias list', checks=[
|
||||
self.check('length(@)', 2)
|
||||
])
|
||||
self.cmd('az alias remove -n \'storage-ls {{{{ url }}}}\' c')
|
||||
self.cmd('az alias list', checks=[
|
||||
self.check('length(@)', 0)
|
||||
])
|
||||
|
||||
def test_remove_alias_non_existing(self):
|
||||
self.kwargs.update({
|
||||
'alias_name': 'c',
|
||||
|
@ -100,7 +120,7 @@ command = create
|
|||
with open(alias.GLOBAL_ALIAS_PATH) as alias_config_file:
|
||||
assert alias_config_file.read() == expected_alias_string
|
||||
|
||||
def test_alias_file_and_hash_remove(self):
|
||||
def test_alias_file_remove(self):
|
||||
self.kwargs.update({
|
||||
'alias_name': 'c',
|
||||
'alias_command': 'create'
|
||||
|
@ -116,6 +136,177 @@ command = create
|
|||
with open(alias.GLOBAL_ALIAS_PATH) as alias_config_file:
|
||||
assert not alias_config_file.read()
|
||||
|
||||
def test_create_and_import_file(self):
|
||||
_, mock_alias_config_file = tempfile.mkstemp()
|
||||
with open(mock_alias_config_file, 'w') as f:
|
||||
f.write('[c]\ncommand = create\n[grp]\ncommand = group')
|
||||
|
||||
self.kwargs.update({
|
||||
'alias_source': mock_alias_config_file
|
||||
})
|
||||
self.cmd('az alias import -s {alias_source}')
|
||||
self.cmd('az alias list', checks=[
|
||||
self.check('[0].alias', 'c'),
|
||||
self.check('[0].command', 'create'),
|
||||
self.check('[1].alias', 'grp'),
|
||||
self.check('[1].command', 'group'),
|
||||
self.check('length(@)', 2)
|
||||
])
|
||||
os.remove(mock_alias_config_file)
|
||||
|
||||
def test_create_and_import_url(self):
|
||||
self.kwargs.update({
|
||||
'alias_source': 'https://raw.githubusercontent.com/chewong/azure-cli-alias-extension/test/azext_alias/tests/alias'
|
||||
})
|
||||
self.cmd('az alias import -s {alias_source}')
|
||||
self.cmd('az alias list', checks=[
|
||||
self.check('[0].alias', 'c'),
|
||||
self.check('[0].command', 'create'),
|
||||
self.check('[1].alias', 'grp'),
|
||||
self.check('[1].command', 'group'),
|
||||
self.check('length(@)', 2)
|
||||
])
|
||||
|
||||
def test_create_and_import_collide(self):
|
||||
self.kwargs.update({
|
||||
'alias_name': 'c',
|
||||
'alias_command': 'vm'
|
||||
})
|
||||
self.cmd('az alias create -n \'{alias_name}\' -c \'{alias_command}\'')
|
||||
self.cmd('az alias list', checks=[
|
||||
self.check('[0].alias', '{alias_name}'),
|
||||
self.check('[0].command', '{alias_command}'),
|
||||
self.check('length(@)', 1)
|
||||
])
|
||||
self.kwargs.update({
|
||||
'alias_source': 'https://raw.githubusercontent.com/chewong/azure-cli-alias-extension/test/azext_alias/tests/alias'
|
||||
})
|
||||
self.cmd('az alias import -s {alias_source}')
|
||||
self.cmd('az alias list', checks=[
|
||||
self.check('[0].alias', 'c'),
|
||||
self.check('[0].command', 'create'),
|
||||
self.check('[1].alias', 'grp'),
|
||||
self.check('[1].command', 'group'),
|
||||
self.check('length(@)', 2)
|
||||
])
|
||||
|
||||
def test_import_invalid_content_from_url(self):
|
||||
self.kwargs.update({
|
||||
'alias_source': 'https://raw.githubusercontent.com/chewong/azure-cli-alias-extension/test/azext_alias/tests/invalid_alias'
|
||||
})
|
||||
self.cmd('az alias import -s {alias_source}', expect_failure=True)
|
||||
self.cmd('az alias list', checks=[
|
||||
self.check('length(@)', 0)
|
||||
])
|
||||
|
||||
def test_remove_all_aliases(self):
|
||||
self.kwargs.update({
|
||||
'alias_name': 'list-vm {{ resource_group }}',
|
||||
'alias_command': 'vm list --resource-group {{ resource_group }}'
|
||||
})
|
||||
self.cmd('az alias create -n \'{alias_name}\' -c \'{alias_command}\'')
|
||||
self.kwargs.update({
|
||||
'alias_name': 'storage-ls {{ url }}',
|
||||
'alias_command': 'storage blob list --account-name {{ url.replace("https://", "").split(".")[0] }} --container-name {{ url.replace("https://", "").split("/")[1] }}'
|
||||
})
|
||||
self.cmd('az alias create -n \'{alias_name}\' -c \'{alias_command}\'')
|
||||
self.cmd('az alias list', checks=[
|
||||
self.check('length(@)', 2)
|
||||
])
|
||||
self.cmd('az alias remove-all --yes')
|
||||
self.cmd('az alias list', checks=[
|
||||
self.check('length(@)', 0)
|
||||
])
|
||||
|
||||
def test_excessive_whitespaces_in_alias_command(self):
|
||||
self.kwargs.update({
|
||||
'alias_name': ' list-vm \n{{ resource_group }} ',
|
||||
'alias_command': ' vm \n list --resource-group {{ resource_group }} '
|
||||
})
|
||||
self.cmd('az alias create -n \'{alias_name}\' -c \'{alias_command}\'')
|
||||
self.cmd('az alias list', checks=[
|
||||
self.check('[0].alias', 'list-vm {{{{ resource_group }}}}'),
|
||||
self.check('[0].command', 'vm list --resource-group {{{{ resource_group }}}}'),
|
||||
self.check('length(@)', 1)
|
||||
])
|
||||
|
||||
@mock.patch('os.getcwd')
|
||||
def test_export_file_name_only(self, mock_os_getcwd):
|
||||
mock_os_getcwd.return_value = os.path.join(self.mock_config_dir, 'export')
|
||||
self._pre_test_export()
|
||||
self.cmd('az alias export -p alias')
|
||||
self._post_test_export(os.path.join(self.mock_config_dir, 'export', 'alias'))
|
||||
|
||||
@mock.patch('os.getcwd')
|
||||
def test_export_existing_file(self, mock_os_getcwd):
|
||||
mock_os_getcwd.return_value = os.path.join(self.mock_config_dir, 'export')
|
||||
self._pre_test_export()
|
||||
self.cmd('az alias export -p alias')
|
||||
self.cmd('az alias export -p alias', expect_failure=True)
|
||||
|
||||
@mock.patch('os.getcwd')
|
||||
def test_export_path_relative_path(self, mock_os_getcwd):
|
||||
mock_os_getcwd.return_value = os.path.join(self.mock_config_dir, 'export')
|
||||
self._pre_test_export()
|
||||
self.cmd('az alias export -p test1/test2/alias')
|
||||
self._post_test_export(os.path.join(self.mock_config_dir, 'export', 'test1', 'test2', 'alias'))
|
||||
|
||||
@mock.patch('os.getcwd')
|
||||
def test_export_path_dir_only(self, mock_os_getcwd):
|
||||
mock_os_getcwd.return_value = os.path.join(self.mock_config_dir, 'export')
|
||||
self._pre_test_export()
|
||||
self.cmd('az alias export -p {}'.format(os.path.join(self.mock_config_dir, 'export')))
|
||||
self._post_test_export(os.path.join(self.mock_config_dir, 'export', 'alias'))
|
||||
|
||||
@mock.patch('os.getcwd')
|
||||
def test_export_path_absolute_path(self, mock_os_getcwd):
|
||||
mock_os_getcwd.return_value = os.path.join(self.mock_config_dir, 'export')
|
||||
self._pre_test_export()
|
||||
self.cmd('az alias export -p {}'.format(os.path.join(self.mock_config_dir, 'export', 'alias12345')))
|
||||
self._post_test_export(os.path.join(self.mock_config_dir, 'export', 'alias12345'))
|
||||
|
||||
@mock.patch('os.getcwd')
|
||||
def test_export_path_exclusion(self, mock_os_getcwd):
|
||||
mock_os_getcwd.return_value = os.path.join(self.mock_config_dir, 'export')
|
||||
self._pre_test_export()
|
||||
self.cmd('az alias export -p {} -e \'{}\''.format('alias', 'storage-ls {{{{ url }}}}'))
|
||||
self._post_test_export(os.path.join(self.mock_config_dir, 'export', 'alias'), test_exclusion=True)
|
||||
|
||||
@mock.patch('os.getcwd')
|
||||
def test_export_path_exclusion_error(self, mock_os_getcwd):
|
||||
mock_os_getcwd.return_value = os.path.join(self.mock_config_dir, 'export')
|
||||
self._pre_test_export()
|
||||
self.cmd('az alias export -p {} -e {}'.format('alias', 'invalid_alias'), expect_failure=True)
|
||||
|
||||
def _pre_test_export(self):
|
||||
self.kwargs.update({
|
||||
'alias_name': 'c',
|
||||
'alias_command': 'create'
|
||||
})
|
||||
self.cmd('az alias create -n \'{alias_name}\' -c \'{alias_command}\'')
|
||||
self.kwargs.update({
|
||||
'alias_name': 'storage-ls {{ url }}',
|
||||
'alias_command': 'storage blob list --account-name {{ url.replace("https://", "").split(".")[0] }} --container-name {{ url.replace("https://", "").split("/")[1] }}'
|
||||
})
|
||||
self.cmd('az alias create -n \'{alias_name}\' -c \'{alias_command}\'')
|
||||
self.cmd('az alias list', checks=[
|
||||
self.check('length(@)', 2)
|
||||
])
|
||||
|
||||
def _post_test_export(self, export_path, test_exclusion=False): # pylint: disable=no-self-use
|
||||
with open(export_path, 'r') as f:
|
||||
expected = '''[c]
|
||||
command = create
|
||||
|
||||
[storage-ls {{ url }}]
|
||||
command = storage blob list --account-name {{ url.replace("https://", "").split(".")[0] }} --container-name {{ url.replace("https://", "").split("/")[1] }}
|
||||
|
||||
''' if not test_exclusion else '''[c]
|
||||
command = create
|
||||
|
||||
'''
|
||||
assert f.read() == expected
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -117,7 +117,7 @@ class TestArgument(unittest.TestCase):
|
|||
'arg_2': 'test_2'
|
||||
}
|
||||
render_template('{{ arg_1 }} {{ arg_2 }', pos_args_table)
|
||||
self.assertEqual(str(cm.exception), 'alias: Encounted the following error when injecting positional arguments to ""{{ arg_1 }}" "{{ arg_2 }" - unexpected \'}\'')
|
||||
self.assertEqual(str(cm.exception), 'alias: Encounted error when injecting positional arguments to ""{{ arg_1 }}" "{{ arg_2 }". Error detail: unexpected \'}\'')
|
||||
|
||||
def test_check_runtime_errors_no_error(self):
|
||||
pos_args_table = {
|
||||
|
@ -133,7 +133,7 @@ class TestArgument(unittest.TestCase):
|
|||
'arg_2': 'test_2'
|
||||
}
|
||||
check_runtime_errors('{{ arg_1.split("_")[2] }} {{ arg_2.split("_")[1] }}', pos_args_table)
|
||||
self.assertEqual(str(cm.exception), 'alias: Encounted the following error when evaluating "arg_1.split("_")[2]" - list index out of range')
|
||||
self.assertEqual(str(cm.exception), 'alias: Encounted error when evaluating "arg_1.split("_")[2]". Error detail: list index out of range')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
# pylint: disable=line-too-long,no-self-use,protected-access
|
||||
|
||||
import unittest
|
||||
from mock import Mock
|
||||
from mock import Mock, patch
|
||||
|
||||
from knack.util import CLIError
|
||||
|
||||
|
@ -22,11 +22,14 @@ from azext_alias.custom import (
|
|||
|
||||
class AliasCustomCommandTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUp(cls):
|
||||
azext_alias.cached_reserved_commands = TEST_RESERVED_COMMANDS
|
||||
def setUp(self):
|
||||
self.patcher = patch('azext_alias.cached_reserved_commands', TEST_RESERVED_COMMANDS)
|
||||
self.patcher.start()
|
||||
azext_alias.custom._commit_change = Mock()
|
||||
|
||||
def tearDown(self):
|
||||
self.patcher.stop()
|
||||
|
||||
def test_create_alias(self):
|
||||
create_alias('ac', 'account')
|
||||
|
||||
|
@ -74,7 +77,7 @@ class AliasCustomCommandTest(unittest.TestCase):
|
|||
mock_alias_table.set('ac', 'command', 'account')
|
||||
azext_alias.custom.get_alias_table = Mock(return_value=mock_alias_table)
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
remove_alias('dns')
|
||||
remove_alias(['dns'])
|
||||
self.assertEqual(str(cm.exception), 'alias: "dns" alias not found')
|
||||
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import tempfile
|
|||
import unittest
|
||||
import mock
|
||||
|
||||
import azext_alias
|
||||
from azext_alias.util import remove_pos_arg_placeholders, build_tab_completion_table, get_config_parser
|
||||
from azext_alias._const import ALIAS_TAB_COMP_TABLE_FILE_NAME
|
||||
from azext_alias.tests._const import TEST_RESERVED_COMMANDS
|
||||
|
@ -21,12 +20,15 @@ class TestUtil(unittest.TestCase):
|
|||
|
||||
def setUp(self):
|
||||
self.mock_config_dir = tempfile.mkdtemp()
|
||||
self.patcher = mock.patch('azext_alias.util.GLOBAL_ALIAS_TAB_COMP_TABLE_PATH', os.path.join(self.mock_config_dir, ALIAS_TAB_COMP_TABLE_FILE_NAME))
|
||||
self.patcher.start()
|
||||
azext_alias.cached_reserved_commands = TEST_RESERVED_COMMANDS
|
||||
self.patchers = []
|
||||
self.patchers.append(mock.patch('azext_alias.util.GLOBAL_ALIAS_TAB_COMP_TABLE_PATH', os.path.join(self.mock_config_dir, ALIAS_TAB_COMP_TABLE_FILE_NAME)))
|
||||
self.patchers.append(mock.patch('azext_alias.cached_reserved_commands', TEST_RESERVED_COMMANDS))
|
||||
for patcher in self.patchers:
|
||||
patcher.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.patcher.stop()
|
||||
for patcher in self.patchers:
|
||||
patcher.stop()
|
||||
shutil.rmtree(self.mock_config_dir)
|
||||
|
||||
def test_remove_pos_arg_placeholders(self):
|
||||
|
|
|
@ -3,58 +3,151 @@
|
|||
# Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
# pylint: disable=line-too-long,no-self-use,too-many-public-methods
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from mock import patch
|
||||
|
||||
from knack.util import CLIError
|
||||
|
||||
from azext_alias._validators import process_alias_create_namespace
|
||||
from azext_alias._validators import process_alias_create_namespace, process_alias_import_namespace
|
||||
from azext_alias.tests._const import TEST_RESERVED_COMMANDS
|
||||
|
||||
|
||||
class TestValidators(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.patcher = patch('azext_alias.cached_reserved_commands', TEST_RESERVED_COMMANDS)
|
||||
self.patcher.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.patcher.stop()
|
||||
|
||||
def test_process_alias_create_namespace_non_existing_command(self):
|
||||
with self.assertRaises(CLIError):
|
||||
process_alias_create_namespace(MockNamespace('test', 'non existing command'))
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('test', 'non existing command'))
|
||||
self.assertEqual(str(cm.exception), 'alias: Invalid Azure CLI command "non existing command"')
|
||||
|
||||
def test_process_alias_create_namespace_empty_alias_name(self):
|
||||
with self.assertRaises(CLIError):
|
||||
process_alias_create_namespace(MockNamespace('', 'account'))
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('', 'account'))
|
||||
self.assertEqual(str(cm.exception), 'alias: Empty alias name or command is invalid')
|
||||
|
||||
def test_process_alias_create_namespace_empty_alias_command(self):
|
||||
with self.assertRaises(CLIError):
|
||||
process_alias_create_namespace(MockNamespace('ac', ''))
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('ac', ''))
|
||||
self.assertEqual(str(cm.exception), 'alias: Empty alias name or command is invalid')
|
||||
|
||||
def test_process_alias_create_namespace_non_existing_commands_with_pos_arg(self):
|
||||
with self.assertRaises(CLIError):
|
||||
process_alias_create_namespace(MockNamespace('test {{ arg }}', 'account list {{ arg }}'))
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('test {{ arg }}', 'account list {{ arg }}'))
|
||||
self.assertEqual(str(cm.exception), 'alias: Invalid Azure CLI command "account list {{ arg }}"')
|
||||
|
||||
def test_process_alias_create_namespace_inconsistent_pos_arg_name(self):
|
||||
with self.assertRaises(CLIError):
|
||||
process_alias_create_namespace(MockNamespace('test {{ arg }}', 'account {{ ar }}'))
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('test {{ arg }}', 'account {{ ar }}'))
|
||||
if sys.version_info.major == 2:
|
||||
self.assertTrue(str(cm.exception) in ['alias: Positional arguments set([\'ar\', \'arg\']) are not in both alias name and alias command', 'alias: Positional arguments set([\'arg\', \'ar\']) are not in both alias name and alias command'])
|
||||
else:
|
||||
self.assertTrue(str(cm.exception) in ['alias: Positional arguments {\'ar\', \'arg\'} are not in both alias name and alias command', 'alias: Positional arguments {\'arg\', \'ar\'} are not in both alias name and alias command'])
|
||||
|
||||
def test_process_alias_create_namespace_pos_arg_only(self):
|
||||
with self.assertRaises(CLIError):
|
||||
process_alias_create_namespace(MockNamespace('test {{ arg }}', '{{ arg }}'))
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('test {{ arg }}', '{{ arg }}'))
|
||||
self.assertEqual(str(cm.exception), 'alias: Invalid Azure CLI command "{{ arg }}"')
|
||||
|
||||
def test_process_alias_create_namespace_inconsistent_number_pos_arg(self):
|
||||
with self.assertRaises(CLIError):
|
||||
process_alias_create_namespace(MockNamespace('test {{ arg_1 }} {{ arg_2 }}', 'account {{ arg_2 }}'))
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('test {{ arg_1 }} {{ arg_2 }}', 'account {{ arg_2 }}'))
|
||||
if sys.version_info.major == 2:
|
||||
self.assertEqual(str(cm.exception), 'alias: Positional argument set([\'arg_1\']) is not in both alias name and alias command')
|
||||
else:
|
||||
self.assertEqual(str(cm.exception), 'alias: Positional argument {\'arg_1\'} is not in both alias name and alias command')
|
||||
|
||||
def test_process_alias_create_namespace_lvl_error(self):
|
||||
with self.assertRaises(CLIError):
|
||||
process_alias_create_namespace(MockNamespace('network', 'account list'))
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('network', 'account list'))
|
||||
self.assertEqual(str(cm.exception), 'alias: Invalid Azure CLI command "account list"')
|
||||
|
||||
def test_process_alias_create_namespace_lvl_error_with_pos_arg(self):
|
||||
with self.assertRaises(CLIError):
|
||||
process_alias_create_namespace(MockNamespace('account {{ test }}', 'dns {{ test }}'))
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('account {{ test }}', 'dns {{ test }}'))
|
||||
self.assertEqual(str(cm.exception), 'alias: "account {{ test }}" is a reserved command and cannot be used to represent "dns {{ test }}"')
|
||||
|
||||
def test_process_alias_create_namespace_pos_arg_1(self):
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('test', 'group delete resourceGroupName'))
|
||||
|
||||
def test_process_alias_create_namespace_pos_arg_2(self):
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('test', 'delete resourceGroupName'))
|
||||
|
||||
def test_process_alias_create_namespace_pos_arg_3(self):
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('test', 'group delete resourceGroupName -p param'))
|
||||
|
||||
def test_process_alias_create_namespace_pos_arg_4(self):
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('test', 'group resourceGroupName'))
|
||||
self.assertEqual(str(cm.exception), 'alias: Invalid Azure CLI command "group resourceGroupName"')
|
||||
|
||||
def test_process_alias_create_namespace_pos_arg_5(self):
|
||||
process_alias_create_namespace(MockAliasCreateNamespace('test', 'group delete -p param resourceGroupName'))
|
||||
|
||||
def test_process_alias_import_namespace(self):
|
||||
process_alias_import_namespace(MockAliasImportNamespace('https://raw.githubusercontent.com/chewong/azure-cli-alias-extension/test/azext_alias/tests/alias'))
|
||||
|
||||
def test_process_alias_import_namespace_invalid_url_python_2(self):
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_import_namespace(MockAliasImportNamespace('https://raw.githubusercontent.com/chewong/azure-cli-alias-extension/test/azext_alias/tests/alia'))
|
||||
if sys.version_info.major == 2:
|
||||
self.assertEqual(str(cm.exception), 'alias: Encounted error when retrieving alias file from https://raw.githubusercontent.com/chewong/azure-cli-alias-extension/test/azext_alias/tests/alia. Error detail: 404: Not Found')
|
||||
else:
|
||||
self.assertEqual(str(cm.exception), 'alias: Encounted error when retrieving alias file from https://raw.githubusercontent.com/chewong/azure-cli-alias-extension/test/azext_alias/tests/alia. Error detail: HTTP Error 404: Not Found')
|
||||
|
||||
def test_process_alias_import_namespace_invalid_content_from_url(self):
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_import_namespace(MockAliasImportNamespace('https://raw.githubusercontent.com/chewong/azure-cli-alias-extension/test/azext_alias/tests/invalid_alias'))
|
||||
if sys.version_info.major == 2:
|
||||
self.assertEqual(str(cm.exception), 'alias: Please ensure you have a valid alias configuration file. Error detail: File contains no alias headers.file: https://raw.githubusercontent.com/chewong/azure-cli-alias-extension/test/azext_alias/tests/invalid_alias, line: 1\'[c\'')
|
||||
else:
|
||||
self.assertEqual(str(cm.exception), 'alias: Please ensure you have a valid alias configuration file. Error detail: File contains no alias headers.file: \'https://raw.githubusercontent.com/chewong/azure-cli-alias-extension/test/azext_alias/tests/invalid_alias\', line: 1\'[c\'')
|
||||
|
||||
def test_process_alias_import_namespace_file(self):
|
||||
_, mock_alias_config_file = tempfile.mkstemp()
|
||||
process_alias_import_namespace(MockAliasImportNamespace(mock_alias_config_file))
|
||||
os.remove(mock_alias_config_file)
|
||||
|
||||
def test_process_alias_import_namespace_invalid_content_in_file(self):
|
||||
_, mock_alias_config_file = tempfile.mkstemp()
|
||||
with open(mock_alias_config_file, 'w') as f:
|
||||
f.write('invalid alias config format')
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_import_namespace(MockAliasImportNamespace(mock_alias_config_file))
|
||||
if sys.version_info.major == 2:
|
||||
self.assertEqual(str(cm.exception), 'alias: Please ensure you have a valid alias configuration file. Error detail: File contains no alias headers.file: {}, line: 1\'invalid alias config format\''.format(mock_alias_config_file))
|
||||
else:
|
||||
self.assertEqual(str(cm.exception), 'alias: Please ensure you have a valid alias configuration file. Error detail: File contains no alias headers.file: \'{}\', line: 1\'invalid alias config format\''.format(mock_alias_config_file))
|
||||
os.remove(mock_alias_config_file)
|
||||
|
||||
def test_process_alias_import_namespace_dir(self):
|
||||
with self.assertRaises(CLIError) as cm:
|
||||
process_alias_import_namespace(MockAliasImportNamespace(os.getcwd()))
|
||||
self.assertEqual(str(cm.exception), 'alias: {} is a directory'.format(os.getcwd()))
|
||||
|
||||
|
||||
class MockNamespace(object): # pylint: disable=too-few-public-methods
|
||||
class MockAliasCreateNamespace(object): # pylint: disable=too-few-public-methods
|
||||
|
||||
def __init__(self, alias_name, alias_command):
|
||||
self.alias_name = alias_name
|
||||
self.alias_command = alias_command
|
||||
|
||||
|
||||
class MockAliasImportNamespace(object): # pylint: disable=too-few-public-methods
|
||||
|
||||
def __init__(self, alias_source):
|
||||
self.alias_source = alias_source
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -3,15 +3,21 @@
|
|||
# Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
# pylint: disable=wrong-import-order,import-error,relative-import
|
||||
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import shlex
|
||||
from collections import defaultdict
|
||||
from six.moves import configparser
|
||||
from six.moves.urllib.parse import urlparse
|
||||
from six.moves.urllib.request import urlretrieve
|
||||
|
||||
from knack.util import CLIError
|
||||
|
||||
import azext_alias
|
||||
from azext_alias._const import COLLISION_CHECK_LEVEL_DEPTH, GLOBAL_ALIAS_TAB_COMP_TABLE_PATH
|
||||
from azext_alias._const import COLLISION_CHECK_LEVEL_DEPTH, GLOBAL_ALIAS_TAB_COMP_TABLE_PATH, ALIAS_FILE_URL_ERROR
|
||||
|
||||
|
||||
def get_config_parser():
|
||||
|
@ -38,14 +44,25 @@ def get_alias_table():
|
|||
return get_config_parser()
|
||||
|
||||
|
||||
def is_alias_create_command(args):
|
||||
def is_alias_command(subcommands, args):
|
||||
"""
|
||||
Check if the user is invoking 'az alias create'.
|
||||
Check if the user is invoking one of the comments in 'subcommands' in the from az alias .
|
||||
|
||||
Args:
|
||||
subcommands: The list of subcommands to check through.
|
||||
args: The CLI arguments to process.
|
||||
|
||||
Returns:
|
||||
True if the user is invoking 'az alias create'.
|
||||
True if the user is invoking 'az alias {command}'.
|
||||
"""
|
||||
return args and args[:2] == ['alias', 'create']
|
||||
if not args:
|
||||
return False
|
||||
|
||||
for subcommand in subcommands:
|
||||
if args[:2] == ['alias', subcommand]:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def cache_reserved_commands(load_cmd_tbl_func):
|
||||
|
@ -54,6 +71,9 @@ def cache_reserved_commands(load_cmd_tbl_func):
|
|||
for alias and command validation when the user invokes alias create).
|
||||
This cache saves the entire command table globally so custom.py can have access to it.
|
||||
Alter this cache through cache_reserved_commands(load_cmd_tbl_func) in util.py.
|
||||
|
||||
Args:
|
||||
load_cmd_tbl_func: The function to load the entire command table.
|
||||
"""
|
||||
if not azext_alias.cached_reserved_commands:
|
||||
azext_alias.cached_reserved_commands = list(load_cmd_tbl_func([]).keys())
|
||||
|
@ -65,6 +85,9 @@ def remove_pos_arg_placeholders(alias_command):
|
|||
|
||||
Args:
|
||||
alias_command: The alias command to remove from.
|
||||
|
||||
Returns:
|
||||
The alias command string without positional argument placeholder.
|
||||
"""
|
||||
# Boundary index is the index at which named argument or positional argument starts
|
||||
split_command = shlex.split(alias_command)
|
||||
|
@ -130,3 +153,73 @@ def build_tab_completion_table(alias_table):
|
|||
f.write(json.dumps(tab_completion_table))
|
||||
|
||||
return tab_completion_table
|
||||
|
||||
|
||||
def is_url(s):
|
||||
"""
|
||||
Check if the argument is an URL.
|
||||
|
||||
Returns:
|
||||
True if the argument is an URL.
|
||||
"""
|
||||
return urlparse(s).scheme in ('http', 'https')
|
||||
|
||||
|
||||
def reduce_alias_table(alias_table):
|
||||
"""
|
||||
Reduce the alias table to a tuple that contains the alias and the command that the alias points to.
|
||||
|
||||
Args:
|
||||
The alias table to be reduced.
|
||||
|
||||
Yields
|
||||
A tuple that contains the alias and the command that the alias points to.
|
||||
"""
|
||||
for alias in alias_table.sections():
|
||||
if alias_table.has_option(alias, 'command'):
|
||||
yield (alias, alias_table.get(alias, 'command'))
|
||||
|
||||
|
||||
def retrieve_file_from_url(url):
|
||||
"""
|
||||
Retrieve a file from an URL
|
||||
|
||||
Args:
|
||||
url: The URL to retrieve the file from.
|
||||
|
||||
Returns:
|
||||
The absolute path of the downloaded file.
|
||||
"""
|
||||
try:
|
||||
alias_source, _ = urlretrieve(url)
|
||||
# Check for HTTPError in Python 2.x
|
||||
with open(alias_source, 'r') as f:
|
||||
content = f.read()
|
||||
if content[:3].isdigit():
|
||||
raise CLIError(ALIAS_FILE_URL_ERROR.format(url, content.strip()))
|
||||
except Exception as exception:
|
||||
if isinstance(exception, CLIError):
|
||||
raise
|
||||
|
||||
# Python 3.x
|
||||
raise CLIError(ALIAS_FILE_URL_ERROR.format(url, exception))
|
||||
|
||||
return alias_source
|
||||
|
||||
|
||||
def filter_alias_create_namespace(namespace):
|
||||
"""
|
||||
Filter alias name and alias command inside alias create namespace to appropriate strings.
|
||||
|
||||
Args
|
||||
namespace: The alias create namespace.
|
||||
|
||||
Returns:
|
||||
Filtered namespace where excessive whitespaces are removed in strings.
|
||||
"""
|
||||
def filter_string(s):
|
||||
return ' '.join(s.strip().split())
|
||||
|
||||
namespace.alias_name = filter_string(namespace.alias_name)
|
||||
namespace.alias_command = filter_string(namespace.alias_command)
|
||||
return namespace
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
# Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
VERSION = '0.4.0'
|
||||
VERSION = '0.5.0'
|
||||
|
|
|
@ -461,9 +461,9 @@
|
|||
],
|
||||
"alias": [
|
||||
{
|
||||
"filename": "alias-0.4.0-py2.py3-none-any.whl",
|
||||
"sha256Digest": "fdc818e1b814c7f687f4cda4718e6a1c16eb827eac72b22afc5368dce9cdd358",
|
||||
"downloadUrl": "https://azurecliprod.blob.core.windows.net/cli-extensions/alias-0.4.0-py2.py3-none-any.whl",
|
||||
"filename": "alias-0.5.0-py2.py3-none-any.whl",
|
||||
"sha256Digest": "f50723edb97a4f67535e0832d843abebf8272152efc4ec8201a48307999aa59c",
|
||||
"downloadUrl": "https://azurecliprod.blob.core.windows.net/cli-extensions/alias-0.5.0-py2.py3-none-any.whl",
|
||||
"metadata": {
|
||||
"azext.isPreview": true,
|
||||
"azext.minCliCoreVersion": "2.0.31.dev0",
|
||||
|
@ -510,7 +510,7 @@
|
|||
}
|
||||
],
|
||||
"summary": "Support for command aliases",
|
||||
"version": "0.4.0"
|
||||
"version": "0.5.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
Загрузка…
Ссылка в новой задаче