diff --git a/CHANGELOG b/CHANGELOG index 9e49ae9cf..3397760f9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +4.0.3 + + * Fixes bug with `slcli vs ready` command + + * Fixes bug with `slcli loadbal service-add` command + + * Fixes bug with `slcli vlan list` with vlans that don't have a datacenter + + * Improves validation of virtual server and hardware create commands + 4.0.2 * Fixes a bug that breaks user confirmation prompts diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index a68395225..066b2173e 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -176,6 +176,7 @@ def output_result(ctx, result, timings=False, **kwargs): def main(): """Main program. Catches several common errors and displays them nicely.""" exit_status = 0 + try: cli.main() except SoftLayer.SoftLayerAPIError as ex: diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index f58005900..ab3d1dbe0 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -16,6 +16,9 @@ # pylint: disable=too-many-instance-attributes, invalid-name, no-self-use +# Calling pkg_resources.iter_entry_points shows a false-positive +# pylint: disable=no-member + class Environment(object): """Provides access to the current CLI environment.""" diff --git a/SoftLayer/CLI/loadbal/service_add.py b/SoftLayer/CLI/loadbal/service_add.py index 5a9fa982a..a9fdc04d9 100644 --- a/SoftLayer/CLI/loadbal/service_add.py +++ b/SoftLayer/CLI/loadbal/service_add.py @@ -24,9 +24,9 @@ @click.option('--healthcheck-type', required=True, help="The health check type") -@click.option('--ip-address', '--ip', +@click.option('--ip-address', required=True, - help="The IP of the service") + help="The IP address of the service") @environment.pass_env def cli(env, identifier, enabled, port, weight, healthcheck_type, ip_address): """Adds a new load balancer service.""" @@ -40,7 +40,8 @@ def cli(env, identifier, enabled, port, weight, healthcheck_type, ip_address): if ip_address: ip_service = env.client['Network_Subnet_IpAddress'] ip_record = ip_service.getByIpAddress(ip_address) - ip_address_id = ip_record['id'] + if len(ip_record) > 0: + ip_address_id = ip_record['id'] mgr.add_service(loadbal_id, group_id, diff --git a/SoftLayer/CLI/server/create.py b/SoftLayer/CLI/server/create.py index 7e2517a84..704f5b5b3 100644 --- a/SoftLayer/CLI/server/create.py +++ b/SoftLayer/CLI/server/create.py @@ -52,6 +52,8 @@ def cli(env, **args): """Order/create a dedicated server.""" template.update_with_template_args(args, list_args=['key']) + _validate_args(args) + mgr = SoftLayer.HardwareManager(env.client) # Get the SSH keys @@ -125,3 +127,20 @@ def cli(env, **args): output = table return output + + +def _validate_args(args): + """Raises an ArgumentError if the given arguments are not valid.""" + missing = [] + for arg in ['size', + 'datacenter', + 'os', + 'port_speed', + 'hostname', + 'domain']: + if not args.get(arg): + missing.append(arg) + + if missing: + raise exceptions.ArgumentError('Missing required options: %s' + % ', '.join(missing)) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index 6f8ab6fa4..db09191b4 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -154,6 +154,14 @@ def cli(env, **args): def _validate_args(args): """Raises an ArgumentError if the given arguments are not valid.""" + missing = [] + for arg in ['cpu', 'memory', 'hostname', 'domain']: + if not args.get(arg): + missing.append(arg) + + if missing: + raise exceptions.ArgumentError('Missing required options: %s' + % ', '.join(missing)) if all([args['userdata'], args['userfile']]): raise exceptions.ArgumentError( diff --git a/SoftLayer/CLI/virt/ready.py b/SoftLayer/CLI/virt/ready.py index 1a1f05661..2ad1722a7 100644 --- a/SoftLayer/CLI/virt/ready.py +++ b/SoftLayer/CLI/virt/ready.py @@ -2,6 +2,7 @@ # :license: MIT, see LICENSE for more details. import SoftLayer +from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import helpers @@ -11,14 +12,13 @@ @click.command() @click.argument('identifier') @click.option('--wait', default=0, type=click.INT, help="Name of the image") +@environment.pass_env def cli(env, identifier, wait): """Check if a virtual server is ready.""" vsi = SoftLayer.VSManager(env.client) - vs_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') ready = vsi.wait_for_ready(vs_id, wait) - if ready: return "READY" else: diff --git a/SoftLayer/CLI/vlan/list.py b/SoftLayer/CLI/vlan/list.py index e2b8d2670..cc7beef84 100644 --- a/SoftLayer/CLI/vlan/list.py +++ b/SoftLayer/CLI/vlan/list.py @@ -4,6 +4,7 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer import utils import click @@ -43,7 +44,7 @@ def cli(env, sortby, datacenter, number, name): table.add_row([ vlan['id'], vlan['vlanNumber'], - vlan['primaryRouter']['datacenter']['name'], + utils.lookup(vlan, 'primaryRouter', 'datacenter', 'name'), vlan.get('name') or formatting.blank(), vlan['totalPrimaryIpAddressCount'], len(vlan['hardware']), diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index e0b285363..674f04608 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v4.0.2' +VERSION = 'v4.0.3' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index 221cafd46..9c07a15da 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer import exceptions from SoftLayer import utils @@ -28,8 +29,13 @@ def _find_item_prices(self, size, categorycode=''): 'categories': { 'categoryCode': {'operation': categorycode} }}}) - item_price = item_prices[0]['prices'][0]['id'] - return item_price + + for item_price in item_prices: + for price in item_price['prices']: + return price['id'] + + raise exceptions.SoftLayerError( + "Could not find a valid price with for the given size") def _build_order(self, item_price, location): """Returns a dict appropriate for Product_Order::placeOrder().""" diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index c7f9fbcfb..ebd523f45 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -449,7 +449,7 @@ def create_instance(self, **kwargs): incur a fee on your account. :param int public_vlan: The ID of the public VLAN on which you want this VS placed. - :param int private_vlan: The ID of the public VLAN on which you want + :param int private_vlan: The ID of the private VLAN on which you want this VS placed. :param list disks: A list of disk capacities for this server. :param string post_uri: The URI of the post-install script to run diff --git a/SoftLayer/testing/xmlrpc.py b/SoftLayer/testing/xmlrpc.py index 920f86e17..47272616e 100644 --- a/SoftLayer/testing/xmlrpc.py +++ b/SoftLayer/testing/xmlrpc.py @@ -92,6 +92,6 @@ def create_test_server(transport, host='localhost', port=0): """Create a test XML-RPC server in a new thread.""" server = TestServer(transport, (host, port), TestHandler) thread = threading.Thread(target=server.serve_forever, - kwargs={'poll_interval': 0.05}) + kwargs={'poll_interval': 0.01}) thread.start() return server diff --git a/SoftLayer/tests/functional_tests.py b/SoftLayer/tests/functional_tests.py index 53abe6eb9..69972319c 100644 --- a/SoftLayer/tests/functional_tests.py +++ b/SoftLayer/tests/functional_tests.py @@ -47,12 +47,10 @@ def test_no_hostname(self): endpoint_url='http://notvalidsoftlayer.com', ) transport(request) - except SoftLayer.SoftLayerAPIError as ex: - self.assertIn('not known', str(ex)) - self.assertIn('not known', ex.faultString) + except SoftLayer.TransportError as ex: self.assertEqual(ex.faultCode, 0) else: - self.fail('No Exception Raised') + self.fail('Transport Error Exception Not Raised') class AuthedUser(FunctionalTest): diff --git a/docs/conf.py b/docs/conf.py index 55abd0f3f..0472001bc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,9 +55,9 @@ # built documents. # # The short X.Y version. -version = '4.0.2' +version = '4.0.3' # The full version, including alpha/beta/rc tags. -release = '4.0.2' +release = '4.0.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 087610c3a..88132dbe8 100644 --- a/setup.py +++ b/setup.py @@ -2,6 +2,8 @@ import sys import os +from codecs import open + try: from setuptools import setup, find_packages except ImportError: @@ -18,7 +20,7 @@ 'six >= 1.7.0', 'prettytable >= 0.7.0', 'click', - 'requests', + 'requests >= 2.7.0', ] if sys.version_info < (2, 7): @@ -27,14 +29,14 @@ DESCRIPTION = "A library for SoftLayer's API" if os.path.exists('README.rst'): - with open('README.rst') as readme_file: + with open('README.rst', 'r', 'utf-8') as readme_file: LONG_DESCRIPTION = readme_file.read() else: LONG_DESCRIPTION = DESCRIPTION setup( name='SoftLayer', - version='4.0.2', + version='4.0.3', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', diff --git a/tox.ini b/tox.ini index cc18582c9..2f693b5e8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,10 @@ [tox] envlist = py26,py27,py33,py34,pypy,analysis,coverage + [testenv] +setenv = + LANG = {env:LANG:C.UTF-8} + deps = -r{toxinidir}/tools/test-requirements.txt commands = nosetests @@ -24,13 +28,12 @@ commands = pylint SoftLayer \ -r n \ # Don't show the long report --ignore=tests,fixtures \ - -d too-many-locals \ # Too many local variables - -d star-args \ # Used * or ** magic - -d I0011 \ # Locally Disabling + -d too-many-locals \ + -d star-args \ + -d locally-disabled \ --max-args=20 \ - --max-branches=40 \ - --max-statements=87 \ - --max-returns=8 \ + --max-branches=20 \ + --max-statements=60 \ --min-public-methods=0 \ --min-similarity-lines=30 pylint SoftLayer/testing/fixtures \