diff --git a/Dockerfile b/Dockerfile
index 204659f5c05..20df86a2157 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,36 +1,26 @@
-FROM ubuntu:14.04
-
-RUN rm /bin/sh && ln -s /bin/bash /bin/sh
+FROM ubuntu:15.10
RUN apt-get update -qq && \
apt-get install -qqy --no-install-recommends\
- build-essential \
- curl \
- ca-certificates \
- git \
- python-pip \
- libffi-dev \
- libssl-dev \
- python-dev \
+ python3-pip \
vim \
- nano \
jq && \
- rm -rf /var/lib/apt/lists/* && \
- pip install azure==2.0.0a1 && \
- pip install --upgrade requests && \
- pip install cryptography && \
- pip install pyopenssl ndg-httpsclient pyasn1
+ rm -rf /var/lib/apt/lists/*
+
+RUN pip3 install azure==2.0.0rc1
-ENV AZURECLITEMP /tmp/azure-cli
+ENV AZURECLITEMP /opt/azure-cli
ENV PYTHONPATH $PYTHONPATH:$AZURECLITEMP/src
ENV PATH $PATH:$AZURECLITEMP
RUN mkdir -p $AZURECLITEMP
-ADD src $AZURECLITEMP/src
+COPY src $AZURECLITEMP/src
+COPY az.completion.sh $AZURECLITEMP/
+COPY az $AZURECLITEMP/
-RUN echo '#!/bin/bash'>$AZURECLITEMP/az && \
- echo 'python -m azure.cli "$@"'>>$AZURECLITEMP/az && \
- chmod +x $AZURECLITEMP/az && \
- az
+RUN chmod +x $AZURECLITEMP/az
+RUN ln /usr/bin/python3 /usr/bin/python
+RUN echo "source $AZURECLITEMP/az.completion.sh" >> ~/.bashrc
+RUN az
-ENV EDITOR vim
+ENV EDITOR vim
\ No newline at end of file
diff --git a/Dockerfile-2.7 b/Dockerfile-2.7
new file mode 100644
index 00000000000..8ceae9011ce
--- /dev/null
+++ b/Dockerfile-2.7
@@ -0,0 +1,33 @@
+FROM ubuntu:14.04
+
+RUN apt-get update -qq && \
+ apt-get install -qqy --no-install-recommends\
+ build-essential \
+ curl \
+ ca-certificates \
+ python-pip \
+ libffi-dev \
+ libssl-dev \
+ python-dev \
+ vim \
+ jq && \
+ rm -rf /var/lib/apt/lists/* && \
+ pip install azure==2.0.0rc1 && \
+ pip install --upgrade requests && \
+ pip install cryptography && \
+ pip install pyopenssl ndg-httpsclient pyasn1
+
+ENV AZURECLITEMP /opt/azure-cli
+ENV PYTHONPATH $PYTHONPATH:$AZURECLITEMP/src
+ENV PATH $PATH:$AZURECLITEMP
+
+RUN mkdir -p $AZURECLITEMP
+COPY src $AZURECLITEMP/src
+COPY az.completion.sh $AZURECLITEMP/
+COPY az $AZURECLITEMP/
+
+RUN chmod +x $AZURECLITEMP/az
+RUN echo "source $AZURECLITEMP/az.completion.sh" >> ~/.bashrc
+RUN az
+
+ENV EDITOR vim
\ No newline at end of file
diff --git a/az b/az
index c4b5a8d6d7b..e7b0cefdac4 100644
--- a/az
+++ b/az
@@ -10,6 +10,5 @@ DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
export PYTHONPATH="${DIR}/src:${PYTHONPATH}"
-python -m azure.cli "$@"
-
+python -m azure.cli "$@"
diff --git a/az.completion.sh b/az.completion.sh
new file mode 100644
index 00000000000..654b52eb0d2
--- /dev/null
+++ b/az.completion.sh
@@ -0,0 +1,13 @@
+if type compdef &>/dev/null; then
+ #ZSH
+ _az_complete() {
+ compadd -- `"${COMP_WORDS[0]}" --complete "${words[@]:1}"`
+ }
+ compdef _az_complete az
+elif type complete &>/dev/null; then
+ #BASH
+ _az_complete() {
+ COMPREPLY=( $(compgen -W '$("${COMP_WORDS[0]}" --complete "${COMP_WORDS[@]:1}")' -- "${COMP_WORDS[COMP_CWORD]}") )
+ }
+ complete -F _az_complete az
+fi
diff --git a/azure-cli.pyproj b/azure-cli.pyproj
index f029358170c..28a4caa8398 100644
--- a/azure-cli.pyproj
+++ b/azure-cli.pyproj
@@ -12,7 +12,7 @@
.
{888888a0-9f3d-457c-b088-3a5042f75d52}
Standard Python launcher
- {4ae3497d-f45c-4ec2-9e26-57476ef276a0}
+ {1dd9c42b-5980-42ce-a2c3-46d3bf0eede4}
3.5
@@ -25,7 +25,12 @@
+
+
+
+
+
@@ -54,28 +59,6 @@
-
- {83c20e12-84e3-4c10-a1ba-466d2b57483d}
- {2af0f10d-7135-4994-9156-5d01c9c11b7e}
- 2.7
- env27b (Python 2.7)
- Scripts\python.exe
- Scripts\pythonw.exe
- Lib\
- PYTHONPATH
- X86
-
-
- {4ae3497d-f45c-4ec2-9e26-57476ef276a0}
- {2af0f10d-7135-4994-9156-5d01c9c11b7e}
- 3.5
- env35d (Python 3.5)
- Scripts\python.exe
- Scripts\pythonw.exe
- Lib\
- PYTHONPATH
- X86
-
{1dd9c42b-5980-42ce-a2c3-46d3bf0eede4}
{2af0f10d-7135-4994-9156-5d01c9c11b7e}
diff --git a/requirements.txt b/requirements.txt
index 7f63073e6f3..558ba24df39 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-azure==2.0.0a1
+azure==2.0.0rc1
mock==1.3.0
pylint==1.5.4
six==1.10.0
diff --git a/src/azure/cli/_argparse.py b/src/azure/cli/_argparse.py
index fc7ae204ed6..ab825f2045b 100644
--- a/src/azure/cli/_argparse.py
+++ b/src/azure/cli/_argparse.py
@@ -3,6 +3,7 @@
from ._locale import L, get_file as locale_get_file
from ._logging import logger
+from ._output import OutputProducer
# Named arguments are prefixed with one of these strings
ARG_PREFIXES = sorted(('-', '--', '/'), key=len, reverse=True)
@@ -57,6 +58,10 @@ def _index(string, char, default=sys.maxsize):
except ValueError:
return default
+class ArgumentParserResult(object): #pylint: disable=too-few-public-methods
+ def __init__(self, result, output_format=None):
+ self.result = result
+ self.output_format = output_format
class ArgumentParser(object):
def __init__(self, prog):
@@ -125,6 +130,7 @@ def add_command(self,
#pylint: disable=too-many-branches
#pylint: disable=too-many-statements
#pylint: disable=too-many-locals
+ #pylint: disable=too-many-return-statements
def execute(self,
args,
show_usage=False,
@@ -177,9 +183,9 @@ def not_global(a):
show_usage = True
if show_completions:
- return self._display_completions(m, args, out)
+ return ArgumentParserResult(self._display_completions(m, args, out))
if show_usage:
- return self._display_usage(nouns, m, out)
+ return ArgumentParserResult(self._display_usage(nouns, m, out))
parsed = Arguments()
others = Arguments()
@@ -199,7 +205,7 @@ def not_global(a):
if value is not None:
print(L("argument '{0}' does not take a value").format(key_n),
file=out)
- return self._display_usage(nouns, m, out)
+ return ArgumentParserResult(self._display_usage(nouns, m, out))
parsed.add_from_dotted(target_value[0], True)
else:
# Arg with a value
@@ -217,15 +223,24 @@ def not_global(a):
parsed[a]
except KeyError:
print(L("Missing required argument {}".format(a)))
- return self._display_usage(nouns, m, out)
+ return ArgumentParserResult(self._display_usage(nouns, m, out))
+
+ output_format = None
+ if others and 'output' in others:
+ if others['output'] in OutputProducer.format_dict:
+ output_format = others['output']
+ del others['output']
+ else:
+ print(L("Invalid output format '{}' specified".format(others['output'])))
+ return ArgumentParserResult(self._display_usage(nouns, m, out))
old_stdout = sys.stdout
try:
sys.stdout = out
- return handler(parsed, others)
+ return ArgumentParserResult(handler(parsed, others), output_format)
except IncorrectUsageError as ex:
print(str(ex), file=out)
- return self._display_usage(nouns, m, out)
+ return ArgumentParserResult(self._display_usage(nouns, m, out))
finally:
sys.stdout = old_stdout
@@ -264,15 +279,16 @@ def _display_usage(self, nouns, noun_map, out=sys.stdout):
out.flush()
logger.debug('Expected documentation at %s', doc_file)
- def _display_completions(self, noun_map, arguments, out=sys.stdout): # pylint: disable=no-self-use
- arguments.remove('--complete')
+ def _display_completions(self, noun_map, arguments, out=sys.stdout):
+ for a in self.complete_args:
+ arguments.remove(a)
command_candidates = set([k for k in noun_map if not k.startswith('$')])
if command_candidates and not arguments[-1].startswith('-'):
command_candidates = set([c for c in command_candidates if c.startswith(arguments[-1])])
kwargs = noun_map.get('$kwargs') or []
- args_candidates = set('--' + a for a in kwargs if a)
+ args_candidates = set(('--' if len(a) > 1 else '-') + a for a in kwargs)
if arguments[-1].startswith('-'):
# TODO: We don't have enough metadata about the command to do parameter value
# completion (yet). This should only apply to value arguments, not flag arguments
diff --git a/src/azure/cli/_output.py b/src/azure/cli/_output.py
index f5dc596f7e0..88acb27eb49 100644
--- a/src/azure/cli/_output.py
+++ b/src/azure/cli/_output.py
@@ -2,13 +2,9 @@
import sys
import json
+import re
-try:
- # Python 3
- from io import StringIO
-except ImportError:
- # Python 2
- from StringIO import StringIO #pylint: disable=import-error
+from six import StringIO
class OutputFormatException(Exception):
pass
@@ -40,8 +36,20 @@ def format_text(obj):
except TypeError:
return ''
+def format_list(obj):
+ obj_list = obj if isinstance(obj, list) else [obj]
+ lo = ListOutput()
+ return lo.dump(obj_list)
+
class OutputProducer(object): #pylint: disable=too-few-public-methods
+ format_dict = {
+ 'json': format_json,
+ 'table': format_table,
+ 'text': format_text,
+ 'list': format_list
+ }
+
def __init__(self, formatter=format_json, file=sys.stdout): #pylint: disable=redefined-builtin
self.formatter = formatter
self.file = file
@@ -49,6 +57,84 @@ def __init__(self, formatter=format_json, file=sys.stdout): #pylint: disable=red
def out(self, obj):
print(self.formatter(obj), file=self.file)
+ @staticmethod
+ def get_formatter(format_type):
+ return OutputProducer.format_dict.get(format_type, format_list)
+
+class ListOutput(object): #pylint: disable=too-few-public-methods
+
+ # Match the capital letters in a camel case string
+ FORMAT_KEYS_PATTERN = re.compile('([A-Z][^A-Z]*)')
+
+ def __init__(self):
+ self._formatted_keys_cache = {}
+
+ @staticmethod
+ def _get_max_key_len(keys):
+ return len(max(keys, key=len)) if keys else 0
+
+ @staticmethod
+ def _sort_key_func(key, item):
+ # We want dictionaries to be last so use ASCII char 126 ~ to
+ # prefix dictionary and list key names.
+ if isinstance(item[key], dict):
+ return '~~'+key
+ elif isinstance(item[key], list):
+ return '~'+key
+ else:
+ return key
+
+ def _get_formatted_key(self, key):
+ def _format_key(key):
+ words = [word for word in re.split(ListOutput.FORMAT_KEYS_PATTERN, key) if word]
+ return ' '.join(words).title()
+
+ try:
+ return self._formatted_keys_cache[key]
+ except KeyError:
+ self._formatted_keys_cache[key] = _format_key(key)
+ return self._formatted_keys_cache[key]
+
+ @staticmethod
+ def _dump_line(io, line, indent):
+ io.write(' ' * indent)
+ io.write(line)
+ io.write('\n')
+
+ def _dump_object(self, io, obj, indent):
+ if isinstance(obj, list):
+ for array_item in obj:
+ self._dump_object(io, array_item, indent)
+ elif isinstance(obj, dict):
+ # Get the formatted keys for this item
+ # Skip dicts/lists because those will be handled recursively later.
+ # We use this object to calc key width and don't want to dicts/lists in this.
+ obj_fk = {k: self._get_formatted_key(k)
+ for k in obj if not isinstance(obj[k], dict) and not isinstance(obj[k], list)}
+ key_width = ListOutput._get_max_key_len(obj_fk.values())
+ for key in sorted(obj, key=lambda x: ListOutput._sort_key_func(x, obj)):
+ if isinstance(obj[key], dict) or isinstance(obj[key], list):
+ # complex object
+ io.write('\n')
+ ListOutput._dump_line(io, self._get_formatted_key(key).upper(), indent+1)
+ self._dump_object(io, obj[key] if obj[key] else 'None', indent+1)
+ else:
+ # non-complex so write it
+ line = '%s : %s' % (self._get_formatted_key(key).ljust(key_width),
+ 'None' if obj[key] is None else obj[key])
+ ListOutput._dump_line(io, line, indent)
+ else:
+ ListOutput._dump_line(io, obj, indent)
+
+ def dump(self, data):
+ io = StringIO()
+ for obj in data:
+ self._dump_object(io, obj, 0)
+ io.write('\n')
+ result = io.getvalue()
+ io.close()
+ return result
+
class TableOutput(object):
def __init__(self):
self._rows = [{}]
@@ -59,16 +145,18 @@ def dump(self):
if len(self._rows) == 1:
return
- with StringIO() as io:
- cols = [(c, self._columns[c]) for c in self._column_order]
- io.write(' | '.join(c.center(w) for c, w in cols))
- io.write('\n')
- io.write('-|-'.join('-' * w for c, w in cols))
+ io = StringIO()
+ cols = [(c, self._columns[c]) for c in self._column_order]
+ io.write(' | '.join(c.center(w) for c, w in cols))
+ io.write('\n')
+ io.write('-|-'.join('-' * w for c, w in cols))
+ io.write('\n')
+ for r in self._rows[:-1]:
+ io.write(' | '.join(r[c].ljust(w) for c, w in cols))
io.write('\n')
- for r in self._rows[:-1]:
- io.write(' | '.join(r[c].ljust(w) for c, w in cols))
- io.write('\n')
- return io.getvalue()
+ result = io.getvalue()
+ io.close()
+ return result
@property
def any_rows(self):
@@ -99,17 +187,19 @@ def add(self, identifier, value):
self.identifiers[identifier] = [value]
def dump(self):
- with StringIO() as io:
- for identifier in sorted(self.identifiers):
- io.write(identifier.upper())
+ io = StringIO()
+ for identifier in sorted(self.identifiers):
+ io.write(identifier.upper())
+ io.write('\t')
+ for col in self.identifiers[identifier]:
+ if isinstance(col, str):
+ io.write(col)
+ else:
+ # TODO: Need to handle complex objects
+ io.write("null")
io.write('\t')
- for col in self.identifiers[identifier]:
- if isinstance(col, str):
- io.write(col)
- else:
- # TODO: Need to handle complex objects
- io.write("null")
- io.write('\t')
- io.write('\n')
- return io.getvalue()
+ io.write('\n')
+ result = io.getvalue()
+ io.close()
+ return result
diff --git a/src/azure/cli/commands/_auto_command.py b/src/azure/cli/commands/_auto_command.py
index 02a2722b190..821aa4d1fe6 100644
--- a/src/azure/cli/commands/_auto_command.py
+++ b/src/azure/cli/commands/_auto_command.py
@@ -23,7 +23,8 @@ def __call__(self, poller):
try:
while not poller.done():
if self.progress_file:
- print('.', end='', flush=True, file=self.progress_file)
+ print('.', end='', file=self.progress_file)
+ self.progress_file.flush()
time.sleep(self.poll_interval_ms / 1000.0)
result = poller.result()
succeeded = True
diff --git a/src/azure/cli/commands/network.py b/src/azure/cli/commands/network.py
index 39f0615eb68..b7a201e9a0b 100644
--- a/src/azure/cli/commands/network.py
+++ b/src/azure/cli/commands/network.py
@@ -1,3 +1,4 @@
+from msrest import Serializer
from .._locale import L
from azure.mgmt.network import NetworkManagementClient, NetworkManagementClientConfiguration
from azure.mgmt.network.operations import (ApplicationGatewaysOperations,
@@ -21,6 +22,7 @@
from ._command_creation import get_service_client
from ..commands._auto_command import build_operation, LongRunningOperation
+from ..commands import command, description, option
def _network_client_factory():
return get_service_client(NetworkManagementClient, NetworkManagementClientConfiguration)
@@ -32,12 +34,12 @@ def _network_client_factory():
"application_gateways",
_network_client_factory,
[
- (ApplicationGatewaysOperations.delete, None),
+ (ApplicationGatewaysOperations.delete, LongRunningOperation(L('Deleting application gateway'), L('Application gateway deleted'))),
(ApplicationGatewaysOperations.get, 'ApplicationGateway'),
(ApplicationGatewaysOperations.list, '[ApplicationGateway]'),
(ApplicationGatewaysOperations.list_all, '[ApplicationGateway]'),
- (ApplicationGatewaysOperations.start, None),
- (ApplicationGatewaysOperations.stop, LongRunningOperation(L('Starting application gateway'), L('Application gateway started'))),
+ (ApplicationGatewaysOperations.start, LongRunningOperation(L('Starting application gateway'), L('Application gateway started'))),
+ (ApplicationGatewaysOperations.stop, LongRunningOperation(L('Stopping application gateway'), L('Application gateway stopped'))),
])
# ExpressRouteCircuitAuthorizationsOperations
@@ -46,7 +48,7 @@ def _network_client_factory():
"express_route_circuit_authorizations",
_network_client_factory,
[
- (ExpressRouteCircuitAuthorizationsOperations.delete, None),
+ (ExpressRouteCircuitAuthorizationsOperations.delete, LongRunningOperation(L('Deleting express route authorization'), L('Express route authorization deleted'))),
(ExpressRouteCircuitAuthorizationsOperations.get, 'ExpressRouteCircuitAuthorization'),
(ExpressRouteCircuitAuthorizationsOperations.list, '[ExpressRouteCircuitAuthorization]'),
])
@@ -57,7 +59,7 @@ def _network_client_factory():
"express_route_circuit_peerings",
_network_client_factory,
[
- (ExpressRouteCircuitPeeringsOperations.delete, None),
+ (ExpressRouteCircuitPeeringsOperations.delete, LongRunningOperation(L('Deleting express route circuit peering'), L('Express route circuit peering deleted'))),
(ExpressRouteCircuitPeeringsOperations.get, 'ExpressRouteCircuitPeering'),
(ExpressRouteCircuitPeeringsOperations.list, '[ExpressRouteCircuitPeering]'),
])
@@ -68,7 +70,7 @@ def _network_client_factory():
"express_route_circuits",
_network_client_factory,
[
- (ExpressRouteCircuitsOperations.delete, None),
+ (ExpressRouteCircuitsOperations.delete, LongRunningOperation(L('Deleting express route circuit'), L('Express route circuit deleted'))),
(ExpressRouteCircuitsOperations.get, 'ExpressRouteCircuit'),
(ExpressRouteCircuitsOperations.list_arp_table, '[ExpressRouteCircuitArpTable]'),
(ExpressRouteCircuitsOperations.list_routes_table, '[ExpressRouteCircuitRoutesTable]'),
@@ -92,7 +94,7 @@ def _network_client_factory():
"load_balancers",
_network_client_factory,
[
- (LoadBalancersOperations.delete, None),
+ (LoadBalancersOperations.delete, LongRunningOperation(L('Deleting load balancer'), L('Load balancer deleted'))),
(LoadBalancersOperations.get, 'LoadBalancer'),
(LoadBalancersOperations.list_all, '[LoadBalancer]'),
(LoadBalancersOperations.list, '[LoadBalancer]'),
@@ -105,7 +107,7 @@ def _network_client_factory():
_network_client_factory,
[
(LocalNetworkGatewaysOperations.get, 'LocalNetworkGateway'),
- (LocalNetworkGatewaysOperations.delete, None),
+ (LocalNetworkGatewaysOperations.delete, LongRunningOperation(L('Deleting local network gateway'), L('Local network gateway deleted'))),
(LocalNetworkGatewaysOperations.list, '[LocalNetworkGateway]'),
])
@@ -116,7 +118,7 @@ def _network_client_factory():
"network_interfaces",
_network_client_factory,
[
- (NetworkInterfacesOperations.delete, None),
+ (NetworkInterfacesOperations.delete, LongRunningOperation(L('Deleting network interface'), L('Network interface deleted'))),
(NetworkInterfacesOperations.get, 'NetworkInterface'),
(NetworkInterfacesOperations.list_virtual_machine_scale_set_vm_network_interfaces, '[NetworkInterface]'),
(NetworkInterfacesOperations.list_virtual_machine_scale_set_network_interfaces, '[NetworkInterface]'),
@@ -131,7 +133,7 @@ def _network_client_factory():
"network_security_groups",
_network_client_factory,
[
- (NetworkSecurityGroupsOperations.delete, None),
+ (NetworkSecurityGroupsOperations.delete, LongRunningOperation(L('Deleting network security group'), L('Network security group deleted'))),
(NetworkSecurityGroupsOperations.delete, 'NetworkSecurityGroup'),
(NetworkSecurityGroupsOperations.list_all, '[NetworkSecurityGroup]'),
(NetworkSecurityGroupsOperations.list, '[NetworkSecurityGroup]'),
@@ -143,7 +145,7 @@ def _network_client_factory():
"public_ip_addresses",
_network_client_factory,
[
- (PublicIPAddressesOperations.delete, None),
+ (PublicIPAddressesOperations.delete, LongRunningOperation(L('Deleting public IP address'), L('Public IP address deleted'))),
(PublicIPAddressesOperations.get, 'PublicIPAddress'),
(PublicIPAddressesOperations.list_all, '[PublicIPAddress]'),
(PublicIPAddressesOperations.list, '[PublicIPAddress]'),
@@ -155,7 +157,7 @@ def _network_client_factory():
"route_tables",
_network_client_factory,
[
- (RouteTablesOperations.delete, None),
+ (RouteTablesOperations.delete, LongRunningOperation(L('Deleting route table'), L('Route table deleted'))),
(RouteTablesOperations.get, 'RouteTable'),
(RouteTablesOperations.list, '[RouteTable]'),
(RouteTablesOperations.list_all, '[RouteTable]'),
@@ -167,7 +169,7 @@ def _network_client_factory():
"routes",
_network_client_factory,
[
- (RoutesOperations.delete, None),
+ (RoutesOperations.delete, LongRunningOperation(L('Deleting route'), L('Route deleted'))),
(RoutesOperations.get, 'Route'),
(RoutesOperations.list, '[Route]'),
])
@@ -178,7 +180,7 @@ def _network_client_factory():
"security_rules",
_network_client_factory,
[
- (SecurityRulesOperations.delete, None),
+ (SecurityRulesOperations.delete, LongRunningOperation(L('Deleting security rule'), L('Security rule deleted'))),
(SecurityRulesOperations.get, 'SecurityRule'),
(SecurityRulesOperations.list, '[SecurityRule]'),
])
@@ -189,7 +191,7 @@ def _network_client_factory():
"subnets",
_network_client_factory,
[
- (SubnetsOperations.delete, None),
+ (SubnetsOperations.delete, LongRunningOperation(L('Deleting subnet'), L('Subnet deleted'))),
(SubnetsOperations.get, 'Subnet'),
(SubnetsOperations.list, '[Subnet]'),
])
@@ -209,7 +211,7 @@ def _network_client_factory():
"virtual_network_gateway_connections",
_network_client_factory,
[
- (VirtualNetworkGatewayConnectionsOperations.delete, None),
+ (VirtualNetworkGatewayConnectionsOperations.delete, LongRunningOperation(L('Deleting virtual network gateway connection'), L('Virtual network gateway connection deleted'))),
(VirtualNetworkGatewayConnectionsOperations.get, 'VirtualNetworkGatewayConnection'),
(VirtualNetworkGatewayConnectionsOperations.get_shared_key, 'ConnectionSharedKeyResult'),
(VirtualNetworkGatewayConnectionsOperations.list, '[VirtualNetworkGatewayConnection]'),
@@ -223,7 +225,7 @@ def _network_client_factory():
"virtual_network_gateways",
_network_client_factory,
[
- (VirtualNetworkGatewaysOperations.delete, None),
+ (VirtualNetworkGatewaysOperations.delete, LongRunningOperation(L('Deleting virtual network gateway'), L('Virtual network gateway deleted'))),
(VirtualNetworkGatewaysOperations.get, 'VirtualNetworkGateway'),
(VirtualNetworkGatewaysOperations.list, '[VirtualNetworkGateway]'),
(VirtualNetworkGatewaysOperations.reset, 'VirtualNetworkGateway'),
@@ -235,8 +237,79 @@ def _network_client_factory():
"virtual_networks",
_network_client_factory,
[
- (VirtualNetworksOperations.delete, None),
+ (VirtualNetworksOperations.delete, LongRunningOperation(L('Deleting virtual network'), L('Virtual network deleted'))),
(VirtualNetworksOperations.get, 'VirtualNetwork'),
(VirtualNetworksOperations.list, '[VirtualNetwork]'),
(VirtualNetworksOperations.list_all, '[VirtualNetwork]'),
])
+
+@command('network vnet create')
+@description(L('Create or update a virtual network (VNet)'))
+@option('--resource-group -g ', L('the resource group name'), required=True)
+@option('--name -n ', L('the VNet name'), required=True)
+@option('--location -l ', L('the VNet location'), required=True)
+@option('--address-space -a ', L('the VNet address-space in CIDR notation or multiple address-spaces, quoted and space-seperated'), required=True)
+@option('--dns-servers -d ', L('the VNet DNS servers, quoted and space-seperated'))
+def create_update_vnet(args, unexpected): #pylint: disable=unused-argument
+ from azure.mgmt.network.models import AddressSpace, DhcpOptions, VirtualNetwork
+
+ resource_group = args.get('resource-group')
+ name = args.get('name')
+ location = args.get('location')
+ address_space = AddressSpace(address_prefixes=args.get('address-space').split())
+ dhcp_options = DhcpOptions(dns_servers=args.get('dns-servers').split())
+
+ vnet_settings = VirtualNetwork(location=location,
+ address_space=address_space,
+ dhcp_options=dhcp_options)
+
+ op = LongRunningOperation('Creating virtual network', 'Virtual network created')
+ smc = _network_client_factory()
+ poller = smc.virtual_networks.create_or_update(resource_group, name, vnet_settings)
+ return Serializer().serialize_data(op(poller), 'VirtualNetwork')
+
+@command('network subnet create')
+@description(L('Create or update a virtual network (VNet) subnet'))
+@option('--resource-group -g ', L('the the resource group name'), required=True)
+@option('--name -n ', L('the the subnet name'), required=True)
+@option('--vnet -v ', L('the name of the subnet vnet'), required=True)
+@option('--address-prefix -a ', L('the the address prefix in CIDR format'), required=True)
+# TODO: setting the IPConfiguration fails, will contact owning team
+#@option('--ip-name -ipn ', L('the IP address configuration name'))
+#@option('--ip-private-address -ippa ', L('the private IP address'))
+#@option('--ip-allocation-method -ipam ', L('the IP address allocation method'))
+#@option('--ip-public-address -ipa ', L('the public IP address'))
+def create_update_subnet(args, unexpected): #pylint: disable=unused-argument
+ from azure.mgmt.network.models import (Subnet,
+ # TODO: setting the IPConfiguration fails
+ #IPConfiguration,
+ )
+
+ resource_group = args.get('resource-group')
+ vnet = args.get('vnet')
+ name = args.get('name')
+ address_prefix = args.get('address-prefix')
+ # TODO: setting the IPConfiguration fails, will contact owning team
+ #ip_name = args.get('ip-name')
+ #ip_private_address = args.get('ip-private-address')
+ #ip_allocation_method = args.get('ip-allocation-method')
+ #ip_public_address = args.get('ip-public-address')
+
+ # TODO: setting the IPConfiguration fails, will contact owning team
+ #ip_configuration = IPConfiguration(subnet = name,
+ # name = ip_name,
+ # private_ip_address = ip_private_address,
+ # private_ip_allocation_method = ip_allocation_method,
+ # public_ip_address = ip_public_address)
+
+ subnet_settings = Subnet(name=name,
+ address_prefix=address_prefix)
+ # TODO: setting the IPConfiguration fails, will contact owning team
+ #ip_configurations = [ip_configuration])
+
+ op = LongRunningOperation('Creating subnet', 'Subnet created')
+ smc = _network_client_factory()
+ poller = smc.subnets.create_or_update(resource_group, vnet, name, subnet_settings)
+ return Serializer().serialize_data(op(poller), 'Subnet')
+
+
diff --git a/src/azure/cli/commands/vm.py b/src/azure/cli/commands/vm.py
index 28f2f077c42..7b9ca346485 100644
--- a/src/azure/cli/commands/vm.py
+++ b/src/azure/cli/commands/vm.py
@@ -44,7 +44,7 @@ def _compute_client_factory():
"virtual_machine_extensions",
_compute_client_factory,
[
- (VirtualMachineExtensionsOperations.delete, None),
+ (VirtualMachineExtensionsOperations.delete, LongRunningOperation(L('Deleting VM extension'), L('VM extension deleted'))),
(VirtualMachineExtensionsOperations.get, 'VirtualMachineExtension'),
])
@@ -81,14 +81,14 @@ def _compute_client_factory():
"virtual_machines",
_compute_client_factory,
[
- (VirtualMachinesOperations.delete, None),
- (VirtualMachinesOperations.deallocate, None),
+ (VirtualMachinesOperations.delete, LongRunningOperation(L('Deleting VM'), L('VM Deleted'))),
+ (VirtualMachinesOperations.deallocate, LongRunningOperation(L('Deallocating VM'), L('VM Deallocated'))),
(VirtualMachinesOperations.generalize, None),
(VirtualMachinesOperations.get, 'VirtualMachine'),
(VirtualMachinesOperations.list, '[VirtualMachine]'),
(VirtualMachinesOperations.list_all, '[VirtualMachine]'),
(VirtualMachinesOperations.list_available_sizes, '[VirtualMachineSize]'),
- (VirtualMachinesOperations.power_off, None),
+ (VirtualMachinesOperations.power_off, LongRunningOperation(L('Powering off VM'), L('VM powered off'))),
(VirtualMachinesOperations.restart, LongRunningOperation(L('Restarting VM'), L('VM Restarted'))),
(VirtualMachinesOperations.start, LongRunningOperation(L('Starting VM'), L('VM Started'))),
])
@@ -98,18 +98,18 @@ def _compute_client_factory():
"virtual_machine_scale_sets",
_compute_client_factory,
[
- (VirtualMachineScaleSetsOperations.deallocate, None),
- (VirtualMachineScaleSetsOperations.delete, None),
+ (VirtualMachineScaleSetsOperations.deallocate, LongRunningOperation(L('Deallocating VM scale set'), L('VM scale set deallocated'))),
+ (VirtualMachineScaleSetsOperations.delete, LongRunningOperation(L('Deleting VM scale set'), L('VM scale set deleted'))),
(VirtualMachineScaleSetsOperations.get, 'VirtualMachineScaleSet'),
- (VirtualMachineScaleSetsOperations.delete_instances, None),
+ (VirtualMachineScaleSetsOperations.delete_instances, LongRunningOperation(L('Deleting VM scale set instances'), L('VM scale set instances deleted'))),
(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),
+ (VirtualMachineScaleSetsOperations.power_off, LongRunningOperation(L('Powering off VM scale set'), L('VM scale set powered off'))),
+ (VirtualMachineScaleSetsOperations.restart, LongRunningOperation(L('Restarting VM scale set'), L('VM scale set restarted'))),
+ (VirtualMachineScaleSetsOperations.start, LongRunningOperation(L('Starting VM scale set'), L('VM scale set started'))),
+ (VirtualMachineScaleSetsOperations.update_instances, LongRunningOperation(L('Updating VM scale set instances'), L('VM scale set instances updated'))),
])
build_operation("vm",
@@ -117,12 +117,12 @@ def _compute_client_factory():
"virtual_machine_scale_set_vms",
_compute_client_factory,
[
- (VirtualMachineScaleSetVMsOperations.deallocate, None),
- (VirtualMachineScaleSetVMsOperations.delete, None),
- (VirtualMachineScaleSetVMsOperations.get, None),
+ (VirtualMachineScaleSetVMsOperations.deallocate, LongRunningOperation(L('Deallocating VM scale set VMs'), L('VM scale set VMs deallocated'))),
+ (VirtualMachineScaleSetVMsOperations.delete, LongRunningOperation(L('Deleting VM scale set VMs'), L('VM scale set VMs deleted'))),
+ (VirtualMachineScaleSetVMsOperations.get, 'VirtualMachineScaleSetVM'),
(VirtualMachineScaleSetVMsOperations.get_instance_view, 'VirtualMachineScaleSetVMInstanceView'),
(VirtualMachineScaleSetVMsOperations.list, '[VirtualMachineScaleSetVM]'),
- (VirtualMachineScaleSetVMsOperations.power_off, None),
- (VirtualMachineScaleSetVMsOperations.restart, None),
- (VirtualMachineScaleSetVMsOperations.start, None),
+ (VirtualMachineScaleSetVMsOperations.power_off, LongRunningOperation(L('Powering off VM scale set VMs'), L('VM scale set VMs powered off'))),
+ (VirtualMachineScaleSetVMsOperations.restart, LongRunningOperation(L('Restarting VM scale set VMs'), L('VM scale set VMs restarted'))),
+ (VirtualMachineScaleSetVMsOperations.start, LongRunningOperation(L('Starting VM scale set VMs'), L('VM scale set VMs started'))),
])
diff --git a/src/azure/cli/main.py b/src/azure/cli/main.py
index a6f20cfa0a8..a12faebfeba 100644
--- a/src/azure/cli/main.py
+++ b/src/azure/cli/main.py
@@ -39,11 +39,12 @@ def main(args, file=sys.stdout): #pylint: disable=redefined-builtin
commands.add_to_parser(parser)
try:
- result = parser.execute(args)
+ cmd_res = parser.execute(args)
# Commands can return a dictionary/list of results
# If they do, we print the results.
- if result:
- OutputProducer(file=file).out(result)
+ if cmd_res.result:
+ formatter = OutputProducer.get_formatter(cmd_res.output_format)
+ OutputProducer(formatter=formatter, file=file).out(cmd_res.result)
except RuntimeError as ex:
logger.error(ex.args[0])
return ex.args[1] if len(ex.args) >= 2 else -1
diff --git a/src/azure/cli/tests/test_argparse.py b/src/azure/cli/tests/test_argparse.py
index 1ba3540800a..d8b7efd702f 100644
--- a/src/azure/cli/tests/test_argparse.py
+++ b/src/azure/cli/tests/test_argparse.py
@@ -1,9 +1,17 @@
+try:
+ # on Python2, we like to use the module "StringIO" rather "io" so to
+ # avoid "print" errors like: TypeError: string argument expected, got 'str'
+ from StringIO import StringIO
+except ImportError:
+ # Python 3
+ from io import StringIO
+
import unittest
from azure.cli._argparse import ArgumentParser, IncorrectUsageError
from azure.cli._logging import logger
import logging
-
+import azure.cli._util as util
class Test_argparse(unittest.TestCase):
@classmethod
@@ -38,31 +46,38 @@ def test_args(self):
p = ArgumentParser('test')
p.add_command(lambda a, b: (a, b), 'n1', args=[('--arg -a', '', False), ('-b ', '', False)])
- res, other = p.execute('n1 -a x'.split())
+ cmd_res = p.execute('n1 -a x'.split())
+ res, other = cmd_res.result
self.assertTrue(res.arg)
self.assertSequenceEqual(res.positional, ['x'])
# Should recognize args with alternate prefix
- res, other = p.execute('n1 /a'.split())
+ cmd_res = p.execute('n1 /a'.split())
+ res, other = cmd_res.result
self.assertTrue(res.arg)
- res, other = p.execute('n1 /arg'.split())
+ cmd_res = p.execute('n1 /arg'.split())
+ res, other = cmd_res.result
self.assertTrue(res.arg)
# Should not recognize "------a"
- res, other = p.execute('n1 ------a'.split())
+ cmd_res = p.execute('n1 ------a'.split())
+ res, other = cmd_res.result
self.assertNotIn('arg', res)
# First two '--' match, so '----a' is added to dict
self.assertIn('----a', other)
- res = p.execute('n1 -a:x'.split())
+ cmd_res = p.execute('n1 -a:x'.split())
+ res = cmd_res.result
self.assertIsNone(res)
- res, other = p.execute('n1 -b -a x'.split())
+ cmd_res = p.execute('n1 -b -a x'.split())
+ res, other = cmd_res.result
self.assertEqual(res.b, '-a')
self.assertSequenceEqual(res.positional, ['x'])
self.assertRaises(IncorrectUsageError, lambda: res.arg)
- res, other = p.execute('n1 -b:-a x'.split())
+ cmd_res = p.execute('n1 -b:-a x'.split())
+ res, other = cmd_res.result
self.assertEqual(res.b, '-a')
self.assertSequenceEqual(res.positional, ['x'])
self.assertRaises(IncorrectUsageError, lambda: res.arg)
@@ -71,15 +86,18 @@ def test_unexpected_args(self):
p = ArgumentParser('test')
p.add_command(lambda a, b: (a, b), 'n1', args=[('-a', '', False)])
- res, other = p.execute('n1 -b=2'.split())
+ cmd_res = p.execute('n1 -b=2'.split())
+ res, other = cmd_res.result
self.assertFalse(res)
self.assertEqual('2', other.b)
- res, other = p.execute('n1 -b.c.d=2'.split())
+ cmd_res = p.execute('n1 -b.c.d=2'.split())
+ res, other = cmd_res.result
self.assertFalse(res)
self.assertEqual('2', other.b.c.d)
- res, other = p.execute('n1 -b.c.d 2 -b.c.e:3'.split())
+ cmd_res = p.execute('n1 -b.c.d 2 -b.c.e:3'.split())
+ res, other = cmd_res.result
self.assertFalse(res)
self.assertEqual('2', other.b.c.d)
self.assertEqual('3', other.b.c.e)
@@ -88,12 +106,63 @@ def test_required_args(self):
p = ArgumentParser('test')
p.add_command(lambda a, b: (a, b), 'n1', args=[('--arg -a', '', True), ('-b ', '', False)])
- res, other = p.execute('n1 -a x'.split())
+ cmd_res = p.execute('n1 -a x'.split())
+ res, other = cmd_res.result
self.assertTrue(res.arg)
self.assertSequenceEqual(res.positional, ['x'])
- self.assertIsNone(p.execute('n1 -b x'.split()))
+ self.assertIsNone(p.execute('n1 -b x'.split()).result)
+
+ def test_args_completion(self):
+ p = ArgumentParser('test')
+ p.add_command(lambda a, b: (a, b), 'n1', args=[('--arg -a', '', True), ('-b ', '', False)])
+ io = StringIO()
+
+ p.execute('n1 - --complete'.split(),
+ show_usage=False,
+ show_completions=True,
+ out=io)
+ candidates = util.normalize_newlines(io.getvalue())
+ self.assertEqual(candidates, '--arg\n-a\n-b\n')
+
+ io = StringIO()
+ p.execute('n1 --a --complete'.split(),
+ show_usage=False,
+ out=io)
+ candidates = util.normalize_newlines(io.getvalue())
+ self.assertEqual(candidates, '--arg\n')
+
+ io = StringIO()
+ p.execute('n --a --complete'.split(),
+ show_usage=False,
+ show_completions=True,
+ out=io)
+ candidates = util.normalize_newlines(io.getvalue())
+ self.assertEqual(candidates, 'n1\n')
+
+ def test_specify_output_format(self):
+ p = ArgumentParser('test')
+ p.add_command(lambda a, b: (a, b), 'n1', args=[('--arg -a', '', True), ('-b ', '', False)])
+
+ cmd_res = p.execute('n1 -a x'.split())
+ self.assertEqual(cmd_res.output_format, None)
+
+ cmd_res = p.execute('n1 -a x --output json'.split())
+ self.assertEqual(cmd_res.output_format, 'json')
+
+ cmd_res = p.execute('n1 -a x --output table'.split())
+ self.assertEqual(cmd_res.output_format, 'table')
+
+ cmd_res = p.execute('n1 -a x --output text'.split())
+ self.assertEqual(cmd_res.output_format, 'text')
+
+ # Invalid format
+ cmd_res = p.execute('n1 -a x --output unknown'.split())
+ self.assertEqual(cmd_res.output_format, None)
+ # Invalid format
+ cmd_res = p.execute('n1 -a x --output'.split())
+ self.assertEqual(cmd_res.output_format, None)
if __name__ == '__main__':
unittest.main()
diff --git a/src/azure/cli/tests/test_autocommand.py b/src/azure/cli/tests/test_autocommand.py
index 7cc8fddabe2..d352455adff 100644
--- a/src/azure/cli/tests/test_autocommand.py
+++ b/src/azure/cli/tests/test_autocommand.py
@@ -72,8 +72,8 @@ def testfunc(args, _):
p = ArgumentParser('automcommandtest')
add_to_parser(p, 'test')
- result = p.execute(command_name.split(' ') + '--tre wombat'.split(' '))
- self.assertEqual(result, func)
+ cmd_res = p.execute(command_name.split(' ') + '--tre wombat'.split(' '))
+ self.assertEqual(cmd_res.result, func)
if __name__ == '__main__':
unittest.main()
diff --git a/src/azure/cli/tests/test_output.py b/src/azure/cli/tests/test_output.py
index 14ab85cb96c..c2c62f32357 100644
--- a/src/azure/cli/tests/test_output.py
+++ b/src/azure/cli/tests/test_output.py
@@ -9,7 +9,8 @@
# Python 2
from StringIO import StringIO
-from azure.cli._output import OutputProducer, OutputFormatException, format_json, format_table, format_text
+from azure.cli._output import (OutputProducer, OutputFormatException, format_json, format_table, format_list, format_text,
+ ListOutput)
import azure.cli._util as util
class TestOutput(unittest.TestCase):
@@ -42,8 +43,6 @@ def test_out_json_valid(self):
"""))
def test_out_table_valid(self):
- """
- """
output_producer = OutputProducer(formatter=format_table, file=self.io)
output_producer.out({'active': True, 'id': '0b1f6472'})
self.assertEqual(util.normalize_newlines(self.io.getvalue()), util.normalize_newlines(
@@ -53,5 +52,109 @@ def test_out_table_valid(self):
"""))
+ def test_out_list_valid(self):
+ output_producer = OutputProducer(formatter=format_list, file=self.io)
+ output_producer.out({'active': True, 'id': '0b1f6472'})
+ self.assertEqual(util.normalize_newlines(self.io.getvalue()), util.normalize_newlines(
+"""Active : True
+Id : 0b1f6472
+
+
+"""))
+
+ def test_out_list_valid_none_val(self):
+ output_producer = OutputProducer(formatter=format_list, file=self.io)
+ output_producer.out({'active': None, 'id': '0b1f6472'})
+ self.assertEqual(util.normalize_newlines(self.io.getvalue()), util.normalize_newlines(
+"""Active : None
+Id : 0b1f6472
+
+
+"""))
+
+ def test_out_list_valid_empty_array(self):
+ output_producer = OutputProducer(formatter=format_list, file=self.io)
+ output_producer.out({'active': None, 'id': '0b1f6472', 'hosts': []})
+ self.assertEqual(util.normalize_newlines(self.io.getvalue()), util.normalize_newlines(
+"""Active : None
+Id : 0b1f6472
+
+ HOSTS
+ None
+
+
+"""))
+
+ def test_out_list_valid_array_complex(self):
+ output_producer = OutputProducer(formatter=format_list, file=self.io)
+ output_producer.out([{'active': True, 'id': '783yesdf'}, {'active': False, 'id': '3hjnme32'}, {'active': False, 'id': '23hiujbs'}])
+ self.assertEqual(util.normalize_newlines(self.io.getvalue()), util.normalize_newlines(
+"""Active : True
+Id : 783yesdf
+
+Active : False
+Id : 3hjnme32
+
+Active : False
+Id : 23hiujbs
+
+
+"""))
+
+ def test_out_list_valid_str_array(self):
+ output_producer = OutputProducer(formatter=format_list, file=self.io)
+ output_producer.out(['location', 'id', 'host', 'server'])
+ self.assertEqual(util.normalize_newlines(self.io.getvalue()), util.normalize_newlines(
+"""location
+
+id
+
+host
+
+server
+
+
+"""))
+
+ def test_out_list_valid_complex_array(self):
+ output_producer = OutputProducer(formatter=format_list, file=self.io)
+ output_producer.out({'active': True, 'id': '0b1f6472', 'myarray': ['1', '2', '3', '4']})
+ self.assertEqual(util.normalize_newlines(self.io.getvalue()), util.normalize_newlines(
+"""Active : True
+Id : 0b1f6472
+
+ MYARRAY
+ 1
+ 2
+ 3
+ 4
+
+
+"""))
+
+ def test_out_list_format_key_simple(self):
+ lo = ListOutput()
+ self.assertEqual(lo._formatted_keys_cache, {})
+ lo._get_formatted_key('locationId')
+ self.assertEqual(lo._formatted_keys_cache, {'locationId': 'Location Id'})
+
+ def test_out_list_format_key_single(self):
+ lo = ListOutput()
+ self.assertEqual(lo._formatted_keys_cache, {})
+ lo._get_formatted_key('location')
+ self.assertEqual(lo._formatted_keys_cache, {'location': 'Location'})
+
+ def test_out_list_format_key_multiple_caps(self):
+ lo = ListOutput()
+ self.assertEqual(lo._formatted_keys_cache, {})
+ lo._get_formatted_key('fooIDS')
+ self.assertEqual(lo._formatted_keys_cache, {'fooIDS': 'Foo I D S'})
+
+ def test_out_list_format_key_multiple_words(self):
+ lo = ListOutput()
+ self.assertEqual(lo._formatted_keys_cache, {})
+ lo._get_formatted_key('locationIdState')
+ self.assertEqual(lo._formatted_keys_cache, {'locationIdState': 'Location Id State'})
+
if __name__ == '__main__':
unittest.main()