diff --git a/azure-cli.pyproj b/azure-cli.pyproj index 0fa304524fa..4b08683558d 100644 --- a/azure-cli.pyproj +++ b/azure-cli.pyproj @@ -14,6 +14,9 @@ Standard Python launcher {1dd9c42b-5980-42ce-a2c3-46d3bf0eede4} 3.5 + + + False @@ -25,12 +28,19 @@ + + + + Code + + Code + Code @@ -43,6 +53,7 @@ Code + Code diff --git a/src/azure/cli/_argparse.py b/src/azure/cli/_argparse.py index 5e0e7f9c3b9..2e46c9186f4 100644 --- a/src/azure/cli/_argparse.py +++ b/src/azure/cli/_argparse.py @@ -208,7 +208,7 @@ def not_global(a): return handler(parsed, others) except IncorrectUsageError as ex: print(str(ex), file=out) - return self.display_usage(nouns, m, args, out) + return self._display_usage(nouns, m, args, out) finally: sys.stdout = old_stdout diff --git a/src/azure/cli/commands/__init__.py b/src/azure/cli/commands/__init__.py index df622e9c0a7..f4172e18b04 100644 --- a/src/azure/cli/commands/__init__.py +++ b/src/azure/cli/commands/__init__.py @@ -3,11 +3,13 @@ # TODO: Alternatively, simply scan the directory for all modules COMMAND_MODULES = [ + 'account', 'login', 'logout', - 'account', + 'network', + 'resourcegroup', 'storage', - 'resourcegroup' + 'vm', ] _COMMANDS = {} diff --git a/src/azure/cli/commands/_auto_command.py b/src/azure/cli/commands/_auto_command.py new file mode 100644 index 00000000000..e79f8f28e2d --- /dev/null +++ b/src/azure/cli/commands/_auto_command.py @@ -0,0 +1,58 @@ +import inspect +from msrest import Serializer +from ..commands import command, description, option +from azure.cli._argparse import IncorrectUsageError + +def _decorate_command(name, func): + return command(name)(func) + +def _decorate_description(desc, func): + return description(desc)(func) + +def _decorate_option(spec, descr, func): + return option(spec, descr)(func) + +def _make_func(client_factory, member_name, return_type_name, unbound_func): + def call_client(args, unexpected): #pylint: disable=unused-argument + client = client_factory() + ops_instance = getattr(client, member_name) + try: + result = unbound_func(ops_instance, **args) + if not return_type_name: + return {} + return Serializer().serialize_data(result, return_type_name) + except TypeError as exception: + # TODO: Evaluate required/missing parameters and provide specific + # usage for missing params... + raise IncorrectUsageError(exception) + + return call_client + +def _option_description(operation, arg): + """Pull out parameter help from doccomments of the command + """ + # TODO: We are currently doing this for every option/argument. + # We should do it (at most) once for a given command... + return ' '.join(l.split(':')[-1] for l in inspect.getdoc(operation).splitlines() + if l.startswith(':param') and arg + ':' in l) + +EXCLUDED_PARAMS = frozenset(['self', 'raw', 'custom_headers', 'operation_config']) + +def operation_builder(package_name, resource_type, member_name, client_type, operations): + for operation, return_type_name in operations: + opname = operation.__name__ + func = _make_func(client_type, member_name, return_type_name, operation) + func = _decorate_command(' '.join([package_name, resource_type, opname]), func) + + args = [] + try: + # only supported in python3 - falling back to argspec if not available + sig = inspect.signature(operation) + args = sig.parameters + except AttributeError: + sig = inspect.getargspec(operation) #pylint: disable=deprecated-method + args = sig.args + + for arg in [a for a in args if not a in EXCLUDED_PARAMS]: + spec = '--%s <%s>' % (arg, arg) + func = _decorate_option(spec, _option_description(operation, arg), func=func) diff --git a/src/azure/cli/commands/network.py b/src/azure/cli/commands/network.py new file mode 100644 index 00000000000..08fff22a772 --- /dev/null +++ b/src/azure/cli/commands/network.py @@ -0,0 +1,241 @@ +from azure.mgmt.network import NetworkManagementClient, NetworkManagementClientConfiguration +from azure.mgmt.network.operations import (ApplicationGatewaysOperations, + ExpressRouteCircuitAuthorizationsOperations, + ExpressRouteCircuitPeeringsOperations, + ExpressRouteCircuitsOperations, + ExpressRouteServiceProvidersOperations, + LoadBalancersOperations, + LocalNetworkGatewaysOperations, + NetworkInterfacesOperations, + NetworkSecurityGroupsOperations, + PublicIPAddressesOperations, + RouteTablesOperations, + RoutesOperations, + SecurityRulesOperations, + SubnetsOperations, + UsagesOperations, + VirtualNetworkGatewayConnectionsOperations, + VirtualNetworkGatewaysOperations, + VirtualNetworksOperations) + +from ._command_creation import get_service_client +from ..commands import _auto_command + +def _network_client_factory(): + return get_service_client(NetworkManagementClient, NetworkManagementClientConfiguration) + +# pylint: disable=line-too-long +# Application gateways +_auto_command.operation_builder("network", + "appgateway", + "application_gateways", + _network_client_factory, + [ + (ApplicationGatewaysOperations.delete, None), + (ApplicationGatewaysOperations.get, 'ApplicationGateway'), + (ApplicationGatewaysOperations.list, '[ApplicationGateway]'), + (ApplicationGatewaysOperations.list_all, '[ApplicationGateway]'), + (ApplicationGatewaysOperations.start, None), + (ApplicationGatewaysOperations.stop, None), + ]) + +# ExpressRouteCircuitAuthorizationsOperations +_auto_command.operation_builder("network", + "expressroutecircuitauth", + "express_route_circuit_authorizations", + _network_client_factory, + [ + (ExpressRouteCircuitAuthorizationsOperations.delete, None), + (ExpressRouteCircuitAuthorizationsOperations.get, 'ExpressRouteCircuitAuthorization'), + (ExpressRouteCircuitAuthorizationsOperations.list, '[ExpressRouteCircuitAuthorization]'), + ]) + +# ExpressRouteCircuitPeeringsOperations +_auto_command.operation_builder("network", + "expressroutecircuitpeering", + "express_route_circuit_peerings", + _network_client_factory, + [ + (ExpressRouteCircuitPeeringsOperations.delete, None), + (ExpressRouteCircuitPeeringsOperations.get, 'ExpressRouteCircuitPeering'), + (ExpressRouteCircuitPeeringsOperations.list, '[ExpressRouteCircuitPeering]'), + ]) + +# ExpressRouteCircuitsOperations +_auto_command.operation_builder("network", + "expressroutecircuit", + "express_route_circuits", + _network_client_factory, + [ + (ExpressRouteCircuitsOperations.delete, None), + (ExpressRouteCircuitsOperations.get, 'ExpressRouteCircuit'), + (ExpressRouteCircuitsOperations.list_arp_table, '[ExpressRouteCircuitArpTable]'), + (ExpressRouteCircuitsOperations.list_routes_table, '[ExpressRouteCircuitRoutesTable]'), + (ExpressRouteCircuitsOperations.list_stats, '[ExpressRouteCircuitStats]'), + (ExpressRouteCircuitsOperations.list, '[ExpressRouteCircuit]'), + (ExpressRouteCircuitsOperations.list_all, '[ExpressRouteCircuit]'), + ]) + +# ExpressRouteServiceProvidersOperations +_auto_command.operation_builder("network", + "expressroutesp", + "express_route_service_providers", + _network_client_factory, + [ + (ExpressRouteServiceProvidersOperations.list, '[ExpressRouteServiceProvider]'), + ]) + +# LoadBalancersOperations +_auto_command.operation_builder("network", + "lb", + "load_balancers", + _network_client_factory, + [ + (LoadBalancersOperations.delete, None), + (LoadBalancersOperations.get, 'LoadBalancer'), + (LoadBalancersOperations.list_all, '[LoadBalancer]'), + (LoadBalancersOperations.list, '[LoadBalancer]'), + ]) + +# LocalNetworkGatewaysOperations +_auto_command.operation_builder("network", + "localgateways", + "local_network_gateways", + _network_client_factory, + [ + (LocalNetworkGatewaysOperations.get, 'LocalNetworkGateway'), + (LocalNetworkGatewaysOperations.delete, None), + (LocalNetworkGatewaysOperations.list, '[LocalNetworkGateway]'), + ]) + + +# NetworkInterfacesOperations +_auto_command.operation_builder("network", + "nic", + "network_interfaces", + _network_client_factory, + [ + (NetworkInterfacesOperations.delete, None), + (NetworkInterfacesOperations.get, 'NetworkInterface'), + (NetworkInterfacesOperations.list_virtual_machine_scale_set_vm_network_interfaces, '[NetworkInterface]'), + (NetworkInterfacesOperations.list_virtual_machine_scale_set_network_interfaces, '[NetworkInterface]'), + (NetworkInterfacesOperations.get_virtual_machine_scale_set_network_interface, 'NetworkInterface'), + (NetworkInterfacesOperations.list_all, '[NetworkInterface]'), + (NetworkInterfacesOperations.list, '[NetworkInterface]'), + ]) + +# NetworkSecurityGroupsOperations +_auto_command.operation_builder("network", + "securitygroup", + "network_security_groups", + _network_client_factory, + [ + (NetworkSecurityGroupsOperations.delete, None), + (NetworkSecurityGroupsOperations.delete, 'NetworkSecurityGroup'), + (NetworkSecurityGroupsOperations.list_all, '[NetworkSecurityGroup]'), + (NetworkSecurityGroupsOperations.list, '[NetworkSecurityGroup]'), + ]) + +# PublicIPAddressesOperations +_auto_command.operation_builder("network", + "publicipaddress", + "public_ip_addresses", + _network_client_factory, + [ + (PublicIPAddressesOperations.delete, None), + (PublicIPAddressesOperations.get, 'PublicIPAddress'), + (PublicIPAddressesOperations.list_all, '[PublicIPAddress]'), + (PublicIPAddressesOperations.list, '[PublicIPAddress]'), + ]) + +# RouteTablesOperations +_auto_command.operation_builder("network", + "routetable", + "route_tables", + _network_client_factory, + [ + (RouteTablesOperations.delete, None), + (RouteTablesOperations.get, 'RouteTable'), + (RouteTablesOperations.list, '[RouteTable]'), + (RouteTablesOperations.list_all, '[RouteTable]'), + ]) + +# RoutesOperations +_auto_command.operation_builder("network", + "routeoperation", + "routes", + _network_client_factory, + [ + (RoutesOperations.delete, None), + (RoutesOperations.get, 'Route'), + (RoutesOperations.list, '[Route]'), + ]) + +# SecurityRulesOperations +_auto_command.operation_builder("network", + "securityrules", + "security_rules", + _network_client_factory, + [ + (SecurityRulesOperations.delete, None), + (SecurityRulesOperations.get, 'SecurityRule'), + (SecurityRulesOperations.list, '[SecurityRule]'), + ]) + +# SubnetsOperations +_auto_command.operation_builder("network", + "subnet", + "subnets", + _network_client_factory, + [ + (SubnetsOperations.delete, None), + (SubnetsOperations.get, 'Subnet'), + (SubnetsOperations.list, '[Subnet]'), + ]) + +# UsagesOperations +_auto_command.operation_builder("network", + "usage", + "usages", + _network_client_factory, + [ + (UsagesOperations.list, '[Usage]'), + ]) + +# VirtualNetworkGatewayConnectionsOperations +_auto_command.operation_builder("network", + "vnetgatewayconnection", + "virtual_network_gateway_connections", + _network_client_factory, + [ + (VirtualNetworkGatewayConnectionsOperations.delete, None), + (VirtualNetworkGatewayConnectionsOperations.get, 'VirtualNetworkGatewayConnection'), + (VirtualNetworkGatewayConnectionsOperations.get_shared_key, 'ConnectionSharedKeyResult'), + (VirtualNetworkGatewayConnectionsOperations.list, '[VirtualNetworkGatewayConnection]'), + (VirtualNetworkGatewayConnectionsOperations.reset_shared_key, 'ConnectionResetSharedKey'), + (VirtualNetworkGatewayConnectionsOperations.set_shared_key, 'ConnectionSharedKey'), + ]) + +# VirtualNetworkGatewaysOperations +_auto_command.operation_builder("network", + "vnetgateway", + "virtual_network_gateways", + _network_client_factory, + [ + (VirtualNetworkGatewaysOperations.delete, None), + (VirtualNetworkGatewaysOperations.get, 'VirtualNetworkGateway'), + (VirtualNetworkGatewaysOperations.list, '[VirtualNetworkGateway]'), + (VirtualNetworkGatewaysOperations.reset, 'VirtualNetworkGateway'), + ]) + +# VirtualNetworksOperations +_auto_command.operation_builder("network", + "vnet", + "virtual_networks", + _network_client_factory, + [ + (VirtualNetworksOperations.delete, None), + (VirtualNetworksOperations.get, 'VirtualNetwork'), + (VirtualNetworksOperations.list, '[VirtualNetwork]'), + (VirtualNetworksOperations.list_all, '[VirtualNetwork]'), + ]) diff --git a/src/azure/cli/commands/vm.py b/src/azure/cli/commands/vm.py new file mode 100644 index 00000000000..3cd18407c5f --- /dev/null +++ b/src/azure/cli/commands/vm.py @@ -0,0 +1,127 @@ +from azure.mgmt.compute import ComputeManagementClient, ComputeManagementClientConfiguration +from azure.mgmt.compute.operations import (AvailabilitySetsOperations, + VirtualMachineExtensionImagesOperations, + VirtualMachineExtensionsOperations, + VirtualMachineImagesOperations, + UsageOperations, + VirtualMachineSizesOperations, + VirtualMachinesOperations, + VirtualMachineScaleSetsOperations, + VirtualMachineScaleSetVMsOperations) + +from ._command_creation import get_service_client +from ..commands import _auto_command + +def _compute_client_factory(): + return get_service_client(ComputeManagementClient, ComputeManagementClientConfiguration) + +# pylint: disable=line-too-long +_auto_command.operation_builder("vm", + "availabilityset", + "availability_sets", + _compute_client_factory, + [ + (AvailabilitySetsOperations.delete, None), + (AvailabilitySetsOperations.get, 'AvailabilitySet'), + (AvailabilitySetsOperations.list, '[AvailabilitySet]'), + (AvailabilitySetsOperations.list_available_sizes, '[VirtualMachineSize]') + ]) + + +_auto_command.operation_builder("vm", + "machineextensionimages", + "virtual_machine_extension_images", + _compute_client_factory, + [ + (VirtualMachineExtensionImagesOperations.get, 'VirtualMachineExtensionImage'), + (VirtualMachineExtensionImagesOperations.list_types, '[VirtualMachineImageResource]'), + (VirtualMachineExtensionImagesOperations.list_versions, '[VirtualMachineImageResource]'), + ]) + +_auto_command.operation_builder("vm", + "extensions", + "virtual_machine_extensions", + _compute_client_factory, + [ + (VirtualMachineExtensionsOperations.delete, None), + (VirtualMachineExtensionsOperations.get, 'VirtualMachineExtension'), + ]) + +_auto_command.operation_builder("vm", + "image", + "virtual_machine_images", + _compute_client_factory, + [ + (VirtualMachineImagesOperations.get, 'VirtualMachineImage'), + (VirtualMachineImagesOperations.list, '[VirtualMachineImageResource]'), + (VirtualMachineImagesOperations.list_offers, '[VirtualMachineImageResource]'), + (VirtualMachineImagesOperations.list_publishers, '[VirtualMachineImageResource]'), + (VirtualMachineImagesOperations.list_skus, '[VirtualMachineImageResource]'), + ]) + +_auto_command.operation_builder("vm", + "usage", + "usage", + _compute_client_factory, + [ + (UsageOperations.list, '[Usage]'), + ]) + +_auto_command.operation_builder("vm", + "size", + "virtual_machine_sizes", + _compute_client_factory, + [ + (VirtualMachineSizesOperations.list, '[VirtualMachineSize]'), + ]) + +_auto_command.operation_builder("vm", + "", + "virtual_machines", + _compute_client_factory, + [ + (VirtualMachinesOperations.delete, None), + (VirtualMachinesOperations.deallocate, None), + (VirtualMachinesOperations.generalize, None), + (VirtualMachinesOperations.get, 'VirtualMachine'), + (VirtualMachinesOperations.list, '[VirtualMachine]'), + (VirtualMachinesOperations.list_all, '[VirtualMachine]'), + (VirtualMachinesOperations.list_available_sizes, '[VirtualMachineSize]'), + (VirtualMachinesOperations.power_off, None), + (VirtualMachinesOperations.restart, None), + (VirtualMachinesOperations.start, None), + ]) + +_auto_command.operation_builder("vm", + "scaleset", + "virtual_machine_scale_sets", + _compute_client_factory, + [ + (VirtualMachineScaleSetsOperations.deallocate, None), + (VirtualMachineScaleSetsOperations.delete, None), + (VirtualMachineScaleSetsOperations.get, 'VirtualMachineScaleSet'), + (VirtualMachineScaleSetsOperations.delete_instances, None), + (VirtualMachineScaleSetsOperations.get_instance_view, 'VirtualMachineScaleSetInstanceView'), + (VirtualMachineScaleSetsOperations.list, '[VirtualMachineScaleSet]'), + (VirtualMachineScaleSetsOperations.list_all, '[VirtualMachineScaleSet]'), + (VirtualMachineScaleSetsOperations.list_skus, '[VirtualMachineScaleSet]'), + (VirtualMachineScaleSetsOperations.power_off, None), + (VirtualMachineScaleSetsOperations.restart, None), + (VirtualMachineScaleSetsOperations.start, None), + (VirtualMachineScaleSetsOperations.update_instances, None), + ]) + +_auto_command.operation_builder("vm", + "vmscaleset", + "virtual_machine_scale_set_vms", + _compute_client_factory, + [ + (VirtualMachineScaleSetVMsOperations.deallocate, None), + (VirtualMachineScaleSetVMsOperations.delete, None), + (VirtualMachineScaleSetVMsOperations.get, None), + (VirtualMachineScaleSetVMsOperations.get_instance_view, 'VirtualMachineScaleSetVMInstanceView'), + (VirtualMachineScaleSetVMsOperations.list, '[VirtualMachineScaleSetVM]'), + (VirtualMachineScaleSetVMsOperations.power_off, None), + (VirtualMachineScaleSetVMsOperations.restart, None), + (VirtualMachineScaleSetVMsOperations.start, None), + ]) diff --git a/src/azure/cli/tests/test_autocommand.py b/src/azure/cli/tests/test_autocommand.py new file mode 100644 index 00000000000..ee403cae0f0 --- /dev/null +++ b/src/azure/cli/tests/test_autocommand.py @@ -0,0 +1,79 @@ +import logging +import unittest + +from azure.cli.commands._auto_command import (_decorate_command, + _decorate_option) + +from azure.cli.commands import _COMMANDS + +class Test_autocommand(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Ensure initialization has occurred correctly + import azure.cli.main + logging.basicConfig(level=logging.DEBUG) + + @classmethod + def tearDownClass(cls): + logging.shutdown() + + def test_raw_register_command(self): + command_name = 'da command' + def testfunc(): + return testfunc + + # Run test code + _decorate_command(command_name, testfunc) + + # Verify + registered_command = _COMMANDS.get(testfunc, None) + self.assertIsNotNone(registered_command) + self.assertFalse('args' in registered_command.keys()) + self.assertEqual(registered_command['name'], command_name) + + def test_raw_register_command_with_one_option(self): + command_name = 'da command with one arg' + def testfunc(): + return testfunc + + # Run test code + func = _decorate_command(command_name, testfunc) + spec = '--tre ' + desc = 'Kronor' + func = _decorate_option(spec, desc, func) + + # Verify + registered_command = _COMMANDS.get(testfunc, None) + self.assertIsNotNone(registered_command) + self.assertEqual(registered_command['name'], command_name) + self.assertEqual(len(registered_command['args']), 1) + self.assertEqual(registered_command['args'][0], (spec, desc)) + + def test_load_test_commands(self): + import sys + from azure.cli._argparse import ArgumentParser + from azure.cli.commands import add_to_parser + + # sneaky trick to avoid loading any command modules... + sys.modules['azure.cli.commands.test'] = sys + + command_name = 'da command with one arg and unexpected' + def testfunc(args, _): + # Check that the argument passing actually works... + self.assertEqual(args['tre'], 'wombat') + return testfunc + + # Run test code + func = _decorate_command(command_name, testfunc) + spec = '--tre ' + desc = 'Kronor' + func = _decorate_option(spec, desc, func) + + p = ArgumentParser('automcommandtest') + add_to_parser(p, 'test') + + result = p.execute(command_name.split(' ') + '--tre wombat'.split(' ')) + self.assertEqual(result, func) + +if __name__ == '__main__': + unittest.main()