Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions azure-cli.pyproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<Compile Include="azure\cli\commands\_command_creation.py" />
<Compile Include="azure\cli\commands\__init__.py" />
<Compile Include="azure\cli\command_modules\__init__.py" />
<Compile Include="azure\cli\extensions\id.py" />
<Compile Include="azure\cli\extensions\query.py">
<SubType>Code</SubType>
</Compile>
Expand Down
9 changes: 9 additions & 0 deletions src/azure/cli/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Application(object):
COMMAND_PARSER_CREATED = 'CommandParser.Created'
COMMAND_PARSER_LOADED = 'CommandParser.Loaded'
COMMAND_PARSER_PARSED = 'CommandParser.Parsed'
COMMAND_TABLE_LOADED = 'CommandTable.Loaded'

def __init__(self, configuration):
self._event_handlers = defaultdict(lambda: [])
Expand All @@ -45,6 +46,7 @@ def __init__(self, configuration):
self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments)
self.register(self.COMMAND_PARSER_LOADED, Application._enable_autocomplete)
self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments)
self.register(self.COMMAND_PARSER_PARSED, self._resolve_computed_values)

# Let other extensions make their presence known
azure.cli.extensions.register_extensions(self)
Expand All @@ -57,6 +59,7 @@ def __init__(self, configuration):

def execute(self, argv):
command_table = self.configuration.get_command_table()
self.raise_event(self.COMMAND_TABLE_LOADED, command_table)
self.parser.load_command_table(command_table)
self.raise_event(self.COMMAND_PARSER_LOADED, self.parser)

Expand Down Expand Up @@ -148,3 +151,9 @@ def _register_builtin_arguments(parser):
def _handle_builtin_arguments(self, args):
self.configuration.output_format = args._output_format #pylint: disable=protected-access
del args._output_format

@staticmethod
def _resolve_computed_values(args):
for value in args.__dict__.values():
if callable(value) and getattr(value, 'computed', False):
value(args)
19 changes: 16 additions & 3 deletions src/azure/cli/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@

COMMON_PARAMETERS = {
'resource_group_name': {
'name': '--resource-group -g',
'dest': RESOURCE_GROUP_ARG_NAME,
'name': 'resource_group_name',
'metavar': 'RESOURCEGROUP',
'help': 'The name of the resource group.',
'required': True
'_semantic_type': 'resource_group_name',
},
'location': {
'name': '--location -l',
Expand Down Expand Up @@ -116,6 +115,20 @@ def wrapper(func):
return func
return wrapper

def computed_value(func):
'''For options that have values that are computed based on the
value of other arguments, we extend the "normal" argparse options
to allow for a computed default value.

Example:
{ name='--hello -h', 'default': computed_value(lambda args: return args.foo + args.bar) }
'''
def wrapped(args):
return func(args)

setattr(wrapped, 'computed', True)
return wrapped

def _get_command_table(module_name):
module = import_module('azure.cli.command_modules.' + module_name)
return module.command_table
Expand Down
2 changes: 2 additions & 0 deletions src/azure/cli/extensions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from .query import register as register_query
from .transform import register as register_transform
from .id import register as register_id

def register_extensions(application):
register_query(application)
register_transform(application)
register_id(application)
52 changes: 52 additions & 0 deletions src/azure/cli/extensions/id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import argparse
from azure.cli.commands import computed_value

def register(application):
def split_id(source, target):
def split(namespace):
try:
idstr = getattr(namespace, source)
parts = idstr.split('/')
setattr(namespace, target, parts[8])
setattr(namespace, source, parts[4])
except Exception:
raise RuntimeError(
'Invalid RESOURCEID "{0}". You have to specify a RESOURCEGROUP and NAME or a valid RESOURCEID' # pylint: disable=line-too-long
.format(idstr))
return split

def annotate_id(command_table):
for command in command_table.values():
arguments = command['arguments']
rg_name = {arg['_semantic_type']: arg
for arg in arguments
if arg.get('_semantic_type', None) in ('resource_group_name',
'resource_name')}
try:
rg = rg_name['resource_group_name']
name = rg_name['resource_name']
arguments.remove(rg)
arguments.remove(name)
name_dest = name.get('dest') or \
name.get('name').split()[0].replace('--', '', 1).replace('-', '_')
arguments.extend((
{
'name': rg.get('dest', 'resource_group_name'),
'metavar': '(RESOURCEID | %s %s)' % (rg.get('metavar', 'RESOURCEGROUP'),
name.get('metavar', 'NAME')),
'help': 'Resource ID or resource group name followed by resource name',
},
{
'name': name_dest,
'help': argparse.SUPPRESS,
'default': computed_value(
split_id(
rg.get('dest', 'resource_group_name'),
name.get('dest'))),
'nargs': '?'
}))
except KeyError:
pass

application.register(application.COMMAND_TABLE_LOADED, annotate_id)

11 changes: 9 additions & 2 deletions src/azure/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ def load_command_table(self, command_table):
parents=self.parents, conflict_handler='resolve',
help_file=metadata.get('help_file'))
for arg in metadata['arguments']:
names = arg.get('name').split()
command_parser.add_argument(*names, **{k:v for k, v in arg.items() if k != 'name'})
names = (arg.get('name') or '').split()
command_parser.add_argument(*names, **{k:v for k, v in arg.items()
if k != 'name' and not k.startswith('_')})
command_parser.set_defaults(func=handler)

def _get_subparser(self, path):
Expand Down Expand Up @@ -67,6 +68,12 @@ def _get_subparser(self, path):
self.subparsers[tuple(path[0:length])] = parent_subparser
return parent_subparser

def add_argument(self, *args, **kwargs):
if args and not args[0].startswith('-'):
kwargs.pop('dest', None)
kwargs.pop('required', None)
return super(AzCliCommandParser, self).add_argument(*args, **kwargs)

def format_help(self):
is_group = not self._defaults.get('func')
_help.show_help(self.prog.split()[1:],
Expand Down
7 changes: 4 additions & 3 deletions src/azure/cli/tests/test_autocommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ def test_autocommand_basic(self):
self.assertEqual(command_metadata['name'], 'test autocommand sample-vm-get', 'Unexpected command name...')
self.assertEqual(len(command_metadata['arguments']), 4, 'We expected exactly 4 arguments')
some_expected_arguments = [
{'name': '--resource-group -g', 'dest': 'resource_group_name', 'required': True, 'help':'The name of the resource group.'},
{'name': 'resource_group_name', 'help':'The name of the resource group.'},
{'name': '--vm-name', 'dest': 'vm_name', 'required': True, 'help': 'The name of the virtual machine.'},
{'name': '--opt-param', 'required': False, 'help': 'Used to verify auto-command correctly identifies optional params.'},
{'name': '--expand', 'required': False, 'help': 'The expand expression to apply on the operation.'},
]

print(command_metadata['arguments'])
for probe in some_expected_arguments:
existing = [arg for arg in command_metadata['arguments'] if arg['name'] == probe['name']][0]
self.assertDictContainsSubset(probe, existing)
Expand Down Expand Up @@ -89,7 +90,7 @@ def test_autocommand_with_parameter_alias(self):
self.assertEqual(command_metadata['name'], 'test autocommand sample-vm-get', 'Unexpected command name...')
self.assertEqual(len(command_metadata['arguments']), 4, 'We expected exactly 4 arguments')
some_expected_arguments = [
{'name': '--resource-group -g', 'dest': 'resource_group_name', 'required': True},
{'name': 'resource_group_name' },
{'name': '--wonky-name -n', 'dest': 'vm_name', 'required': False},
]

Expand Down Expand Up @@ -122,7 +123,7 @@ def test_autocommand_with_extra_parameters(self):
self.assertEqual(command_metadata['name'], 'test autocommand sample-vm-get', 'Unexpected command name...')
self.assertEqual(len(command_metadata['arguments']), 5, 'We expected exactly 5 arguments')
some_expected_arguments = [
{'name': '--resource-group -g', 'dest': 'resource_group_name', 'required': True},
{'name': 'resource_group_name' },
{'name': '--vm-name', 'dest': 'vm_name', 'required': True},
{'name': '--added-param', 'required': True},
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def _network_client_factory(_):
],
command_table,
{
'application_gateway_name': {'name': '--name -n'}
'application_gateway_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})

# ExpressRouteCircuitAuthorizationsOperations
Expand All @@ -67,7 +67,7 @@ def _network_client_factory(_):
],
command_table,
{
'authorization_name': {'name': '--name -n'}
'authorization_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})


Expand All @@ -82,7 +82,7 @@ def _network_client_factory(_):
],
command_table,
{
'peering_name': {'name': '--name -n'}
'peering_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})

# ExpressRouteCircuitsOperations
Expand All @@ -100,7 +100,7 @@ def _network_client_factory(_):
],
command_table,
{
'circuit_name': {'name': '--name -n'}
'circuit_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})

# ExpressRouteServiceProvidersOperations
Expand All @@ -124,7 +124,7 @@ def _network_client_factory(_):
],
command_table,
{
'load_balancer_name': {'name': '--name -n'}
'load_balancer_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})

# LocalNetworkGatewaysOperations
Expand All @@ -138,7 +138,7 @@ def _network_client_factory(_):
],
command_table,
{
'local_network_gateway_name': {'name': '--name -n'}
'local_network_gateway_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})


Expand All @@ -154,7 +154,7 @@ def _network_client_factory(_):
],
command_table,
{
'network_interface_name': {'name': '--name -n'}
'network_interface_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})

# NetworkInterfacesOperations: scaleset
Expand All @@ -169,7 +169,7 @@ def _network_client_factory(_):
command_table,
{
'virtual_machine_scale_set_name': {'name': '--vm-scale-set'},
'network_interface_name': {'name': '--name -n'},
'network_interface_name': {'name': '--name -n', '_semantic_type': 'resource_name', },
'virtualmachine_index': {'name': '--vm-index'}
})

Expand All @@ -185,7 +185,7 @@ def _network_client_factory(_):
],
command_table,
{
'network_security_group_name': {'name': '--name -n'}
'network_security_group_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})

# PublicIPAddressesOperations
Expand All @@ -200,7 +200,7 @@ def _network_client_factory(_):
],
command_table,
{
'public_ip_address_name': {'name': '--name -n'}
'public_ip_address_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})

# RouteTablesOperations
Expand All @@ -215,7 +215,7 @@ def _network_client_factory(_):
],
command_table,
{
'route_table_name': {'name': '--name -n'}
'route_table_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})


Expand All @@ -230,7 +230,7 @@ def _network_client_factory(_):
],
command_table,
{
'route_name': {'name': '--name -n'}
'route_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})

# SecurityRulesOperations
Expand All @@ -244,8 +244,8 @@ def _network_client_factory(_):
],
command_table,
{
'security_rule_name': {'name': '--name'},
'network_security_group_name': {'name': '--nsg-name'}
'security_rule_name': {'name': '--name', '_semantic_type': 'resource_name', },
'network_security_group_name': {'name': '--nsg-name', '_semantic_type': 'resource_name', }
})

# SubnetsOperations
Expand All @@ -259,8 +259,8 @@ def _network_client_factory(_):
],
command_table,
{
'subnet_name': {'name': '--name -n'},
'virtual_network_name': {'name': _VNET_PARAM_NAME}
'subnet_name': {'name': '--name -n', '_semantic_type': 'resource_name', },
'virtual_network_name': {'name': _VNET_PARAM_NAME, '_semantic_type': 'resource_name', }
})

# UsagesOperations
Expand All @@ -283,7 +283,7 @@ def _network_client_factory(_):
],
command_table,
{
'virtual_network_gateway_connection_name': {'name': '--name -n'}
'virtual_network_gateway_connection_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})

# VirtualNetworkGatewayConnectionsOperations: shared-key
Expand All @@ -297,8 +297,8 @@ def _network_client_factory(_):
],
command_table,
{
'virtual_network_gateway_connection_name': {'name': '--connection-name'},
'connection_shared_key_name': {'name': '--name -n'}
'virtual_network_gateway_connection_name': {'name': '--connection-name', '_semantic_type': 'resource_name', },
'connection_shared_key_name': {'name': '--name -n', '_semantic_type': 'resource_name', }

})

Expand All @@ -314,7 +314,7 @@ def _network_client_factory(_):
],
command_table,
{
'virtual_network_gateway_name': {'name': '--name -n'}
'virtual_network_gateway_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})

# VirtualNetworksOperations
Expand All @@ -329,7 +329,7 @@ def _network_client_factory(_):
],
command_table,
{
'virtual_network_name': {'name': '--name -n'}
'virtual_network_name': {'name': '--name -n', '_semantic_type': 'resource_name', }
})

# BUG: we are waiting on autorest to support this rename (https://github.com/Azure/autorest/issues/941)
Expand Down Expand Up @@ -369,8 +369,8 @@ def _network_client_factory(_):
@command_table.command('network subnet create')
@command_table.description(L('Create or update a virtual network (VNet) subnet'))
@command_table.option(**COMMON_PARAMETERS['resource_group_name'])
@command_table.option('--name -n', help=L('the subnet name'), required=True)
@command_table.option(_VNET_PARAM_NAME, help=L('the name of the vnet'), required=True)
@command_table.option('--subnet-name -s', help=L('the subnet name'), required=True)
@command_table.option(_VNET_PARAM_NAME, help=L('the name of the vnet'), required=True, _semantic_type='resource_name')
@command_table.option('--address-prefix -a', help=L('the the address prefix in CIDR format'), required=True)
def create_update_subnet(args):
from azure.mgmt.network.models import Subnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
{
'test_name': 'network_nic_list',
'command': 'network nic list -g travistestresourcegroup'
'command': 'network nic list travistestresourcegroup'
}
]

Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ def list_groups(args):
@command_table.description(
L('Show details of a specific resource in a resource group or subscription'))
@command_table.option(**COMMON_PARAMETERS['resource_group_name'])
@command_table.option('--name -n', help=L('the resource name'), required=True)
@command_table.option('--name -n', help=L('the resource name'), required=True,
_semantic_type='resource_name')
@command_table.option('--resource-type -r',
help=L('the resource type in format: <provider-namespace>/<type>'),
required=True)
Expand Down
Loading