Merge branch 'master' of https://github.com/tjprescott/azure-cli into ConvertHandAuthoredCommands

# Conflicts:
#	src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/__init__.py
#	src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/generated.py
This commit is contained in:
Travis Prescott 2016-05-05 09:38:36 -07:00
Родитель 582aa54e8e fe8a9e4be8
Коммит ff3ea55f9d
18 изменённых файлов: 379 добавлений и 194 удалений

Просмотреть файл

@ -132,26 +132,6 @@
<Compile Include="command_modules\azure-cli-storage\setup.py" />
<Compile Include="command_modules\azure-cli-taskhelp\azure\cli\command_modules\taskhelp\__init__.py" />
<Compile Include="command_modules\azure-cli-taskhelp\setup.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\credentials.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\exceptions.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\basic_dependency.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\dependency.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\deployment_extended.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\deployment_parameter_virtual_network_ip_address_prefix.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\deployment_properties_extended.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\deployment_vm.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\parameters_link.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\provider.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\provider_resource_type.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\template_link.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\vm_creation_client_enums.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\__init__.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\operations\vm_operations.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\operations\__init__.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\version.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\vm_creation_client.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\__init__.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\__init__.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\tests\command_specs.py">
<SubType>Code</SubType>
</Compile>
@ -223,11 +203,6 @@
<Folder Include="command_modules\azure-cli-vm\azure\cli\" />
<Folder Include="command_modules\azure-cli-vm\azure\cli\command_modules\" />
<Folder Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\" />
<Folder Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\" />
<Folder Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\" />
<Folder Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\models\" />
<Folder Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\lib\operations\" />
<Folder Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\" />
<Folder Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\tests\" />
<Folder Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\tests\recordings\" />
</ItemGroup>
@ -254,20 +229,6 @@
<Content Include="command_modules\azure-cli-storage\requirements.txt" />
<Content Include="command_modules\azure-cli-taskhelp\requirements.txt" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\tests\aliases.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\azuredeploy.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\ExistingStorageAccount.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\ExistingVNet.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\NewStorageAccount.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\NewVNet.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\nic_ip_existing_dns_none.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\nic_ip_new_dns_new.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\nic_ip_new_dns_none.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\nic_ip_none_dns_none.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\vm_existing_password.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\vm_existing_sshkey.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\vm_none_password.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\nested_templates\vm_none_sshkey.json" />
<Content Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt\swagger_create_vm.json" />
<Content Include="command_modules\azure-cli-vm\requirements.txt" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -120,16 +120,22 @@ def prompt_input(message):
return None if DISABLE_PROMPTS else input(message)
def get_install_dir():
prompt_message = 'In what directory would you like to place the install? (default {}): '.format(DEFAULT_INSTALL_DIR)
prompt_message = 'In what directory would you like to place the install? (leave blank to use {}): '.format(DEFAULT_INSTALL_DIR)
install_dir = prompt_input(prompt_message) or DEFAULT_INSTALL_DIR
install_dir = os.path.realpath(os.path.expanduser(install_dir))
if not os.path.isdir(install_dir):
print("Directory '{}' does not exist. Creating directory...".format(install_dir))
create_dir(install_dir)
print("We will install at '{}'.".format(install_dir))
return install_dir
def get_exec_dir():
prompt_message = 'In what directory would you like to place the executable? (default {}): '.format(DEFAULT_EXEC_DIR)
prompt_message = 'In what directory would you like to place the executable? (leave blank to use {}): '.format(DEFAULT_EXEC_DIR)
exec_dir = prompt_input(prompt_message) or DEFAULT_EXEC_DIR
exec_dir = os.path.realpath(os.path.expanduser(exec_dir))
if not os.path.isdir(exec_dir):
print("Directory '{}' does not exist. Creating directory...".format(exec_dir))
create_dir(exec_dir)
print("The executable will be in '{}'.".format(exec_dir))
return exec_dir

Просмотреть файл

@ -4,7 +4,7 @@
#
# Calling the script
# e.g. python <filename> <path_to_cli_install> <path_to_rc_file>
# <path_to_rc_file> is optional as a default will be used.
# <path_to_rc_file> is optional as a default will be used. (e.g. ~/.bashrc)
#
# - Optional Environment Variables Available
# AZURE_CLI_DISABLE_PROMPTS - Disable prompts during installation and use the defaults
@ -24,7 +24,6 @@ except NameError:
DISABLE_PROMPTS = os.environ.get('AZURE_CLI_DISABLE_PROMPTS')
COMPLETION_FILENAME = 'az.completion'
DEFAULT_RC_FILE = os.path.expanduser(os.path.join('~', '.bashrc'))
REGISTER_PYTHON_ARGCOMPLETE = """
_python_argcomplete() {
@ -44,11 +43,20 @@ def create_tab_completion_file(filename):
with open(filename, 'w') as completion_file:
completion_file.write(REGISTER_PYTHON_ARGCOMPLETE)
def _get_default_rc_file():
user_bash_rc = os.path.expanduser(os.path.join('~', '.bashrc'))
user_bash_profile = os.path.expanduser(os.path.join('~', '.bash_profile'))
bashrc_exists = os.path.isfile(user_bash_rc)
bash_profile_exists = os.path.isfile(user_bash_profile)
if not bashrc_exists and bash_profile_exists:
return user_bash_profile
return user_bash_rc if bashrc_exists else None
def backup_rc(rc_file):
try:
shutil.copyfile(rc_file, rc_file+'.backup')
print("Backed up '{}' to '{}'".format(rc_file, rc_file+'.backup'))
except FileNotFoundError:
except (OSError, IOError):
pass
def find_line_in_file(file_path, search_pattern):
@ -57,7 +65,7 @@ def find_line_in_file(file_path, search_pattern):
for line in search_file:
if search_pattern in line:
return True
except FileNotFoundError:
except (OSError, IOError):
pass
return False
@ -76,12 +84,19 @@ def main():
completion_file_path = os.path.join(sys.argv[1], COMPLETION_FILENAME)
create_tab_completion_file(completion_file_path)
try:
# use value from argv if available else fall back to prompt or default
rc_file = sys.argv[2] if len(sys.argv) >= 3 else prompt_input('Path to rc file (default {}): '.format(DEFAULT_RC_FILE)) or DEFAULT_RC_FILE
except EOFError:
error_exit('Unable to prompt for input. Pass the rc file as an argument to this script.')
rc_file_path = os.path.realpath(os.path.expanduser(rc_file))
default_rc_file = _get_default_rc_file()
rc_file_exists = False
while not rc_file_exists:
try:
# use value from argv if available else fall back to prompt or default
rc_prompt_message = 'Path to rc file to update (leave blank to use {}): '.format(default_rc_file) if default_rc_file else 'Path to rc file to update: '
rc_file = sys.argv[2] if len(sys.argv) >= 3 else prompt_input(rc_prompt_message) or default_rc_file
except EOFError:
error_exit('Unable to prompt for input. Pass the rc file as an argument to this script.')
rc_file_path = os.path.realpath(os.path.expanduser(rc_file))
rc_file_exists = os.path.isfile(rc_file_path)
if not rc_file_exists:
print("ERROR: File '{}' does not exist!".format(rc_file_path))
backup_rc(rc_file_path)
line_to_add = "source '{}'".format(completion_file_path)
modify_rc(rc_file_path, line_to_add)

Просмотреть файл

@ -75,9 +75,18 @@ def print_arguments(help_file):
required_tag = L(' [Required]')
max_name_length = max(len(p.name) + (len(required_tag) if p.required else 0)
for p in help_file.parameters)
for p in sorted(help_file.parameters, key=lambda p: str(not p.required) + p.name):
last_group_name = None
for p in sorted(help_file.parameters,
key=lambda p: str(p.group_name or 'A')
+ str(not p.required) + p.name):
indent = 1
required_text = required_tag if p.required else ''
p.short_summary = (p.short_summary if p.short_summary else '') + _get_choices_str(p)
if p.group_name != last_group_name:
if p.group_name:
print('')
print(p.group_name)
last_group_name = p.group_name
_print_indent('{0}{1}{2}{3}'.format(p.name,
_get_column_indent(p.name + required_text,
max_name_length),
@ -91,7 +100,6 @@ def print_arguments(help_file):
_print_indent('{0}'.format(p.long_summary.rstrip()), indent)
if p.value_sources:
_print_indent('')
_print_indent(L("Values from: {0}").format(', '.join(p.value_sources)), indent)
if p.long_summary or p.value_sources:
@ -104,7 +112,7 @@ def _print_header(help_file):
_print_indent(L('Command') if help_file.type == 'command' else L('Group'), indent)
indent += 1
_print_indent('{0}{1}'.format(help_file.command,
_print_indent('{0}{1}'.format('az ' + help_file.command,
': ' + help_file.short_summary
if help_file.short_summary
else ''),
@ -127,8 +135,17 @@ def _print_groups(help_file):
indent)
_print_indent('')
def _get_choices_str(p):
choice_str = ""
if p.choices:
choice_str = (' ' if p.short_summary else '') \
+ '[{}{}]'.format(', '.join(p.choices),
('; default: ' + p.default) if p.default else '')
return choice_str
def _print_examples(help_file):
indent = 0
print('')
_print_indent(L('Examples'), indent)
for e in help_file.examples:
@ -207,7 +224,13 @@ class CommandHelpFile(HelpFile): #pylint: disable=too-few-public-methods
for action in [a for a in parser._actions if a.help != argparse.SUPPRESS]: # pylint: disable=protected-access
self.parameters.append(HelpParameter(' '.join(sorted(action.option_strings)),
action.help,
required=action.required))
required=action.required,
choices=action.choices,
default=action.default,
group_name=action.container.description))
help_param = next(p for p in self.parameters if p.name == '--help -h')
help_param.group_name = 'Global Arguments'
def _load_from_data(self, data):
super(CommandHelpFile, self)._load_from_data(data)
@ -231,14 +254,18 @@ class CommandHelpFile(HelpFile): #pylint: disable=too-few-public-methods
self.parameters = loaded_params
class HelpParameter(object): #pylint: disable=too-few-public-methods
def __init__(self, param_name, description, required):
class HelpParameter(object): #pylint: disable=too-few-public-methods, too-many-instance-attributes
def __init__(self, param_name, description, required, choices=None, #pylint: disable=too-many-arguments
default=None, group_name=None):
self.name = param_name
self.required = required
self.type = 'string'
self.short_summary = description
self.long_summary = ''
self.value_sources = []
self.choices = choices
self.default = default
self.group_name = group_name
def update_from_data(self, data):
if self.name != data.get('name'):

Просмотреть файл

@ -1,6 +1,8 @@
import logging
import os
import logging
from logging.handlers import RotatingFileHandler
LOG_LEVEL_CONFIGS = [
CONSOLE_LOG_CONFIGS = [
# (default)
{
'az': logging.WARNING,
@ -17,6 +19,12 @@ LOG_LEVEL_CONFIGS = [
'root': logging.DEBUG,
}]
AZ_LOGFILE_NAME = 'az.log'
DEFAULT_LOG_DIR = os.path.expanduser(os.path.join('~', '.azure', 'logs'))
ENABLE_LOG_FILE = os.environ.get('AZURE_CLI_ENABLE_LOG_FILE')
LOG_DIR = os.environ.get('AZURE_CLI_LOG_DIR')
def _determine_verbose_level(argv):
# Get verbose level by reading the arguments.
# Remove any consumed args.
@ -33,26 +41,52 @@ def _determine_verbose_level(argv):
else:
i += 1
# Use max verbose level if too much verbosity specified.
return verbose_level if verbose_level < len(LOG_LEVEL_CONFIGS) else len(LOG_LEVEL_CONFIGS)-1
return verbose_level if verbose_level < len(CONSOLE_LOG_CONFIGS) else len(CONSOLE_LOG_CONFIGS)-1
def _configure_root_logger(log_level_config):
root_logger = logging.getLogger()
root_logger.setLevel(log_level_config['root'])
def _init_console_handlers(root_logger, az_logger, log_level_config):
console_log_format = logging.Formatter('%(levelname)s: %(message)s')
def _configure_az_logger(log_level_config):
az_logger = logging.getLogger('az')
az_logger.setLevel(log_level_config['az'])
root_console_handler = logging.StreamHandler()
root_console_handler.setFormatter(console_log_format)
root_console_handler.setLevel(log_level_config['root'])
root_logger.addHandler(root_console_handler)
def _init_console_handler():
logging.basicConfig(format='%(levelname)s: %(message)s')
az_console_handler = logging.StreamHandler()
az_console_handler.setFormatter(console_log_format)
az_console_handler.setLevel(log_level_config['az'])
az_logger.addHandler(az_console_handler)
def _get_log_file_path():
log_dir = LOG_DIR or DEFAULT_LOG_DIR
if not os.path.isdir(log_dir):
os.makedirs(log_dir)
return os.path.join(log_dir, AZ_LOGFILE_NAME)
def _init_logfile_handlers(root_logger, az_logger):
if not ENABLE_LOG_FILE:
return
log_file_path = _get_log_file_path()
logfile_handler = RotatingFileHandler(log_file_path, maxBytes=5*1024*1024, backupCount=5)
lfmt = logging.Formatter('%(process)d : %(asctime)s : %(name)s : %(levelname)s : %(message)s')
logfile_handler.setFormatter(lfmt)
logfile_handler.setLevel(logging.DEBUG)
root_logger.addHandler(logfile_handler)
az_logger.addHandler(logfile_handler)
def configure_logging(argv):
verbose_level = _determine_verbose_level(argv)
log_level_config = LOG_LEVEL_CONFIGS[verbose_level]
log_level_config = CONSOLE_LOG_CONFIGS[verbose_level]
_init_console_handler()
_configure_root_logger(log_level_config)
_configure_az_logger(log_level_config)
root_logger = logging.getLogger()
az_logger = logging.getLogger('az')
# Set the levels of the loggers to lowest level.
# Handlers can override by choosing a higher level.
root_logger.setLevel(logging.DEBUG)
az_logger.setLevel(logging.DEBUG)
az_logger.propagate = False
_init_console_handlers(root_logger, az_logger, log_level_config)
_init_logfile_handlers(root_logger, az_logger)
def get_az_logger(module_name=None):
return logging.getLogger('az.' + module_name if module_name else 'az')

Просмотреть файл

@ -50,7 +50,8 @@ class Application(object):
azure.cli.extensions.register_extensions(self)
self.global_parser = AzCliCommandParser(prog='az', add_help=False)
self.raise_event(self.GLOBAL_PARSER_CREATED, self.global_parser)
global_group = self.global_parser.add_argument_group('global', 'Global Arguments')
self.raise_event(self.GLOBAL_PARSER_CREATED, global_group)
self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser])
self.raise_event(self.COMMAND_PARSER_CREATED, self.parser)
@ -134,16 +135,20 @@ class Application(object):
argcomplete.autocomplete(parser)
@staticmethod
def _register_builtin_arguments(parser):
parser.add_argument('--subscription', dest='_subscription_id', help=argparse.SUPPRESS)
parser.add_argument('--output', '-o', dest='_output_format',
choices=['list', 'json', 'tsv'],
help='Output format of type "list", "json" or "tsv"')
def _register_builtin_arguments(global_group):
global_group.add_argument('--subscription', dest='_subscription_id', help=argparse.SUPPRESS)
global_group.add_argument('--output', '-o', dest='_output_format',
choices=['list', 'json', 'tsv'],
default='list',
help='Output format')
# The arguments for verbosity don't get parsed by argparse but we add it here for help.
parser.add_argument('--verbose', dest='_log_verbosity_verbose',
help='Increase logging verbosity. Use --debug for full debug logs.')
parser.add_argument('--debug', dest='_log_verbosity_debug',
help='Increase logging verbosity to show all debug logs.')
global_group.add_argument('--verbose', dest='_log_verbosity_verbose',
help='Increase logging verbosity.'
' Use --debug for full debug logs.',
action='store_true')
global_group.add_argument('--debug', dest='_log_verbosity_debug',
help='Increase logging verbosity to show all debug logs.',
action='store_true')
def _handle_builtin_arguments(self, args):
self.configuration.output_format = args._output_format #pylint: disable=protected-access

Просмотреть файл

@ -1,9 +1,9 @@
import collections
def _register_global_parameter(parser):
def _register_global_parameter(global_group):
# Let the program know that we are adding a parameter --query
parser.add_argument('--query', dest='_jmespath_query', metavar='JMESPATH',
help='JMESPath query string. See http://jmespath.org/ for more information and examples.') # pylint: disable=line-too-long
global_group.add_argument('--query', dest='_jmespath_query', metavar='JMESPATH',
help='JMESPath query string. See http://jmespath.org/ for more information and examples.') # pylint: disable=line-too-long
def register(application):
def handle_query_parameter(args):

Просмотреть файл

@ -106,7 +106,7 @@ class Test_argparse(unittest.TestCase):
with self.assertRaises(SystemExit):
app.execute('n1 -h'.split())
self.assertEqual(True, io.getvalue().startswith('\nCommand\n n1\n long description'))
self.assertEqual(True, io.getvalue().startswith('\nCommand\n az n1\n long description'))
@redirect_io
def test_help_long_description_and_short_description(self):
@ -131,7 +131,7 @@ class Test_argparse(unittest.TestCase):
with self.assertRaises(SystemExit):
app.execute('n1 -h'.split())
self.assertEqual(True, io.getvalue().startswith('\nCommand\n n1: short description\n long description'))
self.assertEqual(True, io.getvalue().startswith('\nCommand\n az n1: short description\n long description'))
@redirect_io
def test_help_docstring_description_overrides_short_description(self):
@ -185,7 +185,7 @@ class Test_argparse(unittest.TestCase):
with self.assertRaises(SystemExit):
app.execute('n1 -h'.split())
self.assertEqual(True, io.getvalue().startswith('\nCommand\n n1\n line1\n line2'))
self.assertEqual(True, io.getvalue().startswith('\nCommand\n az n1\n line1\n line2'))
@redirect_io
@mock.patch('azure.cli.application.Application.register', return_value=None)
@ -228,7 +228,7 @@ class Test_argparse(unittest.TestCase):
app.execute('n1 -h'.split())
s = '''
Command
n1
az n1
Arguments
--foobar2 -fb2 [Required]: one line partial sentence
@ -236,10 +236,11 @@ Arguments
--foobar -fb : one line partial sentence
text, markdown, etc.
Values from: az vm list, default
--foobar3 -fb3 : the foobar3
Global Arguments
--help -h : show this help message and exit
'''
self.assertEqual(s, io.getvalue())
@ -291,7 +292,7 @@ Arguments
app.execute('n1 -h'.split())
s = '''
Command
n1: this module does xyz one-line or so
az n1: this module does xyz one-line or so
this module.... kjsdflkj... klsfkj paragraph1
this module.... kjsdflkj... klsfkj paragraph2
@ -301,10 +302,12 @@ Arguments
--foobar -fb : one line partial sentence
text, markdown, etc.
Values from: az vm list, default
Global Arguments
--help -h : show this help message and exit
Examples
foo example
example details
@ -377,41 +380,42 @@ Examples
'.*Extra help param --foobar -fb.*',
lambda: app.execute('n1 -h'.split()))
# Will uncomment when partial params don't bypass help (help behaviors implementation) task #115631559
# @redirect_io
# def test_help_with_param_specified(self):
# app = Application(Configuration([]))
# def test_handler(args):
# pass
@redirect_io
@mock.patch('azure.cli.application.Application.register', return_value=None)
def test_help_with_param_specified(self, _):
app = Application(Configuration([]))
def test_handler(args):
pass
# cmd_table = {
# test_handler: {
# 'name': 'n1',
# 'arguments': [
# {'name': '--arg -a', 'required': False},
# {'name': '-b', 'required': False}
# ]
# }
# }
# config = Configuration([])
#config.get_command_table = lambda: cmd_table
#app = Application(config)
cmd_table = {
test_handler: {
'name': 'n1',
'arguments': [
{'name': '--arg -a', 'required': False},
{'name': '-b', 'required': False}
]
}
}
config = Configuration([])
config.get_command_table = lambda: cmd_table
app = Application(config)
# with self.assertRaises(SystemExit):
# cmd_result = app.execute('n1 --arg -h'.split())
with self.assertRaises(SystemExit):
cmd_result = app.execute('n1 --arg foo -h'.split())
# s = '''
#Command
# n1
s = '''
Command
az n1
#Arguments
# --arg -a
Arguments
--arg -a
-b
# -b
Global Arguments
--help -h: show this help message and exit
'''
#'''
# self.assertEqual(s, io.getvalue())
self.assertEqual(s, io.getvalue())
@redirect_io
def test_help_group_children(self):
@ -443,39 +447,42 @@ Examples
with self.assertRaises(SystemExit):
app.execute('group1 -h'.split())
s = '\nGroup\n group1\n\nSub-Commands\n group2\n group3\n\n'
s = '\nGroup\n az group1\n\nSub-Commands\n group2\n group3\n\n'
self.assertEqual(s, io.getvalue())
# Will uncomment when all errors are shown at once (help behaviors implementation) task #115631559
#@redirect_io
#def test_help_extra_missing_params(self):
# app = Application(Configuration([]))
# def test_handler(args):
# pass
@redirect_io
def test_help_extra_missing_params(self):
app = Application(Configuration([]))
def test_handler(args):
pass
# cmd_table = {
# test_handler: {
# 'name': 'n1',
# 'arguments': [
# {'name': '--foobar -fb', 'required': False},
# {'name': '--foobar2 -fb2', 'required': True}
# ]
# }
# }
# config = Configuration([])
#config.get_command_table = lambda: cmd_table
#app = Application(config)
cmd_table = {
test_handler: {
'name': 'n1',
'arguments': [
{'name': '--foobar -fb', 'required': False},
{'name': '--foobar2 -fb2', 'required': True}
]
}
}
config = Configuration([])
config.get_command_table = lambda: cmd_table
app = Application(config)
# with self.assertRaises(SystemExit):
# app.execute('n1 -fb a --foobar3 bad'.split())
# there is an argparse bug on <2.7.10 where SystemExit is not thrown on missing required param
if sys.version_info < (2, 7, 10):
app.execute('n1 -fb a --foobar value'.split())
app.execute('n1 -fb a --foobar2 value --foobar3 extra'.split())
else:
with self.assertRaises(SystemExit):
app.execute('n1 -fb a --foobar value'.split())
with self.assertRaises(SystemExit):
app.execute('n1 -fb a --foobar2 value --foobar3 extra'.split())
# with open(r'C:\temp\value.txt', 'w') as f:
# f.write(io.getvalue())
# self.assertTrue('required' in io.getvalue()
# and '--foobar/-fb' not in io.getvalue()
# and '--foobar2/-fb' in io.getvalue()
# and 'unrecognized arguments: --foobar3' in io.getvalue())
self.assertTrue('required' in io.getvalue()
and '--foobar/-fb' not in io.getvalue()
and '--foobar2/-fb2' in io.getvalue()
and 'unrecognized arguments: --foobar3 extra' in io.getvalue())
@redirect_io
def test_help_group_help(self):
@ -520,19 +527,74 @@ Examples
app.execute('test_group1 test_group2 --help'.split())
s = '''
Group
test_group1 test_group2: this module does xyz one-line or so
az test_group1 test_group2: this module does xyz one-line or so
this module.... kjsdflkj... klsfkj paragraph1
this module.... kjsdflkj... klsfkj paragraph2
Sub-Commands
n1: this module does xyz one-line or so
Examples
foo example
example details
'''
self.assertEqual(s, io.getvalue())
@redirect_io
@mock.patch('azure.cli.application.Application.register', return_value=None)
@mock.patch('azure.cli.extensions.register_extensions', return_value=None)
def test_help_global_params(self, mock_register_extensions, _):
def register_globals(global_group):
global_group.add_argument('--query2', dest='_jmespath_query', metavar='JMESPATH',
help='JMESPath query string. See http://jmespath.org/ '
'for more information and examples.')
mock_register_extensions.return_value = None
mock_register_extensions.side_effect = lambda app: \
app._event_handlers[app.GLOBAL_PARSER_CREATED].append(register_globals)
def test_handler(args):
pass
cmd_table = {
test_handler: {
'name': 'n1',
'help_file': '''
long-summary: |
line1
line2
''',
'arguments': [
{'name': '--arg -a', 'required': False},
{'name': '-b', 'required': False}
]
}
}
config = Configuration([])
config.get_command_table = lambda: cmd_table
app = Application(config)
with self.assertRaises(SystemExit):
app.execute('n1 -h'.split())
s = """
Command
az n1
line1
line2
Arguments
--arg -a
-b
Global Arguments
--help -h: show this help message and exit
--query2 : JMESPath query string. See http://jmespath.org/ for more information and examples.
"""
self.assertEqual(s, io.getvalue())
if __name__ == '__main__':
unittest.main()

Просмотреть файл

@ -35,7 +35,6 @@ class CommandTestGenerator(object):
def __init__(self, recording_dir, test_def, env_var):
self.test_def = test_def
self.recording_dir = recording_dir
logging.basicConfig()
logging.getLogger('vcr').setLevel(logging.ERROR)
self.my_vcr = vcr.VCR(
cassette_library_dir=recording_dir,

Просмотреть файл

@ -18,7 +18,7 @@ from ._validators import (
# FACTORIES
def storage_client_factory(**kwargs):
def storage_client_factory(**_):
return get_mgmt_service_client(StorageManagementClient, StorageManagementClientConfiguration)
def file_data_service_factory(**kwargs):

Просмотреть файл

@ -89,7 +89,7 @@ class ConvenienceBlobServiceCommands(object):
def __init__(self, **kwargs):
self.client = blob_data_service_factory(**kwargs)
def container_exists(self, container_name, snapshot=None, timeout=None, **kwargs):
def container_exists(self, container_name, snapshot=None, timeout=None):
'''Check if a storage container exists.
:param str snapshot:UTC datetime value which specifies a snapshot
'''
@ -101,7 +101,7 @@ class ConvenienceBlobServiceCommands(object):
def upload(self, container_name, blob_name, blob_type, upload_from,
content_type=None, content_disposition=None,
content_encoding=None, content_language=None, content_md5=None,
content_cache_control=None, **kwargs):
content_cache_control=None):
'''Upload a blob to a container.
:param str blob_type:type of blob to upload
:param str upload_from:local path to upload from
@ -145,7 +145,7 @@ class ConvenienceBlobServiceCommands(object):
}
return type_func[blob_type]()
def download(self, container_name, blob_name, download_to, **kwargs):
def download(self, container_name, blob_name, download_to):
''' Download the specified blob.
:param str download_to:the file path to download to
'''
@ -153,7 +153,7 @@ class ConvenienceBlobServiceCommands(object):
self.client.get_blob_to_path(container_name, blob_name, download_to,
progress_callback=_update_progress)
def blob_exists(self, container_name, blob_name, snapshot=None, timeout=None, **kwargs):
def blob_exists(self, container_name, blob_name, snapshot=None, timeout=None):
''' Check if a storage blob exists. '''
return self.client.exists(
blob_name=blob_name,
@ -166,22 +166,22 @@ class ConvenienceFileServiceCommands(object):
def __init__(self, **kwargs):
self.client = file_data_service_factory(**kwargs)
def share_exists(self, share_name, **kwargs):
def share_exists(self, share_name):
''' Check if a file share exists.'''
return self.client.exists(share_name=share_name)
def dir_exists(self, share_name, directory_name, **kwargs):
def dir_exists(self, share_name, directory_name):
''' Check if a share directory exists.'''
return self.client.exists(share_name=share_name, directory_name=directory_name)
def download(self, share_name, file_name, local_file_name, directory_name=None, **kwargs):
def download(self, share_name, file_name, local_file_name, directory_name=None):
''' Download a file from a file share.
:param str file_name:the file name
:param str local_file_name:the path to the local file to download to'''
self.client.get_file_to_path(share_name, directory_name, file_name, local_file_name,
progress_callback=_update_progress)
def file_exists(self, share_name, file_name, directory_name=None, **kwargs):
def file_exists(self, share_name, file_name, directory_name=None):
''' Check if a file exists at a specified path.
:param str file_name:the file name to check
:param str directory_name:subdirectory path to the file
@ -190,7 +190,7 @@ class ConvenienceFileServiceCommands(object):
directory_name=directory_name,
file_name=file_name)
def upload(self, share_name, file_name, local_file_name, directory_name=None, **kwargs):
def upload(self, share_name, file_name, local_file_name, directory_name=None):
''' Upload a file to a file share path.
:param str file_name:the destination file name
:param str local_file_name:the path and file name to upload

Просмотреть файл

@ -1,4 +1,5 @@
import argparse
import os
import re
from azure.mgmt.compute.operations import (AvailabilitySetsOperations,
@ -207,11 +208,25 @@ class VMImageFieldAction(argparse.Action): #pylint: disable=too-few-public-metho
else:
namespace.os_type = image
class VMSSHFieldAction(argparse.Action): #pylint: disable=too-few-public-methods
def __call__(self, parser, namespace, values, option_string=None):
ssh_value = values
if os.path.exists(ssh_value):
with open(ssh_value, 'r') as f:
namespace.ssh_key_value = f.read()
else:
namespace.ssh_key_value = ssh_value
extra_parameters = [
{
'name': '--image',
'help': 'The OS image. Supported values: Common OS (e.g. Win2012R2Datacenter), URN (e.g. "publisher:offer:sku:version"), or existing VHD URI.',
'action': VMImageFieldAction
},
{
'name': '--ssh-key-value',
'action': VMSSHFieldAction
}
]
@ -244,7 +259,7 @@ helps['vm create'] = """
--virtual-network-type existing --virtual-network-name myvnet --subnet-name default
--availability-set-type existing --availability-set-id myavailset
--public-ip-address-type new --dns-name-for-public-ip myGloballyUniqueVmDnsName
-l "West US" -g myvms --name myvm18o --ssh-key-value "<ssh-rsa-key>"
-l "West US" -g myvms --name myvm18o --ssh-key-value "<ssh-rsa-key or key-file-path>"
"""
build_operation('vm',

Просмотреть файл

@ -11,8 +11,9 @@
},
"adminPassword": {
"type": "securestring",
"defaultValue": "",
"metadata": {
"description": "Password for the Virtual Machine."
"description": "Password for the Virtual Machine. Required if SSH (Linux only) is not specified."
}
},
"adminUsername": {

Просмотреть файл

@ -38,7 +38,8 @@ class DeploymentVM(Model):
used to access the Virtual Machine.
:type dns_name_for_public_ip: str
:param storage_account_type: Whether to use an existing storage account
or create a new one.
or create a new one. Possible values include: 'new', 'existing'. Default
value: "new" .
:type storage_account_type: str
:param os_disk_uri: URI for a custom VHD image.
:type os_disk_uri: str
@ -48,16 +49,21 @@ class DeploymentVM(Model):
:param name: The VM resource name.
:type name: str
:param virtual_network_type: Whether to use an existing VNet or create a
new one.
new one. Possible values include: 'new', 'existing'. Default value:
"new" .
:type virtual_network_type: str
:param admin_password: Password for the Virtual Machine.
:param admin_password: Password for the Virtual Machine. Required if SSH
(Linux only) is not specified.
:type admin_password: str
:param os_sku: The OS SKU to install.
:type os_sku: str
:param subnet_name: The subnet name.
:type subnet_name: str
:param os_type: Common OS choices. Choose 'Custom' to specify an image
with the osPublisher, osOffer, osSKU, and osVersion parameters.
with the osPublisher, osOffer, osSKU, and osVersion parameters. Possible
values include: 'CentOS', 'CoreOS', 'Debian', 'openSUSE', 'RHEL',
'SLES', 'UbuntuLTS', 'Win2012R2Datacenter', 'Win2012Datacenter',
'Win2008R2SP1', 'Custom'. Default value: "Win2012R2Datacenter" .
:type os_type: str
:param admin_username: Username for the Virtual Machine.
:type admin_username: str
@ -70,10 +76,12 @@ class DeploymentVM(Model):
:param os_offer: The OS Offer to install.
:type os_offer: str
:param public_ip_address_allocation: Dynamic or Static public IP address
allocation.
allocation. Possible values include: 'Dynamic', 'Static'. Default value:
"Dynamic" .
:type public_ip_address_allocation: str
:param authentication_type: Authentication method: password-only or add
ssh-keys (Linux-only).
ssh-keys (Linux-only). Possible values include: 'password', 'sshkey'.
Default value: "password" .
:type authentication_type: str
:param storage_account_name: Name of storage account for the VM OS disk.
:type storage_account_name: str
@ -82,7 +90,8 @@ class DeploymentVM(Model):
:param size: The VM Size that should be created (e.g. Standard_A2)
:type size: str
:param public_ip_address_type: Use a public IP Address for the VM Nic.
(new, existing or none).
(new, existing or none). Possible values include: 'none', 'new',
'existing'. Default value: "none" .
:type public_ip_address_type: str
:param virtual_network_ip_address_prefix: The IP address prefix.
:type virtual_network_ip_address_prefix: str
@ -95,12 +104,14 @@ class DeploymentVM(Model):
:param os_publisher: The OS publisher of the OS image.
:type os_publisher: str
:param availability_set_type: Flag to add the VM to an existing
availability set.
availability set. Possible values include: 'none', 'existing'. Default
value: "none" .
:type availability_set_type: str
:param public_ip_address_name: Name of public IP address to use.
:type public_ip_address_name: str
:param dns_name_type: Associate VMs with a public IP address to a DNS
name (new or none).
name (new or none). Possible values include: 'none', 'new'. Default
value: "none" .
:type dns_name_type: str
:ivar mode: Gets or sets the deployment mode. Default value:
"Incremental" .
@ -111,7 +122,6 @@ class DeploymentVM(Model):
'uri': {'required': True, 'constant': True},
'_artifacts_location': {'constant': True},
'name': {'required': True},
'admin_password': {'required': True},
'admin_username': {'required': True},
'mode': {'required': True, 'constant': True},
}
@ -161,7 +171,7 @@ class DeploymentVM(Model):
mode = "Incremental"
def __init__(self, name, admin_password, admin_username, content_version=None, storage_container_name=None, virtual_network_name=None, subnet_ip_address_prefix=None, private_ip_address_allocation=None, dns_name_for_public_ip=None, storage_account_type=None, os_disk_uri=None, virtual_network_type=None, os_sku=None, subnet_name=None, os_type=None, os_version=None, os_disk_name=None, ssh_key_path=None, os_offer=None, public_ip_address_allocation=None, authentication_type=None, storage_account_name=None, storage_redundancy_type=None, size=None, public_ip_address_type=None, virtual_network_ip_address_prefix=None, availability_set_id=None, ssh_key_value=None, location=None, os_publisher=None, availability_set_type=None, public_ip_address_name=None, dns_name_type=None):
def __init__(self, name, admin_username, content_version=None, storage_container_name=None, virtual_network_name=None, subnet_ip_address_prefix=None, private_ip_address_allocation=None, dns_name_for_public_ip=None, storage_account_type="new", os_disk_uri=None, virtual_network_type="new", admin_password=None, os_sku=None, subnet_name=None, os_type="Win2012R2Datacenter", os_version=None, os_disk_name=None, ssh_key_path=None, os_offer=None, public_ip_address_allocation="Dynamic", authentication_type="password", storage_account_name=None, storage_redundancy_type=None, size=None, public_ip_address_type="none", virtual_network_ip_address_prefix=None, availability_set_id=None, ssh_key_value=None, location=None, os_publisher=None, availability_set_type="none", public_ip_address_name=None, dns_name_type="none"):
self.content_version = content_version
self.storage_container_name = storage_container_name
self.virtual_network_name = virtual_network_name

Просмотреть файл

@ -32,7 +32,7 @@ class VMOperations(object):
self.config = config
def create_or_update(
self, resource_group_name, deployment_name, name, admin_password, admin_username, content_version=None, storage_container_name=None, virtual_network_name=None, subnet_ip_address_prefix=None, private_ip_address_allocation=None, dns_name_for_public_ip=None, storage_account_type=None, os_disk_uri=None, virtual_network_type=None, os_sku=None, subnet_name=None, os_type=None, os_version=None, os_disk_name=None, ssh_key_path=None, os_offer=None, public_ip_address_allocation=None, authentication_type=None, storage_account_name=None, storage_redundancy_type=None, size=None, public_ip_address_type=None, virtual_network_ip_address_prefix=None, availability_set_id=None, ssh_key_value=None, location=None, os_publisher=None, availability_set_type=None, public_ip_address_name=None, dns_name_type=None, custom_headers={}, raw=False, **operation_config):
self, resource_group_name, deployment_name, name, admin_username, content_version=None, storage_container_name=None, virtual_network_name=None, subnet_ip_address_prefix=None, private_ip_address_allocation=None, dns_name_for_public_ip=None, storage_account_type="new", os_disk_uri=None, virtual_network_type="new", admin_password=None, os_sku=None, subnet_name=None, os_type="Win2012R2Datacenter", os_version=None, os_disk_name=None, ssh_key_path=None, os_offer=None, public_ip_address_allocation="Dynamic", authentication_type="password", storage_account_name=None, storage_redundancy_type=None, size=None, public_ip_address_type="none", virtual_network_ip_address_prefix=None, availability_set_id=None, ssh_key_value=None, location=None, os_publisher=None, availability_set_type="none", public_ip_address_name=None, dns_name_type="none", custom_headers={}, raw=False, **operation_config):
"""
Create or update a virtual machine.
@ -43,8 +43,6 @@ class VMOperations(object):
:type deployment_name: str
:param name: The VM resource name.
:type name: str
:param admin_password: Password for the Virtual Machine.
:type admin_password: str
:param admin_username: Username for the Virtual Machine.
:type admin_username: str
:param content_version: If included it must match the ContentVersion
@ -64,19 +62,26 @@ class VMOperations(object):
Public IP used to access the Virtual Machine.
:type dns_name_for_public_ip: str
:param storage_account_type: Whether to use an existing storage
account or create a new one.
account or create a new one. Possible values include: 'new',
'existing'
:type storage_account_type: str
:param os_disk_uri: URI for a custom VHD image.
:type os_disk_uri: str
:param virtual_network_type: Whether to use an existing VNet or
create a new one.
create a new one. Possible values include: 'new', 'existing'
:type virtual_network_type: str
:param admin_password: Password for the Virtual Machine. Required if
SSH (Linux only) is not specified.
:type admin_password: str
:param os_sku: The OS SKU to install.
:type os_sku: str
:param subnet_name: The subnet name.
:type subnet_name: str
:param os_type: Common OS choices. Choose 'Custom' to specify an
image with the osPublisher, osOffer, osSKU, and osVersion parameters.
image with the osPublisher, osOffer, osSKU, and osVersion
parameters. Possible values include: 'CentOS', 'CoreOS', 'Debian',
'openSUSE', 'RHEL', 'SLES', 'UbuntuLTS', 'Win2012R2Datacenter',
'Win2012Datacenter', 'Win2008R2SP1', 'Custom'
:type os_type: str
:param os_version: The OS version to install.
:type os_version: str
@ -87,10 +92,11 @@ class VMOperations(object):
:param os_offer: The OS Offer to install.
:type os_offer: str
:param public_ip_address_allocation: Dynamic or Static public IP
address allocation.
address allocation. Possible values include: 'Dynamic', 'Static'
:type public_ip_address_allocation: str
:param authentication_type: Authentication method: password-only or
add ssh-keys (Linux-only).
add ssh-keys (Linux-only). Possible values include: 'password',
'sshkey'
:type authentication_type: str
:param storage_account_name: Name of storage account for the VM OS
disk.
@ -100,7 +106,8 @@ class VMOperations(object):
:param size: The VM Size that should be created (e.g. Standard_A2)
:type size: str
:param public_ip_address_type: Use a public IP Address for the VM
Nic. (new, existing or none).
Nic. (new, existing or none). Possible values include: 'none',
'new', 'existing'
:type public_ip_address_type: str
:param virtual_network_ip_address_prefix: The IP address prefix.
:type virtual_network_ip_address_prefix: str
@ -113,12 +120,12 @@ class VMOperations(object):
:param os_publisher: The OS publisher of the OS image.
:type os_publisher: str
:param availability_set_type: Flag to add the VM to an existing
availability set.
availability set. Possible values include: 'none', 'existing'
:type availability_set_type: str
:param public_ip_address_name: Name of public IP address to use.
:type public_ip_address_name: str
:param dns_name_type: Associate VMs with a public IP address to a DNS
name (new or none).
name (new or none). Possible values include: 'none', 'new'
:type dns_name_type: str
:param dict custom_headers: headers that will be added to the request
:param bool raw: returns the direct response alongside the

Просмотреть файл

@ -90,7 +90,6 @@
"osProfile": {
"computerName": "[variables('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]",
"linuxConfiguration": {
"disablePasswordAuthentication": true,
"ssh": {

Просмотреть файл

@ -88,7 +88,6 @@
"osProfile": {
"computerName": "[variables('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]",
"linuxConfiguration": {
"disablePasswordAuthentication": true,
"ssh": {

Просмотреть файл

@ -327,7 +327,6 @@
}
},
"required": [
"adminPassword",
"adminUsername",
"virtualMachineName"
]
@ -382,6 +381,11 @@
"value": {
"type": "string",
"description": "Whether to use an existing storage account or create a new one.",
"enum": [
"new",
"existing"
],
"default": "new",
"x-ms-client-name": "storageAccountType"
}
}
@ -427,6 +431,11 @@
"value": {
"type": "string",
"description": "Whether to use an existing VNet or create a new one.",
"enum": [
"new",
"existing"
],
"default": "new",
"x-ms-client-name": "virtualNetworkType"
}
}
@ -435,14 +444,10 @@
"properties": {
"value": {
"type": "string",
"description": "Password for the Virtual Machine.",
"description": "Password for the Virtual Machine. Required if SSH (Linux only) is not specified.",
"x-ms-client-name": "adminPassword"
}
},
"required": [
"value"
]
}
},
"DeploymentParameter_osSKU": {
"properties": {
@ -467,6 +472,20 @@
"value": {
"type": "string",
"description": "Common OS choices. Choose 'Custom' to specify an image with the osPublisher, osOffer, osSKU, and osVersion parameters.",
"enum": [
"CentOS",
"CoreOS",
"Debian",
"openSUSE",
"RHEL",
"SLES",
"UbuntuLTS",
"Win2012R2Datacenter",
"Win2012Datacenter",
"Win2008R2SP1",
"Custom"
],
"default": "Win2012R2Datacenter",
"x-ms-client-name": "osType"
}
}
@ -525,6 +544,11 @@
"value": {
"type": "string",
"description": "Dynamic or Static public IP address allocation.",
"enum": [
"Dynamic",
"Static"
],
"default": "Dynamic",
"x-ms-client-name": "publicIpAddressAllocation"
}
}
@ -534,6 +558,11 @@
"value": {
"type": "string",
"description": "Authentication method: password-only or add ssh-keys (Linux-only).",
"enum": [
"password",
"sshkey"
],
"default": "password",
"x-ms-client-name": "authenticationType"
}
}
@ -570,6 +599,12 @@
"value": {
"type": "string",
"description": "Use a public IP Address for the VM Nic. (new, existing or none).",
"enum": [
"none",
"new",
"existing"
],
"default": "none",
"x-ms-client-name": "publicIpAddressType"
}
}
@ -624,6 +659,11 @@
"value": {
"type": "string",
"description": "Flag to add the VM to an existing availability set.",
"enum": [
"none",
"existing"
],
"default": "none",
"x-ms-client-name": "availabilitySetType"
}
}
@ -642,6 +682,11 @@
"value": {
"type": "string",
"description": "Associate VMs with a public IP address to a DNS name (new or none).",
"enum": [
"none",
"new"
],
"default": "none",
"x-ms-client-name": "dnsNameType"
}
}