From 81fca4c8b591c2be959d2110d1f6b58eb6a8629c Mon Sep 17 00:00:00 2001 From: Nathan Beittenmiller Date: Fri, 9 May 2014 13:13:31 -0500 Subject: [PATCH 0001/2667] Adding a few minor examples --- SoftLayer/managers/vs.py | 50 +++++++++++++++++++++++++++++++++++++++ docs/api/managers/cci.rst | 2 ++ 2 files changed, 52 insertions(+) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 6cd84e450..f33155a5c 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -133,6 +133,19 @@ def get_instance(self, instance_id, **kwargs): :returns: A dictionary containing a large amount of information about the specified instance. + :: + + # Print out the FQDN and IP address for instance ID 12345. + # env variables + # SL_USERNAME = YOUR_USERNAME + # SL_API_KEY = YOUR_API_KEY + import SoftLayer + client = SoftLayer.Client() + + mgr = SoftLayer.VSManager(client) + vsi = mgr.get_instance(12345) + print vsi['fullyQualifiedDomainName'], vs['primaryIpAddress'] + """ if 'mask' not in kwargs: @@ -189,6 +202,18 @@ def cancel_instance(self, instance_id): :param integer instance_id: the instance ID to cancel + :: + + # Cancel for instance ID 12345. + # env variables + # SL_USERNAME = YOUR_USERNAME + # SL_API_KEY = YOUR_API_KEY + import SoftLayer + client = SoftLayer.Client() + + mgr = SoftLayer.VSManager(client) + mgr.cancel_instance(12345) + """ return self.guest.deleteObject(id=instance_id) @@ -199,6 +224,20 @@ def reload_instance(self, instance_id, post_uri=None, ssh_keys=None): :param string post_url: The URI of the post-install script to run after reload :param list ssh_keys: The SSH keys to add to the root user + + :: + + # Reload instance ID 12345 then run a custom post-provision script. + # env variables + # SL_USERNAME = YOUR_USERNAME + # SL_API_KEY = YOUR_API_KEY + import SoftLayer + client = SoftLayer.Client() + + post_uri = 'https://somehost.com/bootstrap.sh' + mgr = SoftLayer.VSManager(client) + vsi = mgr.reload_instance(12345, post_uri=post_url) + """ config = {} @@ -497,12 +536,23 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=True): """ Upgrades a VS instance + :param int instance_id: Instance id of the VS to be upgraded :param int cpus: The number of virtual CPUs to upgrade to of a VS instance. :param bool public: CPU will be in Private/Public Node. :param int memory: RAM of the VS to be upgraded to. :param int nic_speed: The port speed to set + + :: + + # Upgrade instance 12345 to 4 CPUs and 4 GB of memory + import SoftLayer + client = SoftLayer.Client(config="~/.softlayer") + + mgr = SoftLayer.VSManager(client) + mgr.upgrade(12345, cpus=4, memory=4) + """ package_items = self._get_package_items() item_id = [] diff --git a/docs/api/managers/cci.rst b/docs/api/managers/cci.rst index 32619bc18..a9b2e18a8 100644 --- a/docs/api/managers/cci.rst +++ b/docs/api/managers/cci.rst @@ -1,5 +1,7 @@ .. _cci: +.. warning:: This module is now deprecated. It has been replaced with the VSIManager. + .. automodule:: SoftLayer.managers.cci :members: :inherited-members: From 8b8a2609022212a405d9cde5204f193b3b631472 Mon Sep 17 00:00:00 2001 From: Nathan Beittenmiller Date: Fri, 9 May 2014 15:59:15 -0500 Subject: [PATCH 0002/2667] Fixing flake8 failures --- SoftLayer/CLI/modules/server.py | 3 +-- SoftLayer/CLI/modules/vs.py | 3 +-- SoftLayer/managers/vs.py | 9 ++++----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index dc7f6349e..06fbac10e 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -819,8 +819,7 @@ def execute(self, args): output.append(FormattedItem( '', ' -- ! Prices reflected here are retail and do not ' - 'take account level discounts and are not guaranteed.') - ) + 'take account level discounts and are not guaranteed.')) if args['--export']: export_file = args.pop('--export') diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 68347804b..4ee69a518 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -437,8 +437,7 @@ def execute(self, args): output.append(FormattedItem( None, ' -- ! Prices reflected here are retail and do not ' - 'take account level discounts and are not guaranteed.') - ) + 'take account level discounts and are not guaranteed.')) if args['--export']: export_file = args.pop('--export') diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index f33155a5c..d34a8e281 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -558,21 +558,20 @@ def upgrade(self, instance_id, cpus=None, memory=None, item_id = [] if cpus: item_id.append({'id': self._get_item_id_for_upgrade( - package_items, 'cpus', cpus, public)}) + package_items, 'cpus', cpus, public)}) if memory: item_id.append({'id': self._get_item_id_for_upgrade( - package_items, 'memory', memory)}) + package_items, 'memory', memory)}) if nic_speed: item_id.append({'id': self._get_item_id_for_upgrade( - package_items, 'nic_speed', - nic_speed)}) + package_items, 'nic_speed', nic_speed)}) order = {} order['complexType'] = \ 'SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade' order['virtualGuests'] = [{'id': int(instance_id)}] order['prices'] = item_id order['properties'] = [{'name': 'MAINTENANCE_WINDOW', - 'value': str(datetime.datetime.now())}] + 'value': str(datetime.datetime.now())}] if cpus or memory or nic_speed: self.client['Product_Order'].verifyOrder(order) self.client['Product_Order'].placeOrder(order) From 55c073dab2f57cd9ba5beb848987eb3f7fa7fb5a Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 12 May 2014 15:45:14 -0400 Subject: [PATCH 0003/2667] Cleans up multi-line masks. Removes un-needed sets --- SoftLayer/managers/hardware.py | 25 ++++++++++++------------- SoftLayer/managers/iscsi.py | 4 ++-- SoftLayer/managers/messaging.py | 4 ++-- SoftLayer/managers/vs.py | 20 +++++++++++--------- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 372f96858..c294cb1c4 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -100,7 +100,7 @@ def list_hardware(self, tags=None, cpus=None, memory=None, hostname=None, """ if 'mask' not in kwargs: - hw_items = set([ + hw_items = [ 'id', 'hostname', 'domain', @@ -112,10 +112,10 @@ def list_hardware(self, tags=None, cpus=None, memory=None, hostname=None, 'primaryBackendIpAddress', 'primaryIpAddress', 'datacenter', - ]) - server_items = set([ + ] + server_items = [ 'activeTransaction[id, transactionStatus[friendlyName,name]]', - ]) + ] kwargs['mask'] = '[mask[%s],' \ ' mask(SoftLayer_Hardware_Server)[%s]]' % \ @@ -263,7 +263,7 @@ def get_hardware(self, hardware_id, **kwargs): """ if 'mask' not in kwargs: - items = set([ + items = [ 'id', 'globalIdentifier', 'fullyQualifiedDomainName', @@ -280,21 +280,20 @@ def get_hardware(self, hardware_id, **kwargs): 'networkManagementIpAddress', 'userData', 'datacenter', - 'networkComponents[id, status, speed, maxSpeed, name,' - 'ipmiMacAddress, ipmiIpAddress, macAddress, primaryIpAddress,' - 'port, primarySubnet]', - 'networkComponents.primarySubnet[id, netmask,' - 'broadcastAddress, networkIdentifier, gateway]', + '''networkComponents[id, status, speed, maxSpeed, name, + ipmiMacAddress, ipmiIpAddress, macAddress, primaryIpAddress, + port, primarySubnet[id, netmask, broadcastAddress, + networkIdentifier, gateway]]''', 'hardwareChassis[id,name]', 'activeTransaction[id, transactionStatus[friendlyName,name]]', - 'operatingSystem.softwareLicense.' 'softwareDescription[manufacturer,name,version,referenceCode]', - 'operatingSystem.passwords[username,password]', + '''operatingSystem[softwareLicense, + passwords[username,password]]''', 'billingItem.recurringFee', 'hourlyBillingFlag', 'tagReferences[id,tag[name,id]]', 'networkVlans[id,vlanNumber,networkSpace]', - ]) + ] kwargs['mask'] = "mask[%s]" % ','.join(items) return self.hardware.getObject(id=hardware_id, **kwargs) diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index ffadd4342..bd640ab41 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -79,7 +79,7 @@ def get_iscsi(self, volume_id, **kwargs): """ if 'mask' not in kwargs: - items = set([ + items = [ 'id', 'serviceResourceName', 'createDate', @@ -92,7 +92,7 @@ def get_iscsi(self, volume_id, **kwargs): 'notes', 'username', 'password' - ]) + ] kwargs['mask'] = "mask[%s]" % ','.join(items) return self.iscsi_svc.getObject(id=volume_id, **kwargs) diff --git a/SoftLayer/managers/messaging.py b/SoftLayer/managers/messaging.py index ea835b4dc..fbe6c400c 100644 --- a/SoftLayer/managers/messaging.py +++ b/SoftLayer/managers/messaging.py @@ -78,12 +78,12 @@ def list_accounts(self, **kwargs): :param dict \\*\\*kwargs: response-level options (mask, limit, etc.) """ if 'mask' not in kwargs: - items = set([ + items = [ 'id', 'name', 'status', 'nodes', - ]) + ] kwargs['mask'] = "mask[%s]" % ','.join(items) return self.client['Account'].getMessageQueueAccounts(**kwargs) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index d34a8e281..9f158f311 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -58,7 +58,7 @@ def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, """ if 'mask' not in kwargs: - items = set([ + items = [ 'id', 'globalIdentifier', 'hostname', @@ -73,7 +73,7 @@ def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, 'datacenter', 'activeTransaction.transactionStatus[friendlyName,name]', 'status', - ]) + ] kwargs['mask'] = "mask[%s]" % ','.join(items) call = 'getVirtualGuests' @@ -149,7 +149,7 @@ def get_instance(self, instance_id, **kwargs): """ if 'mask' not in kwargs: - items = set([ + items = [ 'id', 'globalIdentifier', 'fullyQualifiedDomainName', @@ -163,8 +163,9 @@ def get_instance(self, instance_id, **kwargs): 'privateNetworkOnlyFlag', 'primaryBackendIpAddress', 'primaryIpAddress', - 'networkComponents[id, status, speed, maxSpeed, name,' - 'macAddress, primaryIpAddress, port, primarySubnet]', + '''networkComponents[id, status, speed, maxSpeed, name,' + macAddress, primaryIpAddress, port, + primarySubnet]''' 'lastKnownPowerState.name', 'powerState', 'status', @@ -177,14 +178,15 @@ def get_instance(self, instance_id, **kwargs): 'blockDeviceTemplateGroup[id, name, globalIdentifier]', 'postInstallScriptUri', 'userData', - 'operatingSystem.softwareLicense.' - 'softwareDescription[manufacturer,name,version,referenceCode]', - 'operatingSystem.passwords[username,password]', + '''operatingSystem[passwords[username,password], + softwareLicense.softwareDescription[ + manufacturer,name,version, + referenceCode]]''', 'hourlyBillingFlag', 'billingItem.recurringFee', 'tagReferences[id,tag[name,id]]', 'networkVlans[id,vlanNumber,networkSpace]', - ]) + ] kwargs['mask'] = "mask[%s]" % ','.join(items) return self.guest.getObject(id=instance_id, **kwargs) From 2533cacdfeb9a05f2e966cfdfda9bb7591ed37e9 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 13 May 2014 12:28:27 -0400 Subject: [PATCH 0004/2667] Only call getReverseDomainRecords when the instance has a public ip Resolves #332 --- SoftLayer/CLI/modules/server.py | 4 +++- SoftLayer/CLI/modules/vs.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 06fbac10e..cfc564201 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -178,7 +178,9 @@ def execute(self, args): if tag_row: table.add_row(['tags', listing(tag_row, separator=',')]) - if not result['privateNetworkOnlyFlag']: + # Test to see if this is not private only and actually has a primary + # ip address. (See issue #332) + if not result['privateNetworkOnlyFlag'] and result['primaryIpAddress']: ptr_domains = self.client['Hardware_Server']\ .getReverseDomainRecords(id=hardware_id) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 4ee69a518..407f4a610 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -197,7 +197,9 @@ def execute(self, args): if tag_row: table.add_row(['tags', listing(tag_row, separator=',')]) - if not result['privateNetworkOnlyFlag']: + # Test to see if this is not private only and actually has a primary + # ip address. (See issue #332) + if not result['privateNetworkOnlyFlag'] and result['primaryIpAddress']: ptr_domains = self.client['Virtual_Guest'].\ getReverseDomainRecords(id=vs_id) From 209e6966967e1b10480bc80d68e7a0e6143ad22b Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 13 May 2014 13:15:13 -0400 Subject: [PATCH 0005/2667] Adds test that covers the case of a server without a public ip address --- SoftLayer/CLI/modules/server.py | 5 ++--- SoftLayer/CLI/modules/vs.py | 5 ++--- SoftLayer/tests/CLI/modules/server_tests.py | 20 ++++++++++++++++---- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index cfc564201..eb4e66e81 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -178,9 +178,8 @@ def execute(self, args): if tag_row: table.add_row(['tags', listing(tag_row, separator=',')]) - # Test to see if this is not private only and actually has a primary - # ip address. (See issue #332) - if not result['privateNetworkOnlyFlag'] and result['primaryIpAddress']: + # Test to see if this actually has a primary (public) ip address + if result['primaryIpAddress']: ptr_domains = self.client['Hardware_Server']\ .getReverseDomainRecords(id=hardware_id) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 407f4a610..3047c5c5d 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -197,9 +197,8 @@ def execute(self, args): if tag_row: table.add_row(['tags', listing(tag_row, separator=',')]) - # Test to see if this is not private only and actually has a primary - # ip address. (See issue #332) - if not result['privateNetworkOnlyFlag'] and result['primaryIpAddress']: + # Test to see if this actually has a primary (public) ip address + if result['primaryIpAddress']: ptr_domains = self.client['Virtual_Guest'].\ getReverseDomainRecords(id=vs_id) diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index 930c789b6..90dfd91fe 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -17,6 +17,7 @@ builtins_name = '__builtin__' from SoftLayer.CLI.helpers import format_output, CLIAbort, ArgumentError +from SoftLayer.tests.fixtures import Hardware_Server from SoftLayer.CLI.modules import server @@ -203,12 +204,10 @@ def test_ServerCreateOptions_for_bmc(self, bmpi, packages): self.assertEqual(expected, format_output(output, 'python')) - def test_ServerDetails(self): - hw_id = 1234 - + def test_server_details(self): runnable = server.ServerDetails(client=self.client) - args = {'': hw_id, '--passwords': True, '--price': True} + args = {'': 1234, '--passwords': True, '--price': True} output = runnable.execute(args) expected = { @@ -234,6 +233,19 @@ def test_ServerDetails(self): self.assertEqual(expected, format_output(output, 'python')) + def test_server_details_issue_332(self): + runnable = server.ServerDetails(client=self.client) + result = Hardware_Server.getObject.copy() + result['primaryIpAddress'] = None + self.client['Hardware_Server'].getObject.return_value = result + + runnable.execute({'': 1234, + '--passwords': True, + '--price': True}) + + self.assertFalse(self.client['Hardware_Server'] + .getReverseDomainRecords.called) + def test_ListServers(self): runnable = server.ListServers(client=self.client) From fe045a1936b930935ef7168932d0df166d72bae5 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 11 Apr 2014 17:21:43 -0500 Subject: [PATCH 0006/2667] Adds config CLI tests --- SoftLayer/CLI/modules/config.py | 153 ++++++++++---------- SoftLayer/tests/CLI/modules/config_tests.py | 110 ++++++++++++++ SoftLayer/tests/fixture_client.py | 6 +- SoftLayer/tests/fixtures/Account.py | 3 +- SoftLayer/tests/fixtures/User_Customer.py | 1 + 5 files changed, 196 insertions(+), 77 deletions(-) create mode 100644 SoftLayer/tests/CLI/modules/config_tests.py create mode 100644 SoftLayer/tests/fixtures/User_Customer.py diff --git a/SoftLayer/CLI/modules/config.py b/SoftLayer/CLI/modules/config.py index 37f111b5c..14a0e7cc5 100644 --- a/SoftLayer/CLI/modules/config.py +++ b/SoftLayer/CLI/modules/config.py @@ -12,7 +12,8 @@ import os.path from SoftLayer import ( - Client, SoftLayerAPIError, API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT) + SoftLayerAPIError, API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT) +from SoftLayer.auth import BasicAuthentication from SoftLayer.CLI import ( CLIRunnable, CLIAbort, KeyValueTable, confirm, format_output) from SoftLayer.utils import configparser @@ -50,34 +51,32 @@ def config_table(settings): return table -def get_api_key(username, secret, endpoint_url=None): +def get_api_key(client, username, secret, endpoint_url=None): """ Tries API-Key and password auth to get (and potentially generate) an API key. """ + + client.endpoint_url = endpoint_url + client.auth = None # Try to use a client with username/api key - try: - client = Client( - username=username, - api_key=secret, - endpoint_url=endpoint_url, - timeout=5) - - client['Account'].getCurrentUser() - return secret - except SoftLayerAPIError as ex: - if 'invalid api token' not in ex.faultString.lower(): - raise - - # Try to use a client with username/password - client = Client(endpoint_url=endpoint_url, timeout=5) - client.authenticate_with_password(username, secret) - - user_record = client['Account'].getCurrentUser( - mask='id, apiAuthenticationKeys') - api_keys = user_record['apiAuthenticationKeys'] - if len(api_keys) == 0: - return client['User_Customer'].addApiAuthenticationKey( - id=user_record['id']) - return api_keys[0]['authenticationKey'] + if len(secret) == 64: + try: + client.auth = BasicAuthentication(username, secret) + client['Account'].getCurrentUser() + return secret + except SoftLayerAPIError as ex: + if 'invalid api token' not in ex.faultString.lower(): + raise + else: + # Try to use a client with username/password + client.authenticate_with_password(username, secret) + + user_record = client['Account'].getCurrentUser( + mask='id, apiAuthenticationKeys') + api_keys = user_record['apiAuthenticationKeys'] + if len(api_keys) == 0: + return client['User_Customer'].addApiAuthenticationKey( + id=user_record['id']) + return api_keys[0]['authenticationKey'] class Setup(CLIRunnable): @@ -89,57 +88,21 @@ class Setup(CLIRunnable): action = 'setup' def execute(self, args): - settings = get_settings_from_client(self.client) - - # User Input - # Ask for username - while True: - username = self.env.input( - 'Username [%s]: ' % settings['username']) \ - or settings['username'] - if username: - break - - # Ask for 'secret' which can be api_key or their password - while True: - secret = self.env.getpass( - 'API Key or Password [%s]: ' % settings['api_key']) \ - or settings['api_key'] - if secret: - break - - # Ask for which endpoint they want to use - while True: - endpoint_type = self.env.input( - 'Endpoint (public|private|custom): ') - endpoint_type = endpoint_type.lower() - if not endpoint_type: - endpoint_url = API_PUBLIC_ENDPOINT - break - if endpoint_type == 'public': - endpoint_url = API_PUBLIC_ENDPOINT - break - elif endpoint_type == 'private': - endpoint_url = API_PRIVATE_ENDPOINT - break - elif endpoint_type == 'custom': - endpoint_url = self.env.input( - 'Endpoint URL [%s]: ' % settings['endpoint_url'] - ) or settings['endpoint_url'] - break + username, secret, endpoint_url, timeout = self.get_user_input() - api_key = get_api_key(username, secret, endpoint_url=endpoint_url) - - settings['username'] = username - settings['api_key'] = api_key - settings['endpoint_url'] = endpoint_url + api_key = get_api_key(self.client, username, secret, + endpoint_url=endpoint_url) path = '~/.softlayer' if args.get('--config'): path = args.get('--config') config_path = os.path.expanduser(path) - self.env.out(format_output(config_table(settings))) + self.env.out( + format_output(config_table({'username': username, + 'api_key': api_key, + 'endpoint_url': endpoint_url, + 'timeout': timeout}))) if not confirm('Are you sure you want to write settings to "%s"?' % config_path, default=True): @@ -154,9 +117,9 @@ def execute(self, args): except configparser.DuplicateSectionError: pass - config.set('softlayer', 'username', settings['username']) - config.set('softlayer', 'api_key', settings['api_key']) - config.set('softlayer', 'endpoint_url', settings['endpoint_url']) + config.set('softlayer', 'username', username) + config.set('softlayer', 'api_key', api_key) + config.set('softlayer', 'endpoint_url', endpoint_url) config_file = os.fdopen(os.open(config_path, (os.O_WRONLY | os.O_CREAT), @@ -169,6 +132,50 @@ def execute(self, args): return "Configuration Updated Successfully" + def get_user_input(self): + """ Ask for username, secret (api_key or password) and endpoint_url """ + + defaults = get_settings_from_client(self.client) + timeout = defaults['timeout'] + + # Ask for username + while True: + username = self.env.input( + 'Username [%s]: ' % defaults['username']) \ + or defaults['username'] + if username: + break + + # Ask for 'secret' which can be api_key or their password + while True: + secret = self.env.getpass( + 'API Key or Password [%s]: ' % defaults['api_key']) \ + or defaults['api_key'] + if secret: + break + + # Ask for which endpoint they want to use + while True: + endpoint_type = self.env.input( + 'Endpoint (public|private|custom): ') + endpoint_type = endpoint_type.lower() + if not endpoint_type: + endpoint_url = API_PUBLIC_ENDPOINT + break + if endpoint_type == 'public': + endpoint_url = API_PUBLIC_ENDPOINT + break + elif endpoint_type == 'private': + endpoint_url = API_PRIVATE_ENDPOINT + break + elif endpoint_type == 'custom': + endpoint_url = self.env.input( + 'Endpoint URL [%s]: ' % defaults['endpoint_url'] + ) or defaults['endpoint_url'] + break + + return username, secret, endpoint_url, timeout + class Show(CLIRunnable): """ diff --git a/SoftLayer/tests/CLI/modules/config_tests.py b/SoftLayer/tests/CLI/modules/config_tests.py new file mode 100644 index 000000000..71c7d8d55 --- /dev/null +++ b/SoftLayer/tests/CLI/modules/config_tests.py @@ -0,0 +1,110 @@ +""" + SoftLayer.tests.CLI.modules.config_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from mock import MagicMock, patch +import tempfile + +from SoftLayer import API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT +from SoftLayer.auth import BasicAuthentication +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.modules import config +from SoftLayer.CLI.helpers import format_output +from SoftLayer.CLI.exceptions import CLIAbort + + +class TestHelpShow(unittest.TestCase): + def setUp(self): + client = MagicMock() + client.auth.username = 'user' + client.auth.api_key = '12345' + client.endpoint_url = 'https://some/endpoint' + client.timeout = 10 + self.client = client + + def test_show(self): + command = config.Show(client=self.client) + + output = command.execute({}) + + expected = {'Username': self.client.auth.username, + 'Endpoint URL': self.client.endpoint_url, + 'API Key': self.client.auth.api_key, + 'Timeout': self.client.timeout} + self.assertEqual(expected, format_output(output, 'python')) + + +class TestHelpSetup(unittest.TestCase): + def setUp(self): + client = FixtureClient() + client.auth = BasicAuthentication('default-user', 'default-key') + client.endpoint_url = 'default-endpoint-url' + client.timeout = 10 + self.client = client + self.env = MagicMock() + + @patch('SoftLayer.CLI.modules.config.confirm') + def test_setup(self, confirm_mock): + with tempfile.NamedTemporaryFile() as config_file: + confirm_mock.return_value = True + self.env.getpass.return_value = 'A' * 64 + self.env.input.side_effect = ['user', 'public'] + + command = config.Setup(client=self.client, env=self.env) + output = command.execute({'--config': config_file.name}) + + self.assertEqual('Configuration Updated Successfully', output) + self.assertEqual( + config_file.read().decode("utf-8"), + '''[softlayer] +username = user +api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +endpoint_url = %s + +''' % API_PUBLIC_ENDPOINT) + + @patch('SoftLayer.CLI.modules.config.confirm') + def test_setup_cancel(self, confirm_mock): + with tempfile.NamedTemporaryFile() as config_file: + confirm_mock.return_value = False + self.env.getpass.return_value = 'A' * 64 + self.env.input.side_effect = ['user', 'public'] + + command = config.Setup(client=self.client, env=self.env) + self.assertRaises(CLIAbort, + command.execute, {'--config': config_file.name}) + + def test_get_user_input_private(self): + command = config.Setup(client=self.client, env=self.env) + # get_user_input + self.env.getpass.return_value = 'A' * 64 + self.env.input.side_effect = ['user', 'private'] + + username, secret, endpoint_url, timeout = command.get_user_input() + + self.assertEqual(username, 'user') + self.assertEqual(secret, 'A' * 64) + self.assertEqual(endpoint_url, API_PRIVATE_ENDPOINT) + self.assertEqual(timeout, 10) + + def test_get_user_input_custom(self): + command = config.Setup(client=self.client, env=self.env) + # get_user_input + self.env.getpass.return_value = 'A' * 64 + self.env.input.side_effect = ['user', 'custom', 'custom-endpoint'] + + _, _, endpoint_url, _ = command.get_user_input() + + self.assertEqual(endpoint_url, 'custom-endpoint') + + def test_get_user_input_default(self): + command = config.Setup(client=self.client, env=self.env) + # get_user_input + self.env.getpass.return_value = 'A' * 64 + self.env.input.side_effect = ['user', ''] + + _, _, endpoint_url, _ = command.get_user_input() + + self.assertEqual(endpoint_url, API_PUBLIC_ENDPOINT) diff --git a/SoftLayer/tests/fixture_client.py b/SoftLayer/tests/fixture_client.py index 75e99da3e..94e1100c5 100644 --- a/SoftLayer/tests/fixture_client.py +++ b/SoftLayer/tests/fixture_client.py @@ -4,11 +4,11 @@ :license: MIT, see LICENSE for more details. """ -from mock import MagicMock +from mock import MagicMock, MagicMixin from importlib import import_module -class FixtureClient(object): +class FixtureClient(MagicMixin): def __init__(self): # Keep track of Service instances in order to do future assertions @@ -27,7 +27,7 @@ def reset_mock(self): self.loaded_services = {} -class FixtureService(object): +class FixtureService(MagicMixin): def __init__(self, service_name): self.service_name = service_name diff --git a/SoftLayer/tests/fixtures/Account.py b/SoftLayer/tests/fixtures/Account.py index 0f904b471..b1199ad85 100644 --- a/SoftLayer/tests/fixtures/Account.py +++ b/SoftLayer/tests/fixtures/Account.py @@ -315,7 +315,8 @@ getClosedTickets = [ticket for ticket in getTickets if ticket['statusId'] == 1002] -getCurrentUser = {"id": 12345} +getCurrentUser = {'id': 12345, + 'apiAuthenticationKeys': [{'authenticationKey': 'A' * 64}]} getCdnAccounts = [ { diff --git a/SoftLayer/tests/fixtures/User_Customer.py b/SoftLayer/tests/fixtures/User_Customer.py new file mode 100644 index 000000000..72d28db8a --- /dev/null +++ b/SoftLayer/tests/fixtures/User_Customer.py @@ -0,0 +1 @@ +addApiAuthenticationKey = "A" * 64 From c35c3cbbcf95c0a44e5cb7a8ba3110c1cf6d1322 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 24 Apr 2014 17:19:09 -0500 Subject: [PATCH 0007/2667] CDN Tests + Cleanup * Removed a function tests that periodically fails * Adds CDN CLI Tests * Addresses some small issues with CDN * Fixes a test which was failing due to differences in dict ordering --- SoftLayer/CLI/modules/cdn.py | 8 +- SoftLayer/managers/cdn.py | 3 +- SoftLayer/tests/CLI/modules/cdn_tests.py | 84 +++++++++++++++++++ .../Network_ContentDelivery_Account.py | 3 +- SoftLayer/tests/functional_tests.py | 14 ---- SoftLayer/tests/managers/queue_tests.py | 4 +- 6 files changed, 90 insertions(+), 26 deletions(-) create mode 100644 SoftLayer/tests/CLI/modules/cdn_tests.py diff --git a/SoftLayer/CLI/modules/cdn.py b/SoftLayer/CLI/modules/cdn.py index 839794b25..67c4a21d6 100644 --- a/SoftLayer/CLI/modules/cdn.py +++ b/SoftLayer/CLI/modules/cdn.py @@ -90,8 +90,7 @@ class LoadContent(CLIRunnable): def execute(self, args): manager = CDNManager(self.client) - return str(manager.load_content(args.get(''), - args.get(''))) + manager.load_content(args.get(''), args.get('')) class PurgeContent(CLIRunnable): @@ -158,10 +157,7 @@ class AddOrigin(CLIRunnable): def execute(self, args): manager = CDNManager(self.client) - media_type = args.get('--type', 'http') - - if not media_type: - media_type = 'http' + media_type = args.get('--type') or 'http' manager.add_origin(args.get(''), media_type, args.get(''), args.get('--cname', None)) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 03baa8f0a..2270c75d7 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -41,8 +41,7 @@ def get_account(self, account_id, **kwargs): """ if 'mask' not in kwargs: - items = set(['status']) - kwargs['mask'] = 'mask[%s]' % ','.join(items) + kwargs['mask'] = 'status' return self.account.getObject(id=account_id, **kwargs) diff --git a/SoftLayer/tests/CLI/modules/cdn_tests.py b/SoftLayer/tests/CLI/modules/cdn_tests.py new file mode 100644 index 000000000..31e66aef6 --- /dev/null +++ b/SoftLayer/tests/CLI/modules/cdn_tests.py @@ -0,0 +1,84 @@ +""" + SoftLayer.tests.CLI.modules.cdn_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.helpers import format_output +from SoftLayer.CLI.modules import cdn + + +class CdnTests(unittest.TestCase): + def setUp(self): + self.client = FixtureClient() + + def test_list_accounts(self): + command = cdn.ListAccounts(client=self.client) + + output = command.execute({'--sortby': None}) + self.assertEqual([{'notes': None, + 'created': '2012-06-25T14:05:28-07:00', + 'type': 'ORIGIN_PULL', + 'id': 1234, + 'account_name': '1234a'}, + {'notes': None, + 'created': '2012-07-24T13:34:25-07:00', + 'type': 'POP_PULL', + 'id': 1234, + 'account_name': '1234a'}], + format_output(output, 'python')) + + def test_detail_account(self): + command = cdn.DetailAccount(client=self.client) + + output = command.execute({'': '1234'}) + self.assertEqual({'notes': None, + 'created': '2012-06-25T14:05:28-07:00', + 'type': 'ORIGIN_PULL', + 'status': 'ACTIVE', + 'id': 1234, + 'account_name': '1234a'}, + format_output(output, 'python')) + + def test_load_content(self): + command = cdn.LoadContent(client=self.client) + + output = command.execute({'': '1234', + '': ['http://example.com']}) + self.assertEqual(None, output) + + def test_purge_content(self): + command = cdn.PurgeContent(client=self.client) + + output = command.execute({'': '1234', + '': ['http://example.com']}) + self.assertEqual(None, output) + + def test_list_origins(self): + command = cdn.ListOrigins(client=self.client) + + output = command.execute({'': '1234'}) + self.assertEqual([ + {'media_type': 'FLASH', + 'origin_url': 'http://ams01.objectstorage.softlayer.net:80', + 'cname': None, + 'id': '12345'}, + {'media_type': 'FLASH', + 'origin_url': 'http://sng01.objectstorage.softlayer.net:80', + 'cname': None, + 'id': '12345'}], format_output(output, 'python')) + + def test_add_origin(self): + command = cdn.AddOrigin(client=self.client) + + output = command.execute({'': '1234', + '': 'http://example.com'}) + self.assertEqual(None, output) + + def test_remove_origin(self): + command = cdn.RemoveOrigin(client=self.client) + + output = command.execute({'': '1234', + '': '12345'}) + self.assertEqual(None, output) diff --git a/SoftLayer/tests/fixtures/Network_ContentDelivery_Account.py b/SoftLayer/tests/fixtures/Network_ContentDelivery_Account.py index b96da8d13..9581d5b72 100644 --- a/SoftLayer/tests/fixtures/Network_ContentDelivery_Account.py +++ b/SoftLayer/tests/fixtures/Network_ContentDelivery_Account.py @@ -7,7 +7,8 @@ "dependantServiceFlag": True, "cdnSolutionName": "ORIGIN_PULL", "statusId": 4, - "accountId": 1234 + "accountId": 1234, + "status": {'name': 'ACTIVE'}, } getOriginPullMappingInformation = [ diff --git a/SoftLayer/tests/functional_tests.py b/SoftLayer/tests/functional_tests.py index c1b1f5024..16cc91d7d 100644 --- a/SoftLayer/tests/functional_tests.py +++ b/SoftLayer/tests/functional_tests.py @@ -32,20 +32,6 @@ def test_failed_auth(self): SoftLayer.SoftLayerAPIError, client['SoftLayer_User_Customer'].getPortalLoginToken) - def test_404(self): - client = SoftLayer.Client( - username='doesnotexist', api_key='issurelywrong', timeout=20, - endpoint_url='http://httpbin.org/status/404') - - try: - client['SoftLayer_User_Customer'].doSomething() - except SoftLayer.SoftLayerAPIError as e: - self.assertEqual(e.faultCode, 404) - self.assertIn('NOT FOUND', e.faultString) - self.assertIn('NOT FOUND', e.reason) - else: - self.fail('No Exception Raised') - def test_no_hostname(self): try: # This test will fail if 'notvalidsoftlayer.com' becomes a thing diff --git a/SoftLayer/tests/managers/queue_tests.py b/SoftLayer/tests/managers/queue_tests.py index f7dbebc26..2851c4cf6 100644 --- a/SoftLayer/tests/managers/queue_tests.py +++ b/SoftLayer/tests/managers/queue_tests.py @@ -429,9 +429,7 @@ def test_create_subscription(self, make_request): 'example_topic', 'queue', **endpoint_details) make_request.assert_called_with( - 'post', 'topics/example_topic/subscriptions', - data=json.dumps({ - 'endpoint_type': 'queue', 'endpoint': endpoint_details})) + 'post', 'topics/example_topic/subscriptions', data=ANY) self.assertEqual(SUBSCRIPTION_1, result) @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') From 232c46689c530709ba38766a300bc6ca1106d383 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 07:47:59 -0500 Subject: [PATCH 0008/2667] Adds DNS CLI tests --- SoftLayer/CLI/modules/dns.py | 62 ++++-------- SoftLayer/CLI/modules/vs.py | 8 +- SoftLayer/exceptions.py | 5 - SoftLayer/tests/CLI/modules/dns_tests.py | 115 +++++++++++++++++++++++ SoftLayer/tests/fixtures/Account.py | 5 +- SoftLayer/tests/fixtures/Dns_Domain.py | 7 +- SoftLayer/tests/managers/dns_tests.py | 41 +++----- 7 files changed, 151 insertions(+), 92 deletions(-) create mode 100644 SoftLayer/tests/CLI/modules/dns_tests.py diff --git a/SoftLayer/CLI/modules/dns.py b/SoftLayer/CLI/modules/dns.py index 72431885b..8b7fe6402 100755 --- a/SoftLayer/CLI/modules/dns.py +++ b/SoftLayer/CLI/modules/dns.py @@ -18,7 +18,7 @@ from SoftLayer.CLI import ( CLIRunnable, no_going_back, Table, CLIAbort, resolve_id) -from SoftLayer import DNSManager, DNSZoneNotFound +from SoftLayer import DNSManager class DumpZone(CLIRunnable): @@ -35,10 +35,7 @@ class DumpZone(CLIRunnable): def execute(self, args): manager = DNSManager(self.client) zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') - try: - return manager.dump_zone(zone_id) - except DNSZoneNotFound: - raise CLIAbort("No zone found matching: %s" % args['']) + return manager.dump_zone(zone_id) class CreateZone(CLIRunnable): @@ -102,13 +99,7 @@ def execute(self, args): def list_zone(self, args): """ list records for a particular zone """ manager = DNSManager(self.client) - table = Table([ - "id", - "record", - "type", - "ttl", - "value", - ]) + table = Table(['id', 'record', 'type', 'ttl', 'value']) table.align['ttl'] = 'l' table.align['record'] = 'r' @@ -116,16 +107,13 @@ def list_zone(self, args): zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') - try: - records = manager.get_records( - zone_id, - record_type=args.get('--type'), - host=args.get('--record'), - ttl=args.get('--ttl'), - data=args.get('--data'), - ) - except DNSZoneNotFound: - raise CLIAbort("No zone found matching: %s" % args['']) + records = manager.get_records( + zone_id, + record_type=args.get('--type'), + host=args.get('--record'), + ttl=args.get('--ttl'), + data=args.get('--data'), + ) for record in records: table.add_row([ @@ -142,12 +130,7 @@ def list_all_zones(self): """ List all zones """ manager = DNSManager(self.client) zones = manager.list_zones() - table = Table([ - "id", - "zone", - "serial", - "updated", - ]) + table = Table(['id', 'zone', 'serial', 'updated']) table.align['serial'] = 'c' table.align['updated'] = 'c' @@ -184,14 +167,13 @@ def execute(self, args): manager = DNSManager(self.client) zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') - args['--ttl'] = args['--ttl'] or 7200 manager.create_record( zone_id, args[''], args[''], args[''], - ttl=args['--ttl']) + ttl=args['--ttl'] or 7200) class EditRecord(CLIRunnable): @@ -216,12 +198,9 @@ def execute(self, args): manager = DNSManager(self.client) zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') - try: - results = manager.get_records( - zone_id, - host=args['']) - except DNSZoneNotFound: - raise CLIAbort("No zone found matching: %s" % args['']) + results = manager.get_records( + zone_id, + host=args['']) for result in results: if args['--id'] and str(result['id']) != args['--id']: @@ -254,18 +233,13 @@ def execute(self, args): if args['--id']: records = [{'id': args['--id']}] else: - try: - records = manager.get_records( - zone_id, - host=args['']) - except DNSZoneNotFound: - raise CLIAbort("No zone found matching: %s" % args['']) + records = manager.get_records( + zone_id, + host=args['']) if args['--really'] or no_going_back('yes'): table = Table(['record']) for result in records: - if args.get('--id') and args['--id'] != result['id']: - continue manager.delete_record(result['id']) table.add_row([result['id']]) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 3047c5c5d..0d6c05e5d 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -31,7 +31,7 @@ from os import linesep import os.path -from SoftLayer import VSManager, SshKeyManager, DNSManager, DNSZoneNotFound +from SoftLayer import VSManager, SshKeyManager, DNSManager from SoftLayer.utils import lookup from SoftLayer.CLI import ( CLIRunnable, Table, no_going_back, confirm, mb_to_gb, listing, @@ -910,11 +910,7 @@ def sync_ptr_record(): if not instance['primaryIpAddress']: raise CLIAbort('No primary IP address associated with this VS') - try: - zone = dns.get_zone(zone_id) - except DNSZoneNotFound: - raise CLIAbort("Unable to create A record, " - "no zone found matching: %s" % instance['domain']) + zone = dns.get_zone(zone_id) go_for_it = args['--really'] or confirm( "Attempt to update DNS records for %s" diff --git a/SoftLayer/exceptions.py b/SoftLayer/exceptions.py index 8b8cd017f..5d86a9b35 100644 --- a/SoftLayer/exceptions.py +++ b/SoftLayer/exceptions.py @@ -89,8 +89,3 @@ class InvalidMethodParameters(ServerError): class InternalError(ServerError): """ Internal Server Error """ pass - - -class DNSZoneNotFound(SoftLayerError): - """ DNS Zone was not found """ - pass diff --git a/SoftLayer/tests/CLI/modules/dns_tests.py b/SoftLayer/tests/CLI/modules/dns_tests.py new file mode 100644 index 000000000..633c61c5d --- /dev/null +++ b/SoftLayer/tests/CLI/modules/dns_tests.py @@ -0,0 +1,115 @@ +""" + SoftLayer.tests.CLI.modules.dns_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from mock import patch + +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.helpers import format_output +from SoftLayer.CLI.exceptions import CLIAbort +from SoftLayer.CLI.modules import dns + + +class DnsTests(unittest.TestCase): + def setUp(self): + self.client = FixtureClient() + + def test_dump_zone(self): + command = dns.DumpZone(client=self.client) + + output = command.execute({'': '1234'}) + self.assertEqual('lots of text', output) + + def test_create_zone(self): + command = dns.CreateZone(client=self.client) + + output = command.execute({'': 'example.com'}) + self.assertEqual(None, output) + + @patch('SoftLayer.CLI.modules.dns.no_going_back') + def test_delete_zone(self, no_going_back_mock): + no_going_back_mock.return_value = True + command = dns.DeleteZone(client=self.client) + + output = command.execute({'': 'example.com', '--really': False}) + self.assertEqual(None, output) + + no_going_back_mock.return_value = False + command = dns.DeleteZone(client=self.client) + + self.assertRaises(CLIAbort, + command.execute, {'': 'example.com', + '--really': False}) + + def test_list_zones(self): + command = dns.ListZones(client=self.client) + + output = command.execute({'': None}) + self.assertEqual([{'serial': 2014030728, + 'updated': '2014-03-07T13:52:31-06:00', + 'id': 12345, 'zone': 'example.com'}], + format_output(output, 'python')) + + def test_list_all_zones(self): + command = dns.ListZones(client=self.client) + + output = command.execute({'': 'example.com'}) + self.assertEqual([{'id': 1, + 'record': 'hostname', + 'ttl': 100, + 'type': 'A', + 'value': 'd'}], + format_output(output, 'python')) + + def test_add_record(self): + command = dns.AddRecord(client=self.client) + + output = command.execute({'': 'example.com', + '': 'hostname', + '': 'A', + '': 'd', + '--ttl': 100}) + self.assertEqual(None, output) + + def test_edit_record(self): + command = dns.EditRecord(client=self.client) + + output = command.execute({'': 'example.com', + '': 'hostname', + '': 'A', + '--data': 'd', + '--id': 1, + '--ttl': 100}) + self.assertEqual(None, output) + + output = command.execute({'': 'example.com', + '': 'hostname', + '': 'A', + '--data': 'd', + '--id': None, + '--ttl': 100}) + self.assertEqual(None, output) + + @patch('SoftLayer.CLI.modules.dns.no_going_back') + def test_delete_record(self, no_going_back_mock): + no_going_back_mock.return_value = True + command = dns.RecordRemove(client=self.client) + output = command.execute({'': 'example.com', + '': 'hostname', + '--id': '1', + '--really': False}) + self.assertEqual([{'record': '1'}], format_output(output, 'python')) + + output = command.execute({'': 'example.com', + '': 'hostname', + '--id': None, + '--really': False}) + self.assertEqual([{'record': 1}], format_output(output, 'python')) + + no_going_back_mock.return_value = False + self.assertRaises(CLIAbort, command.execute, {'': 'example.com', + '': 'hostname', + '--id': 1, + '--really': False}) diff --git a/SoftLayer/tests/fixtures/Account.py b/SoftLayer/tests/fixtures/Account.py index b1199ad85..d38ac87a3 100644 --- a/SoftLayer/tests/fixtures/Account.py +++ b/SoftLayer/tests/fixtures/Account.py @@ -175,7 +175,10 @@ }, ] }] -getDomains = [{'name': 'example.com', 'id': 12345}] +getDomains = [{'name': 'example.com', + 'id': 12345, + 'serial': 2014030728, + 'updateDate': '2014-03-07T13:52:31-06:00'}] getObject = { 'networkVlans': [{ diff --git a/SoftLayer/tests/fixtures/Dns_Domain.py b/SoftLayer/tests/fixtures/Dns_Domain.py index d61377d66..f9c5106ad 100644 --- a/SoftLayer/tests/fixtures/Dns_Domain.py +++ b/SoftLayer/tests/fixtures/Dns_Domain.py @@ -3,11 +3,6 @@ editObject = True getZoneFileContents = 'lots of text' getResourceRecords = [ - {'ttl': 7200, 'data': 'd', 'host': 'a', 'type': 'cname'}, - {'ttl': 900, 'data': '1', 'host': 'b', 'type': 'a'}, - {'ttl': 900, 'data': 'x', 'host': 'c', 'type': 'ptr'}, - {'ttl': 86400, 'data': 'b', 'host': 'd', 'type': 'txt'}, - {'ttl': 86400, 'data': 'b', 'host': 'e', 'type': 'txt'}, - {'ttl': 600, 'data': 'b', 'host': 'f', 'type': 'txt'}, + {'id': 1, 'ttl': 7200, 'data': 'd', 'host': 'hostname', 'type': 'a'}, ] getObject = {'id': 98765, 'name': 'test-example.com'} diff --git a/SoftLayer/tests/managers/dns_tests.py b/SoftLayer/tests/managers/dns_tests.py index d0196b784..708212441 100644 --- a/SoftLayer/tests/managers/dns_tests.py +++ b/SoftLayer/tests/managers/dns_tests.py @@ -106,41 +106,22 @@ def test_dump_zone(self): def test_get_record(self): D = self.client['Dns_Domain'].getResourceRecords - records = Dns_Domain.getResourceRecords # maybe valid domain, but no records matching D.return_value = [] - self.assertEqual(self.dns_client.get_records(12345), - []) + self.assertEqual(self.dns_client.get_records(12345), []) D.reset_mock() - D.return_value = [records[1]] - self.dns_client.get_records(12345, record_type='a') + D.return_value = [Dns_Domain.getResourceRecords[0]] + self.dns_client.get_records(12345, + record_type='a', + host='hostname', + data='a', + ttl='86400') D.assert_called_once_with( id=12345, - filter={'resourceRecords': {'type': {"operation": "_= a"}}}, - mask=ANY) - - D.reset_mock() - D.return_value = [records[0]] - self.dns_client.get_records(12345, host='a') - D.assert_called_once_with( - id=12345, - filter={'resourceRecords': {'host': {"operation": "_= a"}}}, - mask=ANY) - - D.reset_mock() - D.return_value = records[3:5] - self.dns_client.get_records(12345, data='a') - D.assert_called_once_with( - id=12345, - filter={'resourceRecords': {'data': {"operation": "_= a"}}}, - mask=ANY) - - D.reset_mock() - D.return_value = records[3:5] - self.dns_client.get_records(12345, ttl='86400') - D.assert_called_once_with( - id=12345, - filter={'resourceRecords': {'ttl': {"operation": 86400}}}, + filter={'resourceRecords': {'type': {'operation': '_= a'}, + 'host': {'operation': '_= hostname'}, + 'data': {'operation': '_= a'}, + 'ttl': {'operation': 86400}}}, mask=ANY) From c7e7a666e21af33820654be3eb05a04a7681908a Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 07:55:29 -0500 Subject: [PATCH 0009/2667] Makes config CLI test less likely to fail. Fixes style issue --- SoftLayer/tests/CLI/modules/config_tests.py | 15 +++++++-------- SoftLayer/tests/CLI/modules/dns_tests.py | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/SoftLayer/tests/CLI/modules/config_tests.py b/SoftLayer/tests/CLI/modules/config_tests.py index 71c7d8d55..2bfa86667 100644 --- a/SoftLayer/tests/CLI/modules/config_tests.py +++ b/SoftLayer/tests/CLI/modules/config_tests.py @@ -56,14 +56,13 @@ def test_setup(self, confirm_mock): output = command.execute({'--config': config_file.name}) self.assertEqual('Configuration Updated Successfully', output) - self.assertEqual( - config_file.read().decode("utf-8"), - '''[softlayer] -username = user -api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -endpoint_url = %s - -''' % API_PUBLIC_ENDPOINT) + contents = config_file.read().decode("utf-8") + self.assertTrue('[softlayer]' in contents) + self.assertTrue('username = user' in contents) + self.assertTrue('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA' in contents) + self.assertTrue('endpoint_url = %s' % API_PUBLIC_ENDPOINT + in contents) @patch('SoftLayer.CLI.modules.config.confirm') def test_setup_cancel(self, confirm_mock): diff --git a/SoftLayer/tests/CLI/modules/dns_tests.py b/SoftLayer/tests/CLI/modules/dns_tests.py index 633c61c5d..231b8141a 100644 --- a/SoftLayer/tests/CLI/modules/dns_tests.py +++ b/SoftLayer/tests/CLI/modules/dns_tests.py @@ -48,8 +48,8 @@ def test_list_zones(self): output = command.execute({'': None}) self.assertEqual([{'serial': 2014030728, - 'updated': '2014-03-07T13:52:31-06:00', - 'id': 12345, 'zone': 'example.com'}], + 'updated': '2014-03-07T13:52:31-06:00', + 'id': 12345, 'zone': 'example.com'}], format_output(output, 'python')) def test_list_all_zones(self): From 40a48e9cbaecafd5d307fd4f657279e828e28ead Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 09:00:12 -0500 Subject: [PATCH 0010/2667] Adds some globalip tests --- SoftLayer/CLI/modules/globalip.py | 20 ++++----------- SoftLayer/tests/fixtures/Account.py | 30 +++++++++++++++++------ SoftLayer/tests/managers/network_tests.py | 2 +- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/SoftLayer/CLI/modules/globalip.py b/SoftLayer/CLI/modules/globalip.py index 61f73a27e..5640d2dd2 100644 --- a/SoftLayer/CLI/modules/globalip.py +++ b/SoftLayer/CLI/modules/globalip.py @@ -35,9 +35,6 @@ def execute(self, args): global_ip_id = resolve_id(mgr.resolve_global_ip_ids, args.get(''), name='global ip') - if not global_ip_id: - raise CLIAbort("Unable to find global IP record for " + - args['']) mgr.assign_global_ip(global_ip_id, args['']) @@ -60,7 +57,7 @@ def execute(self, args): if args['--really'] or no_going_back(global_ip_id): mgr.cancel_global_ip(global_ip_id) else: - CLIAbort('Aborted') + raise CLIAbort('Aborted') class GlobalIpCreate(CLIRunnable): @@ -89,8 +86,7 @@ def execute(self, args): raise CLIAbort('Cancelling order.') result = mgr.add_global_ip(version=version, test_order=args.get('--test')) - if not result: - return 'Unable to place order: No valid price IDs found.' + table = Table(['Item', 'cost']) table.align['Item'] = 'r' table.align['cost'] = 'r' @@ -121,9 +117,7 @@ class GlobalIpList(CLIRunnable): def execute(self, args): mgr = NetworkManager(self.client) - table = Table([ - 'id', 'ip', 'assigned', 'target' - ]) + table = Table(['id', 'ip', 'assigned', 'target']) table.sortby = args.get('--sortby') or 'id' version = 0 @@ -146,9 +140,8 @@ def execute(self, args): target += (' (%s)' % virtual_guest['fullyQualifiedDomainName']) elif ip_address['destinationIpAddress'].get('hardware'): - target += ' (' + \ - dest['hardware']['fullyQualifiedDomainName'] + \ - ')' + target += (' (%s)' + % dest['hardware']['fullyQualifiedDomainName']) table.add_row([ip_address['id'], ip_address['ipAddress']['ipAddress'], @@ -173,7 +166,4 @@ def execute(self, args): global_ip_id = resolve_id(mgr.resolve_global_ip_ids, args.get(''), name='global ip') - if not global_ip_id: - raise CLIAbort("Unable to find global IP record for " + - args['']) mgr.unassign_global_ip(global_ip_id) diff --git a/SoftLayer/tests/fixtures/Account.py b/SoftLayer/tests/fixtures/Account.py index d38ac87a3..c5bf6cc5e 100644 --- a/SoftLayer/tests/fixtures/Account.py +++ b/SoftLayer/tests/fixtures/Account.py @@ -233,15 +233,29 @@ 'privateResidenceFlag': 'privateResidenceFlag', } -getGlobalIpRecords = [ - { - 'id': '200', - 'ipAddress': { - 'subnet': { - 'networkIdentifier': '10.0.0.1', - }, +getGlobalIpRecords = [{ + 'id': '200', + 'ipAddress': { + 'subnet': { + 'networkIdentifier': '10.0.0.1', }, - }] + 'ipAddress': '127.0.0.1', + }, + 'destinationIpAddress': { + 'ipAddress': '127.0.0.1', + 'virtualGuest': {'fullyQualifiedDomainName': 'example.com'}} +}, { + 'id': '201', + 'ipAddress': { + 'subnet': { + 'networkIdentifier': '10.0.0.1', + }, + 'ipAddress': '127.0.0.1', + }, + 'destinationIpAddress': { + 'ipAddress': '127.0.0.1', + 'hardware': {'fullyQualifiedDomainName': 'example.com'}} +}] getSubnets = [ { diff --git a/SoftLayer/tests/managers/network_tests.py b/SoftLayer/tests/managers/network_tests.py index 10a923f05..5bc4d249e 100644 --- a/SoftLayer/tests/managers/network_tests.py +++ b/SoftLayer/tests/managers/network_tests.py @@ -239,7 +239,7 @@ def test_summary_by_datacenter(self): def test_resolve_global_ip_ids(self): service = self.client['Account'] _id = self.network.resolve_global_ip_ids('10.0.0.1') - self.assertEqual(_id, ['200']) + self.assertEqual(_id, ['200', '201']) service.getGlobalIpRecords.return_value = [] _id = self.network.resolve_global_ip_ids('nope') From bf965beb5a82215641536528fd23baf9ca36f180 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 11:43:14 -0500 Subject: [PATCH 0011/2667] Adds globalip tests --- SoftLayer/tests/CLI/modules/globalip_tests.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 SoftLayer/tests/CLI/modules/globalip_tests.py diff --git a/SoftLayer/tests/CLI/modules/globalip_tests.py b/SoftLayer/tests/CLI/modules/globalip_tests.py new file mode 100644 index 000000000..8384b6280 --- /dev/null +++ b/SoftLayer/tests/CLI/modules/globalip_tests.py @@ -0,0 +1,63 @@ +""" + SoftLayer.tests.CLI.modules.globalip_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from mock import patch + +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.helpers import format_output +from SoftLayer.CLI.exceptions import CLIAbort +from SoftLayer.CLI.modules import globalip + + +class DnsTests(unittest.TestCase): + def setUp(self): + self.client = FixtureClient() + + def test_ip_assign(self): + command = globalip.GlobalIpAssign(client=self.client) + + output = command.execute({'': '1', + '': '127.0.0.1'}) + self.assertEqual(None, output) + + @patch('SoftLayer.CLI.modules.globalip.no_going_back') + def test_ip_cancel(self, no_going_back_mock): + no_going_back_mock.return_value = True + command = globalip.GlobalIpCancel(client=self.client) + + output = command.execute({'': '1', '--really': False}) + self.assertEqual(None, output) + + no_going_back_mock.return_value = False + + self.assertRaises(CLIAbort, + command.execute, + {'': '1', '--really': False}) + + def test_ip_list(self): + command = globalip.GlobalIpList(client=self.client) + + output = command.execute({'--v4': True}) + self.assertEqual([{'assigned': 'Yes', + 'id': '200', + 'ip': '127.0.0.1', + 'target': '127.0.0.1 (example.com)'}, + {'assigned': 'Yes', + 'id': '201', + 'ip': '127.0.0.1', + 'target': '127.0.0.1 (example.com)'}], + format_output(output, 'python')) + + output = command.execute({'--v6': True}) + self.assertEqual([{'assigned': 'Yes', + 'id': '200', + 'ip': '127.0.0.1', + 'target': '127.0.0.1 (example.com)'}, + {'assigned': 'Yes', + 'id': '201', + 'ip': '127.0.0.1', + 'target': '127.0.0.1 (example.com)'}], + format_output(output, 'python')) From 229adb3bcd998e9eb751ba25bf4ddfc6841d2142 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 11:45:05 -0500 Subject: [PATCH 0012/2667] Adds NAS CLI test --- SoftLayer/tests/CLI/modules/nas_tests.py | 26 ++++++++++++++++++++++++ SoftLayer/tests/fixtures/Account.py | 9 ++++++++ 2 files changed, 35 insertions(+) create mode 100644 SoftLayer/tests/CLI/modules/nas_tests.py diff --git a/SoftLayer/tests/CLI/modules/nas_tests.py b/SoftLayer/tests/CLI/modules/nas_tests.py new file mode 100644 index 000000000..053c10331 --- /dev/null +++ b/SoftLayer/tests/CLI/modules/nas_tests.py @@ -0,0 +1,26 @@ +""" + SoftLayer.tests.CLI.modules.nas_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.modules import nas +from SoftLayer.CLI.helpers import format_output + + +class RWhoisTests(unittest.TestCase): + def setUp(self): + self.client = FixtureClient() + + def test_list_nas(self): + command = nas.ListNAS(client=self.client) + output = command.execute({}) + + self.assertEqual([{'username': 'user', + 'datacenter': 'Dallas', + 'server': '127.0.0.1', + 'password': 'pass', + 'id': 1, + 'size': 10}], + format_output(output, 'python')) diff --git a/SoftLayer/tests/fixtures/Account.py b/SoftLayer/tests/fixtures/Account.py index c5bf6cc5e..f6ecaf79f 100644 --- a/SoftLayer/tests/fixtures/Account.py +++ b/SoftLayer/tests/fixtures/Account.py @@ -373,3 +373,12 @@ }] getAdcLoadBalancers = [] + +getNasNetworkStorage = [{ + 'id': 1, + 'capacityGb': 10, + 'serviceResource': {'datacenter': {'name': 'Dallas'}}, + 'username': 'user', + 'password': 'pass', + 'serviceResourceBackendIpAddress': '127.0.0.1', +}] From 16f54e7581ba3966a127728005c7838ba3afeb5c Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 19:00:09 -0500 Subject: [PATCH 0013/2667] Adds the first firewall CLI test. I made firewall an non-happy case in the fixtures due to them basically being tacked onto the already-existing VLAN structure. --- SoftLayer/tests/CLI/modules/firewall_tests.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 SoftLayer/tests/CLI/modules/firewall_tests.py diff --git a/SoftLayer/tests/CLI/modules/firewall_tests.py b/SoftLayer/tests/CLI/modules/firewall_tests.py new file mode 100644 index 000000000..1969bdeb5 --- /dev/null +++ b/SoftLayer/tests/CLI/modules/firewall_tests.py @@ -0,0 +1,55 @@ +""" + SoftLayer.tests.CLI.modules.firewall_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +# from mock import patch + +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.helpers import format_output +# from SoftLayer.CLI.exceptions import CLIAbort +from SoftLayer.CLI.modules import firewall + + +class FirewallTests(unittest.TestCase): + def setUp(self): + self.client = FixtureClient() + + def test_list_firewalls(self): + call = self.client['Account'].getNetworkVlans + call.return_value = [{ + 'id': 1, + 'dedicatedFirewallFlag': True, + 'highAvailabilityFirewallFlag': True, + 'networkVlanFirewall': {'id': 1234}, + }, { + 'id': 2, + 'dedicatedFirewallFlag': False, + 'firewallGuestNetworkComponents': [{ + 'id': 1234, + 'guestNetworkComponent': {'guest': {'id': 1}}, + 'status': 'ok'}], + 'firewallNetworkComponents': [{ + 'id': 1234, + 'networkComponent': {'downlinkComponent': {'hardwareId': 1}}, + 'status': 'ok'}], + } + ] + command = firewall.FWList(client=self.client) + + output = command.execute({}) + + self.assertEqual([{'type': 'VLAN - dedicated', + 'server/vlan id': 1, + 'features': ['HA'], + 'firewall id': 'vlan:1234'}, + {'features': '-', + 'firewall id': 'cci:1234', + 'server/vlan id': 1, + 'type': 'CCI - standard'}, + {'features': '-', + 'firewall id': 'server:1234', + 'server/vlan id': 1, + 'type': 'Server - standard'}], + format_output(output, 'python')) From 93a275467e3c533b9ed82555268eb7b6b12732b8 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 19:02:07 -0500 Subject: [PATCH 0014/2667] Adds first firewall CLI tests --- SoftLayer/CLI/modules/firewall.py | 36 +++++++++++++--------- SoftLayer/managers/firewall.py | 24 ++++++--------- SoftLayer/tests/fixtures/Account.py | 11 ------- SoftLayer/tests/managers/firewall_tests.py | 17 ++++++++-- 4 files changed, 45 insertions(+), 43 deletions(-) diff --git a/SoftLayer/CLI/modules/firewall.py b/SoftLayer/CLI/modules/firewall.py index f374d82f5..b4e4b93f3 100755 --- a/SoftLayer/CLI/modules/firewall.py +++ b/SoftLayer/CLI/modules/firewall.py @@ -14,13 +14,15 @@ # :license: MIT, see LICENSE for more details. from __future__ import print_function -from SoftLayer import FirewallManager, SoftLayerError -from SoftLayer.CLI import CLIRunnable, Table, listing, resolve_id, confirm -from SoftLayer.CLI.helpers import blank, CLIAbort from subprocess import call import os import tempfile +from SoftLayer import FirewallManager, SoftLayerError +from SoftLayer.utils import lookup +from SoftLayer.CLI import CLIRunnable, Table, listing, resolve_id, confirm +from SoftLayer.CLI.helpers import blank, CLIAbort + DELIMITER = "=========================================\n" @@ -217,12 +219,11 @@ def execute(self, args): 'type', 'features', 'server/vlan id']) - fwvlans = mgr.get_firewalls() - dedicatedfws = [firewall for firewall in fwvlans - if firewall['dedicatedFirewallFlag']] + dedicated_firewalls = [firewall for firewall in fwvlans + if firewall['dedicatedFirewallFlag']] - for vlan in dedicatedfws: + for vlan in dedicated_firewalls: features = [] if vlan['highAvailabilityFirewallFlag']: features.append('HA') @@ -242,10 +243,11 @@ def execute(self, args): shared_vlan = [firewall for firewall in fwvlans if not firewall['dedicatedFirewallFlag']] for vlan in shared_vlan: - fwls = [guest for guest in vlan['firewallGuestNetworkComponents'] - if has_firewall_component(guest)] + vs_firewalls = [guest + for guest in vlan['firewallGuestNetworkComponents'] + if has_firewall_component(guest)] - for firewall in fwls: + for firewall in vs_firewalls: table.add_row([ 'cci:%s' % firewall['id'], 'CCI - standard', @@ -253,15 +255,19 @@ def execute(self, args): firewall['guestNetworkComponent']['guest']['id'] ]) - fwls = [server for server in vlan['firewallNetworkComponents'] - if has_firewall_component(server)] + server_firewalls = [server + for server in vlan['firewallNetworkComponents'] + if has_firewall_component(server)] - for fwl in fwls: + for firewall in server_firewalls: table.add_row([ - 'server:%s' % fwl['id'], + 'server:%s' % firewall['id'], 'Server - standard', '-', - fwl['networkComponent']['downlinkComponent']['hardwareId'] + lookup(firewall, + 'networkComponent', + 'downlinkComponent', + 'hardwareId') ]) return table diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index 49bc37366..a2bf90e42 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -167,20 +167,16 @@ def get_firewalls(self): :returns: A list of firewalls on the current account. """ - results = self.account.getObject( - mask={ - 'networkVlans': { - 'firewallNetworkComponents': None, - 'networkVlanFirewall': None, - 'dedicatedFirewallFlag': None, - 'firewallGuestNetworkComponents': None, - 'firewallInterfaces': {}, - 'firewallRules': None, - 'highAvailabilityFirewallFlag': None, - # 'primarySubnet': None, - } - })['networkVlans'] - return [firewall for firewall in results + mask = ('firewallNetworkComponents,' + 'networkVlanFirewall,' + 'dedicatedFirewallFlag,' + 'firewallGuestNetworkComponents,' + 'firewallInterfaces,' + 'firewallRules,' + 'highAvailabilityFirewallFlag') + + return [firewall + for firewall in self.account.getNetworkVlans(mask=mask) if has_firewall(firewall)] def get_standard_fwl_rules(self, firewall_id): diff --git a/SoftLayer/tests/fixtures/Account.py b/SoftLayer/tests/fixtures/Account.py index f6ecaf79f..1c8d8ebae 100644 --- a/SoftLayer/tests/fixtures/Account.py +++ b/SoftLayer/tests/fixtures/Account.py @@ -181,15 +181,6 @@ 'updateDate': '2014-03-07T13:52:31-06:00'}] getObject = { - 'networkVlans': [{ - 'firewallNetworkComponents': [{'id': 1234}], - 'networkVlanFirewall': [{'id': 1234}], - 'dedicatedFirewallFlag': True, - 'firewallGuestNetworkComponents': [{'id': 1234}], - 'firewallInterfaces': [{'id': 1234}], - 'firewallRules': [{'id': 1234}], - 'highAvailabilityFirewallFlag': True, - }], 'cdnAccounts': [ { "cdnAccountName": "1234a", @@ -266,8 +257,6 @@ 'subnetType': 'PRIMARY' }] -getNetworkVlans = {'id': 1234} - getSshKeys = [{'id': '100', 'label': 'Test 1'}, {'id': '101', 'label': 'Test 2', 'notes': 'Test notes', diff --git a/SoftLayer/tests/managers/firewall_tests.py b/SoftLayer/tests/managers/firewall_tests.py index 066372af3..1d079a15b 100644 --- a/SoftLayer/tests/managers/firewall_tests.py +++ b/SoftLayer/tests/managers/firewall_tests.py @@ -6,7 +6,7 @@ """ from SoftLayer import FirewallManager from SoftLayer.tests import unittest, FixtureClient -from SoftLayer.tests.fixtures import (Account, Network_Component_Firewall, +from SoftLayer.tests.fixtures import (Network_Component_Firewall, Network_Vlan_Firewall, Billing_Item) from mock import ANY @@ -23,12 +23,23 @@ def setUp(self): self.firewall = FirewallManager(self.client) def test_get_firewalls(self): - call = self.client['Account'].getObject + call = self.client['Account'].getNetworkVlans + firewall_vlan = { + 'id': 1, + 'firewallNetworkComponents': [{'id': 1234}], + 'networkVlanFirewall': {'id': 1234}, + 'dedicatedFirewallFlag': True, + 'firewallGuestNetworkComponents': [{'id': 1234}], + 'firewallInterfaces': [{'id': 1234}], + 'firewallRules': [{'id': 1234}], + 'highAvailabilityFirewallFlag': True, + } + call.return_value = [firewall_vlan] firewalls = self.firewall.get_firewalls() call.assert_called_once_with(mask=ANY) - self.assertEqual(firewalls, Account.getObject['networkVlans']) + self.assertEqual(firewalls, [firewall_vlan]) def test_get_standard_fwl_rules(self): call = self.client['Network_Component_Firewall'].getRules From d184d5df2e48ecf29af4beed982cfe4d12c51832 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 28 Apr 2014 09:00:24 -0500 Subject: [PATCH 0015/2667] Adds a few tests for vs module update_with_template_args() now takes a list_args argument to specify which arguments should be treated as a comma-separated list. Bumped required coverage percentage --- SoftLayer/CLI/modules/server.py | 11 +- SoftLayer/CLI/modules/vs.py | 11 +- SoftLayer/CLI/template.py | 44 +++--- SoftLayer/tests/CLI/helper_tests.py | 4 +- SoftLayer/tests/CLI/modules/server_tests.py | 18 --- SoftLayer/tests/CLI/modules/vs_tests.py | 132 ++++++++++++++++++ SoftLayer/tests/fixtures/Virtual_Guest.py | 8 +- .../tests/fixtures/sample_vs_template.conf | 3 +- setup.cfg | 2 +- 9 files changed, 172 insertions(+), 61 deletions(-) create mode 100644 SoftLayer/tests/CLI/modules/vs_tests.py diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index eb4e66e81..4001c1996 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -779,17 +779,8 @@ class CreateServer(CLIRunnable): '--memory', '--os'] def execute(self, args): - update_with_template_args(args) + update_with_template_args(args, list_args=['--disk', '--key']) mgr = HardwareManager(self.client) - - # Disks will be a comma-separated list. Let's make it a real list. - if isinstance(args.get('--disk'), str): - args['--disk'] = args.get('--disk').split(',') - - # Do the same thing for SSH keys - if isinstance(args.get('--key'), str): - args['--key'] = args.get('--key').split(',') - self._validate_args(args) ds_options = mgr.get_dedicated_server_create_options(args['--chassis']) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 0d6c05e5d..d1e1a356e 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -383,18 +383,9 @@ class CreateVS(CLIRunnable): required_params = ['--hostname', '--domain', '--cpu', '--memory'] def execute(self, args): - update_with_template_args(args) + update_with_template_args(args, list_args=['--disk', '--key']) vsi = VSManager(self.client) self._update_with_like_args(args) - - # Disks will be a comma-separated list. Let's make it a real list. - if isinstance(args.get('--disk'), str): - args['--disk'] = args.get('--disk').split(',') - - # SSH keys may be a comma-separated list. Let's make it a real list. - if isinstance(args.get('--key'), str): - args['--key'] = args.get('--key').split(',') - self._validate_args(args) # Do not create a virtual server with --test or --export diff --git a/SoftLayer/CLI/template.py b/SoftLayer/CLI/template.py index 657ca4b80..35a718e63 100644 --- a/SoftLayer/CLI/template.py +++ b/SoftLayer/CLI/template.py @@ -13,29 +13,35 @@ from SoftLayer.utils import configparser, StringIO -def update_with_template_args(args): +def update_with_template_args(args, list_args=None): """ Populates arguments with arguments from the template file, if provided. :param dict args: command-line arguments """ - if args.get('--template'): - template_path = args.pop('--template') - if not os.path.exists(template_path): - raise ArgumentError( - 'File does not exist [-t | --template] = %s' - % template_path) - - config = configparser.ConfigParser() - ini_str = '[settings]\n' + open( - os.path.expanduser(template_path), 'r').read() - ini_fp = StringIO(ini_str) - config.readfp(ini_fp) - - # Merge template options with the options passed in - for key, value in config.items('settings'): - option_key = '--%s' % key - if not args.get(option_key): - args[option_key] = value + if not args.get('--template'): + return + + list_args = list_args or [] + + template_path = args.pop('--template') + if not os.path.exists(template_path): + raise ArgumentError( + 'File does not exist [-t | --template] = %s' + % template_path) + + config = configparser.ConfigParser() + ini_str = '[settings]\n' + open( + os.path.expanduser(template_path), 'r').read() + ini_fp = StringIO(ini_str) + config.readfp(ini_fp) + + # Merge template options with the options passed in + for key, value in config.items('settings'): + option_key = '--%s' % key + if option_key in list_args: + value = value.split(',') + if not args.get(option_key): + args[option_key] = value def export_to_template(filename, args, exclude=None): diff --git a/SoftLayer/tests/CLI/helper_tests.py b/SoftLayer/tests/CLI/helper_tests.py index 5560d4fa6..69a9ad03e 100644 --- a/SoftLayer/tests/CLI/helper_tests.py +++ b/SoftLayer/tests/CLI/helper_tests.py @@ -377,8 +377,9 @@ def test_template_options(self): '--memory': '32', '--template': path, '--hourly': False, + '--disk': [], } - cli.helpers.update_with_template_args(args) + cli.helpers.update_with_template_args(args, list_args=['--disk']) self.assertEqual(args, { '--cpu': '4', '--datacenter': 'dal05', @@ -390,6 +391,7 @@ def test_template_options(self): '--network': '100', '--os': 'DEBIAN_7_64', 'key': 'value', + '--disk': ['50', '100'], }) diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index 90dfd91fe..df6ae28fb 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -519,15 +519,6 @@ def test_CreateServer(self): self.assertEqual(expected, format_output(output, 'python')) - # And make sure we can pass in disk and SSH keys as comma separated - # strings, which is what templates do - args['--disk'] = '1000_DRIVE,1000_DRIVE' - args['--key'] = '123,456' - - output = runnable.execute(args) - - self.assertEqual(expected, format_output(output, 'python')) - # Test explicitly setting a RAID configuration args['--controller'] = 'RAID0' @@ -691,15 +682,6 @@ def test_CreateServer_for_bmc(self, bmpi, packages): self.assertEqual(expected, format_output(output, 'python')) - # And make sure we can pass in disk and SSH keys as comma separated - # strings, which is what templates do - args['--disk'] = '1000_DRIVE,1000_DRIVE' - args['--key'] = '123,456' - - output = runnable.execute(args) - - self.assertEqual(expected, format_output(output, 'python')) - # Test explicitly setting a RAID configuration args['--controller'] = 'RAID0' diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py new file mode 100644 index 000000000..26f934654 --- /dev/null +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -0,0 +1,132 @@ +""" + SoftLayer.tests.CLI.modules.vs_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from mock import patch + +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.helpers import format_output +from SoftLayer.CLI.modules import vs + + +class DnsTests(unittest.TestCase): + def setUp(self): + self.client = FixtureClient() + + def test_list_vs(self): + command = vs.ListVSIs(client=self.client) + + output = command.execute({'--tags': 'tag'}) + self.assertEqual([{'datacenter': 'TEST00', + 'primary_ip': '172.16.240.2', + 'host': 'vs-test1.test.sftlyr.ws', + 'memory': 1024, + 'cores': 2, + 'active_transaction': None, + 'id': 100, + 'backend_ip': '10.45.19.37'}, + {'datacenter': 'TEST00', + 'primary_ip': '172.16.240.7', + 'host': 'vs-test2.test.sftlyr.ws', + 'memory': 4096, + 'cores': 4, + 'active_transaction': None, + 'id': 104, + 'backend_ip': '10.45.19.35'}], + format_output(output, 'python')) + + def test_detail_vs(self): + command = vs.VSDetails(client=self.client) + + output = command.execute({'': '100', + '--passwords': True, + '--price': True}) + + self.assertEqual({'active_transaction': None, + 'cores': 2, + 'created': '2013-08-01 15:23:45', + 'datacenter': 'TEST00', + 'hostname': 'vs-test1.test.sftlyr.ws', + 'id': 100, + 'memory': 1024, + 'modified': {}, + 'os': '12.04-64 Minimal for CCI', + 'os_version': '12.04-64 Minimal for CCI', + 'price rate': {}, + 'notes': 'notes', + 'tags': ['production'], + 'private_cpu': {}, + 'private_ip': '10.45.19.37', + 'private_only': {}, + 'ptr': 'test.softlayer.com.', + 'public_ip': '172.16.240.2', + 'state': 'RUNNING', + 'status': 'ACTIVE', + 'users': [{'password': 'pass', 'username': 'user'}], + 'vlans': [{'type': 'PUBLIC', + 'number': 23, + 'id': 1}]}, + format_output(output, 'python')) + + def test_create_options(self): + command = vs.CreateOptionsVS(client=self.client) + + output = command.execute({'--all': True, + '--cpu': True, + '--datacenter': True, + '--disk': True, + '--memory': True, + '--nic': True, + '--os': True}) + + self.assertEqual({'cpus (private)': [], + 'cpus (standard)': ['1', '2', '3', '4'], + 'datacenter': ['ams01', 'dal05'], + 'local disk(0)': ['25', '100'], + 'memory': ['1024', '2048', '3072', '4096'], + 'nic': ['10', '100', '1000'], + 'os (CENTOS)': 'CENTOS_6_64', + 'os (DEBIAN)': 'DEBIAN_7_64', + 'os (UBUNTU)': 'UBUNTU_12_64'}, + format_output(output, 'python')) + + @patch('SoftLayer.CLI.modules.vs.confirm') + def test_create(self, confirm_mock): + confirm_mock.return_value = True + command = vs.CreateVS(client=self.client) + + output = command.execute({'--cpu': '2', + '--domain': 'example.com', + '--hostname': 'host', + '--image': None, + '--os': 'UBUNTU_LATEST', + '--memory': '1024', + '--nic': '100', + '--hourly': True, + '--monthly': False, + '--like': None, + '--datacenter': None, + '--dedicated': False, + '--san': False, + '--test': False, + '--export': None, + '--userfile': None, + '--postinstall': None, + '--key': [], + '--like': [], + '--network': [], + '--disk': [], + '--private': False, + '--template': None, + '--userdata': None, + '--vlan_public': None, + '--vlan_private': None, + '--wait': None, + '--really': False}) + + self.assertEqual([{'guid': '1a2b3c-1701', + 'id': 100, + 'created': '2013-08-01 15:23:45'}], + format_output(output, 'python')) diff --git a/SoftLayer/tests/fixtures/Virtual_Guest.py b/SoftLayer/tests/fixtures/Virtual_Guest.py index f12270845..ce24df919 100644 --- a/SoftLayer/tests/fixtures/Virtual_Guest.py +++ b/SoftLayer/tests/fixtures/Virtual_Guest.py @@ -18,11 +18,17 @@ 'blockDevices': [{"device": 0, "uuid": 1}, {"device": 1}, {"device": 2, "uuid": 2}], + 'notes': 'notes', + 'networkVlans': [{'networkSpace': 'PUBLIC', + 'vlanNumber': 23, + 'id': 1}], 'operatingSystem': { + 'passwords': [{'username': 'user', 'password': 'pass'}], 'softwareLicense': { 'softwareDescription': {'version': '12.04-64 Minimal for CCI', 'name': 'Ubuntu'}} - } + }, + 'tagReferences': [{'tag': {'name': 'production'}}], } getCreateObjectOptions = { diff --git a/SoftLayer/tests/fixtures/sample_vs_template.conf b/SoftLayer/tests/fixtures/sample_vs_template.conf index dc07ba1b4..90b0edf52 100644 --- a/SoftLayer/tests/fixtures/sample_vs_template.conf +++ b/SoftLayer/tests/fixtures/sample_vs_template.conf @@ -6,4 +6,5 @@ memory = 1024 os = DEBIAN_7_64 network = 100 hourly = true -monthly= false \ No newline at end of file +monthly= false +disk=50,100 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 4df91efba..d53bef49e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ verbosity=2 detailed-errors=1 with-coverage=1 -cover-min-percentage=66 +cover-min-percentage=74 cover-erase=true cover-package=SoftLayer cover-html=1 From 7a0fb48a818eeced770515eafe7afd2cb4404c00 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 8 May 2014 10:33:06 -0500 Subject: [PATCH 0016/2667] Re-adds some fixures --- SoftLayer/tests/CLI/modules/dns_tests.py | 15 +++++++++------ SoftLayer/tests/fixtures/Dns_Domain.py | 7 ++++++- SoftLayer/tests/managers/dns_tests.py | 10 +++++----- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/SoftLayer/tests/CLI/modules/dns_tests.py b/SoftLayer/tests/CLI/modules/dns_tests.py index 231b8141a..053a357dc 100644 --- a/SoftLayer/tests/CLI/modules/dns_tests.py +++ b/SoftLayer/tests/CLI/modules/dns_tests.py @@ -7,6 +7,7 @@ from mock import patch from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests.fixtures import Dns_Domain from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.exceptions import CLIAbort from SoftLayer.CLI.modules import dns @@ -56,12 +57,12 @@ def test_list_all_zones(self): command = dns.ListZones(client=self.client) output = command.execute({'': 'example.com'}) - self.assertEqual([{'id': 1, - 'record': 'hostname', - 'ttl': 100, - 'type': 'A', - 'value': 'd'}], - format_output(output, 'python')) + self.assertEqual({'record': 'a', + 'type': 'CNAME', + 'id': 1, + 'value': 'd', + 'ttl': 100}, + format_output(output, 'python')[0]) def test_add_record(self): command = dns.AddRecord(client=self.client) @@ -95,6 +96,8 @@ def test_edit_record(self): @patch('SoftLayer.CLI.modules.dns.no_going_back') def test_delete_record(self, no_going_back_mock): no_going_back_mock.return_value = True + self.client['Dns_Domain'].getResourceRecords.return_value = [ + Dns_Domain.getResourceRecords[0]] command = dns.RecordRemove(client=self.client) output = command.execute({'': 'example.com', '': 'hostname', diff --git a/SoftLayer/tests/fixtures/Dns_Domain.py b/SoftLayer/tests/fixtures/Dns_Domain.py index f9c5106ad..998549458 100644 --- a/SoftLayer/tests/fixtures/Dns_Domain.py +++ b/SoftLayer/tests/fixtures/Dns_Domain.py @@ -3,6 +3,11 @@ editObject = True getZoneFileContents = 'lots of text' getResourceRecords = [ - {'id': 1, 'ttl': 7200, 'data': 'd', 'host': 'hostname', 'type': 'a'}, + {'id': 1, 'ttl': 7200, 'data': 'd', 'host': 'a', 'type': 'cname'}, + {'id': 2, 'ttl': 900, 'data': '1', 'host': 'b', 'type': 'a'}, + {'id': 3, 'ttl': 900, 'data': 'x', 'host': 'c', 'type': 'ptr'}, + {'id': 4, 'ttl': 86400, 'data': 'b', 'host': 'd', 'type': 'txt'}, + {'id': 5, 'ttl': 86400, 'data': 'b', 'host': 'e', 'type': 'txt'}, + {'id': 6, 'ttl': 600, 'data': 'b', 'host': 'f', 'type': 'txt'}, ] getObject = {'id': 98765, 'name': 'test-example.com'} diff --git a/SoftLayer/tests/managers/dns_tests.py b/SoftLayer/tests/managers/dns_tests.py index 708212441..3d79c766c 100644 --- a/SoftLayer/tests/managers/dns_tests.py +++ b/SoftLayer/tests/managers/dns_tests.py @@ -105,20 +105,20 @@ def test_dump_zone(self): f.assert_called_once_with(id=1) def test_get_record(self): - D = self.client['Dns_Domain'].getResourceRecords + records = self.client['Dns_Domain'].getResourceRecords # maybe valid domain, but no records matching - D.return_value = [] + records.return_value = [] self.assertEqual(self.dns_client.get_records(12345), []) - D.reset_mock() - D.return_value = [Dns_Domain.getResourceRecords[0]] + records.reset_mock() + records.return_value = [Dns_Domain.getResourceRecords[0]] self.dns_client.get_records(12345, record_type='a', host='hostname', data='a', ttl='86400') - D.assert_called_once_with( + records.assert_called_once_with( id=12345, filter={'resourceRecords': {'type': {'operation': '_= a'}, 'host': {'operation': '_= hostname'}, From f0f6657f0e74f50cec2f1117ab261ff1a3bc3c2c Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 8 May 2014 10:46:29 -0500 Subject: [PATCH 0017/2667] Fixes pylint errors --- SoftLayer/managers/vs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 9f158f311..8bcd9e184 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -567,6 +567,7 @@ def upgrade(self, instance_id, cpus=None, memory=None, if nic_speed: item_id.append({'id': self._get_item_id_for_upgrade( package_items, 'nic_speed', nic_speed)}) + order = {} order['complexType'] = \ 'SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade' From 257d62686ac0fd26ca6ee296199ac3e9f6a32ef3 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 14 May 2014 16:47:07 -0400 Subject: [PATCH 0018/2667] Sort Imports --- SoftLayer/tests/managers/queue_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/tests/managers/queue_tests.py b/SoftLayer/tests/managers/queue_tests.py index 2851c4cf6..aa75ae96a 100644 --- a/SoftLayer/tests/managers/queue_tests.py +++ b/SoftLayer/tests/managers/queue_tests.py @@ -5,12 +5,12 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import MessagingManager, Unauthenticated, SoftLayerError -import SoftLayer.managers.messaging from SoftLayer.consts import USER_AGENT from SoftLayer.tests import unittest +import SoftLayer.managers.messaging -import json from mock import MagicMock, patch, ANY +import json QUEUE_1 = { 'expiration': 40000, From 329c5e42d9e142f56a6a6a9491da68e9238920e0 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 14 May 2014 16:54:26 -0400 Subject: [PATCH 0019/2667] Remove Unused json import (cause merging and stuff) --- SoftLayer/tests/managers/queue_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/tests/managers/queue_tests.py b/SoftLayer/tests/managers/queue_tests.py index aa75ae96a..5ed2db0ab 100644 --- a/SoftLayer/tests/managers/queue_tests.py +++ b/SoftLayer/tests/managers/queue_tests.py @@ -10,7 +10,6 @@ import SoftLayer.managers.messaging from mock import MagicMock, patch, ANY -import json QUEUE_1 = { 'expiration': 40000, From 9fca20965bd57cfe7ed80d7ffb0387fd12f3fcb0 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 15 May 2014 20:30:21 -0400 Subject: [PATCH 0020/2667] Adds Python 3.4 testing with travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index aabd7e374..b9d9610f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ env: - TOX_ENV=py26 - TOX_ENV=py27 - TOX_ENV=py33 + - TOX_ENV=py34 - TOX_ENV=pypy - TOX_ENV=pep8 - TOX_ENV=pylint From 48c0274e72f0fdb908ed1cb90c8d0c0a0fa0e116 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 21 May 2014 10:51:26 -0500 Subject: [PATCH 0021/2667] Fixes #336 --- SoftLayer/CLI/modules/nas.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/modules/nas.py b/SoftLayer/CLI/modules/nas.py index d252b61b6..cfc59804e 100644 --- a/SoftLayer/CLI/modules/nas.py +++ b/SoftLayer/CLI/modules/nas.py @@ -9,7 +9,8 @@ # :license: MIT, see LICENSE for more details. from SoftLayer.CLI import CLIRunnable, Table, FormattedItem -from SoftLayer.CLI.helpers import NestedDict, blank +from SoftLayer.CLI.helpers import blank +from SoftLayer.utils import lookup class ListNAS(CLIRunnable): @@ -27,7 +28,6 @@ def execute(self, args): nas_accounts = account.getNasNetworkStorage( mask='eventCount,serviceResource[datacenter.name]') - nas_accounts = [NestedDict(n) for n in nas_accounts] table = Table(['id', 'datacenter', 'size', 'username', 'password', 'server']) @@ -35,8 +35,10 @@ def execute(self, args): for nas_account in nas_accounts: table.add_row([ nas_account['id'], - nas_account['serviceResource']['datacenter'].get('name', - blank()), + lookup(nas_account, + 'serviceResource', + 'datacenter', + 'name') or blank(), FormattedItem( nas_account.get('capacityGb', blank()), "%dGB" % nas_account.get('capacityGb', 0)), From cfb35d00bbd66779f616aa3c3e310a2c42a1ba37 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 21 May 2014 11:25:28 -0500 Subject: [PATCH 0022/2667] Adds some nice text around unexpected exceptions --- SoftLayer/CLI/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 67bcb865b..89b457372 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -244,7 +244,9 @@ def main(args=sys.argv[1:], env=Environment()): exit_status = 1 except Exception: import traceback + env.err("An unexpected error has occured:") env.err(traceback.format_exc()) + env.err("Feel free to report this error as it is likely a bug.") exit_status = 1 sys.exit(exit_status) From d87806f3bbb1b677dfd85b2158a8ce5a82e2ec83 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 21 May 2014 13:27:45 -0500 Subject: [PATCH 0023/2667] Skip OS descriptions that cannot be parsed --- SoftLayer/CLI/modules/server.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 4001c1996..59108e98b 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -608,6 +608,11 @@ def get_create_options(self, ds_options, section, pretty=True): os_code = self._generate_windows_code(opsys['description']) else: os_results = os_regex.search(opsys['description']) + + # Skip this operating system if it's not parsable + if os_results is None: + continue + name = os_results.group(1) version = os_results.group(2) bits = bit_regex.search(opsys['description']) From d51e9f27aab108dcf998dd8e4bde16a9b70e73f4 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 21 May 2014 13:32:31 -0500 Subject: [PATCH 0024/2667] Increase max statements allowed by pylint :( --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1ac9f3154..76bc4f943 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,7 @@ commands = pylint SoftLayer \ -d W0142 \ # Used * or ** magic --max-args=20 \ --max-branches=40 \ - --max-statements=85 \ + --max-statements=86 \ --max-module-lines=1200 \ --max-returns=8 \ --min-similarity-lines=50 # TODO: Remove \ No newline at end of file From 17bfff4bf63f12a0cec50f1f3cc38326cdfbb8fd Mon Sep 17 00:00:00 2001 From: njain Date: Wed, 21 May 2014 13:39:14 -0500 Subject: [PATCH 0025/2667] Adding list_ecsci function to iscsi manager --- SoftLayer/CLI/modules/iscsi.py | 9 ++------- SoftLayer/managers/iscsi.py | 8 ++++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/modules/iscsi.py b/SoftLayer/CLI/modules/iscsi.py index fe83b4d1f..cc916f98d 100644 --- a/SoftLayer/CLI/modules/iscsi.py +++ b/SoftLayer/CLI/modules/iscsi.py @@ -28,12 +28,9 @@ class ListISCSIs(CLIRunnable): action = 'list' def execute(self, args): - account = self.client['Account'] - - iscsi_list = account.getIscsiNetworkStorage( - mask='eventCount,serviceResource[datacenter.name]') + iscsi_mgr = ISCSIManager(self.client) + iscsi_list = iscsi_mgr.list_iscsi() iscsi_list = [NestedDict(n) for n in iscsi_list] - table = Table([ 'id', 'datacenter', @@ -42,7 +39,6 @@ def execute(self, args): 'password', 'server' ]) - for iscsi in iscsi_list: table.add_row([ iscsi['id'], @@ -53,7 +49,6 @@ def execute(self, args): iscsi.get('username', blank()), iscsi.get('password', blank()), iscsi.get('serviceResourceBackendIpAddress', blank())]) - return table diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index bd640ab41..4ef19ca17 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -69,6 +69,14 @@ def create_iscsi(self, size=None, location=None): self.product_order.verifyOrder(iscsi_order) self.product_order.placeOrder(iscsi_order) + def list_iscsi(self): + """List iSCSI volume + """ + account = self.client['Account'] + iscsi_list = account.getIscsiNetworkStorage( + mask='eventCount,serviceResource[datacenter.name]') + return iscsi_list + def get_iscsi(self, volume_id, **kwargs): """ Get details about a iSCSI storage From 5488bc0917f0d79212f1ca232bf1f922d4ddac04 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 22 May 2014 16:07:19 -0500 Subject: [PATCH 0026/2667] Adds link to gh issues on unexpected error --- SoftLayer/CLI/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 89b457372..c2e5f7fc4 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -246,7 +246,8 @@ def main(args=sys.argv[1:], env=Environment()): import traceback env.err("An unexpected error has occured:") env.err(traceback.format_exc()) - env.err("Feel free to report this error as it is likely a bug.") + env.err("Feel free to report this error as it is likely a bug:") + env.err(" https://github.com/softlayer/softlayer-python/issues") exit_status = 1 sys.exit(exit_status) From b69e92f314647014c76226781104fd5556518bf5 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 16 May 2014 12:02:54 -0400 Subject: [PATCH 0027/2667] Adds pep8-naming to pep8 tests. Adds py3 and py2 pep8 tests --- .travis.yml | 2 +- SoftLayer/exceptions.py | 9 ++-- SoftLayer/tests/CLI/core_tests.py | 20 ++++----- SoftLayer/tests/CLI/environment_tests.py | 6 +-- SoftLayer/tests/CLI/helper_tests.py | 22 +++++----- SoftLayer/tests/CLI/modules/cdn_tests.py | 6 +-- SoftLayer/tests/CLI/modules/config_tests.py | 10 ++--- SoftLayer/tests/CLI/modules/dns_tests.py | 6 +-- SoftLayer/tests/CLI/modules/firewall_tests.py | 6 +-- SoftLayer/tests/CLI/modules/globalip_tests.py | 6 +-- SoftLayer/tests/CLI/modules/help_tests.py | 4 +- SoftLayer/tests/CLI/modules/import_tests.py | 4 +- SoftLayer/tests/CLI/modules/nas_tests.py | 6 +-- SoftLayer/tests/CLI/modules/rwhois_tests.py | 6 +-- SoftLayer/tests/CLI/modules/server_tests.py | 44 +++++++++---------- SoftLayer/tests/CLI/modules/sshkey_tests.py | 6 +-- SoftLayer/tests/CLI/modules/summary_tests.py | 6 +-- SoftLayer/tests/CLI/modules/vs_tests.py | 6 +-- SoftLayer/tests/__init__.py | 14 ++++++ SoftLayer/tests/api_tests.py | 18 ++++---- SoftLayer/tests/auth_tests.py | 12 ++--- SoftLayer/tests/basic_tests.py | 14 +++--- SoftLayer/tests/config_tests.py | 10 ++--- SoftLayer/tests/functional_tests.py | 6 +-- SoftLayer/tests/managers/cci_tests.py | 6 +-- SoftLayer/tests/managers/cdn_tests.py | 6 +-- SoftLayer/tests/managers/dns_tests.py | 6 +-- SoftLayer/tests/managers/firewall_tests.py | 18 ++++---- SoftLayer/tests/managers/hardware_tests.py | 6 +-- SoftLayer/tests/managers/image_tests.py | 6 +-- SoftLayer/tests/managers/iscsi_tests.py | 14 +++--- SoftLayer/tests/managers/loadbal_tests.py | 6 +-- SoftLayer/tests/managers/metadata_tests.py | 10 ++--- SoftLayer/tests/managers/network_tests.py | 6 +-- SoftLayer/tests/managers/queue_tests.py | 14 +++--- SoftLayer/tests/managers/sshkey_tests.py | 6 +-- SoftLayer/tests/managers/ssl_tests.py | 6 +-- SoftLayer/tests/managers/ticket_tests.py | 6 +-- SoftLayer/tests/managers/vs_tests.py | 16 +++---- SoftLayer/tests/transport_tests.py | 8 ++-- tox.ini | 42 +++++++++--------- 41 files changed, 222 insertions(+), 209 deletions(-) diff --git a/.travis.yml b/.travis.yml index b9d9610f1..a736003a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,6 @@ env: - TOX_ENV=py34 - TOX_ENV=pypy - TOX_ENV=pep8 - - TOX_ENV=pylint + - TOX_ENV=py2pep8 install: pip install tox --use-mirrors script: tox -e $TOX_ENV diff --git a/SoftLayer/exceptions.py b/SoftLayer/exceptions.py index 5d86a9b35..7227168af 100644 --- a/SoftLayer/exceptions.py +++ b/SoftLayer/exceptions.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +# pylint: disable=C0103 class SoftLayerError(Exception): @@ -21,10 +22,10 @@ class SoftLayerAPIError(SoftLayerError): Provides faultCode and faultString properties. """ - def __init__(self, faultCode, faultString, *args): - SoftLayerError.__init__(self, faultString, *args) - self.faultCode = faultCode # pylint: disable=C0103 - self.reason = self.faultString = faultString # pylint: disable=C0103 + def __init__(self, fault_code, fault_string, *args): + SoftLayerError.__init__(self, fault_string, *args) + self.faultCode = fault_code + self.reason = self.faultString = fault_string def __repr__(self): return '<%s(%s): %s>' % \ diff --git a/SoftLayer/tests/CLI/core_tests.py b/SoftLayer/tests/CLI/core_tests.py index 0e8a3bc7d..b700568c9 100644 --- a/SoftLayer/tests/CLI/core_tests.py +++ b/SoftLayer/tests/CLI/core_tests.py @@ -8,7 +8,7 @@ import SoftLayer -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from SoftLayer.CLI import core from SoftLayer.CLI.helpers import CLIAbort from SoftLayer.CLI.environment import Environment, InvalidModule, CLIRunnable @@ -28,7 +28,7 @@ def module_no_command_fixture(): """ -class submodule_fixture(CLIRunnable): +class SubmoduleFixture(CLIRunnable): """ usage: sl vs list [options] @@ -44,7 +44,7 @@ def execute(self, args): class EnvironmentFixture(Environment): def __init__(self): super(EnvironmentFixture, self).__init__() - self.plugins = {'vs': {'list': submodule_fixture}} + self.plugins = {'vs': {'list': SubmoduleFixture}} self.aliases = { 'meta': 'metadata', 'my': 'metadata', @@ -58,8 +58,8 @@ def plugin_list(self, *args, **kwargs): return self.plugins.keys() -class CommandLineTests(unittest.TestCase): - def setUp(self): +class CommandLineTests(TestCase): + def set_up(self): self.env = EnvironmentFixture() self.env.get_module_name = MagicMock() @@ -105,19 +105,19 @@ def test_invalid_module(self): def test_module_with_no_command(self): self.env.plugins = { - 'vs': {'list': submodule_fixture, None: submodule_fixture} + 'vs': {'list': SubmoduleFixture, None: SubmoduleFixture} } self.env.get_module_name.return_value = 'vs' self.env.load_module = MagicMock() self.env.load_module.return_value = module_no_command_fixture resolver = core.CommandParser(self.env) command, command_args = resolver.parse(['vs', 'list']) - self.assertEqual(submodule_fixture, command) + self.assertEqual(SubmoduleFixture, command) def test_main(self): self.env.get_module_name.return_value = 'vs' self.env.plugins = { - 'vs': {'list': submodule_fixture} + 'vs': {'list': SubmoduleFixture} } self.assertRaises( SystemExit, core.main, @@ -181,8 +181,8 @@ def test_uncaught_error(self, m): m.assert_called_once_with() -class TestCommandParser(unittest.TestCase): - def setUp(self): +class TestCommandParser(TestCase): + def set_up(self): self.env = EnvironmentFixture() self.parser = core.CommandParser(self.env) diff --git a/SoftLayer/tests/CLI/environment_tests.py b/SoftLayer/tests/CLI/environment_tests.py index d7b0e3471..bdc19e0bf 100644 --- a/SoftLayer/tests/CLI/environment_tests.py +++ b/SoftLayer/tests/CLI/environment_tests.py @@ -7,13 +7,13 @@ import os from mock import patch, MagicMock -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from SoftLayer.CLI.environment import Environment, InvalidCommand -class EnvironmentTests(unittest.TestCase): +class EnvironmentTests(TestCase): - def setUp(self): + def set_up(self): self.env = Environment() def test_plugin_list(self): diff --git a/SoftLayer/tests/CLI/helper_tests.py b/SoftLayer/tests/CLI/helper_tests.py index 69a9ad03e..f24291753 100644 --- a/SoftLayer/tests/CLI/helper_tests.py +++ b/SoftLayer/tests/CLI/helper_tests.py @@ -9,7 +9,7 @@ import json import SoftLayer.CLI as cli -from SoftLayer.tests import FIXTURE_PATH, unittest +from SoftLayer.tests import FIXTURE_PATH, TestCase from mock import patch, mock_open, call if sys.version_info >= (3,): @@ -18,7 +18,7 @@ open_path = '__builtin__.open' -class CLIJSONEncoderTest(unittest.TestCase): +class CLIJSONEncoderTest(TestCase): def test_default(self): out = json.dumps({ 'formattedItem': cli.helpers.FormattedItem('normal', 'formatted') @@ -35,7 +35,7 @@ def test_fail(self): json.dumps, {'test': object()}, cls=cli.formatting.CLIJSONEncoder) -class PromptTests(unittest.TestCase): +class PromptTests(TestCase): @patch('SoftLayer.CLI.formatting.console_input') def test_invalid_response(self, raw_input_mock): @@ -104,7 +104,7 @@ def test_confirmation(self, raw_input_mock): self.assertTrue(res) -class FormattedItemTests(unittest.TestCase): +class FormattedItemTests(TestCase): def test_init(self): item = cli.FormattedItem('test', 'test_formatted') @@ -152,7 +152,7 @@ def test_blank(self): self.assertEqual('NULL', str(item)) -class FormattedListTests(unittest.TestCase): +class FormattedListTests(TestCase): def test_init(self): l = cli.listing([1, 'two'], separator=':') self.assertEqual([1, 'two'], list(l)) @@ -180,7 +180,7 @@ def test_str(self): self.assertEqual('1:two', result) -class FormattedTxnTests(unittest.TestCase): +class FormattedTxnTests(TestCase): def test_active_txn_empty(self): self.assertRaises(KeyError, cli.active_txn, {}) @@ -229,7 +229,7 @@ def test_transaction_status_missing(self): self.assertEqual(result.original, b.original) -class CLIAbortTests(unittest.TestCase): +class CLIAbortTests(TestCase): def test_init(self): e = cli.helpers.CLIAbort("something") @@ -238,7 +238,7 @@ def test_init(self): self.assertIsInstance(e, cli.helpers.CLIHalt) -class ResolveIdTests(unittest.TestCase): +class ResolveIdTests(TestCase): def test_resolve_id_one(self): resolver = lambda r: [12345] @@ -257,7 +257,7 @@ def test_resolve_id_multiple(self): cli.helpers.CLIAbort, cli.helpers.resolve_id, resolver, 'test') -class TestFormatOutput(unittest.TestCase): +class TestFormatOutput(TestCase): def test_format_output_string(self): t = cli.helpers.format_output('just a string', 'raw') @@ -356,7 +356,7 @@ def test_format_output_python_keyvaluetable(self): self.assertEqual({'nothing': None}, ret) -class TestTemplateArgs(unittest.TestCase): +class TestTemplateArgs(TestCase): def test_no_template_option(self): args = {'key': 'value'} @@ -395,7 +395,7 @@ def test_template_options(self): }) -class TestExportToTemplate(unittest.TestCase): +class TestExportToTemplate(TestCase): def test_export_to_template(self): with patch(open_path, mock_open(), create=True) as open_: cli.helpers.export_to_template('filename', { diff --git a/SoftLayer/tests/CLI/modules/cdn_tests.py b/SoftLayer/tests/CLI/modules/cdn_tests.py index 31e66aef6..d26ae1854 100644 --- a/SoftLayer/tests/CLI/modules/cdn_tests.py +++ b/SoftLayer/tests/CLI/modules/cdn_tests.py @@ -4,13 +4,13 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.modules import cdn -class CdnTests(unittest.TestCase): - def setUp(self): +class CdnTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_list_accounts(self): diff --git a/SoftLayer/tests/CLI/modules/config_tests.py b/SoftLayer/tests/CLI/modules/config_tests.py index 2bfa86667..9f23ec48b 100644 --- a/SoftLayer/tests/CLI/modules/config_tests.py +++ b/SoftLayer/tests/CLI/modules/config_tests.py @@ -9,14 +9,14 @@ from SoftLayer import API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT from SoftLayer.auth import BasicAuthentication -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.modules import config from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.exceptions import CLIAbort -class TestHelpShow(unittest.TestCase): - def setUp(self): +class TestHelpShow(TestCase): + def set_up(self): client = MagicMock() client.auth.username = 'user' client.auth.api_key = '12345' @@ -36,8 +36,8 @@ def test_show(self): self.assertEqual(expected, format_output(output, 'python')) -class TestHelpSetup(unittest.TestCase): - def setUp(self): +class TestHelpSetup(TestCase): + def set_up(self): client = FixtureClient() client.auth = BasicAuthentication('default-user', 'default-key') client.endpoint_url = 'default-endpoint-url' diff --git a/SoftLayer/tests/CLI/modules/dns_tests.py b/SoftLayer/tests/CLI/modules/dns_tests.py index 053a357dc..fa8705909 100644 --- a/SoftLayer/tests/CLI/modules/dns_tests.py +++ b/SoftLayer/tests/CLI/modules/dns_tests.py @@ -6,15 +6,15 @@ """ from mock import patch -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Dns_Domain from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.exceptions import CLIAbort from SoftLayer.CLI.modules import dns -class DnsTests(unittest.TestCase): - def setUp(self): +class DnsTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_dump_zone(self): diff --git a/SoftLayer/tests/CLI/modules/firewall_tests.py b/SoftLayer/tests/CLI/modules/firewall_tests.py index 1969bdeb5..ef7b8b041 100644 --- a/SoftLayer/tests/CLI/modules/firewall_tests.py +++ b/SoftLayer/tests/CLI/modules/firewall_tests.py @@ -6,14 +6,14 @@ """ # from mock import patch -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.helpers import format_output # from SoftLayer.CLI.exceptions import CLIAbort from SoftLayer.CLI.modules import firewall -class FirewallTests(unittest.TestCase): - def setUp(self): +class FirewallTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_list_firewalls(self): diff --git a/SoftLayer/tests/CLI/modules/globalip_tests.py b/SoftLayer/tests/CLI/modules/globalip_tests.py index 8384b6280..0a6eb5cdc 100644 --- a/SoftLayer/tests/CLI/modules/globalip_tests.py +++ b/SoftLayer/tests/CLI/modules/globalip_tests.py @@ -6,14 +6,14 @@ """ from mock import patch -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.exceptions import CLIAbort from SoftLayer.CLI.modules import globalip -class DnsTests(unittest.TestCase): - def setUp(self): +class DnsTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_ip_assign(self): diff --git a/SoftLayer/tests/CLI/modules/help_tests.py b/SoftLayer/tests/CLI/modules/help_tests.py index 5403d5bbf..8431dc657 100644 --- a/SoftLayer/tests/CLI/modules/help_tests.py +++ b/SoftLayer/tests/CLI/modules/help_tests.py @@ -4,12 +4,12 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from SoftLayer.CLI.modules import help from SoftLayer.CLI.environment import Environment -class HelpTests(unittest.TestCase): +class HelpTests(TestCase): def test_help(self): command = help.Show(env=Environment()) diff --git a/SoftLayer/tests/CLI/modules/import_tests.py b/SoftLayer/tests/CLI/modules/import_tests.py index 60db8b4a4..a23f9f0d2 100644 --- a/SoftLayer/tests/CLI/modules/import_tests.py +++ b/SoftLayer/tests/CLI/modules/import_tests.py @@ -4,13 +4,13 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from SoftLayer.CLI.modules import get_module_list from importlib import import_module -class TestImportCLIModules(unittest.TestCase): +class TestImportCLIModules(TestCase): def test_import_all(self): modules = get_module_list() diff --git a/SoftLayer/tests/CLI/modules/nas_tests.py b/SoftLayer/tests/CLI/modules/nas_tests.py index 053c10331..5fd8e013e 100644 --- a/SoftLayer/tests/CLI/modules/nas_tests.py +++ b/SoftLayer/tests/CLI/modules/nas_tests.py @@ -4,13 +4,13 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.modules import nas from SoftLayer.CLI.helpers import format_output -class RWhoisTests(unittest.TestCase): - def setUp(self): +class RWhoisTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_list_nas(self): diff --git a/SoftLayer/tests/CLI/modules/rwhois_tests.py b/SoftLayer/tests/CLI/modules/rwhois_tests.py index 8921bbcbd..cbb39d81a 100644 --- a/SoftLayer/tests/CLI/modules/rwhois_tests.py +++ b/SoftLayer/tests/CLI/modules/rwhois_tests.py @@ -4,14 +4,14 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.modules import rwhois from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.exceptions import CLIAbort -class RWhoisTests(unittest.TestCase): - def setUp(self): +class RWhoisTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_edit_nothing(self): diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index df6ae28fb..7186914d6 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -7,7 +7,7 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from mock import Mock, patch try: # Python 3.x compatibility @@ -21,11 +21,11 @@ from SoftLayer.CLI.modules import server -class ServerCLITests(unittest.TestCase): - def setUp(self): +class ServerCLITests(TestCase): + def set_up(self): self.client = FixtureClient() - def test_ServerCancelReasons(self): + def test_server_cancel_reasons(self): runnable = server.ServerCancelReasons(client=self.client) output = runnable.execute({}) @@ -54,7 +54,7 @@ def test_ServerCancelReasons(self): f(expected, format_output(output, 'python')) @patch('SoftLayer.HardwareManager.get_available_dedicated_server_packages') - def test_ServerCreateOptions(self, packages): + def test_server_create_options(self, packages): args = { '': '999', '--all': True, @@ -101,7 +101,7 @@ def test_ServerCreateOptions(self, packages): self.assertEqual(expected, format_output(output, 'python')) @patch('SoftLayer.HardwareManager.get_available_dedicated_server_packages') - def test_ServerCreateOptions_with_cpu_only(self, packages): + def test_server_create_options_with_cpu_only(self, packages): args = { '': '999', '--all': False, @@ -135,7 +135,7 @@ def test_ServerCreateOptions_with_cpu_only(self, packages): self.assertEqual(expected, format_output(output, 'python')) @patch('SoftLayer.HardwareManager.get_available_dedicated_server_packages') - def test_ServerCreateOptions_with_invalid_chassis(self, packages): + def test_server_create_options_with_invalid_chassis(self, packages): args = { '': '999', '--all': True, @@ -159,7 +159,7 @@ def test_ServerCreateOptions_with_invalid_chassis(self, packages): @patch('SoftLayer.HardwareManager.get_available_dedicated_server_packages') @patch('SoftLayer.HardwareManager.get_bare_metal_package_id') - def test_ServerCreateOptions_for_bmc(self, bmpi, packages): + def test_server_create_options_for_bmc(self, bmpi, packages): args = { '': '1099', '--all': True, @@ -246,7 +246,7 @@ def test_server_details_issue_332(self): self.assertFalse(self.client['Hardware_Server'] .getReverseDomainRecords.called) - def test_ListServers(self): + def test_list_servers(self): runnable = server.ListServers(client=self.client) output = runnable.execute({'--tags': 'openstack'}) @@ -290,7 +290,7 @@ def test_ListServers(self): @patch('SoftLayer.CLI.modules.server.no_going_back') @patch('SoftLayer.HardwareManager.reload') @patch('SoftLayer.CLI.modules.server.resolve_id') - def test_ServerReload( + def test_server_reload( self, resolve_mock, reload_mock, ngb_mock, abort_mock): hw_id = 12345 resolve_mock.return_value = hw_id @@ -313,7 +313,7 @@ def test_ServerReload( @patch('SoftLayer.CLI.modules.server.no_going_back') @patch('SoftLayer.HardwareManager.cancel_hardware') @patch('SoftLayer.CLI.modules.server.resolve_id') - def test_CancelServer( + def test_cancel_server( self, resolve_mock, cancel_mock, ngb_mock, abort_mock): hw_id = 12345 resolve_mock.return_value = hw_id @@ -339,7 +339,7 @@ def test_CancelServer( env_mock.assert_called() @patch('SoftLayer.CLI.modules.server.confirm') - def test_ServerPowerOff(self, confirm_mock): + def test_server_power_off(self, confirm_mock): hw_id = 12345 runnable = server.ServerPowerOff(client=self.client) @@ -356,7 +356,7 @@ def test_ServerPowerOff(self, confirm_mock): self.assertRaises(CLIAbort, runnable.execute, args) @patch('SoftLayer.CLI.modules.server.confirm') - def test_ServerReboot(self, confirm_mock): + def test_server_reboot(self, confirm_mock): hw_id = 12345 runnable = server.ServerReboot(client=self.client) @@ -387,7 +387,7 @@ def test_ServerReboot(self, confirm_mock): args['--really'] = False self.assertRaises(CLIAbort, runnable.execute, args) - def test_ServerPowerOn(self): + def test_server_power_on(self): hw_id = 12345 runnable = server.ServerPowerOn(client=self.client) @@ -400,7 +400,7 @@ def test_ServerPowerOn(self): self.client['Hardware_Server'].powerOn.assert_called_with(id=hw_id) @patch('SoftLayer.CLI.modules.server.confirm') - def test_ServerPowerCycle(self, confirm_mock): + def test_server_power_cycle(self, confirm_mock): hw_id = 12345 runnable = server.ServerPowerCycle(client=self.client) @@ -420,7 +420,7 @@ def test_ServerPowerCycle(self, confirm_mock): @patch('SoftLayer.HardwareManager.change_port_speed') @patch('SoftLayer.CLI.modules.server.resolve_id') - def test_NicEditServer(self, resolve_mock, port_mock): + def test_cic_edit_server(self, resolve_mock, port_mock): hw_id = 12345 resolve_mock.return_value = hw_id @@ -443,7 +443,7 @@ def test_NicEditServer(self, resolve_mock, port_mock): runnable.execute(args) @patch('SoftLayer.HardwareManager.get_available_dedicated_server_packages') - def test_ListChassisServer(self, packages): + def test_list_chassis_server(self, packages): test_data = [ (1, 'Chassis 1'), (2, 'Chassis 2') @@ -460,7 +460,7 @@ def test_ListChassisServer(self, packages): self.assertEqual(expected, format_output(output, 'python')) - def test_CreateServer(self): + def test_create_server(self): args = { '--chassis': 999, '--hostname': 'test', @@ -549,7 +549,7 @@ def test_CreateServer(self): self.assertRaises(CLIAbort, runnable.execute, args) - def test_CreateServer_failures(self): + def test_create_server_failures(self): # This is missing a required argument args = { @@ -586,7 +586,7 @@ def test_CreateServer_failures(self): self.assertRaises(CLIAbort, runnable.execute, args) @patch('SoftLayer.CLI.modules.server.export_to_template') - def test_CreateServer_with_export(self, export_to_template): + def test_create_server_with_export(self, export_to_template): args = { '--chassis': 999, '--hostname': 'test', @@ -616,7 +616,7 @@ def test_CreateServer_with_export(self, export_to_template): @patch('SoftLayer.HardwareManager.get_available_dedicated_server_packages') @patch('SoftLayer.HardwareManager.get_bare_metal_package_id') - def test_CreateServer_for_bmc(self, bmpi, packages): + def test_create_server_for_bmc(self, bmpi, packages): args = { '--chassis': '1099', '--hostname': 'test', @@ -712,7 +712,7 @@ def test_CreateServer_for_bmc(self, bmpi, packages): self.assertRaises(CLIAbort, runnable.execute, args) - def test_EditServer(self): + def test_edit_server(self): # Test both userdata and userfile at once args = { '': 1000, diff --git a/SoftLayer/tests/CLI/modules/sshkey_tests.py b/SoftLayer/tests/CLI/modules/sshkey_tests.py index 5ec272e0c..a98ba0be4 100644 --- a/SoftLayer/tests/CLI/modules/sshkey_tests.py +++ b/SoftLayer/tests/CLI/modules/sshkey_tests.py @@ -8,14 +8,14 @@ from mock import patch import tempfile -from SoftLayer.tests import unittest, FixtureClient, FIXTURE_PATH +from SoftLayer.tests import TestCase, FixtureClient, FIXTURE_PATH from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.modules import sshkey from SoftLayer.CLI.exceptions import CLIAbort -class SshKeyTests(unittest.TestCase): - def setUp(self): +class SshKeyTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_add_by_option(self): diff --git a/SoftLayer/tests/CLI/modules/summary_tests.py b/SoftLayer/tests/CLI/modules/summary_tests.py index c77be8bb8..4680f4d24 100644 --- a/SoftLayer/tests/CLI/modules/summary_tests.py +++ b/SoftLayer/tests/CLI/modules/summary_tests.py @@ -4,14 +4,14 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.modules import summary from SoftLayer.CLI.environment import Environment from SoftLayer.CLI.helpers import format_output -class SummaryTests(unittest.TestCase): - def setUp(self): +class SummaryTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_summary(self): diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index 26f934654..96bd59cf6 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -6,13 +6,13 @@ """ from mock import patch -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.modules import vs -class DnsTests(unittest.TestCase): - def setUp(self): +class DnsTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_list_vs(self): diff --git a/SoftLayer/tests/__init__.py b/SoftLayer/tests/__init__.py index f1047d958..13be79bdc 100644 --- a/SoftLayer/tests/__init__.py +++ b/SoftLayer/tests/__init__.py @@ -14,4 +14,18 @@ FIXTURE_PATH = os.path.abspath(os.path.join(__file__, '..', 'fixtures')) + +class TestCase(unittest.TestCase): + def set_up(self): + pass + + def tear_down(self): + pass + + def setUp(self): # NOQA + return self.set_up() + + def tearDown(self): # NOQA + return self.tear_down() + __all__ = ['unittest', 'FixtureClient'] diff --git a/SoftLayer/tests/api_tests.py b/SoftLayer/tests/api_tests.py index 630e3a77a..2c7646c95 100644 --- a/SoftLayer/tests/api_tests.py +++ b/SoftLayer/tests/api_tests.py @@ -8,11 +8,11 @@ import SoftLayer import SoftLayer.API -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from SoftLayer.consts import USER_AGENT -class Inititialization(unittest.TestCase): +class Inititialization(TestCase): def test_init(self): client = SoftLayer.Client(username='doesnotexist', api_key='issurelywrong', timeout=10) @@ -38,7 +38,7 @@ def test_env(self, get_client_settings): self.assertEquals(client.endpoint_url, 'http://endpoint_url') -class ClientMethods(unittest.TestCase): +class ClientMethods(TestCase): def test_help(self): help(SoftLayer) help(SoftLayer.Client) @@ -71,8 +71,8 @@ def test_len(self): self.assertEqual(len(client), 0) -class APIClient(unittest.TestCase): - def setUp(self): +class APIClient(TestCase): + def set_up(self): self.client = SoftLayer.Client( username='doesnotexist', api_key='issurelywrong', endpoint_url="ENDPOINT") @@ -304,8 +304,8 @@ def test_call_compression_override(self, make_xml_rpc_api_call): }) -class APITimedClient(unittest.TestCase): - def setUp(self): +class APITimedClient(TestCase): + def set_up(self): self.client = SoftLayer.TimedClient( username='doesnotexist', api_key='issurelywrong', endpoint_url="ENDPOINT") @@ -324,8 +324,8 @@ def test_overriden_call_times_methods(self, _call, _time): self.assertEqual(expected_calls, self.client.get_last_calls()) -class UnauthenticatedAPIClient(unittest.TestCase): - def setUp(self): +class UnauthenticatedAPIClient(TestCase): + def set_up(self): self.client = SoftLayer.Client(endpoint_url="ENDPOINT") @patch('SoftLayer.API.get_client_settings') diff --git a/SoftLayer/tests/auth_tests.py b/SoftLayer/tests/auth_tests.py index 7da24490a..abb793511 100644 --- a/SoftLayer/tests/auth_tests.py +++ b/SoftLayer/tests/auth_tests.py @@ -6,17 +6,17 @@ """ from SoftLayer.auth import ( AuthenticationBase, BasicAuthentication, TokenAuthentication) -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase -class TestAuthenticationBase(unittest.TestCase): +class TestAuthenticationBase(TestCase): def test_get_headers(self): auth = AuthenticationBase() self.assertRaises(NotImplementedError, auth.get_headers) -class TestBasicAuthentication(unittest.TestCase): - def setUp(self): +class TestBasicAuthentication(TestCase): + def set_up(self): self.auth = BasicAuthentication('USERNAME', 'APIKEY') def test_attribs(self): @@ -37,8 +37,8 @@ def test_repr(self): self.assertIn('USERNAME', s) -class TestTokenAuthentication(unittest.TestCase): - def setUp(self): +class TestTokenAuthentication(TestCase): + def set_up(self): self.auth = TokenAuthentication(12345, 'TOKEN') def test_attribs(self): diff --git a/SoftLayer/tests/basic_tests.py b/SoftLayer/tests/basic_tests.py index f8d881bc3..5867fc891 100644 --- a/SoftLayer/tests/basic_tests.py +++ b/SoftLayer/tests/basic_tests.py @@ -6,10 +6,10 @@ :license: MIT, see LICENSE for more details. """ import SoftLayer -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase -class TestExceptions(unittest.TestCase): +class TestExceptions(TestCase): def test_softlayer_api_error(self): e = SoftLayer.SoftLayerAPIError('fault code', 'fault string') @@ -32,7 +32,7 @@ def test_parse_error(self): str(e), "ParseError(fault code): fault string") -class TestUtils(unittest.TestCase): +class TestUtils(TestCase): def test_query_filter(self): result = SoftLayer.utils.query_filter('test') @@ -60,7 +60,7 @@ def test_query_filter(self): self.assertEqual({'operation': 10}, result) -class TestNestedDict(unittest.TestCase): +class TestNestedDict(TestCase): def test_basic(self): n = SoftLayer.utils.NestedDict() @@ -100,7 +100,7 @@ def test_to_dict(self): self.assertEqual(dict, type(d['test']['test1']['test2']['test3'])) -class TestLookup(unittest.TestCase): +class TestLookup(TestCase): def test_lookup(self): d = {'test': {'nested': 1}} @@ -131,9 +131,9 @@ class IdentifierFixture(SoftLayer.utils.IdentifierMixin): resolvers = [is_a, is_b] -class TestIdentifierMixin(unittest.TestCase): +class TestIdentifierMixin(TestCase): - def setUp(self): + def set_up(self): self.fixture = IdentifierFixture() def test_integer(self): diff --git a/SoftLayer/tests/config_tests.py b/SoftLayer/tests/config_tests.py index aacb2253b..c71316b0f 100644 --- a/SoftLayer/tests/config_tests.py +++ b/SoftLayer/tests/config_tests.py @@ -9,10 +9,10 @@ from SoftLayer.config import ( get_client_settings_args, get_client_settings_env, get_client_settings_config_file, get_client_settings) -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase -class TestGetClientSettings(unittest.TestCase): +class TestGetClientSettings(TestCase): @patch('SoftLayer.config.SETTING_RESOLVERS', []) def test_no_resolvers(self): @@ -40,7 +40,7 @@ def test_inherit(self): self.assertEqual(result, {'auth': 'AUTH HANDLER', 'timeout': 20}) -class TestGetClientSettingsArgs(unittest.TestCase): +class TestGetClientSettingsArgs(TestCase): def test_username_api_key(self): result = get_client_settings_args(username='username', @@ -74,7 +74,7 @@ def test_with_auth(self): self.assertEqual(result['auth'], auth) -class TestGetClientSettingsEnv(unittest.TestCase): +class TestGetClientSettingsEnv(TestCase): @patch.dict('os.environ', {'SL_USERNAME': 'username', 'SL_API_KEY': 'api_key', @@ -93,7 +93,7 @@ def test_no_auth(self): self.assertEqual(result, {'proxy': ANY}) -class TestGetClientSettingsConfigFile(unittest.TestCase): +class TestGetClientSettingsConfigFile(TestCase): @patch('six.moves.configparser.RawConfigParser') def test_username_api_key(self, config_parser): diff --git a/SoftLayer/tests/functional_tests.py b/SoftLayer/tests/functional_tests.py index 16cc91d7d..afb54d67e 100644 --- a/SoftLayer/tests/functional_tests.py +++ b/SoftLayer/tests/functional_tests.py @@ -7,7 +7,7 @@ import os import SoftLayer -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase, unittest def get_creds(): @@ -24,7 +24,7 @@ def get_creds(): } -class UnauthedUser(unittest.TestCase): +class UnauthedUser(TestCase): def test_failed_auth(self): client = SoftLayer.Client( username='doesnotexist', api_key='issurelywrong', timeout=20) @@ -45,7 +45,7 @@ def test_no_hostname(self): self.fail('No Exception Raised') -class AuthedUser(unittest.TestCase): +class AuthedUser(TestCase): def test_service_does_not_exist(self): creds = get_creds() client = SoftLayer.Client( diff --git a/SoftLayer/tests/managers/cci_tests.py b/SoftLayer/tests/managers/cci_tests.py index 9772a7357..71bef617c 100644 --- a/SoftLayer/tests/managers/cci_tests.py +++ b/SoftLayer/tests/managers/cci_tests.py @@ -5,17 +5,17 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import CCIManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from mock import ANY, call -class CCITests(unittest.TestCase): +class CCITests(TestCase): """ Small class to verify the rename of CCIManager to VSManager didn't break any existing code. """ - def setUp(self): + def set_up(self): self.client = FixtureClient() self.cci = CCIManager(self.client) diff --git a/SoftLayer/tests/managers/cdn_tests.py b/SoftLayer/tests/managers/cdn_tests.py index 64cf300fa..d2f403a9d 100644 --- a/SoftLayer/tests/managers/cdn_tests.py +++ b/SoftLayer/tests/managers/cdn_tests.py @@ -6,15 +6,15 @@ """ from SoftLayer.managers.cdn import (CDNManager, MAX_URLS_PER_LOAD, MAX_URLS_PER_PURGE) -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Account from mock import call from math import ceil -class CDNTests(unittest.TestCase): +class CDNTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.cdn_client = CDNManager(self.client) diff --git a/SoftLayer/tests/managers/dns_tests.py b/SoftLayer/tests/managers/dns_tests.py index 3d79c766c..23730a23f 100644 --- a/SoftLayer/tests/managers/dns_tests.py +++ b/SoftLayer/tests/managers/dns_tests.py @@ -5,15 +5,15 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import DNSManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Dns_Domain, Account from mock import ANY -class DNSTests(unittest.TestCase): +class DNSTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.dns_client = DNSManager(self.client) diff --git a/SoftLayer/tests/managers/firewall_tests.py b/SoftLayer/tests/managers/firewall_tests.py index 1d079a15b..fa78a1afc 100644 --- a/SoftLayer/tests/managers/firewall_tests.py +++ b/SoftLayer/tests/managers/firewall_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import FirewallManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import (Network_Component_Firewall, Network_Vlan_Firewall, Billing_Item) from mock import ANY @@ -16,9 +16,9 @@ 'sourceIpAddress,sourceIpSubnetMask,version]') -class FirewallTests(unittest.TestCase): +class FirewallTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.firewall = FirewallManager(self.client) @@ -120,8 +120,8 @@ def test_cancel_firewall(self): f.assert_called_once_with(id=billing_item_id) self.assertEqual(result, Billing_Item.cancelService) call = self.client['Network_Component_Firewall'].getObject - MASK = ('mask[id,billingItem[id]]') - call.assert_called_once_with(id=6327, mask=MASK) + mask = ('mask[id,billingItem[id]]') + call.assert_called_once_with(id=6327, mask=mask) # test dedicated firewalls billing_item_id = 21370815 result = self.firewall.cancel_firewall(fwl_id, dedicated=True) @@ -129,8 +129,8 @@ def test_cancel_firewall(self): f.assert_called_twice_with(id=billing_item_id) self.assertEqual(result, Billing_Item.cancelService) call = self.client['Network_Vlan_Firewall'].getObject - MASK = ('mask[id,billingItem[id]]') - call.assert_called_once_with(id=6327, mask=MASK) + mask = ('mask[id,billingItem[id]]') + call.assert_called_once_with(id=6327, mask=mask) def test_add_standard_firewall_cci(self): # test standard firewalls for CCI @@ -228,9 +228,9 @@ def test_edit_standard_fwl_rules(self): fwl_id = 1234 self.firewall.edit_standard_fwl_rules(firewall_id=fwl_id, rules=rules) - tempObject = { + temp_object = { "networkComponentFirewallId": fwl_id, "rules": rules} f = self.client['Network_Firewall_Update_Request'].createObject - f.assert_called_once_with(tempObject) + f.assert_called_once_with(temp_object) diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py index 3d57ea586..724e36f2f 100644 --- a/SoftLayer/tests/managers/hardware_tests.py +++ b/SoftLayer/tests/managers/hardware_tests.py @@ -6,16 +6,16 @@ """ from SoftLayer import HardwareManager from SoftLayer.managers.hardware import get_default_value -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import ( Hardware_Server, Account, Billing_Item, Ticket) from mock import ANY, call, patch -class HardwareTests(unittest.TestCase): +class HardwareTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.hardware = HardwareManager(self.client) diff --git a/SoftLayer/tests/managers/image_tests.py b/SoftLayer/tests/managers/image_tests.py index e191072ed..e386f6d2a 100644 --- a/SoftLayer/tests/managers/image_tests.py +++ b/SoftLayer/tests/managers/image_tests.py @@ -5,16 +5,16 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import ImageManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import ( Virtual_Guest_Block_Device_Template_Group, Account) from mock import ANY -class ImageTests(unittest.TestCase): +class ImageTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.image = ImageManager(self.client) self.vgbdtg = self.client['Virtual_Guest_Block_Device_Template_Group'] diff --git a/SoftLayer/tests/managers/iscsi_tests.py b/SoftLayer/tests/managers/iscsi_tests.py index 26c28c6c0..d4fac55a9 100644 --- a/SoftLayer/tests/managers/iscsi_tests.py +++ b/SoftLayer/tests/managers/iscsi_tests.py @@ -5,13 +5,13 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import ISCSIManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Network_Storage_Iscsi from mock import ANY -class ISCSITests(unittest.TestCase): - def setUp(self): +class ISCSITests(TestCase): + def set_up(self): self.client = FixtureClient() self.iscsi = ISCSIManager(self.client) @@ -46,8 +46,8 @@ def test_invalid_datacenter(self): size=10, location='foo') def test_create_iscsi(self): - getItems = self.client['Product_Package'].getItems - getItems.return_value = [ + get_items = self.client['Product_Package'].getItems + get_items.return_value = [ { 'id': 4439, 'capacity': '1', @@ -77,8 +77,8 @@ def test_create_snapshot(self): f.assert_called_once_with('unNeeded', id=iscsi_id) def test_create_snapshot_space(self): - getItems = self.client['Product_Package'].getItems - getItems.return_value = [ + get_items = self.client['Product_Package'].getItems + get_items.return_value = [ { 'id': 1121, 'capacity': '20', diff --git a/SoftLayer/tests/managers/loadbal_tests.py b/SoftLayer/tests/managers/loadbal_tests.py index ff4874552..09e3c4663 100644 --- a/SoftLayer/tests/managers/loadbal_tests.py +++ b/SoftLayer/tests/managers/loadbal_tests.py @@ -5,13 +5,13 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import LoadBalancerManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Billing_Item -class LoadBalancerTests(unittest.TestCase): +class LoadBalancerTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.lb_mgr = LoadBalancerManager(self.client) diff --git a/SoftLayer/tests/managers/metadata_tests.py b/SoftLayer/tests/managers/metadata_tests.py index da3b79472..506b90567 100644 --- a/SoftLayer/tests/managers/metadata_tests.py +++ b/SoftLayer/tests/managers/metadata_tests.py @@ -6,14 +6,14 @@ """ from SoftLayer import MetadataManager, SoftLayerError, SoftLayerAPIError from SoftLayer.consts import API_PRIVATE_ENDPOINT_REST, USER_AGENT -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from mock import patch, MagicMock -class MetadataTests(unittest.TestCase): +class MetadataTests(TestCase): - def setUp(self): + def set_up(self): self.metadata = MetadataManager() self.make_request = MagicMock() self.metadata.make_request = self.make_request @@ -73,9 +73,9 @@ def test_networks(self): }, r) -class MetadataTestsMakeRequest(unittest.TestCase): +class MetadataTestsMakeRequest(TestCase): - def setUp(self): + def set_up(self): self.metadata = MetadataManager() self.url = '/'.join([ API_PRIVATE_ENDPOINT_REST.rstrip('/'), diff --git a/SoftLayer/tests/managers/network_tests.py b/SoftLayer/tests/managers/network_tests.py index 5bc4d249e..1150934a5 100644 --- a/SoftLayer/tests/managers/network_tests.py +++ b/SoftLayer/tests/managers/network_tests.py @@ -5,15 +5,15 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import NetworkManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Product_Order from mock import ANY, call -class NetworkTests(unittest.TestCase): +class NetworkTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.network = NetworkManager(self.client) diff --git a/SoftLayer/tests/managers/queue_tests.py b/SoftLayer/tests/managers/queue_tests.py index 5ed2db0ab..a10f6b4c6 100644 --- a/SoftLayer/tests/managers/queue_tests.py +++ b/SoftLayer/tests/managers/queue_tests.py @@ -6,7 +6,7 @@ """ from SoftLayer import MessagingManager, Unauthenticated, SoftLayerError from SoftLayer.consts import USER_AGENT -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase import SoftLayer.managers.messaging from mock import MagicMock, patch, ANY @@ -52,8 +52,8 @@ def mocked_auth_call(self): self.auth_token = 'NEW_AUTH_TOKEN' -class QueueAuthTests(unittest.TestCase): - def setUp(self): +class QueueAuthTests(TestCase): + def set_up(self): self.auth = SoftLayer.managers.messaging.QueueAuth( 'endpoint', 'username', 'api_key', auth_token='auth_token') @@ -119,9 +119,9 @@ def test_call_unauthed(self): self.assertEqual(request.headers, {'X-Auth-Token': 'NEW_AUTH_TOKEN'}) -class MessagingManagerTests(unittest.TestCase): +class MessagingManagerTests(TestCase): - def setUp(self): + def set_up(self): self.client = MagicMock() self.manager = MessagingManager(self.client) @@ -200,9 +200,9 @@ def test_ping(self, get): self.assertTrue(result) -class MessagingConnectionTests(unittest.TestCase): +class MessagingConnectionTests(TestCase): - def setUp(self): + def set_up(self): self.conn = SoftLayer.managers.messaging.MessagingConnection( 'acount_id', endpoint='endpoint') self.auth = MagicMock() diff --git a/SoftLayer/tests/managers/sshkey_tests.py b/SoftLayer/tests/managers/sshkey_tests.py index 633527f6c..4881e9d7f 100644 --- a/SoftLayer/tests/managers/sshkey_tests.py +++ b/SoftLayer/tests/managers/sshkey_tests.py @@ -5,13 +5,13 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import SshKeyManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from mock import call -class SshKeyTests(unittest.TestCase): +class SshKeyTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.sshkey = SshKeyManager(self.client) diff --git a/SoftLayer/tests/managers/ssl_tests.py b/SoftLayer/tests/managers/ssl_tests.py index 12d9de7b0..d32478fe1 100644 --- a/SoftLayer/tests/managers/ssl_tests.py +++ b/SoftLayer/tests/managers/ssl_tests.py @@ -5,14 +5,14 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import SSLManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from mock import ANY -class SSLTests(unittest.TestCase): +class SSLTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.ssl = SSLManager(self.client) self.test_id = 10 diff --git a/SoftLayer/tests/managers/ticket_tests.py b/SoftLayer/tests/managers/ticket_tests.py index 0799488b5..b1f67204d 100644 --- a/SoftLayer/tests/managers/ticket_tests.py +++ b/SoftLayer/tests/managers/ticket_tests.py @@ -5,14 +5,14 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import TicketManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Ticket from mock import ANY, call -class TicketTests(unittest.TestCase): +class TicketTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.ticket = TicketManager(self.client) diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 7d3eab461..b094b6810 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -5,15 +5,15 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import VSManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Virtual_Guest from mock import MagicMock, ANY, call, patch -class VSTests(unittest.TestCase): +class VSTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.vs = VSManager(self.client) @@ -534,11 +534,11 @@ def test_captures(self): def test_upgrade(self): # Testing Upgrade - orderClient = self.client['Product_Order'] + order_client = self.client['Product_Order'] # test single upgrade self.vs.upgrade(1, cpus=4, public=False) - orderClient.placeOrder.called_once_with(1, cpus=4, public=False) + order_client.placeOrder.called_once_with(1, cpus=4, public=False) # Now test a blank upgrade self.vs.upgrade(1) @@ -547,7 +547,7 @@ def test_upgrade(self): # Testing all parameters Upgrade self.vs.upgrade(1, cpus=4, memory=2, nic_speed=1000, public=True) args = {'cpus': 4, 'memory': 2, 'nic_speed': 1000, 'public': 1000} - orderClient.placeOrder.called_once_with(1, **args) + order_client.placeOrder.called_once_with(1, **args) def test_get_item_id_for_upgrade(self): item_id = 0 @@ -560,9 +560,9 @@ def test_get_item_id_for_upgrade(self): self.assertEqual(1133, item_id) -class VSWaitReadyGoTests(unittest.TestCase): +class VSWaitReadyGoTests(TestCase): - def setUp(self): + def set_up(self): self.client = MagicMock() self.vs = VSManager(self.client) self.guestObject = self.client['Virtual_Guest'].getObject diff --git a/SoftLayer/tests/transport_tests.py b/SoftLayer/tests/transport_tests.py index 56bbbd742..535d12a98 100644 --- a/SoftLayer/tests/transport_tests.py +++ b/SoftLayer/tests/transport_tests.py @@ -8,13 +8,13 @@ from SoftLayer import SoftLayerAPIError, TransportError from SoftLayer.transports import make_rest_api_call, make_xml_rpc_api_call -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from requests import HTTPError, RequestException -class TestXmlRpcAPICall(unittest.TestCase): +class TestXmlRpcAPICall(TestCase): - def setUp(self): + def set_up(self): self.send_content = ''' @@ -75,7 +75,7 @@ def test_valid_proxy(self, send): timeout=None) -class TestRestAPICall(unittest.TestCase): +class TestRestAPICall(TestCase): @patch('SoftLayer.transports.requests.request') def test_json(self, request): diff --git a/tox.ini b/tox.ini index 76bc4f943..f39c99810 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26,py27,py33,py34,pypy,pep8,pylint +envlist = py26,py27,py33,py34,pypy,pep8,py2pep8 [testenv] deps = @@ -17,24 +17,22 @@ deps = -r{toxinidir}/tools/test-requirements.txt commands = {envpython} setup.py nosetests [] [testenv:pep8] -deps = flake8 -commands = flake8 \ - --max-complexity=36 \ - --statistics \ - SoftLayer - -[testenv:pylint] -deps = pylint -commands = pylint SoftLayer \ - --ignore=tests \ - -d R0903 \ # Too few public methods - -d R0914 \ # Too many local variables - -d R0201 \ # Method could be a function - -d I0011 \ # Locally Disabling - -d W0142 \ # Used * or ** magic - --max-args=20 \ - --max-branches=40 \ - --max-statements=86 \ - --max-module-lines=1200 \ - --max-returns=8 \ - --min-similarity-lines=50 # TODO: Remove \ No newline at end of file +deps = + flake8 + pep8-naming + pylint +commands = + flake8 --max-complexity=36 --statistics SoftLayer + pylint SoftLayer \ + --ignore=tests \ + -d R0903 \ # Too few public methods + -d R0914 \ # Too many local variables + -d R0201 \ # Method could be a function + -d I0011 \ # Locally Disabling + -d W0142 \ # Used * or ** magic + --max-args=20 \ + --max-branches=40 \ + --max-statements=86 \ + --max-module-lines=1200 \ + --max-returns=8 \ + --min-similarity-lines=50 # TODO: Remove From 3b7ea2131e10ca3d5944950da8195940dad0f2e5 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 22 May 2014 21:13:29 -0500 Subject: [PATCH 0028/2667] Remove py3 flake8 until the issues can be worked out --- .travis.yml | 1 - tox.ini | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index a736003a4..36b5d012f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,5 @@ env: - TOX_ENV=py34 - TOX_ENV=pypy - TOX_ENV=pep8 - - TOX_ENV=py2pep8 install: pip install tox --use-mirrors script: tox -e $TOX_ENV diff --git a/tox.ini b/tox.ini index f39c99810..54378c7b5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,5 @@ [tox] -envlist = py26,py27,py33,py34,pypy,pep8,py2pep8 - +envlist = py26,py27,py33,py34,pypy,pep8 [testenv] deps = unittest2 @@ -17,6 +16,7 @@ deps = -r{toxinidir}/tools/test-requirements.txt commands = {envpython} setup.py nosetests [] [testenv:pep8] +basepython = python2.7 deps = flake8 pep8-naming From d30667c3e475e41ee8cd75110eea28ee7b145caa Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 23 May 2014 10:29:51 -0500 Subject: [PATCH 0029/2667] Fixes a couple errors in how the mask was being constructed for VSManager.get_instance --- SoftLayer/managers/vs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 8bcd9e184..296faf43f 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -163,9 +163,9 @@ def get_instance(self, instance_id, **kwargs): 'privateNetworkOnlyFlag', 'primaryBackendIpAddress', 'primaryIpAddress', - '''networkComponents[id, status, speed, maxSpeed, name,' + '''networkComponents[id, status, speed, maxSpeed, name, macAddress, primaryIpAddress, port, - primarySubnet]''' + primarySubnet]''', 'lastKnownPowerState.name', 'powerState', 'status', From effeaa2e971d82e8d09f8833234d03fe71d9a7f9 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 23 May 2014 10:38:25 -0500 Subject: [PATCH 0030/2667] Fixes additional mask issue --- SoftLayer/managers/hardware.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c294cb1c4..648999676 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -286,9 +286,12 @@ def get_hardware(self, hardware_id, **kwargs): networkIdentifier, gateway]]''', 'hardwareChassis[id,name]', 'activeTransaction[id, transactionStatus[friendlyName,name]]', - 'softwareDescription[manufacturer,name,version,referenceCode]', - '''operatingSystem[softwareLicense, - passwords[username,password]]''', + '''operatingSystem[ + softwareLicense[softwareDescription[manufacturer, + name, + version, + referenceCode]], + passwords[username,password]]''', 'billingItem.recurringFee', 'hourlyBillingFlag', 'tagReferences[id,tag[name,id]]', From af082e6219536ef4d2ca23e8e1369ca53da78d8c Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 23 May 2014 15:10:48 -0500 Subject: [PATCH 0031/2667] Config setup aborts after 3 attempts on each user input --- SoftLayer/CLI/modules/config.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/modules/config.py b/SoftLayer/CLI/modules/config.py index 14a0e7cc5..5a8fe62b8 100644 --- a/SoftLayer/CLI/modules/config.py +++ b/SoftLayer/CLI/modules/config.py @@ -139,23 +139,27 @@ def get_user_input(self): timeout = defaults['timeout'] # Ask for username - while True: + for _ in range(3): username = self.env.input( 'Username [%s]: ' % defaults['username']) \ or defaults['username'] if username: break + else: + raise CLIAbort('Aborted after 3 attempts') # Ask for 'secret' which can be api_key or their password - while True: + for _ in range(3): secret = self.env.getpass( 'API Key or Password [%s]: ' % defaults['api_key']) \ or defaults['api_key'] if secret: break + else: + raise CLIAbort('Aborted after 3 attempts') # Ask for which endpoint they want to use - while True: + for _ in range(3): endpoint_type = self.env.input( 'Endpoint (public|private|custom): ') endpoint_type = endpoint_type.lower() @@ -173,6 +177,8 @@ def get_user_input(self): 'Endpoint URL [%s]: ' % defaults['endpoint_url'] ) or defaults['endpoint_url'] break + else: + raise CLIAbort('Aborted after 3 attempts') return username, secret, endpoint_url, timeout From 57691aaf65a74eeb55cd9cc1cbc8a4691d3a6db2 Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Mon, 26 May 2014 13:52:30 -0500 Subject: [PATCH 0032/2667] Ordering manager initial commit Tests, ordering manager logic and support for hardware manager --- SoftLayer/CLI/modules/server.py | 34 +++---- SoftLayer/managers/__init__.py | 4 +- SoftLayer/managers/hardware.py | 58 +++++------ SoftLayer/managers/ordering.py | 101 ++++++++++++++++++++ SoftLayer/tests/fixtures/Product_Package.py | 14 ++- SoftLayer/tests/managers/hardware_tests.py | 10 +- SoftLayer/tests/managers/ordering_tests.py | 42 ++++++++ 7 files changed, 203 insertions(+), 60 deletions(-) create mode 100644 SoftLayer/managers/ordering.py create mode 100644 SoftLayer/tests/managers/ordering_tests.py diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 59108e98b..8df9c69ff 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -30,14 +30,14 @@ CLIRunnable, Table, KeyValueTable, FormattedItem, NestedDict, CLIAbort, blank, listing, gb, active_txn, no_going_back, resolve_id, confirm, ArgumentError, update_with_template_args, export_to_template) -from SoftLayer import HardwareManager, SshKeyManager +from SoftLayer import HardwareManager, SshKeyManager, OrderingManager class ListServers(CLIRunnable): """ usage: sl server list [options] -List hardware servers on the acount +List hardware servers on the account Examples: sl server list --datacenter=dal05 @@ -63,7 +63,7 @@ class ListServers(CLIRunnable): action = 'list' def execute(self, args): - manager = HardwareManager(self.client) + manager = HardwareManager(self.client, OrderingManager(self.client)) tags = None if args.get('--tags'): @@ -119,7 +119,7 @@ class ServerDetails(CLIRunnable): action = 'detail' def execute(self, args): - hardware = HardwareManager(self.client) + hardware = HardwareManager(self.client, OrderingManager(self.client)) table = KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' @@ -207,7 +207,7 @@ class ServerReload(CLIRunnable): options = ['confirm'] def execute(self, args): - hardware = HardwareManager(self.client) + hardware = HardwareManager(self.client, OrderingManager(self.client)) hardware_id = resolve_id( hardware.resolve_ids, args.get(''), 'hardware') keys = [] @@ -238,7 +238,7 @@ class CancelServer(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id( mgr.resolve_ids, args.get(''), 'hardware') @@ -269,7 +269,7 @@ def execute(self, args): table.align['Code'] = 'r' table.align['Reason'] = 'l' - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) for code, reason in mgr.get_cancellation_reasons().items(): table.add_row([code, reason]) @@ -287,7 +287,7 @@ class ServerPowerOff(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if args['--really'] or confirm('This will power off the server with ' @@ -312,7 +312,7 @@ class ServerReboot(CLIRunnable): def execute(self, args): hardware_server = self.client['Hardware_Server'] - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if args['--really'] or confirm('This will power off the server with ' @@ -336,7 +336,7 @@ class ServerPowerOn(CLIRunnable): action = 'power-on' def execute(self, args): - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') self.client['Hardware_Server'].powerOn(id=hw_id) @@ -352,7 +352,7 @@ class ServerPowerCycle(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') @@ -379,7 +379,7 @@ class NicEditServer(CLIRunnable): def execute(self, args): public = args['public'] - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') @@ -399,7 +399,7 @@ def execute(self, args): table.align['Code'] = 'r' table.align['Chassis'] = 'l' - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) chassis = mgr.get_available_dedicated_server_packages() for chassis in chassis: @@ -431,7 +431,7 @@ class ServerCreateOptions(CLIRunnable): 'controller'] def execute(self, args): - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) table = KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' @@ -785,7 +785,7 @@ class CreateServer(CLIRunnable): def execute(self, args): update_with_template_args(args, list_args=['--disk', '--key']) - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) self._validate_args(args) ds_options = mgr.get_dedicated_server_create_options(args['--chassis']) @@ -845,7 +845,7 @@ def _process_args(self, args, ds_options): Helper method to centralize argument processing without convoluting code flow of the main execute method. """ - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) order = { 'hostname': args['--hostname'], @@ -1038,7 +1038,7 @@ def execute(self, args): data['hostname'] = args.get('--hostname') data['domain'] = args.get('--domain') - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if not mgr.edit(hw_id, **data): diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index b508483b3..6c7c6d702 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -22,7 +22,9 @@ from SoftLayer.managers.ticket import TicketManager from SoftLayer.managers.iscsi import ISCSIManager from SoftLayer.managers.vs import VSManager +from SoftLayer.managers.ordering import OrderingManager __all__ = ['CCIManager', 'DNSManager', 'FirewallManager', 'HardwareManager', 'ImageManager', 'MessagingManager', 'MetadataManager', 'CDNManager', 'NetworkManager', 'SshKeyManager', 'SSLManager', 'TicketManager', - 'VSManager', 'ISCSIManager', 'LoadBalancerManager'] + 'VSManager', 'ISCSIManager', 'LoadBalancerManager', + 'OrderingManager'] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 648999676..8bf8b77d5 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -18,11 +18,12 @@ class HardwareManager(IdentifierMixin, object): :param SoftLayer.API.Client client: an API client instance """ - def __init__(self, client): + def __init__(self, client, ordering_manager): self.client = client self.hardware = self.client['Hardware_Server'] self.account = self.client['Account'] self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname] + self.ordering_manager = ordering_manager def cancel_hardware(self, hardware_id, reason='unneeded', comment=''): """ Cancels the specified dedicated server. @@ -185,17 +186,11 @@ def get_bare_metal_create_options(self): def get_bare_metal_package_id(self): """ Return the bare metal package id """ - packages = self.client['Product_Package'].getAllObjects( - mask='mask[id, name]', - filter={'name': query_filter('Bare Metal Instance')}) + ordering_manager = self.ordering_manager + mask = "mask[id,name,description,type[keyName]]" + package = ordering_manager.get_package_by_type('BARE_METAL_CORE', mask) - hw_id = 0 - for package in packages: - if 'Bare Metal Instance' == package['name']: - hw_id = package['id'] - break - - return hw_id + return package['id'] def get_available_dedicated_server_packages(self): """ Retrieves a list of packages that are available for ordering @@ -204,33 +199,26 @@ def get_available_dedicated_server_packages(self): :returns: A list of tuples of available dedicated server packages in the form (id, name, description) """ + available_packages = [] + ordering_manager = self.ordering_manager - package_obj = self.client['Product_Package'] - packages = [] - - # Pull back only server packages - mask = 'id,name,description,type' - _filter = { - 'type': { - 'keyName': { - 'operation': 'in', - 'options': [ - {'name': 'data', - 'value': ['BARE_METAL_CPU', 'BARE_METAL_CORE']} - ], - }, - }, - } + mask = 'id,name,description,type,isActive' + package_types = ['BARE_METAL_CPU', 'BARE_METAL_CORE'] - for package in package_obj.getAllObjects(mask=mask, filter=_filter): - # Filter out packages without a name or that are designated as - # 'OUTLET.' The outlet packages are missing some necessary data - # and their orders will fail. - if package.get('name') and 'OUTLET' not in package['description']: - packages.append((package['id'], package['name'], - package['description'])) + packages = ordering_manager.get_packages_of_type(package_types, + mask) + # We only want packages that are active (we can place new orders for) + # and non-outlet. + # Outlet packages require specialized logic and we don't want to deal + # with them right now. + packages = ordering_manager.get_only_active_packages(packages) + packages = ordering_manager.filter_outlet_packages(packages) + + for package in packages: + available_packages.append((package['id'], package['name'], + package['description'])) - return packages + return available_packages def get_dedicated_server_create_options(self, package_id): """ Retrieves the available options for creating a dedicated server in diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py new file mode 100644 index 000000000..f21c2b27a --- /dev/null +++ b/SoftLayer/managers/ordering.py @@ -0,0 +1,101 @@ +""" + SoftLayer.ordering + ~~~~~~~~~~~~~~~~~~ + Ordering Manager + + :license: MIT, see LICENSE for more details. +""" + + +class OrderingManager(object): + """ + Manages hardware devices. + + :param SoftLayer.API.Client client: an API client instance + """ + + def __init__(self, client): + self.client = client + + def get_packages_of_type(self, package_types, mask): + """ Get packages that match a certain type + + Each ordering package has a type, so return all packages that match + the types we are looking for + + :param list package_types: List of strings representing the package + type keynames we are interested in. + :param string mask: Mask to specify the properties we want to retrieve + """ + package_service = self.get_package_service() + _filter = { + 'type': { + 'keyName': { + 'operation': 'in', + 'options': [ + {'name': 'data', + 'value': package_types} + ], + }, + }, + } + + packages = package_service.getAllObjects(mask=mask, filter=_filter) + packages = self.filter_outlet_packages(packages) + return packages + + def get_package_service(self): + """ Get the service to query product packages + :return SoftLayer.API.Service + """ + return self.client['Product_Package'] + + @staticmethod + def filter_outlet_packages(packages): + """ Remove packages designated as OUTLET + + Those type of packages must be handled in a different way, + and they are not supported at the moment. + + :param packages: Dictionary of packages. Name and description keys + must be present in each of them. + """ + non_outlet_packages = [] + + for package in packages: + if 'OUTLET' not in package['description'].upper() and \ + 'OUTLET' not in package['name'].upper(): + non_outlet_packages.append(package) + + return non_outlet_packages + + + @staticmethod + def get_only_active_packages(packages): + """ Return only active packages + + If a package is active, it is eligible for ordering + This will inspect the 'isActive' property on the provided packages + + :param packages Dictionary of packages, isActive key must be present + """ + active_packages = [] + + for package in packages: + if package['isActive']: + active_packages.append(package) + + return active_packages + + def get_package_by_type(self, package_type, mask): + """ Get a single package of a given type. + + Syntactic sugar to retrieve a single package of a given type. + If multiple packages share the given type, this will return the first + one returned by the API. + + :param package_type string representing the package type key name + we are interested in + """ + packages = self.get_packages_of_type([package_type], mask) + return packages.pop() diff --git a/SoftLayer/tests/fixtures/Product_Package.py b/SoftLayer/tests/fixtures/Product_Package.py index ba9805095..6e7ef9107 100644 --- a/SoftLayer/tests/fixtures/Product_Package.py +++ b/SoftLayer/tests/fixtures/Product_Package.py @@ -1,9 +1,17 @@ getAllObjects = [ - {'id': 13, 'name': 'Mock Testing Package', 'description': 'a thing'}, + {'id': 13, 'name': 'Mock Testing Package', 'description': 'a thing', + 'type': {'keyName': 'BARE_METAL_CPU'}, 'isActive': 1}, + {'id': 15, 'name': 'Inactive package', 'description': 'a cool server', + 'type': {'keyName': 'BARE_METAL_CPU'}, 'isActive': 0}, {'id': 27, 'name': 'An additional testing category', - 'description': 'Another thing - OUTLET'}, + 'description': 'Another thing - OUTLET', + 'type': {'keyName': 'BARE_METAL_CPU'}, 'isActive': 1}, + {'id': 28, 'name': 'An outlet package', + 'description': 'Super fun package', + 'type': {'keyName': 'BARE_METAL_CPU'}, 'isActive': 1}, {'id': 50, 'name': 'Bare Metal Instance', - 'description': 'Bare Metal Instance'}, + 'description': 'Bare Metal Instance', + 'type': {'keyName': 'BARE_METAL_CORE'}, 'isActive': 1}, ] getObject = getAllObjects[0] diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py index 3d57ea586..9770a95c5 100644 --- a/SoftLayer/tests/managers/hardware_tests.py +++ b/SoftLayer/tests/managers/hardware_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import HardwareManager +from SoftLayer import HardwareManager, OrderingManager from SoftLayer.managers.hardware import get_default_value from SoftLayer.tests import unittest, FixtureClient from SoftLayer.tests.fixtures import ( @@ -17,7 +17,8 @@ class HardwareTests(unittest.TestCase): def setUp(self): self.client = FixtureClient() - self.hardware = HardwareManager(self.client) + self.hardware = HardwareManager(self.client, + OrderingManager(self.client)) def test_list_hardware(self): mcall = call(mask=ANY, filter={}) @@ -94,7 +95,8 @@ def test_reload(self): def test_get_bare_metal_create_options_returns_none_on_error(self): self.client['Product_Package'].getAllObjects.return_value = [ - {'name': 'No Matching Instances', 'id': 0}] + {'name': 'No Matching Instances', 'id': 0, + 'description': 'Nothing'}] self.assertIsNone(self.hardware.get_bare_metal_create_options()) @@ -306,7 +308,7 @@ def test_get_available_dedicated_server_packages(self): } } f = self.client['Product_Package'].getAllObjects - f.assert_has_calls([call(mask='id,name,description,type', + f.assert_has_calls([call(mask='id,name,description,type,isActive', filter=filter_mock)]) def test_get_dedicated_server_options(self): diff --git a/SoftLayer/tests/managers/ordering_tests.py b/SoftLayer/tests/managers/ordering_tests.py new file mode 100644 index 000000000..34aaeded5 --- /dev/null +++ b/SoftLayer/tests/managers/ordering_tests.py @@ -0,0 +1,42 @@ +""" + SoftLayer.tests.managers.ordering_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from SoftLayer import OrderingManager +from SoftLayer.tests import unittest, FixtureClient + + +class OrderingTests(unittest.TestCase): + + def setUp(self): + self.client = FixtureClient() + self.ordering = OrderingManager(self.client) + + def test_get_package_by_type_returns_no_outlet_packages(self): + fixture_outlet_package_ids = [27, 28] + packages = self._get_server_packages() + filtered_packages = self.ordering.filter_outlet_packages(packages) + + for package_id in fixture_outlet_package_ids: + self._assert_package_id_not_present(package_id, filtered_packages) + + def _get_server_packages(self): + mask = 'id, name, description, type, isActive' + return self.ordering.get_packages_of_type(['BARE_METAL_CPU'], mask) + + def _assert_package_id_not_present(self, package_id, packages): + package_ids = [] + for package in packages: + package_ids.append(package['id']) + + self.assertNotIn(package_id, package_ids) + + def test_get_active_packages(self): + fixture_inactive_package_ids = [15] + packages = self._get_server_packages() + filtered_packages = self.ordering.get_only_active_packages(packages) + + for package_id in fixture_inactive_package_ids: + self._assert_package_id_not_present(package_id, filtered_packages) From c6ad9d146620659a83f74d805d37d8e240a9d26f Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Mon, 26 May 2014 14:02:29 -0500 Subject: [PATCH 0033/2667] Fix pylint issue --- SoftLayer/managers/ordering.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index f21c2b27a..c35846460 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -69,7 +69,6 @@ def filter_outlet_packages(packages): return non_outlet_packages - @staticmethod def get_only_active_packages(packages): """ Return only active packages From d30f57b30c6db25056c739887e19d35f8d1d073a Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Wed, 28 May 2014 19:57:30 -0500 Subject: [PATCH 0034/2667] Remove required parameter for ordering manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let’s keep it backwards compatible --- SoftLayer/CLI/modules/server.py | 32 +++++++++++----------- SoftLayer/managers/hardware.py | 13 +++++++-- SoftLayer/tests/managers/hardware_tests.py | 8 ++++-- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 8df9c69ff..b2e16852e 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -30,7 +30,7 @@ CLIRunnable, Table, KeyValueTable, FormattedItem, NestedDict, CLIAbort, blank, listing, gb, active_txn, no_going_back, resolve_id, confirm, ArgumentError, update_with_template_args, export_to_template) -from SoftLayer import HardwareManager, SshKeyManager, OrderingManager +from SoftLayer import HardwareManager, SshKeyManager class ListServers(CLIRunnable): @@ -63,7 +63,7 @@ class ListServers(CLIRunnable): action = 'list' def execute(self, args): - manager = HardwareManager(self.client, OrderingManager(self.client)) + manager = HardwareManager(self.client) tags = None if args.get('--tags'): @@ -119,7 +119,7 @@ class ServerDetails(CLIRunnable): action = 'detail' def execute(self, args): - hardware = HardwareManager(self.client, OrderingManager(self.client)) + hardware = HardwareManager(self.client) table = KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' @@ -207,7 +207,7 @@ class ServerReload(CLIRunnable): options = ['confirm'] def execute(self, args): - hardware = HardwareManager(self.client, OrderingManager(self.client)) + hardware = HardwareManager(self.client) hardware_id = resolve_id( hardware.resolve_ids, args.get(''), 'hardware') keys = [] @@ -238,7 +238,7 @@ class CancelServer(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id( mgr.resolve_ids, args.get(''), 'hardware') @@ -269,7 +269,7 @@ def execute(self, args): table.align['Code'] = 'r' table.align['Reason'] = 'l' - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) for code, reason in mgr.get_cancellation_reasons().items(): table.add_row([code, reason]) @@ -287,7 +287,7 @@ class ServerPowerOff(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if args['--really'] or confirm('This will power off the server with ' @@ -312,7 +312,7 @@ class ServerReboot(CLIRunnable): def execute(self, args): hardware_server = self.client['Hardware_Server'] - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if args['--really'] or confirm('This will power off the server with ' @@ -336,7 +336,7 @@ class ServerPowerOn(CLIRunnable): action = 'power-on' def execute(self, args): - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') self.client['Hardware_Server'].powerOn(id=hw_id) @@ -352,7 +352,7 @@ class ServerPowerCycle(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') @@ -379,7 +379,7 @@ class NicEditServer(CLIRunnable): def execute(self, args): public = args['public'] - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') @@ -399,7 +399,7 @@ def execute(self, args): table.align['Code'] = 'r' table.align['Chassis'] = 'l' - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) chassis = mgr.get_available_dedicated_server_packages() for chassis in chassis: @@ -431,7 +431,7 @@ class ServerCreateOptions(CLIRunnable): 'controller'] def execute(self, args): - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) table = KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' @@ -785,7 +785,7 @@ class CreateServer(CLIRunnable): def execute(self, args): update_with_template_args(args, list_args=['--disk', '--key']) - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) self._validate_args(args) ds_options = mgr.get_dedicated_server_create_options(args['--chassis']) @@ -845,7 +845,7 @@ def _process_args(self, args, ds_options): Helper method to centralize argument processing without convoluting code flow of the main execute method. """ - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) order = { 'hostname': args['--hostname'], @@ -1038,7 +1038,7 @@ def execute(self, args): data['hostname'] = args.get('--hostname') data['domain'] = args.get('--domain') - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if not mgr.edit(hw_id, **data): diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8bf8b77d5..9598d8ec3 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,6 +9,7 @@ # pylint: disable=C0103 import socket from SoftLayer.utils import NestedDict, query_filter, IdentifierMixin +from SoftLayer.managers.ordering import OrderingManager class HardwareManager(IdentifierMixin, object): @@ -16,14 +17,20 @@ class HardwareManager(IdentifierMixin, object): Manages hardware devices. :param SoftLayer.API.Client client: an API client instance + :param SoftLayer.managers.OrderingManager ordering_manager: an optional + manager to handle ordering. + If none is provided, one will be + auto initialized. """ - - def __init__(self, client, ordering_manager): + def __init__(self, client, ordering_manager=None): self.client = client self.hardware = self.client['Hardware_Server'] self.account = self.client['Account'] self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname] - self.ordering_manager = ordering_manager + if ordering_manager is None: + self.ordering_manager = OrderingManager(client) + else: + self.ordering_manager = ordering_manager def cancel_hardware(self, hardware_id, reason='unneeded', comment=''): """ Cancels the specified dedicated server. diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py index 9770a95c5..6fb183061 100644 --- a/SoftLayer/tests/managers/hardware_tests.py +++ b/SoftLayer/tests/managers/hardware_tests.py @@ -17,8 +17,7 @@ class HardwareTests(unittest.TestCase): def setUp(self): self.client = FixtureClient() - self.hardware = HardwareManager(self.client, - OrderingManager(self.client)) + self.hardware = HardwareManager(self.client) def test_list_hardware(self): mcall = call(mask=ANY, filter={}) @@ -311,6 +310,11 @@ def test_get_available_dedicated_server_packages(self): f.assert_has_calls([call(mask='id,name,description,type,isActive', filter=filter_mock)]) + def test_get_server_packages_with_ordering_manager_provided(self): + self.hardware = HardwareManager(self.client, + OrderingManager(self.client)) + self.test_get_available_dedicated_server_packages() + def test_get_dedicated_server_options(self): package_id = 13 self.hardware.get_dedicated_server_create_options(package_id) From e14c25fc9aff082ac779dae3a72fc54ccc0bb20a Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Sun, 1 Jun 2014 21:50:55 -0500 Subject: [PATCH 0035/2667] Fix pep8 naming for ordering manager To be in sync with changes from pull request #343 --- SoftLayer/tests/managers/ordering_tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/tests/managers/ordering_tests.py b/SoftLayer/tests/managers/ordering_tests.py index 34aaeded5..1c5385713 100644 --- a/SoftLayer/tests/managers/ordering_tests.py +++ b/SoftLayer/tests/managers/ordering_tests.py @@ -5,12 +5,12 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import OrderingManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient -class OrderingTests(unittest.TestCase): +class OrderingTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.ordering = OrderingManager(self.client) From 6f6ec6966903ff99085f76fb72f45180b2998bcc Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Tue, 3 Jun 2014 22:59:44 -0500 Subject: [PATCH 0036/2667] Remove hardcoded package ID from VS manager, and added more tests --- SoftLayer/managers/ordering.py | 20 +++++++++++++- SoftLayer/managers/vs.py | 23 +++++++++++++--- SoftLayer/tests/fixtures/Product_Package.py | 3 +++ SoftLayer/tests/managers/ordering_tests.py | 29 +++++++++++++++++++++ SoftLayer/tests/managers/vs_tests.py | 10 +++++-- 5 files changed, 78 insertions(+), 7 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index c35846460..24484b3f8 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -92,9 +92,27 @@ def get_package_by_type(self, package_type, mask): Syntactic sugar to retrieve a single package of a given type. If multiple packages share the given type, this will return the first one returned by the API. + If no packages are found, returns None :param package_type string representing the package type key name we are interested in """ packages = self.get_packages_of_type([package_type], mask) - return packages.pop() + if len(packages) == 0: + return None + else: + return packages.pop() + + def get_package_id_by_type(self, package_type): + """ Return the package ID of a Product Package with a given type. + + :param package_type string representing the package type key name + we are interested in + :raises ValueError when no package of the given type is found + """ + mask = "mask[id, name, description, isActive, type[keyName]]" + package = self.get_package_by_type(package_type, mask) + if package: + return package['id'] + else: + raise ValueError("No package found for type: " + package_type) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 296faf43f..98912c553 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -11,15 +11,28 @@ from itertools import repeat from SoftLayer.utils import NestedDict, query_filter, IdentifierMixin, lookup +from SoftLayer.managers.ordering import OrderingManager class VSManager(IdentifierMixin, object): - """ Manage Virtual Servers """ - def __init__(self, client): + """ + Manages Virtual Servers + + :param SoftLayer.API.Client client: an API client instance + :param SoftLayer.managers.OrderingManager ordering_manager: an optional + manager to handle ordering. + If none is provided, one will be + auto initialized. + """ + def __init__(self, client, ordering_manager=None): self.client = client self.account = client['Account'] self.guest = client['Virtual_Guest'] self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname] + if ordering_manager is None: + self.ordering_manager = OrderingManager(client) + else: + self.ordering_manager = ordering_manager def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, memory=None, hostname=None, domain=None, @@ -586,8 +599,10 @@ def _get_package_items(self): Following Method gets all the item ids related to VS """ mask = "mask[description,capacity,prices.id,categories[name,id]]" - package = self.client['Product_Package'] - return package.getItems(id=46, mask=mask) + package_type = "VIRTUAL_SERVER_INSTANCE" + package_id = self.ordering_manager.get_package_id_by_type(package_type) + return self.ordering_manager\ + .get_package_service().getItems(id=package_id, mask=mask) def _get_item_id_for_upgrade(self, package_items, option, value, public=True): diff --git a/SoftLayer/tests/fixtures/Product_Package.py b/SoftLayer/tests/fixtures/Product_Package.py index 6e7ef9107..d77f3f2af 100644 --- a/SoftLayer/tests/fixtures/Product_Package.py +++ b/SoftLayer/tests/fixtures/Product_Package.py @@ -9,6 +9,9 @@ {'id': 28, 'name': 'An outlet package', 'description': 'Super fun package', 'type': {'keyName': 'BARE_METAL_CPU'}, 'isActive': 1}, + {'id': 46, 'name': 'Virtual Servers', + 'description': 'Bare Metal Instance', + 'type': {'keyName': 'VIRTUAL_SERVER_INSTANCE'}, 'isActive': 1}, {'id': 50, 'name': 'Bare Metal Instance', 'description': 'Bare Metal Instance', 'type': {'keyName': 'BARE_METAL_CORE'}, 'isActive': 1}, diff --git a/SoftLayer/tests/managers/ordering_tests.py b/SoftLayer/tests/managers/ordering_tests.py index 1c5385713..7f55634ee 100644 --- a/SoftLayer/tests/managers/ordering_tests.py +++ b/SoftLayer/tests/managers/ordering_tests.py @@ -40,3 +40,32 @@ def test_get_active_packages(self): for package_id in fixture_inactive_package_ids: self._assert_package_id_not_present(package_id, filtered_packages) + + def test_get_package_by_type_returns_if_found(self): + package_type = "BARE_METAL_CORE" + mask = "mask[id, name]" + package = self.ordering.get_package_by_type(package_type, mask) + self.assertIsNotNone(package) + + def test_get_package_by_type_returns_none_if_not_found(self): + package_type = "PIZZA_FLAVORED_SERVERS" + mask = "mask[id, name]" + self.ordering.client['Product_Package'].getAllObjects.return_value = [] + package = self.ordering.get_package_by_type(package_type, mask) + self.assertIsNone(package) + + def test_get_package_id_by_type_returns_valid_id(self): + package_type = "VIRTUAL_SERVER_INSTANCE" + self.ordering.client['Product_Package'].getAllObjects.return_value = [ + {'id': 46, 'name': 'Virtual Servers', + 'description': 'Virtual Server Instances', + 'type': {'keyName': 'VIRTUAL_SERVER_INSTANCE'}, 'isActive': 1}, + ] + package_id = self.ordering.get_package_id_by_type(package_type) + self.assertEqual(46, package_id) + + def test_get_package_id_by_type_fails_for_nonexistent_package_type(self): + package_type = "STRAWBERRY_FLAVORED_SERVERS" + self.ordering.client['Product_Package'].getAllObjects.return_value = [] + with self.assertRaises(ValueError): + self.ordering.get_package_id_by_type(package_type) diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index b094b6810..e7a5f0508 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import VSManager +from SoftLayer import VSManager, OrderingManager from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Virtual_Guest @@ -15,7 +15,7 @@ class VSTests(TestCase): def set_up(self): self.client = FixtureClient() - self.vs = VSManager(self.client) + self.vs = VSManager(self.client, OrderingManager(self.client)) def test_list_instances(self): mcall = call(mask=ANY, filter={}) @@ -536,6 +536,12 @@ def test_upgrade(self): # Testing Upgrade order_client = self.client['Product_Order'] + self.client['Product_Package'].getAllObjects.return_value = [ + {'id': 46, 'name': 'Virtual Servers', + 'description': 'Virtual Server Instances', + 'type': {'keyName': 'VIRTUAL_SERVER_INSTANCE'}, 'isActive': 1}, + ] + # test single upgrade self.vs.upgrade(1, cpus=4, public=False) order_client.placeOrder.called_once_with(1, cpus=4, public=False) From 4a7de62957bc224ffccf640c02c6f360741da4ae Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Fri, 6 Jun 2014 18:10:16 -0500 Subject: [PATCH 0037/2667] Remove verifyOrder calls immediately followed by placeOrder Checked with order verification SME: placeOrder internally verifies the order, so no need to verify first if we intent to place the order, it will be verified automatically. --- SoftLayer/managers/iscsi.py | 2 -- SoftLayer/managers/vs.py | 1 - 2 files changed, 3 deletions(-) diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index 4ef19ca17..09fbb2c7f 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -66,7 +66,6 @@ def create_iscsi(self, size=None, location=None): item_price = self._find_item_prices(int(size), categorycode='iscsi') iscsi_order = self._build_order(item_price, location) - self.product_order.verifyOrder(iscsi_order) self.product_order.placeOrder(iscsi_order) def list_iscsi(self): @@ -147,7 +146,6 @@ def create_snapshot_space(self, volume_id, capacity): 'prices': [{'id': item_price}], 'quantity': 1, 'volumeId': volume_id} - self.product_order.verifyOrder(snapshotspaceorder) self.product_order.placeOrder(snapshotspaceorder) def delete_snapshot(self, snapshot_id): diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 98912c553..08b806306 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -589,7 +589,6 @@ def upgrade(self, instance_id, cpus=None, memory=None, order['properties'] = [{'name': 'MAINTENANCE_WINDOW', 'value': str(datetime.datetime.now())}] if cpus or memory or nic_speed: - self.client['Product_Order'].verifyOrder(order) self.client['Product_Order'].placeOrder(order) return True return False From 9f6624f450d4fdaabbd193ed344403292aa64255 Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Fri, 13 Jun 2014 23:14:02 -0500 Subject: [PATCH 0038/2667] Minor change to improve code readability --- SoftLayer/managers/vs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 08b806306..039d9460a 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -600,8 +600,9 @@ def _get_package_items(self): mask = "mask[description,capacity,prices.id,categories[name,id]]" package_type = "VIRTUAL_SERVER_INSTANCE" package_id = self.ordering_manager.get_package_id_by_type(package_type) - return self.ordering_manager\ - .get_package_service().getItems(id=package_id, mask=mask) + package_service = self.ordering_manager.get_package_service() + + return package_service.getItems(id=package_id, mask=mask) def _get_item_id_for_upgrade(self, package_items, option, value, public=True): From 3f453f503902f5624e8df99e416827ae637e9f71 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 18 Jun 2014 16:33:30 -0500 Subject: [PATCH 0039/2667] Bumps required six version --- setup.py | 2 +- tools/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 5151bc4a5..e49ee3b65 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ extra = {} requires = [ - 'six >= 1.1.0', + 'six >= 1.7.0', 'prettytable >= 0.7.0', 'docopt == 0.6.1', 'requests', diff --git a/tools/requirements.txt b/tools/requirements.txt index a20851709..ef915c8bb 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,4 +1,4 @@ requests prettytable >= 0.7.0 docopt == 0.6.1 -six >= 1.1.0 +six >= 1.7.0 From 2ffa7b70545939addf737aa66857f0ab6b007425 Mon Sep 17 00:00:00 2001 From: chechu Date: Mon, 30 Jun 2014 16:48:40 +0100 Subject: [PATCH 0040/2667] added info on bmi and vsi (owner field) --- SoftLayer/CLI/modules/server.py | 6 ++++-- SoftLayer/CLI/modules/vs.py | 6 +++++- SoftLayer/managers/hardware.py | 2 ++ SoftLayer/managers/vs.py | 2 ++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index b2e16852e..e5d5451bc 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -86,7 +86,8 @@ def execute(self, args): 'memory', 'primary_ip', 'backend_ip', - 'active_transaction' + 'active_transaction', + 'owner' ]) table.sortby = args.get('--sortby') or 'host' @@ -101,6 +102,7 @@ def execute(self, args): server['primaryIpAddress'] or blank(), server['primaryBackendIpAddress'] or blank(), active_txn(server), + server['billingItem']['orderItem']['order']['userRecord']['username'], ]) return table @@ -150,7 +152,7 @@ def execute(self, args): ['softwareDescription']['name'] or blank() )]) table.add_row(['created', result['provisionDate'] or blank()]) - + table.add_row(['owner', result ['billingItem']['orderItem']['order']['userRecord']['username']]) vlan_table = Table(['type', 'number', 'id']) for vlan in result['networkVlans']: vlan_table.add_row([ diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index d1e1a356e..db1f01d0a 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -95,7 +95,7 @@ def execute(self, args): table = Table([ 'id', 'datacenter', 'host', 'cores', 'memory', 'primary_ip', - 'backend_ip', 'active_transaction', + 'backend_ip', 'active_transaction','owner' ]) table.sortby = args.get('--sortby') or 'host' @@ -110,6 +110,7 @@ def execute(self, args): guest['primaryIpAddress'] or blank(), guest['primaryBackendIpAddress'] or blank(), active_txn(guest), + guest['billingItem']['orderItem']['order']['userRecord']['username'] ]) return table @@ -170,6 +171,9 @@ def execute(self, args): table.add_row(['private_cpu', result['dedicatedAccountHostOnlyFlag']]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) + table.add_row(['owner', FormattedItem( + lookup(result, 'billingItem', 'orderItem','order','userRecord','username'), + )]) vlan_table = Table(['type', 'number', 'id']) for vlan in result['networkVlans']: diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 9598d8ec3..cb01481fb 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -120,6 +120,7 @@ def list_hardware(self, tags=None, cpus=None, memory=None, hostname=None, 'primaryBackendIpAddress', 'primaryIpAddress', 'datacenter', + 'billingItem.orderItem.order.userRecord[username]', ] server_items = [ 'activeTransaction[id, transactionStatus[friendlyName,name]]', @@ -291,6 +292,7 @@ def get_hardware(self, hardware_id, **kwargs): 'hourlyBillingFlag', 'tagReferences[id,tag[name,id]]', 'networkVlans[id,vlanNumber,networkSpace]', + 'billingItem.orderItem.order.userRecord[username]', ] kwargs['mask'] = "mask[%s]" % ','.join(items) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 039d9460a..1eb325cc7 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -86,6 +86,7 @@ def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, 'datacenter', 'activeTransaction.transactionStatus[friendlyName,name]', 'status', + 'billingItem.orderItem.order.userRecord[username]' ] kwargs['mask'] = "mask[%s]" % ','.join(items) @@ -199,6 +200,7 @@ def get_instance(self, instance_id, **kwargs): 'billingItem.recurringFee', 'tagReferences[id,tag[name,id]]', 'networkVlans[id,vlanNumber,networkSpace]', + 'billingItem.orderItem.order.userRecord[username]' ] kwargs['mask'] = "mask[%s]" % ','.join(items) From 5709a7d35f97cfdecfe1430f6a0d9890a419f8a1 Mon Sep 17 00:00:00 2001 From: chechu Date: Mon, 7 Jul 2014 12:02:21 +0100 Subject: [PATCH 0041/2667] tox passed --- SoftLayer/CLI/modules/server.py | 8 ++++++-- SoftLayer/CLI/modules/vs.py | 7 ++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index e5d5451bc..9574a3d63 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -102,7 +102,9 @@ def execute(self, args): server['primaryIpAddress'] or blank(), server['primaryBackendIpAddress'] or blank(), active_txn(server), - server['billingItem']['orderItem']['order']['userRecord']['username'], + server + ['billingItem']['orderItem']['order']['userRecord']['username'] + or blank(), ]) return table @@ -152,7 +154,9 @@ def execute(self, args): ['softwareDescription']['name'] or blank() )]) table.add_row(['created', result['provisionDate'] or blank()]) - table.add_row(['owner', result ['billingItem']['orderItem']['order']['userRecord']['username']]) + table.add_row(['owner', + result['billingItem']['orderItem']['order'] + ['userRecord']['username'] or blank()]) vlan_table = Table(['type', 'number', 'id']) for vlan in result['networkVlans']: vlan_table.add_row([ diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index db1f01d0a..365be8c48 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -95,7 +95,7 @@ def execute(self, args): table = Table([ 'id', 'datacenter', 'host', 'cores', 'memory', 'primary_ip', - 'backend_ip', 'active_transaction','owner' + 'backend_ip', 'active_transaction', 'owner' ]) table.sortby = args.get('--sortby') or 'host' @@ -110,7 +110,8 @@ def execute(self, args): guest['primaryIpAddress'] or blank(), guest['primaryBackendIpAddress'] or blank(), active_txn(guest), - guest['billingItem']['orderItem']['order']['userRecord']['username'] + guest['billingItem']['orderItem']['order'] + ['userRecord']['username'] ]) return table @@ -172,7 +173,7 @@ def execute(self, args): table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) table.add_row(['owner', FormattedItem( - lookup(result, 'billingItem', 'orderItem','order','userRecord','username'), + lookup(result, 'billingItem', 'orderItem', 'order', 'userRecord', 'username'), )]) vlan_table = Table(['type', 'number', 'id']) From 7d5172357a24fd94443a047bdeeab57f893d93f9 Mon Sep 17 00:00:00 2001 From: chechu Date: Tue, 8 Jul 2014 17:08:53 +0100 Subject: [PATCH 0042/2667] error on orderItem --- SoftLayer/CLI/modules/server.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 9574a3d63..122725171 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -93,6 +93,10 @@ def execute(self, args): for server in servers: server = NestedDict(server) + if 'billingItem' in server: + if 'orderItem' in server['billingItem']: + user = (server['billingItem']['orderItem']['order'] + ['userRecord']['username']) table.add_row([ server['id'], server['datacenter']['name'] or blank(), @@ -102,9 +106,7 @@ def execute(self, args): server['primaryIpAddress'] or blank(), server['primaryBackendIpAddress'] or blank(), active_txn(server), - server - ['billingItem']['orderItem']['order']['userRecord']['username'] - or blank(), + user or blank(), ]) return table @@ -154,9 +156,12 @@ def execute(self, args): ['softwareDescription']['name'] or blank() )]) table.add_row(['created', result['provisionDate'] or blank()]) + if 'billingItem' in result: + if 'orderItem' in result['billingItem']: + user = (result['billingItem']['orderItem']['order'] + ['userRecord']['username']) table.add_row(['owner', - result['billingItem']['orderItem']['order'] - ['userRecord']['username'] or blank()]) + user or blank()]) vlan_table = Table(['type', 'number', 'id']) for vlan in result['networkVlans']: vlan_table.add_row([ From d90770d185c28742ee750a732aa3749ff58ff158 Mon Sep 17 00:00:00 2001 From: underscorephil Date: Tue, 8 Jul 2014 12:46:47 -0500 Subject: [PATCH 0043/2667] Add contrib info and CLAs --- CONTRIBUTING.md | 17 ++++++ docs/cla-corporate.md | 134 +++++++++++++++++++++++++++++++++++++++++ docs/cla-individual.md | 85 ++++++++++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 docs/cla-corporate.md create mode 100644 docs/cla-individual.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..1d18c35ec --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,17 @@ +# Contributing to softlayer-python + +We are happy to accept contributions to softlayer-python. Please follow the +guidelines below. + +* Sign our contributor agreement (CLA) You can find the [CLA +* here](./docs/cla-individual.md). + +* If you're contributing on behalf of your employer we'll need a signed copy of +* our corporate contributor agreement (CCLA) as well. You can find the [CCLA +* here](./docs/cla-corporate.md). + +* Fork the repo, make your changes, and open a pull request. + +* Additional infomration can be found in our [contribution guide](http://softlayer-python.readthedocs.org/en/latest/dev/index.html) + + diff --git a/docs/cla-corporate.md b/docs/cla-corporate.md new file mode 100644 index 000000000..7a939f0b2 --- /dev/null +++ b/docs/cla-corporate.md @@ -0,0 +1,134 @@ +#### International Business machines, Inc. +#####Software Grant and Corporate Contributor License Agreement ("Agreement") + +http://www.github.org/softlayer/softlayer-python/ + + +Thank you for your interest in IBM’s softlayer-python project (“the +Project"). In order to clarify the intellectual property license granted with Contributions from any person or entity, IBM must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms +below. This license is for your protection as a Contributor as well +as the protection of IBM and its users; it does not change your rights to use your own Contributions for any other purpose. + +This version of the Agreement allows an entity (the "Corporation") to submit Contributions to the Project, to authorize Contributions +submitted by its designated employees to the Project, and to grant +copyright and patent licenses thereto + +If you have not already done so, please complete and sign, then scan and email a pdf file of this Agreement to pjackson@softlayer.com. + + + +Please read this document carefully before signing and keep a copy for your records. + +Corporation name: ________________________________________________ + +Corporation address: ________________________________________________ + +Point of Contact: ________________________________________________ + +E-Mail: ________________________________________________ + +Telephone: _____________________ + + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to the Project. Except +for the license granted herein to IBM and recipients of software distributed by IBM, You reserve all right, title, and interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with IBM. For legal entities, the entity making a Contribution and + all other entities that control, are controlled by, or are under + common control with that entity are considered to be a single + Contributor. For the purposes of this definition, "control" means + (i) the power, direct or indirect, to cause the direction or + management of such entity, whether by contract or otherwise, or + (ii) ownership of fifty percent (50%) or more of the outstanding + shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean the code, documentation or other original + works of authorship expressly identified in Schedule B, as well as + any original work of authorship, including any modifications or +additions to an existing work, that is intentionally submitted by You to IBM for inclusion in, or documentation of, the Project managed by IBM (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to IBM or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, IBM for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions + of this Agreement, You hereby grant to IBM and to + recipients of software distributed by IBM a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute + Your Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to IBM and to recipients + of software distributed by IBM a perpetual, worldwide, + non-exclusive, no-charge, royalty-free, irrevocable (except as + stated in this section) patent license to make, have made, use, + offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by You that are necessarily infringed by Your Contribution(s) + alone or by combination of Your Contribution(s) with the Work to + which such Contribution(s) were submitted. If any entity institutes + patent litigation against You or any other entity (including a + cross-claim or counterclaim in a lawsuit) alleging that your + Contribution, or the Work to which you have contributed, constitutes + direct or contributory patent infringement, then any patent licenses + granted to that entity under this Agreement for that Contribution or + Work shall terminate as of the date such litigation is filed. + +4. You represent that You are legally entitled to grant the above + license. You represent further that each employee of the + Corporation designated on Schedule A below (or in a subsequent + written modification to that Schedule) is authorized to submit + Contributions on behalf of the Corporation. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, + MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to IBM separately from any + Contribution, identifying the complete details of its source and + of any license or other restriction (including, but not limited + to, related patents, trademarks, and license agreements) of which + you are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. It is your responsibility to notify IBM when any change + is required to the list of designated employees authorized to submit + Contributions on behalf of the Corporation, or to the Corporation's + Point of Contact with IBM. + + + +Please sign: __________________________________ Date: _______________ + +Title: __________________________________ + +Corporation: __________________________________ + + +Schedule A + + [Initial list of designated employees. NB: authorization is not + tied to particular Contributions.] + + + + +Schedule B + + [Identification of optional concurrent software grant. Would be + left blank or omitted if there is no concurrent software grant.] + + + diff --git a/docs/cla-individual.md b/docs/cla-individual.md new file mode 100644 index 000000000..95e6299ed --- /dev/null +++ b/docs/cla-individual.md @@ -0,0 +1,85 @@ +#### International Business Machines, Inc. (IBM) +#####Individual Contributor License Agreement ("Agreement") + +http://www.github.com/softlayer/softlayer-python + +Thank you for your interest in the softlayer-python project ("the Project"). + +In order to clarify the intellectual property license granted with Contributions from any person or entity, IBM must have a Contributor License Agreement ("CLA") on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of IBM and its customers; it does not change your rights to use your own Contributions for any other purpose. + +If you have not already done so, please complete and sign, then scan and email a pdf file of this Agreement to pjackson@softlayer.com + +Please read this document carefully before signing and keep a copy for your records. + + Full name: ______________________________________________________ + + (optional) Public name: _________________________________________ + + Mailing Address: ________________________________________________ + + Country: ______________________________________________________ + + Telephone: ______________________________________________________ + + E-Mail: ______________________________________________________ + + +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to the Project. Except for the license granted herein to IBM and recipients of software distributed by IBM, You reserve all right, title, and interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement +with IBM. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to the Project for inclusion +in, or documentation of, the Project (”the Work”). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Project or its representatives,including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of +this Agreement, You hereby grant to IBM and to recipients of software distributed by IBM a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of +this Agreement, You hereby grant to IBM and to recipients of software distributed by IBM a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work to which Your Contribution(s) were submitted, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to the Project, or that your employer has + executed a separate Corporate CLA with IBM. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to the Project separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify IBM of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Please sign: __________________________________ Date: ________________ + + From 6385978bfa30b74c5b9bb77ba6ad3e5f0fbd36c6 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 8 Jul 2014 12:49:28 -0500 Subject: [PATCH 0044/2667] Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d18c35ec..8b54b2cc4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,12 +3,10 @@ We are happy to accept contributions to softlayer-python. Please follow the guidelines below. -* Sign our contributor agreement (CLA) You can find the [CLA -* here](./docs/cla-individual.md). +* Sign our contributor agreement (CLA) You can find the [CLA here](./docs/cla-individual.md). * If you're contributing on behalf of your employer we'll need a signed copy of -* our corporate contributor agreement (CCLA) as well. You can find the [CCLA -* here](./docs/cla-corporate.md). +* our corporate contributor agreement (CCLA) as well. You can find the [CCLA here](./docs/cla-corporate.md). * Fork the repo, make your changes, and open a pull request. From a1bda439171e42cfbc8ba40f27ca9446a08975c3 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 8 Jul 2014 12:57:20 -0500 Subject: [PATCH 0045/2667] Update CONTRIBUTING.md format fixes --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b54b2cc4..7a82eb151 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,8 +5,7 @@ guidelines below. * Sign our contributor agreement (CLA) You can find the [CLA here](./docs/cla-individual.md). -* If you're contributing on behalf of your employer we'll need a signed copy of -* our corporate contributor agreement (CCLA) as well. You can find the [CCLA here](./docs/cla-corporate.md). +* If you're contributing on behalf of your employer we'll need a signed copy of our corporate contributor agreement (CCLA) as well. You can find the [CCLA here](./docs/cla-corporate.md). * Fork the repo, make your changes, and open a pull request. From 6d77c894698123d3b68c133113f5302041b073d5 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 8 Jul 2014 13:50:33 -0500 Subject: [PATCH 0046/2667] Update cla-corporate.md line wrapping fixes --- docs/cla-corporate.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/cla-corporate.md b/docs/cla-corporate.md index 7a939f0b2..776162c05 100644 --- a/docs/cla-corporate.md +++ b/docs/cla-corporate.md @@ -4,15 +4,10 @@ http://www.github.org/softlayer/softlayer-python/ -Thank you for your interest in IBM’s softlayer-python project (“the -Project"). In order to clarify the intellectual property license granted with Contributions from any person or entity, IBM must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms -below. This license is for your protection as a Contributor as well -as the protection of IBM and its users; it does not change your rights to use your own Contributions for any other purpose. - -This version of the Agreement allows an entity (the "Corporation") to submit Contributions to the Project, to authorize Contributions -submitted by its designated employees to the Project, and to grant -copyright and patent licenses thereto - +Thank you for your interest in IBM’s softlayer-python project (“the Project"). In order to clarify the intellectual property license granted with Contributions from any person or entity, IBM must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of IBM and its users; it does not change your rights to use your own Contributions for any other purpose. + +This version of the Agreement allows an entity (the "Corporation") to submit Contributions to the Project, to authorize Contributions submitted by its designated employees to the Project, and to grant copyright and patent licenses thereto. + If you have not already done so, please complete and sign, then scan and email a pdf file of this Agreement to pjackson@softlayer.com. @@ -30,9 +25,7 @@ E-Mail: ________________________________________________ Telephone: _____________________ -You accept and agree to the following terms and conditions for Your -present and future Contributions submitted to the Project. Except -for the license granted herein to IBM and recipients of software distributed by IBM, You reserve all right, title, and interest in and to Your Contributions. +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to the Project. Except for the license granted herein to IBM and recipients of software distributed by IBM, You reserve all right, title, and interest in and to Your Contributions. 1. Definitions. @@ -50,7 +43,16 @@ for the license granted herein to IBM and recipients of software distributed by "Contribution" shall mean the code, documentation or other original works of authorship expressly identified in Schedule B, as well as any original work of authorship, including any modifications or -additions to an existing work, that is intentionally submitted by You to IBM for inclusion in, or documentation of, the Project managed by IBM (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to IBM or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, IBM for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + additions to an existing work, that is intentionally submitted by + You to IBM for inclusion in, or documentation of, the Project managed + by IBM (the "Work"). For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent to + IBM or its representatives, including but not limited to communication + on electronic mailing lists, source code control systems, and issue + tracking systems that are managed by, or on behalf of, IBM for the + purpose of discussing and improving the Work, but excluding + communication that is conspicuously marked or otherwise designated + in writing by You as "Not a Contribution." 2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to IBM and to From ae072bcfafdc2ba21f0d44b267e8fdc0e7706679 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 8 Jul 2014 13:55:19 -0500 Subject: [PATCH 0047/2667] Update cla-individual.md line wrapping fixes --- docs/cla-individual.md | 62 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/docs/cla-individual.md b/docs/cla-individual.md index 95e6299ed..86a4fff44 100644 --- a/docs/cla-individual.md +++ b/docs/cla-individual.md @@ -30,21 +30,58 @@ You accept and agree to the following terms and conditions for Your present and "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement -with IBM. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + with IBM. For legal entities, the entity making a Contribution and + all other entities that control, are controlled by, or are under + common control with that entity are considered to be a single + Contributor. For the purposes of this definition, "control" means + (i) the power, direct or indirect, to cause the direction or + management of such entity, whether by contract or otherwise, + or (ii) ownership of fifty percent (50%) or more of the outstanding + shares, or (iii) beneficial ownership of such entity. "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to the Project for inclusion -in, or documentation of, the Project (”the Work”). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Project or its representatives,including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." - -2. Grant of Copyright License. Subject to the terms and conditions of -this Agreement, You hereby grant to IBM and to recipients of software distributed by IBM a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. - -3. Grant of Patent License. Subject to the terms and conditions of -this Agreement, You hereby grant to IBM and to recipients of software distributed by IBM a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work to which Your Contribution(s) were submitted, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + in, or documentation of, the Project (”the Work”). For the purposes + of this definition, "submitted" means any form of electronic, verbal, + or written communication sent to the Project or its representatives, + including but not limited to communication on electronic mailing lists, + source code control systems, and issue tracking systems that are + managed by, or on behalf of, the Project for the purpose of discussing + and improving the Work, but excluding communication that is conspicuously + marked or otherwise designated in writing by You as "Not a Contribution." + +2. Grant of Copyright License. + + Subject to the terms and conditions of this Agreement, You hereby grant + to IBM and to recipients of software distributed by IBM a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright + license to reproduce, prepare derivative works of, publicly display, + publicly perform, sublicense, and distribute Your Contributions and + such derivative works. + +3. Grant of Patent License. + + Subject to the terms and conditions of this Agreement, You hereby grant + to IBM and to recipients of software distributed by IBM a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except + as stated in this section) patent license to make, have made, use, offer + to sell, sell, import, and otherwise transfer the Work to which Your + Contribution(s) were submitted, where such license applies only to those + patent claims licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) with the + Work to which such Contribution(s) was submitted. If any entity institutes + patent litigation against You or any other entity (including a cross-claim + or counterclaim in a lawsuit) alleging that your Contribution, or the Work + to which you have contributed, constitutes direct or contributory patent + infringement, then any patent licenses granted to that entity under this + Agreement for that Contribution or Work shall terminate as of the date + such litigation is filed. 4. You represent that you are legally entitled to grant the above - license. If your employer(s) has rights to intellectual property + license. + + If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for @@ -60,9 +97,10 @@ this Agreement, You hereby grant to IBM and to recipients of software distribute Contributions. 6. You are not expected to provide support for Your Contributions, - except to the extent You desire to provide support. You may provide - support for free, for a fee, or not at all. Unless required by - applicable law or agreed to in writing, You provide Your + except to the extent You desire to provide support. + + You may provide support for free, for a fee, or not at all. + Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- From 16b92c4d07933b876c0a0c9d5cd4111bea0e3408 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 8 Jul 2014 13:57:10 -0500 Subject: [PATCH 0048/2667] Update cla-corporate.md additional readability fixes --- docs/cla-corporate.md | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/cla-corporate.md b/docs/cla-corporate.md index 776162c05..cc5e35ffd 100644 --- a/docs/cla-corporate.md +++ b/docs/cla-corporate.md @@ -54,18 +54,22 @@ You accept and agree to the following terms and conditions for Your present and communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." -2. Grant of Copyright License. Subject to the terms and conditions - of this Agreement, You hereby grant to IBM and to - recipients of software distributed by IBM a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare derivative works of, - publicly display, publicly perform, sublicense, and distribute - Your Contributions and such derivative works. - -3. Grant of Patent License. Subject to the terms and conditions of - this Agreement, You hereby grant to IBM and to recipients - of software distributed by IBM a perpetual, worldwide, - non-exclusive, no-charge, royalty-free, irrevocable (except as +2. Grant of Copyright License. + + Subject to the terms and conditions of this Agreement, + You hereby grant to IBM and to recipients of software + distributed by IBM a perpetual, worldwide, non-exclusive, + no-charge, royalty-free, irrevocable copyright license to + reproduce, prepare derivative works of, publicly display, + publicly perform, sublicense, and distribute Your Contributions + and such derivative works. + +3. Grant of Patent License. + + Subject to the terms and conditions of this Agreement, + You hereby grant to IBM and to recipients of software + distributed by IBM a perpetual, worldwide, non-exclusive, + no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable @@ -80,9 +84,11 @@ You accept and agree to the following terms and conditions for Your present and Work shall terminate as of the date such litigation is filed. 4. You represent that You are legally entitled to grant the above - license. You represent further that each employee of the - Corporation designated on Schedule A below (or in a subsequent - written modification to that Schedule) is authorized to submit + license. + + You represent further that each employee of the Corporation + designated on Schedule A below (or in a subsequent written + modification to that Schedule) is authorized to submit Contributions on behalf of the Corporation. 5. You represent that each of Your Contributions is Your original From 489fb7956567fd5d13a9a5c73ddb6d67210e6d0b Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 8 Jul 2014 14:20:34 -0500 Subject: [PATCH 0049/2667] Fixes incorrect URL --- docs/cla-corporate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cla-corporate.md b/docs/cla-corporate.md index cc5e35ffd..9285bc432 100644 --- a/docs/cla-corporate.md +++ b/docs/cla-corporate.md @@ -1,7 +1,7 @@ #### International Business machines, Inc. #####Software Grant and Corporate Contributor License Agreement ("Agreement") -http://www.github.org/softlayer/softlayer-python/ +http://www.github.com/softlayer/softlayer-python/ Thank you for your interest in IBM’s softlayer-python project (“the Project"). In order to clarify the intellectual property license granted with Contributions from any person or entity, IBM must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of IBM and its users; it does not change your rights to use your own Contributions for any other purpose. From c9d9c363c3992998fdf8430e6b0bfaf1e58f482f Mon Sep 17 00:00:00 2001 From: chechu Date: Tue, 8 Jul 2014 20:47:52 +0100 Subject: [PATCH 0050/2667] test added but fail --- SoftLayer/CLI/modules/server.py | 2 + SoftLayer/tests/CLI/modules/server_tests.py | 3 +- SoftLayer/tests/CLI/modules/vs_tests.py | 9 ++-- SoftLayer/tests/fixtures/Account.py | 49 +++++++++++++++++++-- SoftLayer/tests/fixtures/Hardware_Server.py | 10 ++++- SoftLayer/tests/fixtures/Virtual_Guest.py | 9 ++++ 6 files changed, 74 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 122725171..cf83c812f 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -93,6 +93,7 @@ def execute(self, args): for server in servers: server = NestedDict(server) + user = None if 'billingItem' in server: if 'orderItem' in server['billingItem']: user = (server['billingItem']['orderItem']['order'] @@ -156,6 +157,7 @@ def execute(self, args): ['softwareDescription']['name'] or blank() )]) table.add_row(['created', result['provisionDate'] or blank()]) + user = None if 'billingItem' in result: if 'orderItem' in result['billingItem']: user = (result['billingItem']['orderItem']['order'] diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index 7186914d6..a5d602f41 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -228,7 +228,8 @@ def test_server_details(self): 'tags': ['test_tag'], 'users': ['root abc123'], 'vlans': [{'id': 9653, 'number': 1800, 'type': 'PRIVATE'}, - {'id': 19082, 'number': 3672, 'type': 'PUBLIC'}] + {'id': 19082, 'number': 3672, 'type': 'PUBLIC'}], + 'owner': 'chechu' } self.assertEqual(expected, format_output(output, 'python')) diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index 96bd59cf6..f09561c6c 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -26,7 +26,8 @@ def test_list_vs(self): 'cores': 2, 'active_transaction': None, 'id': 100, - 'backend_ip': '10.45.19.37'}, + 'backend_ip': '10.45.19.37', + 'owner': 'chechu' }, {'datacenter': 'TEST00', 'primary_ip': '172.16.240.7', 'host': 'vs-test2.test.sftlyr.ws', @@ -34,7 +35,8 @@ def test_list_vs(self): 'cores': 4, 'active_transaction': None, 'id': 104, - 'backend_ip': '10.45.19.35'}], + 'backend_ip': '10.45.19.35', + 'owner': 'chechu' }], format_output(output, 'python')) def test_detail_vs(self): @@ -67,7 +69,8 @@ def test_detail_vs(self): 'users': [{'password': 'pass', 'username': 'user'}], 'vlans': [{'type': 'PUBLIC', 'number': 23, - 'id': 1}]}, + 'id': 1}], + 'owner': 'chechu'}, format_output(output, 'python')) def test_create_options(self): diff --git a/SoftLayer/tests/fixtures/Account.py b/SoftLayer/tests/fixtures/Account.py index 1c8d8ebae..1de74d1bf 100644 --- a/SoftLayer/tests/fixtures/Account.py +++ b/SoftLayer/tests/fixtures/Account.py @@ -33,6 +33,16 @@ 'globalIdentifier': '1a2b3c-1701', 'primaryBackendIpAddress': '10.45.19.37', 'hourlyBillingFlag': False, + + 'billingItem': {'id': 6327, 'recurringFee': 1.54, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, }, { 'id': 104, 'hostname': 'vs-test2', @@ -49,6 +59,15 @@ 'globalIdentifier': '05a8ac-6abf0', 'primaryBackendIpAddress': '10.45.19.35', 'hourlyBillingFlag': True, + 'billingItem': {'id': 6327, 'recurringFee': 1.54, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, }] getMonthlyVirtualGuests = [vs for vs in getVirtualGuests @@ -61,7 +80,15 @@ 'id': 1000, 'datacenter': {'id': 50, 'name': 'TEST00', 'description': 'Test Data Center'}, - 'billingItem': {'id': 6327, 'recurringFee': 1.54}, + 'billingItem': {'id': 6327, 'recurringFee': 1.54, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, 'primaryIpAddress': '172.16.1.100', 'hostname': 'hardware-test1', 'domain': 'test.sftlyr.ws', @@ -110,7 +137,15 @@ 'id': 1001, 'datacenter': {'name': 'TEST00', 'description': 'Test Data Center'}, - 'billingItem': {'id': 7112}, + 'billingItem': {'id': 7112, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, 'primaryIpAddress': '172.16.4.94', 'hostname': 'hardware-test2', 'domain': 'test.sftlyr.ws', @@ -144,7 +179,15 @@ 'id': 1002, 'datacenter': {'name': 'TEST00', 'description': 'Test Data Center'}, - 'billingItem': {'id': 7112}, + 'billingItem': {'id': 7112, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, 'primaryIpAddress': '172.16.4.95', 'hostname': 'hardware-bad-memory', 'domain': 'test.sftlyr.ws', diff --git a/SoftLayer/tests/fixtures/Hardware_Server.py b/SoftLayer/tests/fixtures/Hardware_Server.py index 9e4f69325..71e4626e6 100644 --- a/SoftLayer/tests/fixtures/Hardware_Server.py +++ b/SoftLayer/tests/fixtures/Hardware_Server.py @@ -2,7 +2,15 @@ 'id': 1000, 'datacenter': {'id': 50, 'name': 'TEST00', 'description': 'Test Data Center'}, - 'billingItem': {'id': 6327, 'recurringFee': 1.54}, + 'billingItem': {'id': 6327, 'recurringFee': 1.54, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, 'primaryIpAddress': '172.16.1.100', 'hostname': 'hardware-test1', 'domain': 'test.sftlyr.ws', diff --git a/SoftLayer/tests/fixtures/Virtual_Guest.py b/SoftLayer/tests/fixtures/Virtual_Guest.py index ce24df919..be74621bc 100644 --- a/SoftLayer/tests/fixtures/Virtual_Guest.py +++ b/SoftLayer/tests/fixtures/Virtual_Guest.py @@ -4,6 +4,15 @@ 'domain': 'test.sftlyr.ws', 'fullyQualifiedDomainName': 'vs-test1.test.sftlyr.ws', 'status': {'keyName': 'ACTIVE', 'name': 'Active'}, + 'billingItem': {'id': 6327, 'recurringFee': 1.54, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, 'datacenter': {'id': 50, 'name': 'TEST00', 'description': 'Test Data Center'}, 'powerState': {'keyName': 'RUNNING', 'name': 'Running'}, From 8bbd902bbdceeb2f216bef3d72eb332faed804c6 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 8 Jul 2014 22:17:38 -0500 Subject: [PATCH 0051/2667] Adds hacking to pep8 tox tests. Fixes many errors --- SoftLayer/API.py | 74 ++-- SoftLayer/CLI/core.py | 72 ++-- SoftLayer/CLI/environment.py | 72 ++-- SoftLayer/CLI/exceptions.py | 25 +- SoftLayer/CLI/formatting.py | 104 +++-- SoftLayer/CLI/helpers.py | 32 +- SoftLayer/CLI/modules/__init__.py | 6 +- SoftLayer/CLI/modules/cdn.py | 50 ++- SoftLayer/CLI/modules/config.py | 71 ++-- SoftLayer/CLI/modules/dns.py | 70 ++-- SoftLayer/CLI/modules/firewall.py | 99 ++--- SoftLayer/CLI/modules/globalip.py | 61 +-- SoftLayer/CLI/modules/help.py | 8 +- SoftLayer/CLI/modules/image.py | 62 +-- SoftLayer/CLI/modules/iscsi.py | 63 +-- SoftLayer/CLI/modules/loadbal.py | 149 +++---- SoftLayer/CLI/modules/messaging.py | 103 ++--- SoftLayer/CLI/modules/metadata.py | 64 +-- SoftLayer/CLI/modules/nas.py | 31 +- SoftLayer/CLI/modules/rwhois.py | 20 +- SoftLayer/CLI/modules/server.py | 268 +++++++------ SoftLayer/CLI/modules/snapshot.py | 79 ++-- SoftLayer/CLI/modules/sshkey.py | 59 +-- SoftLayer/CLI/modules/ssl.py | 48 ++- SoftLayer/CLI/modules/subnet.py | 81 ++-- SoftLayer/CLI/modules/summary.py | 11 +- SoftLayer/CLI/modules/ticket.py | 62 +-- SoftLayer/CLI/modules/vlan.py | 34 +- SoftLayer/CLI/modules/vs.py | 376 ++++++++++-------- SoftLayer/CLI/template.py | 14 +- SoftLayer/__init__.py | 12 +- SoftLayer/auth.py | 13 +- SoftLayer/config.py | 28 +- SoftLayer/exceptions.py | 41 +- SoftLayer/managers/__init__.py | 35 +- SoftLayer/managers/cci.py | 5 +- SoftLayer/managers/cdn.py | 13 +- SoftLayer/managers/dns.py | 20 +- SoftLayer/managers/firewall.py | 18 +- SoftLayer/managers/hardware.py | 46 +-- SoftLayer/managers/image.py | 20 +- SoftLayer/managers/iscsi.py | 4 +- SoftLayer/managers/load_balancer.py | 40 +- SoftLayer/managers/messaging.py | 19 +- SoftLayer/managers/metadata.py | 21 +- SoftLayer/managers/network.py | 46 ++- SoftLayer/managers/ordering.py | 4 +- SoftLayer/managers/sshkey.py | 8 +- SoftLayer/managers/ticket.py | 4 +- SoftLayer/managers/vs.py | 70 ++-- SoftLayer/testing/__init__.py | 38 ++ .../{tests => testing}/fixture_client.py | 25 +- .../{tests => testing}/fixtures/Account.py | 3 - .../fixtures/Billing_Item.py | 0 .../{tests => testing}/fixtures/Dns_Domain.py | 0 .../fixtures/Dns_Domain_ResourceRecord.py | 0 .../fixtures/Hardware_Server.py | 0 .../{tests => testing}/fixtures/Location.py | 0 .../fixtures/Location_Datacenter.py | 0 ...ntroller_LoadBalancer_Health_Check_Type.py | 0 ..._Controller_LoadBalancer_Routing_Method.py | 0 ...ry_Controller_LoadBalancer_Routing_Type.py | 0 ...elivery_Controller_LoadBalancer_Service.py | 0 ...y_Controller_LoadBalancer_Service_Group.py | 0 ...ontroller_LoadBalancer_VirtualIpAddress.py | 0 ...y_Controller_LoadBalancer_VirtualServer.py | 0 .../fixtures/Network_Component_Firewall.py | 0 .../Network_ContentDelivery_Account.py | 0 .../Network_Firewall_Update_Request.py | 4 +- .../fixtures/Network_Storage_Iscsi.py | 14 +- .../fixtures/Network_Subnet.py | 0 .../fixtures/Network_Subnet_IpAddress.py | 0 .../Network_Subnet_IpAddress_Global.py | 0 .../fixtures/Network_Subnet_Rwhois_Data.py | 0 .../fixtures/Network_Vlan.py | 0 .../fixtures/Network_Vlan_Firewall.py | 4 +- .../fixtures/Product_Order.py | 0 .../fixtures/Product_Package.py | 46 +-- .../fixtures/Security_Certificate.py | 0 .../fixtures/Security_Ssh_Key.py | 0 .../{tests => testing}/fixtures/Ticket.py | 0 .../fixtures/Ticket_Subject.py | 0 .../fixtures/User_Customer.py | 0 .../fixtures/Virtual_Guest.py | 4 +- ...rtual_Guest_Block_Device_Template_Group.py | 0 .../{tests => testing}/fixtures/__init__.py | 0 .../{tests => testing}/fixtures/empty.conf | 0 .../{tests => testing}/fixtures/full.conf | 0 .../{tests => testing}/fixtures/id_rsa.pub | 0 .../fixtures/no_options.conf | 0 .../fixtures/sample_vs_template.conf | 0 SoftLayer/tests/CLI/core_tests.py | 39 +- SoftLayer/tests/CLI/environment_tests.py | 25 +- SoftLayer/tests/CLI/helper_tests.py | 225 ++++++----- SoftLayer/tests/CLI/modules/cdn_tests.py | 14 +- SoftLayer/tests/CLI/modules/config_tests.py | 39 +- SoftLayer/tests/CLI/modules/dns_tests.py | 41 +- SoftLayer/tests/CLI/modules/firewall_tests.py | 11 +- SoftLayer/tests/CLI/modules/globalip_tests.py | 20 +- SoftLayer/tests/CLI/modules/help_tests.py | 8 +- SoftLayer/tests/CLI/modules/import_tests.py | 14 +- SoftLayer/tests/CLI/modules/nas_tests.py | 10 +- SoftLayer/tests/CLI/modules/rwhois_tests.py | 14 +- SoftLayer/tests/CLI/modules/server_tests.py | 170 ++++---- SoftLayer/tests/CLI/modules/sshkey_tests.py | 28 +- SoftLayer/tests/CLI/modules/summary_tests.py | 15 +- SoftLayer/tests/CLI/modules/vs_tests.py | 20 +- SoftLayer/tests/__init__.py | 31 -- SoftLayer/tests/api_tests.py | 122 +++--- SoftLayer/tests/auth_tests.py | 31 +- SoftLayer/tests/basic_tests.py | 32 +- SoftLayer/tests/config_tests.py | 79 ++-- SoftLayer/tests/functional_tests.py | 10 +- SoftLayer/tests/managers/cci_tests.py | 14 +- SoftLayer/tests/managers/cdn_tests.py | 39 +- SoftLayer/tests/managers/dns_tests.py | 24 +- SoftLayer/tests/managers/firewall_tests.py | 46 +-- SoftLayer/tests/managers/hardware_tests.py | 75 ++-- SoftLayer/tests/managers/image_tests.py | 54 +-- SoftLayer/tests/managers/iscsi_tests.py | 23 +- SoftLayer/tests/managers/loadbal_tests.py | 18 +- SoftLayer/tests/managers/metadata_tests.py | 41 +- SoftLayer/tests/managers/network_tests.py | 49 +-- SoftLayer/tests/managers/ordering_tests.py | 10 +- SoftLayer/tests/managers/queue_tests.py | 130 +++--- SoftLayer/tests/managers/sshkey_tests.py | 21 +- SoftLayer/tests/managers/ssl_tests.py | 18 +- SoftLayer/tests/managers/ticket_tests.py | 21 +- SoftLayer/tests/managers/vs_tests.py | 73 ++-- SoftLayer/tests/transport_tests.py | 86 ++-- SoftLayer/transports.py | 60 +-- SoftLayer/utils.py | 42 +- setup.py | 1 + tox.ini | 18 +- 134 files changed, 2487 insertions(+), 2247 deletions(-) create mode 100644 SoftLayer/testing/__init__.py rename SoftLayer/{tests => testing}/fixture_client.py (66%) rename SoftLayer/{tests => testing}/fixtures/Account.py (98%) rename SoftLayer/{tests => testing}/fixtures/Billing_Item.py (100%) rename SoftLayer/{tests => testing}/fixtures/Dns_Domain.py (100%) rename SoftLayer/{tests => testing}/fixtures/Dns_Domain_ResourceRecord.py (100%) rename SoftLayer/{tests => testing}/fixtures/Hardware_Server.py (100%) rename SoftLayer/{tests => testing}/fixtures/Location.py (100%) rename SoftLayer/{tests => testing}/fixtures/Location_Datacenter.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_Health_Check_Type.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_Routing_Method.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_Routing_Type.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_Service.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_Service_Group.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_VirtualIpAddress.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_VirtualServer.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Component_Firewall.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_ContentDelivery_Account.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Firewall_Update_Request.py (80%) rename SoftLayer/{tests => testing}/fixtures/Network_Storage_Iscsi.py (81%) rename SoftLayer/{tests => testing}/fixtures/Network_Subnet.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Subnet_IpAddress.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Subnet_IpAddress_Global.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Subnet_Rwhois_Data.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Vlan.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Vlan_Firewall.py (98%) rename SoftLayer/{tests => testing}/fixtures/Product_Order.py (100%) rename SoftLayer/{tests => testing}/fixtures/Product_Package.py (96%) rename SoftLayer/{tests => testing}/fixtures/Security_Certificate.py (100%) rename SoftLayer/{tests => testing}/fixtures/Security_Ssh_Key.py (100%) rename SoftLayer/{tests => testing}/fixtures/Ticket.py (100%) rename SoftLayer/{tests => testing}/fixtures/Ticket_Subject.py (100%) rename SoftLayer/{tests => testing}/fixtures/User_Customer.py (100%) rename SoftLayer/{tests => testing}/fixtures/Virtual_Guest.py (98%) rename SoftLayer/{tests => testing}/fixtures/Virtual_Guest_Block_Device_Template_Group.py (100%) rename SoftLayer/{tests => testing}/fixtures/__init__.py (100%) rename SoftLayer/{tests => testing}/fixtures/empty.conf (100%) rename SoftLayer/{tests => testing}/fixtures/full.conf (100%) rename SoftLayer/{tests => testing}/fixtures/id_rsa.pub (100%) rename SoftLayer/{tests => testing}/fixtures/no_options.conf (100%) rename SoftLayer/{tests => testing}/fixtures/sample_vs_template.conf (100%) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 5bd2fdd28..0f1952882 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -7,12 +7,13 @@ """ import time -from .consts import API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT, USER_AGENT -from .transports import make_xml_rpc_api_call -from .auth import TokenAuthentication -from .config import get_client_settings - +from SoftLayer import auth as slauth +from SoftLayer import config +from SoftLayer import consts +from SoftLayer import transports +API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT +API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT __all__ = ['Client', 'TimedClient', 'API_PUBLIC_ENDPOINT', 'API_PRIVATE_ENDPOINT'] @@ -29,7 +30,7 @@ class Client(object): - """ A SoftLayer API client. + """A SoftLayer API client. :param username: an optional API username if you wish to bypass the package's built-in username @@ -58,13 +59,13 @@ class Client(object): def __init__(self, username=None, api_key=None, endpoint_url=None, timeout=None, auth=None, config_file=None, proxy=None): - settings = get_client_settings(username=username, - api_key=api_key, - endpoint_url=endpoint_url, - timeout=timeout, - auth=auth, - proxy=proxy, - config_file=config_file) + settings = config.get_client_settings(username=username, + api_key=api_key, + endpoint_url=endpoint_url, + timeout=timeout, + auth=auth, + proxy=proxy, + config_file=config_file) self.auth = settings.get('auth') self.endpoint_url = ( settings.get('endpoint_url') or API_PUBLIC_ENDPOINT).rstrip('/') @@ -78,8 +79,7 @@ def __init__(self, username=None, api_key=None, endpoint_url=None, def authenticate_with_password(self, username, password, security_question_id=None, security_question_answer=None): - """ Performs Username/Password Authentication and gives back an auth - handler to use to create a client that uses token-based auth. + """Performs Username/Password Authentication :param string username: your SoftLayer username :param string password: your SoftLayer password @@ -94,11 +94,11 @@ def authenticate_with_password(self, username, password, password, security_question_id, security_question_answer) - self.auth = TokenAuthentication(res['userId'], res['hash']) + self.auth = slauth.TokenAuthentication(res['userId'], res['hash']) return res['userId'], res['hash'] def __getitem__(self, name): - """ Get a SoftLayer Service. + """Get a SoftLayer Service. :param name: The name of the service. E.G. Account @@ -112,7 +112,7 @@ def __getitem__(self, name): return Service(self, name) def call(self, service, method, *args, **kwargs): - """ Make a SoftLayer API call + """Make a SoftLayer API call :param service: the name of the SoftLayer API service :param method: the method to call on the service @@ -162,7 +162,7 @@ def call(self, service, method, *args, **kwargs): } http_headers = { - 'User-Agent': USER_AGENT, + 'User-Agent': consts.USER_AGENT, 'Content-Type': 'application/xml', } @@ -174,17 +174,17 @@ def call(self, service, method, *args, **kwargs): http_headers.update(kwargs.get('raw_headers')) uri = '/'.join([self.endpoint_url, service]) - return make_xml_rpc_api_call(uri, method, args, - headers=headers, - http_headers=http_headers, - timeout=self.timeout, - proxy=self.proxy) + return transports.make_xml_rpc_api_call(uri, method, args, + headers=headers, + http_headers=http_headers, + timeout=self.timeout, + proxy=self.proxy) __call__ = call def iter_call(self, service, method, chunk=100, limit=None, offset=0, *args, **kwargs): - """ A generator that deals with paginating through results. + """A generator that deals with paginating through results. :param service: the name of the SoftLayer API service :param method: the method to call on the service @@ -234,7 +234,7 @@ def iter_call(self, service, method, break def __format_object_mask(self, objectmask, service): - """ Format new and old style object masks into proper headers. + """Format new and old style object masks into proper headers. :param objectmask: a string- or dict-based object mask :param service: a SoftLayer API service name @@ -246,15 +246,15 @@ def __format_object_mask(self, objectmask, service): mheader = self._prefix + 'ObjectMask' objectmask = objectmask.strip() - if not objectmask.startswith('mask') \ - and not objectmask.startswith('['): + if (not objectmask.startswith('mask') + and not objectmask.startswith('[')): objectmask = "mask[%s]" % objectmask return {mheader: {'mask': objectmask}} def __repr__(self): - return "" \ - % (self.endpoint_url, self.auth) + return "" % (self.endpoint_url, + self.auth) __str__ = __repr__ @@ -263,11 +263,12 @@ def __len__(self): class TimedClient(Client): - """ Subclass of Client() + """Client that records API call timings. Using this class will time every call to the API and store it in an internal list. This will have a slight impact on your client's memory usage and performance. You should only use this for debugging. + """ def __init__(self, *args, **kwargs): @@ -275,7 +276,7 @@ def __init__(self, *args, **kwargs): super(TimedClient, self).__init__(*args, **kwargs) def call(self, service, method, *args, **kwargs): - """ See Client.call for documentation. """ + """See Client.call for documentation.""" start_time = time.time() result = super(TimedClient, self).call(service, method, *args, **kwargs) @@ -285,7 +286,7 @@ def call(self, service, method, *args, **kwargs): return result def get_last_calls(self): - """ Retrieves the last_calls property. + """Retrieves the last_calls property. This property will contain a list of tuples in the form ('SERVICE.METHOD', initiated_utc_timestamp, execution_time) @@ -296,7 +297,8 @@ def get_last_calls(self): class Service(object): - """ A SoftLayer Service. + """A SoftLayer Service. + :param client: A SoftLayer.API.Client instance :param name str: The service name @@ -306,7 +308,7 @@ def __init__(self, client, name): self.name = name def call(self, name, *args, **kwargs): - """ Make a SoftLayer API call + """Make a SoftLayer API call. :param method: the method to call on the service :param \\*args: (optional) arguments for the remote call @@ -333,7 +335,7 @@ def call(self, name, *args, **kwargs): __call__ = call def iter_call(self, name, *args, **kwargs): - """ A generator that deals with paginating through results. + """A generator that deals with paginating through results. :param method: the method to call on the service :param integer chunk: result size for each API call diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index c2e5f7fc4..10dc31fed 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -44,15 +44,16 @@ """ # :license: MIT, see LICENSE for more details. -import sys import logging +import sys -from docopt import docopt, DocoptExit +import docopt -from SoftLayer import Client, TimedClient, SoftLayerError, SoftLayerAPIError -from SoftLayer.consts import VERSION -from .helpers import CLIAbort, ArgumentError, format_output, KeyValueTable -from .environment import Environment, InvalidCommand, InvalidModule +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer import consts DEBUG_LOGGING_MAP = { @@ -66,7 +67,7 @@ class CommandParser(object): - """ Helper class to parse commands + """Helper class to parse commands. :param env: Environment instance """ @@ -74,17 +75,17 @@ def __init__(self, env): self.env = env def get_main_help(self): - """ Get main help text """ + """Get main help text.""" return __doc__.strip() def get_module_help(self, module_name): - """ Get help text for a module """ + """Get help text for a module.""" module = self.env.load_module(module_name) arg_doc = module.__doc__ return arg_doc.strip() def get_command_help(self, module_name, command_name): - """ Get help text for a specific command """ + """Get help text for a specific command.""" command = self.env.get_command(module_name, command_name) default_format = 'raw' @@ -113,35 +114,37 @@ def get_command_help(self, module_name, command_name): return arg_doc.strip() def parse_main_args(self, args): - """ Parse root arguments """ + """Parse root arguments.""" main_help = self.get_main_help() - arguments = docopt( + arguments = docopt.docopt( main_help, - version=VERSION, + version=consts.VERSION, argv=args, options_first=True) arguments[''] = self.env.get_module_name(arguments['']) return arguments def parse_module_args(self, module_name, args): - """ Parse module arguments """ + """Parse module arguments.""" arg_doc = self.get_module_help(module_name) - arguments = docopt( + arguments = docopt.docopt( arg_doc, - version=VERSION, + version=consts.VERSION, argv=[module_name] + args, options_first=True) return arguments def parse_command_args(self, module_name, command_name, args): - """ Parse command arguments """ + """Parse command arguments.""" command = self.env.get_command(module_name, command_name) arg_doc = self.get_command_help(module_name, command_name) - arguments = docopt(arg_doc, version=VERSION, argv=[module_name] + args) + arguments = docopt.docopt(arg_doc, + version=consts.VERSION, + argv=[module_name] + args) return command, arguments def parse(self, args): - """ Parse entire tree of arguments """ + """Parse entire tree of arguments.""" # handle `sl ...` main_args = self.parse_main_args(args) module_name = main_args[''] @@ -159,10 +162,8 @@ def parse(self, args): main_args['']) -def main(args=sys.argv[1:], env=Environment()): - """ - Entry point for the command-line client. - """ +def main(args=sys.argv[1:], env=environment.Environment()): + """Entry point for the command-line client.""" # Parse Top-Level Arguments exit_status = 0 resolver = CommandParser(env) @@ -182,9 +183,9 @@ def main(args=sys.argv[1:], env=Environment()): 'config_file': command_args.get('--config') } if command_args.get('--timings'): - client = TimedClient(**kwargs) + client = SoftLayer.TimedClient(**kwargs) else: - client = Client(**kwargs) + client = SoftLayer.Client(**kwargs) # Do the thing runnable = command(client=client, env=env) @@ -192,34 +193,35 @@ def main(args=sys.argv[1:], env=Environment()): if data: out_format = command_args.get('--format', 'table') if out_format not in VALID_FORMATS: - raise ArgumentError('Invalid format "%s"' % out_format) - output = format_output(data, fmt=out_format) + raise exceptions.ArgumentError('Invalid format "%s"' + % out_format) + output = formatting.format_output(data, fmt=out_format) if output: env.out(output) if command_args.get('--timings'): out_format = command_args.get('--format', 'table') api_calls = client.get_last_calls() - timing_table = KeyValueTable(['call', 'time']) + timing_table = formatting.KeyValueTable(['call', 'time']) for call, _, duration in api_calls: timing_table.add_row([call, duration]) - env.err(format_output(timing_table, fmt=out_format)) + env.err(formatting.format_output(timing_table, fmt=out_format)) - except InvalidCommand as ex: + except exceptions.InvalidCommand as ex: env.err(resolver.get_module_help(ex.module_name)) if ex.command_name: env.err('') env.err(str(ex)) exit_status = 1 - except InvalidModule as ex: + except exceptions.InvalidModule as ex: env.err(resolver.get_main_help()) if ex.module_name: env.err('') env.err(str(ex)) exit_status = 1 - except DocoptExit as ex: + except docopt.DocoptExit as ex: env.err(ex.usage) env.err( '\nUnknown argument(s), use -h or --help for available options') @@ -227,19 +229,19 @@ def main(args=sys.argv[1:], env=Environment()): except KeyboardInterrupt: env.out('') exit_status = 1 - except CLIAbort as ex: + except exceptions.CLIAbort as ex: env.err(str(ex.message)) exit_status = ex.code except SystemExit as ex: exit_status = ex.code - except SoftLayerAPIError as ex: + except SoftLayer.SoftLayerAPIError as ex: if 'invalid api token' in ex.faultString.lower(): env.out("Authentication Failed: To update your credentials, use " "'sl config setup'") else: env.err(str(ex)) exit_status = 1 - except SoftLayerError as ex: + except SoftLayer.SoftLayerError as ex: env.err(str(ex)) exit_status = 1 except Exception: diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index c6e27f06a..58a024c4c 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -6,38 +6,21 @@ :license: MIT, see LICENSE for more details. """ import getpass -from importlib import import_module +import importlib import inspect import os import os.path import sys -from SoftLayer.CLI.modules import get_module_list -from SoftLayer.utils import console_input -from SoftLayer import SoftLayerError +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import modules +from SoftLayer import utils # pylint: disable=R0201 -class InvalidCommand(SoftLayerError): - " Raised when trying to use a command that does not exist " - def __init__(self, module_name, command_name, *args): - self.module_name = module_name - self.command_name = command_name - error = 'Invalid command: "%s".' % self.command_name - SoftLayerError.__init__(self, error, *args) - - -class InvalidModule(SoftLayerError): - " Raised when trying to use a module that does not exist " - def __init__(self, module_name, *args): - self.module_name = module_name - error = 'Invalid module: "%s".' % self.module_name - SoftLayerError.__init__(self, error, *args) - - class Environment(object): - """ Provides access to the current CLI environment """ + """Provides access to the current CLI environment.""" def __init__(self): # {'module_name': {'action': 'actionClass'}} self.plugins = {} @@ -56,70 +39,74 @@ def __init__(self): self.stderr = sys.stderr def get_command(self, module_name, command_name): - """ Based on the loaded modules, return a command """ + """Based on the loaded modules, return a command.""" actions = self.plugins.get(module_name) or {} if command_name in actions: return actions[command_name] if None in actions: return actions[None] - raise InvalidCommand(module_name, command_name) + raise exceptions.InvalidCommand(module_name, command_name) def get_module_name(self, module_name): - """ Returns the actual module name. Uses the alias mapping """ + """Returns the actual module name. Uses the alias mapping.""" if module_name in self.aliases: return self.aliases[module_name] return module_name def load_module(self, module_name): # pragma: no cover - """ Loads module by name """ + """Loads module by name.""" try: - module = import_module('SoftLayer.CLI.modules.%s' % module_name) + module = importlib.import_module('SoftLayer.CLI.modules.%s' + % module_name) for _, obj in inspect.getmembers(module): if inspect.isclass(obj) and issubclass(obj, CLIRunnable): self.add_plugin(obj) return module except ImportError: - raise InvalidModule(module_name) + raise exceptions.InvalidModule(module_name) def add_plugin(self, cls): - """ Add a CLIRunnable as a plugin to the environment """ + """Add a CLIRunnable as a plugin to the environment.""" command = cls.__module__.split('.')[-1] if command not in self.plugins: self.plugins[command] = {} self.plugins[command][cls.action] = cls def plugin_list(self): - """ Returns the list of modules in SoftLayer.CLI.modules """ - return get_module_list() + """Returns the list of modules in SoftLayer.CLI.modules.""" + return modules.get_module_list() def out(self, output, newline=True): - """ Outputs a string to the console (stdout) """ + """Outputs a string to the console (stdout).""" self.stdout.write(output) if newline: self.stdout.write(os.linesep) def err(self, output, newline=True): - """ Outputs an error string to the console (stderr) """ + """Outputs an error string to the console (stderr).""" self.stderr.write(output) if newline: self.stderr.write(os.linesep) def input(self, prompt): - """ Provide a command prompt """ - return console_input(prompt) + """Provide a command prompt.""" + return utils.console_input(prompt) def getpass(self, prompt): - """ Provide a password prompt """ + """Provide a password prompt.""" return getpass.getpass(prompt) def exit(self, code=0): - """ Exit """ + """Exit.""" sys.exit(code) class CLIRunnable(object): - """ CLIRunnable is intended to be subclassed. It represents a descrete - command or action in the CLI. """ + """This represents a descrete command or action in the CLI. + + CLIRunnable is intended to be subclassed. + + """ options = [] # set by subclass action = 'not set' # set by subclass @@ -128,6 +115,9 @@ def __init__(self, client=None, env=None): self.env = env def execute(self, args): - """ Execute the command. This is intended to be overridden in a - subclass """ + """Execute the command. + + This is intended to be overridden in a subclass. + + """ pass diff --git a/SoftLayer/CLI/exceptions.py b/SoftLayer/CLI/exceptions.py index 100966945..b1c725ef4 100644 --- a/SoftLayer/CLI/exceptions.py +++ b/SoftLayer/CLI/exceptions.py @@ -6,23 +6,42 @@ :license: MIT, see LICENSE for more details. """ +import SoftLayer + class CLIHalt(SystemExit): - """ Smoothly halt the execution of the command. No error """ + """Smoothly halt the execution of the command. No error.""" def __init__(self, code=0, *args): super(CLIHalt, self).__init__(*args) self.code = code class CLIAbort(CLIHalt): - """ Halt the execution of the command. Gives an exit code of 2 """ + """Halt the execution of the command. Gives an exit code of 2.""" def __init__(self, msg, *args): super(CLIAbort, self).__init__(code=2, *args) self.message = msg class ArgumentError(CLIAbort): - """ Halt the execution of the command because of invalid arguments. """ + """Halt the execution of the command because of invalid arguments.""" def __init__(self, msg, *args): super(ArgumentError, self).__init__(msg, *args) self.message = "Argument Error: %s" % msg + + +class InvalidCommand(SoftLayer.SoftLayerError): + """Raised when trying to use a command that does not exist.""" + def __init__(self, module_name, command_name, *args): + self.module_name = module_name + self.command_name = command_name + error = 'Invalid command: "%s".' % self.command_name + SoftLayer.SoftLayerError.__init__(self, error, *args) + + +class InvalidModule(SoftLayer.SoftLayerError): + """Raised when trying to use a module that does not exist.""" + def __init__(self, module_name, *args): + self.module_name = module_name + error = 'Invalid module: "%s".' % self.module_name + SoftLayer.SoftLayerError.__init__(self, error, *args) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index b3ba88661..c8facc606 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -7,22 +7,24 @@ :license: MIT, see LICENSE for more details. """ # pylint: disable=E0202 -import os import json +import os + +import prettytable -from prettytable import PrettyTable, FRAME, NONE +from SoftLayer import utils -from SoftLayer.utils import string_types, console_input +FALSE_VALUES = ['0', 'false', 'FALSE', 'no', 'False'] def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 - """ Given some data, will format it for output + """Given some data, will format it for console output. :param data: One of: String, Table, FormattedItem, List, Tuple, SequentialOutput :param string fmt (optional): One of: table, raw, json, python """ - if isinstance(data, string_types): + if isinstance(data, utils.string_types): if fmt == 'json': return json.dumps(data) return data @@ -66,13 +68,12 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 def format_prettytable(table): - """ Takes a SoftLayer.CLI.formatting.Table instance and returns a formatted - prettytable """ + """Converts SoftLayer.CLI.formatting.Table instance to a prettytable.""" for i, row in enumerate(table.rows): for j, item in enumerate(row): table.rows[i][j] = format_output(item) ptable = table.prettytable() - ptable.hrules = FRAME + ptable.hrules = prettytable.FRAME ptable.horizontal_char = '.' ptable.vertical_char = ':' ptable.junction_char = ':' @@ -80,15 +81,14 @@ def format_prettytable(table): def format_no_tty(table): - """ Takes a SoftLayer.CLI.formatting.Table instance and returns a formatted - prettytable that has as little formatting as possible """ + """Converts SoftLayer.CLI.formatting.Table instance to a prettytable.""" for i, row in enumerate(table.rows): for j, item in enumerate(row): table.rows[i][j] = format_output(item, fmt='raw') ptable = table.prettytable() for col in table.columns: ptable.align[col] = 'l' - ptable.hrules = NONE + ptable.hrules = prettytable.NONE ptable.border = False ptable.header = False ptable.left_padding_width = 0 @@ -97,8 +97,7 @@ def format_no_tty(table): def mb_to_gb(megabytes): - """ Takes in the number of megabytes and returns a FormattedItem that - displays gigabytes. + """Converts number of megabytes to a FormattedItem in gigabytes. :param int megabytes: number of megabytes """ @@ -106,8 +105,7 @@ def mb_to_gb(megabytes): def gb(gigabytes): # pylint: disable=C0103 - """ Takes in the number of gigabytes and returns a FormattedItem that - displays gigabytes. + """Converts number of gigabytes to a FormattedItem in gigabytes. :param int gigabytes: number of gigabytes """ @@ -116,15 +114,12 @@ def gb(gigabytes): # pylint: disable=C0103 def blank(): - """ Returns FormattedItem to make pretty output use a dash - and raw formatting to use NULL - """ + """Returns a blank FormattedItem.""" return FormattedItem(None, '-') def listing(items, separator=','): - """ Given an iterable, returns a FormattedItem which display a list of - items + """Given an iterable return a FormattedItem which display the list of items :param items: An iterable that outputs strings :param string separator: the separator to use @@ -133,23 +128,17 @@ def listing(items, separator=','): def active_txn(item): - """ Returns a FormattedItem describing the active transaction (if any) on - the given object. If no active transaction is running, returns a blank - FormattedItem. + """Returns a FormattedItem describing the active transaction on a object. + + If no active transaction is running, returns a blank FormattedItem. :param item: An object capable of having an active transaction """ - if not item['activeTransaction'].get('transactionStatus'): - return blank() - - return FormattedItem( - item['activeTransaction']['transactionStatus'].get('name'), - item['activeTransaction']['transactionStatus'].get('friendlyName')) + return transaction_status(item['activeTransaction']) def transaction_status(transaction): - """ Returns a FormattedItem describing the transaction status (if any) on - the given object. If there is no status, returns a blank FormattedItem. + """Returns a FormattedItem describing the given transaction. :param item: An object capable of having an active transaction """ @@ -162,15 +151,17 @@ def transaction_status(transaction): def valid_response(prompt, *valid): - """ Will display a prompt for a command-line user. If the input is in the - valid given valid list then it will return True. Otherwise, it will - return False. If no input is received from the user, None is returned - instead. + """Prompt user for input. + + Will display a prompt for a command-line user. If the input is in the + valid given valid list then it will return True. Otherwise, it will + return False. If no input is received from the user, None is returned + instead. :param string prompt: string prompt to give to the user :param string \\*valid: valid responses """ - ans = console_input(prompt).lower() + ans = utils.console_input(prompt).lower() if ans in valid: return True @@ -181,7 +172,7 @@ def valid_response(prompt, *valid): def confirm(prompt_str, default=False): - """ Show a confirmation prompt to a command-line user. + """Show a confirmation prompt to a command-line user. :param string prompt_str: prompt to give to the user :param bool default: Default value to True or False @@ -200,7 +191,7 @@ def confirm(prompt_str, default=False): def no_going_back(confirmation): - """ Show a confirmation to a user. + """Show a confirmation to a user. :param confirmation str: the string the user has to enter in order to confirm their action. @@ -215,8 +206,9 @@ def no_going_back(confirmation): class SequentialOutput(list): - """ This object represents output in a sequence. The purpose is to - de-couple the separator from the output itself. + """SequentialOutput is used for outputting sequential items. + + The purpose is to de-couple the separator from the output itself. :param separator str: string to use as a default separator """ @@ -225,7 +217,7 @@ def __init__(self, separator=os.linesep, *args, **kwargs): super(SequentialOutput, self).__init__(*args, **kwargs) def to_python(self): - """ returns itself, since it itself is a list """ + """returns itself, since it itself is a list.""" return self def __str__(self): @@ -233,18 +225,16 @@ def __str__(self): class CLIJSONEncoder(json.JSONEncoder): - " A JSON encoder which is able to use a .to_python() method on objects. " + """A JSON encoder which is able to use a .to_python() method on objects.""" def default(self, obj): - """ If the normal JSONEncoder doesn't understand, we have a chance to - encode the object into a native python type. - """ + """Encode object if it implements to_python().""" if hasattr(obj, 'to_python'): return obj.to_python() return super(CLIJSONEncoder, self).default(obj) class Table(object): - """ A Table structure. + """A Table structure used for output. :param list columns: a list of column names """ @@ -256,14 +246,14 @@ def __init__(self, columns): self.sortby = None def add_row(self, row): - """ Add a row to the table + """Add a row to the table. :param list row: the row of string to be added """ self.rows.append(row) def to_python(self): - """ Decode this Table object to standard Python types """ + """Decode this Table object to standard Python types.""" # Adding rows items = [] for row in self.rows: @@ -272,8 +262,8 @@ def to_python(self): return items def prettytable(self): - """ Returns a new prettytable instance. """ - table = PrettyTable(self.columns) + """Returns a new prettytable instance.""" + table = prettytable.PrettyTable(self.columns) if self.sortby: table.sortby = self.sortby for a_col, alignment in self.align.items(): @@ -286,10 +276,9 @@ def prettytable(self): class KeyValueTable(Table): - """ This is a Table which is intended to be used to display key-value - pairs. It expects there to be only two columns.""" + """A table that is oriented towards key-value pairs.""" def to_python(self): - """ Decode this KeyValueTable object to standard Python types """ + """Decode this KeyValueTable object to standard Python types.""" mapping = {} for row in self.rows: mapping[row[0]] = _format_python_value(row[1]) @@ -297,8 +286,7 @@ def to_python(self): class FormattedItem(object): - """ This is an object which wraps a single value that is able to be - represented in a human readable and a machine-readable way. + """This is an object that can be displayed as a human readable and raw. :param original: raw (machine-readable) value :param string formatted: human-readable value @@ -311,11 +299,11 @@ def __init__(self, original, formatted=None): self.formatted = self.original def to_python(self): - """ returns the original (raw) value """ + """returns the original (raw) value.""" return self.original def __str__(self): - """ returns the formatted value """ + """returns the formatted value.""" # If the original value is None, represent this as 'NULL' if self.original is None: return 'NULL' @@ -325,7 +313,7 @@ def __str__(self): def _format_python_value(value): - """ If the value has to_python() defined then return that """ + """If the value has to_python() defined then return that.""" if hasattr(value, 'to_python'): return value.to_python() return value diff --git a/SoftLayer/CLI/helpers.py b/SoftLayer/CLI/helpers.py index 6c9e928ad..135e36052 100644 --- a/SoftLayer/CLI/helpers.py +++ b/SoftLayer/CLI/helpers.py @@ -6,34 +6,11 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.utils import NestedDict -from SoftLayer.CLI.environment import CLIRunnable -from .exceptions import CLIHalt, CLIAbort, ArgumentError -from .formatting import ( - Table, KeyValueTable, FormattedItem, SequentialOutput, confirm, - no_going_back, mb_to_gb, gb, listing, blank, format_output, - active_txn, valid_response, transaction_status) -from .template import update_with_template_args, export_to_template - -__all__ = [ - # Core/Misc - 'CLIRunnable', 'NestedDict', 'FALSE_VALUES', 'resolve_id', - # Exceptions - 'CLIAbort', 'CLIHalt', 'ArgumentError', - # Formatting - 'Table', 'KeyValueTable', 'FormattedItem', 'SequentialOutput', - 'valid_response', 'confirm', 'no_going_back', 'mb_to_gb', 'gb', - 'listing', 'format_output', 'blank', 'active_txn', 'transaction_status', - # Template - 'update_with_template_args', 'export_to_template', -] - -FALSE_VALUES = ['0', 'false', 'FALSE', 'no', 'False'] +from SoftLayer.CLI import exceptions def resolve_id(resolver, identifier, name='object'): - """ Resolves a single id using an id resolver function which returns a list - of ids. + """Resolves a single id using a resolver function. :param resolver: function that resolves ids. Should return None or a list of ids. @@ -44,10 +21,11 @@ def resolve_id(resolver, identifier, name='object'): ids = resolver(identifier) if len(ids) == 0: - raise CLIAbort("Error: Unable to find %s '%s'" % (name, identifier)) + raise exceptions.CLIAbort("Error: Unable to find %s '%s'" + % (name, identifier)) if len(ids) > 1: - raise CLIAbort( + raise exceptions.CLIAbort( "Error: Multiple %s found for '%s': %s" % (name, identifier, ', '.join([str(_id) for _id in ids]))) diff --git a/SoftLayer/CLI/modules/__init__.py b/SoftLayer/CLI/modules/__init__.py index 99add6466..eaad02f96 100644 --- a/SoftLayer/CLI/modules/__init__.py +++ b/SoftLayer/CLI/modules/__init__.py @@ -6,10 +6,10 @@ :license: MIT, see LICENSE for more details. """ -from pkgutil import iter_modules +import pkgutil def get_module_list(): - """ Returns each module under SoftLayer.CLI.modules """ - actions = [action[1] for action in iter_modules(__path__)] + """Returns each module under SoftLayer.CLI.modules.""" + actions = [action[1] for action in pkgutil.iter_modules(__path__)] return actions diff --git a/SoftLayer/CLI/modules/cdn.py b/SoftLayer/CLI/modules/cdn.py index 67c4a21d6..11f0e8b47 100644 --- a/SoftLayer/CLI/modules/cdn.py +++ b/SoftLayer/CLI/modules/cdn.py @@ -14,11 +14,12 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer.CLI import CLIRunnable, Table, KeyValueTable, blank -from SoftLayer.managers.cdn import CDNManager +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting -class ListAccounts(CLIRunnable): +class ListAccounts(environment.CLIRunnable): """ usage: sl cdn list [options] @@ -31,24 +32,28 @@ class ListAccounts(CLIRunnable): action = 'list' def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) accounts = manager.list_accounts() - table = Table(['id', 'account_name', 'type', 'created', 'notes']) + table = formatting.Table(['id', + 'account_name', + 'type', + 'created', + 'notes']) for account in accounts: table.add_row([ account['id'], account['cdnAccountName'], account['cdnSolutionName'], account['createDate'], - account.get('cdnAccountNote', blank()) + account.get('cdnAccountNote', formatting.blank()) ]) table.sortby = args['--sortby'] return table -class DetailAccount(CLIRunnable): +class DetailAccount(environment.CLIRunnable): """ usage: sl cdn detail [options] @@ -57,10 +62,10 @@ class DetailAccount(CLIRunnable): action = 'detail' def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) account = manager.get_account(args.get('')) - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' @@ -69,12 +74,13 @@ def execute(self, args): table.add_row(['type', account['cdnSolutionName']]) table.add_row(['status', account['status']['name']]) table.add_row(['created', account['createDate']]) - table.add_row(['notes', account.get('cdnAccountNote', blank())]) + table.add_row(['notes', + account.get('cdnAccountNote', formatting.blank())]) return table -class LoadContent(CLIRunnable): +class LoadContent(environment.CLIRunnable): """ usage: sl cdn load ... [options] @@ -89,11 +95,11 @@ class LoadContent(CLIRunnable): required_params = ['account', 'content_url'] def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) manager.load_content(args.get(''), args.get('')) -class PurgeContent(CLIRunnable): +class PurgeContent(environment.CLIRunnable): """ usage: sl cdn purge ... [options] @@ -108,12 +114,12 @@ class PurgeContent(CLIRunnable): required_params = ['account', 'content_url'] def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) manager.purge_content(args.get(''), args.get('')) -class ListOrigins(CLIRunnable): +class ListOrigins(environment.CLIRunnable): """ usage: sl cdn origin-list [options] @@ -122,21 +128,21 @@ class ListOrigins(CLIRunnable): action = 'origin-list' def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) origins = manager.get_origins(args.get('')) - table = Table(['id', 'media_type', 'cname', 'origin_url']) + table = formatting.Table(['id', 'media_type', 'cname', 'origin_url']) for origin in origins: table.add_row([origin['id'], origin['mediaType'], - origin.get('cname', blank()), + origin.get('cname', formatting.blank()), origin['originUrl']]) return table -class AddOrigin(CLIRunnable): +class AddOrigin(environment.CLIRunnable): """ usage: sl cdn origin-add [options] @@ -156,14 +162,14 @@ class AddOrigin(CLIRunnable): required_params = ['account', 'url'] def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) media_type = args.get('--type') or 'http' manager.add_origin(args.get(''), media_type, args.get(''), args.get('--cname', None)) -class RemoveOrigin(CLIRunnable): +class RemoveOrigin(environment.CLIRunnable): """ usage: sl cdn origin-remove [options] @@ -177,6 +183,6 @@ class RemoveOrigin(CLIRunnable): required_params = ['account', 'origin_id'] def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) manager.remove_origin(args.get(''), args.get('')) diff --git a/SoftLayer/CLI/modules/config.py b/SoftLayer/CLI/modules/config.py index 5a8fe62b8..3f84e1052 100644 --- a/SoftLayer/CLI/modules/config.py +++ b/SoftLayer/CLI/modules/config.py @@ -11,12 +11,12 @@ import os.path -from SoftLayer import ( - SoftLayerAPIError, API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT) -from SoftLayer.auth import BasicAuthentication -from SoftLayer.CLI import ( - CLIRunnable, CLIAbort, KeyValueTable, confirm, format_output) -from SoftLayer.utils import configparser +import SoftLayer +from SoftLayer import auth +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer import utils def get_settings_from_client(client): @@ -41,7 +41,7 @@ def get_settings_from_client(client): def config_table(settings): """ Returns a config table """ - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' table.add_row(['Username', settings['username'] or 'not set']) @@ -52,18 +52,20 @@ def config_table(settings): def get_api_key(client, username, secret, endpoint_url=None): - """ Tries API-Key and password auth to get (and potentially generate) an - API key. """ + """ Attempts API-Key and password auth to get an API key + + This will also generate an API key if one doesn't exist + """ client.endpoint_url = endpoint_url client.auth = None # Try to use a client with username/api key if len(secret) == 64: try: - client.auth = BasicAuthentication(username, secret) + client.auth = auth.BasicAuthentication(username, secret) client['Account'].getCurrentUser() return secret - except SoftLayerAPIError as ex: + except SoftLayer.SoftLayerAPIError as ex: if 'invalid api token' not in ex.faultString.lower(): raise else: @@ -79,7 +81,7 @@ def get_api_key(client, username, secret, endpoint_url=None): return api_keys[0]['authenticationKey'] -class Setup(CLIRunnable): +class Setup(environment.CLIRunnable): """ usage: sl config setup [options] @@ -99,22 +101,23 @@ def execute(self, args): config_path = os.path.expanduser(path) self.env.out( - format_output(config_table({'username': username, - 'api_key': api_key, - 'endpoint_url': endpoint_url, - 'timeout': timeout}))) + formatting.format_output(config_table({ + 'username': username, + 'api_key': api_key, + 'endpoint_url': endpoint_url, + 'timeout': timeout}))) - if not confirm('Are you sure you want to write settings to "%s"?' - % config_path, default=True): - raise CLIAbort('Aborted.') + if not formatting.confirm('Are you sure you want to write settings ' + 'to "%s"?' % config_path, default=True): + raise exceptions.CLIAbort('Aborted.') # Persist the config file. Read the target config file in before # setting the values to avoid clobbering settings - config = configparser.RawConfigParser() + config = utils.configparser.RawConfigParser() config.read(config_path) try: config.add_section('softlayer') - except configparser.DuplicateSectionError: + except utils.configparser.DuplicateSectionError: pass config.set('softlayer', 'username', username) @@ -140,23 +143,23 @@ def get_user_input(self): # Ask for username for _ in range(3): - username = self.env.input( - 'Username [%s]: ' % defaults['username']) \ - or defaults['username'] + username = (self.env.input('Username [%s]: ' + % defaults['username']) + or defaults['username']) if username: break else: - raise CLIAbort('Aborted after 3 attempts') + raise exceptions.CLIAbort('Aborted after 3 attempts') # Ask for 'secret' which can be api_key or their password for _ in range(3): - secret = self.env.getpass( - 'API Key or Password [%s]: ' % defaults['api_key']) \ - or defaults['api_key'] + secret = (self.env.getpass('API Key or Password [%s]: ' + % defaults['api_key']) + or defaults['api_key']) if secret: break else: - raise CLIAbort('Aborted after 3 attempts') + raise exceptions.CLIAbort('Aborted after 3 attempts') # Ask for which endpoint they want to use for _ in range(3): @@ -164,13 +167,13 @@ def get_user_input(self): 'Endpoint (public|private|custom): ') endpoint_type = endpoint_type.lower() if not endpoint_type: - endpoint_url = API_PUBLIC_ENDPOINT + endpoint_url = SoftLayer.API_PUBLIC_ENDPOINT break if endpoint_type == 'public': - endpoint_url = API_PUBLIC_ENDPOINT + endpoint_url = SoftLayer.API_PUBLIC_ENDPOINT break elif endpoint_type == 'private': - endpoint_url = API_PRIVATE_ENDPOINT + endpoint_url = SoftLayer.API_PRIVATE_ENDPOINT break elif endpoint_type == 'custom': endpoint_url = self.env.input( @@ -178,12 +181,12 @@ def get_user_input(self): ) or defaults['endpoint_url'] break else: - raise CLIAbort('Aborted after 3 attempts') + raise exceptions.CLIAbort('Aborted after 3 attempts') return username, secret, endpoint_url, timeout -class Show(CLIRunnable): +class Show(environment.CLIRunnable): """ usage: sl config show [options] diff --git a/SoftLayer/CLI/modules/dns.py b/SoftLayer/CLI/modules/dns.py index 8b7fe6402..0d9d6f389 100755 --- a/SoftLayer/CLI/modules/dns.py +++ b/SoftLayer/CLI/modules/dns.py @@ -16,12 +16,14 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer.CLI import ( - CLIRunnable, no_going_back, Table, CLIAbort, resolve_id) -from SoftLayer import DNSManager +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers -class DumpZone(CLIRunnable): +class DumpZone(environment.CLIRunnable): """ usage: sl dns print [options] @@ -33,12 +35,13 @@ class DumpZone(CLIRunnable): action = "print" def execute(self, args): - manager = DNSManager(self.client) - zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') + manager = SoftLayer.DNSManager(self.client) + zone_id = helpers.resolve_id(manager.resolve_ids, args[''], + name='zone') return manager.dump_zone(zone_id) -class CreateZone(CLIRunnable): +class CreateZone(environment.CLIRunnable): """ usage: sl dns create [options] @@ -50,11 +53,11 @@ class CreateZone(CLIRunnable): action = 'create' def execute(self, args): - manager = DNSManager(self.client) + manager = SoftLayer.DNSManager(self.client) manager.create_zone(args['']) -class DeleteZone(CLIRunnable): +class DeleteZone(environment.CLIRunnable): """ usage: sl dns delete [options] @@ -67,16 +70,17 @@ class DeleteZone(CLIRunnable): options = ['confirm'] def execute(self, args): - manager = DNSManager(self.client) - zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') + manager = SoftLayer.DNSManager(self.client) + zone_id = helpers.resolve_id(manager.resolve_ids, args[''], + name='zone') - if args['--really'] or no_going_back(args['']): + if args['--really'] or formatting.no_going_back(args['']): manager.delete_zone(zone_id) else: - raise CLIAbort("Aborted.") + raise exceptions.CLIAbort("Aborted.") -class ListZones(CLIRunnable): +class ListZones(environment.CLIRunnable): """ usage: sl dns list [] [options] @@ -98,14 +102,15 @@ def execute(self, args): def list_zone(self, args): """ list records for a particular zone """ - manager = DNSManager(self.client) - table = Table(['id', 'record', 'type', 'ttl', 'value']) + manager = SoftLayer.DNSManager(self.client) + table = formatting.Table(['id', 'record', 'type', 'ttl', 'value']) table.align['ttl'] = 'l' table.align['record'] = 'r' table.align['value'] = 'l' - zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') + zone_id = helpers.resolve_id(manager.resolve_ids, args[''], + name='zone') records = manager.get_records( zone_id, @@ -128,9 +133,9 @@ def list_zone(self, args): def list_all_zones(self): """ List all zones """ - manager = DNSManager(self.client) + manager = SoftLayer.DNSManager(self.client) zones = manager.list_zones() - table = Table(['id', 'zone', 'serial', 'updated']) + table = formatting.Table(['id', 'zone', 'serial', 'updated']) table.align['serial'] = 'c' table.align['updated'] = 'c' @@ -145,7 +150,7 @@ def list_all_zones(self): return table -class AddRecord(CLIRunnable): +class AddRecord(environment.CLIRunnable): """ usage: sl dns add [--ttl=TTL] [options] @@ -164,9 +169,10 @@ class AddRecord(CLIRunnable): action = 'add' def execute(self, args): - manager = DNSManager(self.client) + manager = SoftLayer.DNSManager(self.client) - zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') + zone_id = helpers.resolve_id(manager.resolve_ids, args[''], + name='zone') manager.create_record( zone_id, @@ -176,7 +182,7 @@ def execute(self, args): ttl=args['--ttl'] or 7200) -class EditRecord(CLIRunnable): +class EditRecord(environment.CLIRunnable): """ usage: sl dns edit [--data=DATA] [--ttl=TTL] [--id=ID] [options] @@ -195,8 +201,9 @@ class EditRecord(CLIRunnable): action = 'edit' def execute(self, args): - manager = DNSManager(self.client) - zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') + manager = SoftLayer.DNSManager(self.client) + zone_id = helpers.resolve_id(manager.resolve_ids, args[''], + name='zone') results = manager.get_records( zone_id, @@ -210,7 +217,7 @@ def execute(self, args): manager.edit_record(result) -class RecordRemove(CLIRunnable): +class RecordRemove(environment.CLIRunnable): """ usage: sl dns remove [--id=ID] [options] @@ -227,8 +234,9 @@ class RecordRemove(CLIRunnable): options = ['confirm'] def execute(self, args): - manager = DNSManager(self.client) - zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') + manager = SoftLayer.DNSManager(self.client) + zone_id = helpers.resolve_id(manager.resolve_ids, args[''], + name='zone') if args['--id']: records = [{'id': args['--id']}] @@ -237,11 +245,11 @@ def execute(self, args): zone_id, host=args['']) - if args['--really'] or no_going_back('yes'): - table = Table(['record']) + if args['--really'] or formatting.no_going_back('yes'): + table = formatting.Table(['record']) for result in records: manager.delete_record(result['id']) table.add_row([result['id']]) return table - raise CLIAbort("Aborted.") + raise exceptions.CLIAbort("Aborted.") diff --git a/SoftLayer/CLI/modules/firewall.py b/SoftLayer/CLI/modules/firewall.py index b4e4b93f3..489cf829a 100755 --- a/SoftLayer/CLI/modules/firewall.py +++ b/SoftLayer/CLI/modules/firewall.py @@ -14,14 +14,16 @@ # :license: MIT, see LICENSE for more details. from __future__ import print_function -from subprocess import call import os +import subprocess import tempfile -from SoftLayer import FirewallManager, SoftLayerError -from SoftLayer.utils import lookup -from SoftLayer.CLI import CLIRunnable, Table, listing, resolve_id, confirm -from SoftLayer.CLI.helpers import blank, CLIAbort +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer import utils DELIMITER = "=========================================\n" @@ -34,8 +36,8 @@ def get_ids(input_id): key_value = input_id.split(':') if len(key_value) != 2: - raise CLIAbort('Invalid ID %s: ID should be of the form xxx:yyy' - % input_id) + raise exceptions.CLIAbort( + 'Invalid ID %s: ID should be of the form xxx:yyy' % input_id) return key_value @@ -69,8 +71,8 @@ def get_rules_table(rules): :param list rules: A list containing the rules of the firewall :returns: a formatted table of the firewall rules """ - table = Table(['#', 'action', 'protocol', 'src_ip', 'src_mask', 'dest', - 'dest_mask']) + table = formatting.Table(['#', 'action', 'protocol', 'src_ip', 'src_mask', + 'dest', 'dest_mask']) table.sortby = '#' for rule in rules: table.add_row([ @@ -137,7 +139,7 @@ def open_editor(rules=None, content=None): # if content is provided, just display it as is tfile.write(content) tfile.flush() - call([editor, tfile.name]) + subprocess.call([editor, tfile.name]) tfile.seek(0) data = tfile.read() return data @@ -153,7 +155,7 @@ def open_editor(rules=None, content=None): tfile.write(get_formatted_rule(rule)) tfile.write(DELIMITER) tfile.flush() - call([editor, tfile.name]) + subprocess.call([editor, tfile.name]) tfile.seek(0) data = tfile.read() return data @@ -205,7 +207,7 @@ def parse_rules(content=None): return parsed_rules -class FWList(CLIRunnable): +class FWList(environment.CLIRunnable): """ usage: sl firewall list [options] @@ -214,11 +216,11 @@ class FWList(CLIRunnable): action = 'list' def execute(self, args): - mgr = FirewallManager(self.client) - table = Table(['firewall id', - 'type', - 'features', - 'server/vlan id']) + mgr = SoftLayer.FirewallManager(self.client) + table = formatting.Table(['firewall id', + 'type', + 'features', + 'server/vlan id']) fwvlans = mgr.get_firewalls() dedicated_firewalls = [firewall for firewall in fwvlans if firewall['dedicatedFirewallFlag']] @@ -229,9 +231,9 @@ def execute(self, args): features.append('HA') if features: - feature_list = listing(features, separator=',') + feature_list = formatting.listing(features, separator=',') else: - feature_list = blank() + feature_list = formatting.blank() table.add_row([ 'vlan:%s' % vlan['networkVlanFirewall']['id'], @@ -264,16 +266,16 @@ def execute(self, args): 'server:%s' % firewall['id'], 'Server - standard', '-', - lookup(firewall, - 'networkComponent', - 'downlinkComponent', - 'hardwareId') + utils.lookup(firewall, + 'networkComponent', + 'downlinkComponent', + 'hardwareId') ]) return table -class FWCancel(CLIRunnable): +class FWCancel(environment.CLIRunnable): """ usage: sl firewall cancel [options] @@ -287,23 +289,24 @@ class FWCancel(CLIRunnable): options = ['really'] def execute(self, args): - mgr = FirewallManager(self.client) + mgr = SoftLayer.FirewallManager(self.client) input_id = args.get('') key_value = get_ids(input_id) firewall_id = int(key_value[1]) - if args['--really'] or confirm("This action will cancel a firewall" - " from your account. Continue?"): + if args['--really'] or formatting.confirm("This action will cancel a " + "firewall from your account." + " Continue?"): if key_value[0] in ['cci', 'server']: mgr.cancel_firewall(firewall_id, dedicated=False) elif key_value[0] == 'vlan': mgr.cancel_firewall(firewall_id, dedicated=True) return 'Firewall with id %s is being cancelled!' % input_id else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class FWAdd(CLIRunnable): +class FWAdd(environment.CLIRunnable): """ usage: sl firewall add (--cci | --vlan | --server) [options] @@ -319,8 +322,8 @@ class FWAdd(CLIRunnable): options = ['really', 'ha'] def execute(self, args): - mgr = FirewallManager(self.client) - input_id = resolve_id( + mgr = SoftLayer.FirewallManager(self.client) + input_id = helpers.resolve_id( mgr.resolve_ids, args.get(''), 'firewall') ha_support = args.get('--ha', False) if not args['--really']: @@ -335,9 +338,9 @@ def execute(self, args): return "Unable to add firewall - Is network public enabled?" print_package_info(pkg) - if not confirm("This action will incur charges on your account. " - "Continue?"): - raise CLIAbort('Aborted.') + if not formatting.confirm("This action will incur charges on your " + "account. Continue?"): + raise exceptions.CLIAbort('Aborted.') if args['--vlan']: mgr.add_vlan_firewall(input_id, ha_enabled=ha_support) @@ -349,7 +352,7 @@ def execute(self, args): return "Firewall is being created!" -class FWDetails(CLIRunnable): +class FWDetails(environment.CLIRunnable): """ usage: sl firewall detail [options] @@ -358,7 +361,7 @@ class FWDetails(CLIRunnable): action = 'detail' def execute(self, args): - mgr = FirewallManager(self.client) + mgr = SoftLayer.FirewallManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -370,7 +373,7 @@ def execute(self, args): return get_rules_table(rules) -class FWEdit(CLIRunnable): +class FWEdit(environment.CLIRunnable): """ usage: sl firewall edit [options] @@ -379,7 +382,7 @@ class FWEdit(CLIRunnable): action = 'edit' def execute(self, args): - mgr = FirewallManager(self.client) + mgr = SoftLayer.FirewallManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -391,8 +394,8 @@ def execute(self, args): # open an editor for the user to enter their rules edited_rules = open_editor(rules=orig_rules) print(edited_rules) - if confirm("Would you like to submit the rules. " - "Continue?"): + if formatting.confirm("Would you like to submit the rules. " + "Continue?"): while True: try: rules = parse_rules(edited_rules) @@ -403,19 +406,19 @@ def execute(self, args): rules = mgr.edit_standard_fwl_rules(firewall_id, rules) break - except (SoftLayerError, ValueError) as error: + except (SoftLayer.SoftLayerError, ValueError) as error: print("Unexpected error({%s})" % (error)) - if confirm("Would you like to continue editing the rules" - ". Continue?"): + if formatting.confirm("Would you like to continue editing " + "the rules. Continue?"): edited_rules = open_editor(content=edited_rules) print(edited_rules) - if confirm("Would you like to submit the rules. " - "Continue?"): + if formatting.confirm("Would you like to submit the " + "rules. Continue?"): continue else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') return 'Firewall updated!' else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/modules/globalip.py b/SoftLayer/CLI/modules/globalip.py index 5640d2dd2..6f87c2a10 100644 --- a/SoftLayer/CLI/modules/globalip.py +++ b/SoftLayer/CLI/modules/globalip.py @@ -12,13 +12,14 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer import NetworkManager -from SoftLayer.CLI import ( - CLIRunnable, Table, confirm, no_going_back, resolve_id) -from SoftLayer.CLI.helpers import CLIAbort +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers -class GlobalIpAssign(CLIRunnable): +class GlobalIpAssign(environment.CLIRunnable): """ usage: sl globalip assign [options] @@ -31,14 +32,14 @@ class GlobalIpAssign(CLIRunnable): action = 'assign' def execute(self, args): - mgr = NetworkManager(self.client) - global_ip_id = resolve_id(mgr.resolve_global_ip_ids, - args.get(''), - name='global ip') + mgr = SoftLayer.NetworkManager(self.client) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, + args.get(''), + name='global ip') mgr.assign_global_ip(global_ip_id, args['']) -class GlobalIpCancel(CLIRunnable): +class GlobalIpCancel(environment.CLIRunnable): """ usage: sl globalip cancel [options] @@ -49,18 +50,18 @@ class GlobalIpCancel(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = NetworkManager(self.client) - global_ip_id = resolve_id(mgr.resolve_global_ip_ids, - args.get(''), - name='global ip') + mgr = SoftLayer.NetworkManager(self.client) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, + args.get(''), + name='global ip') - if args['--really'] or no_going_back(global_ip_id): + if args['--really'] or formatting.no_going_back(global_ip_id): mgr.cancel_global_ip(global_ip_id) else: - raise CLIAbort('Aborted') + raise exceptions.CLIAbort('Aborted') -class GlobalIpCreate(CLIRunnable): +class GlobalIpCreate(environment.CLIRunnable): """ usage: sl globalip create [options] @@ -75,19 +76,19 @@ class GlobalIpCreate(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = NetworkManager(self.client) + mgr = SoftLayer.NetworkManager(self.client) version = 4 if args.get('--v6'): version = 6 if not args.get('--test') and not args['--really']: - if not confirm("This action will incur charges on your account." - "Continue?"): - raise CLIAbort('Cancelling order.') + if not formatting.confirm("This action will incur charges on your " + "account. Continue?"): + raise exceptions.CLIAbort('Cancelling order.') result = mgr.add_global_ip(version=version, test_order=args.get('--test')) - table = Table(['Item', 'cost']) + table = formatting.Table(['Item', 'cost']) table.align['Item'] = 'r' table.align['cost'] = 'r' @@ -102,7 +103,7 @@ def execute(self, args): return table -class GlobalIpList(CLIRunnable): +class GlobalIpList(environment.CLIRunnable): """ usage: sl globalip list [options] @@ -115,9 +116,9 @@ class GlobalIpList(CLIRunnable): action = 'list' def execute(self, args): - mgr = NetworkManager(self.client) + mgr = SoftLayer.NetworkManager(self.client) - table = Table(['id', 'ip', 'assigned', 'target']) + table = formatting.Table(['id', 'ip', 'assigned', 'target']) table.sortby = args.get('--sortby') or 'id' version = 0 @@ -150,7 +151,7 @@ def execute(self, args): return table -class GlobalIpUnassign(CLIRunnable): +class GlobalIpUnassign(environment.CLIRunnable): """ usage: sl globalip unassign [options] @@ -162,8 +163,8 @@ class GlobalIpUnassign(CLIRunnable): action = 'unassign' def execute(self, args): - mgr = NetworkManager(self.client) - global_ip_id = resolve_id(mgr.resolve_global_ip_ids, - args.get(''), - name='global ip') + mgr = SoftLayer.NetworkManager(self.client) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, + args.get(''), + name='global ip') mgr.unassign_global_ip(global_ip_id) diff --git a/SoftLayer/CLI/modules/help.py b/SoftLayer/CLI/modules/help.py index 2bf5fae83..0866687e1 100644 --- a/SoftLayer/CLI/modules/help.py +++ b/SoftLayer/CLI/modules/help.py @@ -9,17 +9,17 @@ # Missing docstrings ignored due to __doc__ = __doc__ magic # pylint: disable=C0111 -from SoftLayer.CLI.core import CommandParser -from SoftLayer.CLI import CLIRunnable +from SoftLayer.CLI import core +from SoftLayer.CLI import environment -class Show(CLIRunnable): +class Show(environment.CLIRunnable): # Use the same documentation as the module __doc__ = __doc__ action = None def execute(self, args): - parser = CommandParser(self.env) + parser = core.CommandParser(self.env) if not any([args[''], args['']]): return parser.get_module_help('help') diff --git a/SoftLayer/CLI/modules/image.py b/SoftLayer/CLI/modules/image.py index 099893ba2..7223749f9 100644 --- a/SoftLayer/CLI/modules/image.py +++ b/SoftLayer/CLI/modules/image.py @@ -11,12 +11,14 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer import ImageManager -from SoftLayer.CLI import CLIRunnable, Table, KeyValueTable, blank, resolve_id -from SoftLayer.CLI.helpers import CLIAbort +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers -class ListImages(CLIRunnable): +class ListImages(environment.CLIRunnable): """ usage: sl image list [--public | --private] [options] @@ -29,7 +31,7 @@ class ListImages(CLIRunnable): action = 'list' def execute(self, args): - image_mgr = ImageManager(self.client) + image_mgr = SoftLayer.ImageManager(self.client) neither = not any([args['--private'], args['--public']]) mask = 'id,accountId,name,globalIdentifier,blockDevices,parentId' @@ -45,26 +47,26 @@ def execute(self, args): image['visibility'] = 'public' images.append(image) - table = Table(['id', - 'account', - 'visibility', - 'name', - 'global_identifier']) + table = formatting.Table(['id', + 'account', + 'visibility', + 'name', + 'global_identifier']) images = [image for image in images if image['parentId'] == ''] for image in images: table.add_row([ image['id'], - image.get('accountId', blank()), + image.get('accountId', formatting.blank()), image['visibility'], image['name'].strip(), - image.get('globalIdentifier', blank()), + image.get('globalIdentifier', formatting.blank()), ]) return table -class DetailImage(CLIRunnable): +class DetailImage(environment.CLIRunnable): """ usage: sl image detail [options] @@ -73,27 +75,27 @@ class DetailImage(CLIRunnable): action = 'detail' def execute(self, args): - image_mgr = ImageManager(self.client) - image_id = resolve_id(image_mgr.resolve_ids, - args.get(''), - 'image') + image_mgr = SoftLayer.ImageManager(self.client) + image_id = helpers.resolve_id(image_mgr.resolve_ids, + args.get(''), + 'image') image = image_mgr.get_image(image_id) - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' table.add_row(['id', image['id']]) - table.add_row(['account', image.get('accountId', blank())]) + table.add_row(['account', image.get('accountId', formatting.blank())]) table.add_row(['name', image['name'].strip()]) table.add_row(['global_identifier', - image.get('globalIdentifier', blank())]) + image.get('globalIdentifier', formatting.blank())]) return table -class DeleteImage(CLIRunnable): +class DeleteImage(environment.CLIRunnable): """ usage: sl image delete [options] @@ -102,15 +104,15 @@ class DeleteImage(CLIRunnable): action = 'delete' def execute(self, args): - image_mgr = ImageManager(self.client) - image_id = resolve_id(image_mgr.resolve_ids, - args.get(''), - 'image') + image_mgr = SoftLayer.ImageManager(self.client) + image_id = helpers.resolve_id(image_mgr.resolve_ids, + args.get(''), + 'image') image_mgr.delete_image(image_id) -class EditImage(CLIRunnable): +class EditImage(environment.CLIRunnable): """ usage: sl image edit [--tag=Tag...] [options] @@ -126,7 +128,7 @@ class EditImage(CLIRunnable): action = 'edit' def execute(self, args): - image_mgr = ImageManager(self.client) + image_mgr = SoftLayer.ImageManager(self.client) data = {} if args.get('--name'): data['name'] = args.get('--name') @@ -134,7 +136,7 @@ def execute(self, args): data['note'] = args.get('--note') if args.get('--tag'): data['tag'] = args.get('--tag') - image_id = resolve_id(image_mgr.resolve_ids, - args.get(''), 'image') + image_id = helpers.resolve_id(image_mgr.resolve_ids, + args.get(''), 'image') if not image_mgr.edit(image_id, **data): - raise CLIAbort("Failed to Edit Image") + raise exceptions.CLIAbort("Failed to Edit Image") diff --git a/SoftLayer/CLI/modules/iscsi.py b/SoftLayer/CLI/modules/iscsi.py index cc916f98d..a0d5bbd41 100644 --- a/SoftLayer/CLI/modules/iscsi.py +++ b/SoftLayer/CLI/modules/iscsi.py @@ -12,13 +12,16 @@ For several commands, will be asked for. This will be the id for iSCSI target. """ -from SoftLayer.CLI import (CLIRunnable, Table, no_going_back, FormattedItem) -from SoftLayer.CLI.helpers import ( - CLIAbort, ArgumentError, NestedDict, blank, resolve_id, KeyValueTable) -from SoftLayer import ISCSIManager +# from SoftLayer.CLI import (CLIRunnable, Table, no_going_back, FormattedItem) +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer import utils -class ListISCSIs(CLIRunnable): +class ListISCSIs(environment.CLIRunnable): """ usage: sl iscsi list [options] @@ -28,10 +31,10 @@ class ListISCSIs(CLIRunnable): action = 'list' def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) + iscsi_mgr = SoftLayer.ISCSIManager(self.client) iscsi_list = iscsi_mgr.list_iscsi() - iscsi_list = [NestedDict(n) for n in iscsi_list] - table = Table([ + iscsi_list = [utils.NestedDict(n) for n in iscsi_list] + table = formatting.Table([ 'id', 'datacenter', 'size', @@ -42,17 +45,19 @@ def execute(self, args): for iscsi in iscsi_list: table.add_row([ iscsi['id'], - iscsi['serviceResource']['datacenter'].get('name', blank()), - FormattedItem( - iscsi.get('capacityGb', blank()), + iscsi['serviceResource']['datacenter'].get('name', + formatting.blank()), + formatting.FormattedItem( + iscsi.get('capacityGb', formatting.blank()), "%dGB" % iscsi.get('capacityGb', 0)), - iscsi.get('username', blank()), - iscsi.get('password', blank()), - iscsi.get('serviceResourceBackendIpAddress', blank())]) + iscsi.get('username', formatting.blank()), + iscsi.get('password', formatting.blank()), + iscsi.get('serviceResourceBackendIpAddress', + formatting.blank())]) return table -class CreateISCSI(CLIRunnable): +class CreateISCSI(environment.CLIRunnable): """ usage: sl iscsi create [options] @@ -73,7 +78,7 @@ class CreateISCSI(CLIRunnable): required_params = ['--size', '--datacenter'] def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) + iscsi_mgr = SoftLayer.ISCSIManager(self.client) self._validate_create_args(args) size, location = self._parse_create_args(args) iscsi_mgr.create_iscsi(size=size, location=location) @@ -91,11 +96,11 @@ def _validate_create_args(self, args): """ Raises an ArgumentError if the given arguments are not valid """ invalid_args = [k for k in self.required_params if args.get(k) is None] if invalid_args: - raise ArgumentError('Missing required options: %s' - % ','.join(invalid_args)) + raise exceptions.ArgumentError('Missing required options: %s' + % ','.join(invalid_args)) -class CancelISCSI(CLIRunnable): +class CancelISCSI(environment.CLIRunnable): """ usage: sl iscsi cancel [options] @@ -116,8 +121,8 @@ class CancelISCSI(CLIRunnable): options = ['confirm'] def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) - iscsi_id = resolve_id( + iscsi_mgr = SoftLayer.ISCSIManager(self.client) + iscsi_id = helpers.resolve_id( iscsi_mgr.resolve_ids, args.get(''), 'iSCSI') @@ -125,13 +130,13 @@ def execute(self, args): immediate = args.get('--immediate', False) reason = args.get('--reason') - if args['--really'] or no_going_back(iscsi_id): + if args['--really'] or formatting.no_going_back(iscsi_id): iscsi_mgr.cancel_iscsi(iscsi_id, reason, immediate) else: - CLIAbort('Aborted') + raise exceptions.CLIAbort('Aborted') -class ISCSIDetails(CLIRunnable): +class ISCSIDetails(environment.CLIRunnable): """ usage: sl iscsi detail [--password] [options] @@ -148,17 +153,17 @@ class ISCSIDetails(CLIRunnable): action = 'detail' def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) - table = KeyValueTable(['Name', 'Value']) + iscsi_mgr = SoftLayer.ISCSIManager(self.client) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' - iscsi_id = resolve_id( + iscsi_id = helpers.resolve_id( iscsi_mgr.resolve_ids, args.get(''), 'iSCSI') result = iscsi_mgr.get_iscsi(iscsi_id) - result = NestedDict(result) + result = utils.NestedDict(result) table.add_row(['id', result['id']]) table.add_row(['serviceResourceName', result['serviceResourceName']]) @@ -177,7 +182,7 @@ def execute(self, args): table.add_row(['notes', result['notes']]) if args.get('--password'): - pass_table = Table(['username', 'password']) + pass_table = formatting.Table(['username', 'password']) pass_table.add_row([result['username'], result['password']]) table.add_row(['users', pass_table]) diff --git a/SoftLayer/CLI/modules/loadbal.py b/SoftLayer/CLI/modules/loadbal.py index 2222832d4..cd1e46c00 100755 --- a/SoftLayer/CLI/modules/loadbal.py +++ b/SoftLayer/CLI/modules/loadbal.py @@ -23,10 +23,11 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer import LoadBalancerManager -from SoftLayer.CLI import (CLIRunnable, Table, resolve_id, - confirm, KeyValueTable) -from SoftLayer.CLI.helpers import CLIAbort +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers def get_ids(input_id): @@ -37,8 +38,8 @@ def get_ids(input_id): """ key_value = input_id.split(':') if len(key_value) != 2: - raise CLIAbort('Invalid ID %s: ID should be of the form xxx:yyy' - % input_id) + raise exceptions.CLIAbort( + 'Invalid ID %s: ID should be of the form xxx:yyy' % input_id) return key_value @@ -48,12 +49,12 @@ def get_local_lbs_table(load_balancers): :param dict load_balancers: A dictionary representing the load_balancers :returns: A table containing the local load balancers """ - table = Table(['ID', - 'VIP Address', - 'Location', - 'SSL Offload', - 'Connections/second', - 'Type']) + table = formatting.Table(['ID', + 'VIP Address', + 'Location', + 'SSL Offload', + 'Connections/second', + 'Type']) table.align['Connections/second'] = 'r' @@ -86,7 +87,7 @@ def get_local_lb_table(load_balancer): :param dict load_balancer: A dictionary representing the loadbal :returns: A table containing the local loadbal details """ - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'l' table.align['Value'] = 'l' table.add_row(['General properties', '----------']) @@ -104,8 +105,11 @@ def get_local_lb_table(load_balancer): table.add_row(['Service group %s' % index0, '**************']) index0 += 1 - table2 = Table(['Service group ID', 'Port', 'Allocation', - 'Routing type', 'Routing Method']) + table2 = formatting.Table(['Service group ID', + 'Port', + 'Allocation', + 'Routing type', + 'Routing Method']) for group in virtual_server['serviceGroups']: table2.add_row([ @@ -120,8 +124,13 @@ def get_local_lb_table(load_balancer): table.add_row([' Group Properties', table2]) - table3 = Table(['Service_ID', 'IP Address', 'Port', - 'Health Check', 'Weight', 'Enabled', 'Status']) + table3 = formatting.Table(['Service_ID', + 'IP Address', + 'Port', + 'Health Check', + 'Weight', + 'Enabled', + 'Status']) service_exist = False for service in group['services']: service_exist = True @@ -143,7 +152,7 @@ def get_local_lb_table(load_balancer): return table -class LoadBalancerList(CLIRunnable): +class LoadBalancerList(environment.CLIRunnable): """ usage: sl loadbal list [options] @@ -153,13 +162,13 @@ class LoadBalancerList(CLIRunnable): action = 'list' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) load_balancers = mgr.get_local_lbs() return get_local_lbs_table(load_balancers) -class LoadBalancerHealthChecks(CLIRunnable): +class LoadBalancerHealthChecks(environment.CLIRunnable): """ usage: sl loadbal health-checks [options] @@ -168,10 +177,10 @@ class LoadBalancerHealthChecks(CLIRunnable): action = 'health-checks' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) hc_types = mgr.get_hc_types() - table = KeyValueTable(['ID', 'Name']) + table = formatting.KeyValueTable(['ID', 'Name']) table.align['ID'] = 'l' table.align['Name'] = 'l' table.sortby = 'ID' @@ -180,7 +189,7 @@ def execute(self, args): return table -class LoadBalancerRoutingMethods(CLIRunnable): +class LoadBalancerRoutingMethods(environment.CLIRunnable): """ usage: sl loadbal routing-methods [options] @@ -189,10 +198,10 @@ class LoadBalancerRoutingMethods(CLIRunnable): action = 'routing-methods' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) routing_methods = mgr.get_routing_methods() - table = KeyValueTable(['ID', 'Name']) + table = formatting.KeyValueTable(['ID', 'Name']) table.align['ID'] = 'l' table.align['Name'] = 'l' table.sortby = 'ID' @@ -201,7 +210,7 @@ def execute(self, args): return table -class LoadBalancerRoutingTypes(CLIRunnable): +class LoadBalancerRoutingTypes(environment.CLIRunnable): """ usage: sl loadbal routing-types [options] @@ -210,10 +219,10 @@ class LoadBalancerRoutingTypes(CLIRunnable): action = 'routing-types' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) routing_types = mgr.get_routing_types() - table = KeyValueTable(['ID', 'Name']) + table = formatting.KeyValueTable(['ID', 'Name']) table.align['ID'] = 'l' table.align['Name'] = 'l' table.sortby = 'ID' @@ -222,7 +231,7 @@ def execute(self, args): return table -class LoadBalancerDetails(CLIRunnable): +class LoadBalancerDetails(environment.CLIRunnable): """ usage: sl loadbal detail [options] @@ -232,7 +241,7 @@ class LoadBalancerDetails(CLIRunnable): action = 'detail' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') @@ -243,7 +252,7 @@ def execute(self, args): return get_local_lb_table(load_balancer) -class LoadBalancerCancel(CLIRunnable): +class LoadBalancerCancel(environment.CLIRunnable): """ usage: sl loadbal cancel [options] @@ -254,21 +263,21 @@ class LoadBalancerCancel(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) loadbal_id = int(key_value[1]) - if args['--really'] or confirm("This action will cancel a load " - "balancer. Continue?"): + if args['--really'] or formatting.confirm("This action will cancel a " + "load balancer. Continue?"): mgr.cancel_lb(loadbal_id) return 'Load Balancer with id %s is being cancelled!' % input_id else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class LoadBalancerServiceDelete(CLIRunnable): +class LoadBalancerServiceDelete(environment.CLIRunnable): """ usage: sl loadbal service-delete [options] @@ -279,21 +288,22 @@ class LoadBalancerServiceDelete(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) service_id = int(key_value[1]) - if args['--really'] or confirm("This action will cancel a service " - "from your load balancer. Continue?"): + if args['--really'] or formatting.confirm("This action will cancel a " + "service from your load " + "balancer. Continue?"): mgr.delete_service(service_id) return 'Load balancer service %s is being cancelled!' % input_id else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class LoadBalancerServiceToggle(CLIRunnable): +class LoadBalancerServiceToggle(environment.CLIRunnable): """ usage: sl loadbal service-toggle [options] @@ -304,21 +314,22 @@ class LoadBalancerServiceToggle(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) service_id = int(key_value[1]) - if args['--really'] or confirm("This action will toggle the service " - "status on the service. Continue?"): + if args['--really'] or formatting.confirm("This action will toggle " + "the status on the service. " + "Continue?"): mgr.toggle_service_status(service_id) return 'Load balancer service %s status updated!' % input_id else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class LoadBalancerServiceEdit(CLIRunnable): +class LoadBalancerServiceEdit(environment.CLIRunnable): """ usage: sl loadbal service-edit [options] @@ -334,7 +345,7 @@ class LoadBalancerServiceEdit(CLIRunnable): action = 'service-edit' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -365,7 +376,7 @@ def execute(self, args): return 'Load balancer service %s is being modified!' % input_id -class LoadBalancerServiceAdd(CLIRunnable): +class LoadBalancerServiceAdd(environment.CLIRunnable): """ usage: sl loadbal service-add --ip=IP --port=PORT \ --weight=WEIGHT --hc_type=HCTYPE --enabled=ENABLED [options] @@ -382,7 +393,7 @@ class LoadBalancerServiceAdd(CLIRunnable): action = 'service-add' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -406,7 +417,7 @@ def execute(self, args): return 'Load balancer service is being added!' -class LoadBalancerServiceGroupDelete(CLIRunnable): +class LoadBalancerServiceGroupDelete(environment.CLIRunnable): """ usage: sl loadbal group-delete [options] @@ -417,21 +428,21 @@ class LoadBalancerServiceGroupDelete(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) group_id = int(key_value[1]) - if args['--really'] or confirm("This action will cancel a service" - " group. Continue?"): + if args['--really'] or formatting.confirm("This action will cancel a " + "service group. Continue?"): mgr.delete_service_group(group_id) return 'Service group %s is being deleted!' % input_id else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class LoadBalancerServiceGroupEdit(CLIRunnable): +class LoadBalancerServiceGroupEdit(environment.CLIRunnable): """ usage: sl loadbal group-edit [options] @@ -446,7 +457,7 @@ class LoadBalancerServiceGroupEdit(CLIRunnable): action = 'group-edit' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -471,7 +482,7 @@ def execute(self, args): return 'Load balancer service group %s is being updated!' % input_id -class LoadBalancerServiceGroupReset(CLIRunnable): +class LoadBalancerServiceGroupReset(environment.CLIRunnable): """ usage: sl loadbal group-reset [options] @@ -481,7 +492,7 @@ class LoadBalancerServiceGroupReset(CLIRunnable): action = 'group-reset' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -492,7 +503,7 @@ def execute(self, args): return 'Load balancer service group connections are being reset!' -class LoadBalancerServiceGroupAdd(CLIRunnable): +class LoadBalancerServiceGroupAdd(environment.CLIRunnable): """ usage: sl loadbal group-add --allocation=PERC --port=PORT \ --routing_type=TYPE --routing_method=METHOD [options] @@ -508,7 +519,7 @@ class LoadBalancerServiceGroupAdd(CLIRunnable): action = 'group-add' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -523,7 +534,7 @@ def execute(self, args): return 'Load balancer service group is being added!' -class LoadBalancerCreate(CLIRunnable): +class LoadBalancerCreate(environment.CLIRunnable): """ usage: sl loadbal create (--datacenter=DC) [options] @@ -538,17 +549,17 @@ class LoadBalancerCreate(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = LoadBalancerManager(self.client) - input_id = resolve_id( + mgr = SoftLayer.LoadBalancerManager(self.client) + input_id = helpers.resolve_id( mgr.resolve_ids, args.get(''), 'load_balancer') - if not confirm("This action will incur charges on your account. " - "Continue?"): - raise CLIAbort('Aborted.') + if not formatting.confirm("This action will incur charges on your " + "account. Continue?"): + raise exceptions.CLIAbort('Aborted.') mgr.add_local_lb(input_id, datacenter=args['--datacenter']) return "Load balancer is being created!" -class CreateOptionsLoadBalancer(CLIRunnable): +class CreateOptionsLoadBalancer(environment.CLIRunnable): """ usage: sl loadbal create-options @@ -558,9 +569,9 @@ class CreateOptionsLoadBalancer(CLIRunnable): action = 'create-options' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) - table = Table(['id', 'capacity', 'description', 'price']) + table = formatting.Table(['id', 'capacity', 'description', 'price']) table.sortby = 'price' table.align['price'] = 'r' diff --git a/SoftLayer/CLI/modules/messaging.py b/SoftLayer/CLI/modules/messaging.py index 625466f8d..0c39a8ad0 100644 --- a/SoftLayer/CLI/modules/messaging.py +++ b/SoftLayer/CLI/modules/messaging.py @@ -31,9 +31,10 @@ # pylint: disable=C0111 import sys -from SoftLayer import MessagingManager -from SoftLayer.CLI import CLIRunnable, Table -from SoftLayer.CLI.helpers import CLIAbort, listing, ArgumentError, blank +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting COMMON_MESSAGING_ARGS = """Service Options: @@ -42,7 +43,7 @@ """ -class ListAccounts(CLIRunnable): +class ListAccounts(environment.CLIRunnable): """ usage: sl messaging accounts-list [options] @@ -52,10 +53,10 @@ class ListAccounts(CLIRunnable): action = 'accounts-list' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) accounts = manager.list_accounts() - table = Table([ + table = formatting.Table([ 'id', 'name', 'status' ]) for account in accounts: @@ -71,7 +72,7 @@ def execute(self, args): return table -class ListEndpoints(CLIRunnable): +class ListEndpoints(environment.CLIRunnable): """ usage: sl messaging endpoints-list [options] @@ -81,23 +82,23 @@ class ListEndpoints(CLIRunnable): action = 'endpoints-list' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) regions = manager.get_endpoints() - table = Table([ + table = formatting.Table([ 'name', 'public', 'private' ]) for region, endpoints in regions.items(): table.add_row([ region, - endpoints.get('public') or blank(), - endpoints.get('private') or blank(), + endpoints.get('public') or formatting.blank(), + endpoints.get('private') or formatting.blank(), ]) return table -class Ping(CLIRunnable): +class Ping(environment.CLIRunnable): __doc__ = """ usage: sl messaging ping [options] @@ -107,25 +108,25 @@ class Ping(CLIRunnable): action = 'ping' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) okay = manager.ping( datacenter=args['--datacenter'], network=args['--network']) if okay: return 'OK' else: - CLIAbort('Ping failed') + exceptions.CLIAbort('Ping failed') def queue_table(queue): """ Returns a table with details about a queue """ - table = Table(['property', 'value']) + table = formatting.Table(['property', 'value']) table.align['property'] = 'r' table.align['value'] = 'l' table.add_row(['name', queue['name']]) table.add_row(['message_count', queue['message_count']]) table.add_row(['visible_message_count', queue['visible_message_count']]) - table.add_row(['tags', listing(queue['tags'] or [])]) + table.add_row(['tags', formatting.listing(queue['tags'] or [])]) table.add_row(['expiration', queue['expiration']]) table.add_row(['visibility_interval', queue['visibility_interval']]) return table @@ -133,7 +134,7 @@ def queue_table(queue): def message_table(message): """ Returns a table with details about a message """ - table = Table(['property', 'value']) + table = formatting.Table(['property', 'value']) table.align['property'] = 'r' table.align['value'] = 'l' @@ -147,18 +148,18 @@ def message_table(message): def topic_table(topic): """ Returns a table with details about a topic """ - table = Table(['property', 'value']) + table = formatting.Table(['property', 'value']) table.align['property'] = 'r' table.align['value'] = 'l' table.add_row(['name', topic['name']]) - table.add_row(['tags', listing(topic['tags'] or [])]) + table.add_row(['tags', formatting.listing(topic['tags'] or [])]) return table def subscription_table(sub): """ Returns a table with details about a subscription """ - table = Table(['property', 'value']) + table = formatting.Table(['property', 'value']) table.align['property'] = 'r' table.align['value'] = 'l' @@ -169,7 +170,7 @@ def subscription_table(sub): return table -class QueueList(CLIRunnable): +class QueueList(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-list [options] @@ -179,12 +180,12 @@ class QueueList(CLIRunnable): action = 'queue-list' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) queues = mq_client.get_queues()['items'] - table = Table([ + table = formatting.Table([ 'name', 'message_count', 'visible_message_count' ]) for queue in queues: @@ -196,7 +197,7 @@ def execute(self, args): return table -class QueueDetail(CLIRunnable): +class QueueDetail(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-detail [options] @@ -206,13 +207,13 @@ class QueueDetail(CLIRunnable): action = 'queue-detail' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) queue = mq_client.get_queue(args['']) return queue_table(queue) -class QueueCreate(CLIRunnable): +class QueueCreate(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-add [options] @@ -228,7 +229,7 @@ class QueueCreate(CLIRunnable): action = 'queue-add' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) tags = None if args.get('--tags'): @@ -243,7 +244,7 @@ def execute(self, args): return queue_table(queue) -class QueueModify(CLIRunnable): +class QueueModify(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-edit [options] @@ -259,7 +260,7 @@ class QueueModify(CLIRunnable): action = 'queue-edit' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) tags = None if args.get('--tags'): @@ -274,7 +275,7 @@ def execute(self, args): return queue_table(queue) -class QueueDelete(CLIRunnable): +class QueueDelete(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-remove [] [options] @@ -288,7 +289,7 @@ class QueueDelete(CLIRunnable): action = 'queue-remove' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) if args['']: @@ -298,7 +299,7 @@ def execute(self, args): mq_client.delete_queue(args[''], args.get('--force')) -class QueuePush(CLIRunnable): +class QueuePush(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-push ( | -) [options] @@ -312,7 +313,7 @@ class QueuePush(CLIRunnable): action = 'queue-push' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) body = '' if args[''] == '-': @@ -323,7 +324,7 @@ def execute(self, args): mq_client.push_queue_message(args[''], body)) -class QueuePop(CLIRunnable): +class QueuePop(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-pop [options] @@ -337,7 +338,7 @@ class QueuePop(CLIRunnable): action = 'queue-pop' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) messages = mq_client.pop_messages( @@ -355,7 +356,7 @@ def execute(self, args): return formatted_messages -class TopicList(CLIRunnable): +class TopicList(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-list [options] @@ -365,17 +366,17 @@ class TopicList(CLIRunnable): action = 'topic-list' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) topics = mq_client.get_topics()['items'] - table = Table(['name']) + table = formatting.Table(['name']) for topic in topics: table.add_row([topic['name']]) return table -class TopicDetail(CLIRunnable): +class TopicDetail(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-detail [options] @@ -385,7 +386,7 @@ class TopicDetail(CLIRunnable): action = 'topic-detail' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) topic = mq_client.get_topic(args['']) subscriptions = mq_client.get_subscriptions(args['']) @@ -395,7 +396,7 @@ def execute(self, args): return [topic_table(topic), tables] -class TopicCreate(CLIRunnable): +class TopicCreate(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-add [options] @@ -405,7 +406,7 @@ class TopicCreate(CLIRunnable): action = 'topic-add' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) tags = None if args.get('--tags'): @@ -421,7 +422,7 @@ def execute(self, args): return topic_table(topic) -class TopicDelete(CLIRunnable): +class TopicDelete(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-remove [options] @@ -434,12 +435,12 @@ class TopicDelete(CLIRunnable): action = 'topic-remove' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) mq_client.delete_topic(args[''], args.get('--force')) -class TopicSubscribe(CLIRunnable): +class TopicSubscribe(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-subscribe [options] @@ -456,7 +457,7 @@ class TopicSubscribe(CLIRunnable): action = 'topic-subscribe' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) if args['--type'] == 'queue': subscription = mq_client.create_subscription( @@ -473,12 +474,12 @@ def execute(self, args): body=args['--http-body'] ) else: - raise ArgumentError( + raise exceptions.ArgumentError( '--type should be either queue or http.') return subscription_table(subscription) -class TopicUnsubscribe(CLIRunnable): +class TopicUnsubscribe(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-unsubscribe [options] @@ -489,7 +490,7 @@ class TopicUnsubscribe(CLIRunnable): action = 'topic-unsubscribe' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) mq_client.delete_subscription( @@ -497,7 +498,7 @@ def execute(self, args): args['']) -class TopicPush(CLIRunnable): +class TopicPush(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-push ( | -) [options] @@ -508,7 +509,7 @@ class TopicPush(CLIRunnable): action = 'topic-push' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) # the message body comes from the positional argument or stdin diff --git a/SoftLayer/CLI/modules/metadata.py b/SoftLayer/CLI/modules/metadata.py index 8d0b8ccfa..82539d2de 100644 --- a/SoftLayer/CLI/modules/metadata.py +++ b/SoftLayer/CLI/modules/metadata.py @@ -22,18 +22,21 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer import MetadataManager, TransportError -from SoftLayer.CLI import CLIRunnable, KeyValueTable, listing, CLIAbort +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting -class MetaRunnable(CLIRunnable): +class MetaRunnable(environment.CLIRunnable): """ A CLIRunnable that raises a nice error on connection issues because - the metadata service is only accessable on a SoftLayer device """ + the metadata service is only accessable on a SoftLayer device + """ def execute(self, args): try: return self._execute(args) - except TransportError: - raise CLIAbort( + except SoftLayer.TransportError: + raise exceptions.CLIAbort( 'Cannot connect to the backend service address. Make sure ' 'this command is being ran from a device on the backend ' 'network.') @@ -52,7 +55,8 @@ class BackendMacAddresses(MetaRunnable): action = 'backend_mac' def _execute(self, _): - return listing(MetadataManager().get('backend_mac'), separator=',') + backend_macs = SoftLayer.MetadataManager().get('backend_mac') + return formatting.listing(backend_macs, separator=',') class Datacenter(MetaRunnable): @@ -64,7 +68,7 @@ class Datacenter(MetaRunnable): action = 'datacenter' def _execute(self, _): - return MetadataManager().get('datacenter') + return SoftLayer.MetadataManager().get('datacenter') class DatacenterId(MetaRunnable): @@ -76,7 +80,7 @@ class DatacenterId(MetaRunnable): action = 'datacenter_id' def _execute(self, _): - return MetadataManager().get('datacenter_id') + return SoftLayer.MetadataManager().get('datacenter_id') class FrontendMacAddresses(MetaRunnable): @@ -88,7 +92,8 @@ class FrontendMacAddresses(MetaRunnable): action = 'frontend_mac' def _execute(self, _): - return listing(MetadataManager().get('frontend_mac'), separator=',') + frontend_macs = SoftLayer.MetadataManager().get('frontend_mac') + return formatting.listing(frontend_macs, separator=',') class FullyQualifiedDomainName(MetaRunnable): @@ -100,7 +105,7 @@ class FullyQualifiedDomainName(MetaRunnable): action = 'fqdn' def _execute(self, _): - return MetadataManager().get('fqdn') + return SoftLayer.MetadataManager().get('fqdn') class Hostname(MetaRunnable): @@ -112,7 +117,7 @@ class Hostname(MetaRunnable): action = 'hostname' def _execute(self, _): - return MetadataManager().get('hostname') + return SoftLayer.MetadataManager().get('hostname') class Id(MetaRunnable): @@ -124,7 +129,7 @@ class Id(MetaRunnable): action = 'id' def _execute(self, _): - return MetadataManager().get('id') + return SoftLayer.MetadataManager().get('id') class PrimaryBackendIpAddress(MetaRunnable): @@ -136,7 +141,7 @@ class PrimaryBackendIpAddress(MetaRunnable): action = 'backend_ip' def _execute(self, _): - return MetadataManager().get('primary_backend_ip') + return SoftLayer.MetadataManager().get('primary_backend_ip') class PrimaryIpAddress(MetaRunnable): @@ -148,7 +153,7 @@ class PrimaryIpAddress(MetaRunnable): action = 'ip' def _execute(self, _): - return MetadataManager().get('primary_ip') + return SoftLayer.MetadataManager().get('primary_ip') class ProvisionState(MetaRunnable): @@ -160,7 +165,7 @@ class ProvisionState(MetaRunnable): action = 'provision_state' def _execute(self, _): - return MetadataManager().get('provision_state') + return SoftLayer.MetadataManager().get('provision_state') class Tags(MetaRunnable): @@ -172,10 +177,11 @@ class Tags(MetaRunnable): action = 'tags' def _execute(self, _): - return listing(MetadataManager().get('tags'), separator=',') + return formatting.listing(SoftLayer.MetadataManager().get('tags'), + separator=',') -class UserMetadata(CLIRunnable): +class UserMetadata(environment.CLIRunnable): """ usage: sl metadata user_data [options] @@ -185,11 +191,11 @@ class UserMetadata(CLIRunnable): def _execute(self, _): """ Returns user metadata """ - userdata = MetadataManager().get('user_data') + userdata = SoftLayer.MetadataManager().get('user_data') if userdata: return userdata else: - raise CLIAbort("No user metadata.") + raise exceptions.CLIAbort("No user metadata.") class Network(MetaRunnable): @@ -201,37 +207,37 @@ class Network(MetaRunnable): action = 'network' def _execute(self, args): - meta = MetadataManager() + meta = SoftLayer.MetadataManager() if args['']: - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' network = meta.public_network() table.add_row([ 'mac addresses', - listing(network['mac_addresses'], separator=',')]) + formatting.listing(network['mac_addresses'], separator=',')]) table.add_row([ 'router', network['router']]) table.add_row([ - 'vlans', listing(network['vlans'], separator=',')]) + 'vlans', formatting.listing(network['vlans'], separator=',')]) table.add_row([ 'vlan ids', - listing(network['vlan_ids'], separator=',')]) + formatting.listing(network['vlan_ids'], separator=',')]) return table if args['']: - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' network = meta.private_network() table.add_row([ 'mac addresses', - listing(network['mac_addresses'], separator=',')]) + formatting.listing(network['mac_addresses'], separator=',')]) table.add_row([ 'router', network['router']]) table.add_row([ - 'vlans', listing(network['vlans'], separator=',')]) + 'vlans', formatting.listing(network['vlans'], separator=',')]) table.add_row([ 'vlan ids', - listing(network['vlan_ids'], separator=',')]) + formatting.listing(network['vlan_ids'], separator=',')]) return table diff --git a/SoftLayer/CLI/modules/nas.py b/SoftLayer/CLI/modules/nas.py index cfc59804e..860e7bf09 100644 --- a/SoftLayer/CLI/modules/nas.py +++ b/SoftLayer/CLI/modules/nas.py @@ -8,12 +8,12 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer.CLI import CLIRunnable, Table, FormattedItem -from SoftLayer.CLI.helpers import blank -from SoftLayer.utils import lookup +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils -class ListNAS(CLIRunnable): +class ListNAS(environment.CLIRunnable): """ usage: sl nas list [options] @@ -29,21 +29,22 @@ def execute(self, args): nas_accounts = account.getNasNetworkStorage( mask='eventCount,serviceResource[datacenter.name]') - table = Table(['id', 'datacenter', 'size', 'username', 'password', - 'server']) + table = formatting.Table(['id', 'datacenter', 'size', 'username', + 'password', 'server']) for nas_account in nas_accounts: table.add_row([ nas_account['id'], - lookup(nas_account, - 'serviceResource', - 'datacenter', - 'name') or blank(), - FormattedItem( - nas_account.get('capacityGb', blank()), + utils.lookup(nas_account, + 'serviceResource', + 'datacenter', + 'name') or formatting.blank(), + formatting.FormattedItem( + nas_account.get('capacityGb', formatting.blank()), "%dGB" % nas_account.get('capacityGb', 0)), - nas_account.get('username', blank()), - nas_account.get('password', blank()), - nas_account.get('serviceResourceBackendIpAddress', blank())]) + nas_account.get('username', formatting.blank()), + nas_account.get('password', formatting.blank()), + nas_account.get('serviceResourceBackendIpAddress', + formatting.blank())]) return table diff --git a/SoftLayer/CLI/modules/rwhois.py b/SoftLayer/CLI/modules/rwhois.py index 810cf2365..c036488a0 100644 --- a/SoftLayer/CLI/modules/rwhois.py +++ b/SoftLayer/CLI/modules/rwhois.py @@ -9,12 +9,13 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer import NetworkManager -from SoftLayer.CLI import CLIRunnable, KeyValueTable -from SoftLayer.CLI.helpers import CLIAbort +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting -class RWhoisEdit(CLIRunnable): +class RWhoisEdit(environment.CLIRunnable): """ usage: sl rwhois edit [options] @@ -40,7 +41,7 @@ class RWhoisEdit(CLIRunnable): action = 'edit' def execute(self, args): - mgr = NetworkManager(self.client) + mgr = SoftLayer.NetworkManager(self.client) update = { 'abuse_email': args.get('--abuse'), @@ -62,12 +63,13 @@ def execute(self, args): check = [x for x in update.values() if x is not None] if not check: - raise CLIAbort("You must specify at least one field to update.") + raise exceptions.CLIAbort( + "You must specify at least one field to update.") mgr.edit_rwhois(**update) # pylint: disable=W0142 -class RWhoisShow(CLIRunnable): +class RWhoisShow(environment.CLIRunnable): """ usage: sl rwhois show [options] @@ -76,10 +78,10 @@ class RWhoisShow(CLIRunnable): action = 'show' def execute(self, args): - mgr = NetworkManager(self.client) + mgr = SoftLayer.NetworkManager(self.client) result = mgr.get_rwhois() - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' table.add_row(['Name', result['firstName'] + ' ' + result['lastName']]) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index b2e16852e..2ff78ca2c 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -23,17 +23,19 @@ hostname or the ip address for a piece of hardware. """ # :license: MIT, see LICENSE for more details. -import re import os -from os import linesep -from SoftLayer.CLI.helpers import ( - CLIRunnable, Table, KeyValueTable, FormattedItem, NestedDict, CLIAbort, - blank, listing, gb, active_txn, no_going_back, resolve_id, confirm, - ArgumentError, update_with_template_args, export_to_template) -from SoftLayer import HardwareManager, SshKeyManager +import re +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer.CLI import template +from SoftLayer import utils -class ListServers(CLIRunnable): + +class ListServers(environment.CLIRunnable): """ usage: sl server list [options] @@ -63,7 +65,7 @@ class ListServers(CLIRunnable): action = 'list' def execute(self, args): - manager = HardwareManager(self.client) + manager = SoftLayer.HardwareManager(self.client) tags = None if args.get('--tags'): @@ -78,7 +80,7 @@ def execute(self, args): nic_speed=args.get('--network'), tags=tags) - table = Table([ + table = formatting.Table([ 'id', 'datacenter', 'host', @@ -91,22 +93,22 @@ def execute(self, args): table.sortby = args.get('--sortby') or 'host' for server in servers: - server = NestedDict(server) + server = utils.NestedDict(server) table.add_row([ server['id'], - server['datacenter']['name'] or blank(), + server['datacenter']['name'] or formatting.blank(), server['fullyQualifiedDomainName'], server['processorPhysicalCoreAmount'], - gb(server['memoryCapacity'] or 0), - server['primaryIpAddress'] or blank(), - server['primaryBackendIpAddress'] or blank(), - active_txn(server), + formatting.gb(server['memoryCapacity'] or 0), + server['primaryIpAddress'] or formatting.blank(), + server['primaryBackendIpAddress'] or formatting.blank(), + formatting.active_txn(server), ]) return table -class ServerDetails(CLIRunnable): +class ServerDetails(environment.CLIRunnable): """ usage: sl server detail [--passwords] [--price] [options] @@ -119,39 +121,45 @@ class ServerDetails(CLIRunnable): action = 'detail' def execute(self, args): - hardware = HardwareManager(self.client) + hardware = SoftLayer.HardwareManager(self.client) - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' - hardware_id = resolve_id( + hardware_id = helpers.resolve_id( hardware.resolve_ids, args.get(''), 'hardware') result = hardware.get_hardware(hardware_id) - result = NestedDict(result) + result = utils.NestedDict(result) table.add_row(['id', result['id']]) table.add_row(['hostname', result['fullyQualifiedDomainName']]) table.add_row(['status', result['hardwareStatus']['status']]) - table.add_row(['datacenter', result['datacenter']['name'] or blank()]) + table.add_row(['datacenter', + result['datacenter']['name'] or formatting.blank()]) table.add_row(['cores', result['processorPhysicalCoreAmount']]) - table.add_row(['memory', gb(result['memoryCapacity'])]) - table.add_row(['public_ip', result['primaryIpAddress'] or blank()]) - table.add_row( - ['private_ip', result['primaryBackendIpAddress'] or blank()]) + table.add_row(['memory', + formatting.gb(result['memoryCapacity'])]) + table.add_row(['public_ip', + result['primaryIpAddress'] or formatting.blank()]) + table.add_row(['private_ip', + result['primaryBackendIpAddress'] + or formatting.blank()]) table.add_row(['ipmi_ip', - result['networkManagementIpAddress'] or blank()]) + result['networkManagementIpAddress'] + or formatting.blank()]) table.add_row([ 'os', - FormattedItem( + formatting.FormattedItem( result['operatingSystem']['softwareLicense'] - ['softwareDescription']['referenceCode'] or blank(), + ['softwareDescription']['referenceCode'] or formatting.blank(), result['operatingSystem']['softwareLicense'] - ['softwareDescription']['name'] or blank() + ['softwareDescription']['name'] or formatting.blank() )]) - table.add_row(['created', result['provisionDate'] or blank()]) + table.add_row(['created', + result['provisionDate'] or formatting.blank()]) - vlan_table = Table(['type', 'number', 'id']) + vlan_table = formatting.Table(['type', 'number', 'id']) for vlan in result['networkVlans']: vlan_table.add_row([ vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) @@ -169,19 +177,19 @@ def execute(self, args): for item in result['operatingSystem']['passwords']: user_strs.append( "%s %s" % (item['username'], item['password'])) - table.add_row(['users', listing(user_strs)]) + table.add_row(['users', formatting.listing(user_strs)]) tag_row = [] for tag in result['tagReferences']: tag_row.append(tag['tag']['name']) if tag_row: - table.add_row(['tags', listing(tag_row, separator=',')]) + table.add_row(['tags', formatting.listing(tag_row, separator=',')]) # Test to see if this actually has a primary (public) ip address if result['primaryIpAddress']: - ptr_domains = self.client['Hardware_Server']\ - .getReverseDomainRecords(id=hardware_id) + ptr_domains = (self.client['Hardware_Server'] + .getReverseDomainRecords(id=hardware_id)) for ptr_domain in ptr_domains: for ptr in ptr_domain['resourceRecords']: @@ -190,7 +198,7 @@ def execute(self, args): return table -class ServerReload(CLIRunnable): +class ServerReload(environment.CLIRunnable): """ usage: sl server reload [--key=KEY...] [options] @@ -207,22 +215,22 @@ class ServerReload(CLIRunnable): options = ['confirm'] def execute(self, args): - hardware = HardwareManager(self.client) - hardware_id = resolve_id( + hardware = SoftLayer.HardwareManager(self.client) + hardware_id = helpers.resolve_id( hardware.resolve_ids, args.get(''), 'hardware') keys = [] if args.get('--key'): for key in args.get('--key'): - key_id = resolve_id(SshKeyManager(self.client).resolve_ids, - key, 'SshKey') + resolver = SoftLayer.SshKeyManager(self.client).resolve_ids + key_id = helpers.resolve_id(resolver, key, 'SshKey') keys.append(key_id) - if args['--really'] or no_going_back(hardware_id): + if args['--really'] or formatting.no_going_back(hardware_id): hardware.reload(hardware_id, args['--postinstall'], keys) else: - CLIAbort('Aborted') + raise exceptions.CLIAbort('Aborted') -class CancelServer(CLIRunnable): +class CancelServer(environment.CLIRunnable): """ usage: sl server cancel [options] @@ -238,8 +246,8 @@ class CancelServer(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client) - hw_id = resolve_id( + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id( mgr.resolve_ids, args.get(''), 'hardware') comment = args.get('--comment') @@ -249,13 +257,13 @@ def execute(self, args): reason = args.get('--reason') - if args['--really'] or no_going_back(hw_id): + if args['--really'] or formatting.no_going_back(hw_id): mgr.cancel_hardware(hw_id, reason, comment) else: - CLIAbort('Aborted') + raise exceptions.CLIAbort('Aborted') -class ServerCancelReasons(CLIRunnable): +class ServerCancelReasons(environment.CLIRunnable): """ usage: sl server cancel-reasons @@ -265,11 +273,11 @@ class ServerCancelReasons(CLIRunnable): action = 'cancel-reasons' def execute(self, args): - table = Table(['Code', 'Reason']) + table = formatting.Table(['Code', 'Reason']) table.align['Code'] = 'r' table.align['Reason'] = 'l' - mgr = HardwareManager(self.client) + mgr = SoftLayer.HardwareManager(self.client) for code, reason in mgr.get_cancellation_reasons().items(): table.add_row([code, reason]) @@ -277,7 +285,7 @@ def execute(self, args): return table -class ServerPowerOff(CLIRunnable): +class ServerPowerOff(environment.CLIRunnable): """ usage: sl server power-off [options] @@ -287,17 +295,19 @@ class ServerPowerOff(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client) - hw_id = resolve_id(mgr.resolve_ids, args.get(''), - 'hardware') - if args['--really'] or confirm('This will power off the server with ' - 'id %s. Continue?' % hw_id): + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, + args.get(''), + 'hardware') + if args['--really'] or formatting.confirm('This will power off the ' + 'server with id %s ' + 'Continue?' % hw_id): self.client['Hardware_Server'].powerOff(id=hw_id) else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class ServerReboot(CLIRunnable): +class ServerReboot(environment.CLIRunnable): """ usage: sl server reboot [--hard | --soft] [options] @@ -312,11 +322,13 @@ class ServerReboot(CLIRunnable): def execute(self, args): hardware_server = self.client['Hardware_Server'] - mgr = HardwareManager(self.client) - hw_id = resolve_id(mgr.resolve_ids, args.get(''), - 'hardware') - if args['--really'] or confirm('This will power off the server with ' - 'id %s. Continue?' % hw_id): + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, + args.get(''), + 'hardware') + if args['--really'] or formatting.confirm('This will power off the ' + 'server with id %s. ' + 'Continue?' % hw_id): if args['--hard']: hardware_server.rebootHard(id=hw_id) elif args['--soft']: @@ -324,10 +336,10 @@ def execute(self, args): else: hardware_server.rebootDefault(id=hw_id) else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class ServerPowerOn(CLIRunnable): +class ServerPowerOn(environment.CLIRunnable): """ usage: sl server power-on [options] @@ -336,13 +348,14 @@ class ServerPowerOn(CLIRunnable): action = 'power-on' def execute(self, args): - mgr = HardwareManager(self.client) - hw_id = resolve_id(mgr.resolve_ids, args.get(''), - 'hardware') + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, + args.get(''), + 'hardware') self.client['Hardware_Server'].powerOn(id=hw_id) -class ServerPowerCycle(CLIRunnable): +class ServerPowerCycle(environment.CLIRunnable): """ usage: sl server power-cycle [options] @@ -352,18 +365,20 @@ class ServerPowerCycle(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client) - hw_id = resolve_id(mgr.resolve_ids, args.get(''), - 'hardware') - - if args['--really'] or confirm('This will power off the server with ' - 'id %s. Continue?' % hw_id): + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, + args.get(''), + 'hardware') + + if args['--really'] or formatting.confirm('This will power off the ' + 'server with id %s. ' + 'Continue?' % hw_id): self.client['Hardware_Server'].powerCycle(id=hw_id) else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class NicEditServer(CLIRunnable): +class NicEditServer(environment.CLIRunnable): """ usage: sl server nic-edit (public | private) --speed=SPEED [options] @@ -379,14 +394,15 @@ class NicEditServer(CLIRunnable): def execute(self, args): public = args['public'] - mgr = HardwareManager(self.client) - hw_id = resolve_id(mgr.resolve_ids, args.get(''), - 'hardware') + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, + args.get(''), + 'hardware') mgr.change_port_speed(hw_id, public, args['--speed']) -class ListChassisServer(CLIRunnable): +class ListChassisServer(environment.CLIRunnable): """ usage: sl server list-chassis [options] @@ -395,11 +411,11 @@ class ListChassisServer(CLIRunnable): action = 'list-chassis' def execute(self, args): - table = Table(['Code', 'Chassis']) + table = formatting.Table(['Code', 'Chassis']) table.align['Code'] = 'r' table.align['Chassis'] = 'l' - mgr = HardwareManager(self.client) + mgr = SoftLayer.HardwareManager(self.client) chassis = mgr.get_available_dedicated_server_packages() for chassis in chassis: @@ -408,7 +424,7 @@ def execute(self, args): return table -class ServerCreateOptions(CLIRunnable): +class ServerCreateOptions(environment.CLIRunnable): """ usage: sl server create-options [options] @@ -431,9 +447,9 @@ class ServerCreateOptions(CLIRunnable): 'controller'] def execute(self, args): - mgr = HardwareManager(self.client) + mgr = SoftLayer.HardwareManager(self.client) - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' @@ -446,7 +462,7 @@ def execute(self, args): break if not found: - raise CLIAbort('Invalid chassis specified.') + raise exceptions.CLIAbort('Invalid chassis specified.') ds_options = mgr.get_dedicated_server_create_options(chassis_id) @@ -467,12 +483,12 @@ def execute(self, args): if args['--datacenter'] or show_all: results = self.get_create_options(ds_options, 'datacenter')[0] - table.add_row([results[0], listing(sorted(results[1]))]) + table.add_row([results[0], formatting.listing(sorted(results[1]))]) if (args['--cpu'] or show_all) and not bmc: results = self.get_create_options(ds_options, 'cpu') - cpu_table = Table(['ID', 'Description']) + cpu_table = formatting.Table(['ID', 'Description']) cpu_table.align['ID'] = 'r' cpu_table.align['Description'] = 'l' @@ -483,16 +499,16 @@ def execute(self, args): if (args['--memory'] or show_all) and not bmc: results = self.get_create_options(ds_options, 'memory')[0] - table.add_row([results[0], listing( + table.add_row([results[0], formatting.listing( item[0] for item in sorted(results[1]))]) if bmc and (show_all or args['--memory'] or args['--cpu']): results = self.get_create_options(ds_options, 'server_core') - memory_cpu_table = Table(['memory', 'cpu']) + memory_cpu_table = formatting.Table(['memory', 'cpu']) for result in results: memory_cpu_table.add_row([ result[0], - listing( + formatting.listing( [item[0] for item in sorted( result[1], key=lambda x: int(x[0]) )])]) @@ -504,9 +520,9 @@ def execute(self, args): for result in results: table.add_row([ result[0], - listing( + formatting.listing( [item[0] for item in sorted(result[1])], - separator=linesep + separator=os.linesep )]) if args['--disk'] or show_all: @@ -514,22 +530,22 @@ def execute(self, args): table.add_row([ results[0], - listing( + formatting.listing( [item[0] for item in sorted(results[1])], - separator=linesep + separator=os.linesep )]) if args['--nic'] or show_all: results = self.get_create_options(ds_options, 'nic') for result in results: - table.add_row([result[0], listing( + table.add_row([result[0], formatting.listing( item[0] for item in sorted(result[1],))]) if (args['--controller'] or show_all) and not bmc: results = self.get_create_options(ds_options, 'disk_controller')[0] - table.add_row([results[0], listing( + table.add_row([results[0], formatting.listing( item[0] for item in sorted(results[1],))]) return table @@ -568,8 +584,8 @@ def get_create_options(self, ds_options, section, pretty=True): ram.append((int(option['capacity']), option['price_id'])) return_value = [('memory', ram)] - elif 'server_core' == section and \ - 'server_core' in ds_options['categories']: + elif ('server_core' == section + and 'server_core' in ds_options['categories']): mem_options = {} cpu_regex = re.compile(r'(\d+) x ') memory_regex = re.compile(r' - (\d+) GB Ram', re.I) @@ -740,7 +756,7 @@ def _generate_windows_code(self, description): return os_code -class CreateServer(CLIRunnable): +class CreateServer(environment.CLIRunnable): """ usage: sl server create [--disk=SIZE...] [--key=KEY...] [options] @@ -784,8 +800,8 @@ class CreateServer(CLIRunnable): '--memory', '--os'] def execute(self, args): - update_with_template_args(args, list_args=['--disk', '--key']) - mgr = HardwareManager(self.client) + template.update_with_template_args(args, list_args=['--disk', '--key']) + mgr = SoftLayer.HardwareManager(self.client) self._validate_args(args) ds_options = mgr.get_dedicated_server_create_options(args['--chassis']) @@ -799,7 +815,7 @@ def execute(self, args): if args.get('--test'): result = mgr.verify_order(**order) - table = Table(['Item', 'cost']) + table = formatting.Table(['Item', 'cost']) table.align['Item'] = 'r' table.align['cost'] = 'r' @@ -813,30 +829,31 @@ def execute(self, args): table.add_row(['Total monthly cost', "%.2f" % total]) output = [] output.append(table) - output.append(FormattedItem( + output.append(formatting.FormattedItem( '', ' -- ! Prices reflected here are retail and do not ' 'take account level discounts and are not guaranteed.')) if args['--export']: export_file = args.pop('--export') - export_to_template(export_file, args, exclude=['--wait', '--test']) + template.export_to_template(export_file, args, + exclude=['--wait', '--test']) return 'Successfully exported options to a template file.' if do_create: - if args['--really'] or confirm( + if args['--really'] or formatting.confirm( "This action will incur charges on your account. " "Continue?"): result = mgr.place_order(**order) - table = KeyValueTable(['name', 'value']) + table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' table.add_row(['id', result['orderId']]) table.add_row(['created', result['orderDate']]) output = table else: - raise CLIAbort('Aborting dedicated server order.') + raise exceptions.CLIAbort('Aborting dedicated server order.') return output @@ -845,7 +862,7 @@ def _process_args(self, args, ds_options): Helper method to centralize argument processing without convoluting code flow of the main execute method. """ - mgr = HardwareManager(self.client) + mgr = SoftLayer.HardwareManager(self.client) order = { 'hostname': args['--hostname'], @@ -866,7 +883,7 @@ def _process_args(self, args, ds_options): if os_price: order['os'] = os_price else: - raise CLIAbort('Invalid operating system specified.') + raise exceptions.CLIAbort('Invalid operating system specified.') order['location'] = args['--datacenter'] or 'FIRST_AVAILABLE' @@ -917,7 +934,7 @@ def _process_args(self, args, ds_options): if nic_price: order['port_speed'] = nic_price else: - raise CLIAbort('Invalid NIC speed specified.') + raise exceptions.CLIAbort('Invalid NIC speed specified.') if args.get('--postinstall'): order['post_uri'] = args.get('--postinstall') @@ -926,8 +943,8 @@ def _process_args(self, args, ds_options): if args.get('--key'): keys = [] for key in args.get('--key'): - key_id = resolve_id(SshKeyManager(self.client).resolve_ids, - key, 'SshKey') + resolver = SoftLayer.SshKeyManager(self.client).resolve_ids + key_id = helpers.resolve_id(resolver, key, 'SshKey') keys.append(key_id) order['ssh_keys'] = keys @@ -943,8 +960,8 @@ def _validate_args(self, args): """ Raises an ArgumentError if the given arguments are not valid """ invalid_args = [k for k in self.required_params if args.get(k) is None] if invalid_args: - raise ArgumentError('Missing required options: %s' - % ','.join(invalid_args)) + raise exceptions.ArgumentError('Missing required options: %s' + % ','.join(invalid_args)) def _get_default_value(self, ds_options, option): """ Returns a 'free' price id given an option """ @@ -1003,7 +1020,7 @@ def _get_price_id_from_options(self, ds_options, option, value, return item_options[2] -class EditServer(CLIRunnable): +class EditServer(environment.CLIRunnable): """ usage: sl server edit [options] @@ -1021,11 +1038,11 @@ def execute(self, args): data = {} if args['--userdata'] and args['--userfile']: - raise ArgumentError('[-u | --userdata] not allowed with ' - '[-F | --userfile]') + raise exceptions.ArgumentError( + '[-u | --userdata] not allowed with [-F | --userfile]') if args['--userfile']: if not os.path.exists(args['--userfile']): - raise ArgumentError( + raise exceptions.ArgumentError( 'File does not exist [-u | --userfile] = %s' % args['--userfile']) @@ -1038,8 +1055,9 @@ def execute(self, args): data['hostname'] = args.get('--hostname') data['domain'] = args.get('--domain') - mgr = HardwareManager(self.client) - hw_id = resolve_id(mgr.resolve_ids, args.get(''), - 'hardware') + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, + args.get(''), + 'hardware') if not mgr.edit(hw_id, **data): - raise CLIAbort("Failed to update hardware") + raise exceptions.CLIAbort("Failed to update hardware") diff --git a/SoftLayer/CLI/modules/snapshot.py b/SoftLayer/CLI/modules/snapshot.py index 21bfeeee6..a7d95b3b5 100644 --- a/SoftLayer/CLI/modules/snapshot.py +++ b/SoftLayer/CLI/modules/snapshot.py @@ -13,14 +13,15 @@ For several commands will be asked for.This can be the id of iSCSI volume or iSCSI snapshot. """ -from SoftLayer.CLI import (CLIRunnable, Table) -from SoftLayer.CLI.helpers import ( - ArgumentError, NestedDict, - resolve_id) -from SoftLayer import ISCSIManager +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer import utils -class CreateSnapshot(CLIRunnable): +class CreateSnapshot(environment.CLIRunnable): """ usage: sl snapshot create [options] @@ -38,16 +39,15 @@ class CreateSnapshot(CLIRunnable): action = 'create' def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) - iscsi_id = resolve_id( - iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') + iscsi_mgr = SoftLayer.ISCSIManager(self.client) + iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, + args.get(''), + 'iSCSI') notes = args.get('--notes') iscsi_mgr.create_snapshot(iscsi_id, notes) -class CreateSnapshotSpace(CLIRunnable): +class CreateSnapshotSpace(environment.CLIRunnable): """ usage: sl snapshot create-space [options] @@ -65,20 +65,19 @@ class CreateSnapshotSpace(CLIRunnable): required_params = ['--capacity'] def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) + iscsi_mgr = SoftLayer.ISCSIManager(self.client) invalid_args = [k for k in self.required_params if args.get(k) is None] if invalid_args: - raise ArgumentError('Missing required options: %s' - % ','.join(invalid_args)) - iscsi_id = resolve_id( - iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') + raise exceptions.ArgumentError('Missing required options: %s' + % ','.join(invalid_args)) + iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, + args.get(''), + 'iSCSI') capacity = args.get('--capacity') iscsi_mgr.create_snapshot_space(iscsi_id, capacity) -class CancelSnapshot(CLIRunnable): +class CancelSnapshot(environment.CLIRunnable): """ usage: sl snapshot cancel [options] @@ -89,15 +88,14 @@ class CancelSnapshot(CLIRunnable): action = 'cancel' def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) - snapshot_id = resolve_id( - iscsi_mgr.resolve_ids, - args.get(''), - 'Snapshot') + iscsi_mgr = SoftLayer.ISCSIManager(self.client) + snapshot_id = helpers.resolve_id(iscsi_mgr.resolve_ids, + args.get(''), + 'Snapshot') iscsi_mgr.delete_snapshot(snapshot_id) -class RestoreVolumeFromSnapshot(CLIRunnable): +class RestoreVolumeFromSnapshot(environment.CLIRunnable): """ usage: sl snapshot restore-volume @@ -108,17 +106,17 @@ class RestoreVolumeFromSnapshot(CLIRunnable): action = 'restore-volume' def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) - volume_id = resolve_id( - iscsi_mgr.resolve_ids, args.get(''), 'iSCSI') - snapshot_id = resolve_id( - iscsi_mgr.resolve_ids, - args.get(''), - 'Snapshot') + iscsi_mgr = SoftLayer.ISCSIManager(self.client) + volume_id = helpers.resolve_id(iscsi_mgr.resolve_ids, + args.get(''), + 'iSCSI') + snapshot_id = helpers.resolve_id(iscsi_mgr.resolve_ids, + args.get(''), + 'Snapshot') iscsi_mgr.restore_from_snapshot(volume_id, snapshot_id) -class ListSnapshots(CLIRunnable): +class ListSnapshots(environment.CLIRunnable): """ usage: sl snapshot list @@ -128,15 +126,16 @@ class ListSnapshots(CLIRunnable): action = 'list' def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) - iscsi_id = resolve_id( - iscsi_mgr.resolve_ids, args.get(''), 'iSCSI') + iscsi_mgr = SoftLayer.ISCSIManager(self.client) + iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, + args.get(''), + 'iSCSI') iscsi = self.client['Network_Storage_Iscsi'] snapshots = iscsi.getPartnerships( mask='volumeId,partnerVolumeId,createDate,type', id=iscsi_id) - snapshots = [NestedDict(n) for n in snapshots] + snapshots = [utils.NestedDict(n) for n in snapshots] - table = Table([ + table = formatting.Table([ 'id', 'createDate', 'name', @@ -149,5 +148,5 @@ def execute(self, args): snapshot['createDate'], snapshot['type']['name'], snapshot['type']['description'], - ]) + ]) return table diff --git a/SoftLayer/CLI/modules/sshkey.py b/SoftLayer/CLI/modules/sshkey.py index 88979191b..9649f355f 100644 --- a/SoftLayer/CLI/modules/sshkey.py +++ b/SoftLayer/CLI/modules/sshkey.py @@ -12,14 +12,16 @@ """ # :license: MIT, see LICENSE for more details. -from os.path import expanduser +from os import path -from SoftLayer import SshKeyManager -from SoftLayer.CLI import CLIRunnable, Table, no_going_back -from SoftLayer.CLI.helpers import CLIAbort, resolve_id, KeyValueTable +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers -class AddSshKey(CLIRunnable): +class AddSshKey(environment.CLIRunnable): """ usage: sl sshkey add ''' - @patch('SoftLayer.transports.requests.Session.send') + @mock.patch('SoftLayer.transports.requests.Session.send') def test_call(self, send): send().content = self.send_content @@ -44,43 +44,44 @@ def test_call(self, send): ''' - resp = make_xml_rpc_api_call( + resp = transports.make_xml_rpc_api_call( 'http://something.com/path/to/resource', 'getObject') args = send.call_args self.assertIsNotNone(args) args, kwargs = args - send.assert_called_with(ANY, proxies=None, timeout=None) + send.assert_called_with(mock.ANY, proxies=None, timeout=None) self.assertEqual(resp, []) - self.assertEquals(args[0].body, data) + self.assertEqual(args[0].body, data) def test_proxy_without_protocol(self): self.assertRaises( - TransportError, - make_xml_rpc_api_call, + SoftLayer.TransportError, + transports.make_xml_rpc_api_call, 'http://something.com/path/to/resource', 'getObject', 'localhost:3128') - @patch('SoftLayer.transports.requests.Session.send') + @mock.patch('SoftLayer.transports.requests.Session.send') def test_valid_proxy(self, send): send().content = self.send_content - make_xml_rpc_api_call('http://something.com/path/to/resource', - 'getObject', - proxy='http://localhost:3128') + transports.make_xml_rpc_api_call( + 'http://something.com/path/to/resource', + 'getObject', + proxy='http://localhost:3128') send.assert_called_with( - ANY, + mock.ANY, proxies={'https': 'http://localhost:3128', 'http': 'http://localhost:3128'}, timeout=None) -class TestRestAPICall(TestCase): +class TestRestAPICall(testing.TestCase): - @patch('SoftLayer.transports.requests.request') + @mock.patch('SoftLayer.transports.requests.request') def test_json(self, request): request().content = '{}' - resp = make_rest_api_call( + resp = transports.make_rest_api_call( 'GET', 'http://something.com/path/to/resource.json') self.assertEqual(resp, {}) request.assert_called_with( @@ -90,8 +91,8 @@ def test_json(self, request): timeout=None) # Test JSON Error - e = HTTPError('error') - e.response = MagicMock() + e = requests.HTTPError('error') + e.response = mock.MagicMock() e.response.status_code = 404 e.response.content = '''{ "error": "description", @@ -100,35 +101,36 @@ def test_json(self, request): request().raise_for_status.side_effect = e self.assertRaises( - SoftLayerAPIError, - make_rest_api_call, + SoftLayer.SoftLayerAPIError, + transports.make_rest_api_call, 'GET', 'http://something.com/path/to/resource.json') def test_proxy_without_protocol(self): self.assertRaises( - TransportError, - make_rest_api_call, + SoftLayer.TransportError, + transports.make_rest_api_call, 'GET' 'http://something.com/path/to/resource.txt', 'localhost:3128') - @patch('SoftLayer.transports.requests.request') + @mock.patch('SoftLayer.transports.requests.request') def test_valid_proxy(self, request): - make_rest_api_call('GET', - 'http://something.com/path/to/resource.txt', - proxy='http://localhost:3128') + transports.make_rest_api_call( + 'GET', + 'http://something.com/path/to/resource.txt', + proxy='http://localhost:3128') request.assert_called_with( 'GET', 'http://something.com/path/to/resource.txt', - headers=ANY, + headers=mock.ANY, proxies={'https': 'http://localhost:3128', 'http': 'http://localhost:3128'}, timeout=None) - @patch('SoftLayer.transports.requests.request') + @mock.patch('SoftLayer.transports.requests.request') def test_text(self, request): request().text = 'content' - resp = make_rest_api_call( + resp = transports.make_rest_api_call( 'GET', 'http://something.com/path/to/resource.txt') self.assertEqual(resp, 'content') request.assert_called_with( @@ -138,28 +140,28 @@ def test_text(self, request): timeout=None) # Test Text Error - e = HTTPError('error') - e.response = MagicMock() + e = requests.HTTPError('error') + e.response = mock.MagicMock() e.response.status_code = 404 e.response.content = 'Error Code' request().raise_for_status.side_effect = e self.assertRaises( - SoftLayerAPIError, - make_rest_api_call, + SoftLayer.SoftLayerAPIError, + transports.make_rest_api_call, 'GET', 'http://something.com/path/to/resource.txt') - @patch('SoftLayer.transports.requests.request') + @mock.patch('SoftLayer.transports.requests.request') def test_unknown_error(self, request): - e = RequestException('error') - e.response = MagicMock() + e = requests.RequestException('error') + e.response = mock.MagicMock() e.response.status_code = 404 e.response.content = 'Error Code' request().raise_for_status.side_effect = e self.assertRaises( - TransportError, - make_rest_api_call, + SoftLayer.TransportError, + transports.make_rest_api_call, 'GET', 'http://something.com/path/to/resource.txt') diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 5aa9f5e9c..364c60f24 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -5,21 +5,19 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.exceptions import ( - SoftLayerAPIError, NotWellFormed, UnsupportedEncoding, InvalidCharacter, - SpecViolation, MethodNotFound, InvalidMethodParameters, InternalError, - ApplicationError, RemoteSystemError, TransportError) -from SoftLayer.utils import xmlrpc_client +from SoftLayer import exceptions +from SoftLayer import utils +import json import logging + import requests -import json LOGGER = logging.getLogger(__name__) def _proxies_dict(proxy): - """ Makes a dict appropriate to pass to requests """ + """Makes a dict appropriate to pass to requests.""" if not proxy: return None return {'http': proxy, 'https': proxy} @@ -27,7 +25,7 @@ def _proxies_dict(proxy): def make_xml_rpc_api_call(uri, method, args=None, headers=None, http_headers=None, timeout=None, proxy=None): - """ Makes a SoftLayer API call against the XML-RPC endpoint + """Makes a SoftLayer API call against the XML-RPC endpoint. :param string uri: endpoint URL :param string method: method to call E.G.: 'getObject' @@ -41,9 +39,9 @@ def make_xml_rpc_api_call(uri, method, args=None, headers=None, largs = list(args) largs.insert(0, {'headers': headers}) - payload = xmlrpc_client.dumps(tuple(largs), - methodname=method, - allow_none=True) + payload = utils.xmlrpc_client.dumps(tuple(largs), + methodname=method, + allow_none=True) session = requests.Session() req = requests.Request('POST', uri, data=payload, headers=http_headers).prepare() @@ -59,34 +57,34 @@ def make_xml_rpc_api_call(uri, method, args=None, headers=None, LOGGER.debug(response.headers) LOGGER.debug(response.content) response.raise_for_status() - result = xmlrpc_client.loads(response.content,)[0][0] + result = utils.xmlrpc_client.loads(response.content,)[0][0] return result - except xmlrpc_client.Fault as ex: + except utils.xmlrpc_client.Fault as ex: # These exceptions are formed from the XML-RPC spec # http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php error_mapping = { - '-32700': NotWellFormed, - '-32701': UnsupportedEncoding, - '-32702': InvalidCharacter, - '-32600': SpecViolation, - '-32601': MethodNotFound, - '-32602': InvalidMethodParameters, - '-32603': InternalError, - '-32500': ApplicationError, - '-32400': RemoteSystemError, - '-32300': TransportError, + '-32700': exceptions.NotWellFormed, + '-32701': exceptions.UnsupportedEncoding, + '-32702': exceptions.InvalidCharacter, + '-32600': exceptions.SpecViolation, + '-32601': exceptions.MethodNotFound, + '-32602': exceptions.InvalidMethodParameters, + '-32603': exceptions.InternalError, + '-32500': exceptions.ApplicationError, + '-32400': exceptions.RemoteSystemError, + '-32300': exceptions.TransportError, } - raise error_mapping.get(ex.faultCode, SoftLayerAPIError)( + raise error_mapping.get(ex.faultCode, exceptions.SoftLayerAPIError)( ex.faultCode, ex.faultString) except requests.HTTPError as ex: - raise TransportError(ex.response.status_code, str(ex)) + raise exceptions.TransportError(ex.response.status_code, str(ex)) except requests.RequestException as ex: - raise TransportError(0, str(ex)) + raise exceptions.TransportError(0, str(ex)) def make_rest_api_call(method, url, http_headers=None, timeout=None, proxy=None): - """ Makes a SoftLayer API call against the REST endpoint + """Makes a SoftLayer API call against the REST endpoint. :param string method: HTTP method: GET, POST, PUT, DELETE :param string url: endpoint URL @@ -108,8 +106,10 @@ def make_rest_api_call(method, url, except requests.HTTPError as ex: if url.endswith('.json'): content = json.loads(ex.response.content) - raise SoftLayerAPIError(ex.response.status_code, content['error']) + raise exceptions.SoftLayerAPIError(ex.response.status_code, + content['error']) else: - raise SoftLayerAPIError(ex.response.status_code, ex.response.text) + raise exceptions.SoftLayerAPIError(ex.response.status_code, + ex.response.text) except requests.RequestException as ex: - raise TransportError(0, str(ex)) + raise exceptions.TransportError(0, str(ex)) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 126d9dcd8..e0d28239d 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -6,6 +6,7 @@ :license: MIT, see LICENSE for more details. """ import re + import six UUID_RE = re.compile(r'^[0-9a-f\-]{36}$', re.I) @@ -19,9 +20,10 @@ def lookup(dic, key, *keys): - """ A generic dictionary access helper. This helps simplify code that uses - heavily nested dictionaries. It will return None if any of the keys - in *keys do not exist. + """A generic dictionary access helper. + + This helps simplify code that uses heavily nested dictionaries. It will + return None if any of the keys in *keys do not exist. :: @@ -38,8 +40,11 @@ def lookup(dic, key, *keys): class NestedDict(dict): - """ This helps with accessing a heavily nested dictionary. Access to keys - which don't exist will result in a new, empty dictionary + """This helps with accessing a heavily nested dictionary. + + Dictionary where accessing keys that don't exist will return another + NestedDict object. + """ def __getitem__(self, key): @@ -48,9 +53,9 @@ def __getitem__(self, key): return self.setdefault(key, NestedDict()) def to_dict(self): - """ - Converts a NestedDict instance into a real dictionary. This is - needed for places where strict type checking is done. + """Converts a NestedDict instance into a real dictionary. + + This is needed for places where strict type checking is done. """ new_dict = {} for key, val in self.items(): @@ -62,8 +67,9 @@ def to_dict(self): def query_filter(query): - """ Translate a query-style string to a 'filter'. Query can be the - following formats: + """Translate a query-style string to a 'filter'. + + Query can be the following formats: Case Insensitive 'value' OR '*= value' Contains @@ -106,16 +112,20 @@ def query_filter(query): class IdentifierMixin(object): - """ This mixin provides an interface to provide multiple methods for - converting an 'indentifier' to an id """ + """Mixin used to resolve ids from other names of objects. + + This mixin provides an interface to provide multiple methods for + converting an 'indentifier' to an id + + """ resolvers = [] def resolve_ids(self, identifier): - """ Takes a string and tries to resolve to a list of matching ids. What - exactly 'identifier' can be depends on the resolvers + """Takes a string and tries to resolve to a list of matching ids. - :param string identifier: identifying string + What exactly 'identifier' can be depends on the resolvers + :param string identifier: identifying string :returns list: """ @@ -123,7 +133,7 @@ def resolve_ids(self, identifier): def resolve_ids(identifier, resolvers): - """ Resolves IDs given a list of functions + """Resolves IDs given a list of functions. :param string identifier: identifier string :param list resolvers: a list of functions diff --git a/setup.py b/setup.py index e49ee3b65..ad9be28a3 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ 'SoftLayer.CLI', 'SoftLayer.CLI.modules', 'SoftLayer.managers', + 'SoftLayer.testing', ], license='MIT', zip_safe=False, diff --git a/tox.ini b/tox.ini index 54378c7b5..8e68e57e4 100644 --- a/tox.ini +++ b/tox.ini @@ -18,13 +18,14 @@ commands = {envpython} setup.py nosetests [] [testenv:pep8] basepython = python2.7 deps = - flake8 - pep8-naming + hacking pylint commands = - flake8 --max-complexity=36 --statistics SoftLayer + flake8 --max-complexity=36 --statistics \ + --ignore=H401,H402,H404,H405 \ + SoftLayer pylint SoftLayer \ - --ignore=tests \ + --ignore=tests,testing \ -d R0903 \ # Too few public methods -d R0914 \ # Too many local variables -d R0201 \ # Method could be a function @@ -35,4 +36,11 @@ commands = --max-statements=86 \ --max-module-lines=1200 \ --max-returns=8 \ - --min-similarity-lines=50 # TODO: Remove + --min-similarity-lines=50 + pylint SoftLayer/testing \ + -d C0103 \ # Fixtures don't follow proper naming conventions + -d C0111 \ # Fixtures don't have docstrings + -d I0011 \ # Locally Disabling + --max-module-lines=2000 \ + --min-similarity-lines=50 \ + -r n From 3e8bc26d0f19fd9011109c7bf41399ec05689907 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 8 Jul 2014 21:34:16 -0500 Subject: [PATCH 0052/2667] Adds ability to create multiple virtual servers in one call/order --- SoftLayer/managers/vs.py | 15 +++++++++++---- SoftLayer/testing/fixtures/Virtual_Guest.py | 1 + SoftLayer/tests/managers/vs_tests.py | 13 +++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 71b312d27..708376818 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -445,17 +445,24 @@ def create_instance(self, **kwargs): this VS placed. :param int private_vlan: The ID of the public VLAN on which you want this VS placed. - :param bool bare_metal: Flag to indicate if this is a bare metal server - or a dedicated server (default). :param list disks: A list of disk capacities for this server. :param string post_uri: The URI of the post-install script to run after reload :param bool private: If true, the VS will be provisioned only with access to the private network. Defaults to false :param list ssh_keys: The SSH keys to add to the root user + :param int nic_speed: The port speed to set """ - create_options = self._generate_create_dict(**kwargs) - return self.guest.createObject(create_options) + return self.guest.createObject(self._generate_create_dict(**kwargs)) + + def create_instances(self, config_list): + """ Creates multiple virtual server instances + + This takes a list of dictionaries using the same arguments as + create_instance(). + """ + return self.guest.createObjects([self._generate_create_dict(**kwargs) + for kwargs in config_list]) def change_port_speed(self, instance_id, public, speed): """ Allows you to change the port speed of a virtual server's NICs. diff --git a/SoftLayer/testing/fixtures/Virtual_Guest.py b/SoftLayer/testing/fixtures/Virtual_Guest.py index c1c6d2d7e..4323882d5 100644 --- a/SoftLayer/testing/fixtures/Virtual_Guest.py +++ b/SoftLayer/testing/fixtures/Virtual_Guest.py @@ -218,6 +218,7 @@ setPrivateNetworkInterfaceSpeed = True setPublicNetworkInterfaceSpeed = True createObject = getObject +createObjects = True generateOrderTemplate = {} setUserMetadata = ['meta'] reloadOperatingSystem = 'OK' diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 8b8337ea4..46576d69a 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -142,6 +142,19 @@ def test_create_instance(self, create_dict): self.client['Virtual_Guest'].createObject.assert_called_once_with( {'test': 1, 'verify': 1}) + def test_create_instances(self): + self.vs.create_instances([{'cpus': 1, + 'memory': 1024, + 'hostname': 'server', + 'domain': 'example.com'}]) + self.client['Virtual_Guest'].createObjects.assert_called_once_with([ + {'domain': 'example.com', + 'hourlyBillingFlag': True, + 'localDiskFlag': True, + 'maxMemory': 1024, 'hostname': + 'server', + 'startCpus': 1}]) + def test_generate_os_and_image(self): self.assertRaises( ValueError, From 5d76462324bd914353ea4cd703113b5a727ee370 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 8 Jul 2014 23:46:20 -0500 Subject: [PATCH 0053/2667] Version bump to v3.2.0 --- CHANGELOG | 12 ++++++++++++ SoftLayer/consts.py | 2 +- docs/conf.py | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8b2c3e9bb..4e352b227 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,15 @@ +3.2.0 + + * CLI+API: Added firewall manager and CLI module + + * CLI+API: Added iscsi manager and CLI module + + * API: Added ability to create multiple virtual servers at once to VSManager + + * API: Added OrderingManager. Remove hard-coded price IDs + + * Fixed several small bug fixes + 3.1.0 * CLI+API: Added CDN manager and CLI module diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 4590baa93..6ee3ddc47 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v3.1.0' +VERSION = 'v3.2.0' 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/docs/conf.py b/docs/conf.py index 14b536f61..31889fed7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,9 +51,9 @@ # built documents. # # The short X.Y version. -version = '3.1.0' +version = '3.2.0' # The full version, including alpha/beta/rc tags. -release = '3.1.0' +release = '3.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 51cba64dcb28e3b1d137be56fef52ff0e67000be Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 8 Jul 2014 23:59:39 -0500 Subject: [PATCH 0054/2667] Version bump to v3.2.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ad9be28a3..c0c434b0e 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ setup( name='SoftLayer', - version='3.1.0', + version='3.2.0', description=description, long_description=long_description, author='SoftLayer Technologies, Inc.', From 6f5c229c28ce8466c961aec1bcb253e88875bc21 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 14 Jul 2014 09:08:28 -0500 Subject: [PATCH 0055/2667] Update README.rst --- README.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 4a8ecd9d8..220d19e3f 100644 --- a/README.rst +++ b/README.rst @@ -15,10 +15,7 @@ This library provides a simple interface to interact with SoftLayer's XML-RPC AP Documentation ------------- -Documentation is available at https://softlayer-python.readthedocs.org - -* API Client: https://softlayer-python.readthedocs.org/en/latest/api/client.html -* Command-line Interface: https://softlayer-python.readthedocs.org/en/latest/cli.html +Documentation is available at http://softlayer.github.io/softlayer-python/ Installation ------------ @@ -44,7 +41,7 @@ library. System Requirements ------------------- -* This library has been tested on Python 2.6, 2.7, 3.2 and 3.3. +* This library has been tested on Python 2.6, 2.7, 3.3 and 3.4. * A valid SoftLayer API username and key are required to call SoftLayer's API * A connection to SoftLayer's private network is required to connect to SoftLayer’s private network API endpoints. From d492b7796e8c763e13d5bb390f4c33bb85616161 Mon Sep 17 00:00:00 2001 From: suppandi Date: Mon, 21 Jul 2014 14:52:03 -0400 Subject: [PATCH 0056/2667] add loadbal to the cli help --- SoftLayer/CLI/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 10dc31fed..3aa5260e1 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -19,6 +19,7 @@ cdn Content Delivery Network service management dns Domain Name System firewall Firewall rule and security management + loadbal Load Balancer management globalip Global IP address management messaging Message Queue Service rwhois RWhoIs operations From 8bb98ca3e17e5cfd7395fd8235a37dcbf9a82d99 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 22 Jul 2014 10:48:56 -0500 Subject: [PATCH 0057/2667] Converts the integer IDs to strings to be printed correctly --- SoftLayer/CLI/modules/metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/modules/metadata.py b/SoftLayer/CLI/modules/metadata.py index 82539d2de..07f6c12f3 100644 --- a/SoftLayer/CLI/modules/metadata.py +++ b/SoftLayer/CLI/modules/metadata.py @@ -80,7 +80,7 @@ class DatacenterId(MetaRunnable): action = 'datacenter_id' def _execute(self, _): - return SoftLayer.MetadataManager().get('datacenter_id') + return str(SoftLayer.MetadataManager().get('datacenter_id')) class FrontendMacAddresses(MetaRunnable): @@ -129,7 +129,7 @@ class Id(MetaRunnable): action = 'id' def _execute(self, _): - return SoftLayer.MetadataManager().get('id') + return str(SoftLayer.MetadataManager().get('id')) class PrimaryBackendIpAddress(MetaRunnable): From b09db16b94742f9b0ce484fcb43124c012003562 Mon Sep 17 00:00:00 2001 From: boden Date: Wed, 23 Jul 2014 14:08:03 -0400 Subject: [PATCH 0058/2667] tag support for vs create / edit Adds the ability to specify tags both on the vs (aka cci) create instance CLI / call as well as modify tags on the edit CLI / call. Additionally this PR supports tags w/r/t exporting templates and create using the --like option. Finally unit tests are included. implements https://github.com/softlayer/softlayer-python/issues/354 --- SoftLayer/CLI/modules/vs.py | 12 ++++++++++ SoftLayer/managers/vs.py | 26 ++++++++++++++++++--- SoftLayer/testing/fixtures/Virtual_Guest.py | 4 ++-- SoftLayer/tests/CLI/modules/vs_tests.py | 3 ++- SoftLayer/tests/managers/vs_tests.py | 16 +++++++++++-- SoftLayer/utils.py | 17 ++++++++++++++ 6 files changed, 70 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index f868bd32c..05f421eb6 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -366,6 +366,7 @@ class CreateVS(environment.CLIRunnable): --test Do not create VS, just get a quote --export=FILE Exports options to a template file -F, --userfile=FILE Read userdata from file + -g --tag=TAG Comma list of tags to set or empty string to remove all -i, --postinstall=URI Post-install script to download (Only HTTPS executes, HTTP leaves file in /root) -k, --key=KEY SSH keys to add to the root user. Can be specified @@ -533,6 +534,12 @@ def _update_with_like_args(self, args): '--private': like_details['privateNetworkOnlyFlag'], } + tag_refs = like_details.get('tagReferences', None) + if tag_refs is not None and len(tag_refs) > 0: + tags = ','.join([t['tag']['name'] for t in tag_refs]) + like_args['--tag'] = tags + + # Handle mutually exclusive options like_image = utils.lookup(like_details, 'blockDeviceTemplateGroup', @@ -628,6 +635,9 @@ def _parse_create_args(self, args): if args.get('--vlan_private'): data['private_vlan'] = args['--vlan_private'] + if args.get('--tag'): + data['tag'] = args['--tag'] + return data @@ -970,6 +980,7 @@ class EditVS(environment.CLIRunnable): Options: -D --domain=DOMAIN Domain portion of the FQDN example: example.com -F --userfile=FILE Read userdata from file + -g --tag=TAG Comma list of tags to set or empty string to remove all -H --hostname=HOST Host portion of the FQDN. example: server -u --userdata=DATA User defined metadata string """ @@ -995,6 +1006,7 @@ def execute(self, args): data['hostname'] = args.get('--hostname') data['domain'] = args.get('--domain') + data['tag'] = args.get("--tag") vsi = SoftLayer.VSManager(self.client) vs_id = helpers.resolve_id(vsi.resolve_ids, diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 708376818..21c76e4f7 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -452,8 +452,14 @@ def create_instance(self, **kwargs): access to the private network. Defaults to false :param list ssh_keys: The SSH keys to add to the root user :param int nic_speed: The port speed to set + :param string tag: tags to set on the VS as a comma separated list. + Use the empty string to remove all tags. """ - return self.guest.createObject(self._generate_create_dict(**kwargs)) + tag, = utils.dict_extract(kwargs, {'tag': None}) + inst = self.guest.createObject(self._generate_create_dict(**kwargs)) + if tag is not None: + self.guest.setTags(tag, id=inst['id']) + return inst def create_instances(self, config_list): """ Creates multiple virtual server instances @@ -461,9 +467,18 @@ def create_instances(self, config_list): This takes a list of dictionaries using the same arguments as create_instance(). """ - return self.guest.createObjects([self._generate_create_dict(**kwargs) + tags = [utils.dict_extract(conf, {'tag': None})[0] + for conf in config_list] + + resp = self.guest.createObjects([self._generate_create_dict(**kwargs) for kwargs in config_list]) + for index in range(0, len(resp)): + if tags[index] is not None: + self.guest.setTags(tags[index], id=resp[index]['id']) + + return resp + def change_port_speed(self, instance_id, public, speed): """ Allows you to change the port speed of a virtual server's NICs. @@ -503,7 +518,7 @@ def _get_ids_from_ip(self, ip_address): return [result['id'] for result in results] def edit(self, instance_id, userdata=None, hostname=None, domain=None, - notes=None): + notes=None, tag=None): """ Edit hostname, domain name, notes, and/or the user data of a VS Parameters set to None will be ignored and not attempted to be updated. @@ -514,6 +529,8 @@ def edit(self, instance_id, userdata=None, hostname=None, domain=None, :param string hostname: valid hostname :param string domain: valid domain namem :param string notes: notes about this particular VS + :param string tag: tags to set on the VS as a comma separated list. + Use the empty string to remove all tags. """ @@ -521,6 +538,9 @@ def edit(self, instance_id, userdata=None, hostname=None, domain=None, if userdata: self.guest.setUserMetadata([userdata], id=instance_id) + if tag is not None: + self.guest.setTags(tag, id=instance_id) + if hostname: obj['hostname'] = hostname diff --git a/SoftLayer/testing/fixtures/Virtual_Guest.py b/SoftLayer/testing/fixtures/Virtual_Guest.py index 4323882d5..429549908 100644 --- a/SoftLayer/testing/fixtures/Virtual_Guest.py +++ b/SoftLayer/testing/fixtures/Virtual_Guest.py @@ -218,9 +218,9 @@ setPrivateNetworkInterfaceSpeed = True setPublicNetworkInterfaceSpeed = True createObject = getObject -createObjects = True +createObjects = [getObject] generateOrderTemplate = {} setUserMetadata = ['meta'] reloadOperatingSystem = 'OK' - +setTags = True createArchiveTransaction = {} diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index 2fbb6b61d..a479cf586 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -124,7 +124,8 @@ def test_create(self, confirm_mock): '--vlan_public': None, '--vlan_private': None, '--wait': None, - '--really': False}) + '--really': False, + '--tag': 'dev,green'}) self.assertEqual([{'guid': '1a2b3c-1701', 'id': 100, diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 46576d69a..89216d7f3 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -137,16 +137,19 @@ def test_create_verify(self, create_dict): @mock.patch('SoftLayer.managers.vs.VSManager._generate_create_dict') def test_create_instance(self, create_dict): create_dict.return_value = {'test': 1, 'verify': 1} - self.vs.create_instance(test=1, verify=1) + self.vs.create_instance(test=1, verify=1, tag='dev,green') create_dict.assert_called_once_with(test=1, verify=1) self.client['Virtual_Guest'].createObject.assert_called_once_with( {'test': 1, 'verify': 1}) + self.client['Virtual_Guest'].setTags.assert_called_once_with( + 'dev,green', id=100) def test_create_instances(self): self.vs.create_instances([{'cpus': 1, 'memory': 1024, 'hostname': 'server', - 'domain': 'example.com'}]) + 'domain': 'example.com', + 'tag': 'dev,green'}]) self.client['Virtual_Guest'].createObjects.assert_called_once_with([ {'domain': 'example.com', 'hourlyBillingFlag': True, @@ -154,6 +157,8 @@ def test_create_instances(self): 'maxMemory': 1024, 'hostname': 'server', 'startCpus': 1}]) + self.client['Virtual_Guest'].setTags.assert_called_once_with( + 'dev,green', id=100) def test_generate_os_and_image(self): self.assertRaises( @@ -532,6 +537,13 @@ def test_edit(self): self.vs.edit(100, **args) service.editObject.assert_called_once_with(args, id=100) + # Test tag support + self.vs.edit(100, tag='dev,green') + service.setTags.assert_called_once_with('dev,green', id=100) + service.setTags.reset_mock() + self.vs.edit(100, tag='') + service.setTags.assert_called_once_with('', id=100) + def test_captures(self): archive = self.client['Virtual_Guest'].createArchiveTransaction diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index e0d28239d..84b58946d 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -156,3 +156,20 @@ def resolve_ids(identifier, resolvers): return ids return [] + + +def dict_extract(dictionary, keys): + """Extracts & removes keys from a dict with a default value. + + :param dict dictionary: the target dictionary to operate on + :param dict keys: a dict who's keys specify the keys to extract + from the dictionary and values are the defaults to use if + the key is not in the target dict. + :returns tuple + """ + vals = [] + for key, default in keys.iteritems(): + vals.append(dictionary.get(key, default)) + if dictionary.get(key, None) is not None: + del dictionary[key] + return tuple(vals) From 62d1bcc0abaa902c4f8c0b3491037e4ab2f5fc94 Mon Sep 17 00:00:00 2001 From: boden Date: Wed, 23 Jul 2014 14:44:58 -0400 Subject: [PATCH 0059/2667] vm tag support updates Address code review comments as well as failing CI tests. --- SoftLayer/CLI/modules/vs.py | 3 +-- SoftLayer/managers/vs.py | 9 ++++----- SoftLayer/utils.py | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 05f421eb6..502253ed5 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -366,7 +366,7 @@ class CreateVS(environment.CLIRunnable): --test Do not create VS, just get a quote --export=FILE Exports options to a template file -F, --userfile=FILE Read userdata from file - -g --tag=TAG Comma list of tags to set or empty string to remove all + -g --tag=TAG Comma list of tags to set -i, --postinstall=URI Post-install script to download (Only HTTPS executes, HTTP leaves file in /root) -k, --key=KEY SSH keys to add to the root user. Can be specified @@ -538,7 +538,6 @@ def _update_with_like_args(self, args): if tag_refs is not None and len(tag_refs) > 0: tags = ','.join([t['tag']['name'] for t in tag_refs]) like_args['--tag'] = tags - # Handle mutually exclusive options like_image = utils.lookup(like_details, diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 21c76e4f7..13c757ee5 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -452,8 +452,7 @@ def create_instance(self, **kwargs): access to the private network. Defaults to false :param list ssh_keys: The SSH keys to add to the root user :param int nic_speed: The port speed to set - :param string tag: tags to set on the VS as a comma separated list. - Use the empty string to remove all tags. + :param string tag: tags to set on the VS as a comma separated list """ tag, = utils.dict_extract(kwargs, {'tag': None}) inst = self.guest.createObject(self._generate_create_dict(**kwargs)) @@ -473,9 +472,9 @@ def create_instances(self, config_list): resp = self.guest.createObjects([self._generate_create_dict(**kwargs) for kwargs in config_list]) - for index in range(0, len(resp)): - if tags[index] is not None: - self.guest.setTags(tags[index], id=resp[index]['id']) + for instance, tag in zip(resp, tags): + if tag is not None: + self.guest.setTags(tag, id=instance['id']) return resp diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 84b58946d..0162dc868 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -165,10 +165,10 @@ def dict_extract(dictionary, keys): :param dict keys: a dict who's keys specify the keys to extract from the dictionary and values are the defaults to use if the key is not in the target dict. - :returns tuple + :returns tuple """ vals = [] - for key, default in keys.iteritems(): + for key, default in keys.items(): vals.append(dictionary.get(key, default)) if dictionary.get(key, None) is not None: del dictionary[key] From d0a4042c455e3806b605e0daf94fc8aa7b557d71 Mon Sep 17 00:00:00 2001 From: Hans Kristian Moen Date: Fri, 25 Jul 2014 15:15:19 +0100 Subject: [PATCH 0060/2667] Add support for custom user agent string --- SoftLayer/API.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 0f1952882..e5569f8ae 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -44,6 +44,8 @@ class Client(object): :param auth: an object which responds to get_headers() to be inserted into the xml-rpc headers. Example: `BasicAuthentication` :param config_file: A path to a configuration file used to load settings + :param user_agent: an optional User Agent to report when making API + calls if you wish to bypass the packages built in User Agent string Usage: @@ -57,7 +59,7 @@ class Client(object): _prefix = "SoftLayer_" def __init__(self, username=None, api_key=None, endpoint_url=None, - timeout=None, auth=None, config_file=None, proxy=None): + timeout=None, auth=None, config_file=None, proxy=None, user_agent=None): settings = config.get_client_settings(username=username, api_key=api_key, @@ -75,6 +77,7 @@ def __init__(self, username=None, api_key=None, endpoint_url=None, self.proxy = None if settings.get('proxy'): self.proxy = settings.get('proxy') + self.user_agent = user_agent def authenticate_with_password(self, username, password, security_question_id=None, @@ -162,7 +165,7 @@ def call(self, service, method, *args, **kwargs): } http_headers = { - 'User-Agent': consts.USER_AGENT, + 'User-Agent': self.user_agent or consts.USER_AGENT, 'Content-Type': 'application/xml', } From e48aa080e553209b48627ad8812bf787340afa40 Mon Sep 17 00:00:00 2001 From: Robert Chumbley Date: Fri, 25 Jul 2014 15:19:48 -0500 Subject: [PATCH 0061/2667] The upgrade function errors out a lot due to a timeout. The timeout was happening during the _get_package_items function due to the object mask. I tweaked the mask and now the getItems call takes a few seconds rather than a few minutes. The response structure is slightly different, so I had to modify _get_item_id_for_upgrade to account for that. --- SoftLayer/managers/vs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 708376818..01bf0c42e 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -610,7 +610,7 @@ def _get_package_items(self): """ Following Method gets all the item ids related to VS """ - mask = "mask[description,capacity,prices.id,categories[name,id]]" + mask = "mask[description,capacity,prices[id,categories[name,id]]]" package_type = "VIRTUAL_SERVER_INSTANCE" package_id = self.ordering_manager.get_package_id_by_type(package_type) package_service = self.ordering_manager.get_package_service() @@ -628,8 +628,9 @@ def _get_item_id_for_upgrade(self, package_items, option, value, """ vs_id = {'memory': 3, 'cpus': 80, 'nic_speed': 26} for item in package_items: - for j in range(len(item['categories'])): - if not (item['categories'][j]['id'] == vs_id[option] and + categories = item['prices'][0]['categories'] + for j in range(len(categories)): + if not (categories[j]['id'] == vs_id[option] and item['capacity'] == str(value)): continue if option == 'cpus': From 43a0ff999d42a2c44e86a7bca7d1f49e9acebec9 Mon Sep 17 00:00:00 2001 From: Robert Chumbley Date: Mon, 28 Jul 2014 10:47:19 -0500 Subject: [PATCH 0062/2667] Modified the vs_tests and Product_Package to match the new result set returned by getItems. Categories are now under 'prices'. Before they were under 'item'. --- SoftLayer/testing/fixtures/Product_Package.py | 20 +++++++++---------- SoftLayer/tests/managers/vs_tests.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SoftLayer/testing/fixtures/Product_Package.py b/SoftLayer/testing/fixtures/Product_Package.py index e837298d4..f2621766e 100644 --- a/SoftLayer/testing/fixtures/Product_Package.py +++ b/SoftLayer/testing/fixtures/Product_Package.py @@ -1037,43 +1037,43 @@ def get_bmc_categories_mock(): getItems = [ { 'id': 1234, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 1122}], + 'prices': [{'id': 1122, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], }, { 'id': 2233, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 4477}], + 'prices': [{'id': 4477, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], }, { 'id': 1239, - 'categories': [{'id': 3, 'name': 'RAM'}], 'capacity': '2', 'description': 'RAM', 'itemCategory': {'categoryCode': 'RAM'}, - 'prices': [{'id': 1133}], + 'prices': [{'id': 1133, + 'categories': [{'id': 3, 'name': 'RAM'}]}], }, { 'id': 1240, - 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Private Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1007}], + 'prices': [{'id': 1007, + 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], }, { 'id': 1250, - 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1144}], + 'prices': [{'id': 1144, + 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], }, { 'id': 4439, diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 46576d69a..60238d0d8 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -574,7 +574,7 @@ def test_get_item_id_for_upgrade(self): item_id = 0 package_items = self.client['Product_Package'].getItems(id=46) for item in package_items: - if ((item['categories'][0]['id'] == 3) + if ((item['prices'][0]['categories'][0]['id'] == 3) and (item.get('capacity') == '2')): item_id = item['prices'][0]['id'] break From 19471ad2075eff2f8ad4b35392cee2ff335b9d27 Mon Sep 17 00:00:00 2001 From: Robert Chumbley Date: Mon, 28 Jul 2014 10:50:23 -0500 Subject: [PATCH 0063/2667] Revert "Modified the vs_tests and Product_Package to match the new result set returned by getItems. Categories are now under 'prices'. Before they were under 'item'." This reverts commit 43a0ff999d42a2c44e86a7bca7d1f49e9acebec9. --- SoftLayer/testing/fixtures/Product_Package.py | 20 +++++++++---------- SoftLayer/tests/managers/vs_tests.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SoftLayer/testing/fixtures/Product_Package.py b/SoftLayer/testing/fixtures/Product_Package.py index f2621766e..e837298d4 100644 --- a/SoftLayer/testing/fixtures/Product_Package.py +++ b/SoftLayer/testing/fixtures/Product_Package.py @@ -1037,43 +1037,43 @@ def get_bmc_categories_mock(): getItems = [ { 'id': 1234, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 1122, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], + 'prices': [{'id': 1122}], }, { 'id': 2233, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 4477, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], + 'prices': [{'id': 4477}], }, { 'id': 1239, + 'categories': [{'id': 3, 'name': 'RAM'}], 'capacity': '2', 'description': 'RAM', 'itemCategory': {'categoryCode': 'RAM'}, - 'prices': [{'id': 1133, - 'categories': [{'id': 3, 'name': 'RAM'}]}], + 'prices': [{'id': 1133}], }, { 'id': 1240, + 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Private Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1007, - 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], + 'prices': [{'id': 1007}], }, { 'id': 1250, + 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1144, - 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], + 'prices': [{'id': 1144}], }, { 'id': 4439, diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 60238d0d8..46576d69a 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -574,7 +574,7 @@ def test_get_item_id_for_upgrade(self): item_id = 0 package_items = self.client['Product_Package'].getItems(id=46) for item in package_items: - if ((item['prices'][0]['categories'][0]['id'] == 3) + if ((item['categories'][0]['id'] == 3) and (item.get('capacity') == '2')): item_id = item['prices'][0]['id'] break From f5e61790ca2d5f4503884220a563a9c9a6b5b3d4 Mon Sep 17 00:00:00 2001 From: Robert Chumbley Date: Mon, 28 Jul 2014 10:52:36 -0500 Subject: [PATCH 0064/2667] Modified vs_tests and Product_Package to use the new result set returned by getItems. Categories are now under 'prices'. Before they were under 'item'. --- SoftLayer/testing/fixtures/Product_Package.py | 20 +++++++++---------- SoftLayer/tests/managers/vs_tests.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SoftLayer/testing/fixtures/Product_Package.py b/SoftLayer/testing/fixtures/Product_Package.py index e837298d4..f2621766e 100644 --- a/SoftLayer/testing/fixtures/Product_Package.py +++ b/SoftLayer/testing/fixtures/Product_Package.py @@ -1037,43 +1037,43 @@ def get_bmc_categories_mock(): getItems = [ { 'id': 1234, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 1122}], + 'prices': [{'id': 1122, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], }, { 'id': 2233, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 4477}], + 'prices': [{'id': 4477, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], }, { 'id': 1239, - 'categories': [{'id': 3, 'name': 'RAM'}], 'capacity': '2', 'description': 'RAM', 'itemCategory': {'categoryCode': 'RAM'}, - 'prices': [{'id': 1133}], + 'prices': [{'id': 1133, + 'categories': [{'id': 3, 'name': 'RAM'}]}], }, { 'id': 1240, - 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Private Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1007}], + 'prices': [{'id': 1007, + 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], }, { 'id': 1250, - 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1144}], + 'prices': [{'id': 1144, + 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], }, { 'id': 4439, diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 46576d69a..60238d0d8 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -574,7 +574,7 @@ def test_get_item_id_for_upgrade(self): item_id = 0 package_items = self.client['Product_Package'].getItems(id=46) for item in package_items: - if ((item['categories'][0]['id'] == 3) + if ((item['prices'][0]['categories'][0]['id'] == 3) and (item.get('capacity') == '2')): item_id = item['prices'][0]['id'] break From b10d8681605ae5ca877fed4fe79bee419fdb1e31 Mon Sep 17 00:00:00 2001 From: Hans Kristian Moen Date: Mon, 28 Jul 2014 16:58:00 +0100 Subject: [PATCH 0065/2667] Add support for custom HTTP User-Agent string --- SoftLayer/API.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index e5569f8ae..61851a35a 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -44,7 +44,7 @@ class Client(object): :param auth: an object which responds to get_headers() to be inserted into the xml-rpc headers. Example: `BasicAuthentication` :param config_file: A path to a configuration file used to load settings - :param user_agent: an optional User Agent to report when making API + :param user_agent: an optional User Agent to report when making API calls if you wish to bypass the packages built in User Agent string Usage: @@ -59,7 +59,8 @@ class Client(object): _prefix = "SoftLayer_" def __init__(self, username=None, api_key=None, endpoint_url=None, - timeout=None, auth=None, config_file=None, proxy=None, user_agent=None): + timeout=None, auth=None, config_file=None, proxy=None, + user_agent=None): settings = config.get_client_settings(username=username, api_key=api_key, From 8e2cb794aadc5c93490aebf9f1cd5268c8e900f0 Mon Sep 17 00:00:00 2001 From: Hans Kristian Moen Date: Mon, 28 Jul 2014 16:58:00 +0100 Subject: [PATCH 0066/2667] Remove trailing whitespace and break long line in last patch --- SoftLayer/API.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index e5569f8ae..61851a35a 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -44,7 +44,7 @@ class Client(object): :param auth: an object which responds to get_headers() to be inserted into the xml-rpc headers. Example: `BasicAuthentication` :param config_file: A path to a configuration file used to load settings - :param user_agent: an optional User Agent to report when making API + :param user_agent: an optional User Agent to report when making API calls if you wish to bypass the packages built in User Agent string Usage: @@ -59,7 +59,8 @@ class Client(object): _prefix = "SoftLayer_" def __init__(self, username=None, api_key=None, endpoint_url=None, - timeout=None, auth=None, config_file=None, proxy=None, user_agent=None): + timeout=None, auth=None, config_file=None, proxy=None, + user_agent=None): settings = config.get_client_settings(username=username, api_key=api_key, From 302d2e7725bd4e16724e7fec05805815a41a9f6e Mon Sep 17 00:00:00 2001 From: Robert Chumbley Date: Mon, 28 Jul 2014 10:52:36 -0500 Subject: [PATCH 0067/2667] Changed tests to match _get_package_items Modified vs_tests and Product_Package to use the new result set returned by getItems. Categories are now under 'prices'. Before they were under 'item'. --- SoftLayer/testing/fixtures/Product_Package.py | 20 +++++++++---------- SoftLayer/tests/managers/vs_tests.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SoftLayer/testing/fixtures/Product_Package.py b/SoftLayer/testing/fixtures/Product_Package.py index e837298d4..f2621766e 100644 --- a/SoftLayer/testing/fixtures/Product_Package.py +++ b/SoftLayer/testing/fixtures/Product_Package.py @@ -1037,43 +1037,43 @@ def get_bmc_categories_mock(): getItems = [ { 'id': 1234, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 1122}], + 'prices': [{'id': 1122, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], }, { 'id': 2233, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 4477}], + 'prices': [{'id': 4477, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], }, { 'id': 1239, - 'categories': [{'id': 3, 'name': 'RAM'}], 'capacity': '2', 'description': 'RAM', 'itemCategory': {'categoryCode': 'RAM'}, - 'prices': [{'id': 1133}], + 'prices': [{'id': 1133, + 'categories': [{'id': 3, 'name': 'RAM'}]}], }, { 'id': 1240, - 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Private Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1007}], + 'prices': [{'id': 1007, + 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], }, { 'id': 1250, - 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1144}], + 'prices': [{'id': 1144, + 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], }, { 'id': 4439, diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 46576d69a..60238d0d8 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -574,7 +574,7 @@ def test_get_item_id_for_upgrade(self): item_id = 0 package_items = self.client['Product_Package'].getItems(id=46) for item in package_items: - if ((item['categories'][0]['id'] == 3) + if ((item['prices'][0]['categories'][0]['id'] == 3) and (item.get('capacity') == '2')): item_id = item['prices'][0]['id'] break From 157eecdf5a7763eb02f91bd67382a9dd1ef90527 Mon Sep 17 00:00:00 2001 From: Robert Chumbley Date: Mon, 28 Jul 2014 16:49:51 -0500 Subject: [PATCH 0068/2667] Revert "Changed tests to match _get_package_items Modified vs_tests and Product_Package to use the new result set returned by getItems. Categories are now under 'prices'. Before they were under 'item'." This reverts commit 302d2e7725bd4e16724e7fec05805815a41a9f6e. --- SoftLayer/testing/fixtures/Product_Package.py | 20 +++++++++---------- SoftLayer/tests/managers/vs_tests.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SoftLayer/testing/fixtures/Product_Package.py b/SoftLayer/testing/fixtures/Product_Package.py index f2621766e..e837298d4 100644 --- a/SoftLayer/testing/fixtures/Product_Package.py +++ b/SoftLayer/testing/fixtures/Product_Package.py @@ -1037,43 +1037,43 @@ def get_bmc_categories_mock(): getItems = [ { 'id': 1234, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 1122, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], + 'prices': [{'id': 1122}], }, { 'id': 2233, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 4477, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], + 'prices': [{'id': 4477}], }, { 'id': 1239, + 'categories': [{'id': 3, 'name': 'RAM'}], 'capacity': '2', 'description': 'RAM', 'itemCategory': {'categoryCode': 'RAM'}, - 'prices': [{'id': 1133, - 'categories': [{'id': 3, 'name': 'RAM'}]}], + 'prices': [{'id': 1133}], }, { 'id': 1240, + 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Private Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1007, - 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], + 'prices': [{'id': 1007}], }, { 'id': 1250, + 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1144, - 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], + 'prices': [{'id': 1144}], }, { 'id': 4439, diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 60238d0d8..46576d69a 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -574,7 +574,7 @@ def test_get_item_id_for_upgrade(self): item_id = 0 package_items = self.client['Product_Package'].getItems(id=46) for item in package_items: - if ((item['prices'][0]['categories'][0]['id'] == 3) + if ((item['categories'][0]['id'] == 3) and (item.get('capacity') == '2')): item_id = item['prices'][0]['id'] break From d145d5acb530e0235c7298b450eb76322562ec0f Mon Sep 17 00:00:00 2001 From: Robert Chumbley Date: Mon, 28 Jul 2014 16:52:06 -0500 Subject: [PATCH 0069/2667] Changed tests to match _get_package_items --- SoftLayer/testing/fixtures/Product_Package.py | 20 +++++++++---------- SoftLayer/tests/managers/vs_tests.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SoftLayer/testing/fixtures/Product_Package.py b/SoftLayer/testing/fixtures/Product_Package.py index e837298d4..f2621766e 100644 --- a/SoftLayer/testing/fixtures/Product_Package.py +++ b/SoftLayer/testing/fixtures/Product_Package.py @@ -1037,43 +1037,43 @@ def get_bmc_categories_mock(): getItems = [ { 'id': 1234, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 1122}], + 'prices': [{'id': 1122, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], }, { 'id': 2233, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 4477}], + 'prices': [{'id': 4477, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], }, { 'id': 1239, - 'categories': [{'id': 3, 'name': 'RAM'}], 'capacity': '2', 'description': 'RAM', 'itemCategory': {'categoryCode': 'RAM'}, - 'prices': [{'id': 1133}], + 'prices': [{'id': 1133, + 'categories': [{'id': 3, 'name': 'RAM'}]}], }, { 'id': 1240, - 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Private Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1007}], + 'prices': [{'id': 1007, + 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], }, { 'id': 1250, - 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1144}], + 'prices': [{'id': 1144, + 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], }, { 'id': 4439, diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 46576d69a..60238d0d8 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -574,7 +574,7 @@ def test_get_item_id_for_upgrade(self): item_id = 0 package_items = self.client['Product_Package'].getItems(id=46) for item in package_items: - if ((item['categories'][0]['id'] == 3) + if ((item['prices'][0]['categories'][0]['id'] == 3) and (item.get('capacity') == '2')): item_id = item['prices'][0]['id'] break From b4f8ef0a35576049152ac06c4322fbd59d320c34 Mon Sep 17 00:00:00 2001 From: Robert Chumbley Date: Mon, 28 Jul 2014 16:52:06 -0500 Subject: [PATCH 0070/2667] Changed tests to match _get_package_items --- SoftLayer/testing/fixtures/Product_Package.py | 20 +++++++++---------- SoftLayer/tests/managers/vs_tests.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SoftLayer/testing/fixtures/Product_Package.py b/SoftLayer/testing/fixtures/Product_Package.py index e837298d4..f2621766e 100644 --- a/SoftLayer/testing/fixtures/Product_Package.py +++ b/SoftLayer/testing/fixtures/Product_Package.py @@ -1037,43 +1037,43 @@ def get_bmc_categories_mock(): getItems = [ { 'id': 1234, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 1122}], + 'prices': [{'id': 1122, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], }, { 'id': 2233, - 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}], 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, - 'prices': [{'id': 4477}], + 'prices': [{'id': 4477, + 'categories': [{'id': 26, 'name': 'Uplink Port Speeds'}]}], }, { 'id': 1239, - 'categories': [{'id': 3, 'name': 'RAM'}], 'capacity': '2', 'description': 'RAM', 'itemCategory': {'categoryCode': 'RAM'}, - 'prices': [{'id': 1133}], + 'prices': [{'id': 1133, + 'categories': [{'id': 3, 'name': 'RAM'}]}], }, { 'id': 1240, - 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Private Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1007}], + 'prices': [{'id': 1007, + 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], }, { 'id': 1250, - 'categories': [{'id': 80, 'name': 'Computing Instance'}], 'capacity': '4', 'description': 'Computing Instance', 'itemCategory': {'categoryCode': 'Computing Instance'}, - 'prices': [{'id': 1144}], + 'prices': [{'id': 1144, + 'categories': [{'id': 80, 'name': 'Computing Instance'}]}], }, { 'id': 4439, diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 46576d69a..60238d0d8 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -574,7 +574,7 @@ def test_get_item_id_for_upgrade(self): item_id = 0 package_items = self.client['Product_Package'].getItems(id=46) for item in package_items: - if ((item['categories'][0]['id'] == 3) + if ((item['prices'][0]['categories'][0]['id'] == 3) and (item.get('capacity') == '2')): item_id = item['prices'][0]['id'] break From c895f49ac6cd2d98ae1ef38e8d026ad7ba883923 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 29 Jul 2014 09:17:26 -0500 Subject: [PATCH 0071/2667] Fix for pep8 CI failures --- SoftLayer/CLI/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 3aa5260e1..b3e33cf87 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -45,6 +45,8 @@ """ # :license: MIT, see LICENSE for more details. +# pylint: disable=W0703 + import logging import sys From 55ebdf186052b8778d2f0ed7ff514247a82a8a05 Mon Sep 17 00:00:00 2001 From: underscorephil Date: Wed, 30 Jul 2014 13:46:02 -0500 Subject: [PATCH 0072/2667] Fix missing var test error --- SoftLayer/tests/CLI/modules/server_tests.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index a5d602f41..1429d9ffa 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -261,7 +261,8 @@ def test_list_servers(self): 'cores': 2, 'id': 1000, 'backend_ip': '10.1.0.2', - 'active_transaction': 'TXN_NAME' + 'active_transaction': 'TXN_NAME', + 'owner': 'chechu' }, { 'datacenter': 'TEST00', @@ -271,7 +272,8 @@ def test_list_servers(self): 'cores': 4, 'id': 1001, 'backend_ip': '10.1.0.3', - 'active_transaction': None + 'active_transaction': None, + 'owner': 'chechu' }, { 'datacenter': 'TEST00', @@ -281,7 +283,8 @@ def test_list_servers(self): 'cores': 4, 'id': 1002, 'backend_ip': '10.1.0.4', - 'active_transaction': None + 'active_transaction': None, + 'owner': 'chechu' } ] From 475bd30562e904ba222fc6661eba7be78c215ea8 Mon Sep 17 00:00:00 2001 From: underscorephil Date: Wed, 30 Jul 2014 14:26:19 -0500 Subject: [PATCH 0073/2667] Fix missing price test error --- SoftLayer/tests/CLI/modules/vs_tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index f09561c6c..0acd3f4a2 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -41,7 +41,6 @@ def test_list_vs(self): def test_detail_vs(self): command = vs.VSDetails(client=self.client) - output = command.execute({'': '100', '--passwords': True, '--price': True}) @@ -56,8 +55,8 @@ def test_detail_vs(self): 'modified': {}, 'os': '12.04-64 Minimal for CCI', 'os_version': '12.04-64 Minimal for CCI', - 'price rate': {}, 'notes': 'notes', + 'price rate': 1.54, 'tags': ['production'], 'private_cpu': {}, 'private_ip': '10.45.19.37', From 683e4192b561d6d7bdc58a1614e90dfe6f29abab Mon Sep 17 00:00:00 2001 From: underscorephil Date: Wed, 30 Jul 2014 14:29:33 -0500 Subject: [PATCH 0074/2667] pep8 fixes --- SoftLayer/CLI/modules/vs.py | 20 +++++++++++++++++++- SoftLayer/tests/CLI/modules/vs_tests.py | 7 ++++--- SoftLayer/tests/fixtures/Account.py | 10 +++++----- SoftLayer/tests/fixtures/Hardware_Server.py | 2 +- SoftLayer/tests/fixtures/Virtual_Guest.py | 2 +- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 365be8c48..4a0e86ede 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -43,6 +43,7 @@ class ListVSIs(CLIRunnable): + """ usage: sl vs list [--hourly | --monthly] [--sortby=SORT_COLUMN] [--tags=TAGS] [options] @@ -118,6 +119,7 @@ def execute(self, args): class VSDetails(CLIRunnable): + """ usage: sl vs detail [--passwords] [--price] [options] @@ -173,7 +175,8 @@ def execute(self, args): table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) table.add_row(['owner', FormattedItem( - lookup(result, 'billingItem', 'orderItem', 'order', 'userRecord', 'username'), + lookup(result, 'billingItem', 'orderItem', + 'order', 'userRecord', 'username'), )]) vlan_table = Table(['type', 'number', 'id']) @@ -215,6 +218,7 @@ def execute(self, args): class CreateOptionsVS(CLIRunnable): + """ usage: sl vs create-options [options] @@ -338,6 +342,7 @@ def add_block_rows(disks, name): class CreateVS(CLIRunnable): + """ usage: sl vs create [--disk=SIZE...] [--key=KEY...] [options] @@ -624,6 +629,7 @@ def _parse_create_args(self, args): class ReadyVS(CLIRunnable): + """ usage: sl vs ready [options] @@ -648,6 +654,7 @@ def execute(self, args): class ReloadVS(CLIRunnable): + """ usage: sl vs reload [--key=KEY...] [options] @@ -679,6 +686,7 @@ def execute(self, args): class CancelVS(CLIRunnable): + """ usage: sl vs cancel [options] @@ -698,6 +706,7 @@ def execute(self, args): class VSPowerOff(CLIRunnable): + """ usage: sl vs power-off [--hard] [options] @@ -724,6 +733,7 @@ def execute(self, args): class VSReboot(CLIRunnable): + """ usage: sl vs reboot [--hard | --soft] [options] @@ -753,6 +763,7 @@ def execute(self, args): class VSPowerOn(CLIRunnable): + """ usage: sl vs power-on [options] @@ -768,6 +779,7 @@ def execute(self, args): class VSPause(CLIRunnable): + """ usage: sl vs pause [options] @@ -789,6 +801,7 @@ def execute(self, args): class VSResume(CLIRunnable): + """ usage: sl vs resume [options] @@ -804,6 +817,7 @@ def execute(self, args): class NicEditVS(CLIRunnable): + """ usage: sl vs nic-edit (public | private) --speed=SPEED [options] @@ -825,6 +839,7 @@ def execute(self, args): class VSDNS(CLIRunnable): + """ usage: sl vs dns sync [options] @@ -927,6 +942,7 @@ def sync_ptr_record(): class EditVS(CLIRunnable): + """ usage: sl vs edit [options] @@ -968,6 +984,7 @@ def execute(self, args): class CaptureVS(CLIRunnable): + """ usage: sl vs capture [options] @@ -1011,6 +1028,7 @@ def execute(self, args): class UpgradeVS(CLIRunnable): + """ usage: sl vs upgrade [options] diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index 0acd3f4a2..e7c7ffdfc 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -12,6 +12,7 @@ class DnsTests(TestCase): + def set_up(self): self.client = FixtureClient() @@ -27,7 +28,7 @@ def test_list_vs(self): 'active_transaction': None, 'id': 100, 'backend_ip': '10.45.19.37', - 'owner': 'chechu' }, + 'owner': 'chechu'}, {'datacenter': 'TEST00', 'primary_ip': '172.16.240.7', 'host': 'vs-test2.test.sftlyr.ws', @@ -36,7 +37,7 @@ def test_list_vs(self): 'active_transaction': None, 'id': 104, 'backend_ip': '10.45.19.35', - 'owner': 'chechu' }], + 'owner': 'chechu'}], format_output(output, 'python')) def test_detail_vs(self): @@ -56,7 +57,7 @@ def test_detail_vs(self): 'os': '12.04-64 Minimal for CCI', 'os_version': '12.04-64 Minimal for CCI', 'notes': 'notes', - 'price rate': 1.54, + 'price rate': 1.54, 'tags': ['production'], 'private_cpu': {}, 'private_ip': '10.45.19.37', diff --git a/SoftLayer/tests/fixtures/Account.py b/SoftLayer/tests/fixtures/Account.py index 1de74d1bf..99bd6ca8e 100644 --- a/SoftLayer/tests/fixtures/Account.py +++ b/SoftLayer/tests/fixtures/Account.py @@ -42,7 +42,7 @@ } } } - }, + }, }, { 'id': 104, 'hostname': 'vs-test2', @@ -67,7 +67,7 @@ } } } - }, + }, }] getMonthlyVirtualGuests = [vs for vs in getVirtualGuests @@ -88,7 +88,7 @@ } } } - }, + }, 'primaryIpAddress': '172.16.1.100', 'hostname': 'hardware-test1', 'domain': 'test.sftlyr.ws', @@ -145,7 +145,7 @@ } } } - }, + }, 'primaryIpAddress': '172.16.4.94', 'hostname': 'hardware-test2', 'domain': 'test.sftlyr.ws', @@ -187,7 +187,7 @@ } } } - }, + }, 'primaryIpAddress': '172.16.4.95', 'hostname': 'hardware-bad-memory', 'domain': 'test.sftlyr.ws', diff --git a/SoftLayer/tests/fixtures/Hardware_Server.py b/SoftLayer/tests/fixtures/Hardware_Server.py index 71e4626e6..c1ce0be0d 100644 --- a/SoftLayer/tests/fixtures/Hardware_Server.py +++ b/SoftLayer/tests/fixtures/Hardware_Server.py @@ -10,7 +10,7 @@ } } } - }, + }, 'primaryIpAddress': '172.16.1.100', 'hostname': 'hardware-test1', 'domain': 'test.sftlyr.ws', diff --git a/SoftLayer/tests/fixtures/Virtual_Guest.py b/SoftLayer/tests/fixtures/Virtual_Guest.py index be74621bc..8e5c6af10 100644 --- a/SoftLayer/tests/fixtures/Virtual_Guest.py +++ b/SoftLayer/tests/fixtures/Virtual_Guest.py @@ -12,7 +12,7 @@ } } } - }, + }, 'datacenter': {'id': 50, 'name': 'TEST00', 'description': 'Test Data Center'}, 'powerState': {'keyName': 'RUNNING', 'name': 'Running'}, From c1ff0ec9fe079b8afeea0621da679735204529bf Mon Sep 17 00:00:00 2001 From: underscorephil Date: Mon, 4 Aug 2014 10:34:21 -0500 Subject: [PATCH 0075/2667] Fix missing lib prefixes --- SoftLayer/CLI/modules/server.py | 16 ++++++++-------- SoftLayer/CLI/modules/vs.py | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index e6f1dcee8..7e43ef0f3 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -95,7 +95,7 @@ def execute(self, args): table.sortby = args.get('--sortby') or 'host' for server in servers: - server = NestedDict(server) + server = utils.NestedDict(server) user = None if 'billingItem' in server: if 'orderItem' in server['billingItem']: @@ -106,11 +106,11 @@ def execute(self, args): server['datacenter']['name'] or formatting.blank(), server['fullyQualifiedDomainName'], server['processorPhysicalCoreAmount'], - gb(server['memoryCapacity'] or 0), - server['primaryIpAddress'] or blank(), - server['primaryBackendIpAddress'] or blank(), - active_txn(server), - user or blank(), + formatting.gb(server['memoryCapacity'] or 0), + server['primaryIpAddress'] or formatting.blank(), + server['primaryBackendIpAddress'] or formatting.blank(), + formatting.active_txn(server), + user or formatting.blank(), ]) return table @@ -166,14 +166,14 @@ def execute(self, args): ['softwareDescription']['name'] or formatting.blank() )]) - table.add_row(['created', result['provisionDate'] or blank()]) + table.add_row(['created', result['provisionDate'] or formatting.blank()]) user = None if 'billingItem' in result: if 'orderItem' in result['billingItem']: user = (result['billingItem']['orderItem']['order'] ['userRecord']['username']) table.add_row(['owner', - user or blank()]) + user or formatting.blank()]) vlan_table = Table(['type', 'number', 'id']) for vlan in result['networkVlans']: diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 95edf794f..e7cae07a1 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -105,10 +105,10 @@ def execute(self, args): guest['datacenter']['name'] or formatting.blank(), guest['fullyQualifiedDomainName'], guest['maxCpu'], - mb_to_gb(guest['maxMemory']), - guest['primaryIpAddress'] or blank(), - guest['primaryBackendIpAddress'] or blank(), - active_txn(guest), + formatting.mb_to_gb(guest['maxMemory']), + guest['primaryIpAddress'] or formatting.blank(), + guest['primaryBackendIpAddress'] or formatting.blank(), + formatting.active_txn(guest), guest['billingItem']['orderItem']['order'] ['userRecord']['username'] ]) From 3e1934cf505bb82016427d354197b6e954835b0b Mon Sep 17 00:00:00 2001 From: underscorephil Date: Mon, 4 Aug 2014 10:39:09 -0500 Subject: [PATCH 0076/2667] Add blank return for missing owners on VSI --- SoftLayer/CLI/modules/vs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index e7cae07a1..8d7f62dc8 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -110,7 +110,7 @@ def execute(self, args): guest['primaryBackendIpAddress'] or formatting.blank(), formatting.active_txn(guest), guest['billingItem']['orderItem']['order'] - ['userRecord']['username'] + ['userRecord']['username'] or formatting.blank(), ]) return table From e9529e44f416fa2686746d1b90c3b66f51091706 Mon Sep 17 00:00:00 2001 From: underscorephil Date: Wed, 6 Aug 2014 15:36:35 -0500 Subject: [PATCH 0077/2667] Add vs rescue option. Fix various pep8 issues. Fix missing formatting prefixs --- SoftLayer/CLI/modules/server.py | 5 +- SoftLayer/CLI/modules/vs.py | 32 +++++- SoftLayer/managers/vs.py | 9 ++ SoftLayer/testing/fixtures/Account.py | 99 ++++++++++--------- SoftLayer/testing/fixtures/Hardware_Server.py | 20 ++-- SoftLayer/testing/fixtures/Virtual_Guest.py | 21 ++-- SoftLayer/tests/CLI/modules/vs_tests.py | 4 +- SoftLayer/tests/managers/vs_tests.py | 9 ++ 8 files changed, 128 insertions(+), 71 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 7e43ef0f3..f313d5fe5 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -166,7 +166,8 @@ def execute(self, args): ['softwareDescription']['name'] or formatting.blank() )]) - table.add_row(['created', result['provisionDate'] or formatting.blank()]) + table.add_row( + ['created', result['provisionDate'] or formatting.blank()]) user = None if 'billingItem' in result: if 'orderItem' in result['billingItem']: @@ -174,7 +175,7 @@ def execute(self, args): ['userRecord']['username']) table.add_row(['owner', user or formatting.blank()]) - vlan_table = Table(['type', 'number', 'id']) + vlan_table = formatting.Table(['type', 'number', 'id']) for vlan in result['networkVlans']: vlan_table.add_row([ diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 8d7f62dc8..3135d488b 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -177,9 +177,9 @@ def execute(self, args): table.add_row(['private_cpu', result['dedicatedAccountHostOnlyFlag']]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) - table.add_row(['owner', FormattedItem( - lookup(result, 'billingItem', 'orderItem', - 'order', 'userRecord', 'username'), + table.add_row(['owner', formatting.FormattedItem( + utils.lookup(result, 'billingItem', 'orderItem', + 'order', 'userRecord', 'username'), )]) vlan_table = formatting.Table(['type', 'number', 'id']) @@ -881,6 +881,32 @@ def execute(self, args): vsi.change_port_speed(vs_id, public, args['--speed']) +class VSRescue(environment.CLIRunnable): + + """ +usage: sl vs rescue [options] + +Reboot into Xen rescue image + + +""" + action = 'rescue' + options = ['confirm'] + + def execute(self, args): + vsi = SoftLayer.VSManager(self.client) + vs_id = helpers.resolve_id(vsi.resolve_ids, + args.get(''), + 'VS') + if args['--really'] or formatting.confirm( + "This action will reboot this VSI. " + "Continue?"): + + vsi.rescue(vs_id) + else: + raise exceptions.CLIAbort('Aborted') + + class VSDNS(environment.CLIRunnable): """ diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 5c12b6f15..55c718a5e 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -15,6 +15,7 @@ class VSManager(utils.IdentifierMixin, object): + """ Manages Virtual Servers @@ -24,6 +25,7 @@ class VSManager(utils.IdentifierMixin, object): If none is provided, one will be auto initialized. """ + def __init__(self, client, ordering_manager=None): self.client = client self.account = client['Account'] @@ -556,6 +558,13 @@ def edit(self, instance_id, userdata=None, hostname=None, domain=None, return self.guest.editObject(obj, id=instance_id) + def rescue(self, instance_id): + """ Reboot a VSI into the Xen recsue kernel + + :param integer instance_id: the instance ID to rescue + """ + return self.guest.executeRescueLayer(id=instance_id) + def capture(self, instance_id, name, additional_disks=False, notes=None): """ Capture one or all disks from a VS to a SoftLayer image. diff --git a/SoftLayer/testing/fixtures/Account.py b/SoftLayer/testing/fixtures/Account.py index 3778e4b25..6cf237a5b 100644 --- a/SoftLayer/testing/fixtures/Account.py +++ b/SoftLayer/testing/fixtures/Account.py @@ -1,4 +1,3 @@ - getPrivateBlockDeviceTemplateGroups = [{ 'accountId': 1234, 'blockDevices': [], @@ -33,15 +32,17 @@ 'primaryBackendIpAddress': '10.45.19.37', 'hourlyBillingFlag': False, - 'billingItem': {'id': 6327, 'recurringFee': 1.54, - 'orderItem': { - 'order': { - 'userRecord': { - 'username': 'chechu', - } - } - } - }, + 'billingItem': { + 'id': 6327, + 'recurringFee': 1.54, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, }, { 'id': 104, 'hostname': 'vs-test2', @@ -57,15 +58,17 @@ 'globalIdentifier': '05a8ac-6abf0', 'primaryBackendIpAddress': '10.45.19.35', 'hourlyBillingFlag': True, - 'billingItem': {'id': 6327, 'recurringFee': 1.54, - 'orderItem': { - 'order': { - 'userRecord': { - 'username': 'chechu', - } - } - } - }, + 'billingItem': { + 'id': 6327, + 'recurringFee': 1.54, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, }] getMonthlyVirtualGuests = [vs for vs in getVirtualGuests @@ -78,15 +81,17 @@ 'id': 1000, 'datacenter': {'id': 50, 'name': 'TEST00', 'description': 'Test Data Center'}, - 'billingItem': {'id': 6327, 'recurringFee': 1.54, - 'orderItem': { - 'order': { - 'userRecord': { - 'username': 'chechu', - } - } - } - }, + 'billingItem': { + 'id': 6327, + 'recurringFee': 1.54, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, 'primaryIpAddress': '172.16.1.100', 'hostname': 'hardware-test1', 'domain': 'test.sftlyr.ws', @@ -135,15 +140,16 @@ 'id': 1001, 'datacenter': {'name': 'TEST00', 'description': 'Test Data Center'}, - 'billingItem': {'id': 7112, - 'orderItem': { - 'order': { - 'userRecord': { - 'username': 'chechu', - } - } - } - }, + 'billingItem': { + 'id': 7112, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, 'primaryIpAddress': '172.16.4.94', 'hostname': 'hardware-test2', 'domain': 'test.sftlyr.ws', @@ -177,15 +183,16 @@ 'id': 1002, 'datacenter': {'name': 'TEST00', 'description': 'Test Data Center'}, - 'billingItem': {'id': 7112, - 'orderItem': { - 'order': { - 'userRecord': { - 'username': 'chechu', - } - } - } - }, + 'billingItem': { + 'id': 7112, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, 'primaryIpAddress': '172.16.4.95', 'hostname': 'hardware-bad-memory', 'domain': 'test.sftlyr.ws', diff --git a/SoftLayer/testing/fixtures/Hardware_Server.py b/SoftLayer/testing/fixtures/Hardware_Server.py index c1ce0be0d..883c7741c 100644 --- a/SoftLayer/testing/fixtures/Hardware_Server.py +++ b/SoftLayer/testing/fixtures/Hardware_Server.py @@ -2,15 +2,17 @@ 'id': 1000, 'datacenter': {'id': 50, 'name': 'TEST00', 'description': 'Test Data Center'}, - 'billingItem': {'id': 6327, 'recurringFee': 1.54, - 'orderItem': { - 'order': { - 'userRecord': { - 'username': 'chechu', - } - } - } - }, + 'billingItem': { + 'id': 6327, + 'recurringFee': 1.54, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, 'primaryIpAddress': '172.16.1.100', 'hostname': 'hardware-test1', 'domain': 'test.sftlyr.ws', diff --git a/SoftLayer/testing/fixtures/Virtual_Guest.py b/SoftLayer/testing/fixtures/Virtual_Guest.py index 8641aa002..de9f16ca2 100644 --- a/SoftLayer/testing/fixtures/Virtual_Guest.py +++ b/SoftLayer/testing/fixtures/Virtual_Guest.py @@ -4,15 +4,17 @@ 'domain': 'test.sftlyr.ws', 'fullyQualifiedDomainName': 'vs-test1.test.sftlyr.ws', 'status': {'keyName': 'ACTIVE', 'name': 'Active'}, - 'billingItem': {'id': 6327, 'recurringFee': 1.54, - 'orderItem': { - 'order': { - 'userRecord': { - 'username': 'chechu', - } - } - } - }, + 'billingItem': { + 'id': 6327, + 'recurringFee': 1.54, + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, 'datacenter': {'id': 50, 'name': 'TEST00', 'description': 'Test Data Center'}, 'powerState': {'keyName': 'RUNNING', 'name': 'Running'}, @@ -233,3 +235,4 @@ reloadOperatingSystem = 'OK' setTags = True createArchiveTransaction = {} +executeRescueLayer = True diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index d27a6ab7e..e9b945768 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -38,7 +38,7 @@ def test_list_vs(self): 'id': 104, 'backend_ip': '10.45.19.35', 'owner': 'chechu'}], - format_output(output, 'python')) + formatting.format_output(output, 'python')) def test_detail_vs(self): command = vs.VSDetails(client=self.client) @@ -71,7 +71,7 @@ def test_detail_vs(self): 'number': 23, 'id': 1}], 'owner': 'chechu'}, - format_output(output, 'python')) + formatting.format_output(output, 'python')) def test_create_options(self): command = vs.CreateOptionsVS(client=self.client) diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index a0e7bcea3..bd2bd2f04 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -516,6 +516,15 @@ def test_change_port_speed_private(self): f = service.setPrivateNetworkInterfaceSpeed f.assert_called_once_with(speed, id=vs_id) + def test_rescue(self): + # Test rescue environment + vs_id = 1234 + self.vs.rescue(vs_id) + + service = self.client['Virtual_Guest'] + f = service.executeRescueLayer + f.assert_called_once_with(id=vs_id) + def test_edit(self): # Test editing user data service = self.client['Virtual_Guest'] From a19fa5809016dfd441315692441122c36e7c5229 Mon Sep 17 00:00:00 2001 From: underscorephil Date: Thu, 7 Aug 2014 13:44:45 -0500 Subject: [PATCH 0078/2667] Revert pep8 changes and use utils.lookup --- SoftLayer/CLI/modules/server.py | 38 ++++++++++----------------------- SoftLayer/CLI/modules/vs.py | 24 ++++----------------- 2 files changed, 15 insertions(+), 47 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index f313d5fe5..250a378cd 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -36,7 +36,6 @@ class ListServers(environment.CLIRunnable): - """ usage: sl server list [options] @@ -96,11 +95,7 @@ def execute(self, args): for server in servers: server = utils.NestedDict(server) - user = None - if 'billingItem' in server: - if 'orderItem' in server['billingItem']: - user = (server['billingItem']['orderItem']['order'] - ['userRecord']['username']) + table.add_row([ server['id'], server['datacenter']['name'] or formatting.blank(), @@ -110,14 +105,15 @@ def execute(self, args): server['primaryIpAddress'] or formatting.blank(), server['primaryBackendIpAddress'] or formatting.blank(), formatting.active_txn(server), - user or formatting.blank(), + utils.lookup( + server, 'billingItem', 'orderItem', 'order', 'userRecord', + 'username') or formatting.blank(), ]) return table class ServerDetails(environment.CLIRunnable): - """ usage: sl server detail [--passwords] [--price] [options] @@ -168,13 +164,13 @@ def execute(self, args): table.add_row( ['created', result['provisionDate'] or formatting.blank()]) - user = None - if 'billingItem' in result: - if 'orderItem' in result['billingItem']: - user = (result['billingItem']['orderItem']['order'] - ['userRecord']['username']) - table.add_row(['owner', - user or formatting.blank()]) + + table.add_row(['owner', formatting.FormattedItem( + utils.lookup(result, 'billingItem', 'orderItem', + 'order', 'userRecord', 'username') \ + or formatting.blank(), + )]) + vlan_table = formatting.Table(['type', 'number', 'id']) for vlan in result['networkVlans']: @@ -216,7 +212,6 @@ def execute(self, args): class ServerReload(environment.CLIRunnable): - """ usage: sl server reload [--key=KEY...] [options] @@ -249,7 +244,6 @@ def execute(self, args): class CancelServer(environment.CLIRunnable): - """ usage: sl server cancel [options] @@ -283,7 +277,6 @@ def execute(self, args): class ServerCancelReasons(environment.CLIRunnable): - """ usage: sl server cancel-reasons @@ -306,7 +299,6 @@ def execute(self, args): class ServerPowerOff(environment.CLIRunnable): - """ usage: sl server power-off [options] @@ -329,7 +321,6 @@ def execute(self, args): class ServerReboot(environment.CLIRunnable): - """ usage: sl server reboot [--hard | --soft] [options] @@ -362,7 +353,6 @@ def execute(self, args): class ServerPowerOn(environment.CLIRunnable): - """ usage: sl server power-on [options] @@ -379,7 +369,6 @@ def execute(self, args): class ServerPowerCycle(environment.CLIRunnable): - """ usage: sl server power-cycle [options] @@ -403,7 +392,6 @@ def execute(self, args): class NicEditServer(environment.CLIRunnable): - """ usage: sl server nic-edit (public | private) --speed=SPEED [options] @@ -428,7 +416,6 @@ def execute(self, args): class ListChassisServer(environment.CLIRunnable): - """ usage: sl server list-chassis [options] @@ -451,7 +438,6 @@ def execute(self, args): class ServerCreateOptions(environment.CLIRunnable): - """ usage: sl server create-options [options] @@ -784,7 +770,6 @@ def _generate_windows_code(self, description): class CreateServer(environment.CLIRunnable): - """ usage: sl server create [--disk=SIZE...] [--key=KEY...] [options] @@ -1049,7 +1034,6 @@ def _get_price_id_from_options(self, ds_options, option, value, class EditServer(environment.CLIRunnable): - """ usage: sl server edit [options] diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 3135d488b..086818cd1 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -41,7 +41,6 @@ class ListVSIs(environment.CLIRunnable): - """ usage: sl vs list [--hourly | --monthly] [--sortby=SORT_COLUMN] [--tags=TAGS] [options] @@ -109,15 +108,14 @@ def execute(self, args): guest['primaryIpAddress'] or formatting.blank(), guest['primaryBackendIpAddress'] or formatting.blank(), formatting.active_txn(guest), - guest['billingItem']['orderItem']['order'] - ['userRecord']['username'] or formatting.blank(), + utils.lookup(guest, 'billingItem', 'orderItem', 'order', + 'userRecord', 'username') or formatting.blank(), ]) return table class VSDetails(environment.CLIRunnable): - """ usage: sl vs detail [--passwords] [--price] [options] @@ -179,7 +177,8 @@ def execute(self, args): table.add_row(['modified', result['modifyDate']]) table.add_row(['owner', formatting.FormattedItem( utils.lookup(result, 'billingItem', 'orderItem', - 'order', 'userRecord', 'username'), + 'order', 'userRecord', 'username') \ + or formatting.blank(), )]) vlan_table = formatting.Table(['type', 'number', 'id']) @@ -221,7 +220,6 @@ def execute(self, args): class CreateOptionsVS(environment.CLIRunnable): - """ usage: sl vs create-options [options] @@ -348,7 +346,6 @@ def add_block_rows(disks, name): class CreateVS(environment.CLIRunnable): - """ usage: sl vs create [--disk=SIZE...] [--key=KEY...] [options] @@ -678,7 +675,6 @@ def execute(self, args): class ReloadVS(environment.CLIRunnable): - """ usage: sl vs reload [--key=KEY...] [options] @@ -712,7 +708,6 @@ def execute(self, args): class CancelVS(environment.CLIRunnable): - """ usage: sl vs cancel [options] @@ -734,7 +729,6 @@ def execute(self, args): class VSPowerOff(environment.CLIRunnable): - """ usage: sl vs power-off [--hard] [options] @@ -764,7 +758,6 @@ def execute(self, args): class VSReboot(environment.CLIRunnable): - """ usage: sl vs reboot [--hard | --soft] [options] @@ -797,7 +790,6 @@ def execute(self, args): class VSPowerOn(environment.CLIRunnable): - """ usage: sl vs power-on [options] @@ -815,7 +807,6 @@ def execute(self, args): class VSPause(environment.CLIRunnable): - """ usage: sl vs pause [options] @@ -840,7 +831,6 @@ def execute(self, args): class VSResume(environment.CLIRunnable): - """ usage: sl vs resume [options] @@ -858,7 +848,6 @@ def execute(self, args): class NicEditVS(environment.CLIRunnable): - """ usage: sl vs nic-edit (public | private) --speed=SPEED [options] @@ -882,7 +871,6 @@ def execute(self, args): class VSRescue(environment.CLIRunnable): - """ usage: sl vs rescue [options] @@ -908,7 +896,6 @@ def execute(self, args): class VSDNS(environment.CLIRunnable): - """ usage: sl vs dns sync [options] @@ -1017,7 +1004,6 @@ def sync_ptr_record(): class EditVS(environment.CLIRunnable): - """ usage: sl vs edit [options] @@ -1063,7 +1049,6 @@ def execute(self, args): class CaptureVS(environment.CLIRunnable): - """ usage: sl vs capture [options] @@ -1109,7 +1094,6 @@ def execute(self, args): class UpgradeVS(environment.CLIRunnable): - """ usage: sl vs upgrade [options] From 855c8f683f14b1d4fc1f02affc6cf766090896bf Mon Sep 17 00:00:00 2001 From: underscorephil Date: Thu, 7 Aug 2014 13:52:00 -0500 Subject: [PATCH 0079/2667] pep8 fixes --- SoftLayer/CLI/modules/server.py | 6 +++--- SoftLayer/CLI/modules/vs.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 250a378cd..66417b7b9 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -95,7 +95,7 @@ def execute(self, args): for server in servers: server = utils.NestedDict(server) - + table.add_row([ server['id'], server['datacenter']['name'] or formatting.blank(), @@ -167,8 +167,8 @@ def execute(self, args): table.add_row(['owner', formatting.FormattedItem( utils.lookup(result, 'billingItem', 'orderItem', - 'order', 'userRecord', 'username') \ - or formatting.blank(), + 'order', 'userRecord', + 'username') or formatting.blank() )]) vlan_table = formatting.Table(['type', 'number', 'id']) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 086818cd1..6f5400855 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -177,8 +177,8 @@ def execute(self, args): table.add_row(['modified', result['modifyDate']]) table.add_row(['owner', formatting.FormattedItem( utils.lookup(result, 'billingItem', 'orderItem', - 'order', 'userRecord', 'username') \ - or formatting.blank(), + 'order', 'userRecord', + 'username') or formatting.blank(), )]) vlan_table = formatting.Table(['type', 'number', 'id']) From bc8a6e9afa8eb5480fd3ad445ea723d27b335a65 Mon Sep 17 00:00:00 2001 From: underscorephil Date: Thu, 7 Aug 2014 17:44:37 -0500 Subject: [PATCH 0080/2667] Add basic quote manager and tests --- SoftLayer/managers/ordering.py | 75 ++++++++++++++++++++++ SoftLayer/testing/fixtures/Account.py | 6 ++ SoftLayer/tests/managers/ordering_tests.py | 30 +++++++++ 3 files changed, 111 insertions(+) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 649015601..3942e7df3 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -116,3 +116,78 @@ def get_package_id_by_type(self, package_type): return package['id'] else: raise ValueError("No package found for type: " + package_type) + + def get_quotes(self): + """ Retrieve a list of quotes + :return a list of SoftLayer_Billing_Order_Quote + """ + quotes = self.client['Account'].getActiveQuotes() + return quotes + + def get_quote_details(self, quote_id): + """ Retrieve quote details + :param quote_id ID number of target quote + """ + quote = self.client['Billing_Order_Quote'].getObject(id=quote_id) + return quote + + def get_order_container(self, quote_id): + """ Generate an order container from a quote object + :param quote_id ID number of target quote + """ + quote = self.client['Billing_Order_Quote'] + container = quote.getRecalculatedOrderContainer(id=quote_id) + return container['orderContainers'][0] + + def generate_order_template(self, quote_id=None, hostnames=None, + domain=None, quantity=None): + """ Generate a complete order template + :param int quote_id: ID of target quote + :param list hostnames: List of hostnames as strings + :param string domain: Domain name to be used for all servers + :param int quantity: Quantity to override default + """ + container = self.get_order_container(quote_id) + if quantity is not None: + container['quantity'] = quantity + if container['packageId'] == 46: + product_type = 'virtualGuests' + else: + product_type = 'hardware' + + if len(hostnames) != container['quantity']: + raise ValueError("You must specify a hostname for each " + "server in the quote") + + container[product_type] = [] + for hostname in hostnames: + container[product_type].append( + {'hostname': hostname, 'domain': domain} + ) + container['presetId'] = None + return container + + def verify_quote(self, **kwargs): + """ + Verifies that a quote order is valid without actually ordering + the resources + + :param int quote_id: ID for the target quote + :param list hostnames: hostnames of the servers + :param string domain: domain of the new servers + :param int quantity: Quantity to override default + """ + container = self.generate_order_template(**kwargs) + return self.client['Product_Order'].verifyOrder(container) + + def order_quote(self, **kwargs): + """ + Places an order using a quote + + :param int quote_id: ID for the target quote + :param list hostnames: hostnames of the servers + :param string domain: domain of the new server + :param int quantity: Quantity to override default + """ + container = self.generate_order_template(**kwargs) + return self.client['Product_Order'].placeOrder(container) diff --git a/SoftLayer/testing/fixtures/Account.py b/SoftLayer/testing/fixtures/Account.py index 6cf237a5b..b3f0b64f0 100644 --- a/SoftLayer/testing/fixtures/Account.py +++ b/SoftLayer/testing/fixtures/Account.py @@ -418,3 +418,9 @@ 'password': 'pass', 'serviceResourceBackendIpAddress': '127.0.0.1', }] + +getActiveQuotes = [{ + 'id': 1234, + 'name': 'TestQuote1234', + 'quoteKey': '1234test4321', +}] diff --git a/SoftLayer/tests/managers/ordering_tests.py b/SoftLayer/tests/managers/ordering_tests.py index d723792db..66ffcda86 100644 --- a/SoftLayer/tests/managers/ordering_tests.py +++ b/SoftLayer/tests/managers/ordering_tests.py @@ -69,3 +69,33 @@ def test_get_package_id_by_type_fails_for_nonexistent_package_type(self): self.ordering.client['Product_Package'].getAllObjects.return_value = [] with self.assertRaises(ValueError): self.ordering.get_package_id_by_type(package_type) + + def test_get_order_container(self): + container = self.ordering.get_order_container(1234) + quote = self.ordering.client['Billing_Order_Quote'] + container_fixture = quote.getRecalculatedOrderContainer(id=1234) + self.assertEqual(container, container_fixture['orderContainers'][0]) + + def test_get_quotes(self): + quotes = self.ordering.get_quotes() + quotes_fixture = self.ordering.client['Account'].getActiveQuotes() + self.assertEqual(quotes, quotes_fixture) + + def test_get_quote_details(self): + quote = self.ordering.get_quote_details(1234) + quote_fixture = self.ordering.client['Billing_Order_Quote'].getObject( + id=1234) + self.assertEqual(quote, quote_fixture) + + def test_verify_quote(self): + result = self.ordering.verify_quote( + quote_id=1234, + domain='example.com', + hostnames=['test1'], + quantity=1) + + self.assertEqual(result, self.ordering.client['Product_Order']. + verifyOrder()) + + def test_order_quote(self): + return True From e0d075661677b4b02fa29d108472e80b9fbcad02 Mon Sep 17 00:00:00 2001 From: underscorephil Date: Thu, 7 Aug 2014 17:48:20 -0500 Subject: [PATCH 0081/2667] Add quote fixture --- .../testing/fixtures/Billing_Order_Quote.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 SoftLayer/testing/fixtures/Billing_Order_Quote.py diff --git a/SoftLayer/testing/fixtures/Billing_Order_Quote.py b/SoftLayer/testing/fixtures/Billing_Order_Quote.py new file mode 100644 index 000000000..6302bfa94 --- /dev/null +++ b/SoftLayer/testing/fixtures/Billing_Order_Quote.py @@ -0,0 +1,18 @@ +getObject = { + 'accountId': 1234, + 'id': 1234, + 'name': 'TestQuote1234', + 'quoteKey': '1234test4321', +} + +getRecalculatedOrderContainer = { + 'orderContainers': [{ + 'presetId': '', + 'prices': [{ + 'id': 1921 + }], + 'quantity': 1, + 'packageId': 50, + 'useHourlyPricing': '', + }], +} From 8e62dd6bc964244865356f44fe827ff49ac50b2b Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Thu, 7 Aug 2014 23:29:45 -0500 Subject: [PATCH 0082/2667] removing = from argument values --- .idea/scopes/scope_settings.xml | 5 + .idea/workspace.xml | 397 ++++++++++++++++++++++++++++++++ SoftLayer/CLI/modules/vs.py | 1 + SoftLayer/utils.py | 7 + 4 files changed, 410 insertions(+) create mode 100644 .idea/scopes/scope_settings.xml create mode 100644 .idea/workspace.xml diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 000000000..922003b84 --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 000000000..0a852168e --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1407442328001 + 1407442328001 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 6f5400855..e331c83ec 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -577,6 +577,7 @@ def _parse_create_args(self, args): :param dict args: CLI arguments """ + args = utils.sanitize_args(args) data = { "hourly": args['--hourly'], "cpus": args['--cpu'], diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 0162dc868..ad49b7688 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -173,3 +173,10 @@ def dict_extract(dictionary, keys): if dictionary.get(key, None) is not None: del dictionary[key] return tuple(vals) + + +def sanitize_args(args): + for key, value in args.items(): + if isinstance(value, str) and value.startswith('='): + args[key] = value[1:] + return args \ No newline at end of file From 17ab2c171bb3714a2ed3af7bd0a622389666475c Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Thu, 7 Aug 2014 23:33:18 -0500 Subject: [PATCH 0083/2667] removing unintended changes --- .idea/scopes/scope_settings.xml | 5 - .idea/workspace.xml | 397 -------------------------------- 2 files changed, 402 deletions(-) delete mode 100644 .idea/scopes/scope_settings.xml delete mode 100644 .idea/workspace.xml diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml deleted file mode 100644 index 922003b84..000000000 --- a/.idea/scopes/scope_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 0a852168e..000000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,397 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1407442328001 - 1407442328001 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From fabdfc93a09ef12aff5c00fcd6f14de07675ad68 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Thu, 7 Aug 2014 23:35:31 -0500 Subject: [PATCH 0084/2667] add new line --- SoftLayer/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index ad49b7688..57b44629f 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -179,4 +179,4 @@ def sanitize_args(args): for key, value in args.items(): if isinstance(value, str) and value.startswith('='): args[key] = value[1:] - return args \ No newline at end of file + return args From cc85ca57618febb6d33c76ddaf6057f024ec3b66 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Fri, 8 Aug 2014 09:54:50 -0500 Subject: [PATCH 0085/2667] adding doctring --- SoftLayer/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 57b44629f..c0cce001a 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -176,6 +176,9 @@ def dict_extract(dictionary, keys): def sanitize_args(args): + """ sanitize input (remove = sign from argument values) + :returns args back + """ for key, value in args.items(): if isinstance(value, str) and value.startswith('='): args[key] = value[1:] From 9199ac202f2eb8981be0d5a842e514bac5be8f61 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 8 Aug 2014 10:21:37 -0500 Subject: [PATCH 0086/2667] Removes dict_extract(). Renames 'tag' argument to 'tags' *** This will be a breaking change for people working off of master*** --- SoftLayer/managers/vs.py | 9 ++++----- SoftLayer/tests/CLI/modules/vs_tests.py | 2 +- SoftLayer/tests/managers/vs_tests.py | 5 +++-- SoftLayer/utils.py | 17 ----------------- 4 files changed, 8 insertions(+), 25 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 55c718a5e..d8538fc5b 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -458,10 +458,10 @@ def create_instance(self, **kwargs): :param int nic_speed: The port speed to set :param string tag: tags to set on the VS as a comma separated list """ - tag, = utils.dict_extract(kwargs, {'tag': None}) + tags = kwargs.pop('tags', None) inst = self.guest.createObject(self._generate_create_dict(**kwargs)) - if tag is not None: - self.guest.setTags(tag, id=inst['id']) + if tags is not None: + self.guest.setTags(tags, id=inst['id']) return inst def create_instances(self, config_list): @@ -470,8 +470,7 @@ def create_instances(self, config_list): This takes a list of dictionaries using the same arguments as create_instance(). """ - tags = [utils.dict_extract(conf, {'tag': None})[0] - for conf in config_list] + tags = [conf.pop('tags', None) for conf in config_list] resp = self.guest.createObjects([self._generate_create_dict(**kwargs) for kwargs in config_list]) diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index e9b945768..bc3f6d384 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -128,7 +128,7 @@ def test_create(self, confirm_mock): '--vlan_private': None, '--wait': None, '--really': False, - '--tag': 'dev,green'}) + '--tags': 'dev,green'}) self.assertEqual([{'guid': '1a2b3c-1701', 'id': 100, diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index bd2bd2f04..4c7493587 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -137,7 +137,8 @@ def test_create_verify(self, create_dict): @mock.patch('SoftLayer.managers.vs.VSManager._generate_create_dict') def test_create_instance(self, create_dict): create_dict.return_value = {'test': 1, 'verify': 1} - self.vs.create_instance(test=1, verify=1, tag='dev,green') + self.vs.create_instance(test=1, verify=1, tags='dev,green') + create_dict.assert_called_once_with(test=1, verify=1) self.client['Virtual_Guest'].createObject.assert_called_once_with( {'test': 1, 'verify': 1}) @@ -149,7 +150,7 @@ def test_create_instances(self): 'memory': 1024, 'hostname': 'server', 'domain': 'example.com', - 'tag': 'dev,green'}]) + 'tags': 'dev,green'}]) self.client['Virtual_Guest'].createObjects.assert_called_once_with([ {'domain': 'example.com', 'hourlyBillingFlag': True, diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 0162dc868..e0d28239d 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -156,20 +156,3 @@ def resolve_ids(identifier, resolvers): return ids return [] - - -def dict_extract(dictionary, keys): - """Extracts & removes keys from a dict with a default value. - - :param dict dictionary: the target dictionary to operate on - :param dict keys: a dict who's keys specify the keys to extract - from the dictionary and values are the defaults to use if - the key is not in the target dict. - :returns tuple - """ - vals = [] - for key, default in keys.items(): - vals.append(dictionary.get(key, default)) - if dictionary.get(key, None) is not None: - del dictionary[key] - return tuple(vals) From 002f01657c831588f246f44760a4886d2f25b38d Mon Sep 17 00:00:00 2001 From: underscorephil Date: Fri, 8 Aug 2014 13:58:40 -0500 Subject: [PATCH 0087/2667] Add hardware support for rescue env ref issues/371 --- SoftLayer/CLI/modules/server.py | 25 +++++++++++++++++++++++++ SoftLayer/managers/hardware.py | 7 +++++++ 2 files changed, 32 insertions(+) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 66417b7b9..d767aa373 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -436,6 +436,31 @@ def execute(self, args): return table +class ServerRescue(environment.CLIRunnable): + """ +usage: sl server rescue [options] + +Reboot server into a rescue image + + +""" + action = 'rescue' + options = ['confirm'] + + def execute(self, args): + server = SoftLayer.HardwareManager(self.client) + server_id = helpers.resolve_id(server.resolve_ids, + args.get(''), + 'hardware') + + if args['--really'] or formatting.confirm( + "This action will reboot this server. " + "Continue?"): + + server.rescue(server_id) + else: + raise exceptions.CLIAbort('Aborted') + class ServerCreateOptions(environment.CLIRunnable): """ diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index f96d599ef..7f6de620c 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -318,6 +318,13 @@ def reload(self, hardware_id, post_uri=None, ssh_keys=None): return self.hardware.reloadOperatingSystem('FORCE', config, id=hardware_id) + def rescue(self, hardware_id): + """ Reboot a server into the a recsue kernel + + :param integer instance_id: the server ID to rescue + """ + return self.hardware.bootToRescueLayer(id=hardware_id) + def change_port_speed(self, hardware_id, public, speed): """ Allows you to change the port speed of a server's NICs. From 8c2f58f3295de1458f39a5e22b6aea627b63626d Mon Sep 17 00:00:00 2001 From: underscorephil Date: Fri, 8 Aug 2014 14:06:06 -0500 Subject: [PATCH 0088/2667] Add tests for hardware rescue --- SoftLayer/testing/fixtures/Hardware_Server.py | 1 + SoftLayer/tests/managers/hardware_tests.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/SoftLayer/testing/fixtures/Hardware_Server.py b/SoftLayer/testing/fixtures/Hardware_Server.py index 883c7741c..98fb903f7 100644 --- a/SoftLayer/testing/fixtures/Hardware_Server.py +++ b/SoftLayer/testing/fixtures/Hardware_Server.py @@ -73,3 +73,4 @@ reloadOperatingSystem = 'OK' getReverseDomainRecords = [ {'resourceRecords': [{'data': '2.0.1.10.in-addr.arpa'}]}] +bootToRescueLayer = True diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py index 147eee3a0..51fd22f08 100644 --- a/SoftLayer/tests/managers/hardware_tests.py +++ b/SoftLayer/tests/managers/hardware_tests.py @@ -430,3 +430,12 @@ def test_edit(self): self.hardware.edit(100, **args) service.editObject.assert_called_once_with(args, id=100) + + def test_rescue(self): + # Test rescue environment + hardware_id = 1234 + self.hardware.rescue(hardware_id) + + service = self.client['Hardware_Server'] + f = service.bootToRescueLayer + f.assert_called_once_with(id=hardware_id) From ae2c1d13e4d10d57beec08a15ead735a62db72f0 Mon Sep 17 00:00:00 2001 From: underscorephil Date: Fri, 8 Aug 2014 14:12:45 -0500 Subject: [PATCH 0089/2667] Fix pep8 issue --- SoftLayer/CLI/modules/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index d767aa373..5b663fe72 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -436,6 +436,7 @@ def execute(self, args): return table + class ServerRescue(environment.CLIRunnable): """ usage: sl server rescue [options] From ae0b5a440d126ccc30012ed3f7068dfde87035db Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Fri, 8 Aug 2014 14:37:14 -0500 Subject: [PATCH 0090/2667] adding testcases and moving function from utils to helpers --- SoftLayer/CLI/helpers.py | 10 ++++++++++ SoftLayer/CLI/modules/vs.py | 2 +- SoftLayer/tests/CLI/helper_tests.py | 17 ++++++++++++++++- SoftLayer/tests/CLI/modules/vs_tests.py | 2 +- SoftLayer/utils.py | 10 ---------- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/helpers.py b/SoftLayer/CLI/helpers.py index 135e36052..60d2852e9 100644 --- a/SoftLayer/CLI/helpers.py +++ b/SoftLayer/CLI/helpers.py @@ -30,3 +30,13 @@ def resolve_id(resolver, identifier, name='object'): (name, identifier, ', '.join([str(_id) for _id in ids]))) return ids[0] + + +def sanitize_args(args): + """ sanitize input (remove = sign from argument values) + :returns args back + """ + for key, value in args.items(): + if isinstance(value, str) and value.startswith('='): + args[key] = value[1:] + return args diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index e331c83ec..679acef60 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -577,7 +577,7 @@ def _parse_create_args(self, args): :param dict args: CLI arguments """ - args = utils.sanitize_args(args) + args = helpers.sanitize_args(args) data = { "hourly": args['--hourly'], "cpus": args['--cpu'], diff --git a/SoftLayer/tests/CLI/helper_tests.py b/SoftLayer/tests/CLI/helper_tests.py index e6dfdd2c7..3c804c688 100644 --- a/SoftLayer/tests/CLI/helper_tests.py +++ b/SoftLayer/tests/CLI/helper_tests.py @@ -249,7 +249,6 @@ class ResolveIdTests(testing.TestCase): def test_resolve_id_one(self): resolver = lambda r: [12345] id = helpers.resolve_id(resolver, 'test') - self.assertEqual(id, 12345) def test_resolve_id_none(self): @@ -423,3 +422,19 @@ def test_export_to_template(self): mock.call('datacenter=ams01\n'), mock.call('disk=disk1,disk2\n'), ], any_order=True) # Order isn't really guaranteed + + +class TestInputSanitization(testing.TestCase): + + def test_equalto_removed(self): + args = { + '--os': None, + '--datacenter': '=ams01', + '--memory': '=1g', + '--test': '=1344'} + argnew = helpers.sanitize_args(args) + self.assertEqual(argnew, { + '--os': None, + '--datacenter': 'ams01', + '--memory': '1g', + '--test': '1344'}) diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index e9b945768..3599287ff 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -105,7 +105,7 @@ def test_create(self, confirm_mock): '--hostname': 'host', '--image': None, '--os': 'UBUNTU_LATEST', - '--memory': '1024', + '--memory': '=1024', '--nic': '100', '--hourly': True, '--monthly': False, diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index c0cce001a..0162dc868 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -173,13 +173,3 @@ def dict_extract(dictionary, keys): if dictionary.get(key, None) is not None: del dictionary[key] return tuple(vals) - - -def sanitize_args(args): - """ sanitize input (remove = sign from argument values) - :returns args back - """ - for key, value in args.items(): - if isinstance(value, str) and value.startswith('='): - args[key] = value[1:] - return args From a48933b2171857466baef4f3a03c1c2616d3ae0d Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Mon, 11 Aug 2014 13:42:56 -0500 Subject: [PATCH 0091/2667] check argument values --- SoftLayer/tests/CLI/modules/vs_tests.py | 78 ++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index 3599287ff..ecae41308 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -105,7 +105,7 @@ def test_create(self, confirm_mock): '--hostname': 'host', '--image': None, '--os': 'UBUNTU_LATEST', - '--memory': '=1024', + '--memory': '1024', '--nic': '100', '--hourly': True, '--monthly': False, @@ -134,3 +134,79 @@ def test_create(self, confirm_mock): 'id': 100, 'created': '2013-08-01 15:23:45'}], formatting.format_output(output, 'python')) + + def test_create_args(self, confirm_mock): + confirm_mock.return_value = True + command = vs.CreateVS(client=self.client) + output = command.execute({'--cpu': '2', + '--domain': 'example.com', + '--hostname': 'host', + '--image': None, + '--os': 'UBUNTU_LATEST', + '--memory': '=1024', + '--nic': '100', + '--hourly': True, + '--monthly': False, + '--like': None, + '--datacenter': None, + '--dedicated': False, + '--san': False, + '--test': False, + '--export': None, + '--userfile': None, + '--postinstall': None, + '--key': [], + '--network': [], + '--disk': [], + '--private': False, + '--template': None, + '--userdata': None, + '--vlan_public': None, + '--vlan_private': None, + '--wait': None, + '--really': False, + '--tag': 'dev,green'}) + + self.assertEqual([{'guid': '1a2b3c-1701', + 'id': 100, + 'created': '2013-08-01 15:23:45'}], + formatting.format_output(output, 'python')) + + def test_check_create_args(self, confirm_mock): + confirm_mock.return_value = True + command = vs.CreateVS(client=self.client) + output = command.execute({'--cpu': '2', + '--domain': 'example.com', + '--hostname': 'host', + '--image': None, + '--os': 'UBUNTU_LATEST', + '--memory': '=1024', + '--nic': '100', + '--hourly': True, + '--monthly': False, + '--like': None, + '--datacenter': None, + '--dedicated': False, + '--san': False, + '--test': False, + '--export': None, + '--userfile': None, + '--postinstall': None, + '--key': [], + '--network': [], + '--disk': [], + '--private': False, + '--template': None, + '--userdata': None, + '--vlan_public': None, + '--vlan_private': None, + '--wait': None, + '--really': False, + '--tag': 'dev,green'}) + + self.assertEqual([{'guid': '1a2b3c-1701', + 'id': 100, + 'created': '2013-08-01 15:23:45'}], + formatting.format_output(output, 'python')) + service = self.client['Virtual_Guest'] + service.createObject.assert_called_with(memory=1024) \ No newline at end of file From ba9a034710fe340e533794dc59128a733c533196 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Mon, 11 Aug 2014 15:43:30 -0500 Subject: [PATCH 0092/2667] check argument values --- SoftLayer/tests/CLI/modules/vs_tests.py | 38 +------------------------ 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index ecae41308..67fad1b72 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -135,43 +135,7 @@ def test_create(self, confirm_mock): 'created': '2013-08-01 15:23:45'}], formatting.format_output(output, 'python')) - def test_create_args(self, confirm_mock): - confirm_mock.return_value = True - command = vs.CreateVS(client=self.client) - output = command.execute({'--cpu': '2', - '--domain': 'example.com', - '--hostname': 'host', - '--image': None, - '--os': 'UBUNTU_LATEST', - '--memory': '=1024', - '--nic': '100', - '--hourly': True, - '--monthly': False, - '--like': None, - '--datacenter': None, - '--dedicated': False, - '--san': False, - '--test': False, - '--export': None, - '--userfile': None, - '--postinstall': None, - '--key': [], - '--network': [], - '--disk': [], - '--private': False, - '--template': None, - '--userdata': None, - '--vlan_public': None, - '--vlan_private': None, - '--wait': None, - '--really': False, - '--tag': 'dev,green'}) - - self.assertEqual([{'guid': '1a2b3c-1701', - 'id': 100, - 'created': '2013-08-01 15:23:45'}], - formatting.format_output(output, 'python')) - + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_check_create_args(self, confirm_mock): confirm_mock.return_value = True command = vs.CreateVS(client=self.client) From ac394d829ba9c7a9c97cdb5dd196aa5fa37ccc73 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Mon, 11 Aug 2014 16:12:00 -0500 Subject: [PATCH 0093/2667] check argument values --- SoftLayer/tests/CLI/modules/vs_tests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index 67fad1b72..609c5e9f4 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -165,12 +165,11 @@ def test_check_create_args(self, confirm_mock): '--vlan_public': None, '--vlan_private': None, '--wait': None, - '--really': False, - '--tag': 'dev,green'}) + '--really': False}) self.assertEqual([{'guid': '1a2b3c-1701', 'id': 100, 'created': '2013-08-01 15:23:45'}], formatting.format_output(output, 'python')) service = self.client['Virtual_Guest'] - service.createObject.assert_called_with(memory=1024) \ No newline at end of file + service.createObject.assert_called_with(maxMemory=1024, mask=mock.ANY) \ No newline at end of file From cb48379bd2a0e758ab6e9b22d4c504f93ef0e937 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Tue, 12 Aug 2014 10:11:17 -0500 Subject: [PATCH 0094/2667] check argument values --- SoftLayer/tests/CLI/modules/vs_tests.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index 609c5e9f4..9fae490d1 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -172,4 +172,10 @@ def test_check_create_args(self, confirm_mock): 'created': '2013-08-01 15:23:45'}], formatting.format_output(output, 'python')) service = self.client['Virtual_Guest'] - service.createObject.assert_called_with(maxMemory=1024, mask=mock.ANY) \ No newline at end of file + service.createObject.assert_called_with({'domain': 'example.com', + 'localDiskFlag': True, + 'startCpus': 2, + 'operatingSystemReferenceCode': 'UBUNTU_LATEST', + 'maxMemory': 1024, + 'hourlyBillingFlag': True, + 'hostname': 'host'}) From 476755b83977ba309f10ba857fd00327f8e6fd21 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Tue, 12 Aug 2014 10:40:31 -0500 Subject: [PATCH 0095/2667] check argument values --- SoftLayer/tests/CLI/modules/vs_tests.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index 9fae490d1..07acbc5ff 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -172,10 +172,11 @@ def test_check_create_args(self, confirm_mock): 'created': '2013-08-01 15:23:45'}], formatting.format_output(output, 'python')) service = self.client['Virtual_Guest'] - service.createObject.assert_called_with({'domain': 'example.com', - 'localDiskFlag': True, - 'startCpus': 2, - 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'maxMemory': 1024, - 'hourlyBillingFlag': True, - 'hostname': 'host'}) + service.createObject.assert_called_with({ + 'domain': 'example.com', + 'localDiskFlag': True, + 'startCpus': 2, + 'operatingSystemReferenceCode': 'UBUNTU_LATEST', + 'maxMemory': 1024, + 'hourlyBillingFlag': True, + 'hostname': 'host'}) From d449ae58ef61ab99faea54ddc9f9d1da1dd37634 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Tue, 12 Aug 2014 14:34:11 -0500 Subject: [PATCH 0096/2667] catch and ignore when exceptions are raised while retreiving reverse domain records --- SoftLayer/CLI/modules/server.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 5b663fe72..1e0d24d0b 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -200,14 +200,16 @@ def execute(self, args): table.add_row(['tags', formatting.listing(tag_row, separator=',')]) # Test to see if this actually has a primary (public) ip address - if result['primaryIpAddress']: - ptr_domains = (self.client['Hardware_Server'] - .getReverseDomainRecords(id=hardware_id)) - - for ptr_domain in ptr_domains: - for ptr in ptr_domain['resourceRecords']: - table.add_row(['ptr', ptr['data']]) - + try: + if result['primaryIpAddress']: + ptr_domains = (self.client['Hardware_Server'] + .getReverseDomainRecords(id=hardware_id)) + + for ptr_domain in ptr_domains: + for ptr in ptr_domain['resourceRecords']: + table.add_row(['ptr', ptr['data']]) + except: + pass return table From 57c093d4deea0549cbfd532eeca13c415b94e249 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Tue, 12 Aug 2014 15:50:01 -0500 Subject: [PATCH 0097/2667] handle non-existent reverse domains --- SoftLayer/CLI/modules/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 1e0d24d0b..4b52985c5 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -208,7 +208,7 @@ def execute(self, args): for ptr_domain in ptr_domains: for ptr in ptr_domain['resourceRecords']: table.add_row(['ptr', ptr['data']]) - except: + except Exception: pass return table From fc325ccfc33ca874963569dde72de321cc755e1c Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Wed, 13 Aug 2014 09:51:32 -0500 Subject: [PATCH 0098/2667] removing general exception --- SoftLayer/CLI/modules/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 4b52985c5..879ecd7ac 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -25,6 +25,7 @@ # :license: MIT, see LICENSE for more details. import os import re +import SoftLayer import SoftLayer from SoftLayer.CLI import environment @@ -208,7 +209,7 @@ def execute(self, args): for ptr_domain in ptr_domains: for ptr in ptr_domain['resourceRecords']: table.add_row(['ptr', ptr['data']]) - except Exception: + except SoftLayer.SoftLayerAPIError: pass return table From a532c530e29b166c7e6dd84ddcf3af001f660040 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Wed, 13 Aug 2014 10:10:11 -0500 Subject: [PATCH 0099/2667] removing general exception --- SoftLayer/CLI/modules/server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 879ecd7ac..f7aa1d925 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -25,7 +25,6 @@ # :license: MIT, see LICENSE for more details. import os import re -import SoftLayer import SoftLayer from SoftLayer.CLI import environment From 899b2a94adc571cdd9ff34f0d21bd24a898d6627 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Wed, 13 Aug 2014 11:11:51 -0500 Subject: [PATCH 0100/2667] IMS checks privateNetworkOnlyFlag for reserver domains --- SoftLayer/CLI/modules/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index f7aa1d925..be390b386 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -201,7 +201,7 @@ def execute(self, args): # Test to see if this actually has a primary (public) ip address try: - if result['primaryIpAddress']: + if not result['privateNetworkOnlyFlag']: ptr_domains = (self.client['Hardware_Server'] .getReverseDomainRecords(id=hardware_id)) From 4b4dc64ff9b99caa3f72a1494a68f2b1d343cd6a Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Wed, 13 Aug 2014 11:35:13 -0500 Subject: [PATCH 0101/2667] changing test for issue 370 to sync it with IMS check --- SoftLayer/tests/CLI/modules/server_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index feb6dc556..32671126b 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -243,7 +243,7 @@ def test_server_details(self): def test_server_details_issue_332(self): runnable = server.ServerDetails(client=self.client) result = fixtures.Hardware_Server.getObject.copy() - result['primaryIpAddress'] = None + result['privateNetworkOnlyFlag'] = True self.client['Hardware_Server'].getObject.return_value = result runnable.execute({'': 1234, From c352245d0b31a9df9e01aa7d3fd1c3526ff59493 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 19 Aug 2014 17:02:57 -0500 Subject: [PATCH 0102/2667] Added check for privateNetworkOnly in vs to match server --- SoftLayer/CLI/modules/vs.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 6f5400855..1a5c5807e 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -208,13 +208,16 @@ def execute(self, args): table.add_row(['tags', formatting.listing(tag_row, separator=',')]) # Test to see if this actually has a primary (public) ip address - if result['primaryIpAddress']: - ptr_domains = (self.client['Virtual_Guest'] - .getReverseDomainRecords(id=vs_id)) - - for ptr_domain in ptr_domains: - for ptr in ptr_domain['resourceRecords']: - table.add_row(['ptr', ptr['data']]) + try: + if not result['privateNetworkOnlyFlag']: + ptr_domains = (self.client['Virtual_Guest'] + .getReverseDomainRecords(id=vs_id)) + + for ptr_domain in ptr_domains: + for ptr in ptr_domain['resourceRecords']: + table.add_row(['ptr', ptr['data']]) + except SoftLayer.SoftLayerAPIError: + pass return table From 7246780cbe8739e10db846b2d93f64bcafef2faf Mon Sep 17 00:00:00 2001 From: Scott Thompson Date: Thu, 21 Aug 2014 16:47:07 -0500 Subject: [PATCH 0103/2667] Fixes #381\nFirewall orders now take into account redundant interface pairs (or any other interface groups) when determining which firewall to order for a server --- SoftLayer/managers/firewall.py | 50 ++++++++++++++--- SoftLayer/testing/fixtures/Hardware_Server.py | 33 ++++++++++- SoftLayer/testing/fixtures/Virtual_Guest.py | 2 +- SoftLayer/tests/managers/firewall_tests.py | 56 +++++++++++++------ 4 files changed, 113 insertions(+), 28 deletions(-) diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index df6947bc5..9d04c8b9d 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -49,16 +49,10 @@ def get_standard_package(self, server_id, is_cci=True): False for a server :returns: A dictionary containing the standard CCI firewall package """ - mask = ('mask[primaryNetworkComponent[speed]]') - if is_cci: - svc = self.client['Virtual_Guest'] - else: - svc = self.client['Hardware_Server'] - - item = svc.getObject(mask=mask, id=server_id) + firewall_port_speed = self._get_fwl_port_speed(server_id, is_cci) _filter = utils.NestedDict({}) - _value = "%s%s" % (item['primaryNetworkComponent']['speed'], + _value = "%s%s" % (firewall_port_speed, "Mbps Hardware Firewall") _filter['items']['description'] = utils.query_filter(_value) @@ -162,6 +156,46 @@ def _get_fwl_billing_item(self, firewall_id, dedicated=False): fwl_svc = self.client['Network_Component_Firewall'] return fwl_svc.getObject(id=firewall_id, mask=mask) + def _get_fwl_port_speed(self, server_id, is_cci=True): + fwl_port_speed = 0 + if is_cci: + mask = ('mask[primaryNetworkComponent[maxSpeed]]') + svc = self.client['Virtual_Guest'] + primary = svc.getObject(mask=mask, id=server_id) + fwl_port_speed = primary['primaryNetworkComponent']['maxSpeed'] + else: + mask = ('mask[id,maxSpeed,' + 'networkComponentGroup.networkComponents]') + svc = self.client['Hardware_Server'] + network_components = svc.getFrontendNetworkComponents( + mask=mask, id=server_id) + grouped = [interface['networkComponentGroup']['networkComponents'] + for interface in network_components + if 'networkComponentGroup' in interface] + ungrouped = [interface + for interface in network_components + if not('networkComponentGroup' in interface)] + + # For each group, sum the maxSpeeds of each compoment in the + # group. Put the sum for each in a new list + group_speeds = [] + for group in grouped: + group_speed = 0 + for interface in group: + group_speed += interface['maxSpeed'] + group_speeds.append(group_speed) + + # The max speed of all groups is the max of the list + max_grouped_speed = max(group_speeds) + + max_ungrouped = 0 + for interface in ungrouped: + max_ungrouped = max(max_ungrouped, interface['maxSpeed']) + + fwl_port_speed = max(max_grouped_speed, max_ungrouped) + + return fwl_port_speed + def get_firewalls(self): """ Returns a list of all firewalls on the account. diff --git a/SoftLayer/testing/fixtures/Hardware_Server.py b/SoftLayer/testing/fixtures/Hardware_Server.py index 98fb903f7..d5650cc85 100644 --- a/SoftLayer/testing/fixtures/Hardware_Server.py +++ b/SoftLayer/testing/fixtures/Hardware_Server.py @@ -23,7 +23,7 @@ 'primaryBackendIpAddress': '10.1.0.2', 'networkManagementIpAddress': '10.1.0.3', 'hardwareStatus': {'status': 'ACTIVE'}, - "primaryNetworkComponent": {"speed": 10}, + 'primaryNetworkComponent': {'maxSpeed': 10, 'speed': 10}, 'provisionDate': '2013-08-01 15:23:45', 'notes': 'These are test notes.', 'operatingSystem': { @@ -74,3 +74,34 @@ getReverseDomainRecords = [ {'resourceRecords': [{'data': '2.0.1.10.in-addr.arpa'}]}] bootToRescueLayer = True +getFrontendNetworkComponents = [ + {'maxSpeed': 100}, + { + 'maxSpeed': 1000, + 'networkComponentGroup': { + 'groupTypeId': 2, + 'networkComponents': [{'maxSpeed': 1000}, {'maxSpeed': 1000}] + } + }, + { + 'maxSpeed': 1000, + 'networkComponentGroup': { + 'groupTypeId': 2, + 'networkComponents': [{'maxSpeed': 1000}, {'maxSpeed': 1000}] + } + }, + { + 'maxSpeed': 1000, + 'networkComponentGroup': { + 'groupTypeId': 2, + 'networkComponents': [{'maxSpeed': 1000}, {'maxSpeed': 1000}] + } + }, + { + 'maxSpeed': 1000, + 'networkComponentGroup': { + 'groupTypeId': 2, + 'networkComponents': [{'maxSpeed': 1000}, {'maxSpeed': 1000}] + } + } +] diff --git a/SoftLayer/testing/fixtures/Virtual_Guest.py b/SoftLayer/testing/fixtures/Virtual_Guest.py index de9f16ca2..0ea9adeaa 100644 --- a/SoftLayer/testing/fixtures/Virtual_Guest.py +++ b/SoftLayer/testing/fixtures/Virtual_Guest.py @@ -23,7 +23,7 @@ 'primaryIpAddress': '172.16.240.2', 'globalIdentifier': '1a2b3c-1701', 'primaryBackendIpAddress': '10.45.19.37', - "primaryNetworkComponent": {"speed": 10}, + "primaryNetworkComponent": {"speed": 10, "maxSpeed": 100}, 'hourlyBillingFlag': False, 'createDate': '2013-08-01 15:23:45', 'blockDevices': [{"device": 0, "uuid": 1}, diff --git a/SoftLayer/tests/managers/firewall_tests.py b/SoftLayer/tests/managers/firewall_tests.py index 97d3d25b1..93e367832 100644 --- a/SoftLayer/tests/managers/firewall_tests.py +++ b/SoftLayer/tests/managers/firewall_tests.py @@ -55,35 +55,43 @@ def test_get_dedicated_fwl_rules(self): call.assert_called_once_with(id=1234, mask=MASK) self.assertEqual(rules, fixtures.Network_Vlan_Firewall.getRules) - def test_get_standard_package(self): + def test_get_standard_package_virtual_server(self): # test standard firewalls self.firewall.get_standard_package(server_id=1234, is_cci=True) - call2 = self.client['Virtual_Guest'].getObject - mask = ('mask[primaryNetworkComponent[speed]]') - f = self.client['Product_Package'].getItems + package_call = self.client['Product_Package'].getItems _filter = { 'items': { 'description': { - 'operation': '_= 10Mbps Hardware Firewall' + 'operation': '_= 100Mbps Hardware Firewall' } } } - f.assert_called_once_with(filter=_filter, id=0) + package_call.assert_called_once_with(filter=_filter, id=0) + + mask = ('mask[primaryNetworkComponent[maxSpeed]]') + call2 = self.client['Virtual_Guest'].getObject call2.assert_called_once_with(id=1234, mask=mask) + def test_get_standard_package_bare_metal(self): self.firewall.get_standard_package(server_id=1234, is_cci=False) - call2 = self.client['Hardware_Server'].getObject - mask = ('mask[primaryNetworkComponent[speed]]') + + # we should ask for the frontEndNetworkComponents to get + # the firewall port speed + mask = ('mask[id,maxSpeed,' + 'networkComponentGroup.networkComponents]') + fenc_call = self.client['Hardware_Server'].getFrontendNetworkComponents + fenc_call.assert_called_once_with(id=1234, mask=mask) + + # shiould call the product package for a 2000Mbps firwall f = self.client['Product_Package'].getItems _filter = { 'items': { 'description': { - 'operation': '_= 10Mbps Hardware Firewall' + 'operation': '_= 2000Mbps Hardware Firewall' } } } - f.assert_called_twice_with(filter=_filter, id=0) - call2.assert_called_once_with(id=1234, mask=mask) + f.assert_called_once_with(filter=_filter, id=0) def test_get_dedicated_package_ha(self): # test dedicated HA firewalls @@ -140,38 +148,50 @@ def test_add_standard_firewall_cci(self): _filter = { 'items': { 'description': { - 'operation': '_= 10Mbps Hardware Firewall' + 'operation': '_= 100Mbps Hardware Firewall' } } } f.assert_called_once_with(filter=_filter, id=0) call2 = self.client['Virtual_Guest'].getObject - mask = ('mask[primaryNetworkComponent[speed]]') - call2.assert_called_once_with(id=6327, mask=mask) + mask = ('mask[primaryNetworkComponent[maxSpeed]]') + call2.assert_called_once_with(id=server_id, mask=mask) f = self.client['Product_Order'].placeOrder f.assert_called_once() def test_add_standard_firewall_server(self): # test dedicated firewall for Servers server_id = 6327 - mask = ('mask[primaryNetworkComponent[speed]]') self.firewall.add_standard_firewall(server_id, is_cci=False) + + # The placeOrder call should be made at the end of the routine f = self.client['Product_Order'].placeOrder f.assert_called_once() + # We should query the product package for a 2000Mbps firewall f = self.client['Product_Package'].getItems _filter = { 'items': { 'description': { - 'operation': '_= 10Mbps Hardware Firewall' + 'operation': '_= 2000Mbps Hardware Firewall' } } } f.assert_called_once_with(filter=_filter, id=0) - call2 = self.client['Hardware_Server'].getObject - call2.assert_called_once_with(id=6327, mask=mask) + # we should ask for the frontEndNetworkComponents to get + # the firewall port speed + mask = ('mask[id,maxSpeed,' + 'networkComponentGroup.networkComponents]') + fenc_call = self.client['Hardware_Server'].getFrontendNetworkComponents + fenc_call.assert_called_once_with(id=server_id, mask=mask) + + def test__get_fwl_port_speed_server(self): + # Test the routine that calculates the speed of firewall + # required for a server + port_speed = self.firewall._get_fwl_port_speed(186908, False) + self.assertEqual(port_speed, 2000) def test_add_vlan_firewall(self): # test dedicated firewall for Vlan From 13420f20bd26dd8f79311aff27b6add7c788ca49 Mon Sep 17 00:00:00 2001 From: Scott Thompson Date: Thu, 21 Aug 2014 17:19:54 -0500 Subject: [PATCH 0104/2667] Removed unneeded parens and adde a docstring --- SoftLayer/managers/firewall.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index 9d04c8b9d..ef9ad74f3 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -157,6 +157,12 @@ def _get_fwl_billing_item(self, firewall_id, dedicated=False): return fwl_svc.getObject(id=firewall_id, mask=mask) def _get_fwl_port_speed(self, server_id, is_cci=True): + """ Determines the appropriate speed for a firewall + + :param int server_id: The ID of server the firewall is for + :param bool is_cci: true if the server_id is for a virtual server + :returns: a integer representing the Mbps speed of a firewall + """ fwl_port_speed = 0 if is_cci: mask = ('mask[primaryNetworkComponent[maxSpeed]]') @@ -174,7 +180,7 @@ def _get_fwl_port_speed(self, server_id, is_cci=True): if 'networkComponentGroup' in interface] ungrouped = [interface for interface in network_components - if not('networkComponentGroup' in interface)] + if 'networkComponentGroup' not in interface] # For each group, sum the maxSpeeds of each compoment in the # group. Put the sum for each in a new list From 879ae02e3e16139aeaa58c18f2af715b6d4d3d9a Mon Sep 17 00:00:00 2001 From: Scott Thompson Date: Fri, 22 Aug 2014 09:04:04 -0500 Subject: [PATCH 0105/2667] fixes #381. Additional commit to indicate a corrected build fixing this issue From c7dd64384608e55ca6388c773fc1aad7338cc213 Mon Sep 17 00:00:00 2001 From: Scott Thompson Date: Fri, 22 Aug 2014 09:11:23 -0500 Subject: [PATCH 0106/2667] fixes #381. Making pep8 happy From 943d9da9b09576844a9637ee22f41f57c9e77ad4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 4 Sep 2014 11:13:56 -0500 Subject: [PATCH 0107/2667] Adding some basic DNS import support to the CLI --- SoftLayer/CLI/modules/dns.py | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/SoftLayer/CLI/modules/dns.py b/SoftLayer/CLI/modules/dns.py index 0d9d6f389..25ec0916b 100755 --- a/SoftLayer/CLI/modules/dns.py +++ b/SoftLayer/CLI/modules/dns.py @@ -8,6 +8,7 @@ delete Delete zone list List zones or a zone's records print Print zone in BIND format + import Import a BIND style zone file The available record commands are: add Add resource record @@ -80,6 +81,61 @@ def execute(self, args): raise exceptions.CLIAbort("Aborted.") +class ImportZone(environment.CLIRunnable): + """ +usage: sl dns import + +Creates a new zone based of a BIND formatted file + """ + action = 'import' + def execute(self,args): + import pprint + import re + import sys + manager = SoftLayer.DNSManager(self.client) + zone = '' + records = {} + pp = pprint.PrettyPrinter(indent=2) + lines = [line.strip() for line in open(args[''])] + zoneSearch = re.search('\$ORIGIN (?P.*)\.',lines[0]) + zone = zoneSearch.group('zone') + + + try: + zone_id = helpers.resolve_id(manager.resolve_ids, zone,name='zone') + except : + print "Unexpected error:", sys.exc_info()[0] + zone_id = None + pass + if (zone_id is None): + + print "CREATING ZONE: %s" % (zone) + manager.create_zone(zone) + zone_id = helpers.resolve_id(manager.resolve_ids, zone,name='zone') + + print "ZoneID: %s" % (zone_id) + + + + for content in lines: + domainSearch = re.search('(?P\w+)\s+(?P\d+)\s+(?P\w+)\s+(?P\w+)\s+(?P.*)',content) + if (domainSearch is not None): + domainName = domainSearch.group('domain') + domainttl = domainSearch.group('ttl') + domainClass = domainSearch.group('class') + domainType = domainSearch.group('type') + domainRecord = domainSearch.group('record') + print "Domain: %s TTL: %s Class: %s Type: %s Record: %s" % (domainName,domainttl,domainClass,domainType,domainRecord) + manager.create_record(zone_id,domainName,domainType,domainRecord,domainttl) + + continue + # print content + # zone = dns.zone.from_file(args['']) + print "IMPORT ZONE %s" % (args['']) + # pp.pprint(zone) + + + class ListZones(environment.CLIRunnable): """ usage: sl dns list [] [options] From 2a2ca0ff15acc8bafa7f59f1a116dc697c95bcb5 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 4 Sep 2014 12:41:19 -0500 Subject: [PATCH 0108/2667] Fixes test failures due to requests changes --- SoftLayer/tests/functional_tests.py | 6 ++--- SoftLayer/tests/transport_tests.py | 35 +++++++++++++++++++---------- SoftLayer/transports.py | 19 +++++++++------- tox.ini | 3 ++- 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/SoftLayer/tests/functional_tests.py b/SoftLayer/tests/functional_tests.py index 025e59ed1..ed70fe877 100644 --- a/SoftLayer/tests/functional_tests.py +++ b/SoftLayer/tests/functional_tests.py @@ -37,10 +37,8 @@ def test_no_hostname(self): # This test will fail if 'notvalidsoftlayer.com' becomes a thing SoftLayer.transports.make_xml_rpc_api_call( 'http://notvalidsoftlayer.com', 'getObject') - except SoftLayer.SoftLayerAPIError as e: - self.assertEqual(e.faultCode, 0) - self.assertIn('not known', e.faultString) - self.assertIn('not known', e.reason) + except Exception as ex: + self.assertIn('not known', str(ex)) else: self.fail('No Exception Raised') diff --git a/SoftLayer/tests/transport_tests.py b/SoftLayer/tests/transport_tests.py index 196795dbc..5284e2bed 100644 --- a/SoftLayer/tests/transport_tests.py +++ b/SoftLayer/tests/transport_tests.py @@ -15,7 +15,8 @@ class TestXmlRpcAPICall(testing.TestCase): def set_up(self): - self.send_content = ''' + self.response = mock.MagicMock() + self.response.content = ''' @@ -26,9 +27,9 @@ def set_up(self): ''' - @mock.patch('SoftLayer.transports.requests.Session.send') - def test_call(self, send): - send().content = self.send_content + @mock.patch('requests.request') + def test_call(self, request): + request.return_value = self.response data = ''' @@ -46,33 +47,43 @@ def test_call(self, send): ''' resp = transports.make_xml_rpc_api_call( 'http://something.com/path/to/resource', 'getObject') - args = send.call_args + args = request.call_args self.assertIsNotNone(args) args, kwargs = args - send.assert_called_with(mock.ANY, proxies=None, timeout=None) + request.assert_called_with('POST', + 'http://something.com/path/to/resource', + headers=None, + proxies=None, + data=data, + timeout=None) self.assertEqual(resp, []) - self.assertEqual(args[0].body, data) def test_proxy_without_protocol(self): + # NOTE(sudorandom): This used to be an instance of requests.HTTPError, + # but something changes in requests to make that no + # longer the case. self.assertRaises( - SoftLayer.TransportError, + Exception, # NOQA transports.make_xml_rpc_api_call, 'http://something.com/path/to/resource', 'getObject', 'localhost:3128') - @mock.patch('SoftLayer.transports.requests.Session.send') - def test_valid_proxy(self, send): - send().content = self.send_content + @mock.patch('requests.request') + def test_valid_proxy(self, request): + request.return_value = self.response transports.make_xml_rpc_api_call( 'http://something.com/path/to/resource', 'getObject', proxy='http://localhost:3128') - send.assert_called_with( + request.assert_called_with( + 'POST', mock.ANY, + headers=None, proxies={'https': 'http://localhost:3128', 'http': 'http://localhost:3128'}, + data=mock.ANY, timeout=None) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 364c60f24..44fc9812c 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -42,17 +42,16 @@ def make_xml_rpc_api_call(uri, method, args=None, headers=None, payload = utils.xmlrpc_client.dumps(tuple(largs), methodname=method, allow_none=True) - session = requests.Session() - req = requests.Request('POST', uri, data=payload, - headers=http_headers).prepare() LOGGER.debug("=== REQUEST ===") LOGGER.info('POST %s', uri) - LOGGER.debug(req.headers) + LOGGER.debug(http_headers) LOGGER.debug(payload) - response = session.send(req, - timeout=timeout, - proxies=_proxies_dict(proxy)) + response = requests.request('POST', uri, + data=payload, + headers=http_headers, + timeout=timeout, + proxies=_proxies_dict(proxy)) LOGGER.debug("=== RESPONSE ===") LOGGER.debug(response.headers) LOGGER.debug(response.content) @@ -91,14 +90,18 @@ def make_rest_api_call(method, url, :param dict http_headers: HTTP headers to use for the request :param int timeout: number of seconds to use as a timeout """ + LOGGER.debug("=== REQUEST ===") LOGGER.info('%s %s', method, url) + LOGGER.debug(http_headers) try: resp = requests.request(method, url, headers=http_headers, timeout=timeout, proxies=_proxies_dict(proxy)) - resp.raise_for_status() + LOGGER.debug("=== RESPONSE ===") + LOGGER.debug(resp.headers) LOGGER.debug(resp.content) + resp.raise_for_status() if url.endswith('.json'): return json.loads(resp.content) else: diff --git a/tox.ini b/tox.ini index 8e68e57e4..5abdf9e0d 100644 --- a/tox.ini +++ b/tox.ini @@ -21,10 +21,11 @@ deps = hacking pylint commands = - flake8 --max-complexity=36 --statistics \ + flake8 --max-complexity=36 \ --ignore=H401,H402,H404,H405 \ SoftLayer pylint SoftLayer \ + -r n \ # Don't show the long report --ignore=tests,testing \ -d R0903 \ # Too few public methods -d R0914 \ # Too many local variables From 61d62f2b67062ca5aaa804fbe855a3ed9379f04e Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 4 Sep 2014 12:49:32 -0500 Subject: [PATCH 0109/2667] Adds notes/references to the requests lib issue --- SoftLayer/tests/functional_tests.py | 3 +++ SoftLayer/tests/transport_tests.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/SoftLayer/tests/functional_tests.py b/SoftLayer/tests/functional_tests.py index ed70fe877..9f2af984d 100644 --- a/SoftLayer/tests/functional_tests.py +++ b/SoftLayer/tests/functional_tests.py @@ -38,6 +38,9 @@ def test_no_hostname(self): SoftLayer.transports.make_xml_rpc_api_call( 'http://notvalidsoftlayer.com', 'getObject') except Exception as ex: + # NOTE(sudorandom): This used to be an instance of + # SoftLayer.SoftLayerAPIError + # Related issue: https://github.com/kennethreitz/requests/pull/2193 self.assertIn('not known', str(ex)) else: self.fail('No Exception Raised') diff --git a/SoftLayer/tests/transport_tests.py b/SoftLayer/tests/transport_tests.py index 5284e2bed..bacd3c887 100644 --- a/SoftLayer/tests/transport_tests.py +++ b/SoftLayer/tests/transport_tests.py @@ -63,6 +63,8 @@ def test_proxy_without_protocol(self): # NOTE(sudorandom): This used to be an instance of requests.HTTPError, # but something changes in requests to make that no # longer the case. + # Related issue: + # https://github.com/kennethreitz/requests/pull/2193 self.assertRaises( Exception, # NOQA transports.make_xml_rpc_api_call, From 3e1c10487b80a642b1372ebb1b016923de6efe74 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 4 Sep 2014 17:23:27 -0500 Subject: [PATCH 0110/2667] Adds common CLI options at all levels --- SoftLayer/CLI/core.py | 41 +++++++++++++++++-------------- SoftLayer/CLI/modules/summary.py | 17 ++++++------- SoftLayer/managers/network.py | 3 +-- SoftLayer/tests/CLI/core_tests.py | 3 --- 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index b3e33cf87..fa383eb74 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -69,6 +69,25 @@ VALID_FORMATS = ['raw', 'table', 'json'] +def _append_common_options(arg_doc): + """Append common options to the doc string""" + default_format = 'raw' + if sys.stdout.isatty(): + default_format = 'table' + + arg_doc += """ +Standard Options: + --format=ARG Output format. [Options: table, raw] [Default: %s] + -C FILE --config=FILE Config file location. [Default: ~/.softlayer] + --debug=LEVEL Specifies the debug noise level + 1=warn, 2=info, 3=debug + --timings Time each API call and display after results + --proxy=PROTO:PROXY_URL HTTP[s] proxy to be use to make API calls + -h --help Show this screen +""" % default_format + return arg_doc + + class CommandParser(object): """Helper class to parse commands. @@ -79,22 +98,17 @@ def __init__(self, env): def get_main_help(self): """Get main help text.""" - return __doc__.strip() + return _append_common_options(__doc__).strip() def get_module_help(self, module_name): """Get help text for a module.""" module = self.env.load_module(module_name) arg_doc = module.__doc__ - return arg_doc.strip() + return _append_common_options(arg_doc).strip() def get_command_help(self, module_name, command_name): """Get help text for a specific command.""" command = self.env.get_command(module_name, command_name) - - default_format = 'raw' - if sys.stdout.isatty(): - default_format = 'table' - arg_doc = command.__doc__ if 'confirm' in command.options: @@ -103,18 +117,7 @@ def get_command_help(self, module_name, command_name): -y, --really Confirm all prompt actions """ - if '[options]' in arg_doc: - arg_doc += """ -Standard Options: - --format=ARG Output format. [Options: table, raw] [Default: %s] - -C FILE --config=FILE Config file location. [Default: ~/.softlayer] - --debug=LEVEL Specifies the debug noise level - 1=warn, 2=info, 3=debug - --timings Time each API call and display after results - --proxy=PROTO:PROXY_URL HTTP[s] proxy to be use to make API calls - -h --help Show this screen -""" % default_format - return arg_doc.strip() + return _append_common_options(arg_doc).strip() def parse_main_args(self, args): """Parse root arguments.""" diff --git a/SoftLayer/CLI/modules/summary.py b/SoftLayer/CLI/modules/summary.py index 7c00aaf61..5a7630778 100644 --- a/SoftLayer/CLI/modules/summary.py +++ b/SoftLayer/CLI/modules/summary.py @@ -1,9 +1,14 @@ """ -usage: sl summary [options] +usage: sl summary [] [...] [options] Display summary information about the account + +Options: + --sortby=ARG Column to sort by. options: datacenter, vlans, + subnets, IPs, networking, hardware, vs """ # :license: MIT, see LICENSE for more details. +# pylint: disable=missing-docstring import SoftLayer from SoftLayer.CLI import environment @@ -11,15 +16,7 @@ class Summary(environment.CLIRunnable): - """ -usage: sl summary [options] - -Display summary information about the account - -Options: - --sortby=ARG Column to sort by. options: datacenter, vlans, - subnets, IPs, networking, hardware, vs -""" + __doc__ = __doc__ action = None def execute(self, args): diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 42667a342..f693f8893 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -334,8 +334,7 @@ def summary_by_datacenter(self): unique_network = [] for vlan in self.list_vlans(): - datacenter = vlan['primaryRouter']['datacenter'] - name = datacenter['name'] + name = utils.lookup(vlan, 'primaryRouter', 'datacenter', 'name') if name not in datacenters: datacenters[name] = { 'hardwareCount': 0, diff --git a/SoftLayer/tests/CLI/core_tests.py b/SoftLayer/tests/CLI/core_tests.py index 1dca5290a..ce6056d34 100644 --- a/SoftLayer/tests/CLI/core_tests.py +++ b/SoftLayer/tests/CLI/core_tests.py @@ -197,7 +197,6 @@ def test_primary_help(self): args = self.parser.parse_main_args(args=[]) self.assertEqual({ '--help': False, - '-h': False, '': [], '': None, '': None, @@ -207,7 +206,6 @@ def test_primary_help(self): args = self.parser.parse_main_args(args=['help']) self.assertEqual({ '--help': False, - '-h': False, '': [], '': 'help', '': None, @@ -217,7 +215,6 @@ def test_primary_help(self): args = self.parser.parse_main_args(args=['help', 'module']) self.assertEqual({ '--help': False, - '-h': False, '': ['module'], '': 'help', '': None, From 0b6536d9d398b6c8cdfcd17f2f50702086b41648 Mon Sep 17 00:00:00 2001 From: Wolfgang Nagele Date: Thu, 4 Sep 2014 15:20:18 +1000 Subject: [PATCH 0111/2667] Fix for issue #383 --- SoftLayer/managers/hardware.py | 2 +- SoftLayer/managers/ordering.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 7f6de620c..0caed70df 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -224,7 +224,7 @@ def get_available_dedicated_server_packages(self): for package in packages: available_packages.append((package['id'], package['name'], - package['description'])) + package.get('description', None))) return available_packages diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 3942e7df3..d99d9dcbb 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -63,8 +63,8 @@ def filter_outlet_packages(packages): non_outlet_packages = [] for package in packages: - if all(['OUTLET' not in package['description'].upper(), - 'OUTLET' not in package['name'].upper()]): + if all(['OUTLET' not in package.get('description', '').upper(), + 'OUTLET' not in package.get('name', '').upper()]): non_outlet_packages.append(package) return non_outlet_packages From 354fcf486604b06384d3fac2f5a124f79313cf2e Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 5 Sep 2014 11:42:46 -0500 Subject: [PATCH 0112/2667] Adds CONTRIBUTORS file --- CONTRIBUTORS | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 CONTRIBUTORS diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 000000000..88442234c --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,29 @@ +Amol Jadhav +Aparna Patil +Boden Russell +Brian Cline +chechuironman +Christopher Gallo +David Ibarra +Hans Kristian Moen +Jake Williams +Jason Johnson +Kevin Landreth +Kevin McDonald +Łukasz Oleś +Nathan Beittenmiller +Neetu Jain +Paul Sroufe +Phil Jackson +Robert Chumbley +Ryan Hanson +Scott Thompson +Sergio Carlos +Shane Poage +simplydave +SoftLayer +suppandi +Swapnil Khanapurkar +The SoftLayer Developer Network +Tim Ariyeh +Wissam Elriachy From e933f819a7e007e0b7afcb48cb68d63d56079f14 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Mon, 8 Sep 2014 12:31:40 -0500 Subject: [PATCH 0113/2667] handle the case of missing billingitem in subnet cancel --- SoftLayer/managers/network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 42667a342..536b6ba9f 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -132,8 +132,9 @@ def cancel_subnet(self, subnet_id): :param int subnet_id: The ID of the subnet to be cancelled. """ subnet = self.get_subnet(subnet_id, mask='id, billingItem.id') + if "billingItem" not in subnet: + raise ValueError('can not cancel subnet %s' % subnet_id) billing_id = subnet['billingItem']['id'] - return self.client['Billing_Item'].cancelService(id=billing_id) def edit_rwhois(self, abuse_email=None, address1=None, address2=None, From 2f7e0dc6f4ebaf8b645a978ecdfbaf0be98a0459 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Mon, 8 Sep 2014 12:52:44 -0500 Subject: [PATCH 0114/2667] changing error raised --- SoftLayer/managers/network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 536b6ba9f..d3b299930 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -7,6 +7,7 @@ """ from SoftLayer import utils +from SoftLayer import exceptions DEFAULT_SUBNET_MASK = ','.join(['hardware', 'datacenter', @@ -133,7 +134,7 @@ def cancel_subnet(self, subnet_id): """ subnet = self.get_subnet(subnet_id, mask='id, billingItem.id') if "billingItem" not in subnet: - raise ValueError('can not cancel subnet %s' % subnet_id) + raise exceptions.SoftLayerError('Can not cancel subnet %s' % subnet_id) billing_id = subnet['billingItem']['id'] return self.client['Billing_Item'].cancelService(id=billing_id) From fd16472f1c358a5ad000dcb68bb32e628bb07675 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Mon, 8 Sep 2014 13:06:51 -0500 Subject: [PATCH 0115/2667] changing error raised --- SoftLayer/managers/network.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index d3b299930..4c2611954 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -6,8 +6,8 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import utils from SoftLayer import exceptions +from SoftLayer import utils DEFAULT_SUBNET_MASK = ','.join(['hardware', 'datacenter', @@ -134,7 +134,8 @@ def cancel_subnet(self, subnet_id): """ subnet = self.get_subnet(subnet_id, mask='id, billingItem.id') if "billingItem" not in subnet: - raise exceptions.SoftLayerError('Can not cancel subnet %s' % subnet_id) + raise exceptions.SoftLayerError('Can not cancel subnet %' + % subnet_id) billing_id = subnet['billingItem']['id'] return self.client['Billing_Item'].cancelService(id=billing_id) From b8515da3c5b2dc59dbaa247494c35446d380b794 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Mon, 8 Sep 2014 13:21:26 -0500 Subject: [PATCH 0116/2667] changing error raised --- SoftLayer/managers/network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 4c2611954..64b1fcebe 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -134,8 +134,8 @@ def cancel_subnet(self, subnet_id): """ subnet = self.get_subnet(subnet_id, mask='id, billingItem.id') if "billingItem" not in subnet: - raise exceptions.SoftLayerError('Can not cancel subnet %' - % subnet_id) + raise exceptions.SoftLayerError('Can not cancel ' + 'subnet %' % subnet_id) billing_id = subnet['billingItem']['id'] return self.client['Billing_Item'].cancelService(id=billing_id) From 172df2b2ac9e76df6efb43ebf79d6dc5bdd6cd4a Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Mon, 8 Sep 2014 13:37:35 -0500 Subject: [PATCH 0117/2667] pep8 happy --- SoftLayer/managers/network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 64b1fcebe..fcc6e28e6 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -134,8 +134,8 @@ def cancel_subnet(self, subnet_id): """ subnet = self.get_subnet(subnet_id, mask='id, billingItem.id') if "billingItem" not in subnet: - raise exceptions.SoftLayerError('Can not cancel ' - 'subnet %' % subnet_id) + raise exceptions.SoftLayerError('Can not cancel subnet %' % + subnet_id) billing_id = subnet['billingItem']['id'] return self.client['Billing_Item'].cancelService(id=billing_id) From 936f3065a730b7390644136e1d673695e0e504b1 Mon Sep 17 00:00:00 2001 From: Neetu Jain Date: Tue, 9 Sep 2014 12:01:12 -0500 Subject: [PATCH 0118/2667] pep8 happy --- SoftLayer/managers/network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index fcc6e28e6..d337c6f5a 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -134,8 +134,8 @@ def cancel_subnet(self, subnet_id): """ subnet = self.get_subnet(subnet_id, mask='id, billingItem.id') if "billingItem" not in subnet: - raise exceptions.SoftLayerError('Can not cancel subnet %' % - subnet_id) + raise exceptions.SoftLayerError("subnet %s can not be cancelled" + " " % subnet_id) billing_id = subnet['billingItem']['id'] return self.client['Billing_Item'].cancelService(id=billing_id) From a61500e62981c11f9c0403aebe68e12112758699 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 9 Sep 2014 15:31:18 -0500 Subject: [PATCH 0119/2667] Reverts tests because Requests 2.4.1 was released --- SoftLayer/tests/functional_tests.py | 7 +++---- SoftLayer/tests/transport_tests.py | 7 +------ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/SoftLayer/tests/functional_tests.py b/SoftLayer/tests/functional_tests.py index 9f2af984d..d121f9958 100644 --- a/SoftLayer/tests/functional_tests.py +++ b/SoftLayer/tests/functional_tests.py @@ -37,11 +37,10 @@ def test_no_hostname(self): # This test will fail if 'notvalidsoftlayer.com' becomes a thing SoftLayer.transports.make_xml_rpc_api_call( 'http://notvalidsoftlayer.com', 'getObject') - except Exception as ex: - # NOTE(sudorandom): This used to be an instance of - # SoftLayer.SoftLayerAPIError - # Related issue: https://github.com/kennethreitz/requests/pull/2193 + except SoftLayer.SoftLayerAPIError as ex: self.assertIn('not known', str(ex)) + self.assertIn('not known', ex.faultString) + self.assertEqual(ex.faultCode, 0) else: self.fail('No Exception Raised') diff --git a/SoftLayer/tests/transport_tests.py b/SoftLayer/tests/transport_tests.py index bacd3c887..a0e82f558 100644 --- a/SoftLayer/tests/transport_tests.py +++ b/SoftLayer/tests/transport_tests.py @@ -60,13 +60,8 @@ def test_call(self, request): self.assertEqual(resp, []) def test_proxy_without_protocol(self): - # NOTE(sudorandom): This used to be an instance of requests.HTTPError, - # but something changes in requests to make that no - # longer the case. - # Related issue: - # https://github.com/kennethreitz/requests/pull/2193 self.assertRaises( - Exception, # NOQA + SoftLayer.TransportError, # NOQA transports.make_xml_rpc_api_call, 'http://something.com/path/to/resource', 'getObject', From 68a667a4067415a04a37975533e5e489eeaae094 Mon Sep 17 00:00:00 2001 From: underscorephil Date: Wed, 10 Sep 2014 11:33:05 -0500 Subject: [PATCH 0120/2667] Fix doc error --- SoftLayer/CLI/modules/vs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 81a476b8b..9d5a1ef55 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -1104,7 +1104,7 @@ class UpgradeVS(environment.CLIRunnable): Upgrade parameters of a virtual server Examples: - sl vs upgrade --cpus 2 + sl vs upgrade --cpu 2 sl vs upgrade --memory 2048 --network 1000 Options: --cpu=CPU Number of CPU cores From 8d6270fdc559aa1e209a812658ff4f85d30c9a75 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 10 Sep 2014 14:42:49 -0500 Subject: [PATCH 0121/2667] Adds more details to image commands --- SoftLayer/CLI/formatting.py | 9 +++++++ SoftLayer/CLI/modules/image.py | 49 +++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index c8facc606..8c2578f28 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -104,6 +104,15 @@ def mb_to_gb(megabytes): return FormattedItem(megabytes, "%dG" % (float(megabytes) / 1024)) +def b_to_gb(_bytes): + """Converts number of bytes to a FormattedItem in gigabytes. + + :param int _bytes: number of bytes + """ + return FormattedItem(_bytes, + "%.2fG" % (float(_bytes) / 1024 / 1024 / 1024)) + + def gb(gigabytes): # pylint: disable=C0103 """Converts number of gigabytes to a FormattedItem in gigabytes. diff --git a/SoftLayer/CLI/modules/image.py b/SoftLayer/CLI/modules/image.py index 7223749f9..607561024 100644 --- a/SoftLayer/CLI/modules/image.py +++ b/SoftLayer/CLI/modules/image.py @@ -16,6 +16,14 @@ from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer import utils + +MASK = ('id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,' + 'imageType') +DETAIL_MASK = MASK + (',children[id,blockDevicesDiskSpaceTotal,datacenter],' + 'note') +PUBLIC_TYPE = formatting.FormattedItem('PUBLIC', 'Public') +PRIVATE_TYPE = formatting.FormattedItem('PRIVATE', 'Private') class ListImages(environment.CLIRunnable): @@ -34,32 +42,34 @@ def execute(self, args): image_mgr = SoftLayer.ImageManager(self.client) neither = not any([args['--private'], args['--public']]) - mask = 'id,accountId,name,globalIdentifier,blockDevices,parentId' images = [] if args['--private'] or neither: - for image in image_mgr.list_private_images(mask=mask): - image['visibility'] = 'private' + for image in image_mgr.list_private_images(mask=MASK): images.append(image) if args['--public'] or neither: - for image in image_mgr.list_public_images(mask=mask): - image['visibility'] = 'public' + for image in image_mgr.list_public_images(mask=MASK): images.append(image) table = formatting.Table(['id', 'account', - 'visibility', 'name', + 'type', + 'visibility', 'global_identifier']) images = [image for image in images if image['parentId'] == ''] for image in images: + table.add_row([ image['id'], image.get('accountId', formatting.blank()), - image['visibility'], image['name'].strip(), + formatting.FormattedItem( + utils.lookup(image, 'imageType', 'keyName'), + utils.lookup(image, 'imageType', 'name')), + PUBLIC_TYPE if image['publicFlag'] else PRIVATE_TYPE, image.get('globalIdentifier', formatting.blank()), ]) @@ -80,17 +90,36 @@ def execute(self, args): args.get(''), 'image') - image = image_mgr.get_image(image_id) + image = image_mgr.get_image(image_id, mask=DETAIL_MASK) table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' table.add_row(['id', image['id']]) - table.add_row(['account', image.get('accountId', formatting.blank())]) - table.add_row(['name', image['name'].strip()]) table.add_row(['global_identifier', image.get('globalIdentifier', formatting.blank())]) + table.add_row(['name', image['name'].strip()]) + table.add_row(['account', image.get('accountId', formatting.blank())]) + table.add_row(['visibility', + PUBLIC_TYPE if image['publicFlag'] else PRIVATE_TYPE]) + table.add_row(['type', + formatting.FormattedItem( + utils.lookup(image, 'imageType', 'keyName'), + utils.lookup(image, 'imageType', 'name'), + )]) + table.add_row(['flex', image.get('flexImageFlag')]) + table.add_row(['note', image.get('note')]) + disk_space = 0 + datacenters = [] + for child in image.get('children'): + disk_space = child.get('blockDevicesDiskSpaceTotal', 0) + if child.get('datacenter'): + datacenters.append(utils.lookup(child, 'datacenter', 'name')) + + table.add_row(['disk_space', formatting.b_to_gb(disk_space)]) + table.add_row(['datacenters', formatting.listing(datacenters, + separator=',')]) return table From 4fca17ef3f55c6fe7f77e9b22bc66086bb81c9f7 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 10 Sep 2014 14:45:15 -0500 Subject: [PATCH 0122/2667] Makes disk space an int --- SoftLayer/CLI/modules/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/modules/image.py b/SoftLayer/CLI/modules/image.py index 607561024..e74bdc6d0 100644 --- a/SoftLayer/CLI/modules/image.py +++ b/SoftLayer/CLI/modules/image.py @@ -113,7 +113,7 @@ def execute(self, args): disk_space = 0 datacenters = [] for child in image.get('children'): - disk_space = child.get('blockDevicesDiskSpaceTotal', 0) + disk_space = int(child.get('blockDevicesDiskSpaceTotal', 0)) if child.get('datacenter'): datacenters.append(utils.lookup(child, 'datacenter', 'name')) From be20777b062161c32cbf851c70c7d4d526cc0699 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 11 Sep 2014 11:16:24 -0500 Subject: [PATCH 0123/2667] Small cleanup. Adds creation date --- SoftLayer/CLI/modules/image.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/modules/image.py b/SoftLayer/CLI/modules/image.py index e74bdc6d0..653eeea1d 100644 --- a/SoftLayer/CLI/modules/image.py +++ b/SoftLayer/CLI/modules/image.py @@ -21,7 +21,7 @@ MASK = ('id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,' 'imageType') DETAIL_MASK = MASK + (',children[id,blockDevicesDiskSpaceTotal,datacenter],' - 'note') + 'note,createDate') PUBLIC_TYPE = formatting.FormattedItem('PUBLIC', 'Public') PRIVATE_TYPE = formatting.FormattedItem('PRIVATE', 'Private') @@ -91,6 +91,12 @@ def execute(self, args): 'image') image = image_mgr.get_image(image_id, mask=DETAIL_MASK) + disk_space = 0 + datacenters = [] + for child in image.get('children'): + disk_space = int(child.get('blockDevicesDiskSpaceTotal', 0)) + if child.get('datacenter'): + datacenters.append(utils.lookup(child, 'datacenter', 'name')) table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' @@ -110,13 +116,7 @@ def execute(self, args): )]) table.add_row(['flex', image.get('flexImageFlag')]) table.add_row(['note', image.get('note')]) - disk_space = 0 - datacenters = [] - for child in image.get('children'): - disk_space = int(child.get('blockDevicesDiskSpaceTotal', 0)) - if child.get('datacenter'): - datacenters.append(utils.lookup(child, 'datacenter', 'name')) - + table.add_row(['created', image.get('createDate')]) table.add_row(['disk_space', formatting.b_to_gb(disk_space)]) table.add_row(['datacenters', formatting.listing(datacenters, separator=',')]) From 29c23744ac480f50592b8f6f24b650445f01f072 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 11 Sep 2014 13:34:33 -0500 Subject: [PATCH 0124/2667] Adds image status to image detail command --- SoftLayer/CLI/modules/image.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/modules/image.py b/SoftLayer/CLI/modules/image.py index 653eeea1d..9ef6b329c 100644 --- a/SoftLayer/CLI/modules/image.py +++ b/SoftLayer/CLI/modules/image.py @@ -21,7 +21,7 @@ MASK = ('id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,' 'imageType') DETAIL_MASK = MASK + (',children[id,blockDevicesDiskSpaceTotal,datacenter],' - 'note,createDate') + 'note,createDate,status') PUBLIC_TYPE = formatting.FormattedItem('PUBLIC', 'Public') PRIVATE_TYPE = formatting.FormattedItem('PRIVATE', 'Private') @@ -106,6 +106,10 @@ def execute(self, args): table.add_row(['global_identifier', image.get('globalIdentifier', formatting.blank())]) table.add_row(['name', image['name'].strip()]) + table.add_row(['status', formatting.FormattedItem( + utils.lookup(image, 'status', 'keyname'), + utils.lookup(image, 'status', 'name'), + )]) table.add_row(['account', image.get('accountId', formatting.blank())]) table.add_row(['visibility', PUBLIC_TYPE if image['publicFlag'] else PRIVATE_TYPE]) From 9c5e4cfcf17f958a798573188074d64f37792190 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 11 Sep 2014 14:07:01 -0500 Subject: [PATCH 0125/2667] Sorts datacenter list in image detail by name --- SoftLayer/CLI/modules/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/modules/image.py b/SoftLayer/CLI/modules/image.py index 9ef6b329c..5075eed60 100644 --- a/SoftLayer/CLI/modules/image.py +++ b/SoftLayer/CLI/modules/image.py @@ -122,7 +122,7 @@ def execute(self, args): table.add_row(['note', image.get('note')]) table.add_row(['created', image.get('createDate')]) table.add_row(['disk_space', formatting.b_to_gb(disk_space)]) - table.add_row(['datacenters', formatting.listing(datacenters, + table.add_row(['datacenters', formatting.listing(sorted(datacenters), separator=',')]) return table From 81323523066caa66a0e6525d507ae8a75f5a32e3 Mon Sep 17 00:00:00 2001 From: Wolfgang Nagele Date: Fri, 5 Sep 2014 21:30:17 +1000 Subject: [PATCH 0126/2667] Support immediate cancellation of servers --- SoftLayer/CLI/modules/server.py | 5 ++++- SoftLayer/managers/hardware.py | 5 +++-- SoftLayer/tests/CLI/modules/server_tests.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index be390b386..e49891991 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -252,6 +252,8 @@ class CancelServer(environment.CLIRunnable): Cancel a dedicated server Options: + --immediate Cancels the server immediately (instead of on the billing + anniversary) --comment=COMMENT An optional comment to add to the cancellation ticket --reason=REASON An optional cancellation reason. See cancel-reasons for a list of available options @@ -271,9 +273,10 @@ def execute(self, args): comment = self.env.input("(Optional) Add a cancellation comment:") reason = args.get('--reason') + immediate = args.get('--immediate') if args['--really'] or formatting.no_going_back(hw_id): - mgr.cancel_hardware(hw_id, reason, comment) + mgr.cancel_hardware(hw_id, reason, comment, immediate) else: raise exceptions.CLIAbort('Aborted') diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 0caed70df..2f8addd40 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -33,7 +33,8 @@ def __init__(self, client, ordering_manager=None): else: self.ordering_manager = ordering_manager - def cancel_hardware(self, hardware_id, reason='unneeded', comment=''): + def cancel_hardware(self, hardware_id, reason='unneeded', comment='', + immediate=False): """ Cancels the specified dedicated server. :param int hardware_id: The ID of the hardware to be cancelled. @@ -48,7 +49,7 @@ def cancel_hardware(self, hardware_id, reason='unneeded', comment=''): mask='id,bareMetalInstanceFlag') if server.get('bareMetalInstanceFlag'): - return self.cancel_metal(hardware_id) + return self.cancel_metal(hardware_id, immediate) reasons = self.get_cancellation_reasons() cancel_reason = reasons['unneeded'] diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index 32671126b..29b6f14a5 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -329,7 +329,7 @@ def test_cancel_server(self, resolve_mock, cancel_mock, ngb_mock): args = {'--really': True, '--reason': 'Test'} runnable.execute(args) - cancel_mock.assert_called_with(hw_id, args['--reason'], None) + cancel_mock.assert_called_with(hw_id, args['--reason'], None, None) # Now check to make sure we properly call CLIAbort in the negative case env_mock = mock.Mock() From bce391da0d51c359c0a1739c82c3da91fa847caa Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 12 Sep 2014 17:52:53 -0500 Subject: [PATCH 0127/2667] cleaning up some code --- SoftLayer/CLI/modules/dns.py | 77 +++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 31 deletions(-) mode change 100755 => 100644 SoftLayer/CLI/modules/dns.py diff --git a/SoftLayer/CLI/modules/dns.py b/SoftLayer/CLI/modules/dns.py old mode 100755 new mode 100644 index 25ec0916b..96e43ee9c --- a/SoftLayer/CLI/modules/dns.py +++ b/SoftLayer/CLI/modules/dns.py @@ -83,57 +83,72 @@ def execute(self, args): class ImportZone(environment.CLIRunnable): """ -usage: sl dns import +usage: sl dns import [--dryRun] + +Creates a new zone based off a nicely BIND formatted file + +Arguments: + Path to the bind zone file you want to import +Options: + --dryRun don't actually do anything. This will show you what we were able to parse. -Creates a new zone based of a BIND formatted file """ action = 'import' def execute(self,args): - import pprint import re - import sys + dryRun = args.get('--dryRun') + manager = SoftLayer.DNSManager(self.client) - zone = '' - records = {} - pp = pprint.PrettyPrinter(indent=2) lines = [line.strip() for line in open(args[''])] zoneSearch = re.search('\$ORIGIN (?P.*)\.',lines[0]) zone = zoneSearch.group('zone') - - try: - zone_id = helpers.resolve_id(manager.resolve_ids, zone,name='zone') - except : - print "Unexpected error:", sys.exc_info()[0] - zone_id = None - pass - if (zone_id is None): - - print "CREATING ZONE: %s" % (zone) - manager.create_zone(zone) - zone_id = helpers.resolve_id(manager.resolve_ids, zone,name='zone') - - print "ZoneID: %s" % (zone_id) - - + if (dryRun): + print "Starting up a dry run..." + zone_id = 0 + else: + try: + zone_id = helpers.resolve_id(manager.resolve_ids, zone,name='zone') + except : + print "\033[92mCREATED ZONE: %s\033[0m" % (zone) + manager.create_zone(zone) + zone_id = helpers.resolve_id(manager.resolve_ids, zone,name='zone') for content in lines: - domainSearch = re.search('(?P\w+)\s+(?P\d+)\s+(?P\w+)\s+(?P\w+)\s+(?P.*)',content) + domainSearch = re.search('((?P(\w+(\.)?)*|\@)?\s+(?P\d+)?\s+(?P\w+)?)?\s+(?P\w+)\s+(?P.*)',content) if (domainSearch is not None): domainName = domainSearch.group('domain') + #The API requires we send a host, although bind allows a blank entry. @ is the same thing as blank + if (domainName is None): + domainName = "@" + domainttl = domainSearch.group('ttl') domainClass = domainSearch.group('class') domainType = domainSearch.group('type') domainRecord = domainSearch.group('record') - print "Domain: %s TTL: %s Class: %s Type: %s Record: %s" % (domainName,domainttl,domainClass,domainType,domainRecord) - manager.create_record(zone_id,domainName,domainType,domainRecord,domainttl) - continue - # print content - # zone = dns.zone.from_file(args['']) - print "IMPORT ZONE %s" % (args['']) - # pp.pprint(zone) + #This will skip the SOA record bit. And any domain that gets parsed oddly. + if (domainType.upper() == 'IN'): + print "SKIPPED: Host: %s TTL: %s Type: %s Record: %s" % (domainName,domainttl,domainType,domainRecord) + continue + + #the dns class doesn't support weighted MX records yet, so we chomp that part out. + if (domainType.upper() == "MX"): + recordSearch = re.search('(?P\d+)\s+(?P.*)',domainRecord) + domainRecord = recordSearch.group('record') + + try: + if (dryRun): + print "Parsed: Host: %s TTL: %s Type: %s Record: %s" % (domainName,domainttl,domainType,domainRecord) + else: + manager.create_record(zone_id,domainName,domainType,domainRecord,domainttl) + print "\033[92mCreated: Host: %s TTL: %s Type: %s Record: %s\033[0m" % (domainName,domainttl,domainType,domainRecord) + except Exception, e: + print "\033[91mFAILED: Host: %s Type: %s Record: %s" % (domainName,domainType,domainRecord.upper()) + print "\t", e ,"\033[0m" + + return "Finished" class ListZones(environment.CLIRunnable): From c0470894ff1b68ff521bd09c5bb7cf86f4d7291d Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 10 Sep 2014 11:42:39 -0500 Subject: [PATCH 0128/2667] Makes Authentication More Flexible --- SoftLayer/API.py | 20 ++++++++++++------- SoftLayer/auth.py | 32 ++++++++++++++---------------- SoftLayer/tests/api_tests.py | 6 +++--- SoftLayer/tests/auth_tests.py | 32 ++++++++++++++++++------------ SoftLayer/tests/transport_tests.py | 8 ++++++-- SoftLayer/transports.py | 13 ++++++++---- 6 files changed, 65 insertions(+), 46 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 61851a35a..f2d8bf75c 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -146,9 +146,6 @@ def call(self, service, method, *args, **kwargs): headers = kwargs.get('headers', {}) - if self.auth: - headers.update(self.auth.get_headers()) - if kwargs.get('id') is not None: headers[service + 'InitParameters'] = {'id': kwargs.get('id')} @@ -178,11 +175,18 @@ def call(self, service, method, *args, **kwargs): http_headers.update(kwargs.get('raw_headers')) uri = '/'.join([self.endpoint_url, service]) + options = { + 'headers': headers, + 'http_headers': http_headers, + 'timeout': self.timeout, + 'proxy': self.proxy, + } + + if self.auth: + options = self.auth.get_options(options) + return transports.make_xml_rpc_api_call(uri, method, args, - headers=headers, - http_headers=http_headers, - timeout=self.timeout, - proxy=self.proxy) + **options) __call__ = call @@ -326,6 +330,8 @@ def call(self, name, *args, **kwargs): :param int offset: (optional) offset results by this many :param boolean iter: (optional) if True, returns a generator with the results + :param bool verify: verify SSL cert + :param cert: client certificate path Usage: >>> import SoftLayer diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py index e430839a7..a0b1daf0b 100644 --- a/SoftLayer/auth.py +++ b/SoftLayer/auth.py @@ -10,8 +10,8 @@ class AuthenticationBase(object): """A base authentication class intended to be overridden.""" - def get_headers(self): - """Return a dictionary of headers to be inserted for authentication.""" + def get_options(self, options): + """Receives request options and returns request options.""" raise NotImplementedError @@ -26,15 +26,14 @@ def __init__(self, user_id, auth_token): self.user_id = user_id self.auth_token = auth_token - def get_headers(self): - """Returns token-based auth headers.""" - return { - 'authenticate': { - 'complexType': 'PortalLoginToken', - 'userId': self.user_id, - 'authToken': self.auth_token, - } + def get_options(self, options): + """Sets token-based auth headers.""" + options['headers']['authenticate'] = { + 'complexType': 'PortalLoginToken', + 'userId': self.user_id, + 'authToken': self.auth_token, } + return options def __repr__(self): return "" % (self.user_id, self.auth_token) @@ -50,14 +49,13 @@ def __init__(self, username, api_key): self.username = username self.api_key = api_key - def get_headers(self): - """Returns token-based auth headers.""" - return { - 'authenticate': { - 'username': self.username, - 'apiKey': self.api_key, - } + def get_options(self, options): + """Sets token-based auth headers.""" + options['headers']['authenticate'] = { + 'username': self.username, + 'apiKey': self.api_key, } + return options def __repr__(self): return "" % (self.username) diff --git a/SoftLayer/tests/api_tests.py b/SoftLayer/tests/api_tests.py index d9408cdc9..8260f4e62 100644 --- a/SoftLayer/tests/api_tests.py +++ b/SoftLayer/tests/api_tests.py @@ -17,9 +17,9 @@ def test_init(self): client = SoftLayer.Client(username='doesnotexist', api_key='issurelywrong', timeout=10) - auth_headers = {'authenticate': {'username': 'doesnotexist', - 'apiKey': 'issurelywrong'}} - self.assertEqual(client.auth.get_headers(), auth_headers) + self.assertIsInstance(client.auth, SoftLayer.BasicAuthentication) + self.assertEqual(client.auth.username, 'doesnotexist') + self.assertEqual(client.auth.api_key, 'issurelywrong') self.assertEqual(client.endpoint_url, SoftLayer.API_PUBLIC_ENDPOINT.rstrip('/')) self.assertEqual(client.timeout, 10) diff --git a/SoftLayer/tests/auth_tests.py b/SoftLayer/tests/auth_tests.py index 2a3af4b1f..3e1fe74ee 100644 --- a/SoftLayer/tests/auth_tests.py +++ b/SoftLayer/tests/auth_tests.py @@ -9,9 +9,9 @@ class TestAuthenticationBase(testing.TestCase): - def test_get_headers(self): + def test_get_options(self): auth_base = auth.AuthenticationBase() - self.assertRaises(NotImplementedError, auth_base.get_headers) + self.assertRaises(NotImplementedError, auth_base.get_options, {}) class TestBasicAuthentication(testing.TestCase): @@ -22,11 +22,14 @@ def test_attribs(self): self.assertEqual(self.auth.username, 'USERNAME') self.assertEqual(self.auth.api_key, 'APIKEY') - def test_get_headers(self): - self.assertEqual(self.auth.get_headers(), { - 'authenticate': { - 'username': 'USERNAME', - 'apiKey': 'APIKEY', + def test_get_options(self): + headers = {'headers': {}} + self.assertEqual(self.auth.get_options(headers), { + 'headers': { + 'authenticate': { + 'username': 'USERNAME', + 'apiKey': 'APIKEY', + } } }) @@ -44,12 +47,15 @@ def test_attribs(self): self.assertEqual(self.auth.user_id, 12345) self.assertEqual(self.auth.auth_token, 'TOKEN') - def test_get_headers(self): - self.assertEqual(self.auth.get_headers(), { - 'authenticate': { - 'complexType': 'PortalLoginToken', - 'userId': 12345, - 'authToken': 'TOKEN', + def test_get_options(self): + headers = {'headers': {}} + self.assertEqual(self.auth.get_options(headers), { + 'headers': { + 'authenticate': { + 'complexType': 'PortalLoginToken', + 'userId': 12345, + 'authToken': 'TOKEN', + } } }) diff --git a/SoftLayer/tests/transport_tests.py b/SoftLayer/tests/transport_tests.py index a0e82f558..e65ca6595 100644 --- a/SoftLayer/tests/transport_tests.py +++ b/SoftLayer/tests/transport_tests.py @@ -56,7 +56,9 @@ def test_call(self, request): headers=None, proxies=None, data=data, - timeout=None) + timeout=None, + cert=None, + verify=True) self.assertEqual(resp, []) def test_proxy_without_protocol(self): @@ -81,7 +83,9 @@ def test_valid_proxy(self, request): proxies={'https': 'http://localhost:3128', 'http': 'http://localhost:3128'}, data=mock.ANY, - timeout=None) + timeout=None, + cert=None, + verify=True) class TestRestAPICall(testing.TestCase): diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 44fc9812c..332ae9a98 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -23,8 +23,9 @@ def _proxies_dict(proxy): return {'http': proxy, 'https': proxy} -def make_xml_rpc_api_call(uri, method, args=None, headers=None, - http_headers=None, timeout=None, proxy=None): +def make_xml_rpc_api_call(url, method, args=None, headers=None, + http_headers=None, timeout=None, proxy=None, + verify=True, cert=None): """Makes a SoftLayer API call against the XML-RPC endpoint. :param string uri: endpoint URL @@ -32,6 +33,8 @@ def make_xml_rpc_api_call(uri, method, args=None, headers=None, :param dict headers: XML-RPC headers to use for the request :param dict http_headers: HTTP headers to use for the request :param int timeout: number of seconds to use as a timeout + :param bool verify: verify SSL cert + :param cert: client certificate path """ if args is None: args = tuple() @@ -43,14 +46,16 @@ def make_xml_rpc_api_call(uri, method, args=None, headers=None, methodname=method, allow_none=True) LOGGER.debug("=== REQUEST ===") - LOGGER.info('POST %s', uri) + LOGGER.info('POST %s', url) LOGGER.debug(http_headers) LOGGER.debug(payload) - response = requests.request('POST', uri, + response = requests.request('POST', url, data=payload, headers=http_headers, timeout=timeout, + verify=verify, + cert=cert, proxies=_proxies_dict(proxy)) LOGGER.debug("=== RESPONSE ===") LOGGER.debug(response.headers) From 373e1cb5d1864e2947bb684b831ed0043e4bc891 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 15 Sep 2014 14:20:21 -0500 Subject: [PATCH 0129/2667] added cli test for zone import --- .../tests/CLI/modules/artifacts/realtest.com | 21 +++++++++++++++++++ SoftLayer/tests/CLI/modules/dns_tests.py | 12 +++++++++++ 2 files changed, 33 insertions(+) create mode 100644 SoftLayer/tests/CLI/modules/artifacts/realtest.com diff --git a/SoftLayer/tests/CLI/modules/artifacts/realtest.com b/SoftLayer/tests/CLI/modules/artifacts/realtest.com new file mode 100644 index 000000000..0c5550d1d --- /dev/null +++ b/SoftLayer/tests/CLI/modules/artifacts/realtest.com @@ -0,0 +1,21 @@ +$ORIGIN realtest.com. +$TTL 86400 +@ IN SOA ns1.softlayer.com. support.softlayer.com. ( + 2014052300 ; Serial + 7200 ; Refresh + 600 ; Retry + 1728000 ; Expire + 43200) ; Minimum + +@ 86400 IN NS ns1.softlayer.com. +@ 86400 IN NS ns2.softlayer.com. + + IN MX 10 test.realtest.com. +testing 86400 IN A 127.0.0.1 +testing1 86400 IN A 12.12.0.1 +server2 IN A 1.0.3.4 +ftp IN CNAME server2 +dev.realtest.com IN TXT "This is just a test of the txt record" + IN AAAA 2001:db8:10::1 +spf IN TXT "v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a -all" + diff --git a/SoftLayer/tests/CLI/modules/dns_tests.py b/SoftLayer/tests/CLI/modules/dns_tests.py index 811556db2..b4986a0d5 100644 --- a/SoftLayer/tests/CLI/modules/dns_tests.py +++ b/SoftLayer/tests/CLI/modules/dns_tests.py @@ -119,3 +119,15 @@ def test_delete_record(self, no_going_back_mock): '': 'hostname', '--id': 1, '--really': False}) + + def test_import_zone(self): + import pprint + pp = pprint.PrettyPrinter(indent=4) + + command = dns.ImportZone(client=self.client) + output = command.execute({ + '' : 'realtest.com' + '--dryRun' : '--dryRun' + }) + pp.pprint(output) + self.assertEqual(['Finished'],output) \ No newline at end of file From e115be7233cfb56f87ea3b0a04f446f44b5b0067 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 18 Sep 2014 18:18:03 -0500 Subject: [PATCH 0130/2667] Adds backwards compat for old style auth objects --- SoftLayer/API.py | 10 ++++++++- SoftLayer/auth.py | 17 +++++++++++++-- SoftLayer/tests/auth_tests.py | 3 ++- SoftLayer/tests/deprecated_tests.py | 33 +++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 SoftLayer/tests/deprecated_tests.py diff --git a/SoftLayer/API.py b/SoftLayer/API.py index f2d8bf75c..c306e1ae2 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -6,6 +6,7 @@ :license: MIT, see LICENSE for more details. """ import time +import warnings from SoftLayer import auth as slauth from SoftLayer import config @@ -183,7 +184,14 @@ def call(self, service, method, *args, **kwargs): } if self.auth: - options = self.auth.get_options(options) + if getattr(self.auth, "get_headers", None): + warnings.warn("auth.get_headers() is deprecation and will be " + "removed in the next major version", + DeprecationWarning) + headers.update(self.auth.get_headers()) + + if getattr(self.auth, "get_options", None): + options = self.auth.get_options(options) return transports.make_xml_rpc_api_call(uri, method, args, **options) diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py index a0b1daf0b..931042e72 100644 --- a/SoftLayer/auth.py +++ b/SoftLayer/auth.py @@ -10,9 +10,22 @@ class AuthenticationBase(object): """A base authentication class intended to be overridden.""" + def get_options(self, options): - """Receives request options and returns request options.""" - raise NotImplementedError + """Receives request options and returns request options. + + :param options dict: dictionary of request options + + """ + return options + + def get_headers(self): + """Return a dictionary of headers to be inserted for authentication. + + .. deprecated:: 3.3.0 + Use :func:`get_options` instead. + """ + return {} class TokenAuthentication(AuthenticationBase): diff --git a/SoftLayer/tests/auth_tests.py b/SoftLayer/tests/auth_tests.py index 3e1fe74ee..45405c7e8 100644 --- a/SoftLayer/tests/auth_tests.py +++ b/SoftLayer/tests/auth_tests.py @@ -11,7 +11,8 @@ class TestAuthenticationBase(testing.TestCase): def test_get_options(self): auth_base = auth.AuthenticationBase() - self.assertRaises(NotImplementedError, auth_base.get_options, {}) + self.assertEqual(auth_base.get_options({}), {}) + self.assertEqual(auth_base.get_headers(), {}) class TestBasicAuthentication(testing.TestCase): diff --git a/SoftLayer/tests/deprecated_tests.py b/SoftLayer/tests/deprecated_tests.py new file mode 100644 index 000000000..096b1c28d --- /dev/null +++ b/SoftLayer/tests/deprecated_tests.py @@ -0,0 +1,33 @@ +""" + SoftLayer.tests.depecated_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import mock + +import SoftLayer +import SoftLayer.API +from SoftLayer import testing + + +class DeprecatedAuth(SoftLayer.AuthenticationBase): + """Auth that only implements get_headers().""" + + def get_headers(self): + return {'deprecated': 'header'} + + +class APIClient(testing.TestCase): + def set_up(self): + self.client = SoftLayer.Client(auth=DeprecatedAuth()) + + @mock.patch('SoftLayer.transports.make_xml_rpc_api_call') + def test_simple_call(self, make_xml_rpc_api_call): + self.client['SERVICE'].METHOD() + make_xml_rpc_api_call.assert_called_with( + mock.ANY, mock.ANY, mock.ANY, + headers={'deprecated': 'header'}, + proxy=mock.ANY, + timeout=mock.ANY, + http_headers=mock.ANY) From fc98a222070874871bf4889ec3f1687da6413fd8 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 19 Sep 2014 11:36:54 -0500 Subject: [PATCH 0131/2667] Adds better test/implementation for deprecated functionality --- SoftLayer/API.py | 10 +++++----- SoftLayer/tests/deprecated_tests.py | 24 +++++++++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index c306e1ae2..e1956c92a 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -184,14 +184,14 @@ def call(self, service, method, *args, **kwargs): } if self.auth: - if getattr(self.auth, "get_headers", None): - warnings.warn("auth.get_headers() is deprecation and will be " + extra_headers = self.auth.get_headers() + if extra_headers: + warnings.warn("auth.get_headers() is deprecated and will be " "removed in the next major version", DeprecationWarning) - headers.update(self.auth.get_headers()) + headers.update(extra_headers) - if getattr(self.auth, "get_options", None): - options = self.auth.get_options(options) + options = self.auth.get_options(options) return transports.make_xml_rpc_api_call(uri, method, args, **options) diff --git a/SoftLayer/tests/deprecated_tests.py b/SoftLayer/tests/deprecated_tests.py index 096b1c28d..207d8e0ed 100644 --- a/SoftLayer/tests/deprecated_tests.py +++ b/SoftLayer/tests/deprecated_tests.py @@ -4,6 +4,8 @@ :license: MIT, see LICENSE for more details. """ +import warnings + import mock import SoftLayer @@ -24,10 +26,18 @@ def set_up(self): @mock.patch('SoftLayer.transports.make_xml_rpc_api_call') def test_simple_call(self, make_xml_rpc_api_call): - self.client['SERVICE'].METHOD() - make_xml_rpc_api_call.assert_called_with( - mock.ANY, mock.ANY, mock.ANY, - headers={'deprecated': 'header'}, - proxy=mock.ANY, - timeout=mock.ANY, - http_headers=mock.ANY) + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + + self.client['SERVICE'].METHOD() + + make_xml_rpc_api_call.assert_called_with( + mock.ANY, mock.ANY, mock.ANY, + headers={'deprecated': 'header'}, + proxy=mock.ANY, + timeout=mock.ANY, + http_headers=mock.ANY) + self.assertEqual(len(w), 1) + self.assertEqual(w[0].category, DeprecationWarning) + self.assertIn("deprecated", str(w[0].message)) From 8deeb4c160c358c273448493eb9400467c518955 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 22 Sep 2014 17:05:12 -0500 Subject: [PATCH 0132/2667] Adds generic SoftLayer request object This allows both transports to (mostly) use this one object to define the specifications of an API call. --- SoftLayer/API.py | 64 ++--- SoftLayer/auth.py | 16 +- SoftLayer/managers/metadata.py | 39 ++- SoftLayer/tests/api_tests.py | 177 ++++--------- SoftLayer/tests/auth_tests.py | 37 ++- SoftLayer/tests/deprecated_tests.py | 16 +- SoftLayer/tests/functional_tests.py | 10 +- SoftLayer/tests/managers/metadata_tests.py | 151 ++++++----- SoftLayer/tests/transport_tests.py | 286 +++++++++++++++++---- SoftLayer/transports.py | 170 +++++++++--- setup.cfg | 2 +- 11 files changed, 575 insertions(+), 393 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index e1956c92a..cab5e6921 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -145,24 +145,6 @@ def call(self, service, method, *args, **kwargs): if not service.startswith(self._prefix): service = self._prefix + service - headers = kwargs.get('headers', {}) - - if kwargs.get('id') is not None: - headers[service + 'InitParameters'] = {'id': kwargs.get('id')} - - if kwargs.get('mask') is not None: - headers.update(self.__format_object_mask(kwargs.get('mask'), - service)) - - if kwargs.get('filter') is not None: - headers['%sObjectFilter' % service] = kwargs.get('filter') - - if kwargs.get('limit'): - headers['resultLimit'] = { - 'limit': kwargs.get('limit'), - 'offset': kwargs.get('offset', 0), - } - http_headers = { 'User-Agent': self.user_agent or consts.USER_AGENT, 'Content-Type': 'application/xml', @@ -175,13 +157,19 @@ def call(self, service, method, *args, **kwargs): if kwargs.get('raw_headers'): http_headers.update(kwargs.get('raw_headers')) - uri = '/'.join([self.endpoint_url, service]) - options = { - 'headers': headers, - 'http_headers': http_headers, - 'timeout': self.timeout, - 'proxy': self.proxy, - } + request = transports.Request() + request.endpoint = self.endpoint_url + request.service = service + request.method = method + request.args = args + request.transport_headers = http_headers + request.timeout = self. timeout + request.proxy = self.proxy + request.identifier = kwargs.get('id') + request.mask = kwargs.get('mask') + request.filter = kwargs.get('filter') + request.limit = kwargs.get('limit') + request.offset = kwargs.get('offset') if self.auth: extra_headers = self.auth.get_headers() @@ -189,12 +177,11 @@ def call(self, service, method, *args, **kwargs): warnings.warn("auth.get_headers() is deprecated and will be " "removed in the next major version", DeprecationWarning) - headers.update(extra_headers) + request.headers.update(extra_headers) - options = self.auth.get_options(options) + request = self.auth.get_request(request) - return transports.make_xml_rpc_api_call(uri, method, args, - **options) + return transports.make_xml_rpc_api_call(request) __call__ = call @@ -249,25 +236,6 @@ def iter_call(self, service, method, if len(results) < chunk: break - def __format_object_mask(self, objectmask, service): - """Format new and old style object masks into proper headers. - - :param objectmask: a string- or dict-based object mask - :param service: a SoftLayer API service name - - """ - if isinstance(objectmask, dict): - mheader = '%sObjectMask' % service - else: - mheader = self._prefix + 'ObjectMask' - - objectmask = objectmask.strip() - if (not objectmask.startswith('mask') - and not objectmask.startswith('[')): - objectmask = "mask[%s]" % objectmask - - return {mheader: {'mask': objectmask}} - def __repr__(self): return "" % (self.endpoint_url, self.auth) diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py index 931042e72..c2a1435a4 100644 --- a/SoftLayer/auth.py +++ b/SoftLayer/auth.py @@ -11,13 +11,13 @@ class AuthenticationBase(object): """A base authentication class intended to be overridden.""" - def get_options(self, options): + def get_request(self, request): """Receives request options and returns request options. :param options dict: dictionary of request options """ - return options + return request def get_headers(self): """Return a dictionary of headers to be inserted for authentication. @@ -39,14 +39,14 @@ def __init__(self, user_id, auth_token): self.user_id = user_id self.auth_token = auth_token - def get_options(self, options): + def get_request(self, request): """Sets token-based auth headers.""" - options['headers']['authenticate'] = { + request.headers['authenticate'] = { 'complexType': 'PortalLoginToken', 'userId': self.user_id, 'authToken': self.auth_token, } - return options + return request def __repr__(self): return "" % (self.user_id, self.auth_token) @@ -62,13 +62,13 @@ def __init__(self, username, api_key): self.username = username self.api_key = api_key - def get_options(self, options): + def get_request(self, request): """Sets token-based auth headers.""" - options['headers']['authenticate'] = { + request.headers['authenticate'] = { 'username': self.username, 'apiKey': self.api_key, } - return options + return request def __repr__(self): return "" % (self.username) diff --git a/SoftLayer/managers/metadata.py b/SoftLayer/managers/metadata.py index dda795812..6d6b47b2a 100644 --- a/SoftLayer/managers/metadata.py +++ b/SoftLayer/managers/metadata.py @@ -58,22 +58,6 @@ def __init__(self, client=None, timeout=5): self.timeout = timeout self.client = client - def make_request(self, path): - """ Make a request against the metadata service - - :param string path: path to the specific metadata resource - """ - url = '/'.join([self.url, 'SoftLayer_Resource_Metadata', path]) - try: - return transports.make_rest_api_call( - 'GET', url, - http_headers={'User-Agent': consts.USER_AGENT}, - timeout=self.timeout) - except exceptions.SoftLayerAPIError as ex: - if ex.faultCode == 404: - return None - raise ex - def get(self, name, param=None): """ Retreive a metadata attribute @@ -85,19 +69,30 @@ def get(self, name, param=None): raise exceptions.SoftLayerError('Unknown metadata attribute.') call_details = self.attribs[name] - extension = '.json' + extension = 'json' if self.attribs[name]['call'] == 'UserMetadata': - extension = '.txt' + extension = 'txt' if call_details.get('param_req'): if not param: raise exceptions.SoftLayerError( 'Parameter required to get this attribute.') - path = "%s/%s%s" % (self.attribs[name]['call'], param, extension) - else: - path = "%s%s" % (self.attribs[name]['call'], extension) - return self.make_request(path) + request = transports.Request() + request.endpoint = self.url + request.service = 'SoftLayer_Resource_Metadata' + request.method = self.attribs[name]['call'] + request.transport_headers = {'User-Agent': consts.USER_AGENT} + request.timeout = self.timeout + request.identifier = param + + try: + return transports.make_rest_api_call(request, + extension=extension) + except exceptions.SoftLayerAPIError as ex: + if ex.faultCode == 404: + return None + raise ex def _get_network(self, kind, router=True, vlans=True, vlan_ids=True): """ Wrapper for getting details about networks diff --git a/SoftLayer/tests/api_tests.py b/SoftLayer/tests/api_tests.py index 8260f4e62..c64c33d4e 100644 --- a/SoftLayer/tests/api_tests.py +++ b/SoftLayer/tests/api_tests.py @@ -8,9 +8,11 @@ import SoftLayer import SoftLayer.API -from SoftLayer import consts from SoftLayer import testing +TEST_AUTH_HEADERS = { + 'authenticate': {'apiKey': 'issurelywrong', 'username': 'doesnotexist'}} + class Inititialization(testing.TestCase): def test_init(self): @@ -79,109 +81,49 @@ def set_up(self): @mock.patch('SoftLayer.transports.make_xml_rpc_api_call') def test_simple_call(self, make_xml_rpc_api_call): - self.client['SERVICE'].METHOD() - make_xml_rpc_api_call.assert_called_with( - 'ENDPOINT/SoftLayer_SERVICE', 'METHOD', (), - headers={ - 'authenticate': { - 'username': 'doesnotexist', 'apiKey': 'issurelywrong'}}, - proxy=None, - timeout=None, - http_headers={ - 'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT, - 'Accept': '*/*', - 'Accept-Encoding': 'gzip, deflate, compress', - }) + make_xml_rpc_api_call.return_value = {"test": "result"} + resp = self.client['SERVICE'].METHOD() + + self.assertEqual(resp, {"test": "result"}) + + (request,), kwargs = make_xml_rpc_api_call.call_args + self.assertEqual(request.endpoint, self.client.endpoint_url) + self.assertEqual(request.service, 'SoftLayer_SERVICE') + self.assertEqual(request.method, 'METHOD') + self.assertEqual(request.mask, None) + self.assertEqual(request.filter, None) + self.assertEqual(request.identifier, None) + self.assertEqual(request.args, tuple()) + self.assertEqual(request.limit, None) + self.assertEqual(request.offset, None) + self.assertEqual(request.headers, TEST_AUTH_HEADERS) @mock.patch('SoftLayer.transports.make_xml_rpc_api_call') def test_complex(self, make_xml_rpc_api_call): - self.client['SERVICE'].METHOD( + make_xml_rpc_api_call.return_value = {"test": "result"} + resp = self.client['SERVICE'].METHOD( 1234, id=5678, mask={'object': {'attribute': ''}}, raw_headers={'RAW': 'HEADER'}, filter={ - 'TYPE': {'obj': {'attribute': {'operation': '^= prefix'}}}}, + 'TYPE': {'attribute': {'operation': '^= prefix'}}}, limit=9, offset=10) - make_xml_rpc_api_call.assert_called_with( - 'ENDPOINT/SoftLayer_SERVICE', 'METHOD', (1234, ), - headers={ - 'SoftLayer_SERVICEObjectMask': { - 'mask': {'object': {'attribute': ''}}}, - 'SoftLayer_SERVICEObjectFilter': { - 'TYPE': { - 'obj': {'attribute': {'operation': '^= prefix'}}}}, - 'authenticate': { - 'username': 'doesnotexist', 'apiKey': 'issurelywrong'}, - 'SoftLayer_SERVICEInitParameters': {'id': 5678}, - 'resultLimit': {'limit': 9, 'offset': 10}}, - proxy=None, - timeout=None, - http_headers={ - 'RAW': 'HEADER', - 'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT, - 'Accept': '*/*', - 'Accept-Encoding': 'gzip, deflate, compress', - }) - - @mock.patch('SoftLayer.transports.make_xml_rpc_api_call') - def test_mask_call_v2(self, make_xml_rpc_api_call): - self.client['SERVICE'].METHOD( - mask="mask[something[nested]]") - make_xml_rpc_api_call.assert_called_with( - 'ENDPOINT/SoftLayer_SERVICE', 'METHOD', (), - headers={ - 'authenticate': { - 'username': 'doesnotexist', 'apiKey': 'issurelywrong'}, - 'SoftLayer_ObjectMask': {'mask': 'mask[something[nested]]'}}, - proxy=None, - timeout=None, - http_headers={ - 'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT, - 'Accept': '*/*', - 'Accept-Encoding': 'gzip, deflate, compress', - }) - - @mock.patch('SoftLayer.transports.make_xml_rpc_api_call') - def test_mask_call_v2_dot(self, make_xml_rpc_api_call): - self.client['SERVICE'].METHOD( - mask="mask.something.nested") - make_xml_rpc_api_call.assert_called_with( - 'ENDPOINT/SoftLayer_SERVICE', 'METHOD', (), - headers={ - 'authenticate': { - 'username': 'doesnotexist', 'apiKey': 'issurelywrong'}, - 'SoftLayer_ObjectMask': {'mask': 'mask.something.nested'}}, - proxy=None, - timeout=None, - http_headers={ - 'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT, - 'Accept': '*/*', - 'Accept-Encoding': 'gzip, deflate, compress', - }) - - @mock.patch('SoftLayer.transports.make_xml_rpc_api_call') - def test_mask_call_no_mask_prefix(self, make_xml_rpc_api_call): - self.client['SERVICE'].METHOD(mask="something.nested") - make_xml_rpc_api_call.assert_called_with( - 'ENDPOINT/SoftLayer_SERVICE', 'METHOD', (), - headers={ - 'authenticate': { - 'username': 'doesnotexist', 'apiKey': 'issurelywrong'}, - 'SoftLayer_ObjectMask': {'mask': 'mask[something.nested]'}}, - proxy=None, - timeout=None, - http_headers={ - 'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT, - 'Accept': '*/*', - 'Accept-Encoding': 'gzip, deflate, compress', - }) + self.assertEqual(resp, {"test": "result"}) + + (request,), kwargs = make_xml_rpc_api_call.call_args + self.assertEqual(request.endpoint, self.client.endpoint_url) + self.assertEqual(request.service, 'SoftLayer_SERVICE') + self.assertEqual(request.method, 'METHOD') + self.assertEqual(request.mask, {'object': {'attribute': ''}}) + self.assertEqual(request.filter, + {'TYPE': {'attribute': {'operation': '^= prefix'}}}) + self.assertEqual(request.identifier, 5678) + self.assertEqual(request.args, (1234,)) + self.assertEqual(request.limit, 9) + self.assertEqual(request.offset, 10) + self.assertEqual(request.headers, TEST_AUTH_HEADERS) @mock.patch('SoftLayer.API.Client.iter_call') def test_iterate(self, _iter_call): @@ -250,8 +192,8 @@ def test_iter_call(self, _call): # Chunk size of 0 is invalid self.assertRaises( AttributeError, - lambda: list(self.client.iter_call( - 'SERVICE', 'METHOD', iter=True, chunk=0))) + lambda: list(self.client.iter_call('SERVICE', 'METHOD', + iter=True, chunk=0))) def test_call_invalid_arguments(self): self.assertRaises( @@ -261,30 +203,19 @@ def test_call_invalid_arguments(self): @mock.patch('SoftLayer.transports.make_xml_rpc_api_call') def test_call_compression_disabled(self, make_xml_rpc_api_call): self.client['SERVICE'].METHOD(compress=False) - make_xml_rpc_api_call.assert_called_with( - 'ENDPOINT/SoftLayer_SERVICE', 'METHOD', (), - headers=mock.ANY, - proxy=None, - timeout=None, - http_headers={ - 'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT, - }) + + (request,), kwargs = make_xml_rpc_api_call.call_args + self.assertNotIn('Accept-Encoding', request.transport_headers) + self.assertNotIn('Accept', request.transport_headers) @mock.patch('SoftLayer.transports.make_xml_rpc_api_call') def test_call_compression_enabled(self, make_xml_rpc_api_call): self.client['SERVICE'].METHOD(compress=True) - make_xml_rpc_api_call.assert_called_with( - 'ENDPOINT/SoftLayer_SERVICE', 'METHOD', (), - headers=mock.ANY, - proxy=None, - timeout=None, - http_headers={ - 'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT, - 'Accept': '*/*', - 'Accept-Encoding': 'gzip, deflate, compress', - }) + + (request,), kwargs = make_xml_rpc_api_call.call_args + headers = request.transport_headers + self.assertEqual(headers['Accept-Encoding'], 'gzip, deflate, compress') + self.assertEqual(headers['Accept'], '*/*') @mock.patch('SoftLayer.transports.make_xml_rpc_api_call') def test_call_compression_override(self, make_xml_rpc_api_call): @@ -292,16 +223,10 @@ def test_call_compression_override(self, make_xml_rpc_api_call): self.client['SERVICE'].METHOD( compress=False, raw_headers={'Accept-Encoding': 'gzip'}) - make_xml_rpc_api_call.assert_called_with( - 'ENDPOINT/SoftLayer_SERVICE', 'METHOD', (), - headers=mock.ANY, - proxy=None, - timeout=None, - http_headers={ - 'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT, - 'Accept-Encoding': 'gzip', - }) + + (request,), kwargs = make_xml_rpc_api_call.call_args + self.assertEqual(request.transport_headers['Accept-Encoding'], 'gzip') + self.assertNotIn('Accept', request.transport_headers) class APITimedClient(testing.TestCase): diff --git a/SoftLayer/tests/auth_tests.py b/SoftLayer/tests/auth_tests.py index 45405c7e8..240af3a99 100644 --- a/SoftLayer/tests/auth_tests.py +++ b/SoftLayer/tests/auth_tests.py @@ -6,12 +6,13 @@ """ from SoftLayer import auth from SoftLayer import testing +from SoftLayer import transports class TestAuthenticationBase(testing.TestCase): - def test_get_options(self): + def test_get_request(self): auth_base = auth.AuthenticationBase() - self.assertEqual(auth_base.get_options({}), {}) + self.assertEqual(auth_base.get_request({}), {}) self.assertEqual(auth_base.get_headers(), {}) @@ -23,14 +24,13 @@ def test_attribs(self): self.assertEqual(self.auth.username, 'USERNAME') self.assertEqual(self.auth.api_key, 'APIKEY') - def test_get_options(self): - headers = {'headers': {}} - self.assertEqual(self.auth.get_options(headers), { - 'headers': { - 'authenticate': { - 'username': 'USERNAME', - 'apiKey': 'APIKEY', - } + def test_get_request(self): + req = transports.Request() + authed_req = self.auth.get_request(req) + self.assertEqual(authed_req.headers, { + 'authenticate': { + 'username': 'USERNAME', + 'apiKey': 'APIKEY', } }) @@ -48,15 +48,14 @@ def test_attribs(self): self.assertEqual(self.auth.user_id, 12345) self.assertEqual(self.auth.auth_token, 'TOKEN') - def test_get_options(self): - headers = {'headers': {}} - self.assertEqual(self.auth.get_options(headers), { - 'headers': { - 'authenticate': { - 'complexType': 'PortalLoginToken', - 'userId': 12345, - 'authToken': 'TOKEN', - } + def test_get_request(self): + req = transports.Request() + authed_req = self.auth.get_request(req) + self.assertEqual(authed_req.headers, { + 'authenticate': { + 'complexType': 'PortalLoginToken', + 'userId': 12345, + 'authToken': 'TOKEN', } }) diff --git a/SoftLayer/tests/deprecated_tests.py b/SoftLayer/tests/deprecated_tests.py index 207d8e0ed..f4918c91f 100644 --- a/SoftLayer/tests/deprecated_tests.py +++ b/SoftLayer/tests/deprecated_tests.py @@ -30,14 +30,14 @@ def test_simple_call(self, make_xml_rpc_api_call): # Cause all warnings to always be triggered. warnings.simplefilter("always") - self.client['SERVICE'].METHOD() - - make_xml_rpc_api_call.assert_called_with( - mock.ANY, mock.ANY, mock.ANY, - headers={'deprecated': 'header'}, - proxy=mock.ANY, - timeout=mock.ANY, - http_headers=mock.ANY) + make_xml_rpc_api_call.return_value = {"test": "result"} + resp = self.client['SERVICE'].METHOD() + + self.assertEqual(resp, {"test": "result"}) + + (request,), kwargs = make_xml_rpc_api_call.call_args + self.assertEqual(request.headers, {'deprecated': 'header'}) + self.assertEqual(len(w), 1) self.assertEqual(w[0].category, DeprecationWarning) self.assertIn("deprecated", str(w[0].message)) diff --git a/SoftLayer/tests/functional_tests.py b/SoftLayer/tests/functional_tests.py index d121f9958..18408254c 100644 --- a/SoftLayer/tests/functional_tests.py +++ b/SoftLayer/tests/functional_tests.py @@ -8,6 +8,7 @@ import SoftLayer from SoftLayer import testing +from SoftLayer import transports def get_creds(): @@ -34,9 +35,14 @@ def test_failed_auth(self): def test_no_hostname(self): try: + request = transports.Request() + request.endpoint = 'http://notvalidsoftlayer.com' + request.service = 'SoftLayer_Account' + request.method = 'getObject' + request.id = 1234 + # This test will fail if 'notvalidsoftlayer.com' becomes a thing - SoftLayer.transports.make_xml_rpc_api_call( - 'http://notvalidsoftlayer.com', 'getObject') + transports.make_xml_rpc_api_call(request) except SoftLayer.SoftLayerAPIError as ex: self.assertIn('not known', str(ex)) self.assertIn('not known', ex.faultString) diff --git a/SoftLayer/tests/managers/metadata_tests.py b/SoftLayer/tests/managers/metadata_tests.py index 363982c26..93681c052 100644 --- a/SoftLayer/tests/managers/metadata_tests.py +++ b/SoftLayer/tests/managers/metadata_tests.py @@ -15,49 +15,100 @@ class MetadataTests(testing.TestCase): def set_up(self): self.metadata = SoftLayer.MetadataManager() - self.make_request = mock.MagicMock() - self.metadata.make_request = self.make_request - - def test_no_param(self): - self.make_request.return_value = 'dal01' - r = self.metadata.get('datacenter') - self.make_request.assert_called_with("Datacenter.json") - self.assertEqual('dal01', r) - - def test_w_param(self): - self.make_request.return_value = [123] - r = self.metadata.get('vlans', '1:2:3:4:5') - self.make_request.assert_called_with("Vlans/1:2:3:4:5.json") - self.assertEqual([123], r) - - def test_user_data(self): - self.make_request.return_value = 'user_data' - r = self.metadata.get('user_data') - self.make_request.assert_called_with("UserMetadata.txt") - self.assertEqual('user_data', r) - - def test_return_none(self): - self.make_request.return_value = None - r = self.metadata.get('datacenter') - self.make_request.assert_called_with("Datacenter.json") - self.assertEqual(None, r) - - def test_w_param_error(self): + + @mock.patch('SoftLayer.transports.make_rest_api_call') + def test_get(self, make_request): + make_request.return_value = 'dal01' + resp = self.metadata.get('datacenter') + + self.assertEqual('dal01', resp) + + (request, ), kwargs = make_request.call_args + + self.assertEqual(request.endpoint, self.metadata.url) + self.assertEqual(request.service, 'SoftLayer_Resource_Metadata') + self.assertEqual(request.method, 'Datacenter') + self.assertEqual(request.transport_headers, + {'User-Agent': consts.USER_AGENT}) + self.assertEqual(request.timeout, self.metadata.timeout) + self.assertEqual(request.identifier, None) + self.assertEqual(kwargs['extension'], 'json') + + @mock.patch('SoftLayer.transports.make_rest_api_call') + def test_no_param(self, make_request): + make_request.return_value = 'dal01' + + resp = self.metadata.get('datacenter') + + self.assertEqual('dal01', resp) + (request, ), kwargs = make_request.call_args + self.assertEqual(request.method, 'Datacenter') + self.assertEqual(request.identifier, None) + + @mock.patch('SoftLayer.transports.make_rest_api_call') + def test_w_param(self, make_request): + make_request.return_value = [123] + resp = self.metadata.get('vlans', '1:2:3:4:5') + + self.assertEqual([123], resp) + (request, ), kwargs = make_request.call_args + self.assertEqual(request.method, 'Vlans') + self.assertEqual(request.identifier, '1:2:3:4:5') + + @mock.patch('SoftLayer.transports.make_rest_api_call') + def test_user_data(self, make_request): + make_request.return_value = 'user_data' + resp = self.metadata.get('user_data') + + self.assertEqual('user_data', resp) + (request, ), kwargs = make_request.call_args + self.assertEqual(request.method, 'UserMetadata') + self.assertEqual(request.identifier, None) + self.assertEqual(kwargs['extension'], 'txt') + + @mock.patch('SoftLayer.transports.make_rest_api_call') + def test_return_none(self, make_request): + make_request.return_value = None + resp = self.metadata.get('datacenter') + + self.assertEqual(None, resp) + + @mock.patch('SoftLayer.transports.make_rest_api_call') + def test_404(self, make_request): + make_request.side_effect = SoftLayer.SoftLayerAPIError(404, + 'Not Found') + resp = self.metadata.get('user_data') + + self.assertEqual(None, resp) + + @mock.patch('SoftLayer.transports.make_rest_api_call') + def test_error(self, make_request): + exception = SoftLayer.SoftLayerAPIError(500, 'Error') + make_request.side_effect = exception + + self.assertRaises(SoftLayer.SoftLayerAPIError, + self.metadata.get, 'user_data') + + @mock.patch('SoftLayer.transports.make_rest_api_call') + def test_w_param_error(self, make_request): self.assertRaises(SoftLayer.SoftLayerError, self.metadata.get, 'vlans') - def test_not_exists(self): + @mock.patch('SoftLayer.transports.make_rest_api_call') + def test_not_exists(self, make_request): self.assertRaises(SoftLayer.SoftLayerError, self.metadata.get, 'something') - def test_networks_not_exist(self): - self.make_request.return_value = [] + @mock.patch('SoftLayer.transports.make_rest_api_call') + def test_networks_not_exist(self, make_request): + make_request.return_value = [] r = self.metadata.public_network() self.assertEqual({'mac_addresses': []}, r) - def test_networks(self): + @mock.patch('SoftLayer.transports.make_rest_api_call') + def test_networks(self, make_request): resp = ['list', 'of', 'stuff'] - self.make_request.return_value = resp + make_request.return_value = resp r = self.metadata.public_network() self.assertEqual({ 'vlan_ids': resp, @@ -73,37 +124,3 @@ def test_networks(self): 'vlans': resp, 'mac_addresses': resp }, r) - - -class MetadataTestsMakeRequest(testing.TestCase): - - def set_up(self): - self.metadata = SoftLayer.MetadataManager() - self.url = '/'.join([ - consts.API_PRIVATE_ENDPOINT_REST.rstrip('/'), - 'SoftLayer_Resource_Metadata', - 'something.json']) - - @mock.patch('SoftLayer.transports.make_rest_api_call') - def test_basic(self, make_api_call): - r = self.metadata.make_request('something.json') - make_api_call.assert_called_with( - 'GET', self.url, - timeout=5, - http_headers={'User-Agent': consts.USER_AGENT}) - self.assertEqual(make_api_call(), r) - - @mock.patch('SoftLayer.transports.make_rest_api_call') - def test_raise_error(self, make_api_call): - make_api_call.side_effect = SoftLayer.SoftLayerAPIError( - 'faultCode', 'faultString') - self.assertRaises( - SoftLayer.SoftLayerAPIError, - self.metadata.make_request, 'something.json') - - @mock.patch('SoftLayer.transports.make_rest_api_call') - def test_raise_404_error(self, make_api_call): - make_api_call.side_effect = SoftLayer.SoftLayerAPIError(404, - 'faultString') - r = self.metadata.make_request('something.json') - self.assertEqual(r, None) diff --git a/SoftLayer/tests/transport_tests.py b/SoftLayer/tests/transport_tests.py index e65ca6595..8d02b0236 100644 --- a/SoftLayer/tests/transport_tests.py +++ b/SoftLayer/tests/transport_tests.py @@ -45,14 +45,15 @@ def test_call(self, request): ''' - resp = transports.make_xml_rpc_api_call( - 'http://something.com/path/to/resource', 'getObject') - args = request.call_args - self.assertIsNotNone(args) - args, kwargs = args + + req = transports.Request() + req.endpoint = 'http://something.com' + req.service = 'SoftLayer_Service' + req.method = 'getObject' + resp = transports.make_xml_rpc_api_call(req) request.assert_called_with('POST', - 'http://something.com/path/to/resource', + 'http://something.com/SoftLayer_Service', headers=None, proxies=None, data=data, @@ -62,20 +63,26 @@ def test_call(self, request): self.assertEqual(resp, []) def test_proxy_without_protocol(self): - self.assertRaises( - SoftLayer.TransportError, # NOQA - transports.make_xml_rpc_api_call, - 'http://something.com/path/to/resource', - 'getObject', - 'localhost:3128') + req = transports.Request() + req.endpoint = 'http://something.com' + req.service = 'SoftLayer_Service' + req.method = 'Resource' + req.proxy = 'localhost:3128' + + self.assertRaises(SoftLayer.TransportError, + transports.make_xml_rpc_api_call, req) @mock.patch('requests.request') def test_valid_proxy(self, request): request.return_value = self.response - transports.make_xml_rpc_api_call( - 'http://something.com/path/to/resource', - 'getObject', - proxy='http://localhost:3128') + + req = transports.Request() + req.endpoint = 'http://something.com' + req.service = 'SoftLayer_Service' + req.method = 'Resource' + req.proxy = 'http://localhost:3128' + transports.make_xml_rpc_api_call(req) + request.assert_called_with( 'POST', mock.ANY, @@ -87,17 +94,163 @@ def test_valid_proxy(self, request): cert=None, verify=True) + @mock.patch('requests.request') + def test_identifier(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.identifier = 1234 + transports.make_xml_rpc_api_call(req) + + args, kwargs = request.call_args + self.assertIn( + """ +id +1234 +""", kwargs['data']) + + @mock.patch('requests.request') + def test_filter(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.filter = {'TYPE': {'attribute': {'operation': '^= prefix'}}} + transports.make_xml_rpc_api_call(req) + + args, kwargs = request.call_args + self.assertIn( + """ +operation +^= prefix +""", kwargs['data']) + + @mock.patch('requests.request') + def test_limit_offset(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.limit = 10 + transports.make_xml_rpc_api_call(req) + + args, kwargs = request.call_args + self.assertIn(""" +resultLimit + +""", kwargs['data']) + self.assertIn("""limit +10 +""", kwargs['data']) + + @mock.patch('requests.request') + def test_old_mask(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = {"something": "nested"} + transports.make_xml_rpc_api_call(req) + + args, kwargs = request.call_args + self.assertIn(""" +mask + + +something +nested + + +""", kwargs['data']) + + @mock.patch('requests.request') + def test_mask_call_no_mask_prefix(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = "something.nested" + transports.make_xml_rpc_api_call(req) + + args, kwargs = request.call_args + self.assertIn( + "mask[something.nested]", + kwargs['data']) + + @mock.patch('requests.request') + def test_mask_call_v2(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = "mask[something[nested]]" + transports.make_xml_rpc_api_call(req) + + args, kwargs = request.call_args + self.assertIn( + "mask[something[nested]]", + kwargs['data']) + + @mock.patch('requests.request') + def test_mask_call_v2_dot(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = "mask.something.nested" + transports.make_xml_rpc_api_call(req) + + args, kwargs = request.call_args + self.assertIn("mask.something.nested", + kwargs['data']) + + @mock.patch('requests.request') + def test_request_exception(self, request): + # Test Text Error + e = requests.HTTPError('error') + e.response = mock.MagicMock() + e.response.status_code = 404 + e.response.content = 'Error Code' + request().raise_for_status.side_effect = e + + req = transports.Request() + req.endpoint = 'http://something.com' + req.service = 'SoftLayer_Service' + req.method = 'getObject' + + self.assertRaises(SoftLayer.TransportError, + transports.make_xml_rpc_api_call, req) + class TestRestAPICall(testing.TestCase): - @mock.patch('SoftLayer.transports.requests.request') + @mock.patch('requests.request') def test_json(self, request): request().content = '{}' - resp = transports.make_rest_api_call( - 'GET', 'http://something.com/path/to/resource.json') + req = transports.Request() + req.endpoint = 'http://something.com' + req.service = 'SoftLayer_Service' + req.method = 'Resource' + + resp = transports.make_rest_api_call(req, extension='json') self.assertEqual(resp, {}) request.assert_called_with( - 'GET', 'http://something.com/path/to/resource.json', + 'GET', 'http://something.com/SoftLayer_Service/Resource.json', headers=None, proxies=None, timeout=None) @@ -114,39 +267,71 @@ def test_json(self, request): self.assertRaises( SoftLayer.SoftLayerAPIError, - transports.make_rest_api_call, - 'GET', - 'http://something.com/path/to/resource.json') + transports.make_rest_api_call, req, extension='json') def test_proxy_without_protocol(self): - self.assertRaises( - SoftLayer.TransportError, - transports.make_rest_api_call, - 'GET' - 'http://something.com/path/to/resource.txt', - 'localhost:3128') + req = transports.Request() + req.endpoint = 'http://something.com' + req.service = 'SoftLayer_Service' + req.method = 'Resource' + req.proxy = 'localhost:3128' + + self.assertRaises(SoftLayer.TransportError, + transports.make_rest_api_call, req) - @mock.patch('SoftLayer.transports.requests.request') + @mock.patch('requests.request') def test_valid_proxy(self, request): - transports.make_rest_api_call( - 'GET', - 'http://something.com/path/to/resource.txt', - proxy='http://localhost:3128') + request().content = '{}' + + req = transports.Request() + req.endpoint = 'http://something.com' + req.service = 'SoftLayer_Service' + req.method = 'Resource' + req.proxy = 'http://localhost:3128' + + transports.make_rest_api_call(req) request.assert_called_with( - 'GET', 'http://something.com/path/to/resource.txt', - headers=mock.ANY, + 'GET', 'http://something.com/SoftLayer_Service/Resource.json', proxies={'https': 'http://localhost:3128', 'http': 'http://localhost:3128'}, + timeout=mock.ANY, + headers=mock.ANY) + + @mock.patch('requests.request') + def test_with_id(self, request): + request().content = '{}' + + req = transports.Request() + req.endpoint = 'http://something.com' + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.identifier = 2 + + resp = transports.make_rest_api_call(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'GET', + 'http://something.com/SoftLayer_Service/getObject/2.json', + headers=None, + proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.request') + @mock.patch('requests.request') def test_text(self, request): + request().content = 'content' request().text = 'content' - resp = transports.make_rest_api_call( - 'GET', 'http://something.com/path/to/resource.txt') + + req = transports.Request() + req.endpoint = 'http://something.com' + req.service = 'SoftLayer_Service' + req.method = 'Resource' + + resp = transports.make_rest_api_call(req, extension='txt') self.assertEqual(resp, 'content') request.assert_called_with( - 'GET', 'http://something.com/path/to/resource.txt', + 'GET', + 'http://something.com/SoftLayer_Service/Resource.txt', headers=None, proxies=None, timeout=None) @@ -158,13 +343,10 @@ def test_text(self, request): e.response.content = 'Error Code' request().raise_for_status.side_effect = e - self.assertRaises( - SoftLayer.SoftLayerAPIError, - transports.make_rest_api_call, - 'GET', - 'http://something.com/path/to/resource.txt') + self.assertRaises(SoftLayer.SoftLayerAPIError, + transports.make_rest_api_call, req, extension='txt') - @mock.patch('SoftLayer.transports.requests.request') + @mock.patch('requests.request') def test_unknown_error(self, request): e = requests.RequestException('error') e.response = mock.MagicMock() @@ -172,8 +354,10 @@ def test_unknown_error(self, request): e.response.content = 'Error Code' request().raise_for_status.side_effect = e - self.assertRaises( - SoftLayer.TransportError, - transports.make_rest_api_call, - 'GET', - 'http://something.com/path/to/resource.txt') + req = transports.Request() + req.endpoint = 'http://something.com' + req.service = 'SoftLayer_Service' + req.method = 'getObject' + + self.assertRaises(SoftLayer.TransportError, + transports.make_rest_api_call, req) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 332ae9a98..76a0ea6f2 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -14,49 +14,104 @@ import requests LOGGER = logging.getLogger(__name__) +# transports.Request does have a lot of instance attributes. :( +# pylint: disable=too-many-instance-attributes -def _proxies_dict(proxy): - """Makes a dict appropriate to pass to requests.""" - if not proxy: - return None - return {'http': proxy, 'https': proxy} +class Request(object): + """Transport request object.""" + + def __init__(self): + #: The SoftLayer endpoint address. + self.endpoint = None + + #: Service name. + self.service = None + + #: Method name. + self.method = None + + #: RPC Arguments. + self.args = [] + + #: Transport headers. + self.headers = {} + + #: Transport headers. + self.transport_headers = None + #: Integer timeout. + self.timeout = None -def make_xml_rpc_api_call(url, method, args=None, headers=None, - http_headers=None, timeout=None, proxy=None, - verify=True, cert=None): + #: URL to proxy to. + self.proxy = None + + #: Verify HTTPS Certificate. + self.verify = True + + #: Client certificate path. + self.cert = None + + #: InitParameter/identifier of an object. + self.identifier = None + + #: SoftLayer mask (dict or string). + self.mask = None + + #: SoftLayer Filter (dict). + self.filter = None + + #: Integer result limit. + self.limit = None + + #: Integer result offset. + self.offset = None + + +def make_xml_rpc_api_call(request): """Makes a SoftLayer API call against the XML-RPC endpoint. - :param string uri: endpoint URL - :param string method: method to call E.G.: 'getObject' - :param dict headers: XML-RPC headers to use for the request - :param dict http_headers: HTTP headers to use for the request - :param int timeout: number of seconds to use as a timeout - :param bool verify: verify SSL cert - :param cert: client certificate path + :param request request: Request object """ - if args is None: - args = tuple() try: - largs = list(args) - largs.insert(0, {'headers': headers}) + largs = list(request.args) + + headers = request.headers + + if request.identifier is not None: + header_name = request.service + 'InitParameters' + headers[header_name] = {'id': request.identifier} + if request.mask is not None: + headers.update(__format_object_mask(request.mask, request.service)) + + if request.filter is not None: + headers['%sObjectFilter' % request.service] = request.filter + + if request.limit: + headers['resultLimit'] = { + 'limit': request.limit, + 'offset': request.offset or 0, + } + + largs.insert(0, {'headers': headers or None}) + + url = '/'.join([request.endpoint, request.service]) payload = utils.xmlrpc_client.dumps(tuple(largs), - methodname=method, + methodname=request.method, allow_none=True) LOGGER.debug("=== REQUEST ===") LOGGER.info('POST %s', url) - LOGGER.debug(http_headers) + LOGGER.debug(request.transport_headers) LOGGER.debug(payload) response = requests.request('POST', url, data=payload, - headers=http_headers, - timeout=timeout, - verify=verify, - cert=cert, - proxies=_proxies_dict(proxy)) + headers=request.transport_headers, + timeout=request.timeout, + verify=request.verify, + cert=request.cert, + proxies=_proxies_dict(request.proxy)) LOGGER.debug("=== RESPONSE ===") LOGGER.debug(response.headers) LOGGER.debug(response.content) @@ -86,38 +141,71 @@ def make_xml_rpc_api_call(url, method, args=None, headers=None, raise exceptions.TransportError(0, str(ex)) -def make_rest_api_call(method, url, - http_headers=None, timeout=None, proxy=None): +def make_rest_api_call(request, extension='json'): """Makes a SoftLayer API call against the REST endpoint. - :param string method: HTTP method: GET, POST, PUT, DELETE - :param string url: endpoint URL - :param dict http_headers: HTTP headers to use for the request - :param int timeout: number of seconds to use as a timeout + This currently only works with GET requests + + :param request request: Request object """ + url_parts = [request.endpoint, + request.service, + request.method] + if request.identifier is not None: + url_parts.append(str(request.identifier)) + + url = '%s.%s' % ('/'.join(url_parts), extension) + LOGGER.debug("=== REQUEST ===") - LOGGER.info('%s %s', method, url) - LOGGER.debug(http_headers) + LOGGER.info('%s %s', request.method, url) + LOGGER.debug(request.transport_headers) try: - resp = requests.request(method, url, - headers=http_headers, - timeout=timeout, - proxies=_proxies_dict(proxy)) + resp = requests.request('GET', url, + headers=request.transport_headers, + timeout=request.timeout, + proxies=_proxies_dict(request.proxy)) LOGGER.debug("=== RESPONSE ===") LOGGER.debug(resp.headers) LOGGER.debug(resp.content) resp.raise_for_status() - if url.endswith('.json'): + if extension == 'json': return json.loads(resp.content) else: return resp.text except requests.HTTPError as ex: - if url.endswith('.json'): + if extension == 'json': content = json.loads(ex.response.content) raise exceptions.SoftLayerAPIError(ex.response.status_code, content['error']) else: raise exceptions.SoftLayerAPIError(ex.response.status_code, - ex.response.text) + ex.response.content) except requests.RequestException as ex: raise exceptions.TransportError(0, str(ex)) + + +def _proxies_dict(proxy): + """Makes a dict appropriate to pass to requests.""" + if not proxy: + return None + return {'http': proxy, 'https': proxy} + + +def __format_object_mask(objectmask, service): + """Format new and old style object masks into proper headers. + + :param objectmask: a string- or dict-based object mask + :param service: a SoftLayer API service name + + """ + if isinstance(objectmask, dict): + mheader = '%sObjectMask' % service + else: + mheader = 'SoftLayer_ObjectMask' + + objectmask = objectmask.strip() + if (not objectmask.startswith('mask') + and not objectmask.startswith('[')): + objectmask = "mask[%s]" % objectmask + + return {mheader: {'mask': objectmask}} diff --git a/setup.cfg b/setup.cfg index d53bef49e..3d9f45499 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ verbosity=2 detailed-errors=1 with-coverage=1 -cover-min-percentage=74 +cover-min-percentage=75 cover-erase=true cover-package=SoftLayer cover-html=1 From e3045fa10bc71fa29ca9fc3f889feeed06b45b32 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 22 Sep 2014 17:11:33 -0500 Subject: [PATCH 0133/2667] Removes ability to specify which rest file extension to use --- SoftLayer/managers/metadata.py | 6 +--- SoftLayer/tests/managers/metadata_tests.py | 2 -- SoftLayer/tests/transport_tests.py | 34 ++-------------------- SoftLayer/transports.py | 19 ++++-------- 4 files changed, 9 insertions(+), 52 deletions(-) diff --git a/SoftLayer/managers/metadata.py b/SoftLayer/managers/metadata.py index 6d6b47b2a..074415997 100644 --- a/SoftLayer/managers/metadata.py +++ b/SoftLayer/managers/metadata.py @@ -69,9 +69,6 @@ def get(self, name, param=None): raise exceptions.SoftLayerError('Unknown metadata attribute.') call_details = self.attribs[name] - extension = 'json' - if self.attribs[name]['call'] == 'UserMetadata': - extension = 'txt' if call_details.get('param_req'): if not param: @@ -87,8 +84,7 @@ def get(self, name, param=None): request.identifier = param try: - return transports.make_rest_api_call(request, - extension=extension) + return transports.make_rest_api_call(request) except exceptions.SoftLayerAPIError as ex: if ex.faultCode == 404: return None diff --git a/SoftLayer/tests/managers/metadata_tests.py b/SoftLayer/tests/managers/metadata_tests.py index 93681c052..592924e15 100644 --- a/SoftLayer/tests/managers/metadata_tests.py +++ b/SoftLayer/tests/managers/metadata_tests.py @@ -32,7 +32,6 @@ def test_get(self, make_request): {'User-Agent': consts.USER_AGENT}) self.assertEqual(request.timeout, self.metadata.timeout) self.assertEqual(request.identifier, None) - self.assertEqual(kwargs['extension'], 'json') @mock.patch('SoftLayer.transports.make_rest_api_call') def test_no_param(self, make_request): @@ -64,7 +63,6 @@ def test_user_data(self, make_request): (request, ), kwargs = make_request.call_args self.assertEqual(request.method, 'UserMetadata') self.assertEqual(request.identifier, None) - self.assertEqual(kwargs['extension'], 'txt') @mock.patch('SoftLayer.transports.make_rest_api_call') def test_return_none(self, make_request): diff --git a/SoftLayer/tests/transport_tests.py b/SoftLayer/tests/transport_tests.py index 8d02b0236..972653a64 100644 --- a/SoftLayer/tests/transport_tests.py +++ b/SoftLayer/tests/transport_tests.py @@ -247,7 +247,7 @@ def test_json(self, request): req.service = 'SoftLayer_Service' req.method = 'Resource' - resp = transports.make_rest_api_call(req, extension='json') + resp = transports.make_rest_api_call(req) self.assertEqual(resp, {}) request.assert_called_with( 'GET', 'http://something.com/SoftLayer_Service/Resource.json', @@ -266,8 +266,7 @@ def test_json(self, request): request().raise_for_status.side_effect = e self.assertRaises( - SoftLayer.SoftLayerAPIError, - transports.make_rest_api_call, req, extension='json') + SoftLayer.SoftLayerAPIError, transports.make_rest_api_call, req) def test_proxy_without_protocol(self): req = transports.Request() @@ -317,35 +316,6 @@ def test_with_id(self, request): proxies=None, timeout=None) - @mock.patch('requests.request') - def test_text(self, request): - request().content = 'content' - request().text = 'content' - - req = transports.Request() - req.endpoint = 'http://something.com' - req.service = 'SoftLayer_Service' - req.method = 'Resource' - - resp = transports.make_rest_api_call(req, extension='txt') - self.assertEqual(resp, 'content') - request.assert_called_with( - 'GET', - 'http://something.com/SoftLayer_Service/Resource.txt', - headers=None, - proxies=None, - timeout=None) - - # Test Text Error - e = requests.HTTPError('error') - e.response = mock.MagicMock() - e.response.status_code = 404 - e.response.content = 'Error Code' - request().raise_for_status.side_effect = e - - self.assertRaises(SoftLayer.SoftLayerAPIError, - transports.make_rest_api_call, req, extension='txt') - @mock.patch('requests.request') def test_unknown_error(self, request): e = requests.RequestException('error') diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 76a0ea6f2..4b23ddc4b 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -141,7 +141,7 @@ def make_xml_rpc_api_call(request): raise exceptions.TransportError(0, str(ex)) -def make_rest_api_call(request, extension='json'): +def make_rest_api_call(request): """Makes a SoftLayer API call against the REST endpoint. This currently only works with GET requests @@ -154,7 +154,7 @@ def make_rest_api_call(request, extension='json'): if request.identifier is not None: url_parts.append(str(request.identifier)) - url = '%s.%s' % ('/'.join(url_parts), extension) + url = '%s.%s' % ('/'.join(url_parts), 'json') LOGGER.debug("=== REQUEST ===") LOGGER.info('%s %s', request.method, url) @@ -168,18 +168,11 @@ def make_rest_api_call(request, extension='json'): LOGGER.debug(resp.headers) LOGGER.debug(resp.content) resp.raise_for_status() - if extension == 'json': - return json.loads(resp.content) - else: - return resp.text + return json.loads(resp.content) except requests.HTTPError as ex: - if extension == 'json': - content = json.loads(ex.response.content) - raise exceptions.SoftLayerAPIError(ex.response.status_code, - content['error']) - else: - raise exceptions.SoftLayerAPIError(ex.response.status_code, - ex.response.content) + content = json.loads(ex.response.content) + raise exceptions.SoftLayerAPIError(ex.response.status_code, + content['error']) except requests.RequestException as ex: raise exceptions.TransportError(0, str(ex)) From 36aa8eb3e04256b7716cc57b069bf72d87803426 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 22 Sep 2014 17:30:04 -0500 Subject: [PATCH 0134/2667] Fixes user-metadata bug/clearifies debugging output --- SoftLayer/CLI/modules/metadata.py | 2 +- SoftLayer/transports.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/modules/metadata.py b/SoftLayer/CLI/modules/metadata.py index 07f6c12f3..4bf89d9b4 100644 --- a/SoftLayer/CLI/modules/metadata.py +++ b/SoftLayer/CLI/modules/metadata.py @@ -181,7 +181,7 @@ def _execute(self, _): separator=',') -class UserMetadata(environment.CLIRunnable): +class UserMetadata(MetaRunnable): """ usage: sl metadata user_data [options] diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 4b23ddc4b..05c02f3b2 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -157,7 +157,7 @@ def make_rest_api_call(request): url = '%s.%s' % ('/'.join(url_parts), 'json') LOGGER.debug("=== REQUEST ===") - LOGGER.info('%s %s', request.method, url) + LOGGER.info(url) LOGGER.debug(request.transport_headers) try: resp = requests.request('GET', url, From c987c8309628caaa517fd74f15483f8863d3cd75 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 22 Sep 2014 17:50:52 -0500 Subject: [PATCH 0135/2667] Don't set headers to None when they're empty --- SoftLayer/tests/transport_tests.py | 4 +++- SoftLayer/transports.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/SoftLayer/tests/transport_tests.py b/SoftLayer/tests/transport_tests.py index 972653a64..4afec4f1c 100644 --- a/SoftLayer/tests/transport_tests.py +++ b/SoftLayer/tests/transport_tests.py @@ -39,7 +39,9 @@ def test_call(self, request): headers - + + + diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 05c02f3b2..2e9ff64c5 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -94,7 +94,7 @@ def make_xml_rpc_api_call(request): 'offset': request.offset or 0, } - largs.insert(0, {'headers': headers or None}) + largs.insert(0, {'headers': headers}) url = '/'.join([request.endpoint, request.service]) payload = utils.xmlrpc_client.dumps(tuple(largs), From 0d7e32f627bb65fbd5c31e1ba55ce3e1019ab035 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 22 Sep 2014 18:07:29 -0500 Subject: [PATCH 0136/2667] Proxies missing a protocol raises differently in py2.6 + misc changes --- SoftLayer/tests/transport_tests.py | 18 ++++++++++++++---- SoftLayer/transports.py | 4 ++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/SoftLayer/tests/transport_tests.py b/SoftLayer/tests/transport_tests.py index 4afec4f1c..5f166002b 100644 --- a/SoftLayer/tests/transport_tests.py +++ b/SoftLayer/tests/transport_tests.py @@ -4,6 +4,8 @@ :license: MIT, see LICENSE for more details. """ +import warnings + import mock import requests @@ -71,8 +73,12 @@ def test_proxy_without_protocol(self): req.method = 'Resource' req.proxy = 'localhost:3128' - self.assertRaises(SoftLayer.TransportError, - transports.make_xml_rpc_api_call, req) + try: + self.assertRaises(SoftLayer.TransportError, + transports.make_xml_rpc_api_call, req) + except AssertionError: + warnings.warn("AssertionError raised instead of a " + "SoftLayer.TransportError error") @mock.patch('requests.request') def test_valid_proxy(self, request): @@ -277,8 +283,12 @@ def test_proxy_without_protocol(self): req.method = 'Resource' req.proxy = 'localhost:3128' - self.assertRaises(SoftLayer.TransportError, - transports.make_rest_api_call, req) + try: + self.assertRaises(SoftLayer.TransportError, + transports.make_rest_api_call, req) + except AssertionError: + warnings.warn("AssertionError raised instead of a " + "SoftLayer.TransportError error") @mock.patch('requests.request') def test_valid_proxy(self, request): diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 2e9ff64c5..389e8b443 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -32,7 +32,7 @@ def __init__(self): self.method = None #: RPC Arguments. - self.args = [] + self.args = tuple() #: Transport headers. self.headers = {} @@ -43,7 +43,7 @@ def __init__(self): #: Integer timeout. self.timeout = None - #: URL to proxy to. + #: URL to proxy API requests to. self.proxy = None #: Verify HTTPS Certificate. From 0317a5606ec456dafc95869c586fa01bcbca4ac6 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 23 Sep 2014 10:28:20 -0500 Subject: [PATCH 0137/2667] Clarifies doc blocks --- SoftLayer/tests/transport_tests.py | 14 +++++++------- SoftLayer/transports.py | 22 +++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/SoftLayer/tests/transport_tests.py b/SoftLayer/tests/transport_tests.py index 5f166002b..c655e3b53 100644 --- a/SoftLayer/tests/transport_tests.py +++ b/SoftLayer/tests/transport_tests.py @@ -58,7 +58,7 @@ def test_call(self, request): request.assert_called_with('POST', 'http://something.com/SoftLayer_Service', - headers=None, + headers={}, proxies=None, data=data, timeout=None, @@ -94,7 +94,7 @@ def test_valid_proxy(self, request): request.assert_called_with( 'POST', mock.ANY, - headers=None, + headers={}, proxies={'https': 'http://localhost:3128', 'http': 'http://localhost:3128'}, data=mock.ANY, @@ -248,7 +248,7 @@ def test_request_exception(self, request): class TestRestAPICall(testing.TestCase): @mock.patch('requests.request') - def test_json(self, request): + def test_basic(self, request): request().content = '{}' req = transports.Request() req.endpoint = 'http://something.com' @@ -259,7 +259,7 @@ def test_json(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', 'http://something.com/SoftLayer_Service/Resource.json', - headers=None, + headers={}, proxies=None, timeout=None) @@ -273,8 +273,8 @@ def test_json(self, request): }''' request().raise_for_status.side_effect = e - self.assertRaises( - SoftLayer.SoftLayerAPIError, transports.make_rest_api_call, req) + self.assertRaises(SoftLayer.SoftLayerAPIError, + transports.make_rest_api_call, req) def test_proxy_without_protocol(self): req = transports.Request() @@ -324,7 +324,7 @@ def test_with_id(self, request): request.assert_called_with( 'GET', 'http://something.com/SoftLayer_Service/getObject/2.json', - headers=None, + headers={}, proxies=None, timeout=None) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 389e8b443..e199bea77 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -25,20 +25,20 @@ def __init__(self): #: The SoftLayer endpoint address. self.endpoint = None - #: Service name. + #: API service name. E.G. SoftLayer_Account self.service = None - #: Method name. + #: API method name. E.G. getObject self.method = None - #: RPC Arguments. + #: API Parameters. self.args = tuple() - #: Transport headers. + #: API headers, used for authentication, masks, limits, offsets, etc. self.headers = {} #: Transport headers. - self.transport_headers = None + self.transport_headers = {} #: Integer timeout. self.timeout = None @@ -46,10 +46,10 @@ def __init__(self): #: URL to proxy API requests to. self.proxy = None - #: Verify HTTPS Certificate. + #: Boolean specifying if the server certificate should be verified. self.verify = True - #: Client certificate path. + #: Client certificate file path. self.cert = None #: InitParameter/identifier of an object. @@ -111,7 +111,7 @@ def make_xml_rpc_api_call(request): timeout=request.timeout, verify=request.verify, cert=request.cert, - proxies=_proxies_dict(request.proxy)) + proxies=__proxies_dict(request.proxy)) LOGGER.debug("=== RESPONSE ===") LOGGER.debug(response.headers) LOGGER.debug(response.content) @@ -163,7 +163,7 @@ def make_rest_api_call(request): resp = requests.request('GET', url, headers=request.transport_headers, timeout=request.timeout, - proxies=_proxies_dict(request.proxy)) + proxies=__proxies_dict(request.proxy)) LOGGER.debug("=== RESPONSE ===") LOGGER.debug(resp.headers) LOGGER.debug(resp.content) @@ -177,8 +177,8 @@ def make_rest_api_call(request): raise exceptions.TransportError(0, str(ex)) -def _proxies_dict(proxy): - """Makes a dict appropriate to pass to requests.""" +def __proxies_dict(proxy): + """Makes a proxy dict appropriate to pass to requests.""" if not proxy: return None return {'http': proxy, 'https': proxy} From 99e5c21d202528e93748a2c789baad5b077f9ffa Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 23 Sep 2014 10:29:24 -0500 Subject: [PATCH 0138/2667] Fixes small style issue --- SoftLayer/API.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index cab5e6921..6797cf3b6 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -163,7 +163,7 @@ def call(self, service, method, *args, **kwargs): request.method = method request.args = args request.transport_headers = http_headers - request.timeout = self. timeout + request.timeout = self.timeout request.proxy = self.proxy request.identifier = kwargs.get('id') request.mask = kwargs.get('mask') From a399890fe6791d64d856e4592adb5e5ca07dd4ba Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 1 Oct 2014 13:59:04 -0500 Subject: [PATCH 0139/2667] Adds version to main CLI help --- SoftLayer/CLI/core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index fa383eb74..c47b24a54 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -4,7 +4,7 @@ sl help sl [-h | --help] -SoftLayer Command-line Client +SoftLayer Command-line Client {version} The available modules are: @@ -98,7 +98,8 @@ def __init__(self, env): def get_main_help(self): """Get main help text.""" - return _append_common_options(__doc__).strip() + main_doc = __doc__.format(version=SoftLayer.__version__) + return _append_common_options(main_doc).strip() def get_module_help(self, module_name): """Get help text for a module.""" From a6873a0d11e9058ec839aa562d10fc2b90876e51 Mon Sep 17 00:00:00 2001 From: hack Date: Thu, 9 Oct 2014 15:01:11 +0200 Subject: [PATCH 0140/2667] Support dash (-) in hostname when importing dns zone files (issue #146) --- SoftLayer/CLI/modules/dns.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/modules/dns.py b/SoftLayer/CLI/modules/dns.py index 96e43ee9c..9b1cb7ee0 100644 --- a/SoftLayer/CLI/modules/dns.py +++ b/SoftLayer/CLI/modules/dns.py @@ -100,11 +100,11 @@ def execute(self,args): manager = SoftLayer.DNSManager(self.client) lines = [line.strip() for line in open(args[''])] - zoneSearch = re.search('\$ORIGIN (?P.*)\.',lines[0]) + zoneSearch = re.search('^\$ORIGIN (?P.*)\.',lines[0]) zone = zoneSearch.group('zone') if (dryRun): - print "Starting up a dry run..." + print "Starting up a dry run for %s..." % (zone) zone_id = 0 else: try: @@ -114,9 +114,11 @@ def execute(self,args): manager.create_zone(zone) zone_id = helpers.resolve_id(manager.resolve_ids, zone,name='zone') - for content in lines: - domainSearch = re.search('((?P(\w+(\.)?)*|\@)?\s+(?P\d+)?\s+(?P\w+)?)?\s+(?P\w+)\s+(?P.*)',content) - if (domainSearch is not None): + for content in lines[1:]: + domainSearch = re.search('^((?P([\w-]+(\.)?)*|\@)?\s+(?P\d+)?\s+(?P\w+)?)?\s+(?P\w+)\s+(?P.*)',content) + if (domainSearch is None): + print "\033[92mFailed: unknown line: %s\033[0m" % (content) + else: domainName = domainSearch.group('domain') #The API requires we send a host, although bind allows a blank entry. @ is the same thing as blank if (domainName is None): From 47c72d598df80d79773bff068f7e44699da26ab1 Mon Sep 17 00:00:00 2001 From: Phil Date: Fri, 10 Oct 2014 14:47:38 +0700 Subject: [PATCH 0141/2667] Revert "Issue #146 - DNS Zone Import" --- SoftLayer/CLI/modules/dns.py | 73 ------------------- .../tests/CLI/modules/artifacts/realtest.com | 21 ------ SoftLayer/tests/CLI/modules/dns_tests.py | 12 --- 3 files changed, 106 deletions(-) mode change 100644 => 100755 SoftLayer/CLI/modules/dns.py delete mode 100644 SoftLayer/tests/CLI/modules/artifacts/realtest.com diff --git a/SoftLayer/CLI/modules/dns.py b/SoftLayer/CLI/modules/dns.py old mode 100644 new mode 100755 index 9b1cb7ee0..0d9d6f389 --- a/SoftLayer/CLI/modules/dns.py +++ b/SoftLayer/CLI/modules/dns.py @@ -8,7 +8,6 @@ delete Delete zone list List zones or a zone's records print Print zone in BIND format - import Import a BIND style zone file The available record commands are: add Add resource record @@ -81,78 +80,6 @@ def execute(self, args): raise exceptions.CLIAbort("Aborted.") -class ImportZone(environment.CLIRunnable): - """ -usage: sl dns import [--dryRun] - -Creates a new zone based off a nicely BIND formatted file - -Arguments: - Path to the bind zone file you want to import -Options: - --dryRun don't actually do anything. This will show you what we were able to parse. - - """ - action = 'import' - def execute(self,args): - import re - dryRun = args.get('--dryRun') - - manager = SoftLayer.DNSManager(self.client) - lines = [line.strip() for line in open(args[''])] - zoneSearch = re.search('^\$ORIGIN (?P.*)\.',lines[0]) - zone = zoneSearch.group('zone') - - if (dryRun): - print "Starting up a dry run for %s..." % (zone) - zone_id = 0 - else: - try: - zone_id = helpers.resolve_id(manager.resolve_ids, zone,name='zone') - except : - print "\033[92mCREATED ZONE: %s\033[0m" % (zone) - manager.create_zone(zone) - zone_id = helpers.resolve_id(manager.resolve_ids, zone,name='zone') - - for content in lines[1:]: - domainSearch = re.search('^((?P([\w-]+(\.)?)*|\@)?\s+(?P\d+)?\s+(?P\w+)?)?\s+(?P\w+)\s+(?P.*)',content) - if (domainSearch is None): - print "\033[92mFailed: unknown line: %s\033[0m" % (content) - else: - domainName = domainSearch.group('domain') - #The API requires we send a host, although bind allows a blank entry. @ is the same thing as blank - if (domainName is None): - domainName = "@" - - domainttl = domainSearch.group('ttl') - domainClass = domainSearch.group('class') - domainType = domainSearch.group('type') - domainRecord = domainSearch.group('record') - - #This will skip the SOA record bit. And any domain that gets parsed oddly. - if (domainType.upper() == 'IN'): - print "SKIPPED: Host: %s TTL: %s Type: %s Record: %s" % (domainName,domainttl,domainType,domainRecord) - continue - - #the dns class doesn't support weighted MX records yet, so we chomp that part out. - if (domainType.upper() == "MX"): - recordSearch = re.search('(?P\d+)\s+(?P.*)',domainRecord) - domainRecord = recordSearch.group('record') - - try: - if (dryRun): - print "Parsed: Host: %s TTL: %s Type: %s Record: %s" % (domainName,domainttl,domainType,domainRecord) - else: - manager.create_record(zone_id,domainName,domainType,domainRecord,domainttl) - print "\033[92mCreated: Host: %s TTL: %s Type: %s Record: %s\033[0m" % (domainName,domainttl,domainType,domainRecord) - except Exception, e: - print "\033[91mFAILED: Host: %s Type: %s Record: %s" % (domainName,domainType,domainRecord.upper()) - print "\t", e ,"\033[0m" - - - return "Finished" - - class ListZones(environment.CLIRunnable): """ usage: sl dns list [] [options] diff --git a/SoftLayer/tests/CLI/modules/artifacts/realtest.com b/SoftLayer/tests/CLI/modules/artifacts/realtest.com deleted file mode 100644 index 0c5550d1d..000000000 --- a/SoftLayer/tests/CLI/modules/artifacts/realtest.com +++ /dev/null @@ -1,21 +0,0 @@ -$ORIGIN realtest.com. -$TTL 86400 -@ IN SOA ns1.softlayer.com. support.softlayer.com. ( - 2014052300 ; Serial - 7200 ; Refresh - 600 ; Retry - 1728000 ; Expire - 43200) ; Minimum - -@ 86400 IN NS ns1.softlayer.com. -@ 86400 IN NS ns2.softlayer.com. - - IN MX 10 test.realtest.com. -testing 86400 IN A 127.0.0.1 -testing1 86400 IN A 12.12.0.1 -server2 IN A 1.0.3.4 -ftp IN CNAME server2 -dev.realtest.com IN TXT "This is just a test of the txt record" - IN AAAA 2001:db8:10::1 -spf IN TXT "v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a -all" - diff --git a/SoftLayer/tests/CLI/modules/dns_tests.py b/SoftLayer/tests/CLI/modules/dns_tests.py index b4986a0d5..811556db2 100644 --- a/SoftLayer/tests/CLI/modules/dns_tests.py +++ b/SoftLayer/tests/CLI/modules/dns_tests.py @@ -119,15 +119,3 @@ def test_delete_record(self, no_going_back_mock): '': 'hostname', '--id': 1, '--really': False}) - - def test_import_zone(self): - import pprint - pp = pprint.PrettyPrinter(indent=4) - - command = dns.ImportZone(client=self.client) - output = command.execute({ - '' : 'realtest.com' - '--dryRun' : '--dryRun' - }) - pp.pprint(output) - self.assertEqual(['Finished'],output) \ No newline at end of file From a31bfd67730fa5d5a2f5f011ee57cce362b20aad Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 10 Oct 2014 08:13:54 -0500 Subject: [PATCH 0142/2667] Many fixes to DNS import code Fixes tests (the existing test was failing and didn't actually test) Fixes styling (flake8 and pylint were crying) Moves parsing logic away from the logic to persist changes to SoftLayer --- SoftLayer/CLI/modules/dns.py | 114 +++++++++++++++++++ SoftLayer/managers/dns.py | 1 + SoftLayer/testing/fixtures/realtest.com | 21 ++++ SoftLayer/tests/CLI/modules/dns_tests.py | 134 +++++++++++++++++++++++ 4 files changed, 270 insertions(+) create mode 100644 SoftLayer/testing/fixtures/realtest.com diff --git a/SoftLayer/CLI/modules/dns.py b/SoftLayer/CLI/modules/dns.py index 0d9d6f389..90a995b87 100755 --- a/SoftLayer/CLI/modules/dns.py +++ b/SoftLayer/CLI/modules/dns.py @@ -22,6 +22,14 @@ from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +import re + +RECORD_REGEX = re.compile(r"""^((?P([\w-]+(\.)?)*|\@)?\s+ + (?P\d+)?\s+ + (?P\w+)?)?\s+ + (?P\w+)\s+ + (?P.*)""", re.X) + class DumpZone(environment.CLIRunnable): """ @@ -80,6 +88,112 @@ def execute(self, args): raise exceptions.CLIAbort("Aborted.") +class ImportZone(environment.CLIRunnable): + """ +usage: sl dns import [options] + +Creates a new zone based off a nicely BIND formatted file + +Arguments: + Path to the bind zone file you want to import +Options: + --dry-run Don't actually do anything. This will show you what is parsed + + """ + action = 'import' + + def execute(self, args): + + dry_run = args.get('--dry-run') + + manager = SoftLayer.DNSManager(self.client) + with open(args['']) as zone_file: + zone_contents = zone_file.read() + + zone, records, bad_lines = parse_zone_details(zone_contents) + + self.env.out("Parsed: zone=%s" % zone) + for record in records: + self.env.out("Parsed: %s" % record) + for line in bad_lines: + self.env.out("Unparsed: %s" % line) + + if dry_run: + return + + # Find zone id or create the zone if it doesn't exist + try: + zone_id = helpers.resolve_id(manager.resolve_ids, zone, + name='zone') + except exceptions.CLIAbort: + zone_id = manager.create_zone(zone)['id'] + self.env.out("\033[92mCREATED ZONE: %s\033[0m" % zone) + + # Attempt to create each record + for record in records: + try: + manager.create_record(zone_id, + record['record'], + record['record_type'], + record['data'], + record['ttl']) + self.env.out("\033[92mCreated: Host: %s\033[0m" % record) + except SoftLayer.SoftLayerAPIError as ex: + self.env.out("\033[91mFAILED: %s" % record) + self.env.out("%s \033[0m" % ex) + + return "Finished" + + +def parse_zone_details(zone_contents): + """Parses a zone file into python data-structures""" + records = [] + bad_lines = [] + zone_lines = [line.strip() for line in zone_contents.split('\n')] + + zone_search = re.search(r'^\$ORIGIN (?P.*)\.', zone_lines[0]) + zone = zone_search.group('zone') + + for line in zone_lines[1:]: + record_search = re.search(RECORD_REGEX, line) + if record_search is None: + bad_lines.append(line) + continue + + name = record_search.group('domain') + # The API requires we send a host, although bind allows a blank + # entry. @ is the same thing as blank + if name is None: + name = "@" + + ttl = record_search.group('ttl') + # we don't do anything with the class + # domain_class = domainSearch.group('class') + record_type = record_search.group('type').upper() + data = record_search.group('data') + + # the dns class doesn't support weighted MX records yet, so we chomp + # that part out. + if record_type == "MX": + record_search = re.search(r'(?P\d+)\s+(?P.*)', data) + data = record_search.group('data') + + # This will skip the SOA record bit. And any domain that gets + # parsed oddly. + if record_type == 'IN': + bad_lines.append(line) + continue + + records.append({ + 'record': name, + 'record_type': record_type, + 'data': data, + 'ttl': ttl, + }) + + return zone, records, bad_lines + + class ListZones(environment.CLIRunnable): """ usage: sl dns list [] [options] diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index 6aa9d5429..c0dc5d221 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -21,6 +21,7 @@ def __init__(self, client): self.client = client self.service = self.client['Dns_Domain'] self.record = self.client['Dns_Domain_ResourceRecord'] + self.resolvers = [self._get_zone_id_from_name] def _get_zone_id_from_name(self, name): diff --git a/SoftLayer/testing/fixtures/realtest.com b/SoftLayer/testing/fixtures/realtest.com new file mode 100644 index 000000000..0c5550d1d --- /dev/null +++ b/SoftLayer/testing/fixtures/realtest.com @@ -0,0 +1,21 @@ +$ORIGIN realtest.com. +$TTL 86400 +@ IN SOA ns1.softlayer.com. support.softlayer.com. ( + 2014052300 ; Serial + 7200 ; Refresh + 600 ; Retry + 1728000 ; Expire + 43200) ; Minimum + +@ 86400 IN NS ns1.softlayer.com. +@ 86400 IN NS ns2.softlayer.com. + + IN MX 10 test.realtest.com. +testing 86400 IN A 127.0.0.1 +testing1 86400 IN A 12.12.0.1 +server2 IN A 1.0.3.4 +ftp IN CNAME server2 +dev.realtest.com IN TXT "This is just a test of the txt record" + IN AAAA 2001:db8:10::1 +spf IN TXT "v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a -all" + diff --git a/SoftLayer/tests/CLI/modules/dns_tests.py b/SoftLayer/tests/CLI/modules/dns_tests.py index 811556db2..e9fb70783 100644 --- a/SoftLayer/tests/CLI/modules/dns_tests.py +++ b/SoftLayer/tests/CLI/modules/dns_tests.py @@ -4,6 +4,8 @@ :license: MIT, see LICENSE for more details. """ +import os.path + import mock from SoftLayer.CLI import exceptions @@ -16,6 +18,7 @@ class DnsTests(testing.TestCase): def set_up(self): self.client = testing.FixtureClient() + self.env = mock.MagicMock() def test_dump_zone(self): command = dns.DumpZone(client=self.client) @@ -119,3 +122,134 @@ def test_delete_record(self, no_going_back_mock): '': 'hostname', '--id': 1, '--really': False}) + + def test_parse_zone_file(self): + zone_file = """$ORIGIN realtest.com. +$TTL 86400 +@ IN SOA ns1.softlayer.com. support.softlayer.com. ( + 2014052300 ; Serial + 7200 ; Refresh + 600 ; Retry + 1728000 ; Expire + 43200) ; Minimum + +@ 86400 IN NS ns1.softlayer.com. +@ 86400 IN NS ns2.softlayer.com. + + IN MX 10 test.realtest.com. +testing 86400 IN A 127.0.0.1 +testing1 86400 IN A 12.12.0.1 +server2 IN A 1.0.3.4 +ftp IN CNAME server2 +dev.realtest.com IN TXT "This is just a test of the txt record" + IN AAAA 2001:db8:10::1 +spf IN TXT "v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a" + +""" + expected = [{'data': 'ns1.softlayer.com.', + 'record': '@', + 'record_type': 'NS', + 'ttl': '86400'}, + {'data': 'ns2.softlayer.com.', + 'record': '@', + 'record_type': 'NS', + 'ttl': '86400'}, + {'data': '127.0.0.1', + 'record': 'testing', + 'record_type': 'A', + 'ttl': '86400'}, + {'data': '12.12.0.1', + 'record': 'testing1', + 'record_type': 'A', + 'ttl': '86400'}, + {'data': '1.0.3.4', + 'record': 'server2', + 'record_type': 'A', + 'ttl': None}, + {'data': 'server2', + 'record': 'ftp', + 'record_type': 'CNAME', + 'ttl': None}, + {'data': '"This is just a test of the txt record"', + 'record': 'dev.realtest.com', + 'record_type': 'TXT', + 'ttl': None}, + {'data': '"v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a"', + 'record': 'spf', + 'record_type': 'TXT', + 'ttl': None}] + zone, records, bad_lines = dns.parse_zone_details(zone_file) + self.assertEqual(zone, 'realtest.com') + self.assertEqual(records, expected) + self.assertEqual(len(bad_lines), 13) + + def test_import_zone_dry_run(self): + command = dns.ImportZone(client=self.client, env=self.env) + path = os.path.join(testing.FIXTURE_PATH, 'realtest.com') + output = command.execute({ + '': path, + '--dry-run': True, + }) + + # Dry run should not result in create calls + self.assertFalse(self.client['Dns_Domain'].createObject.called) + record_service = self.client['Dns_Domain_ResourceRecord'] + self.assertFalse(record_service.createObject.called) + + self.assertEqual(None, output) + + def test_import_zone(self): + command = dns.ImportZone(client=self.client, env=self.env) + path = os.path.join(testing.FIXTURE_PATH, 'realtest.com') + output = command.execute({ + '': path, + '--dry-run': False, + }) + + self.assertFalse(self.client['Dns_Domain'].createObject.called) + record_service = self.client['Dns_Domain_ResourceRecord'] + self.assertEqual(record_service.createObject.call_args_list, + [mock.call({'data': 'ns1.softlayer.com.', + 'host': '@', + 'domainId': 12345, + 'type': 'NS', + 'ttl': '86400'}), + mock.call({'data': 'ns2.softlayer.com.', + 'host': '@', + 'domainId': 12345, + 'type': 'NS', + 'ttl': '86400'}), + mock.call({'data': '127.0.0.1', + 'host': 'testing', + 'domainId': 12345, + 'type': 'A', + 'ttl': '86400'}), + mock.call({'data': '12.12.0.1', + 'host': 'testing1', + 'domainId': 12345, + 'type': 'A', + 'ttl': '86400'}), + mock.call({'data': '1.0.3.4', + 'host': 'server2', + 'domainId': 12345, + 'type': 'A', + 'ttl': None}), + mock.call({'data': 'server2', + 'host': 'ftp', + 'domainId': 12345, + 'type': 'CNAME', + 'ttl': None}), + mock.call({'data': + '"This is just a test of the txt record"', + 'host': 'dev.realtest.com', + 'domainId': 12345, + 'type': 'TXT', + 'ttl': None}), + mock.call({'data': '"v=spf1 ip4:192.0.2.0/24 ' + 'ip4:198.51.100.123 a -all"', + 'host': 'spf', + 'domainId': 12345, + 'type': 'TXT', + 'ttl': None})]) + + self.assertEqual("Finished", output) From 9c08eb06e141ae72704f169d346d1e5a8cd96ec2 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 10 Oct 2014 08:41:26 -0500 Subject: [PATCH 0143/2667] Undo unintended new line --- SoftLayer/managers/dns.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index c0dc5d221..6aa9d5429 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -21,7 +21,6 @@ def __init__(self, client): self.client = client self.service = self.client['Dns_Domain'] self.record = self.client['Dns_Domain_ResourceRecord'] - self.resolvers = [self._get_zone_id_from_name] def _get_zone_id_from_name(self, name): From a8fa357e4b034df354a514151938dfcf6e4ca437 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 10 Oct 2014 12:07:48 -0500 Subject: [PATCH 0144/2667] Improves ordering by quote interface --- SoftLayer/managers/ordering.py | 65 +++++++++++++--------- SoftLayer/tests/managers/ordering_tests.py | 44 +++++++++++---- 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index d99d9dcbb..e344d79e9 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -27,7 +27,8 @@ def get_packages_of_type(self, package_types, mask): type keynames we are interested in. :param string mask: Mask to specify the properties we want to retrieve """ - package_service = self.get_package_service() + + package_service = self.client['Product_Package'] _filter = { 'type': { 'keyName': { @@ -44,12 +45,6 @@ def get_packages_of_type(self, package_types, mask): packages = self.filter_outlet_packages(packages) return packages - def get_package_service(self): - """ Get the service to query product packages - :return SoftLayer.API.Service - """ - return self.client['Product_Package'] - @staticmethod def filter_outlet_packages(packages): """ Remove packages designated as OUTLET @@ -60,6 +55,7 @@ def filter_outlet_packages(packages): :param packages: Dictionary of packages. Name and description keys must be present in each of them. """ + non_outlet_packages = [] for package in packages: @@ -78,6 +74,7 @@ def get_only_active_packages(packages): :param packages Dictionary of packages, isActive key must be present """ + active_packages = [] for package in packages: @@ -110,6 +107,7 @@ def get_package_id_by_type(self, package_type): we are interested in :raises ValueError when no package of the given type is found """ + mask = "mask[id, name, description, isActive, type[keyName]]" package = self.get_package_by_type(package_type, mask) if package: @@ -119,55 +117,65 @@ def get_package_id_by_type(self, package_type): def get_quotes(self): """ Retrieve a list of quotes + :return a list of SoftLayer_Billing_Order_Quote """ + quotes = self.client['Account'].getActiveQuotes() return quotes def get_quote_details(self, quote_id): """ Retrieve quote details + :param quote_id ID number of target quote """ + quote = self.client['Billing_Order_Quote'].getObject(id=quote_id) return quote def get_order_container(self, quote_id): """ Generate an order container from a quote object + :param quote_id ID number of target quote """ + quote = self.client['Billing_Order_Quote'] container = quote.getRecalculatedOrderContainer(id=quote_id) return container['orderContainers'][0] - def generate_order_template(self, quote_id=None, hostnames=None, - domain=None, quantity=None): + def generate_order_template(self, quote_id, extra, quantity=1): """ Generate a complete order template + :param int quote_id: ID of target quote - :param list hostnames: List of hostnames as strings - :param string domain: Domain name to be used for all servers - :param int quantity: Quantity to override default + :param list extra: List of dictionaries that have extra details about + the order such as hostname or domain names for + virtual servers or hardware nodes + :param int quantity: Number of ~things~ to order """ + container = self.get_order_container(quote_id) - if quantity is not None: - container['quantity'] = quantity + container['quantity'] = quantity + + # TODO(kmcdonald): This will only work with virtualGuests and hardware. + # There has to be a better way, since this is based on + # an existing quote that supposedly knows about this + # detail if container['packageId'] == 46: product_type = 'virtualGuests' else: product_type = 'hardware' - if len(hostnames) != container['quantity']: - raise ValueError("You must specify a hostname for each " - "server in the quote") + if len(extra) != quantity: + raise ValueError("You must specify extra for each server in the " + "quote") container[product_type] = [] - for hostname in hostnames: - container[product_type].append( - {'hostname': hostname, 'domain': domain} - ) + for extra_details in extra: + container[product_type].append(extra_details) container['presetId'] = None return container - def verify_quote(self, **kwargs): + def verify_quote(self, quote_id, extra, quantity=1): """ Verifies that a quote order is valid without actually ordering the resources @@ -177,17 +185,20 @@ def verify_quote(self, **kwargs): :param string domain: domain of the new servers :param int quantity: Quantity to override default """ - container = self.generate_order_template(**kwargs) + + container = self.generate_order_template(quote_id, extra, + quantity=quantity) return self.client['Product_Order'].verifyOrder(container) - def order_quote(self, **kwargs): - """ - Places an order using a quote + def order_quote(self, quote_id, extra, quantity=1): + """ Places an order using a quote :param int quote_id: ID for the target quote :param list hostnames: hostnames of the servers :param string domain: domain of the new server :param int quantity: Quantity to override default """ - container = self.generate_order_template(**kwargs) + + container = self.generate_order_template(quote_id, extra, + quantity=quantity) return self.client['Product_Order'].placeOrder(container) diff --git a/SoftLayer/tests/managers/ordering_tests.py b/SoftLayer/tests/managers/ordering_tests.py index 66ffcda86..025e31af3 100644 --- a/SoftLayer/tests/managers/ordering_tests.py +++ b/SoftLayer/tests/managers/ordering_tests.py @@ -1,6 +1,6 @@ """ SoftLayer.tests.managers.ordering_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :license: MIT, see LICENSE for more details. """ @@ -83,19 +83,41 @@ def test_get_quotes(self): def test_get_quote_details(self): quote = self.ordering.get_quote_details(1234) - quote_fixture = self.ordering.client['Billing_Order_Quote'].getObject( - id=1234) + quote_service = self.ordering.client['Billing_Order_Quote'] + quote_fixture = quote_service.getObject(id=1234) self.assertEqual(quote, quote_fixture) def test_verify_quote(self): - result = self.ordering.verify_quote( - quote_id=1234, - domain='example.com', - hostnames=['test1'], - quantity=1) + order_service = self.ordering.client['Product_Order'] + result = self.ordering.verify_quote(1234, + [{'hostname': 'test1', + 'domain': 'example.com'}], + quantity=1) - self.assertEqual(result, self.ordering.client['Product_Order']. - verifyOrder()) + self.assertEqual(result, order_service.verifyOrder()) + self.assertTrue(order_service.verifyOrder.called) def test_order_quote(self): - return True + order_service = self.ordering.client['Product_Order'] + result = self.ordering.verify_quote(1234, + [{'hostname': 'test1', + 'domain': 'example.com'}], + quantity=1) + + self.assertEqual(result, order_service.placeOrder()) + self.assertTrue(order_service.placeOrder.called) + + def test_generate_order_template(self): + result = self.ordering.generate_order_template( + 1234, [{'hostname': 'test1', 'domain': 'example.com'}], quantity=1) + self.assertEqual(result, {'presetId': None, + 'hardware': [{'domain': 'example.com', + 'hostname': 'test1'}], + 'useHourlyPricing': '', + 'packageId': 50, + 'prices': [{'id': 1921}], + 'quantity': 1}) + + def test_generate_order_template_extra_quantity(self): + with self.assertRaises(ValueError): + self.ordering.generate_order_template(1234, [], quantity=1) From 4eec144a13fefa37de131c77613bd46619b45645 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 10 Oct 2014 12:20:12 -0500 Subject: [PATCH 0145/2667] Changes TODO to NOTE --- SoftLayer/managers/ordering.py | 2 +- SoftLayer/managers/vs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index e344d79e9..874213e7f 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -156,7 +156,7 @@ def generate_order_template(self, quote_id, extra, quantity=1): container = self.get_order_container(quote_id) container['quantity'] = quantity - # TODO(kmcdonald): This will only work with virtualGuests and hardware. + # NOTE(kmcdonald): This will only work with virtualGuests and hardware. # There has to be a better way, since this is based on # an existing quote that supposedly knows about this # detail diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index d8538fc5b..f558d7501 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -642,7 +642,7 @@ def _get_package_items(self): mask = "mask[description,capacity,prices[id,categories[name,id]]]" package_type = "VIRTUAL_SERVER_INSTANCE" package_id = self.ordering_manager.get_package_id_by_type(package_type) - package_service = self.ordering_manager.get_package_service() + package_service = self.client['Product_Package'] return package_service.getItems(id=package_id, mask=mask) From 7577307c1b2fca9d4d403b9de23ab6e34b674694 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 10 Oct 2014 12:47:19 -0500 Subject: [PATCH 0146/2667] Version bump to 3.3.0 --- CHANGELOG | 18 +++++++++++++++++- SoftLayer/consts.py | 2 +- docs/conf.py | 4 ++-- setup.py | 2 +- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4e352b227..341db6be2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,19 @@ +3.3.0 + + * CLI+API: Load balancer support + + * CLI: More detail added to the `sl image detail` and `sl image list` commands + + * CLI+API: Adds support for booting into rescue images for virtual servers and hardware + + * API: Adds ability to order virtual and hardare servers from a quote to the ordering manager + + * CLI: Fixes bug with sl server list-chassis and sl server list-chassis + + * API: Restructure of the way custom authentication can be plugged in the API client + + * Several other bug fixes + 3.2.0 * CLI+API: Added firewall manager and CLI module @@ -8,7 +24,7 @@ * API: Added OrderingManager. Remove hard-coded price IDs - * Fixed several small bug fixes + * Fixed several small bugs 3.1.0 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 6ee3ddc47..dc1c8c386 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v3.2.0' +VERSION = 'v3.3.0' 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/docs/conf.py b/docs/conf.py index 31889fed7..f7dffe596 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,9 +51,9 @@ # built documents. # # The short X.Y version. -version = '3.2.0' +version = '3.3.0' # The full version, including alpha/beta/rc tags. -release = '3.2.0' +release = '3.3.0' # 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 c0c434b0e..a3e4df4ba 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ setup( name='SoftLayer', - version='3.2.0', + version='3.3.0', description=description, long_description=long_description, author='SoftLayer Technologies, Inc.', From 36e976f848372115cfa057056856303879890c96 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 10 Oct 2014 17:25:44 -0500 Subject: [PATCH 0147/2667] Cleanup firewall and load balancer code --- SoftLayer/managers/firewall.py | 43 +++++------ SoftLayer/managers/load_balancer.py | 87 +++++++++++----------- SoftLayer/tests/managers/firewall_tests.py | 10 +-- SoftLayer/tests/managers/loadbal_tests.py | 14 ++-- 4 files changed, 74 insertions(+), 80 deletions(-) diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index ef9ad74f3..c45fdaf3d 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -29,7 +29,6 @@ def has_firewall(vlan): class FirewallManager(utils.IdentifierMixin, object): - """ Manages firewalls. :param SoftLayer.API.Client client: the API client instance @@ -49,17 +48,13 @@ def get_standard_package(self, server_id, is_cci=True): False for a server :returns: A dictionary containing the standard CCI firewall package """ + firewall_port_speed = self._get_fwl_port_speed(server_id, is_cci) - _filter = utils.NestedDict({}) - _value = "%s%s" % (firewall_port_speed, - "Mbps Hardware Firewall") - _filter['items']['description'] = utils.query_filter(_value) + _value = "%s%s" % (firewall_port_speed, "Mbps Hardware Firewall") + _filter = {'items': {'description': utils.query_filter(_value)}} - kwargs = utils.NestedDict({}) - kwargs['id'] = 0 # look at package id 0 - kwargs['filter'] = _filter.to_dict() - return self.prod_pkg.getItems(**kwargs) + return self.prod_pkg.getItems(id=0, filter=_filter) def get_dedicated_package(self, ha_enabled=False): """ Retrieves the dedicated firewall package. @@ -77,10 +72,7 @@ def get_dedicated_package(self, ha_enabled=False): else: _filter['items']['description'] = utils.query_filter(fwl_filter) - kwargs = utils.NestedDict({}) - kwargs['id'] = 0 # look at package id 0 - kwargs['filter'] = _filter.to_dict() - return self.prod_pkg.getItems(**kwargs) + return self.prod_pkg.getItems(id=0, filter=_filter.to_dict()) def cancel_firewall(self, firewall_id, dedicated=False): """ Cancels the specified firewall. @@ -89,6 +81,7 @@ def cancel_firewall(self, firewall_id, dedicated=False): :param bool dedicated: If true, the firewall instance is dedicated, otherwise, the firewall instance is shared. """ + fwl_billing = self._get_fwl_billing_item(firewall_id, dedicated) billing_id = fwl_billing['billingItem']['id'] billing_item = self.client['Billing_Item'] @@ -102,6 +95,7 @@ def add_standard_firewall(self, server_id, is_cci=True): otherwise for a CCI :returns: A dictionary containing the standard CCI firewall order """ + package = self.get_standard_package(server_id, is_cci) if is_cci: product_order = { @@ -131,6 +125,7 @@ def add_vlan_firewall(self, vlan_id, ha_enabled=False): :returns: A dictionary containing the VLAN firewall order """ + package = self.get_dedicated_package(ha_enabled) product_order = { 'complexType': 'SoftLayer_Container_Product_Order_Network_' @@ -149,6 +144,7 @@ def _get_fwl_billing_item(self, firewall_id, dedicated=False): :param bool dedicated: whether the firewall is dedicated or standard :returns: A dictionary of the firewall billing item. """ + mask = ('mask[id,billingItem[id]]') if dedicated: fwl_svc = self.client['Network_Vlan_Firewall'] @@ -163,15 +159,15 @@ def _get_fwl_port_speed(self, server_id, is_cci=True): :param bool is_cci: true if the server_id is for a virtual server :returns: a integer representing the Mbps speed of a firewall """ + fwl_port_speed = 0 if is_cci: - mask = ('mask[primaryNetworkComponent[maxSpeed]]') + mask = ('primaryNetworkComponent[maxSpeed]') svc = self.client['Virtual_Guest'] primary = svc.getObject(mask=mask, id=server_id) fwl_port_speed = primary['primaryNetworkComponent']['maxSpeed'] else: - mask = ('mask[id,maxSpeed,' - 'networkComponentGroup.networkComponents]') + mask = ('id,maxSpeed,networkComponentGroup.networkComponents') svc = self.client['Hardware_Server'] network_components = svc.getFrontendNetworkComponents( mask=mask, id=server_id) @@ -207,6 +203,7 @@ def get_firewalls(self): :returns: A list of firewalls on the current account. """ + mask = ('firewallNetworkComponents,' 'networkVlanFirewall,' 'dedicatedFirewallFlag,' @@ -225,6 +222,7 @@ def get_standard_fwl_rules(self, firewall_id): :param integer firewall_id: the instance ID of the standard firewall :returns: A list of the rules. """ + svc = self.client['Network_Component_Firewall'] return svc.getRules(id=firewall_id, mask=RULE_MASK) @@ -234,6 +232,7 @@ def get_dedicated_fwl_rules(self, firewall_id): :param integer firewall_id: the instance ID of the dedicated firewall :returns: A list of the rules. """ + svc = self.client['Network_Vlan_Firewall'] return svc.getRules(id=firewall_id, mask=RULE_MASK) @@ -243,6 +242,7 @@ def edit_dedicated_fwl_rules(self, firewall_id, rules): :param integer firewall_id: the instance ID of the dedicated firewall :param dict rules: the rules to be pushed on the firewall """ + mask = ('mask[networkVlan[firewallInterfaces' '[firewallContextAccessControlLists]]]') svc = self.client['Network_Vlan_Firewall'] @@ -257,10 +257,8 @@ def edit_dedicated_fwl_rules(self, firewall_id, rules): continue fwl_ctx_acl_id = control_list['id'] - template = { - 'firewallContextAccessControlListId': fwl_ctx_acl_id, - 'rules': rules - } + template = {'firewallContextAccessControlListId': fwl_ctx_acl_id, + 'rules': rules} svc = self.client['Network_Firewall_Update_Request'] return svc.createObject(template) @@ -271,9 +269,8 @@ def edit_standard_fwl_rules(self, firewall_id, rules): :param integer firewall_id: the instance ID of the standard firewall :param dict rules: the rules to be pushed on the firewall """ + rule_svc = self.client['Network_Firewall_Update_Request'] - template = { - "networkComponentFirewallId": firewall_id, - "rules": rules} + template = {'networkComponentFirewallId': firewall_id, 'rules': rules} return rule_svc.createObject(template) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 883df6bb3..7d441572f 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -9,8 +9,8 @@ class LoadBalancerManager(utils.IdentifierMixin, object): - """ Manages load balancers. + :param SoftLayer.API.Client client: the API client instance """ @@ -27,14 +27,10 @@ def get_lb_pkgs(self): :returns: A dictionary containing the load balancer packages """ - lb_filter = '*Load Balancer*' - _filter = utils.NestedDict({}) - _filter['items']['description'] = utils.query_filter(lb_filter) + _filter = {'items': {'description': + utils.query_filter('*Load Balancer*')}} - kwargs = utils.NestedDict({}) - kwargs['id'] = 0 # look at package id 0 - kwargs['filter'] = _filter.to_dict() - packages = self.prod_pkg.getItems(**kwargs) + packages = self.prod_pkg.getItems(id=0, filter=_filter) pkgs = [] for package in packages: if not package['description'].startswith('Global'): @@ -46,6 +42,7 @@ def get_ip_address(self, ip_address=None): :returns: A dictionary containing the IP address properties """ + svc = self.client['Network_Subnet_IpAddress'] return svc.getByIpAddress(ip_address) @@ -54,6 +51,7 @@ def get_hc_types(self): :returns: A dictionary containing the health check types """ + svc = self.client['Network_Application_Delivery_Controller_' 'LoadBalancer_Health_Check_Type'] return svc.getAllObjects() @@ -63,6 +61,7 @@ def get_routing_methods(self): :returns: A dictionary containing the load balancer routing methods """ + svc = self.client['Network_Application_Delivery_Controller_' 'LoadBalancer_Routing_Method'] return svc.getAllObjects() @@ -72,17 +71,19 @@ def get_routing_types(self): :returns: A dictionary containing the load balancer routing types """ + svc = self.client['Network_Application_Delivery_Controller_' 'LoadBalancer_Routing_Type'] return svc.getAllObjects() - def get_location(self, datacenter): + def _get_location(self, datacenter): """ Returns the location of the specified datacenter :param string datacenter: The datacenter to create the loadbalancer in :returns: the location id of the given datacenter """ + dcenters = self.client['Location'].getDataCenters() for dcenter in dcenters: if dcenter['name'] == datacenter: @@ -94,6 +95,7 @@ def cancel_lb(self, loadbal_id): :param int loadbal_id: Load Balancer ID to be cancelled. """ + lb_billing = self.lb_svc.getBillingItem(id=loadbal_id) billing_id = lb_billing['id'] billing_item = self.client['Billing_Item'] @@ -104,15 +106,15 @@ def add_local_lb(self, price_item_id, datacenter): :param int price_item_id: The price item ID for the load balancer :param string datacenter: The datacenter to create the loadbalancer in - :returns: A dictionary containing the product order """ + product_order = { 'complexType': 'SoftLayer_Container_Product_Order_Network_' 'LoadBalancer', 'quantity': 1, 'packageId': 0, - "location": self.get_location(datacenter), + "location": self._get_location(datacenter), 'prices': [{'id': price_item_id}] } return self.client['Product_Order'].placeOrder(product_order) @@ -122,16 +124,17 @@ def get_local_lbs(self): :returns: A list of all local load balancers on the current account. """ + mask = ('mask[loadBalancerHardware[datacenter],ipAddress]') return self.account.getAdcLoadBalancers(mask=mask) def get_local_lb(self, loadbal_id, **kwargs): """ Returns a specified local load balancer given the id. - :param int loadbal_id: The id of the load balancer to retrieve + :param int loadbal_id: The id of the load balancer to retrieve :returns: A dictionary containing the details of the load balancer """ - # virtualServers.serviceGroups.services.ipAddress + if 'mask' not in kwargs: kwargs['mask'] = ('mask[loadBalancerHardware[datacenter], ' 'ipAddress, virtualServers[serviceGroups' @@ -146,6 +149,7 @@ def delete_service(self, service_id): :param int service_id: The id of the service to delete """ + svc = self.client['Network_Application_Delivery_Controller_' 'LoadBalancer_Service'] @@ -156,6 +160,7 @@ def delete_service_group(self, group_id): :param int group_id: The id of the service group to delete """ + svc = self.client['Network_Application_Delivery_Controller_' 'LoadBalancer_VirtualServer'] @@ -166,6 +171,7 @@ def toggle_service_status(self, service_id): :param int service_id: The id of the service to delete """ + svc = self.client['Network_Application_Delivery_Controller_' 'LoadBalancer_Service'] return svc.toggleStatus(id=service_id) @@ -173,6 +179,7 @@ def toggle_service_status(self, service_id): def edit_service(self, loadbal_id, service_id, ip_address_id=None, port=None, enabled=None, hc_type=None, weight=None): """ Edits an existing service properties + :param int loadbal_id: The id of the loadbal where the service resides :param int service_id: The id of the service to edit :param string ip_address: The ip address of the service @@ -181,28 +188,26 @@ def edit_service(self, loadbal_id, service_id, ip_address_id=None, :param int hc_type: The health check type :param int weight: the weight to give to the service """ - _filter = utils.NestedDict({}) - _filter['virtualServers']['serviceGroups']['services']['id'] = ( - utils.query_filter(service_id)) - kwargs = utils.NestedDict({}) - kwargs['filter'] = _filter.to_dict() - kwargs['mask'] = ('mask[serviceGroups[services[groupReferences,' - 'healthChecks]]]') + _filter = {'virtualServers': {'serviceGroups': {'services': {'id': + utils.query_filter(service_id)}}}} + + mask = 'serviceGroups[services[groupReferences,healthChecks]]' virtual_servers = self.lb_svc.getVirtualServers(id=loadbal_id, - **kwargs) + filter=_filter, + mask=mask) + for service in virtual_servers[0]['serviceGroups'][0]['services']: if service['id'] == service_id: if enabled is not None: - service['enabled'] = int(enabled) + service['enabled'] = enabled if port is not None: - service['port'] = int(port) + service['port'] = port if weight is not None: - service['groupReferences'][0]['weight'] = int(weight) + service['groupReferences'][0]['weight'] = weight if hc_type is not None: - service['healthChecks'][0]['healthCheckTypeId'] = ( - int(hc_type)) + service['healthChecks'][0]['healthCheckTypeId'] = hc_type if ip_address_id is not None: service['ipAddressId'] = ip_address_id @@ -254,16 +259,14 @@ def add_service_group(self, lb_id, allocation=100, port=80, routing_type=2, routing_method=10): """ Adds a new service group to the load balancer :param int loadbal_id: The id of the loadbal where the service resides - :param int allocation: the % of connections to allocate to the group + :param int allocation: percent of connections to allocate toward the + group :param int port: the port of the service group :param int routing_type: the routing type to set on the service group :param int routing_method: The routing method to set on the group """ - kwargs = utils.NestedDict({}) - kwargs['mask'] = ('mask[virtualServers[serviceGroups' - '[services[groupReferences]]]]') - load_balancer = self.lb_svc.getObject(id=lb_id, **kwargs) - virtual_servers = load_balancer['virtualServers'] + mask = 'virtualServers[serviceGroups[services[groupReferences]]]' + load_balancer = self.lb_svc.getObject(id=lb_id, mask=mask) service_template = { 'port': port, 'allocation': allocation, @@ -275,7 +278,7 @@ def add_service_group(self, lb_id, allocation=100, port=80, ] } - virtual_servers.append(service_template) + load_balancer['virtualServers'].append(service_template) return self.lb_svc.editObject(load_balancer, id=lb_id) def edit_service_group(self, loadbal_id, group_id, allocation=None, @@ -298,13 +301,13 @@ def edit_service_group(self, loadbal_id, group_id, allocation=None, if virtual_server['id'] == group_id: service_group = virtual_server['serviceGroups'][0] if allocation is not None: - virtual_server['allocation'] = int(allocation) + virtual_server['allocation'] = allocation if port is not None: - virtual_server['port'] = int(port) + virtual_server['port'] = port if routing_type is not None: - service_group['routingTypeId'] = int(routing_type) + service_group['routingTypeId'] = routing_type if routing_method is not None: - service_group['routingMethodId'] = int(routing_method) + service_group['routingMethodId'] = routing_method break return self.lb_svc.editObject(load_balancer, id=loadbal_id) @@ -313,15 +316,11 @@ def reset_service_group(self, loadbal_id, group_id): :param int loadbal_id: The id of the loadbal :param int group_id: The id of the service group to reset """ - _filter = utils.NestedDict({}) - _filter['virtualServers']['id'] = utils.query_filter(group_id) - - kwargs = utils.NestedDict({}) - kwargs['filter'] = _filter.to_dict() - kwargs['mask'] = 'mask[serviceGroups]' + _filter = {'virtualServers': {'id': utils.query_filter(group_id)}} virtual_servers = self.lb_svc.getVirtualServers(id=loadbal_id, - **kwargs) + filter=_filter, + mask='serviceGroups') actual_id = virtual_servers[0]['serviceGroups'][0]['id'] svc = self.client['Network_Application_Delivery_Controller' diff --git a/SoftLayer/tests/managers/firewall_tests.py b/SoftLayer/tests/managers/firewall_tests.py index 93e367832..825707f04 100644 --- a/SoftLayer/tests/managers/firewall_tests.py +++ b/SoftLayer/tests/managers/firewall_tests.py @@ -68,7 +68,7 @@ def test_get_standard_package_virtual_server(self): } package_call.assert_called_once_with(filter=_filter, id=0) - mask = ('mask[primaryNetworkComponent[maxSpeed]]') + mask = 'primaryNetworkComponent[maxSpeed]' call2 = self.client['Virtual_Guest'].getObject call2.assert_called_once_with(id=1234, mask=mask) @@ -77,8 +77,7 @@ def test_get_standard_package_bare_metal(self): # we should ask for the frontEndNetworkComponents to get # the firewall port speed - mask = ('mask[id,maxSpeed,' - 'networkComponentGroup.networkComponents]') + mask = 'id,maxSpeed,networkComponentGroup.networkComponents' fenc_call = self.client['Hardware_Server'].getFrontendNetworkComponents fenc_call.assert_called_once_with(id=1234, mask=mask) @@ -155,7 +154,7 @@ def test_add_standard_firewall_cci(self): f.assert_called_once_with(filter=_filter, id=0) call2 = self.client['Virtual_Guest'].getObject - mask = ('mask[primaryNetworkComponent[maxSpeed]]') + mask = 'primaryNetworkComponent[maxSpeed]' call2.assert_called_once_with(id=server_id, mask=mask) f = self.client['Product_Order'].placeOrder f.assert_called_once() @@ -182,8 +181,7 @@ def test_add_standard_firewall_server(self): # we should ask for the frontEndNetworkComponents to get # the firewall port speed - mask = ('mask[id,maxSpeed,' - 'networkComponentGroup.networkComponents]') + mask = 'id,maxSpeed,networkComponentGroup.networkComponents' fenc_call = self.client['Hardware_Server'].getFrontendNetworkComponents fenc_call.assert_called_once_with(id=server_id, mask=mask) diff --git a/SoftLayer/tests/managers/loadbal_tests.py b/SoftLayer/tests/managers/loadbal_tests.py index 8c2cfc92e..6972579c9 100644 --- a/SoftLayer/tests/managers/loadbal_tests.py +++ b/SoftLayer/tests/managers/loadbal_tests.py @@ -45,12 +45,12 @@ def test_get_routing_methods(self): f.assert_called_once() def test_get_location(self): - id1 = self.lb_mgr.get_location('sjc01') + id1 = self.lb_mgr._get_location('sjc01') f = self.client['Location'].getDataCenters f.assert_called_once() self.assertEqual(id1, 168642) - id2 = self.lb_mgr.get_location('dal05') + id2 = self.lb_mgr._get_location('dal05') f = self.client['Location'].getDataCenters f.assert_called_once() self.assertEqual(id2, 'FIRST_AVAILABLE') @@ -151,7 +151,7 @@ def test_edit_service(self): } } } - mask = 'mask[serviceGroups[services[groupReferences,healthChecks]]]' + mask = 'serviceGroups[services[groupReferences,healthChecks]]' call.assert_called_once_with(filter=_filter, mask=mask, id=loadbal_id) call = self.client['Network_Application_Delivery_Controller_' @@ -213,8 +213,7 @@ def test_add_service_group(self): call = self.client['Network_Application_Delivery_Controller_' 'LoadBalancer_VirtualIpAddress'].getObject - mask = ('mask[virtualServers[serviceGroups' - '[services[groupReferences]]]]') + mask = 'virtualServers[serviceGroups[services[groupReferences]]]' call.assert_called_once_with(mask=mask, id=loadbal_id) call = self.client['Network_Application_Delivery_Controller_' @@ -229,8 +228,9 @@ def test_reset_service_group(self): 'LoadBalancer_VirtualIpAddress'].getVirtualServers _filter = {'virtualServers': {'id': {'operation': group_id}}} - mask = 'mask[serviceGroups]' - call.assert_called_once_with(filter=_filter, mask=mask, id=loadbal_id) + call.assert_called_once_with(filter=_filter, + mask='serviceGroups', + id=loadbal_id) call = self.client['Network_Application_Delivery_Controller_' 'LoadBalancer_Service_Group'].kickAllConnections From 73a0a15fad77a0b4369eca0a9010a1e9aa86f25f Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 10 Oct 2014 17:40:42 -0500 Subject: [PATCH 0148/2667] More tilde and whitespace --- SoftLayer/managers/load_balancer.py | 30 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 7d441572f..f72af3227 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -1,6 +1,6 @@ """ SoftLayer.load_balancer - ~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~ Load Balancer Manager/helpers :license: MIT, see LICENSE for more details. @@ -136,11 +136,11 @@ def get_local_lb(self, loadbal_id, **kwargs): """ if 'mask' not in kwargs: - kwargs['mask'] = ('mask[loadBalancerHardware[datacenter], ' + kwargs['mask'] = ('loadBalancerHardware[datacenter], ' 'ipAddress, virtualServers[serviceGroups' '[routingMethod,routingType,services' '[healthChecks[type], groupReferences,' - ' ipAddress]]]]') + ' ipAddress]]]') return self.lb_svc.getObject(id=loadbal_id, **kwargs) @@ -184,7 +184,7 @@ def edit_service(self, loadbal_id, service_id, ip_address_id=None, :param int service_id: The id of the service to edit :param string ip_address: The ip address of the service :param int port: the port of the service - :param int enabled: 1 to enable the service, 0 to disable it + :param bool enabled: enable or disable the search :param int hc_type: The health check type :param int weight: the weight to give to the service """ @@ -201,7 +201,7 @@ def edit_service(self, loadbal_id, service_id, ip_address_id=None, for service in virtual_servers[0]['serviceGroups'][0]['services']: if service['id'] == service_id: if enabled is not None: - service['enabled'] = enabled + service['enabled'] = int(enabled) if port is not None: service['port'] = port if weight is not None: @@ -217,13 +217,14 @@ def edit_service(self, loadbal_id, service_id, ip_address_id=None, return load_balancer def add_service(self, loadbal_id, service_group_id, ip_address_id, - port=80, enabled=1, hc_type=21, weight=1): + port=80, enabled=True, hc_type=21, weight=1): """ Adds a new service to the service group + :param int loadbal_id: The id of the loadbal where the service resides :param int service_group_id: The group to add the service to :param int ip_address id: The ip address ID of the service :param int port: the port of the service - :param int enabled: 1 to enable the service, 0 to disable it + :param bool enabled: Enable or disable the service :param int hc_type: The health check type :param int weight: the weight to give to the service """ @@ -236,7 +237,7 @@ def add_service(self, loadbal_id, service_group_id, ip_address_id, for virtual_server in virtual_servers: if virtual_server['id'] == service_group_id: service_template = { - 'enabled': enabled, + 'enabled': int(enabled), 'port': port, 'ipAddressId': ip_address_id, 'healthChecks': [ @@ -258,6 +259,7 @@ def add_service(self, loadbal_id, service_group_id, ip_address_id, def add_service_group(self, lb_id, allocation=100, port=80, routing_type=2, routing_method=10): """ Adds a new service group to the load balancer + :param int loadbal_id: The id of the loadbal where the service resides :param int allocation: percent of connections to allocate toward the group @@ -265,6 +267,7 @@ def add_service_group(self, lb_id, allocation=100, port=80, :param int routing_type: the routing type to set on the service group :param int routing_method: The routing method to set on the group """ + mask = 'virtualServers[serviceGroups[services[groupReferences]]]' load_balancer = self.lb_svc.getObject(id=lb_id, mask=mask) service_template = { @@ -284,6 +287,7 @@ def add_service_group(self, lb_id, allocation=100, port=80, def edit_service_group(self, loadbal_id, group_id, allocation=None, port=None, routing_type=None, routing_method=None): """ Edit an existing service group + :param int loadbal_id: The id of the loadbal where the service resides :param int group_id: The id of the service group :param int allocation: the % of connections to allocate to the group @@ -291,12 +295,12 @@ def edit_service_group(self, loadbal_id, group_id, allocation=None, :param int routing_type: the routing type to set on the service group :param int routing_method: The routing method to set on the group """ - kwargs = utils.NestedDict({}) - kwargs['mask'] = ('mask[virtualServers[serviceGroups' - '[services[groupReferences]]]]') - load_balancer = self.lb_svc.getObject(id=loadbal_id, **kwargs) + mask = 'virtualServers[serviceGroups[services[groupReferences]]]' + + load_balancer = self.lb_svc.getObject(id=loadbal_id, mask=mask) virtual_servers = load_balancer['virtualServers'] + for virtual_server in virtual_servers: if virtual_server['id'] == group_id: service_group = virtual_server['serviceGroups'][0] @@ -309,10 +313,12 @@ def edit_service_group(self, loadbal_id, group_id, allocation=None, if routing_method is not None: service_group['routingMethodId'] = routing_method break + return self.lb_svc.editObject(load_balancer, id=loadbal_id) def reset_service_group(self, loadbal_id, group_id): """ Resets all the connections on the service group + :param int loadbal_id: The id of the loadbal :param int group_id: The id of the service group to reset """ From 7d644fe1ae56b1b44b5e83b256c16e613335169d Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 10 Oct 2014 18:02:43 -0500 Subject: [PATCH 0149/2667] Fixes tests --- SoftLayer/tests/managers/loadbal_tests.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/SoftLayer/tests/managers/loadbal_tests.py b/SoftLayer/tests/managers/loadbal_tests.py index 6972579c9..4142d5929 100644 --- a/SoftLayer/tests/managers/loadbal_tests.py +++ b/SoftLayer/tests/managers/loadbal_tests.py @@ -88,7 +88,7 @@ def test_add_local_lb(self): def test_get_local_lbs(self): self.lb_mgr.get_local_lbs() call = self.client['Account'].getAdcLoadBalancers - mask = ('mask[loadBalancerHardware[datacenter],ipAddress]') + mask = 'mask[loadBalancerHardware[datacenter],ipAddress]' call.assert_called_once_with(mask=mask) def test_get_local_lb(self): @@ -97,11 +97,11 @@ def test_get_local_lb(self): call = self.client['Network_Application_Delivery_Controller_' 'LoadBalancer_VirtualIpAddress'].getObject - mask = ('mask[loadBalancerHardware[datacenter], ' + mask = ('loadBalancerHardware[datacenter], ' 'ipAddress, virtualServers[serviceGroups' '[routingMethod,routingType,services' '[healthChecks[type], groupReferences,' - ' ipAddress]]]]') + ' ipAddress]]]') call.assert_called_once_with(id=lb_id, mask=mask) def test_delete_service(self): @@ -194,8 +194,7 @@ def test_edit_service_group(self): call = self.client['Network_Application_Delivery_Controller_' 'LoadBalancer_VirtualIpAddress'].getObject - mask = ('mask[virtualServers[serviceGroups' - '[services[groupReferences]]]]') + mask = 'virtualServers[serviceGroups[services[groupReferences]]]' call.assert_called_once_with(mask=mask, id=loadbal_id) call = self.client['Network_Application_Delivery_Controller_' From c194146a275ed64dc9a2ff395f59041706718a38 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 10 Oct 2014 18:25:40 -0500 Subject: [PATCH 0150/2667] Styzle --- SoftLayer/managers/load_balancer.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index f72af3227..0980658fe 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -189,8 +189,10 @@ def edit_service(self, loadbal_id, service_id, ip_address_id=None, :param int weight: the weight to give to the service """ - _filter = {'virtualServers': {'serviceGroups': {'services': {'id': - utils.query_filter(service_id)}}}} + _filter = { + 'virtualServers': { + 'serviceGroups': { + 'services': {'id': utils.query_filter(service_id)}}}} mask = 'serviceGroups[services[groupReferences,healthChecks]]' From f3e0b6f654e02e28510d818ed799e5ff7ced52ad Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 13 Oct 2014 16:43:42 -0500 Subject: [PATCH 0151/2667] Adds fast cpu provision as a package type for the HW manager --- SoftLayer/managers/hardware.py | 4 +++- SoftLayer/tests/managers/hardware_tests.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 2f8addd40..d832eeb99 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -212,7 +212,9 @@ def get_available_dedicated_server_packages(self): ordering_manager = self.ordering_manager mask = 'id,name,description,type,isActive' - package_types = ['BARE_METAL_CPU', 'BARE_METAL_CORE'] + package_types = ['BARE_METAL_CPU', + 'BARE_METAL_CORE', + 'BARE_METAL_CPU_FAST_PROVISION'] packages = ordering_manager.get_packages_of_type(package_types, mask) diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py index 51fd22f08..d110154fa 100644 --- a/SoftLayer/tests/managers/hardware_tests.py +++ b/SoftLayer/tests/managers/hardware_tests.py @@ -305,7 +305,9 @@ def test_get_available_dedicated_server_packages(self): 'operation': 'in', 'options': [{ 'name': 'data', - 'value': ['BARE_METAL_CPU', 'BARE_METAL_CORE'] + 'value': ['BARE_METAL_CPU', + 'BARE_METAL_CORE', + 'BARE_METAL_CPU_FAST_PROVISION'] }] } } From f0c05b6e36b2da585a4ec80147efdcf432c4ffed Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 20 Oct 2014 13:51:53 -0500 Subject: [PATCH 0152/2667] Use travis-ci badge with explicit branch --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 220d19e3f..95ba04264 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ SoftLayer API Python Client =========================== -.. image:: https://api.travis-ci.org/softlayer/softlayer-python.png +.. image:: https://travis-ci.org/softlayer/softlayer-python.svg?branch=master :target: https://travis-ci.org/softlayer/softlayer-python .. image:: https://landscape.io/github/softlayer/softlayer-python/master/landscape.png From 1f7b9472d4379e7002dd4b3ebd0dffb64debe01e Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 21 Oct 2014 16:50:04 -0500 Subject: [PATCH 0153/2667] Revert Fast bare metal provision until the implementation is complete --- SoftLayer/managers/hardware.py | 3 +-- SoftLayer/tests/managers/hardware_tests.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d832eeb99..caa586a8b 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -213,8 +213,7 @@ def get_available_dedicated_server_packages(self): mask = 'id,name,description,type,isActive' package_types = ['BARE_METAL_CPU', - 'BARE_METAL_CORE', - 'BARE_METAL_CPU_FAST_PROVISION'] + 'BARE_METAL_CORE'] packages = ordering_manager.get_packages_of_type(package_types, mask) diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py index d110154fa..a6be812ba 100644 --- a/SoftLayer/tests/managers/hardware_tests.py +++ b/SoftLayer/tests/managers/hardware_tests.py @@ -306,8 +306,7 @@ def test_get_available_dedicated_server_packages(self): 'options': [{ 'name': 'data', 'value': ['BARE_METAL_CPU', - 'BARE_METAL_CORE', - 'BARE_METAL_CPU_FAST_PROVISION'] + 'BARE_METAL_CORE'] }] } } From 8a78a1696891731097d55f219d53bb3d9cbf003c Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 23 Oct 2014 10:17:23 -0500 Subject: [PATCH 0154/2667] Adding .spec file for python-softlayer --- python-softlayer.spec | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 python-softlayer.spec diff --git a/python-softlayer.spec b/python-softlayer.spec new file mode 100644 index 000000000..b93faf289 --- /dev/null +++ b/python-softlayer.spec @@ -0,0 +1,47 @@ +# sitelib for noarch packages, sitearch for others (remove the unneeded one) +%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} +%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")} + +Name: python-softlayer +Version: 773ab17 +Release: 1%{?dist} +Summary: softlayer python interface + +License: Softlayer +URL: https://github.com/softlayer/softlayer-python +Source0: python-softlayer-773ab17.tar.gz + +#BuildArch: +BuildRequires: python-devel +Requires: python-requests, python-docopt = 0.6.1, python-prettytable >= 0.7.0 +Requires: python-importlib, python-six >= 1.6.1 + +%description + + +%prep +%setup -q + + +%build +# Remove CFLAGS=... for noarch packages (unneeded) +CFLAGS="$RPM_OPT_FLAGS" %{__python} setup.py build + + +%install +rm -rf $RPM_BUILD_ROOT +%{__python} setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT + + +%files +%doc +/usr/bin/sl +# For noarch packages: sitelib +%{python_sitelib}/* +# For arch-specific packages: sitearch +#%{python_sitearch}/* + + +%changelog +* Wed Mar 19 2014 Andy Bakun 773ab17-1 +- initial packaging From bff00651c28344d034fcec92e1f9fcad3a8098b2 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 23 Oct 2014 12:00:04 -0500 Subject: [PATCH 0155/2667] added github url to the spec file --- python-softlayer.spec | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/python-softlayer.spec b/python-softlayer.spec index b93faf289..e3f78b802 100644 --- a/python-softlayer.spec +++ b/python-softlayer.spec @@ -1,18 +1,20 @@ # sitelib for noarch packages, sitearch for others (remove the unneeded one) %{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} %{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")} +%global commit master +%global shortcommit %(c=%{commit}; echo ${c:0:7}) -Name: python-softlayer -Version: 773ab17 +Name: softlayer-python +Version: %{commit} Release: 1%{?dist} Summary: softlayer python interface License: Softlayer URL: https://github.com/softlayer/softlayer-python -Source0: python-softlayer-773ab17.tar.gz +Source: https://github.com/softlayer/softlayer-python/archive/%{commit}/softlayer-python-%{commit}.tar.gz #BuildArch: -BuildRequires: python-devel +BuildRequires: python-devel, python-setuptools Requires: python-requests, python-docopt = 0.6.1, python-prettytable >= 0.7.0 Requires: python-importlib, python-six >= 1.6.1 @@ -43,5 +45,9 @@ rm -rf $RPM_BUILD_ROOT %changelog +* Thu Oct 23 2014 Christopher Gallo - master-2 +- Changed Source to a proper github url, added python-setuptool build + requirement + * Wed Mar 19 2014 Andy Bakun 773ab17-1 - initial packaging From 585c8ca9b8ec20918ded43380db4b69ab4034cb6 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 23 Oct 2014 12:59:32 -0500 Subject: [PATCH 0156/2667] renamed the spec file --- python-softlayer.spec => softlayer-python.spec | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename python-softlayer.spec => softlayer-python.spec (100%) diff --git a/python-softlayer.spec b/softlayer-python.spec similarity index 100% rename from python-softlayer.spec rename to softlayer-python.spec From 656305b24a4aee209a9a061ce011dde7e1b81a66 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 23 Oct 2014 14:26:05 -0500 Subject: [PATCH 0157/2667] Update CHANGELOG --- CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 341db6be2..826acdc75 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,11 +4,13 @@ * CLI: More detail added to the `sl image detail` and `sl image list` commands + * CLI: Adds command to import DNS entries from BIND zone files + * CLI+API: Adds support for booting into rescue images for virtual servers and hardware * API: Adds ability to order virtual and hardare servers from a quote to the ordering manager - * CLI: Fixes bug with sl server list-chassis and sl server list-chassis + * CLI: Fixes bug with `sl server list-chassis` and `sl server list-chassis` * API: Restructure of the way custom authentication can be plugged in the API client From 3eafa595e7615200a2e5a3b0dd0bdfebcbe7f499 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 7 Oct 2014 08:46:19 -0500 Subject: [PATCH 0158/2667] Conversion to click This includes a mess of changes: * stricter option parsing (ex: global options are specified directly after sl) * no prefix-based option matching that doc-opt did * entrypoint-based loading * Several CLI cleanups: ** Remove help command ** Renames dns subcommands to include zone- and record- ** Renames --hourly and --billing for `sl vs create` and `sl hardware create` to --billing=hourly or --billing=monthly. ** Many other subtle changes --- SoftLayer/CLI/cdn/__init__.py | 2 + SoftLayer/CLI/cdn/detail.py | 32 + SoftLayer/CLI/cdn/list.py | 43 + SoftLayer/CLI/cdn/load.py | 18 + SoftLayer/CLI/cdn/origin_add.py | 24 + SoftLayer/CLI/cdn/origin_list.py | 28 + SoftLayer/CLI/cdn/origin_remove.py | 18 + SoftLayer/CLI/cdn/purge.py | 18 + SoftLayer/CLI/config/__init__.py | 36 + SoftLayer/CLI/config/setup.py | 142 ++ SoftLayer/CLI/config/show.py | 16 + SoftLayer/CLI/core.py | 425 +++--- SoftLayer/CLI/dns/__init__.py | 2 + SoftLayer/CLI/dns/record_add.py | 27 + SoftLayer/CLI/dns/record_edit.py | 28 + SoftLayer/CLI/dns/record_list.py | 49 + SoftLayer/CLI/dns/record_remove.py | 23 + SoftLayer/CLI/dns/zone_create.py | 17 + SoftLayer/CLI/dns/zone_delete.py | 25 + SoftLayer/CLI/dns/zone_import.py | 117 ++ SoftLayer/CLI/dns/zone_list.py | 30 + SoftLayer/CLI/dns/zone_print.py | 19 + SoftLayer/CLI/environment.py | 148 ++- SoftLayer/CLI/exceptions.py | 22 +- SoftLayer/CLI/firewall/__init__.py | 18 + SoftLayer/CLI/firewall/add.py | 54 + SoftLayer/CLI/firewall/cancel.py | 31 + SoftLayer/CLI/firewall/detail.py | 49 + SoftLayer/CLI/firewall/edit.py | 180 +++ SoftLayer/CLI/firewall/list.py | 85 ++ SoftLayer/CLI/globalip/__init__.py | 2 + SoftLayer/CLI/globalip/assign.py | 21 + SoftLayer/CLI/globalip/cancel.py | 26 + SoftLayer/CLI/globalip/create.py | 42 + SoftLayer/CLI/globalip/list.py | 50 + SoftLayer/CLI/globalip/unassign.py | 20 + SoftLayer/CLI/helpers.py | 10 - SoftLayer/CLI/image/__init__.py | 11 + SoftLayer/CLI/image/delete.py | 20 + SoftLayer/CLI/image/detail.py | 59 + SoftLayer/CLI/image/edit.py | 31 + SoftLayer/CLI/image/list.py | 55 + SoftLayer/CLI/iscsi/__init__.py | 1 + SoftLayer/CLI/iscsi/cancel.py | 30 + SoftLayer/CLI/iscsi/create.py | 23 + SoftLayer/CLI/iscsi/detail.py | 54 + SoftLayer/CLI/iscsi/list.py | 40 + SoftLayer/CLI/loadbal/__init__.py | 12 + SoftLayer/CLI/loadbal/cancel.py | 29 + SoftLayer/CLI/loadbal/create.py | 25 + SoftLayer/CLI/loadbal/create_options.py | 35 + SoftLayer/CLI/loadbal/detail.py | 85 ++ SoftLayer/CLI/loadbal/group_add.py | 41 + SoftLayer/CLI/loadbal/group_delete.py | 28 + SoftLayer/CLI/loadbal/group_edit.py | 41 + SoftLayer/CLI/loadbal/group_reset.py | 21 + SoftLayer/CLI/loadbal/health_checks.py | 26 + SoftLayer/CLI/loadbal/list.py | 49 + SoftLayer/CLI/loadbal/routing_methods.py | 25 + SoftLayer/CLI/loadbal/routing_types.py | 24 + SoftLayer/CLI/loadbal/service_add.py | 52 + SoftLayer/CLI/loadbal/service_delete.py | 28 + SoftLayer/CLI/loadbal/service_edit.py | 49 + SoftLayer/CLI/loadbal/service_toggle.py | 28 + SoftLayer/CLI/metadata.py | 67 + SoftLayer/CLI/modules/__init__.py | 15 - SoftLayer/CLI/modules/cdn.py | 188 --- SoftLayer/CLI/modules/config.py | 199 --- SoftLayer/CLI/modules/dns.py | 369 ------ SoftLayer/CLI/modules/filters.py | 32 - SoftLayer/CLI/modules/firewall.py | 424 ------ SoftLayer/CLI/modules/globalip.py | 170 --- SoftLayer/CLI/modules/help.py | 31 - SoftLayer/CLI/modules/image.py | 175 --- SoftLayer/CLI/modules/iscsi.py | 189 --- SoftLayer/CLI/modules/loadbal.py | 591 --------- SoftLayer/CLI/modules/messaging.py | 522 -------- SoftLayer/CLI/modules/metadata.py | 243 ---- SoftLayer/CLI/modules/nas.py | 50 - SoftLayer/CLI/modules/rwhois.py | 98 -- SoftLayer/CLI/modules/server.py | 1107 ---------------- SoftLayer/CLI/modules/snapshot.py | 152 --- SoftLayer/CLI/modules/sshkey.py | 164 --- SoftLayer/CLI/modules/ssl.py | 171 --- SoftLayer/CLI/modules/subnet.py | 283 ---- SoftLayer/CLI/modules/summary.py | 43 - SoftLayer/CLI/modules/ticket.py | 277 ---- SoftLayer/CLI/modules/vlan.py | 141 -- SoftLayer/CLI/modules/vs.py | 1161 ----------------- SoftLayer/CLI/mq/__init__.py | 57 + SoftLayer/CLI/mq/accounts_list.py | 28 + SoftLayer/CLI/mq/endpoints_list.py | 27 + SoftLayer/CLI/mq/ping.py | 25 + SoftLayer/CLI/mq/queue_add.py | 46 + SoftLayer/CLI/mq/queue_detail.py | 26 + SoftLayer/CLI/mq/queue_edit.py | 46 + SoftLayer/CLI/mq/queue_list.py | 34 + SoftLayer/CLI/mq/queue_pop.py | 41 + SoftLayer/CLI/mq/queue_push.py | 32 + SoftLayer/CLI/mq/queue_remove.py | 30 + SoftLayer/CLI/mq/topic_add.py | 47 + SoftLayer/CLI/mq/topic_detail.py | 30 + SoftLayer/CLI/mq/topic_list.py | 29 + SoftLayer/CLI/mq/topic_push.py | 34 + SoftLayer/CLI/mq/topic_remove.py | 25 + SoftLayer/CLI/mq/topic_subscribe.py | 46 + SoftLayer/CLI/mq/topic_unsubscribe.py | 25 + SoftLayer/CLI/nas/__init__.py | 1 + SoftLayer/CLI/nas/list.py | 39 + SoftLayer/CLI/routes.py | 182 +++ SoftLayer/CLI/rwhois/__init__.py | 1 + SoftLayer/CLI/rwhois/edit.py | 55 + SoftLayer/CLI/rwhois/show.py | 34 + SoftLayer/CLI/server/__init__.py | 209 +++ SoftLayer/CLI/server/cancel.py | 38 + SoftLayer/CLI/server/cancel_reasons.py | 25 + SoftLayer/CLI/server/create.py | 295 +++++ SoftLayer/CLI/server/create_options.py | 106 ++ SoftLayer/CLI/server/detail.py | 111 ++ SoftLayer/CLI/server/edit.py | 43 + SoftLayer/CLI/server/list.py | 78 ++ SoftLayer/CLI/server/list_chassis.py | 26 + SoftLayer/CLI/server/nic_edit.py | 24 + SoftLayer/CLI/server/power.py | 79 ++ SoftLayer/CLI/server/reload.py | 37 + SoftLayer/CLI/server/rescue.py | 28 + SoftLayer/CLI/snapshot/__init__.py | 1 + SoftLayer/CLI/snapshot/cancel.py | 22 + SoftLayer/CLI/snapshot/create.py | 21 + SoftLayer/CLI/snapshot/create_space.py | 23 + SoftLayer/CLI/snapshot/list.py | 37 + SoftLayer/CLI/snapshot/restore_volume.py | 23 + SoftLayer/CLI/sshkey/__init__.py | 1 + SoftLayer/CLI/sshkey/add.py | 32 + SoftLayer/CLI/sshkey/edit.py | 25 + SoftLayer/CLI/sshkey/list.py | 34 + SoftLayer/CLI/sshkey/print.py | 36 + SoftLayer/CLI/sshkey/remove.py | 24 + SoftLayer/CLI/ssl/__init__.py | 1 + SoftLayer/CLI/ssl/add.py | 42 + SoftLayer/CLI/ssl/download.py | 34 + SoftLayer/CLI/ssl/edit.py | 39 + SoftLayer/CLI/ssl/list.py | 41 + SoftLayer/CLI/ssl/remove.py | 21 + SoftLayer/CLI/subnet/__init__.py | 1 + SoftLayer/CLI/subnet/cancel.py | 26 + SoftLayer/CLI/subnet/create.py | 64 + SoftLayer/CLI/subnet/detail.py | 71 + SoftLayer/CLI/subnet/list.py | 67 + SoftLayer/CLI/subnet/lookup.py | 60 + SoftLayer/CLI/summary.py | 45 + SoftLayer/CLI/template.py | 29 +- SoftLayer/CLI/ticket/__init__.py | 43 + SoftLayer/CLI/ticket/create.py | 29 + SoftLayer/CLI/ticket/detail.py | 22 + SoftLayer/CLI/ticket/list.py | 39 + SoftLayer/CLI/ticket/subjects.py | 22 + SoftLayer/CLI/ticket/summary.py | 33 + SoftLayer/CLI/ticket/update.py | 26 + SoftLayer/CLI/virt/__init__.py | 2 + SoftLayer/CLI/virt/cancel.py | 25 + SoftLayer/CLI/virt/capture.py | 37 + SoftLayer/CLI/virt/create.py | 282 ++++ SoftLayer/CLI/virt/create_options.py | 112 ++ SoftLayer/CLI/virt/detail.py | 112 ++ SoftLayer/CLI/virt/dns.py | 103 ++ SoftLayer/CLI/virt/edit.py | 51 + SoftLayer/CLI/virt/list.py | 75 ++ SoftLayer/CLI/virt/network.py | 27 + SoftLayer/CLI/virt/power.py | 85 ++ SoftLayer/CLI/virt/ready.py | 25 + SoftLayer/CLI/virt/reload.py | 34 + SoftLayer/CLI/virt/upgrade.py | 53 + SoftLayer/CLI/vlan/__init__.py | 1 + SoftLayer/CLI/vlan/detail.py | 86 ++ SoftLayer/CLI/vlan/list.py | 55 + SoftLayer/managers/cci.py | 5 +- SoftLayer/managers/cdn.py | 19 +- SoftLayer/managers/dns.py | 49 +- SoftLayer/managers/firewall.py | 28 +- SoftLayer/managers/hardware.py | 59 +- SoftLayer/managers/image.py | 19 +- SoftLayer/managers/iscsi.py | 33 +- SoftLayer/managers/load_balancer.py | 45 +- SoftLayer/managers/messaging.py | 70 +- SoftLayer/managers/metadata.py | 15 +- SoftLayer/managers/network.py | 47 +- SoftLayer/managers/ordering.py | 27 +- SoftLayer/managers/sshkey.py | 15 +- SoftLayer/managers/ssl.py | 13 +- SoftLayer/managers/ticket.py | 15 +- SoftLayer/managers/vs.py | 73 +- SoftLayer/testing/__init__.py | 26 +- SoftLayer/testing/fixture_client.py | 49 +- SoftLayer/testing/fixtures/Account.py | 35 +- SoftLayer/tests/CLI/core_tests.py | 314 ++--- SoftLayer/tests/CLI/environment_tests.py | 41 +- SoftLayer/tests/CLI/helper_tests.py | 87 +- SoftLayer/tests/CLI/modules/cdn_tests.py | 66 +- SoftLayer/tests/CLI/modules/config_tests.py | 99 +- SoftLayer/tests/CLI/modules/dns_tests.py | 172 +-- SoftLayer/tests/CLI/modules/firewall_tests.py | 34 +- SoftLayer/tests/CLI/modules/globalip_tests.py | 51 +- SoftLayer/tests/CLI/modules/help_tests.py | 23 - SoftLayer/tests/CLI/modules/import_tests.py | 20 - SoftLayer/tests/CLI/modules/nas_tests.py | 17 +- SoftLayer/tests/CLI/modules/rwhois_tests.py | 89 +- SoftLayer/tests/CLI/modules/server_tests.py | 1000 ++++++-------- SoftLayer/tests/CLI/modules/sshkey_tests.py | 100 +- SoftLayer/tests/CLI/modules/summary_tests.py | 33 +- SoftLayer/tests/CLI/modules/vs_tests.py | 142 +- SoftLayer/tests/managers/cci_tests.py | 5 +- SoftLayer/tests/managers/loadbal_tests.py | 5 - SoftLayer/tests/managers/network_tests.py | 15 +- SoftLayer/tests/transport_tests.py | 2 +- docs/api/client.rst | 2 +- docs/cli.rst | 165 ++- docs/cli/vs.rst | 205 +-- docs/dev/cli.rst | 239 +--- docs/dev/index.rst | 14 +- docs/index.rst | 8 +- setup.cfg | 4 +- setup.py | 22 +- tools/requirements.txt | 4 +- tools/test-requirements.txt | 3 +- tox.ini | 4 +- 226 files changed, 8327 insertions(+), 9219 deletions(-) create mode 100644 SoftLayer/CLI/cdn/__init__.py create mode 100644 SoftLayer/CLI/cdn/detail.py create mode 100644 SoftLayer/CLI/cdn/list.py create mode 100644 SoftLayer/CLI/cdn/load.py create mode 100644 SoftLayer/CLI/cdn/origin_add.py create mode 100644 SoftLayer/CLI/cdn/origin_list.py create mode 100644 SoftLayer/CLI/cdn/origin_remove.py create mode 100644 SoftLayer/CLI/cdn/purge.py create mode 100644 SoftLayer/CLI/config/__init__.py create mode 100644 SoftLayer/CLI/config/setup.py create mode 100644 SoftLayer/CLI/config/show.py create mode 100644 SoftLayer/CLI/dns/__init__.py create mode 100644 SoftLayer/CLI/dns/record_add.py create mode 100644 SoftLayer/CLI/dns/record_edit.py create mode 100644 SoftLayer/CLI/dns/record_list.py create mode 100644 SoftLayer/CLI/dns/record_remove.py create mode 100644 SoftLayer/CLI/dns/zone_create.py create mode 100644 SoftLayer/CLI/dns/zone_delete.py create mode 100644 SoftLayer/CLI/dns/zone_import.py create mode 100644 SoftLayer/CLI/dns/zone_list.py create mode 100644 SoftLayer/CLI/dns/zone_print.py create mode 100644 SoftLayer/CLI/firewall/__init__.py create mode 100644 SoftLayer/CLI/firewall/add.py create mode 100644 SoftLayer/CLI/firewall/cancel.py create mode 100644 SoftLayer/CLI/firewall/detail.py create mode 100644 SoftLayer/CLI/firewall/edit.py create mode 100644 SoftLayer/CLI/firewall/list.py create mode 100644 SoftLayer/CLI/globalip/__init__.py create mode 100644 SoftLayer/CLI/globalip/assign.py create mode 100644 SoftLayer/CLI/globalip/cancel.py create mode 100644 SoftLayer/CLI/globalip/create.py create mode 100644 SoftLayer/CLI/globalip/list.py create mode 100644 SoftLayer/CLI/globalip/unassign.py create mode 100644 SoftLayer/CLI/image/__init__.py create mode 100644 SoftLayer/CLI/image/delete.py create mode 100644 SoftLayer/CLI/image/detail.py create mode 100644 SoftLayer/CLI/image/edit.py create mode 100644 SoftLayer/CLI/image/list.py create mode 100644 SoftLayer/CLI/iscsi/__init__.py create mode 100644 SoftLayer/CLI/iscsi/cancel.py create mode 100644 SoftLayer/CLI/iscsi/create.py create mode 100644 SoftLayer/CLI/iscsi/detail.py create mode 100644 SoftLayer/CLI/iscsi/list.py create mode 100644 SoftLayer/CLI/loadbal/__init__.py create mode 100644 SoftLayer/CLI/loadbal/cancel.py create mode 100644 SoftLayer/CLI/loadbal/create.py create mode 100644 SoftLayer/CLI/loadbal/create_options.py create mode 100644 SoftLayer/CLI/loadbal/detail.py create mode 100644 SoftLayer/CLI/loadbal/group_add.py create mode 100644 SoftLayer/CLI/loadbal/group_delete.py create mode 100644 SoftLayer/CLI/loadbal/group_edit.py create mode 100644 SoftLayer/CLI/loadbal/group_reset.py create mode 100644 SoftLayer/CLI/loadbal/health_checks.py create mode 100644 SoftLayer/CLI/loadbal/list.py create mode 100644 SoftLayer/CLI/loadbal/routing_methods.py create mode 100644 SoftLayer/CLI/loadbal/routing_types.py create mode 100644 SoftLayer/CLI/loadbal/service_add.py create mode 100644 SoftLayer/CLI/loadbal/service_delete.py create mode 100644 SoftLayer/CLI/loadbal/service_edit.py create mode 100644 SoftLayer/CLI/loadbal/service_toggle.py create mode 100644 SoftLayer/CLI/metadata.py delete mode 100644 SoftLayer/CLI/modules/__init__.py delete mode 100644 SoftLayer/CLI/modules/cdn.py delete mode 100644 SoftLayer/CLI/modules/config.py delete mode 100755 SoftLayer/CLI/modules/dns.py delete mode 100644 SoftLayer/CLI/modules/filters.py delete mode 100755 SoftLayer/CLI/modules/firewall.py delete mode 100644 SoftLayer/CLI/modules/globalip.py delete mode 100644 SoftLayer/CLI/modules/help.py delete mode 100644 SoftLayer/CLI/modules/image.py delete mode 100644 SoftLayer/CLI/modules/iscsi.py delete mode 100755 SoftLayer/CLI/modules/loadbal.py delete mode 100644 SoftLayer/CLI/modules/messaging.py delete mode 100644 SoftLayer/CLI/modules/metadata.py delete mode 100644 SoftLayer/CLI/modules/nas.py delete mode 100644 SoftLayer/CLI/modules/rwhois.py delete mode 100644 SoftLayer/CLI/modules/server.py delete mode 100644 SoftLayer/CLI/modules/snapshot.py delete mode 100644 SoftLayer/CLI/modules/sshkey.py delete mode 100755 SoftLayer/CLI/modules/ssl.py delete mode 100644 SoftLayer/CLI/modules/subnet.py delete mode 100644 SoftLayer/CLI/modules/summary.py delete mode 100644 SoftLayer/CLI/modules/ticket.py delete mode 100644 SoftLayer/CLI/modules/vlan.py delete mode 100755 SoftLayer/CLI/modules/vs.py create mode 100644 SoftLayer/CLI/mq/__init__.py create mode 100644 SoftLayer/CLI/mq/accounts_list.py create mode 100644 SoftLayer/CLI/mq/endpoints_list.py create mode 100644 SoftLayer/CLI/mq/ping.py create mode 100644 SoftLayer/CLI/mq/queue_add.py create mode 100644 SoftLayer/CLI/mq/queue_detail.py create mode 100644 SoftLayer/CLI/mq/queue_edit.py create mode 100644 SoftLayer/CLI/mq/queue_list.py create mode 100644 SoftLayer/CLI/mq/queue_pop.py create mode 100644 SoftLayer/CLI/mq/queue_push.py create mode 100644 SoftLayer/CLI/mq/queue_remove.py create mode 100644 SoftLayer/CLI/mq/topic_add.py create mode 100644 SoftLayer/CLI/mq/topic_detail.py create mode 100644 SoftLayer/CLI/mq/topic_list.py create mode 100644 SoftLayer/CLI/mq/topic_push.py create mode 100644 SoftLayer/CLI/mq/topic_remove.py create mode 100644 SoftLayer/CLI/mq/topic_subscribe.py create mode 100644 SoftLayer/CLI/mq/topic_unsubscribe.py create mode 100644 SoftLayer/CLI/nas/__init__.py create mode 100644 SoftLayer/CLI/nas/list.py create mode 100644 SoftLayer/CLI/routes.py create mode 100644 SoftLayer/CLI/rwhois/__init__.py create mode 100644 SoftLayer/CLI/rwhois/edit.py create mode 100644 SoftLayer/CLI/rwhois/show.py create mode 100644 SoftLayer/CLI/server/__init__.py create mode 100644 SoftLayer/CLI/server/cancel.py create mode 100644 SoftLayer/CLI/server/cancel_reasons.py create mode 100644 SoftLayer/CLI/server/create.py create mode 100644 SoftLayer/CLI/server/create_options.py create mode 100644 SoftLayer/CLI/server/detail.py create mode 100644 SoftLayer/CLI/server/edit.py create mode 100644 SoftLayer/CLI/server/list.py create mode 100644 SoftLayer/CLI/server/list_chassis.py create mode 100644 SoftLayer/CLI/server/nic_edit.py create mode 100644 SoftLayer/CLI/server/power.py create mode 100644 SoftLayer/CLI/server/reload.py create mode 100644 SoftLayer/CLI/server/rescue.py create mode 100644 SoftLayer/CLI/snapshot/__init__.py create mode 100644 SoftLayer/CLI/snapshot/cancel.py create mode 100644 SoftLayer/CLI/snapshot/create.py create mode 100644 SoftLayer/CLI/snapshot/create_space.py create mode 100644 SoftLayer/CLI/snapshot/list.py create mode 100644 SoftLayer/CLI/snapshot/restore_volume.py create mode 100644 SoftLayer/CLI/sshkey/__init__.py create mode 100644 SoftLayer/CLI/sshkey/add.py create mode 100644 SoftLayer/CLI/sshkey/edit.py create mode 100644 SoftLayer/CLI/sshkey/list.py create mode 100644 SoftLayer/CLI/sshkey/print.py create mode 100644 SoftLayer/CLI/sshkey/remove.py create mode 100644 SoftLayer/CLI/ssl/__init__.py create mode 100644 SoftLayer/CLI/ssl/add.py create mode 100644 SoftLayer/CLI/ssl/download.py create mode 100644 SoftLayer/CLI/ssl/edit.py create mode 100644 SoftLayer/CLI/ssl/list.py create mode 100644 SoftLayer/CLI/ssl/remove.py create mode 100644 SoftLayer/CLI/subnet/__init__.py create mode 100644 SoftLayer/CLI/subnet/cancel.py create mode 100644 SoftLayer/CLI/subnet/create.py create mode 100644 SoftLayer/CLI/subnet/detail.py create mode 100644 SoftLayer/CLI/subnet/list.py create mode 100644 SoftLayer/CLI/subnet/lookup.py create mode 100644 SoftLayer/CLI/summary.py create mode 100644 SoftLayer/CLI/ticket/__init__.py create mode 100644 SoftLayer/CLI/ticket/create.py create mode 100644 SoftLayer/CLI/ticket/detail.py create mode 100644 SoftLayer/CLI/ticket/list.py create mode 100644 SoftLayer/CLI/ticket/subjects.py create mode 100644 SoftLayer/CLI/ticket/summary.py create mode 100644 SoftLayer/CLI/ticket/update.py create mode 100644 SoftLayer/CLI/virt/__init__.py create mode 100644 SoftLayer/CLI/virt/cancel.py create mode 100644 SoftLayer/CLI/virt/capture.py create mode 100644 SoftLayer/CLI/virt/create.py create mode 100644 SoftLayer/CLI/virt/create_options.py create mode 100644 SoftLayer/CLI/virt/detail.py create mode 100644 SoftLayer/CLI/virt/dns.py create mode 100644 SoftLayer/CLI/virt/edit.py create mode 100644 SoftLayer/CLI/virt/list.py create mode 100644 SoftLayer/CLI/virt/network.py create mode 100644 SoftLayer/CLI/virt/power.py create mode 100644 SoftLayer/CLI/virt/ready.py create mode 100644 SoftLayer/CLI/virt/reload.py create mode 100644 SoftLayer/CLI/virt/upgrade.py create mode 100644 SoftLayer/CLI/vlan/__init__.py create mode 100644 SoftLayer/CLI/vlan/detail.py create mode 100644 SoftLayer/CLI/vlan/list.py delete mode 100644 SoftLayer/tests/CLI/modules/help_tests.py delete mode 100644 SoftLayer/tests/CLI/modules/import_tests.py diff --git a/SoftLayer/CLI/cdn/__init__.py b/SoftLayer/CLI/cdn/__init__.py new file mode 100644 index 000000000..678a299ea --- /dev/null +++ b/SoftLayer/CLI/cdn/__init__.py @@ -0,0 +1,2 @@ +"""Content Delivery Network.""" +# :license: MIT, see LICENSE for more details. diff --git a/SoftLayer/CLI/cdn/detail.py b/SoftLayer/CLI/cdn/detail.py new file mode 100644 index 000000000..64ccc0cbb --- /dev/null +++ b/SoftLayer/CLI/cdn/detail.py @@ -0,0 +1,32 @@ +"""Detail a CDN Account.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('account_id') +@environment.pass_env +def cli(env, account_id): + """Detail a CDN Account.""" + + manager = SoftLayer.CDNManager(env.client) + account = manager.get_account(account_id) + + table = formatting.KeyValueTable(['Name', 'Value']) + table.align['Name'] = 'r' + table.align['Value'] = 'l' + + table.add_row(['id', account['id']]) + table.add_row(['account_name', account['cdnAccountName']]) + table.add_row(['type', account['cdnSolutionName']]) + table.add_row(['status', account['status']['name']]) + table.add_row(['created', account['createDate']]) + table.add_row(['notes', + account.get('cdnAccountNote', formatting.blank())]) + + return table diff --git a/SoftLayer/CLI/cdn/list.py b/SoftLayer/CLI/cdn/list.py new file mode 100644 index 000000000..9cf1220bf --- /dev/null +++ b/SoftLayer/CLI/cdn/list.py @@ -0,0 +1,43 @@ +"""List CDN Accounts.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.option('--sortby', + help='Column to sort by', + type=click.Choice(['id', + 'datacenter', + 'host', + 'cores', + 'memory', + 'primary_ip', + 'backend_ip'])) +@environment.pass_env +def cli(env, sortby): + """List all CDN accounts.""" + + manager = SoftLayer.CDNManager(env.client) + accounts = manager.list_accounts() + + table = formatting.Table(['id', + 'account_name', + 'type', + 'created', + 'notes']) + for account in accounts: + table.add_row([ + account['id'], + account['cdnAccountName'], + account['cdnSolutionName'], + account['createDate'], + account.get('cdnAccountNote', formatting.blank()) + ]) + + table.sortby = sortby + return table diff --git a/SoftLayer/CLI/cdn/load.py b/SoftLayer/CLI/cdn/load.py new file mode 100644 index 000000000..5dc53ca2b --- /dev/null +++ b/SoftLayer/CLI/cdn/load.py @@ -0,0 +1,18 @@ +"""Cache one or more files on all edge nodes.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click + + +@click.command() +@click.argument('account_id') +@click.argument('content_url', nargs=-1) +@environment.pass_env +def cli(env, account_id, content_url): + """Cache one or more files on all edge nodes.""" + + manager = SoftLayer.CDNManager(env.client) + manager.load_content(account_id, content_url) diff --git a/SoftLayer/CLI/cdn/origin_add.py b/SoftLayer/CLI/cdn/origin_add.py new file mode 100644 index 000000000..c8f5635b5 --- /dev/null +++ b/SoftLayer/CLI/cdn/origin_add.py @@ -0,0 +1,24 @@ +"""Create an origin pull mapping.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click +# pylint: disable=redefined-builtin + + +@click.command() +@click.argument('account_id') +@click.argument('content_url') +@click.option('--type', + help='The media type for this mapping (http, flash, wm, ...)', + default='http') +@click.option('--cname', + help='An optional CNAME to attach to the mapping') +@environment.pass_env +def cli(env, account_id, content_url, type, cname): + """Create an origin pull mapping.""" + + manager = SoftLayer.CDNManager(env.client) + manager.add_origin(account_id, type, content_url, cname) diff --git a/SoftLayer/CLI/cdn/origin_list.py b/SoftLayer/CLI/cdn/origin_list.py new file mode 100644 index 000000000..3a7b7c841 --- /dev/null +++ b/SoftLayer/CLI/cdn/origin_list.py @@ -0,0 +1,28 @@ +"""List origin pull mappings.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('account_id') +@environment.pass_env +def cli(env, account_id): + """List origin pull mappings.""" + + manager = SoftLayer.CDNManager(env.client) + origins = manager.get_origins(account_id) + + table = formatting.Table(['id', 'media_type', 'cname', 'origin_url']) + + for origin in origins: + table.add_row([origin['id'], + origin['mediaType'], + origin.get('cname', formatting.blank()), + origin['originUrl']]) + + return table diff --git a/SoftLayer/CLI/cdn/origin_remove.py b/SoftLayer/CLI/cdn/origin_remove.py new file mode 100644 index 000000000..055a93bb2 --- /dev/null +++ b/SoftLayer/CLI/cdn/origin_remove.py @@ -0,0 +1,18 @@ +"""Remove an origin pull mapping.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click + + +@click.command() +@click.argument('account_id') +@click.argument('origin_id') +@environment.pass_env +def cli(env, account_id, origin_id): + """Remove an origin pull mapping.""" + + manager = SoftLayer.CDNManager(env.client) + manager.remove_origin(account_id, origin_id) diff --git a/SoftLayer/CLI/cdn/purge.py b/SoftLayer/CLI/cdn/purge.py new file mode 100644 index 000000000..09d0810ae --- /dev/null +++ b/SoftLayer/CLI/cdn/purge.py @@ -0,0 +1,18 @@ +"""Purge cached files from all edge nodes.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click + + +@click.command() +@click.argument('account_id') +@click.argument('content_url', nargs=-1) +@environment.pass_env +def cli(env, account_id, content_url): + """Purge cached files from all edge nodes.""" + + manager = SoftLayer.CDNManager(env.client) + manager.purge_content(account_id, content_url) diff --git a/SoftLayer/CLI/config/__init__.py b/SoftLayer/CLI/config/__init__.py new file mode 100644 index 000000000..8a66b089a --- /dev/null +++ b/SoftLayer/CLI/config/__init__.py @@ -0,0 +1,36 @@ +"""CLI configuration.""" +# :license: MIT, see LICENSE for more details. + +from SoftLayer.CLI import formatting + + +def get_settings_from_client(client): + """Pull out settings from a SoftLayer.Client instance. + + :param client: SoftLayer.Client instance + """ + settings = { + 'username': '', + 'api_key': '', + 'timeout': client.timeout or '', + 'endpoint_url': client.endpoint_url, + } + try: + settings['username'] = client.auth.username + settings['api_key'] = client.auth.api_key + except AttributeError: + pass + + return settings + + +def config_table(settings): + """Returns a config table.""" + table = formatting.KeyValueTable(['Name', 'Value']) + table.align['Name'] = 'r' + table.align['Value'] = 'l' + table.add_row(['Username', settings['username'] or 'not set']) + table.add_row(['API Key', settings['api_key'] or 'not set']) + table.add_row(['Endpoint URL', settings['endpoint_url'] or 'not set']) + table.add_row(['Timeout', settings['timeout'] or 'not set']) + return table diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py new file mode 100644 index 000000000..5b8fa7306 --- /dev/null +++ b/SoftLayer/CLI/config/setup.py @@ -0,0 +1,142 @@ +"""Setup CLI configuration.""" +# :license: MIT, see LICENSE for more details. +import os.path + +import SoftLayer +from SoftLayer import auth +from SoftLayer.CLI import config +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer import utils + +import click + + +def get_api_key(client, username, secret, endpoint_url=None): + """Attempts API-Key and password auth to get an API key. + + This will also generate an API key if one doesn't exist + """ + + client.endpoint_url = endpoint_url + client.auth = None + # Try to use a client with username/api key + if len(secret) == 64: + try: + client.auth = auth.BasicAuthentication(username, secret) + client['Account'].getCurrentUser() + return secret + except SoftLayer.SoftLayerAPIError as ex: + if 'invalid api token' not in ex.faultString.lower(): + raise + else: + # Try to use a client with username/password + client.authenticate_with_password(username, secret) + + user_record = client['Account'].getCurrentUser( + mask='id, apiAuthenticationKeys') + api_keys = user_record['apiAuthenticationKeys'] + if len(api_keys) == 0: + return client['User_Customer'].addApiAuthenticationKey( + id=user_record['id']) + return api_keys[0]['authenticationKey'] + + +@click.command() +@environment.pass_env +def cli(env): + """Edit configuration.""" + + username, secret, endpoint_url, timeout = get_user_input(env) + + api_key = get_api_key(env.client, username, secret, + endpoint_url=endpoint_url) + + path = '~/.softlayer' + if env.config_file: + path = env.config_file + config_path = os.path.expanduser(path) + + env.out(env.fmt(config.config_table({'username': username, + 'api_key': api_key, + 'endpoint_url': endpoint_url, + 'timeout': timeout}))) + + if not formatting.confirm('Are you sure you want to write settings ' + 'to "%s"?' % config_path, default=True): + raise exceptions.CLIAbort('Aborted.') + + # Persist the config file. Read the target config file in before + # setting the values to avoid clobbering settings + parsed_config = utils.configparser.RawConfigParser() + parsed_config.read(config_path) + try: + parsed_config.add_section('softlayer') + except utils.configparser.DuplicateSectionError: + pass + + parsed_config.set('softlayer', 'username', username) + parsed_config.set('softlayer', 'api_key', api_key) + parsed_config.set('softlayer', 'endpoint_url', endpoint_url) + + config_fd = os.fdopen(os.open(config_path, + (os.O_WRONLY | os.O_CREAT | os.O_TRUNC), + 0o600), + 'w') + try: + parsed_config.write(config_fd) + finally: + config_fd.close() + + return "Configuration Updated Successfully" + + +def get_user_input(env): + """Ask for username, secret (api_key or password) and endpoint_url.""" + + defaults = config.get_settings_from_client(env.client.real_client) + timeout = defaults['timeout'] + + # Ask for username + for _ in range(3): + username = (env.input('Username [%s]: ' % defaults['username']) + or defaults['username']) + if username: + break + else: + raise exceptions.CLIAbort('Aborted after 3 attempts') + + # Ask for 'secret' which can be api_key or their password + for _ in range(3): + secret = (env.getpass('API Key or Password [%s]: ' + % defaults['api_key']) + or defaults['api_key']) + if secret: + break + else: + raise exceptions.CLIAbort('Aborted after 3 attempts') + + # Ask for which endpoint they want to use + for _ in range(3): + endpoint_type = env.input( + 'Endpoint (public|private|custom): ') + endpoint_type = endpoint_type.lower() + if not endpoint_type: + endpoint_url = SoftLayer.API_PUBLIC_ENDPOINT + break + if endpoint_type == 'public': + endpoint_url = SoftLayer.API_PUBLIC_ENDPOINT + break + elif endpoint_type == 'private': + endpoint_url = SoftLayer.API_PRIVATE_ENDPOINT + break + elif endpoint_type == 'custom': + endpoint_url = env.input( + 'Endpoint URL [%s]: ' % defaults['endpoint_url'] + ) or defaults['endpoint_url'] + break + else: + raise exceptions.CLIAbort('Aborted after 3 attempts') + + return username, secret, endpoint_url, timeout diff --git a/SoftLayer/CLI/config/show.py b/SoftLayer/CLI/config/show.py new file mode 100644 index 000000000..a3f7119af --- /dev/null +++ b/SoftLayer/CLI/config/show.py @@ -0,0 +1,16 @@ +"""Show current CLI configuration.""" +# :license: MIT, see LICENSE for more details. + +from SoftLayer.CLI import config +from SoftLayer.CLI import environment + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """Show current configuration.""" + + settings = config.get_settings_from_client(env.client.real_client) + return config.config_table(settings) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index c47b24a54..2f4842e54 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -1,262 +1,233 @@ """ -usage: sl [...] - sl help - sl help - sl [-h | --help] - -SoftLayer Command-line Client {version} - -The available modules are: - -Compute: - image Manages compute and flex images - metadata Get details about this machine. Also available with 'my' and 'meta' - server Bare metal servers - sshkey Manage SSH keys on your account - vs Virtual Servers (formerly CCIs) - -Networking: - cdn Content Delivery Network service management - dns Domain Name System - firewall Firewall rule and security management - loadbal Load Balancer management - globalip Global IP address management - messaging Message Queue Service - rwhois RWhoIs operations - ssl Manages SSL - subnet Subnet ordering and management - vlan Manage VLANs on your account - -Storage: - iscsi View iSCSI details - nas View NAS details - snapshot iSCSI snapshots - -General: - config View and edit configuration for this tool - ticket Manage account tickets - summary Display an overall summary of your account - help Show help - -See 'sl help ' for more information on a specific module. - -To use most commands your SoftLayer username and api_key need to be configured. -The easiest way to do that is to use: 'sl config setup' -""" -# :license: MIT, see LICENSE for more details. - -# pylint: disable=W0703 + SoftLayer.core + ~~~~~~~~~~~~~~ + Core for the SoftLayer CLI + :license: MIT, see LICENSE for more details. +""" +from __future__ import print_function import logging import sys - -import docopt +import time +import types import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting -from SoftLayer import consts +import click +# pylint: disable=too-many-public-methods, broad-except, unused-argument +# pylint: disable=redefined-builtin, super-init-not-called DEBUG_LOGGING_MAP = { - '0': logging.CRITICAL, - '1': logging.WARNING, - '2': logging.INFO, - '3': logging.DEBUG + 0: logging.CRITICAL, + 1: logging.WARNING, + 2: logging.INFO, + 3: logging.DEBUG } -VALID_FORMATS = ['raw', 'table', 'json'] +VALID_FORMATS = ['table', 'raw', 'json'] +DEFAULT_FORMAT = 'raw' +if sys.stdout.isatty(): + DEFAULT_FORMAT = 'table' + + +class CommandLoader(click.MultiCommand): + """Loads commands for click.""" + def __init__(self, module=None, **attrs): + click.MultiCommand.__init__(self, **attrs) + self.module = module + + def list_commands(self, ctx): + """Get module for click.""" + env = ctx.ensure_object(environment.Environment) + return env.command_list(self.module) + + def get_command(self, ctx, name): + """Get command for click.""" + env = ctx.ensure_object(environment.Environment) + command = env.get_command(self.module, name) + return command + +class ModuleLoader(click.MultiCommand): + """Loads module for click.""" -def _append_common_options(arg_doc): - """Append common options to the doc string""" - default_format = 'raw' - if sys.stdout.isatty(): - default_format = 'table' + def list_commands(self, ctx): + """Get module for click.""" + env = ctx.ensure_object(environment.Environment) + return sorted(env.module_list()) - arg_doc += """ -Standard Options: - --format=ARG Output format. [Options: table, raw] [Default: %s] - -C FILE --config=FILE Config file location. [Default: ~/.softlayer] - --debug=LEVEL Specifies the debug noise level - 1=warn, 2=info, 3=debug - --timings Time each API call and display after results - --proxy=PROTO:PROXY_URL HTTP[s] proxy to be use to make API calls - -h --help Show this screen -""" % default_format - return arg_doc + def get_command(self, ctx, name): + """Get command for click.""" + env = ctx.ensure_object(environment.Environment) + # Do alias lookup + module_name = env.get_module_name(name) -class CommandParser(object): - """Helper class to parse commands. + module = env.get_module(module_name) + if isinstance(module, types.ModuleType): + return CommandLoader(module=module_name, help=module.__doc__) + else: + return module + + +class CliClient(SoftLayer.Client): + """A wrapped SoftLayer.Client that adds CLI-specific functionality. + + At the moment, it has a slightly different accounting for API call timings + but will also allow for 2FA and other similar functionality. - :param env: Environment instance """ - def __init__(self, env): - self.env = env - - def get_main_help(self): - """Get main help text.""" - main_doc = __doc__.format(version=SoftLayer.__version__) - return _append_common_options(main_doc).strip() - - def get_module_help(self, module_name): - """Get help text for a module.""" - module = self.env.load_module(module_name) - arg_doc = module.__doc__ - return _append_common_options(arg_doc).strip() - - def get_command_help(self, module_name, command_name): - """Get help text for a specific command.""" - command = self.env.get_command(module_name, command_name) - arg_doc = command.__doc__ - - if 'confirm' in command.options: - arg_doc += """ -Prompt Options: - -y, --really Confirm all prompt actions -""" - return _append_common_options(arg_doc).strip() - - def parse_main_args(self, args): - """Parse root arguments.""" - main_help = self.get_main_help() - arguments = docopt.docopt( - main_help, - version=consts.VERSION, - argv=args, - options_first=True) - arguments[''] = self.env.get_module_name(arguments['']) - return arguments - - def parse_module_args(self, module_name, args): - """Parse module arguments.""" - arg_doc = self.get_module_help(module_name) - arguments = docopt.docopt( - arg_doc, - version=consts.VERSION, - argv=[module_name] + args, - options_first=True) - return arguments - - def parse_command_args(self, module_name, command_name, args): - """Parse command arguments.""" - command = self.env.get_command(module_name, command_name) - arg_doc = self.get_command_help(module_name, command_name) - arguments = docopt.docopt(arg_doc, - version=consts.VERSION, - argv=[module_name] + args) - return command, arguments - - def parse(self, args): - """Parse entire tree of arguments.""" - # handle `sl ...` - main_args = self.parse_main_args(args) - module_name = main_args[''] - - # handle `sl ...` - module_args = self.parse_module_args(module_name, main_args['']) - - # get the command argument - command_name = module_args.get('') - - # handle `sl ...` - return self.parse_command_args( - module_name, - command_name, - main_args['']) - - -def main(args=sys.argv[1:], env=environment.Environment()): - """Entry point for the command-line client.""" - # Parse Top-Level Arguments + def __init__(self, client, *args, **kwargs): + self.real_client = client + self.last_calls = [] + # NOTE(kmcdonald): I really don't like this pattern. + + def call(self, service, method, *args, **kwargs): + """See Client.call for documentation.""" + start_time = time.time() + + result = self.real_client.call(service, method, *args, **kwargs) + + end_time = time.time() + diff = end_time - start_time + self.last_calls.append((service, method, start_time, diff)) + return result + + +@click.group(help="SoftLayer Command-line Client", + epilog="""To use most commands your SoftLayer +username and api_key need to be configured. The easiest way to do that is to +use: 'sl config setup'""", + cls=ModuleLoader) +@click.pass_context +@click.option('--format', + default=DEFAULT_FORMAT, + help="Output format", + type=click.Choice(VALID_FORMATS)) +@click.option('--config', '-C', + required=False, + default=click.get_app_dir('softlayer', + force_posix=True), + help="Config file location", + type=click.Path(resolve_path=True)) +@click.option('--debug', + required=False, + default='0', + help="Sets the debug noise level", + type=click.Choice(sorted([str(key) for key + in DEBUG_LOGGING_MAP.keys()]))) +@click.option('--verbose', '-v', + help="Sets the debug noise level", + type=click.IntRange(0, 3, clamp=True), + count=True) +@click.option('--timings', + required=False, + is_flag=True, + help="Time each API call and display after results") +@click.option('--proxy', + required=False, + help="HTTP[S] proxy to be use to make API calls") +@click.option('--really', '-y', + is_flag=True, + required=False, + help="Confirm all prompt actions") +@click.option('--fixtures', + is_flag=True, + required=False, + help="Use fixtures instead of actually making API calls") +@click.version_option(version=SoftLayer.__version__, + prog_name="SoftLayer Command-line Client") +def cli(ctx, + format='table', + config=None, + debug=0, + verbose=0, + proxy=None, + really=False, + fixtures=False, + **kwargs): + """Main click CLI entry-point.""" + + # Set logging level + debug_int = int(debug) + if debug_int: + verbose = debug_int + + if verbose: + logger = logging.getLogger() + logger.addHandler(logging.StreamHandler()) + logger.setLevel(DEBUG_LOGGING_MAP.get(verbose, logging.DEBUG)) + + # Populate environement with client and set it as the context object + env = ctx.ensure_object(environment.Environment) + env.skip_confirmations = really + env.config_file = config + env.format = format + if env.client is None: + # Environment can be passed in explicitly. This is used for testing + if fixtures: + from SoftLayer import testing + real_client = testing.FixtureClient() + else: + # Create SL Client + real_client = SoftLayer.Client(proxy=proxy, config_file=config) + + client = CliClient(real_client) + env.client = client + + +@cli.resultcallback() +@click.pass_context +def output_result(ctx, result, timings=False, **kwargs): + """Outputs the results returned by the CLI and also outputs timings.""" + + env = ctx.ensure_object(environment.Environment) + output = env.fmt(result) + if output: + env.out(output) + + if timings: + timing_table = formatting.Table(['service', 'method', 'time']) + + for service, call, _, duration in env.client.last_calls: + timing_table.add_row([service, call, duration]) + + env.err(env.fmt(timing_table)) + + +def main(): + """Main program. Catches several common errors and displays them nicely.""" exit_status = 0 - resolver = CommandParser(env) try: - command, command_args = resolver.parse(args) - - # Set logging level - debug_level = command_args.get('--debug') - if debug_level: - logger = logging.getLogger() - handler = logging.StreamHandler() - logger.addHandler(handler) - logger.setLevel(DEBUG_LOGGING_MAP.get(debug_level, logging.DEBUG)) - - kwargs = { - 'proxy': command_args.get('--proxy'), - 'config_file': command_args.get('--config') - } - if command_args.get('--timings'): - client = SoftLayer.TimedClient(**kwargs) - else: - client = SoftLayer.Client(**kwargs) - - # Do the thing - runnable = command(client=client, env=env) - data = runnable.execute(command_args) - if data: - out_format = command_args.get('--format', 'table') - if out_format not in VALID_FORMATS: - raise exceptions.ArgumentError('Invalid format "%s"' - % out_format) - output = formatting.format_output(data, fmt=out_format) - if output: - env.out(output) - - if command_args.get('--timings'): - out_format = command_args.get('--format', 'table') - api_calls = client.get_last_calls() - timing_table = formatting.KeyValueTable(['call', 'time']) - - for call, _, duration in api_calls: - timing_table.add_row([call, duration]) - - env.err(formatting.format_output(timing_table, fmt=out_format)) - - except exceptions.InvalidCommand as ex: - env.err(resolver.get_module_help(ex.module_name)) - if ex.command_name: - env.err('') - env.err(str(ex)) - exit_status = 1 - except exceptions.InvalidModule as ex: - env.err(resolver.get_main_help()) - if ex.module_name: - env.err('') - env.err(str(ex)) - exit_status = 1 - except docopt.DocoptExit as ex: - env.err(ex.usage) - env.err( - '\nUnknown argument(s), use -h or --help for available options') - exit_status = 127 - except KeyboardInterrupt: - env.out('') - exit_status = 1 - except exceptions.CLIAbort as ex: - env.err(str(ex.message)) - exit_status = ex.code - except SystemExit as ex: - exit_status = ex.code + cli.main() except SoftLayer.SoftLayerAPIError as ex: if 'invalid api token' in ex.faultString.lower(): - env.out("Authentication Failed: To update your credentials, use " - "'sl config setup'") + print("Authentication Failed: To update your credentials," + " use 'sl config setup'") + exit_status = 1 else: - env.err(str(ex)) + print(str(ex)) exit_status = 1 except SoftLayer.SoftLayerError as ex: - env.err(str(ex)) + print(str(ex)) exit_status = 1 + except exceptions.CLIAbort as ex: + print(str(ex.message)) + exit_status = ex.code except Exception: import traceback - env.err("An unexpected error has occured:") - env.err(traceback.format_exc()) - env.err("Feel free to report this error as it is likely a bug:") - env.err(" https://github.com/softlayer/softlayer-python/issues") + print("An unexpected error has occured:") + print(str(traceback.format_exc())) + print("Feel free to report this error as it is likely a bug:") + print(" https://github.com/softlayer/softlayer-python/issues") exit_status = 1 sys.exit(exit_status) + + +if __name__ == '__main__': + main() diff --git a/SoftLayer/CLI/dns/__init__.py b/SoftLayer/CLI/dns/__init__.py new file mode 100644 index 000000000..85833186f --- /dev/null +++ b/SoftLayer/CLI/dns/__init__.py @@ -0,0 +1,2 @@ +"""Domain Name System.""" +# :license: MIT, see LICENSE for more details. diff --git a/SoftLayer/CLI/dns/record_add.py b/SoftLayer/CLI/dns/record_add.py new file mode 100644 index 000000000..856c4be53 --- /dev/null +++ b/SoftLayer/CLI/dns/record_add.py @@ -0,0 +1,27 @@ +"""Add resource record.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + +import click +# pylint: disable=redefined-builtin + + +@click.command() +@click.argument('zone') +@click.argument('record') +@click.argument('type') +@click.argument('data') +@click.option('--ttl', + type=click.INT, + default=7200, + help='TTL value in seconds, such as 86400') +@environment.pass_env +def cli(env, zone, record, type, data, ttl): + """Add resource record.""" + + manager = SoftLayer.DNSManager(env.client) + zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') + manager.create_record(zone_id, record, type, data, ttl=ttl) diff --git a/SoftLayer/CLI/dns/record_edit.py b/SoftLayer/CLI/dns/record_edit.py new file mode 100644 index 000000000..f73e4c89f --- /dev/null +++ b/SoftLayer/CLI/dns/record_edit.py @@ -0,0 +1,28 @@ +"""Update DNS record.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click +# pylint: disable=redefined-builtin + + +@click.command() +@click.argument('record_id') +@click.option('--record', help='Host record, such as www') +@click.option('--data', help='Record data, such as an IP address') +@click.option('--ttl', + type=click.INT, + help='TTL value in seconds, such as 86400') +@click.option('--type', help='Record type, such as A or CNAME') +@environment.pass_env +def cli(env, record_id, record, data, ttl, type): + """Update DNS record.""" + manager = SoftLayer.DNSManager(env.client) + result = manager.get_record(record_id) + result['host'] = record or result['record'] + result['ttl'] = ttl or result['ttl'] + result['type'] = type or result['type'] + result['data'] = data or result['data'] + manager.edit_record(result) diff --git a/SoftLayer/CLI/dns/record_list.py b/SoftLayer/CLI/dns/record_list.py new file mode 100644 index 000000000..bf66b9f59 --- /dev/null +++ b/SoftLayer/CLI/dns/record_list.py @@ -0,0 +1,49 @@ +"""List all records in a zone.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + +import click +# pylint: disable=redefined-builtin + + +@click.command() +@click.argument('zone') +@click.option('--data', help='Record data, such as an IP address') +@click.option('--record', help='Host record, such as www') +@click.option('--ttl', + type=click.INT, + help='TTL value in seconds, such as 86400') +@click.option('--type', help='Record type, such as A or CNAME') +@environment.pass_env +def cli(env, zone, data, record, ttl, type): + """List all records in a zone.""" + + manager = SoftLayer.DNSManager(env.client) + table = formatting.Table(['id', 'record', 'type', 'ttl', 'value']) + + table.align['ttl'] = 'l' + table.align['record'] = 'r' + table.align['value'] = 'l' + + zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') + + records = manager.get_records(zone_id, + record_type=type, + host=record, + ttl=ttl, + data=data) + + for record in records: + table.add_row([ + record['id'], + record['host'], + record['type'].upper(), + record['ttl'], + record['data'] + ]) + + return table diff --git a/SoftLayer/CLI/dns/record_remove.py b/SoftLayer/CLI/dns/record_remove.py new file mode 100644 index 000000000..3ef81c90b --- /dev/null +++ b/SoftLayer/CLI/dns/record_remove.py @@ -0,0 +1,23 @@ +"""Remove resource record.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('record_id') +@environment.pass_env +def cli(env, record_id): + """Add resource record.""" + + manager = SoftLayer.DNSManager(env.client) + + if env.skip_confirmations or formatting.no_going_back('yes'): + manager.delete_record(record_id) + else: + raise exceptions.CLIAbort("Aborted.") diff --git a/SoftLayer/CLI/dns/zone_create.py b/SoftLayer/CLI/dns/zone_create.py new file mode 100644 index 000000000..21b9e9289 --- /dev/null +++ b/SoftLayer/CLI/dns/zone_create.py @@ -0,0 +1,17 @@ +"""Create a zone.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click + + +@click.command() +@click.argument('zone') +@environment.pass_env +def cli(env, zone): + """Create a zone.""" + + manager = SoftLayer.DNSManager(env.client) + manager.create_zone(zone) diff --git a/SoftLayer/CLI/dns/zone_delete.py b/SoftLayer/CLI/dns/zone_delete.py new file mode 100644 index 000000000..8608043ef --- /dev/null +++ b/SoftLayer/CLI/dns/zone_delete.py @@ -0,0 +1,25 @@ +"""Delete zone.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('zone') +@environment.pass_env +def cli(env, zone): + """Delete zone.""" + + manager = SoftLayer.DNSManager(env.client) + zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') + + if env.skip_confirmations or formatting.no_going_back(zone): + manager.delete_zone(zone_id) + else: + raise exceptions.CLIAbort("Aborted.") diff --git a/SoftLayer/CLI/dns/zone_import.py b/SoftLayer/CLI/dns/zone_import.py new file mode 100644 index 000000000..505dab7d0 --- /dev/null +++ b/SoftLayer/CLI/dns/zone_import.py @@ -0,0 +1,117 @@ +"""Import zone based off a BIND zone file.""" +# :license: MIT, see LICENSE for more details. +import re + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers + +import click + +RECORD_REGEX = re.compile(r"""^((?P([\w-]+(\.)?)*|\@)?\s+ + (?P\d+)?\s+ + (?P\w+)?)?\s+ + (?P\w+)\s+ + (?P.*)""", re.X) +RECORD_FMT = "type={type}, record={record}, data={data}, ttl={ttl}" + + +@click.command() +@click.argument('zonefile', + type=click.Path(exists=True, readable=True, resolve_path=True)) +@click.option('--dry-run', is_flag=True, help="Don't actually create records") +@environment.pass_env +def cli(env, zonefile, dry_run): + """Import zone based off a BIND zone file.""" + + manager = SoftLayer.DNSManager(env.client) + with open(zonefile) as zone_f: + zone_contents = zone_f.read() + + zone, records, bad_lines = parse_zone_details(zone_contents) + + env.out("Parsed: zone=%s" % zone) + for record in records: + env.out("Parsed: %s" % RECORD_FMT.format(**record)) + + for line in bad_lines: + env.out("Unparsed: %s" % line) + + if dry_run: + return + + # Find zone id or create the zone if it doesn't exist + try: + zone_id = helpers.resolve_id(manager.resolve_ids, zone, + name='zone') + except exceptions.CLIAbort: + zone_id = manager.create_zone(zone)['id'] + env.out(click.style("Created: %s" % zone, fg='green')) + + # Attempt to create each record + for record in records: + try: + manager.create_record(zone_id, + record['record'], + record['type'], + record['data'], + record['ttl']) + + env.out(click.style("Created: %s" % RECORD_FMT.format(**record), + fg='green')) + except SoftLayer.SoftLayerAPIError as ex: + env.out(click.style("Failed: %s" % RECORD_FMT.format(**record), + fg='red')) + env.out(click.style(str(ex), fg='red')) + + env.out(click.style("Finished", fg='green')) + + +def parse_zone_details(zone_contents): + """Parses a zone file into python data-structures.""" + records = [] + bad_lines = [] + zone_lines = [line.strip() for line in zone_contents.split('\n')] + + zone_search = re.search(r'^\$ORIGIN (?P.*)\.', zone_lines[0]) + zone = zone_search.group('zone') + + for line in zone_lines[1:]: + record_search = re.search(RECORD_REGEX, line) + if record_search is None: + bad_lines.append(line) + continue + + name = record_search.group('domain') + # The API requires we send a host, although bind allows a blank + # entry. @ is the same thing as blank + if name is None: + name = "@" + + ttl = record_search.group('ttl') + # we don't do anything with the class + # domain_class = domainSearch.group('class') + record_type = record_search.group('type').upper() + data = record_search.group('data') + + # the dns class doesn't support weighted MX records yet, so we chomp + # that part out. + if record_type == "MX": + record_search = re.search(r'(?P\d+)\s+(?P.*)', data) + data = record_search.group('data') + + # This will skip the SOA record bit. And any domain that gets + # parsed oddly. + if record_type == 'IN': + bad_lines.append(line) + continue + + records.append({ + 'record': name, + 'type': record_type, + 'data': data, + 'ttl': ttl, + }) + + return zone, records, bad_lines diff --git a/SoftLayer/CLI/dns/zone_list.py b/SoftLayer/CLI/dns/zone_list.py new file mode 100644 index 000000000..8ad628af8 --- /dev/null +++ b/SoftLayer/CLI/dns/zone_list.py @@ -0,0 +1,30 @@ +"""List all zones.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List all zones.""" + + manager = SoftLayer.DNSManager(env.client) + zones = manager.list_zones() + table = formatting.Table(['id', 'zone', 'serial', 'updated']) + table.align['serial'] = 'c' + table.align['updated'] = 'c' + + for zone in zones: + table.add_row([ + zone['id'], + zone['name'], + zone['serial'], + zone['updateDate'], + ]) + + return table diff --git a/SoftLayer/CLI/dns/zone_print.py b/SoftLayer/CLI/dns/zone_print.py new file mode 100644 index 000000000..fcb2ab0c2 --- /dev/null +++ b/SoftLayer/CLI/dns/zone_print.py @@ -0,0 +1,19 @@ +"""Print zone in BIND format.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('zone') +@environment.pass_env +def cli(env, zone): + """Print zone in BIND format.""" + + manager = SoftLayer.DNSManager(env.client) + zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') + return manager.dump_zone(zone_id) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index 58a024c4c..58031e80d 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -7,22 +7,22 @@ """ import getpass import importlib -import inspect -import os -import os.path -import sys from SoftLayer.CLI import exceptions -from SoftLayer.CLI import modules +from SoftLayer.CLI import formatting +from SoftLayer.CLI import routes from SoftLayer import utils -# pylint: disable=R0201 +import click +import pkg_resources + +# pylint: disable=too-many-instance-attributes, invalid-name class Environment(object): """Provides access to the current CLI environment.""" def __init__(self): - # {'module_name': {'action': 'actionClass'}} + # {'module_name': {'action': plugin_loader}} self.plugins = {} self.aliases = { 'meta': 'metadata', @@ -35,58 +35,90 @@ def __init__(self): 'virtual': 'vs', 'lb': 'loadbal', } - self.stdout = sys.stdout - self.stderr = sys.stderr + self.client = None + self.format = 'table' + self.skip_confirmations = False + self._modules_loaded = False + self.config_file = None + + def command_list(self, module_name): + """Command listing.""" + + self._load_modules() + # Filter commands registered as None. These are the bases. + return sorted([m for m in self.plugins[module_name].keys() + if m is not None]) + + def module_list(self): + """Returns the list of modules in SoftLayer.CLI.modules.""" + self._load_modules() + return sorted(list(self.plugins.keys())) def get_command(self, module_name, command_name): """Based on the loaded modules, return a command.""" + self._load_modules() actions = self.plugins.get(module_name) or {} + if command_name in actions: - return actions[command_name] - if None in actions: - return actions[None] + return actions[command_name].load() + raise exceptions.InvalidCommand(module_name, command_name) + def get_module(self, module_name): + """Returns the module.""" + self._load_modules() + return self.get_command(module_name, None) + def get_module_name(self, module_name): """Returns the actual module name. Uses the alias mapping.""" if module_name in self.aliases: return self.aliases[module_name] return module_name - def load_module(self, module_name): # pragma: no cover - """Loads module by name.""" - try: - module = importlib.import_module('SoftLayer.CLI.modules.%s' - % module_name) - for _, obj in inspect.getmembers(module): - if inspect.isclass(obj) and issubclass(obj, CLIRunnable): - self.add_plugin(obj) - return module - except ImportError: - raise exceptions.InvalidModule(module_name) - - def add_plugin(self, cls): - """Add a CLIRunnable as a plugin to the environment.""" - command = cls.__module__.split('.')[-1] - if command not in self.plugins: - self.plugins[command] = {} - self.plugins[command][cls.action] = cls - - def plugin_list(self): - """Returns the list of modules in SoftLayer.CLI.modules.""" - return modules.get_module_list() + def _load_modules(self): + """Loads all modules.""" + if self._modules_loaded is True: + return + + self._load_modules_from_python() + self._load_modules_from_entry_points() + + self._modules_loaded = True + + def _load_modules_from_python(self): + """Load modules from the native python source.""" + for name, modpath in routes.ALL_ROUTES: + module, subcommand = _parse_name(name) + if module not in self.plugins: + self.plugins[module] = {} + + if ':' in modpath: + path, attr = modpath.split(':', 1) + else: + path, attr = modpath, None + self.plugins[module][subcommand] = ModuleLoader(path, attr=attr) + + def _load_modules_from_entry_points(self): + """Load modules from the entry_points (slower).""" + for obj in pkg_resources.iter_entry_points(group='softlayer.cli', + name=None): + + module, subcommand = _parse_name(obj.name) + if module not in self.plugins: + self.plugins[module] = {} + self.plugins[module][subcommand] = obj def out(self, output, newline=True): """Outputs a string to the console (stdout).""" - self.stdout.write(output) - if newline: - self.stdout.write(os.linesep) + click.echo(output, nl=newline) def err(self, output, newline=True): """Outputs an error string to the console (stderr).""" - self.stderr.write(output) - if newline: - self.stderr.write(os.linesep) + click.echo(output, nl=newline, err=True) + + def fmt(self, output): + """Format output based on current the environment format.""" + return formatting.format_output(output, fmt=self.format) def input(self, prompt): """Provide a command prompt.""" @@ -96,28 +128,30 @@ def getpass(self, prompt): """Provide a password prompt.""" return getpass.getpass(prompt) - def exit(self, code=0): - """Exit.""" - sys.exit(code) +class ModuleLoader(object): + """Module loader that acts a little like an EntryPoint object.""" -class CLIRunnable(object): - """This represents a descrete command or action in the CLI. + def __init__(self, import_path, attr=None): + self.import_path = import_path + self.attr = attr - CLIRunnable is intended to be subclassed. + def load(self): + """load and return the module/attribute.""" + module = importlib.import_module(self.import_path) + if self.attr: + return getattr(module, self.attr) + return module - """ - options = [] # set by subclass - action = 'not set' # set by subclass - def __init__(self, client=None, env=None): - self.client = client - self.env = env +def _parse_name(name): + """Parse command name and path from the given name.""" + if ':' in name: + module, subcommand = name.split(':', 1) + else: + module, subcommand = name, None - def execute(self, args): - """Execute the command. + return module, subcommand - This is intended to be overridden in a subclass. - """ - pass +pass_env = click.make_pass_decorator(Environment, ensure=True) diff --git a/SoftLayer/CLI/exceptions.py b/SoftLayer/CLI/exceptions.py index b1c725ef4..ea93462a4 100644 --- a/SoftLayer/CLI/exceptions.py +++ b/SoftLayer/CLI/exceptions.py @@ -15,6 +15,12 @@ def __init__(self, code=0, *args): super(CLIHalt, self).__init__(*args) self.code = code + def __str__(self): + return "" % (self.code, + getattr(self, 'message')) + + __repr__ = __str__ + class CLIAbort(CLIHalt): """Halt the execution of the command. Gives an exit code of 2.""" @@ -35,13 +41,9 @@ class InvalidCommand(SoftLayer.SoftLayerError): def __init__(self, module_name, command_name, *args): self.module_name = module_name self.command_name = command_name - error = 'Invalid command: "%s".' % self.command_name - SoftLayer.SoftLayerError.__init__(self, error, *args) - - -class InvalidModule(SoftLayer.SoftLayerError): - """Raised when trying to use a module that does not exist.""" - def __init__(self, module_name, *args): - self.module_name = module_name - error = 'Invalid module: "%s".' % self.module_name - SoftLayer.SoftLayerError.__init__(self, error, *args) + cmd_str = module_name + if command_name is not None: + cmd_str = '%s %s' % (module_name, command_name) + SoftLayer.SoftLayerError.__init__(self, + 'Invalid command: "%s"' % cmd_str, + *args) diff --git a/SoftLayer/CLI/firewall/__init__.py b/SoftLayer/CLI/firewall/__init__.py new file mode 100644 index 000000000..70b92df9f --- /dev/null +++ b/SoftLayer/CLI/firewall/__init__.py @@ -0,0 +1,18 @@ +"""Firewalls.""" +# :license: MIT, see LICENSE for more details. + +from SoftLayer.CLI import exceptions + + +def parse_id(input_id): + """Helper package to retrieve the actual IDs. + + :param input_id: the ID provided by the user + :returns: A list of valid IDs + """ + key_value = input_id.split(':') + + if len(key_value) != 2: + raise exceptions.CLIAbort( + 'Invalid ID %s: ID should be of the form xxx:yyy' % input_id) + return key_value[0], int(key_value[1]) diff --git a/SoftLayer/CLI/firewall/add.py b/SoftLayer/CLI/firewall/add.py new file mode 100644 index 000000000..5a6a7d07a --- /dev/null +++ b/SoftLayer/CLI/firewall/add.py @@ -0,0 +1,54 @@ +"""Create new firewall.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('target') +@click.option('--firewall-type', + type=click.Choice(['vs', 'vlan', 'server']), + help='Firewall type', + required=True) +@click.option('--high-availability', '--ha', + is_flag=True, + help='High available firewall option') +@environment.pass_env +def cli(env, target, firewall_type, high_availability): + """Create new firewall.""" + + mgr = SoftLayer.FirewallManager(env.client) + + if not env.skip_confirmations: + if firewall_type == 'vlan': + pkg = mgr.get_dedicated_package(ha_enabled=high_availability) + elif firewall_type == 'vs': + pkg = mgr.get_standard_package(target, is_cci=True) + elif firewall_type == 'server': + pkg = mgr.get_standard_package(target, is_cci=False) + + if not pkg: + return "Unable to add firewall - Is network public enabled?" + + env.out("******************") + env.out("Product: %s" % pkg[0]['description']) + env.out("Price: $%s monthly" % pkg[0]['prices'][0]['recurringFee']) + env.out("******************") + + if not formatting.confirm("This action will incur charges on your " + "account. Continue?"): + raise exceptions.CLIAbort('Aborted.') + + if firewall_type == 'vlan': + mgr.add_vlan_firewall(target, ha_enabled=high_availability) + elif firewall_type == 'vs': + mgr.add_standard_firewall(target, is_cci=True) + elif firewall_type == 'server': + mgr.add_standard_firewall(target, is_cci=False) + + return "Firewall is being created!" diff --git a/SoftLayer/CLI/firewall/cancel.py b/SoftLayer/CLI/firewall/cancel.py new file mode 100644 index 000000000..c15670eb8 --- /dev/null +++ b/SoftLayer/CLI/firewall/cancel.py @@ -0,0 +1,31 @@ +"""List firewalls.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import firewall +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """List firewalls.""" + + mgr = SoftLayer.FirewallManager(env.client) + firewall_type, firewall_id = firewall.parse_id(identifier) + + if any([env.skip_confirmations, + formatting.confirm("This action will cancel a firewall from your" + "account. Continue?")]): + if firewall_type in ['cci', 'server']: + mgr.cancel_firewall(firewall_id, dedicated=False) + elif firewall_type == 'vlan': + mgr.cancel_firewall(firewall_id, dedicated=True) + return 'Firewall with id %s is being cancelled!' % identifier + else: + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py new file mode 100644 index 000000000..4ba53dc62 --- /dev/null +++ b/SoftLayer/CLI/firewall/detail.py @@ -0,0 +1,49 @@ +"""Detail firewall.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import firewall +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Detail firewall.""" + + mgr = SoftLayer.FirewallManager(env.client) + + firewall_type, firewall_id = firewall.parse_id(identifier) + if firewall_type == 'vlan': + rules = mgr.get_dedicated_fwl_rules(firewall_id) + else: + rules = mgr.get_standard_fwl_rules(firewall_id) + + return get_rules_table(rules) + + +def get_rules_table(rules): + """Helper to format the rules into a table. + + :param list rules: A list containing the rules of the firewall + :returns: a formatted table of the firewall rules + """ + table = formatting.Table(['#', 'action', 'protocol', 'src_ip', 'src_mask', + 'dest', 'dest_mask']) + table.sortby = '#' + for rule in rules: + table.add_row([ + rule['orderValue'], + rule['action'], + rule['protocol'], + rule['sourceIpAddress'], + rule['sourceIpSubnetMask'], + '%s:%s-%s' % (rule['destinationIpAddress'], + rule['destinationPortRangeStart'], + rule['destinationPortRangeEnd']), + rule['destinationIpSubnetMask']]) + return table diff --git a/SoftLayer/CLI/firewall/edit.py b/SoftLayer/CLI/firewall/edit.py new file mode 100644 index 000000000..0ea61e330 --- /dev/null +++ b/SoftLayer/CLI/firewall/edit.py @@ -0,0 +1,180 @@ +"""List firewalls.""" +# :license: MIT, see LICENSE for more details. + +import os +import subprocess +import tempfile + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import firewall +from SoftLayer.CLI import formatting + +import click + +DELIMITER = "=========================================\n" + + +def parse_rules(content=None): + """Helper to parse the input from the user into a list of rules. + + :param string content: the content of the editor + :returns: a list of rules + """ + rules = content.split(DELIMITER) + parsed_rules = list() + order = 1 + for rule in rules: + if rule.strip() == '': + continue + parsed_rule = {} + lines = rule.split("\n") + parsed_rule['orderValue'] = order + order += 1 + for line in lines: + if line.strip() == '': + continue + key_value = line.strip().split(':') + key = key_value[0].strip() + value = key_value[1].strip() + if key == 'action': + parsed_rule['action'] = value + elif key == 'protocol': + parsed_rule['protocol'] = value + elif key == 'source_ip_address': + parsed_rule['sourceIpAddress'] = value + elif key == 'source_ip_subnet_mask': + parsed_rule['sourceIpSubnetMask'] = value + elif key == 'destination_ip_address': + parsed_rule['destinationIpAddress'] = value + elif key == 'destination_ip_subnet_mask': + parsed_rule['destinationIpSubnetMask'] = value + elif key == 'destination_port_range_start': + parsed_rule['destinationPortRangeStart'] = int(value) + elif key == 'destination_port_range_end': + parsed_rule['destinationPortRangeEnd'] = int(value) + elif key == 'version': + parsed_rule['version'] = int(value) + parsed_rules.append(parsed_rule) + return parsed_rules + + +def open_editor(rules=None, content=None): + """Helper to open an editor for editing the firewall rules. + + This method takes two parameters, if content is provided, + that means that submitting the rules failed and we are allowing + the user to re-edit what they provided. If content is not provided, the + rules retrieved from the firewall will be displayed to the user. + + :param list rules: A list containing the rules of the firewall + :param string content: the content that the user provided in the editor + :returns: a formatted string that get be pushed into the editor + """ + + # Let's get the default EDITOR of the environment, + # use nano if none is specified + editor = os.environ.get('EDITOR', 'nano') + + with tempfile.NamedTemporaryFile(suffix=".tmp") as tfile: + + if content: + # if content is provided, just display it as is + tfile.write(content) + tfile.flush() + subprocess.call([editor, tfile.name]) + tfile.seek(0) + data = tfile.read() + return data + + if not rules: + # if the firewall has no rules, provide a template + tfile.write(DELIMITER) + tfile.write(get_formatted_rule()) + else: + # if the firewall has rules, display those to the user + for rule in rules: + tfile.write(DELIMITER) + tfile.write(get_formatted_rule(rule)) + tfile.write(DELIMITER) + tfile.flush() + subprocess.call([editor, tfile.name]) + tfile.seek(0) + data = tfile.read() + return data + + return + + +def get_formatted_rule(rule=None): + """Helper to format the rule into a user friendly format. + + :param dict rule: A dict containing one rule of the firewall + :returns: a formatted string that get be pushed into the editor + """ + rule = rule or {} + return ('action: %s\n' + 'protocol: %s\n' + 'source_ip_address: %s\n' + 'source_ip_subnet_mask: %s\n' + 'destination_ip_address: %s\n' + 'destination_ip_subnet_mask: %s\n' + 'destination_port_range_start: %s\n' + 'destination_port_range_end: %s\n' + 'version: %s\n' + % (rule.get('action', 'permit'), + rule.get('protocol', 'tcp'), + rule.get('sourceIpAddress', 'any'), + rule.get('sourceIpSubnetMask', '255.255.255.255'), + rule.get('destinationIpAddress', 'any'), + rule.get('destinationIpSubnetMask', '255.255.255.255'), + rule.get('destinationPortRangeStart', 1), + rule.get('destinationPortRangeEnd', 1), + rule.get('version', 4))) + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Edit firewall rules.""" + + mgr = SoftLayer.FirewallManager(env.client) + + firewall_type, firewall_id = firewall.parse_id(identifier) + if firewall_type == 'vlan': + orig_rules = mgr.get_dedicated_fwl_rules(firewall_id) + else: + orig_rules = mgr.get_standard_fwl_rules(firewall_id) + # open an editor for the user to enter their rules + edited_rules = open_editor(rules=orig_rules) + env.out(edited_rules) + if formatting.confirm("Would you like to submit the rules. " + "Continue?"): + while True: + try: + rules = parse_rules(edited_rules) + if firewall_type == 'vlan': + rules = mgr.edit_dedicated_fwl_rules(firewall_id, + rules) + else: + rules = mgr.edit_standard_fwl_rules(firewall_id, + rules) + break + except (SoftLayer.SoftLayerError, ValueError) as error: + env.out("Unexpected error({%s})" % (error)) + if formatting.confirm("Would you like to continue editing " + "the rules. Continue?"): + edited_rules = open_editor(content=edited_rules) + env.out(edited_rules) + if formatting.confirm("Would you like to submit the " + "rules. Continue?"): + continue + else: + raise exceptions.CLIAbort('Aborted.') + else: + raise exceptions.CLIAbort('Aborted.') + return 'Firewall updated!' + else: + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/firewall/list.py b/SoftLayer/CLI/firewall/list.py new file mode 100644 index 000000000..9d0a17a8f --- /dev/null +++ b/SoftLayer/CLI/firewall/list.py @@ -0,0 +1,85 @@ +"""List firewalls.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List firewalls.""" + + mgr = SoftLayer.FirewallManager(env.client) + table = formatting.Table(['firewall id', + 'type', + 'features', + 'server/vlan id']) + fwvlans = mgr.get_firewalls() + dedicated_firewalls = [firewall for firewall in fwvlans + if firewall['dedicatedFirewallFlag']] + + for vlan in dedicated_firewalls: + features = [] + if vlan['highAvailabilityFirewallFlag']: + features.append('HA') + + if features: + feature_list = formatting.listing(features, separator=',') + else: + feature_list = formatting.blank() + + table.add_row([ + 'vlan:%s' % vlan['networkVlanFirewall']['id'], + 'VLAN - dedicated', + feature_list, + vlan['id'] + ]) + + shared_vlan = [firewall for firewall in fwvlans + if not firewall['dedicatedFirewallFlag']] + for vlan in shared_vlan: + vs_firewalls = [guest + for guest in vlan['firewallGuestNetworkComponents'] + if has_firewall_component(guest)] + + for firewall in vs_firewalls: + table.add_row([ + 'cci:%s' % firewall['id'], + 'CCI - standard', + '-', + firewall['guestNetworkComponent']['guest']['id'] + ]) + + server_firewalls = [server + for server in vlan['firewallNetworkComponents'] + if has_firewall_component(server)] + + for firewall in server_firewalls: + table.add_row([ + 'server:%s' % firewall['id'], + 'Server - standard', + '-', + utils.lookup(firewall, + 'networkComponent', + 'downlinkComponent', + 'hardwareId') + ]) + + return table + + +def has_firewall_component(server): + """Helper to determine whether or not a server has a firewall. + + :param dict server: A dictionary representing a server + :returns: True if the Server has a firewall. + """ + if server['status'] != 'no_edit': + return True + + return False diff --git a/SoftLayer/CLI/globalip/__init__.py b/SoftLayer/CLI/globalip/__init__.py new file mode 100644 index 000000000..68fb2f949 --- /dev/null +++ b/SoftLayer/CLI/globalip/__init__.py @@ -0,0 +1,2 @@ +"""Global IP addresses.""" +# :license: MIT, see LICENSE for more details. diff --git a/SoftLayer/CLI/globalip/assign.py b/SoftLayer/CLI/globalip/assign.py new file mode 100644 index 000000000..ba13739af --- /dev/null +++ b/SoftLayer/CLI/globalip/assign.py @@ -0,0 +1,21 @@ +"""Assigns the global IP to a target.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('identifier') +@click.argument('target') +@environment.pass_env +def cli(env, identifier, target): + """Assigns the global IP to a target.""" + + mgr = SoftLayer.NetworkManager(env.client) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, identifier, + name='global ip') + mgr.assign_global_ip(global_ip_id, target) diff --git a/SoftLayer/CLI/globalip/cancel.py b/SoftLayer/CLI/globalip/cancel.py new file mode 100644 index 000000000..066088223 --- /dev/null +++ b/SoftLayer/CLI/globalip/cancel.py @@ -0,0 +1,26 @@ +"""Cancel global IP.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Cancel global IP.""" + + mgr = SoftLayer.NetworkManager(env.client) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, identifier, + name='global ip') + + if env.skip_confirmations or formatting.no_going_back(global_ip_id): + mgr.cancel_global_ip(global_ip_id) + else: + raise exceptions.CLIAbort('Aborted') diff --git a/SoftLayer/CLI/globalip/create.py b/SoftLayer/CLI/globalip/create.py new file mode 100644 index 000000000..4d0e62ffc --- /dev/null +++ b/SoftLayer/CLI/globalip/create.py @@ -0,0 +1,42 @@ +"""Creates a global IP.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.option('--ipv6', '--v6', is_flag=True, help='Order a IPv6 IP') +@click.option('--test', help='test order') +@environment.pass_env +def cli(env, ipv6, test): + """Creates a global IP.""" + + mgr = SoftLayer.NetworkManager(env.client) + + version = 4 + if ipv6: + version = 6 + if not test and not env.skip_confirmations: + if not formatting.confirm("This action will incur charges on your " + "account. Continue?"): + raise exceptions.CLIAbort('Cancelling order.') + result = mgr.add_global_ip(version=version, test_order=test) + + table = formatting.Table(['item', 'cost']) + table.align['Item'] = 'r' + table.align['cost'] = 'r' + + total = 0.0 + for price in result['orderDetails']['prices']: + total += float(price.get('recurringFee', 0.0)) + rate = "%.2f" % float(price['recurringFee']) + + table.add_row([price['item']['description'], rate]) + + table.add_row(['Total monthly cost', "%.2f" % total]) + return table diff --git a/SoftLayer/CLI/globalip/list.py b/SoftLayer/CLI/globalip/list.py new file mode 100644 index 000000000..d1a86bc08 --- /dev/null +++ b/SoftLayer/CLI/globalip/list.py @@ -0,0 +1,50 @@ +"""List all global IPs.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.option('--ip-version', + help='Display only IPv4', + type=click.Choice(['v4', 'v6'])) +@environment.pass_env +def cli(env, ip_version): + """List all global IPs.""" + + mgr = SoftLayer.NetworkManager(env.client) + + table = formatting.Table(['id', 'ip', 'assigned', 'target']) + + version = None + if ip_version == 'v4': + version = 4 + elif ip_version == 'v6': + version = 6 + + ips = mgr.list_global_ips(version=version) + + for ip_address in ips: + assigned = 'No' + target = 'None' + if ip_address.get('destinationIpAddress'): + dest = ip_address['destinationIpAddress'] + assigned = 'Yes' + target = dest['ipAddress'] + virtual_guest = dest.get('virtualGuest') + if virtual_guest: + target += (' (%s)' + % virtual_guest['fullyQualifiedDomainName']) + elif ip_address['destinationIpAddress'].get('hardware'): + target += (' (%s)' + % dest['hardware']['fullyQualifiedDomainName']) + + table.add_row([ip_address['id'], + ip_address['ipAddress']['ipAddress'], + assigned, + target]) + return table diff --git a/SoftLayer/CLI/globalip/unassign.py b/SoftLayer/CLI/globalip/unassign.py new file mode 100644 index 000000000..e7c0d9f7e --- /dev/null +++ b/SoftLayer/CLI/globalip/unassign.py @@ -0,0 +1,20 @@ +"""Unassigns a global IP from a target.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Unassigns a global IP from a target.""" + + mgr = SoftLayer.NetworkManager(env.client) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, identifier, + name='global ip') + mgr.unassign_global_ip(global_ip_id) diff --git a/SoftLayer/CLI/helpers.py b/SoftLayer/CLI/helpers.py index 60d2852e9..135e36052 100644 --- a/SoftLayer/CLI/helpers.py +++ b/SoftLayer/CLI/helpers.py @@ -30,13 +30,3 @@ def resolve_id(resolver, identifier, name='object'): (name, identifier, ', '.join([str(_id) for _id in ids]))) return ids[0] - - -def sanitize_args(args): - """ sanitize input (remove = sign from argument values) - :returns args back - """ - for key, value in args.items(): - if isinstance(value, str) and value.startswith('='): - args[key] = value[1:] - return args diff --git a/SoftLayer/CLI/image/__init__.py b/SoftLayer/CLI/image/__init__.py new file mode 100644 index 000000000..a186c27d3 --- /dev/null +++ b/SoftLayer/CLI/image/__init__.py @@ -0,0 +1,11 @@ +"""Compute images.""" +# :license: MIT, see LICENSE for more details. +from SoftLayer.CLI import formatting + + +MASK = ('id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,' + 'imageType') +DETAIL_MASK = MASK + (',children[id,blockDevicesDiskSpaceTotal,datacenter],' + 'note,createDate,status') +PUBLIC_TYPE = formatting.FormattedItem('PUBLIC', 'Public') +PRIVATE_TYPE = formatting.FormattedItem('PRIVATE', 'Private') diff --git a/SoftLayer/CLI/image/delete.py b/SoftLayer/CLI/image/delete.py new file mode 100644 index 000000000..6cfc903b7 --- /dev/null +++ b/SoftLayer/CLI/image/delete.py @@ -0,0 +1,20 @@ +"""Delete an image.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Delete an image.""" + + image_mgr = SoftLayer.ImageManager(env.client) + image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image') + + image_mgr.delete_image(image_id) diff --git a/SoftLayer/CLI/image/detail.py b/SoftLayer/CLI/image/detail.py new file mode 100644 index 000000000..c716c2299 --- /dev/null +++ b/SoftLayer/CLI/image/detail.py @@ -0,0 +1,59 @@ +"""Get details for an image.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer.CLI import image as image_mod +from SoftLayer import utils + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get details for an image.""" + + image_mgr = SoftLayer.ImageManager(env.client) + image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image') + + image = image_mgr.get_image(image_id, mask=image_mod.DETAIL_MASK) + disk_space = 0 + datacenters = [] + for child in image.get('children'): + disk_space = int(child.get('blockDevicesDiskSpaceTotal', 0)) + if child.get('datacenter'): + datacenters.append(utils.lookup(child, 'datacenter', 'name')) + + table = formatting.KeyValueTable(['Name', 'Value']) + table.align['Name'] = 'r' + table.align['Value'] = 'l' + + table.add_row(['id', image['id']]) + table.add_row(['global_identifier', + image.get('globalIdentifier', formatting.blank())]) + table.add_row(['name', image['name'].strip()]) + table.add_row(['status', formatting.FormattedItem( + utils.lookup(image, 'status', 'keyname'), + utils.lookup(image, 'status', 'name'), + )]) + table.add_row(['account', image.get('accountId', formatting.blank())]) + table.add_row(['visibility', + image_mod.PUBLIC_TYPE if image['publicFlag'] + else image_mod.PRIVATE_TYPE]) + table.add_row(['type', + formatting.FormattedItem( + utils.lookup(image, 'imageType', 'keyName'), + utils.lookup(image, 'imageType', 'name'), + )]) + table.add_row(['flex', image.get('flexImageFlag')]) + table.add_row(['note', image.get('note')]) + table.add_row(['created', image.get('createDate')]) + table.add_row(['disk_space', formatting.b_to_gb(disk_space)]) + table.add_row(['datacenters', formatting.listing(sorted(datacenters), + separator=',')]) + + return table diff --git a/SoftLayer/CLI/image/edit.py b/SoftLayer/CLI/image/edit.py new file mode 100644 index 000000000..4f4d68892 --- /dev/null +++ b/SoftLayer/CLI/image/edit.py @@ -0,0 +1,31 @@ +"""Edit details of an image.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--name', help="Name of the image") +@click.option('--note', help="Additional note for the image") +@click.option('--tag', help="Tags for the image") +@environment.pass_env +def cli(env, identifier, name, note, tag): + """Edit details of an image.""" + + image_mgr = SoftLayer.ImageManager(env.client) + data = {} + if name: + data['name'] = name + if note: + data['note'] = note + if tag: + data['tag'] = tag + image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image') + if not image_mgr.edit(image_id, **data): + raise exceptions.CLIAbort("Failed to Edit Image") diff --git a/SoftLayer/CLI/image/list.py b/SoftLayer/CLI/image/list.py new file mode 100644 index 000000000..91901e97c --- /dev/null +++ b/SoftLayer/CLI/image/list.py @@ -0,0 +1,55 @@ +"""List images.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import image as image_mod +from SoftLayer import utils + +import click + + +@click.command() +@click.option('--public/--private', + is_flag=True, + default=None, + help='Display only public or private images') +@environment.pass_env +def cli(env, public): + """List images.""" + + image_mgr = SoftLayer.ImageManager(env.client) + + images = [] + if public in [False, None]: + for image in image_mgr.list_private_images(mask=image_mod.MASK): + images.append(image) + + if public in [True, None]: + for image in image_mgr.list_public_images(mask=image_mod.MASK): + images.append(image) + + table = formatting.Table(['id', + 'account', + 'name', + 'type', + 'visibility', + 'global_identifier']) + + images = [image for image in images if image['parentId'] == ''] + for image in images: + + table.add_row([ + image['id'], + image.get('accountId', formatting.blank()), + image['name'].strip(), + formatting.FormattedItem( + utils.lookup(image, 'imageType', 'keyName'), + utils.lookup(image, 'imageType', 'name')), + image_mod.PUBLIC_TYPE if image['publicFlag'] + else image_mod.PRIVATE_TYPE, + image.get('globalIdentifier', formatting.blank()), + ]) + + return table diff --git a/SoftLayer/CLI/iscsi/__init__.py b/SoftLayer/CLI/iscsi/__init__.py new file mode 100644 index 000000000..2b379049a --- /dev/null +++ b/SoftLayer/CLI/iscsi/__init__.py @@ -0,0 +1 @@ +"""iSCSI storage.""" diff --git a/SoftLayer/CLI/iscsi/cancel.py b/SoftLayer/CLI/iscsi/cancel.py new file mode 100644 index 000000000..ea0e1cd7d --- /dev/null +++ b/SoftLayer/CLI/iscsi/cancel.py @@ -0,0 +1,30 @@ +"""Cancel an existing iSCSI account.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--reason', help="An optional reason for cancellation") +@click.option('--immediate', + is_flag=True, + help="Cancels the iSCSI immediately instead of on the billing " + "anniversary") +@environment.pass_env +def cli(env, identifier, reason, immediate): + """Cancel an existing iSCSI account.""" + + iscsi_mgr = SoftLayer.ISCSIManager(env.client) + iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, identifier, 'iSCSI') + + if env.skip_confirmations or formatting.no_going_back(iscsi_id): + iscsi_mgr.cancel_iscsi(iscsi_id, reason, immediate) + else: + raise exceptions.CLIAbort('Aborted') diff --git a/SoftLayer/CLI/iscsi/create.py b/SoftLayer/CLI/iscsi/create.py new file mode 100644 index 000000000..0a8a480d4 --- /dev/null +++ b/SoftLayer/CLI/iscsi/create.py @@ -0,0 +1,23 @@ +"""Creates an iSCSI target.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click + + +@click.command() +@click.option('--size', + type=click.INT, + required=True, + help="Size of the iSCSI volume to create (in gibibytes)") +@click.option('--datacenter', + required=True, + help="Datacenter shortname (sng01, dal05, ...)") +@environment.pass_env +def cli(env, size, datacenter): + """Creates an iSCSI target.""" + + iscsi_mgr = SoftLayer.ISCSIManager(env.client) + iscsi_mgr.create_iscsi(size=size, location=datacenter) diff --git a/SoftLayer/CLI/iscsi/detail.py b/SoftLayer/CLI/iscsi/detail.py new file mode 100644 index 000000000..b920b41c5 --- /dev/null +++ b/SoftLayer/CLI/iscsi/detail.py @@ -0,0 +1,54 @@ +"""Get details for an iSCSI target.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer import utils + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--password', + is_flag=True, + help="Show credentials to access the iSCSI target") +@environment.pass_env +def cli(env, identifier, password): + """Get details for an iSCSI target.""" + + iscsi_mgr = SoftLayer.ISCSIManager(env.client) + + iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, identifier, 'iSCSI') + result = iscsi_mgr.get_iscsi(iscsi_id) + result = utils.NestedDict(result) + + table = formatting.KeyValueTable(['Name', 'Value']) + table.align['Name'] = 'r' + table.align['Value'] = 'l' + + table.add_row(['id', result['id']]) + table.add_row(['serviceResourceName', result['serviceResourceName']]) + table.add_row(['createDate', result['createDate']]) + table.add_row(['nasType', result['nasType']]) + table.add_row(['capacityGb', result['capacityGb']]) + + if result['snapshotCapacityGb']: + table.add_row(['snapshotCapacityGb', result['snapshotCapacityGb']]) + + table.add_row(['mountableFlag', result['mountableFlag']]) + table.add_row(['serviceResourceBackendIpAddress', + result['serviceResourceBackendIpAddress']]) + table.add_row(['price', result['billingItem']['recurringFee']]) + table.add_row(['BillingItemId', result['billingItem']['id']]) + if result.get('notes'): + table.add_row(['notes', result['notes']]) + + if password: + pass_table = formatting.Table(['username', 'password']) + pass_table.add_row([result['username'], result['password']]) + table.add_row(['users', pass_table]) + + return table diff --git a/SoftLayer/CLI/iscsi/list.py b/SoftLayer/CLI/iscsi/list.py new file mode 100644 index 000000000..60923ce3f --- /dev/null +++ b/SoftLayer/CLI/iscsi/list.py @@ -0,0 +1,40 @@ +"""List iSCSI targets.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List iSCSI targets.""" + + iscsi_mgr = SoftLayer.ISCSIManager(env.client) + iscsi_list = iscsi_mgr.list_iscsi() + iscsi_list = [utils.NestedDict(n) for n in iscsi_list] + table = formatting.Table([ + 'id', + 'datacenter', + 'size', + 'username', + 'password', + 'server' + ]) + for iscsi in iscsi_list: + table.add_row([ + iscsi['id'], + iscsi['serviceResource']['datacenter'].get('name', + formatting.blank()), + formatting.FormattedItem(iscsi.get('capacityGb', + formatting.blank()), + "%dGB" % iscsi.get('capacityGb', 0)), + iscsi.get('username', formatting.blank()), + iscsi.get('password', formatting.blank()), + iscsi.get('serviceResourceBackendIpAddress', + formatting.blank())]) + return table diff --git a/SoftLayer/CLI/loadbal/__init__.py b/SoftLayer/CLI/loadbal/__init__.py new file mode 100644 index 000000000..8f7becb62 --- /dev/null +++ b/SoftLayer/CLI/loadbal/__init__.py @@ -0,0 +1,12 @@ +"""Load balancers.""" + +from SoftLayer.CLI import exceptions + + +def parse_id(input_id): + """Parse the load balancer kind and actual id from the "kind:id" form.""" + parts = input_id.split(':') + if len(parts) != 2: + raise exceptions.CLIAbort( + 'Invalid ID %s: ID should be of the form "kind:id"' % input_id) + return parts[0], int(parts[1]) diff --git a/SoftLayer/CLI/loadbal/cancel.py b/SoftLayer/CLI/loadbal/cancel.py new file mode 100644 index 000000000..6ba2fa0d7 --- /dev/null +++ b/SoftLayer/CLI/loadbal/cancel.py @@ -0,0 +1,29 @@ +"""Cancel an existing load balancer.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Cancel an existing load balancer.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + + _, loadbal_id = loadbal.parse_id(identifier) + + if any([env.skip_confirmations, + formatting.confirm("This action will cancel a load balancer. " + "Continue?")]): + mgr.cancel_lb(loadbal_id) + return 'Load Balancer with id %s is being cancelled!' % identifier + else: + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/loadbal/create.py b/SoftLayer/CLI/loadbal/create.py new file mode 100644 index 000000000..e253d62c7 --- /dev/null +++ b/SoftLayer/CLI/loadbal/create.py @@ -0,0 +1,25 @@ +"""Adds a load balancer given the id returned from create-options.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('billing-id') +@click.option('--datacenter', '-d', + help='Datacenter shortname (sng01, dal05, ...)') +@environment.pass_env +def cli(env, billing_id, datacenter): + """Adds a load balancer given the id returned from create-options.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + if not formatting.confirm("This action will incur charges on your " + "account. Continue?"): + raise exceptions.CLIAbort('Aborted.') + mgr.add_local_lb(billing_id, datacenter=datacenter) + return "Load balancer is being created!" diff --git a/SoftLayer/CLI/loadbal/create_options.py b/SoftLayer/CLI/loadbal/create_options.py new file mode 100644 index 000000000..386256fb9 --- /dev/null +++ b/SoftLayer/CLI/loadbal/create_options.py @@ -0,0 +1,35 @@ +"""Show load balancer options.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """Reset connections on a certain service group.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + + table = formatting.Table(['price_id', 'capacity', 'description', 'price']) + + table.sortby = 'price' + table.align['price'] = 'r' + table.align['capacity'] = 'r' + table.align['id'] = 'r' + + packages = mgr.get_lb_pkgs() + + for package in packages: + table.add_row([ + package['prices'][0]['id'], + package.get('capacity'), + package['description'], + '%.2f' % float(package['prices'][0]['recurringFee']) + ]) + + return table diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py new file mode 100644 index 000000000..9e3a817ec --- /dev/null +++ b/SoftLayer/CLI/loadbal/detail.py @@ -0,0 +1,85 @@ +"""Get Load balancer details.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get Load balancer details.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + _, loadbal_id = loadbal.parse_id(identifier) + + load_balancer = mgr.get_local_lb(loadbal_id) + + table = formatting.KeyValueTable(['Name', 'Value']) + table.align['Name'] = 'l' + table.align['Value'] = 'l' + table.add_row(['General properties', '----------']) + table.add_row([' ID', 'local:%s' % load_balancer['id']]) + table.add_row([' IP Address', load_balancer['ipAddress']['ipAddress']]) + name = load_balancer['loadBalancerHardware'][0]['datacenter']['name'] + table.add_row([' Datacenter', name]) + table.add_row([' Connections limit', load_balancer['connectionLimit']]) + table.add_row([' Dedicated', load_balancer['dedicatedFlag']]) + table.add_row([' HA', load_balancer['highAvailabilityFlag']]) + table.add_row([' SSL Enabled', load_balancer['sslEnabledFlag']]) + table.add_row([' SSL Active', load_balancer['sslActiveFlag']]) + index0 = 1 + for virtual_server in load_balancer['virtualServers']: + table.add_row(['Service group %s' % index0, + '**************']) + index0 += 1 + table2 = formatting.Table(['Service group ID', + 'Port', + 'Allocation', + 'Routing type', + 'Routing Method']) + + for group in virtual_server['serviceGroups']: + table2.add_row([ + '%s:%s' % (load_balancer['id'], virtual_server['id']), + virtual_server['port'], + '%s %%' % virtual_server['allocation'], + '%s:%s' % (group['routingTypeId'], + group['routingType']['name']), + '%s:%s' % (group['routingMethodId'], + group['routingMethod']['name']) + ]) + + table.add_row([' Group Properties', table2]) + + table3 = formatting.Table(['Service_ID', + 'IP Address', + 'Port', + 'Health Check', + 'Weight', + 'Enabled', + 'Status']) + service_exist = False + for service in group['services']: + service_exist = True + health_check = service['healthChecks'][0] + table3.add_row([ + '%s:%s' % (load_balancer['id'], service['id']), + service['ipAddress']['ipAddress'], + service['port'], + '%s:%s' % (health_check['healthCheckTypeId'], + health_check['type']['name']), + service['groupReferences'][0]['weight'], + service['enabled'], + service['status'] + ]) + if service_exist: + table.add_row([' Services', table3]) + else: + table.add_row([' Services', 'None']) + return table diff --git a/SoftLayer/CLI/loadbal/group_add.py b/SoftLayer/CLI/loadbal/group_add.py new file mode 100644 index 000000000..76456f2b3 --- /dev/null +++ b/SoftLayer/CLI/loadbal/group_add.py @@ -0,0 +1,41 @@ +"""Adds a new load_balancer service.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--allocation', + required=True, + type=click.INT, + help="The allocated percent of connections") +@click.option('--port', + required=True, + help="The port number", + type=click.INT) +@click.option('--routing-type', + required=True, + help="The port routing type") +@click.option('--routing-method', + required=True, + help="The routing method") +@environment.pass_env +def cli(env, identifier, allocation, port, routing_type, routing_method): + """Adds a new load_balancer service.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + + _, loadbal_id = loadbal.parse_id(identifier) + + mgr.add_service_group(loadbal_id, + allocation=allocation, + port=port, + routing_type=routing_type, + routing_method=routing_method) + + return 'Load balancer service group is being added!' diff --git a/SoftLayer/CLI/loadbal/group_delete.py b/SoftLayer/CLI/loadbal/group_delete.py new file mode 100644 index 000000000..075dbe899 --- /dev/null +++ b/SoftLayer/CLI/loadbal/group_delete.py @@ -0,0 +1,28 @@ +"""Deletes an existing load balancer service group.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Deletes an existing load balancer service group.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + _, group_id = loadbal.parse_id(identifier) + + if env.skip_confirmations or formatting.confirm("This action will cancel " + "a service group. " + "Continue?"): + mgr.delete_service_group(group_id) + return 'Service group %s is being deleted!' % identifier + else: + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/loadbal/group_edit.py b/SoftLayer/CLI/loadbal/group_edit.py new file mode 100644 index 000000000..6de786284 --- /dev/null +++ b/SoftLayer/CLI/loadbal/group_edit.py @@ -0,0 +1,41 @@ +"""Edit an existing load balancer service group.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--allocation', + type=click.INT, + help="Change the allocated percent of connections") +@click.option('--port', + help="Change the port number", + type=click.INT) +@click.option('--routing-type', + help="Change the port routing type") +@click.option('--routing-method', + help="Change the routing method") +@environment.pass_env +def cli(env, identifier, allocation, port, routing_type, routing_method): + """Edit an existing load balancer service group.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + loadbal_id, group_id = loadbal.parse_id(identifier) + + # check if any input is provided + if not any([allocation, port, routing_type, routing_method]): + return 'At least one property is required to be changed!' + + mgr.edit_service_group(loadbal_id, + group_id, + allocation=allocation, + port=port, + routing_type=routing_type, + routing_method=routing_method) + + return 'Load balancer service group %s is being updated!' % identifier diff --git a/SoftLayer/CLI/loadbal/group_reset.py b/SoftLayer/CLI/loadbal/group_reset.py new file mode 100644 index 000000000..78b227d4e --- /dev/null +++ b/SoftLayer/CLI/loadbal/group_reset.py @@ -0,0 +1,21 @@ +"""Reset connections on a certain service group.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Reset connections on a certain service group.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + loadbal_id, group_id = loadbal.parse_id(identifier) + + mgr.reset_service_group(loadbal_id, group_id) + return 'Load balancer service group connections are being reset!' diff --git a/SoftLayer/CLI/loadbal/health_checks.py b/SoftLayer/CLI/loadbal/health_checks.py new file mode 100644 index 000000000..7f73e0b3a --- /dev/null +++ b/SoftLayer/CLI/loadbal/health_checks.py @@ -0,0 +1,26 @@ +"""List health check types.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List health check types.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + + hc_types = mgr.get_hc_types() + table = formatting.KeyValueTable(['ID', 'Name']) + table.align['ID'] = 'l' + table.align['Name'] = 'l' + table.sortby = 'ID' + for hc_type in hc_types: + table.add_row([hc_type['id'], hc_type['name']]) + + return table diff --git a/SoftLayer/CLI/loadbal/list.py b/SoftLayer/CLI/loadbal/list.py new file mode 100644 index 000000000..a462ef9b1 --- /dev/null +++ b/SoftLayer/CLI/loadbal/list.py @@ -0,0 +1,49 @@ +"""List active load balancers.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List active load balancers.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + load_balancers = mgr.get_local_lbs() + + table = formatting.Table(['ID', + 'VIP Address', + 'Location', + 'SSL Offload', + 'Connections/second', + 'Type']) + + table.align['Connections/second'] = 'r' + + for load_balancer in load_balancers: + ssl_support = 'Not Supported' + if load_balancer['sslEnabledFlag']: + if load_balancer['sslActiveFlag']: + ssl_support = 'On' + else: + ssl_support = 'Off' + lb_type = 'Standard' + if load_balancer['dedicatedFlag']: + lb_type = 'Dedicated' + elif load_balancer['highAvailabilityFlag']: + lb_type = 'HA' + table.add_row([ + 'local:%s' % load_balancer['id'], + load_balancer['ipAddress']['ipAddress'], + load_balancer['loadBalancerHardware'][0]['datacenter']['name'], + ssl_support, + load_balancer['connectionLimit'], + lb_type + ]) + + return table diff --git a/SoftLayer/CLI/loadbal/routing_methods.py b/SoftLayer/CLI/loadbal/routing_methods.py new file mode 100644 index 000000000..4549e5ece --- /dev/null +++ b/SoftLayer/CLI/loadbal/routing_methods.py @@ -0,0 +1,25 @@ +"""List routing methods.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List routing types.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + routing_methods = mgr.get_routing_methods() + table = formatting.KeyValueTable(['ID', 'Name']) + table.align['ID'] = 'l' + table.align['Name'] = 'l' + table.sortby = 'ID' + for routing_method in routing_methods: + table.add_row([routing_method['id'], routing_method['name']]) + + return table diff --git a/SoftLayer/CLI/loadbal/routing_types.py b/SoftLayer/CLI/loadbal/routing_types.py new file mode 100644 index 000000000..b3b76060c --- /dev/null +++ b/SoftLayer/CLI/loadbal/routing_types.py @@ -0,0 +1,24 @@ +"""List routing types.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List routing types.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + routing_methods = mgr.get_routing_methods() + table = formatting.KeyValueTable(['ID', 'Name']) + table.align['ID'] = 'l' + table.align['Name'] = 'l' + table.sortby = 'ID' + for routing_method in routing_methods: + table.add_row([routing_method['id'], routing_method['name']]) + return table diff --git a/SoftLayer/CLI/loadbal/service_add.py b/SoftLayer/CLI/loadbal/service_add.py new file mode 100644 index 000000000..5a9fa982a --- /dev/null +++ b/SoftLayer/CLI/loadbal/service_add.py @@ -0,0 +1,52 @@ +"""Adds a new load balancer service.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--enabled / --disabled', + required=True, + help="Create the service as enable or disabled") +@click.option('--port', + required=True, + help="The port number for the service", + type=click.INT) +@click.option('--weight', + required=True, + type=click.INT, + help="The weight of the service") +@click.option('--healthcheck-type', + required=True, + help="The health check type") +@click.option('--ip-address', '--ip', + required=True, + help="The IP of the service") +@environment.pass_env +def cli(env, identifier, enabled, port, weight, healthcheck_type, ip_address): + """Adds a new load balancer service.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + + loadbal_id, group_id = loadbal.parse_id(identifier) + + # check if the IP is valid + ip_address_id = None + if ip_address: + ip_service = env.client['Network_Subnet_IpAddress'] + ip_record = ip_service.getByIpAddress(ip_address) + ip_address_id = ip_record['id'] + + mgr.add_service(loadbal_id, + group_id, + ip_address_id=ip_address_id, + enabled=enabled, + port=port, + weight=weight, + hc_type=healthcheck_type) + return 'Load balancer service is being added!' diff --git a/SoftLayer/CLI/loadbal/service_delete.py b/SoftLayer/CLI/loadbal/service_delete.py new file mode 100644 index 000000000..8b4d43f7b --- /dev/null +++ b/SoftLayer/CLI/loadbal/service_delete.py @@ -0,0 +1,28 @@ +"""Deletes an existing load balancer service.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Deletes an existing load balancer service.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + _, service_id = loadbal.parse_id(identifier) + + if env.skip_confirmations or formatting.confirm("This action will cancel " + "a service from your load " + "balancer. Continue?"): + mgr.delete_service(service_id) + return 'Load balancer service %s is being cancelled!' % service_id + else: + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/loadbal/service_edit.py b/SoftLayer/CLI/loadbal/service_edit.py new file mode 100644 index 000000000..0ae024edc --- /dev/null +++ b/SoftLayer/CLI/loadbal/service_edit.py @@ -0,0 +1,49 @@ +"""Edit the properties of a service group.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--enabled / --disabled', + default=None, + help="Enable or disable the service") +@click.option('--port', + help="Change the port number for the service", type=click.INT) +@click.option('--weight', + type=click.INT, + help="Change the weight of the service") +@click.option('--healthcheck-type', help="Change the health check type") +@click.option('--ip-address', '--ip', help="Change the IP of the service") +@environment.pass_env +def cli(env, identifier, enabled, port, weight, healthcheck_type, ip_address): + """Edit the properties of a service group.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + + loadbal_id, service_id = loadbal.parse_id(identifier) + + # check if any input is provided + if not any([ip_address, enabled, weight, port, healthcheck_type]): + return 'At least one property is required to be changed!' + + # check if the IP is valid + ip_address_id = None + if ip_address: + ip_service = env.client['Network_Subnet_IpAddress'] + ip_record = ip_service.getByIpAddress(ip_address) + ip_address_id = ip_record['id'] + + mgr.edit_service(loadbal_id, + service_id, + ip_address_id=ip_address_id, + enabled=enabled, + port=port, + weight=weight, + hc_type=healthcheck_type) + return 'Load balancer service %s is being modified!' % identifier diff --git a/SoftLayer/CLI/loadbal/service_toggle.py b/SoftLayer/CLI/loadbal/service_toggle.py new file mode 100644 index 000000000..7f4090e75 --- /dev/null +++ b/SoftLayer/CLI/loadbal/service_toggle.py @@ -0,0 +1,28 @@ +"""Toggle the status of an existing load balancer service.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Toggle the status of an existing load balancer service.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + _, service_id = loadbal.parse_id(identifier) + + if env.skip_confirmations or formatting.confirm("This action will toggle " + "the status on the " + "service. Continue?"): + mgr.toggle_service_status(service_id) + return 'Load balancer service %s status updated!' % identifier + else: + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/metadata.py b/SoftLayer/CLI/metadata.py new file mode 100644 index 000000000..150ea3c6e --- /dev/null +++ b/SoftLayer/CLI/metadata.py @@ -0,0 +1,67 @@ +"""Find details about this machine.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting + +import click + +META_MAPPING = { + 'backend_ip': 'primary_backend_ip', + 'ip': 'primary_ip', +} + + +@click.command(epilog="These commands only work on devices on the backend " + "SoftLayer network. This allows for self-discovery for " + "newly provisioned resources.") +@click.argument('prop', type=click.Choice(['backend_ip', + 'backend_mac', + 'datacenter', + 'datacenter_id', + 'fqdn', + 'frontend_mac', + 'id', + 'ip', + 'network', + 'provision_state', + 'tags', + 'user_data'])) +def cli(prop): + """Find details about this machine.""" + + try: + if prop == 'network': + return get_network() + + meta_prop = META_MAPPING.get(prop) or prop + return SoftLayer.MetadataManager().get(meta_prop) + except SoftLayer.TransportError: + raise exceptions.CLIAbort( + 'Cannot connect to the backend service address. Make sure ' + 'this command is being ran from a device on the backend ' + 'network.') + + +def get_network(): + """Returns a list of tables with public and private network details.""" + meta = SoftLayer.MetadataManager() + network_tables = [] + for network_func in [meta.public_network, meta.private_network]: + network = network_func() + + table = formatting.KeyValueTable(['name', 'value']) + table.align['Name'] = 'r' + table.align['Value'] = 'l' + table.add_row(['mac addresses', + formatting.listing(network['mac_addresses'], + separator=',')]) + table.add_row(['router', network['router']]) + table.add_row(['vlans', + formatting.listing(network['vlans'], separator=',')]) + table.add_row(['vlan ids', + formatting.listing(network['vlan_ids'], separator=',')]) + network_tables.append(table) + + return network_tables diff --git a/SoftLayer/CLI/modules/__init__.py b/SoftLayer/CLI/modules/__init__.py deleted file mode 100644 index eaad02f96..000000000 --- a/SoftLayer/CLI/modules/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SoftLayer.CLI.modules - ~~~~~~~~~~~~~~~~~~~~~ - Contains all plugable modules for the CLI interface - - :license: MIT, see LICENSE for more details. -""" - -import pkgutil - - -def get_module_list(): - """Returns each module under SoftLayer.CLI.modules.""" - actions = [action[1] for action in pkgutil.iter_modules(__path__)] - return actions diff --git a/SoftLayer/CLI/modules/cdn.py b/SoftLayer/CLI/modules/cdn.py deleted file mode 100644 index 11f0e8b47..000000000 --- a/SoftLayer/CLI/modules/cdn.py +++ /dev/null @@ -1,188 +0,0 @@ -""" -usage: sl cdn [] [...] [options] - -Manage CDN accounts and configuration - -The available commands are: - detail Show details for a CDN account - list List CDN accounts - load Cache one or more files on all edge nodes - origin-add Add an origin pull mapping - origin-list Show origin pull mappings on a CDN account - origin-remove Remove an origin pull mapping - purge Purge one or more cached files from all edge nodes -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -class ListAccounts(environment.CLIRunnable): - """ -usage: sl cdn list [options] - -List all CDN accounts - -Options: - --sortby=SORTBY Sort by this value. [Default: id] - [Options: id, account_name, type, created, notes] -""" - action = 'list' - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - accounts = manager.list_accounts() - - table = formatting.Table(['id', - 'account_name', - 'type', - 'created', - 'notes']) - for account in accounts: - table.add_row([ - account['id'], - account['cdnAccountName'], - account['cdnSolutionName'], - account['createDate'], - account.get('cdnAccountNote', formatting.blank()) - ]) - - table.sortby = args['--sortby'] - return table - - -class DetailAccount(environment.CLIRunnable): - """ -usage: sl cdn detail [options] - -Show CDN account details -""" - action = 'detail' - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - account = manager.get_account(args.get('')) - - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - - table.add_row(['id', account['id']]) - table.add_row(['account_name', account['cdnAccountName']]) - table.add_row(['type', account['cdnSolutionName']]) - table.add_row(['status', account['status']['name']]) - table.add_row(['created', account['createDate']]) - table.add_row(['notes', - account.get('cdnAccountNote', formatting.blank())]) - - return table - - -class LoadContent(environment.CLIRunnable): - """ -usage: sl cdn load ... [options] - -Cache one or more files on all edge nodes - -Required: - account The CDN account ID to cache content in - content_url The CDN URL(s) or CDN CNAME-based URL(s) for the content - you wish to cache (can be repeated) -""" - action = 'load' - required_params = ['account', 'content_url'] - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - manager.load_content(args.get(''), args.get('')) - - -class PurgeContent(environment.CLIRunnable): - """ -usage: sl cdn purge ... [options] - -Purge one or more cached files from all edge nodes - -Required: - account The CDN account ID to purge content from - content_url The CDN URL(s) or CDN CNAME-based URL(s) for the content - you wish to cache (can be repeated) -""" - action = 'purge' - required_params = ['account', 'content_url'] - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - manager.purge_content(args.get(''), - args.get('')) - - -class ListOrigins(environment.CLIRunnable): - """ -usage: sl cdn origin-list [options] - -List origin pull mappings associated with a CDN account. -""" - action = 'origin-list' - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - origins = manager.get_origins(args.get('')) - - table = formatting.Table(['id', 'media_type', 'cname', 'origin_url']) - - for origin in origins: - table.add_row([origin['id'], - origin['mediaType'], - origin.get('cname', formatting.blank()), - origin['originUrl']]) - - return table - - -class AddOrigin(environment.CLIRunnable): - """ -usage: sl cdn origin-add [options] - -Create an origin pull mapping on a CDN account - -Required: - account The CDN account ID to create a mapping on - url A full URL where content should be pulled from by - CDN edge nodes - -Options: - --type=TYPE The media type for this mapping (http, flash, wm, ...) - (default: http) - --cname=CNAME An optional CNAME to attach to the mapping -""" - action = 'origin-add' - required_params = ['account', 'url'] - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - media_type = args.get('--type') or 'http' - - manager.add_origin(args.get(''), media_type, - args.get(''), args.get('--cname', None)) - - -class RemoveOrigin(environment.CLIRunnable): - """ -usage: sl cdn origin-remove [options] - -Remove an origin pull mapping from a CDN account - -Required: - account The CDN account ID to remove a mapping from - origin_id The origin mapping ID to remove -""" - action = 'origin-remove' - required_params = ['account', 'origin_id'] - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - manager.remove_origin(args.get(''), - args.get('')) diff --git a/SoftLayer/CLI/modules/config.py b/SoftLayer/CLI/modules/config.py deleted file mode 100644 index 3f84e1052..000000000 --- a/SoftLayer/CLI/modules/config.py +++ /dev/null @@ -1,199 +0,0 @@ -""" -usage: sl config [] [...] [options] - -View and edit configuration - -The available commands are: - setup Setup configuration - show Show current configuration -""" -# :license: MIT, see LICENSE for more details. - -import os.path - -import SoftLayer -from SoftLayer import auth -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer import utils - - -def get_settings_from_client(client): - """ Pull out settings from a SoftLayer.Client instance. - - :param client: SoftLayer.Client instance - """ - settings = { - 'username': '', - 'api_key': '', - 'timeout': client.timeout or '', - 'endpoint_url': client.endpoint_url, - } - try: - settings['username'] = client.auth.username - settings['api_key'] = client.auth.api_key - except AttributeError: - pass - - return settings - - -def config_table(settings): - """ Returns a config table """ - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - table.add_row(['Username', settings['username'] or 'not set']) - table.add_row(['API Key', settings['api_key'] or 'not set']) - table.add_row(['Endpoint URL', settings['endpoint_url'] or 'not set']) - table.add_row(['Timeout', settings['timeout'] or 'not set']) - return table - - -def get_api_key(client, username, secret, endpoint_url=None): - """ Attempts API-Key and password auth to get an API key - - This will also generate an API key if one doesn't exist - """ - - client.endpoint_url = endpoint_url - client.auth = None - # Try to use a client with username/api key - if len(secret) == 64: - try: - client.auth = auth.BasicAuthentication(username, secret) - client['Account'].getCurrentUser() - return secret - except SoftLayer.SoftLayerAPIError as ex: - if 'invalid api token' not in ex.faultString.lower(): - raise - else: - # Try to use a client with username/password - client.authenticate_with_password(username, secret) - - user_record = client['Account'].getCurrentUser( - mask='id, apiAuthenticationKeys') - api_keys = user_record['apiAuthenticationKeys'] - if len(api_keys) == 0: - return client['User_Customer'].addApiAuthenticationKey( - id=user_record['id']) - return api_keys[0]['authenticationKey'] - - -class Setup(environment.CLIRunnable): - """ -usage: sl config setup [options] - -Setup configuration -""" - action = 'setup' - - def execute(self, args): - username, secret, endpoint_url, timeout = self.get_user_input() - - api_key = get_api_key(self.client, username, secret, - endpoint_url=endpoint_url) - - path = '~/.softlayer' - if args.get('--config'): - path = args.get('--config') - config_path = os.path.expanduser(path) - - self.env.out( - formatting.format_output(config_table({ - 'username': username, - 'api_key': api_key, - 'endpoint_url': endpoint_url, - 'timeout': timeout}))) - - if not formatting.confirm('Are you sure you want to write settings ' - 'to "%s"?' % config_path, default=True): - raise exceptions.CLIAbort('Aborted.') - - # Persist the config file. Read the target config file in before - # setting the values to avoid clobbering settings - config = utils.configparser.RawConfigParser() - config.read(config_path) - try: - config.add_section('softlayer') - except utils.configparser.DuplicateSectionError: - pass - - config.set('softlayer', 'username', username) - config.set('softlayer', 'api_key', api_key) - config.set('softlayer', 'endpoint_url', endpoint_url) - - config_file = os.fdopen(os.open(config_path, - (os.O_WRONLY | os.O_CREAT), - 0o600), - 'w') - try: - config.write(config_file) - finally: - config_file.close() - - return "Configuration Updated Successfully" - - def get_user_input(self): - """ Ask for username, secret (api_key or password) and endpoint_url """ - - defaults = get_settings_from_client(self.client) - timeout = defaults['timeout'] - - # Ask for username - for _ in range(3): - username = (self.env.input('Username [%s]: ' - % defaults['username']) - or defaults['username']) - if username: - break - else: - raise exceptions.CLIAbort('Aborted after 3 attempts') - - # Ask for 'secret' which can be api_key or their password - for _ in range(3): - secret = (self.env.getpass('API Key or Password [%s]: ' - % defaults['api_key']) - or defaults['api_key']) - if secret: - break - else: - raise exceptions.CLIAbort('Aborted after 3 attempts') - - # Ask for which endpoint they want to use - for _ in range(3): - endpoint_type = self.env.input( - 'Endpoint (public|private|custom): ') - endpoint_type = endpoint_type.lower() - if not endpoint_type: - endpoint_url = SoftLayer.API_PUBLIC_ENDPOINT - break - if endpoint_type == 'public': - endpoint_url = SoftLayer.API_PUBLIC_ENDPOINT - break - elif endpoint_type == 'private': - endpoint_url = SoftLayer.API_PRIVATE_ENDPOINT - break - elif endpoint_type == 'custom': - endpoint_url = self.env.input( - 'Endpoint URL [%s]: ' % defaults['endpoint_url'] - ) or defaults['endpoint_url'] - break - else: - raise exceptions.CLIAbort('Aborted after 3 attempts') - - return username, secret, endpoint_url, timeout - - -class Show(environment.CLIRunnable): - """ -usage: sl config show [options] - -Show current configuration -""" - action = 'show' - - def execute(self, args): - settings = get_settings_from_client(self.client) - return config_table(settings) diff --git a/SoftLayer/CLI/modules/dns.py b/SoftLayer/CLI/modules/dns.py deleted file mode 100755 index 90a995b87..000000000 --- a/SoftLayer/CLI/modules/dns.py +++ /dev/null @@ -1,369 +0,0 @@ -""" -usage: sl dns [] [...] [options] - -Manage DNS - -The available zone commands are: - create Create zone - delete Delete zone - list List zones or a zone's records - print Print zone in BIND format - -The available record commands are: - add Add resource record - edit Update resource records (bulk/single) - remove Remove resource records -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers - -import re - -RECORD_REGEX = re.compile(r"""^((?P([\w-]+(\.)?)*|\@)?\s+ - (?P\d+)?\s+ - (?P\w+)?)?\s+ - (?P\w+)\s+ - (?P.*)""", re.X) - - -class DumpZone(environment.CLIRunnable): - """ -usage: sl dns print [options] - -print zone in BIND format - -Arguments: - Zone name (softlayer.com) -""" - action = "print" - - def execute(self, args): - manager = SoftLayer.DNSManager(self.client) - zone_id = helpers.resolve_id(manager.resolve_ids, args[''], - name='zone') - return manager.dump_zone(zone_id) - - -class CreateZone(environment.CLIRunnable): - """ -usage: sl dns create [options] - -Create a zone - -Arguments: - Zone name (softlayer.com) -""" - action = 'create' - - def execute(self, args): - manager = SoftLayer.DNSManager(self.client) - manager.create_zone(args['']) - - -class DeleteZone(environment.CLIRunnable): - """ -usage: sl dns delete [options] - -Delete zone - -Arguments: - Zone name (softlayer.com) -""" - action = 'delete' - options = ['confirm'] - - def execute(self, args): - manager = SoftLayer.DNSManager(self.client) - zone_id = helpers.resolve_id(manager.resolve_ids, args[''], - name='zone') - - if args['--really'] or formatting.no_going_back(args['']): - manager.delete_zone(zone_id) - else: - raise exceptions.CLIAbort("Aborted.") - - -class ImportZone(environment.CLIRunnable): - """ -usage: sl dns import [options] - -Creates a new zone based off a nicely BIND formatted file - -Arguments: - Path to the bind zone file you want to import -Options: - --dry-run Don't actually do anything. This will show you what is parsed - - """ - action = 'import' - - def execute(self, args): - - dry_run = args.get('--dry-run') - - manager = SoftLayer.DNSManager(self.client) - with open(args['']) as zone_file: - zone_contents = zone_file.read() - - zone, records, bad_lines = parse_zone_details(zone_contents) - - self.env.out("Parsed: zone=%s" % zone) - for record in records: - self.env.out("Parsed: %s" % record) - for line in bad_lines: - self.env.out("Unparsed: %s" % line) - - if dry_run: - return - - # Find zone id or create the zone if it doesn't exist - try: - zone_id = helpers.resolve_id(manager.resolve_ids, zone, - name='zone') - except exceptions.CLIAbort: - zone_id = manager.create_zone(zone)['id'] - self.env.out("\033[92mCREATED ZONE: %s\033[0m" % zone) - - # Attempt to create each record - for record in records: - try: - manager.create_record(zone_id, - record['record'], - record['record_type'], - record['data'], - record['ttl']) - self.env.out("\033[92mCreated: Host: %s\033[0m" % record) - except SoftLayer.SoftLayerAPIError as ex: - self.env.out("\033[91mFAILED: %s" % record) - self.env.out("%s \033[0m" % ex) - - return "Finished" - - -def parse_zone_details(zone_contents): - """Parses a zone file into python data-structures""" - records = [] - bad_lines = [] - zone_lines = [line.strip() for line in zone_contents.split('\n')] - - zone_search = re.search(r'^\$ORIGIN (?P.*)\.', zone_lines[0]) - zone = zone_search.group('zone') - - for line in zone_lines[1:]: - record_search = re.search(RECORD_REGEX, line) - if record_search is None: - bad_lines.append(line) - continue - - name = record_search.group('domain') - # The API requires we send a host, although bind allows a blank - # entry. @ is the same thing as blank - if name is None: - name = "@" - - ttl = record_search.group('ttl') - # we don't do anything with the class - # domain_class = domainSearch.group('class') - record_type = record_search.group('type').upper() - data = record_search.group('data') - - # the dns class doesn't support weighted MX records yet, so we chomp - # that part out. - if record_type == "MX": - record_search = re.search(r'(?P\d+)\s+(?P.*)', data) - data = record_search.group('data') - - # This will skip the SOA record bit. And any domain that gets - # parsed oddly. - if record_type == 'IN': - bad_lines.append(line) - continue - - records.append({ - 'record': name, - 'record_type': record_type, - 'data': data, - 'ttl': ttl, - }) - - return zone, records, bad_lines - - -class ListZones(environment.CLIRunnable): - """ -usage: sl dns list [] [options] - -List zones and optionally, records - -Filters: - --data=DATA Record data, such as an IP address - --record=HOST Host record, such as www - --ttl=TTL TTL value in seconds, such as 86400 - --type=TYPE Record type, such as A or CNAME -""" - action = 'list' - - def execute(self, args): - if args['']: - return self.list_zone(args) - - return self.list_all_zones() - - def list_zone(self, args): - """ list records for a particular zone """ - manager = SoftLayer.DNSManager(self.client) - table = formatting.Table(['id', 'record', 'type', 'ttl', 'value']) - - table.align['ttl'] = 'l' - table.align['record'] = 'r' - table.align['value'] = 'l' - - zone_id = helpers.resolve_id(manager.resolve_ids, args[''], - name='zone') - - records = manager.get_records( - zone_id, - record_type=args.get('--type'), - host=args.get('--record'), - ttl=args.get('--ttl'), - data=args.get('--data'), - ) - - for record in records: - table.add_row([ - record['id'], - record['host'], - record['type'].upper(), - record['ttl'], - record['data'] - ]) - - return table - - def list_all_zones(self): - """ List all zones """ - manager = SoftLayer.DNSManager(self.client) - zones = manager.list_zones() - table = formatting.Table(['id', 'zone', 'serial', 'updated']) - table.align['serial'] = 'c' - table.align['updated'] = 'c' - - for zone in zones: - table.add_row([ - zone['id'], - zone['name'], - zone['serial'], - zone['updateDate'], - ]) - - return table - - -class AddRecord(environment.CLIRunnable): - """ -usage: sl dns add [--ttl=TTL] [options] - -Add resource record - -Arguments: - Zone name (softlayer.com) - Resource record (www) - Record type. [Options: A, AAAA, - CNAME, MX, NS, PTR, SPF, SRV, TXT] - Record data. NOTE: only minor validation is done - -Options: - --ttl=TTL Time to live -""" - action = 'add' - - def execute(self, args): - manager = SoftLayer.DNSManager(self.client) - - zone_id = helpers.resolve_id(manager.resolve_ids, args[''], - name='zone') - - manager.create_record( - zone_id, - args[''], - args[''], - args[''], - ttl=args['--ttl'] or 7200) - - -class EditRecord(environment.CLIRunnable): - """ -usage: sl dns edit [--data=DATA] [--ttl=TTL] [--id=ID] - [options] - -Update resource records (bulk/single) - -Arguments: - Zone name (softlayer.com) - Resource record (www) - -Options: - --data=DATA - --id=ID Modify only the given ID - --ttl=TTL Time to live -""" - action = 'edit' - - def execute(self, args): - manager = SoftLayer.DNSManager(self.client) - zone_id = helpers.resolve_id(manager.resolve_ids, args[''], - name='zone') - - results = manager.get_records( - zone_id, - host=args['']) - - for result in results: - if args['--id'] and str(result['id']) != args['--id']: - continue - result['data'] = args['--data'] or result['data'] - result['ttl'] = args['--ttl'] or result['ttl'] - manager.edit_record(result) - - -class RecordRemove(environment.CLIRunnable): - """ -usage: sl dns remove [--id=ID] [options] - -Remove resource records - -Arguments: - Zone name (softlayer.com) - Resource record (www) - -Options: - --id=ID Remove only the given ID -""" - action = 'remove' - options = ['confirm'] - - def execute(self, args): - manager = SoftLayer.DNSManager(self.client) - zone_id = helpers.resolve_id(manager.resolve_ids, args[''], - name='zone') - - if args['--id']: - records = [{'id': args['--id']}] - else: - records = manager.get_records( - zone_id, - host=args['']) - - if args['--really'] or formatting.no_going_back('yes'): - table = formatting.Table(['record']) - for result in records: - manager.delete_record(result['id']) - table.add_row([result['id']]) - - return table - raise exceptions.CLIAbort("Aborted.") diff --git a/SoftLayer/CLI/modules/filters.py b/SoftLayer/CLI/modules/filters.py deleted file mode 100644 index ae8073a6d..000000000 --- a/SoftLayer/CLI/modules/filters.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -usage: sl help filters - -Filters are used to limit the amount of results. Some commands will accept a -filter operation for certain fields. Filters can be applied across multiple -fields in most cases. - -Available Operations: - Case Insensitive - 'value' Exact value match - 'value*' Begins with value - '*value' Ends with value - '*value*' Contains value - - Case Sensitive - '~ value' Exact value match - '> value' Greater than value - '< value' Less than value - '>= value' Greater than or equal to value - '<= value' Less than or equal to value - -Examples: - sl server list --datacenter=dal05 - sl server list --hostname='prod*' - sl vs list --network=100 --cpu=2 - sl vs list --network='< 100' --cpu=2 - sl vs list --memory='>= 2048' - -Note: Comparison operators (>, <, >=, <=) can be used with integers, floats, - and strings. -""" -# :license: MIT, see LICENSE for more details. diff --git a/SoftLayer/CLI/modules/firewall.py b/SoftLayer/CLI/modules/firewall.py deleted file mode 100755 index 489cf829a..000000000 --- a/SoftLayer/CLI/modules/firewall.py +++ /dev/null @@ -1,424 +0,0 @@ -""" -usage: sl firewall [] [...] [options] - -Firewall rule and security management - -The available commands are: - add Add a new firewall - cancel Cancel an existing firewall - detail Provide details about a particular firewall - edit Edit the rules of a particular firewall - list List active firewalls - both dedicated and shared - -""" -# :license: MIT, see LICENSE for more details. - -from __future__ import print_function -import os -import subprocess -import tempfile - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer import utils - -DELIMITER = "=========================================\n" - - -def get_ids(input_id): - """ Helper package to retrieve the actual IDs - :param input_id: the ID provided by the user - :returns: A list of valid IDs - """ - key_value = input_id.split(':') - - if len(key_value) != 2: - raise exceptions.CLIAbort( - 'Invalid ID %s: ID should be of the form xxx:yyy' % input_id) - return key_value - - -def print_package_info(package): - """ Helper package to print the firewall price. - - :param dict package: A dictionary representing the firewall package - """ - print("******************") - print("Product: %s" % package[0]['description']) - print("Price: %s$ monthly" % package[0]['prices'][0]['recurringFee']) - print("******************") - return - - -def has_firewall_component(server): - """ Helper to determine whether or not a server has a firewall. - - :param dict server: A dictionary representing a server - :returns: True if the Server has a firewall. - """ - if server['status'] != 'no_edit': - return True - - return False - - -def get_rules_table(rules): - """ Helper to format the rules into a table - - :param list rules: A list containing the rules of the firewall - :returns: a formatted table of the firewall rules - """ - table = formatting.Table(['#', 'action', 'protocol', 'src_ip', 'src_mask', - 'dest', 'dest_mask']) - table.sortby = '#' - for rule in rules: - table.add_row([ - rule['orderValue'], - rule['action'], - rule['protocol'], - rule['sourceIpAddress'], - rule['sourceIpSubnetMask'], - '%s:%s-%s' % (rule['destinationIpAddress'], - rule['destinationPortRangeStart'], - rule['destinationPortRangeEnd']), - rule['destinationIpSubnetMask']]) - return table - - -def get_formatted_rule(rule=None): - """ Helper to format the rule into a user friendly format - for editing purposes - - :param dict rule: A dict containing one rule of the firewall - :returns: a formatted string that get be pushed into the editor - """ - rule = rule or {} - return ('action: %s\n' - 'protocol: %s\n' - 'source_ip_address: %s\n' - 'source_ip_subnet_mask: %s\n' - 'destination_ip_address: %s\n' - 'destination_ip_subnet_mask: %s\n' - 'destination_port_range_start: %s\n' - 'destination_port_range_end: %s\n' - 'version: %s\n' - % (rule.get('action', 'permit'), - rule.get('protocol', 'tcp'), - rule.get('sourceIpAddress', 'any'), - rule.get('sourceIpSubnetMask', '255.255.255.255'), - rule.get('destinationIpAddress', 'any'), - rule.get('destinationIpSubnetMask', '255.255.255.255'), - rule.get('destinationPortRangeStart', 1), - rule.get('destinationPortRangeEnd', 1), - rule.get('version', 4))) - - -def open_editor(rules=None, content=None): - """ Helper to open an editor for editing the firewall rules - This method takes two parameters, if content is provided, - that means that submitting the rules failed and we are allowing - the user to re-edit what they provided. - If content is not provided, the rules retrieved from the firewall - will be displayed to the user. - - :param list rules: A list containing the rules of the firewall - :param string content: the content that the user provided in the editor - :returns: a formatted string that get be pushed into the editor - """ - - # Let's get the default EDITOR of the environment, - # use nano if none is specified - editor = os.environ.get('EDITOR', 'nano') - - with tempfile.NamedTemporaryFile(suffix=".tmp") as tfile: - - if content: - # if content is provided, just display it as is - tfile.write(content) - tfile.flush() - subprocess.call([editor, tfile.name]) - tfile.seek(0) - data = tfile.read() - return data - - if not rules: - # if the firewall has no rules, provide a template - tfile.write(DELIMITER) - tfile.write(get_formatted_rule()) - else: - # if the firewall has rules, display those to the user - for rule in rules: - tfile.write(DELIMITER) - tfile.write(get_formatted_rule(rule)) - tfile.write(DELIMITER) - tfile.flush() - subprocess.call([editor, tfile.name]) - tfile.seek(0) - data = tfile.read() - return data - - return - - -def parse_rules(content=None): - """ Helper to parse the input from the user into a list of rules. - - :param string content: the content of the editor - :returns: a list of rules - """ - rules = content.split(DELIMITER) - parsed_rules = list() - order = 1 - for rule in rules: - if rule.strip() == '': - continue - parsed_rule = {} - lines = rule.split("\n") - parsed_rule['orderValue'] = order - order += 1 - for line in lines: - if line.strip() == '': - continue - key_value = line.strip().split(':') - key = key_value[0].strip() - value = key_value[1].strip() - if key == 'action': - parsed_rule['action'] = value - elif key == 'protocol': - parsed_rule['protocol'] = value - elif key == 'source_ip_address': - parsed_rule['sourceIpAddress'] = value - elif key == 'source_ip_subnet_mask': - parsed_rule['sourceIpSubnetMask'] = value - elif key == 'destination_ip_address': - parsed_rule['destinationIpAddress'] = value - elif key == 'destination_ip_subnet_mask': - parsed_rule['destinationIpSubnetMask'] = value - elif key == 'destination_port_range_start': - parsed_rule['destinationPortRangeStart'] = int(value) - elif key == 'destination_port_range_end': - parsed_rule['destinationPortRangeEnd'] = int(value) - elif key == 'version': - parsed_rule['version'] = int(value) - parsed_rules.append(parsed_rule) - return parsed_rules - - -class FWList(environment.CLIRunnable): - """ -usage: sl firewall list [options] - -List active firewalls -""" - action = 'list' - - def execute(self, args): - mgr = SoftLayer.FirewallManager(self.client) - table = formatting.Table(['firewall id', - 'type', - 'features', - 'server/vlan id']) - fwvlans = mgr.get_firewalls() - dedicated_firewalls = [firewall for firewall in fwvlans - if firewall['dedicatedFirewallFlag']] - - for vlan in dedicated_firewalls: - features = [] - if vlan['highAvailabilityFirewallFlag']: - features.append('HA') - - if features: - feature_list = formatting.listing(features, separator=',') - else: - feature_list = formatting.blank() - - table.add_row([ - 'vlan:%s' % vlan['networkVlanFirewall']['id'], - 'VLAN - dedicated', - feature_list, - vlan['id'] - ]) - - shared_vlan = [firewall for firewall in fwvlans - if not firewall['dedicatedFirewallFlag']] - for vlan in shared_vlan: - vs_firewalls = [guest - for guest in vlan['firewallGuestNetworkComponents'] - if has_firewall_component(guest)] - - for firewall in vs_firewalls: - table.add_row([ - 'cci:%s' % firewall['id'], - 'CCI - standard', - '-', - firewall['guestNetworkComponent']['guest']['id'] - ]) - - server_firewalls = [server - for server in vlan['firewallNetworkComponents'] - if has_firewall_component(server)] - - for firewall in server_firewalls: - table.add_row([ - 'server:%s' % firewall['id'], - 'Server - standard', - '-', - utils.lookup(firewall, - 'networkComponent', - 'downlinkComponent', - 'hardwareId') - ]) - - return table - - -class FWCancel(environment.CLIRunnable): - """ -usage: sl firewall cancel [options] - -Cancels a firewall - -Options: - --really Whether to skip the confirmation prompt - -""" - action = 'cancel' - options = ['really'] - - def execute(self, args): - mgr = SoftLayer.FirewallManager(self.client) - input_id = args.get('') - key_value = get_ids(input_id) - firewall_id = int(key_value[1]) - - if args['--really'] or formatting.confirm("This action will cancel a " - "firewall from your account." - " Continue?"): - if key_value[0] in ['cci', 'server']: - mgr.cancel_firewall(firewall_id, dedicated=False) - elif key_value[0] == 'vlan': - mgr.cancel_firewall(firewall_id, dedicated=True) - return 'Firewall with id %s is being cancelled!' % input_id - else: - raise exceptions.CLIAbort('Aborted.') - - -class FWAdd(environment.CLIRunnable): - """ -usage: sl firewall add (--cci | --vlan | --server) [options] - -Adds a firewall of type either standard (cci or server) or dedicated(vlan) -Options: - --cci creates a standard firewall for a CCI - --vlan creates a dedicated firewall for a VLAN - --server creates a standard firewall for a server - --ha whether HA will be on or off - only for dedicated - --really whether to skip the confirmation prompt -""" - action = 'add' - options = ['really', 'ha'] - - def execute(self, args): - mgr = SoftLayer.FirewallManager(self.client) - input_id = helpers.resolve_id( - mgr.resolve_ids, args.get(''), 'firewall') - ha_support = args.get('--ha', False) - if not args['--really']: - if args['--vlan']: - pkg = mgr.get_dedicated_package(ha_enabled=ha_support) - elif args['--cci']: - pkg = mgr.get_standard_package(input_id) - elif args['--server']: - pkg = mgr.get_standard_package(input_id, is_cci=False) - - if not pkg: - return "Unable to add firewall - Is network public enabled?" - print_package_info(pkg) - - if not formatting.confirm("This action will incur charges on your " - "account. Continue?"): - raise exceptions.CLIAbort('Aborted.') - - if args['--vlan']: - mgr.add_vlan_firewall(input_id, ha_enabled=ha_support) - elif args['--cci']: - mgr.add_standard_firewall(input_id, is_cci=True) - elif args['--server']: - mgr.add_standard_firewall(input_id, is_cci=False) - - return "Firewall is being created!" - - -class FWDetails(environment.CLIRunnable): - """ -usage: sl firewall detail [options] - -Get firewall details -""" - action = 'detail' - - def execute(self, args): - mgr = SoftLayer.FirewallManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - if key_value[0] == 'vlan': - rules = mgr.get_dedicated_fwl_rules(key_value[1]) - else: - rules = mgr.get_standard_fwl_rules(key_value[1]) - - return get_rules_table(rules) - - -class FWEdit(environment.CLIRunnable): - """ -usage: sl firewall edit [options] - -Edit the rules for a firewall -""" - action = 'edit' - - def execute(self, args): - mgr = SoftLayer.FirewallManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - firewall_id = int(key_value[1]) - if key_value[0] == 'vlan': - orig_rules = mgr.get_dedicated_fwl_rules(firewall_id) - else: - orig_rules = mgr.get_standard_fwl_rules(firewall_id) - # open an editor for the user to enter their rules - edited_rules = open_editor(rules=orig_rules) - print(edited_rules) - if formatting.confirm("Would you like to submit the rules. " - "Continue?"): - while True: - try: - rules = parse_rules(edited_rules) - if key_value[0] == 'vlan': - rules = mgr.edit_dedicated_fwl_rules(firewall_id, - rules) - else: - rules = mgr.edit_standard_fwl_rules(firewall_id, - rules) - break - except (SoftLayer.SoftLayerError, ValueError) as error: - print("Unexpected error({%s})" % (error)) - if formatting.confirm("Would you like to continue editing " - "the rules. Continue?"): - edited_rules = open_editor(content=edited_rules) - print(edited_rules) - if formatting.confirm("Would you like to submit the " - "rules. Continue?"): - continue - else: - raise exceptions.CLIAbort('Aborted.') - else: - raise exceptions.CLIAbort('Aborted.') - return 'Firewall updated!' - else: - raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/modules/globalip.py b/SoftLayer/CLI/modules/globalip.py deleted file mode 100644 index 6f87c2a10..000000000 --- a/SoftLayer/CLI/modules/globalip.py +++ /dev/null @@ -1,170 +0,0 @@ -""" -usage: sl globalip [] [...] [options] - -Orders or configures global IP addresses - -The available commands are: - assign Assign a target to a global IP address - cancel Cancels a global IP - create Orders a new global IP address - list Display a list of global IP addresses - unassign Unassigns a global IP -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers - - -class GlobalIpAssign(environment.CLIRunnable): - """ -usage: sl globalip assign [options] - -Assigns a global IP to a target. - -Required: - The ID or address of the global IP - The IP address to assign to the global IP -""" - action = 'assign' - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, - args.get(''), - name='global ip') - mgr.assign_global_ip(global_ip_id, args['']) - - -class GlobalIpCancel(environment.CLIRunnable): - """ -usage: sl globalip cancel [options] - -Cancel a subnet -""" - - action = 'cancel' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, - args.get(''), - name='global ip') - - if args['--really'] or formatting.no_going_back(global_ip_id): - mgr.cancel_global_ip(global_ip_id) - else: - raise exceptions.CLIAbort('Aborted') - - -class GlobalIpCreate(environment.CLIRunnable): - """ -usage: - sl globalip create [options] - -Add a new global IP address to your account. - -Options: - --v6 Orders IPv6 - --test Do not order the IP; just get a quote -""" - action = 'create' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - - version = 4 - if args.get('--v6'): - version = 6 - if not args.get('--test') and not args['--really']: - if not formatting.confirm("This action will incur charges on your " - "account. Continue?"): - raise exceptions.CLIAbort('Cancelling order.') - result = mgr.add_global_ip(version=version, - test_order=args.get('--test')) - - table = formatting.Table(['Item', 'cost']) - table.align['Item'] = 'r' - table.align['cost'] = 'r' - - total = 0.0 - for price in result['orderDetails']['prices']: - total += float(price.get('recurringFee', 0.0)) - rate = "%.2f" % float(price['recurringFee']) - - table.add_row([price['item']['description'], rate]) - - table.add_row(['Total monthly cost', "%.2f" % total]) - return table - - -class GlobalIpList(environment.CLIRunnable): - """ -usage: sl globalip list [options] - -Displays a list of global IPs - -Filters: - --v4 Display only IPV4 - --v6 Display only IPV6 -""" - action = 'list' - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - - table = formatting.Table(['id', 'ip', 'assigned', 'target']) - table.sortby = args.get('--sortby') or 'id' - - version = 0 - if args.get('--v4'): - version = 4 - elif args.get('--v6'): - version = 6 - - ips = mgr.list_global_ips(version=version) - - for ip_address in ips: - assigned = 'No' - target = 'None' - if ip_address.get('destinationIpAddress'): - dest = ip_address['destinationIpAddress'] - assigned = 'Yes' - target = dest['ipAddress'] - virtual_guest = dest.get('virtualGuest') - if virtual_guest: - target += (' (%s)' - % virtual_guest['fullyQualifiedDomainName']) - elif ip_address['destinationIpAddress'].get('hardware'): - target += (' (%s)' - % dest['hardware']['fullyQualifiedDomainName']) - - table.add_row([ip_address['id'], - ip_address['ipAddress']['ipAddress'], - assigned, - target]) - return table - - -class GlobalIpUnassign(environment.CLIRunnable): - """ -usage: sl globalip unassign [options] - -Unassigns a global IP from a target. - -Required: - The ID or address of the global IP -""" - action = 'unassign' - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, - args.get(''), - name='global ip') - mgr.unassign_global_ip(global_ip_id) diff --git a/SoftLayer/CLI/modules/help.py b/SoftLayer/CLI/modules/help.py deleted file mode 100644 index 0866687e1..000000000 --- a/SoftLayer/CLI/modules/help.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -usage: sl help [options] - sl help [options] - sl help [options] - -View help on a module or command. -""" -# :license: MIT, see LICENSE for more details. -# Missing docstrings ignored due to __doc__ = __doc__ magic -# pylint: disable=C0111 - -from SoftLayer.CLI import core -from SoftLayer.CLI import environment - - -class Show(environment.CLIRunnable): - # Use the same documentation as the module - __doc__ = __doc__ - action = None - - def execute(self, args): - parser = core.CommandParser(self.env) - if not any([args[''], args['']]): - return parser.get_module_help('help') - - self.env.load_module(args['']) - - if args['']: - return parser.get_command_help(args[''], args['']) - elif args['']: - return parser.get_module_help(args['']) diff --git a/SoftLayer/CLI/modules/image.py b/SoftLayer/CLI/modules/image.py deleted file mode 100644 index 5075eed60..000000000 --- a/SoftLayer/CLI/modules/image.py +++ /dev/null @@ -1,175 +0,0 @@ -""" -usage: sl image [] [...] [options] - -Manage compute images - -The available commands are: - delete Delete an image - detail Output details about an image - list List images - edit Edit an image -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer import utils - -MASK = ('id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,' - 'imageType') -DETAIL_MASK = MASK + (',children[id,blockDevicesDiskSpaceTotal,datacenter],' - 'note,createDate,status') -PUBLIC_TYPE = formatting.FormattedItem('PUBLIC', 'Public') -PRIVATE_TYPE = formatting.FormattedItem('PRIVATE', 'Private') - - -class ListImages(environment.CLIRunnable): - """ -usage: sl image list [--public | --private] [options] - -List images - -Options: - --private Display only private images - --public Display only public images -""" - action = 'list' - - def execute(self, args): - image_mgr = SoftLayer.ImageManager(self.client) - - neither = not any([args['--private'], args['--public']]) - - images = [] - if args['--private'] or neither: - for image in image_mgr.list_private_images(mask=MASK): - images.append(image) - - if args['--public'] or neither: - for image in image_mgr.list_public_images(mask=MASK): - images.append(image) - - table = formatting.Table(['id', - 'account', - 'name', - 'type', - 'visibility', - 'global_identifier']) - - images = [image for image in images if image['parentId'] == ''] - for image in images: - - table.add_row([ - image['id'], - image.get('accountId', formatting.blank()), - image['name'].strip(), - formatting.FormattedItem( - utils.lookup(image, 'imageType', 'keyName'), - utils.lookup(image, 'imageType', 'name')), - PUBLIC_TYPE if image['publicFlag'] else PRIVATE_TYPE, - image.get('globalIdentifier', formatting.blank()), - ]) - - return table - - -class DetailImage(environment.CLIRunnable): - """ -usage: sl image detail [options] - -Get details for an image -""" - action = 'detail' - - def execute(self, args): - image_mgr = SoftLayer.ImageManager(self.client) - image_id = helpers.resolve_id(image_mgr.resolve_ids, - args.get(''), - 'image') - - image = image_mgr.get_image(image_id, mask=DETAIL_MASK) - disk_space = 0 - datacenters = [] - for child in image.get('children'): - disk_space = int(child.get('blockDevicesDiskSpaceTotal', 0)) - if child.get('datacenter'): - datacenters.append(utils.lookup(child, 'datacenter', 'name')) - - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - - table.add_row(['id', image['id']]) - table.add_row(['global_identifier', - image.get('globalIdentifier', formatting.blank())]) - table.add_row(['name', image['name'].strip()]) - table.add_row(['status', formatting.FormattedItem( - utils.lookup(image, 'status', 'keyname'), - utils.lookup(image, 'status', 'name'), - )]) - table.add_row(['account', image.get('accountId', formatting.blank())]) - table.add_row(['visibility', - PUBLIC_TYPE if image['publicFlag'] else PRIVATE_TYPE]) - table.add_row(['type', - formatting.FormattedItem( - utils.lookup(image, 'imageType', 'keyName'), - utils.lookup(image, 'imageType', 'name'), - )]) - table.add_row(['flex', image.get('flexImageFlag')]) - table.add_row(['note', image.get('note')]) - table.add_row(['created', image.get('createDate')]) - table.add_row(['disk_space', formatting.b_to_gb(disk_space)]) - table.add_row(['datacenters', formatting.listing(sorted(datacenters), - separator=',')]) - - return table - - -class DeleteImage(environment.CLIRunnable): - """ -usage: sl image delete [options] - -Get details for an image -""" - action = 'delete' - - def execute(self, args): - image_mgr = SoftLayer.ImageManager(self.client) - image_id = helpers.resolve_id(image_mgr.resolve_ids, - args.get(''), - 'image') - - image_mgr.delete_image(image_id) - - -class EditImage(environment.CLIRunnable): - """ -usage: sl image edit [--tag=Tag...] [options] - -Edit Details for an image - -Options: - --name=Name Name of the Image - --note=Note Note of the Image - --tag=TAG... Tags of the Image. Can be specified multiple times. - -Note: Image to be edited must be private -""" - action = 'edit' - - def execute(self, args): - image_mgr = SoftLayer.ImageManager(self.client) - data = {} - if args.get('--name'): - data['name'] = args.get('--name') - if args.get('--note'): - data['note'] = args.get('--note') - if args.get('--tag'): - data['tag'] = args.get('--tag') - image_id = helpers.resolve_id(image_mgr.resolve_ids, - args.get(''), 'image') - if not image_mgr.edit(image_id, **data): - raise exceptions.CLIAbort("Failed to Edit Image") diff --git a/SoftLayer/CLI/modules/iscsi.py b/SoftLayer/CLI/modules/iscsi.py deleted file mode 100644 index a0d5bbd41..000000000 --- a/SoftLayer/CLI/modules/iscsi.py +++ /dev/null @@ -1,189 +0,0 @@ -""" -usage: sl iscsi [] [...] [options] - -Manage, order, delete iSCSI targets - -The available commands are: - cancel Cancel an existing iSCSI target - create Order and create an iSCSI target - detail Output details about an iSCSI - list List iSCSI targets on the account - -For several commands, will be asked for. This will be the id -for iSCSI target. -""" -# from SoftLayer.CLI import (CLIRunnable, Table, no_going_back, FormattedItem) -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer import utils - - -class ListISCSIs(environment.CLIRunnable): - - """ -usage: sl iscsi list [options] - -List iSCSI targets -""" - action = 'list' - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - iscsi_list = iscsi_mgr.list_iscsi() - iscsi_list = [utils.NestedDict(n) for n in iscsi_list] - table = formatting.Table([ - 'id', - 'datacenter', - 'size', - 'username', - 'password', - 'server' - ]) - for iscsi in iscsi_list: - table.add_row([ - iscsi['id'], - iscsi['serviceResource']['datacenter'].get('name', - formatting.blank()), - formatting.FormattedItem( - iscsi.get('capacityGb', formatting.blank()), - "%dGB" % iscsi.get('capacityGb', 0)), - iscsi.get('username', formatting.blank()), - iscsi.get('password', formatting.blank()), - iscsi.get('serviceResourceBackendIpAddress', - formatting.blank())]) - return table - - -class CreateISCSI(environment.CLIRunnable): - - """ -usage: sl iscsi create [options] - -Orders and creates an iSCSI target. - -Examples: - sl iscsi create --size=1 --datacenter=dal05 - sl iscsi create --size 1 -d dal05 - sl iscsi create -s 1 -d dal05 - -Required: - -s, --size=SIZE Size of the iSCSI volume to create - -d, --datacenter=DC Datacenter shortname (sng01, dal05, ...) -""" - action = 'create' - options = ['confirm'] - required_params = ['--size', '--datacenter'] - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - self._validate_create_args(args) - size, location = self._parse_create_args(args) - iscsi_mgr.create_iscsi(size=size, location=location) - - def _parse_create_args(self, args): - """ Converts CLI arguments to arguments that can be passed into - ISCSIManager.create_iscsi. - :param dict args: CLI arguments - """ - size = args['--size'] - location = args['--datacenter'] - return int(size), str(location) - - def _validate_create_args(self, args): - """ Raises an ArgumentError if the given arguments are not valid """ - invalid_args = [k for k in self.required_params if args.get(k) is None] - if invalid_args: - raise exceptions.ArgumentError('Missing required options: %s' - % ','.join(invalid_args)) - - -class CancelISCSI(environment.CLIRunnable): - - """ -usage: sl iscsi cancel [options] - -Cancel existing iSCSI - -Examples: - sl iscsi cancel 12345 - sl iscsi cancel 12345 --immediate - sl iscsi cancel 12345 --immediate --reason='no longer needed' - -options : - --immediate Cancels the iSCSI immediately (instead of on the billing - anniversary) - --reason=REASON An optional reason for cancellation. -""" - action = 'cancel' - options = ['confirm'] - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - iscsi_id = helpers.resolve_id( - iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') - - immediate = args.get('--immediate', False) - - reason = args.get('--reason') - if args['--really'] or formatting.no_going_back(iscsi_id): - iscsi_mgr.cancel_iscsi(iscsi_id, reason, immediate) - else: - raise exceptions.CLIAbort('Aborted') - - -class ISCSIDetails(environment.CLIRunnable): - - """ -usage: sl iscsi detail [--password] [options] - -Get details for an iSCSI - -Examples: - sl iscsi detail 12345 - sl iscsi detail 12345 --password - -Options: - --password Show credentials to access the iSCSI target -""" - action = 'detail' - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - - iscsi_id = helpers.resolve_id( - iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') - result = iscsi_mgr.get_iscsi(iscsi_id) - result = utils.NestedDict(result) - - table.add_row(['id', result['id']]) - table.add_row(['serviceResourceName', result['serviceResourceName']]) - table.add_row(['createDate', result['createDate']]) - table.add_row(['nasType', result['nasType']]) - table.add_row(['capacityGb', result['capacityGb']]) - if result['snapshotCapacityGb']: - table.add_row(['snapshotCapacityGb', result['snapshotCapacityGb']]) - table.add_row(['mountableFlag', result['mountableFlag']]) - table.add_row( - ['serviceResourceBackendIpAddress', - result['serviceResourceBackendIpAddress']]) - table.add_row(['price', result['billingItem']['recurringFee']]) - table.add_row(['BillingItemId', result['billingItem']['id']]) - if result.get('notes'): - table.add_row(['notes', result['notes']]) - - if args.get('--password'): - pass_table = formatting.Table(['username', 'password']) - pass_table.add_row([result['username'], result['password']]) - table.add_row(['users', pass_table]) - - return table diff --git a/SoftLayer/CLI/modules/loadbal.py b/SoftLayer/CLI/modules/loadbal.py deleted file mode 100755 index cd1e46c00..000000000 --- a/SoftLayer/CLI/modules/loadbal.py +++ /dev/null @@ -1,591 +0,0 @@ -""" -usage: sl loadbal [] [...] [options] - -Local LoadBalancer management - -The available commands are: - cancel Cancel an existing load balancer - create Create a new load balancer - create-options Lists the different packages for load balancers - detail Provide details about a particular load balancer - group-add Add a new service group in the load balancer - group-delete Delete a service group from the load balancer - group-edit Edit the properties of a service group - group-reset Resets all the connections on a service group - health-checks List the different health check values - list List active load balancers - routing-methods List supported routing methods - routing-types List supported routing types - service-add Add a service to an existing service group - service-delete Delete an existing service - service-edit Edit an existing service - service-toggle Toggle the status of the service -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers - - -def get_ids(input_id): - """ Helper package to retrieve the actual IDs - - :param input_id: the ID provided by the user - :returns: A list of valid IDs - """ - key_value = input_id.split(':') - if len(key_value) != 2: - raise exceptions.CLIAbort( - 'Invalid ID %s: ID should be of the form xxx:yyy' % input_id) - return key_value - - -def get_local_lbs_table(load_balancers): - """ Helper package to format the local load balancers into a table. - - :param dict load_balancers: A dictionary representing the load_balancers - :returns: A table containing the local load balancers - """ - table = formatting.Table(['ID', - 'VIP Address', - 'Location', - 'SSL Offload', - 'Connections/second', - 'Type']) - - table.align['Connections/second'] = 'r' - - for load_balancer in load_balancers: - ssl_support = 'Not Supported' - if load_balancer['sslEnabledFlag']: - if load_balancer['sslActiveFlag']: - ssl_support = 'On' - else: - ssl_support = 'Off' - lb_type = 'Standard' - if load_balancer['dedicatedFlag']: - lb_type = 'Dedicated' - elif load_balancer['highAvailabilityFlag']: - lb_type = 'HA' - table.add_row([ - 'local:%s' % load_balancer['id'], - load_balancer['ipAddress']['ipAddress'], - load_balancer['loadBalancerHardware'][0]['datacenter']['name'], - ssl_support, - load_balancer['connectionLimit'], - lb_type - ]) - return table - - -def get_local_lb_table(load_balancer): - """ Helper package to format the local loadbal details into a table. - - :param dict load_balancer: A dictionary representing the loadbal - :returns: A table containing the local loadbal details - """ - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'l' - table.align['Value'] = 'l' - table.add_row(['General properties', '----------']) - table.add_row([' ID', 'local:%s' % load_balancer['id']]) - table.add_row([' IP Address', load_balancer['ipAddress']['ipAddress']]) - name = load_balancer['loadBalancerHardware'][0]['datacenter']['name'] - table.add_row([' Datacenter', name]) - table.add_row([' Connections limit', load_balancer['connectionLimit']]) - table.add_row([' Dedicated', load_balancer['dedicatedFlag']]) - table.add_row([' HA', load_balancer['highAvailabilityFlag']]) - table.add_row([' SSL Enabled', load_balancer['sslEnabledFlag']]) - table.add_row([' SSL Active', load_balancer['sslActiveFlag']]) - index0 = 1 - for virtual_server in load_balancer['virtualServers']: - table.add_row(['Service group %s' % index0, - '**************']) - index0 += 1 - table2 = formatting.Table(['Service group ID', - 'Port', - 'Allocation', - 'Routing type', - 'Routing Method']) - - for group in virtual_server['serviceGroups']: - table2.add_row([ - '%s:%s' % (load_balancer['id'], virtual_server['id']), - virtual_server['port'], - '%s %%' % virtual_server['allocation'], - '%s:%s' % (group['routingTypeId'], - group['routingType']['name']), - '%s:%s' % (group['routingMethodId'], - group['routingMethod']['name']) - ]) - - table.add_row([' Group Properties', table2]) - - table3 = formatting.Table(['Service_ID', - 'IP Address', - 'Port', - 'Health Check', - 'Weight', - 'Enabled', - 'Status']) - service_exist = False - for service in group['services']: - service_exist = True - health_check = service['healthChecks'][0] - table3.add_row([ - '%s:%s' % (load_balancer['id'], service['id']), - service['ipAddress']['ipAddress'], - service['port'], - '%s:%s' % (health_check['healthCheckTypeId'], - health_check['type']['name']), - service['groupReferences'][0]['weight'], - service['enabled'], - service['status'] - ]) - if service_exist: - table.add_row([' Services', table3]) - else: - table.add_row([' Services', 'None']) - return table - - -class LoadBalancerList(environment.CLIRunnable): - """ -usage: sl loadbal list [options] - -List active load balancers - -""" - action = 'list' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - - load_balancers = mgr.get_local_lbs() - return get_local_lbs_table(load_balancers) - - -class LoadBalancerHealthChecks(environment.CLIRunnable): - """ -usage: sl loadbal health-checks [options] - -List load balancer service health check types that can be used -""" - action = 'health-checks' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - - hc_types = mgr.get_hc_types() - table = formatting.KeyValueTable(['ID', 'Name']) - table.align['ID'] = 'l' - table.align['Name'] = 'l' - table.sortby = 'ID' - for hc_type in hc_types: - table.add_row([hc_type['id'], hc_type['name']]) - return table - - -class LoadBalancerRoutingMethods(environment.CLIRunnable): - """ -usage: sl loadbal routing-methods [options] - -List load balancers routing methods that can be used -""" - action = 'routing-methods' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - - routing_methods = mgr.get_routing_methods() - table = formatting.KeyValueTable(['ID', 'Name']) - table.align['ID'] = 'l' - table.align['Name'] = 'l' - table.sortby = 'ID' - for routing_method in routing_methods: - table.add_row([routing_method['id'], routing_method['name']]) - return table - - -class LoadBalancerRoutingTypes(environment.CLIRunnable): - """ -usage: sl loadbal routing-types [options] - -List load balancers routing types that can be used -""" - action = 'routing-types' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - - routing_types = mgr.get_routing_types() - table = formatting.KeyValueTable(['ID', 'Name']) - table.align['ID'] = 'l' - table.align['Name'] = 'l' - table.sortby = 'ID' - for routing_type in routing_types: - table.add_row([routing_type['id'], routing_type['name']]) - return table - - -class LoadBalancerDetails(environment.CLIRunnable): - """ -usage: sl loadbal detail [options] - -Get Load balancer details - -""" - action = 'detail' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - - input_id = args.get('') - - key_value = get_ids(input_id) - loadbal_id = int(key_value[1]) - - load_balancer = mgr.get_local_lb(loadbal_id) - return get_local_lb_table(load_balancer) - - -class LoadBalancerCancel(environment.CLIRunnable): - """ -usage: sl loadbal cancel [options] - -Cancels an existing load_balancer - -""" - action = 'cancel' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - loadbal_id = int(key_value[1]) - - if args['--really'] or formatting.confirm("This action will cancel a " - "load balancer. Continue?"): - mgr.cancel_lb(loadbal_id) - return 'Load Balancer with id %s is being cancelled!' % input_id - else: - raise exceptions.CLIAbort('Aborted.') - - -class LoadBalancerServiceDelete(environment.CLIRunnable): - """ -usage: sl loadbal service-delete [options] - -Deletes an existing load_balancer service - -""" - action = 'service-delete' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - service_id = int(key_value[1]) - - if args['--really'] or formatting.confirm("This action will cancel a " - "service from your load " - "balancer. Continue?"): - mgr.delete_service(service_id) - return 'Load balancer service %s is being cancelled!' % input_id - else: - raise exceptions.CLIAbort('Aborted.') - - -class LoadBalancerServiceToggle(environment.CLIRunnable): - """ -usage: sl loadbal service-toggle [options] - -Toggle the status of an existing load_balancer service - -""" - action = 'service-toggle' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - service_id = int(key_value[1]) - - if args['--really'] or formatting.confirm("This action will toggle " - "the status on the service. " - "Continue?"): - mgr.toggle_service_status(service_id) - return 'Load balancer service %s status updated!' % input_id - else: - raise exceptions.CLIAbort('Aborted.') - - -class LoadBalancerServiceEdit(environment.CLIRunnable): - """ -usage: sl loadbal service-edit [options] - -Enable an existing load_balancer service -Options: ---enabled=ENABLED Set to 1 to enable the service, or 0 to disable ---port=PORT Change the value of the port ---weight=WEIGHT Change the weight of the service ---hc_type=HCTYPE Change the health check type ---ip=IP Change the IP of the service - -""" - action = 'service-edit' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - loadbal_id = int(key_value[0]) - service_id = int(key_value[1]) - - # check if any input is provided - if not (args['--ip'] or args['--enabled'] or args['--weight'] - or args['--port'] or args['--hc_type']): - return 'At least one property is required to be changed!' - - # check if the IP is valid - ip_address_id = None - if args['--ip']: - ip_address = mgr.get_ip_address(args['--ip']) - if not ip_address: - return 'Provided IP address is not valid!' - else: - ip_address_id = ip_address['id'] - - mgr.edit_service(loadbal_id, - service_id, - ip_address_id=ip_address_id, - enabled=args.get('--enabled'), - port=args.get('--port'), - weight=args.get('--weight'), - hc_type=args.get('--hc_type')) - return 'Load balancer service %s is being modified!' % input_id - - -class LoadBalancerServiceAdd(environment.CLIRunnable): - """ -usage: sl loadbal service-add --ip=IP --port=PORT \ ---weight=WEIGHT --hc_type=HCTYPE --enabled=ENABLED [options] - -Adds a new load_balancer service -Required: ---enabled=ENABLED Set to 1 to enable the service, 0 to disable [default: 1]. ---port=PORT Set to the desired port value [default: 80]. ---weight=WEIGHT Set to the desired weight value [default: 1]. ---hc_type=HCTYPE Set to the desired health check value [default: 21]. ---ip=IP Set to the desired IP value. - -""" - action = 'service-add' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - loadbal_id = int(key_value[0]) - group_id = int(key_value[1]) - - # check if the IP is valid - ip_address = None - if args['--ip']: - ip_address = mgr.get_ip_address(args['--ip']) - if not ip_address: - return 'Provided IP address is not valid!' - - mgr.add_service(loadbal_id, - group_id, - ip_address_id=ip_address['id'], - enabled=args.get('--enabled'), - port=args.get('--port'), - weight=args.get('--weight'), - hc_type=args.get('--hc_type')) - return 'Load balancer service is being added!' - - -class LoadBalancerServiceGroupDelete(environment.CLIRunnable): - """ -usage: sl loadbal group-delete [options] - -Deletes an existing load_balancer service group - -""" - action = 'group-delete' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - group_id = int(key_value[1]) - - if args['--really'] or formatting.confirm("This action will cancel a " - "service group. Continue?"): - mgr.delete_service_group(group_id) - return 'Service group %s is being deleted!' % input_id - else: - raise exceptions.CLIAbort('Aborted.') - - -class LoadBalancerServiceGroupEdit(environment.CLIRunnable): - """ -usage: sl loadbal group-edit [options] - -Edits an existing load_balancer service group -Options: ---allocation=PERC Change the allocated % of connections ---port=PORT Change the port ---routing_type=TYPE Change the port routing type ---routing_method=METHOD Change the routing method - -""" - action = 'group-edit' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - loadbal_id = int(key_value[0]) - group_id = int(key_value[1]) - - # check if any input is provided - if not (args['--allocation'] or args['--port'] - or args['--routing_type'] or args['--routing_method']): - return 'At least one property is required to be changed!' - - routing_type = args.get('--routing_type') - routing_method = args.get('--routing_method') - - mgr.edit_service_group(loadbal_id, - group_id, - allocation=args.get('--allocation'), - port=args.get('--port'), - routing_type=routing_type, - routing_method=routing_method) - - return 'Load balancer service group %s is being updated!' % input_id - - -class LoadBalancerServiceGroupReset(environment.CLIRunnable): - """ -usage: sl loadbal group-reset [options] - -Resets the connections on a certain service group - -""" - action = 'group-reset' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - loadbal_id = int(key_value[0]) - group_id = int(key_value[1]) - - mgr.reset_service_group(loadbal_id, group_id) - return 'Load balancer service group connections are being reset!' - - -class LoadBalancerServiceGroupAdd(environment.CLIRunnable): - """ -usage: sl loadbal group-add --allocation=PERC --port=PORT \ ---routing_type=TYPE --routing_method=METHOD [options] - -Adds a new load_balancer service -Required: ---allocation=PERC The % of connections that will be allocated ---port=PORT The virtual port number for the group ---routing_type=TYPE The routing type for the group ---routing_method=METHOD The routing method for the group - -""" - action = 'group-add' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - key_value = get_ids(input_id) - - loadbal_id = int(key_value[1]) - - mgr.add_service_group(loadbal_id, - allocation=int(args.get('--allocation')), - port=int(args.get('--port')), - routing_type=int(args.get('--routing_type')), - routing_method=int(args.get('--routing_method'))) - - return 'Load balancer service group is being added!' - - -class LoadBalancerCreate(environment.CLIRunnable): - """ -usage: sl loadbal create (--datacenter=DC) [options] - -Adds a load_balancer given the billing id returned from create-options - -Options: - -d, --datacenter=DC Datacenter shortname (sng01, dal05, ...) - Note: Omitting this value defaults to the first - available datacenter -""" - action = 'create' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = helpers.resolve_id( - mgr.resolve_ids, args.get(''), 'load_balancer') - if not formatting.confirm("This action will incur charges on your " - "account. Continue?"): - raise exceptions.CLIAbort('Aborted.') - mgr.add_local_lb(input_id, datacenter=args['--datacenter']) - return "Load balancer is being created!" - - -class CreateOptionsLoadBalancer(environment.CLIRunnable): - """ -usage: sl loadbal create-options - -Output available options when adding a new load balancer - -""" - action = 'create-options' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - - table = formatting.Table(['id', 'capacity', 'description', 'price']) - - table.sortby = 'price' - table.align['price'] = 'r' - table.align['capacity'] = 'r' - table.align['id'] = 'r' - - packages = mgr.get_lb_pkgs() - - for package in packages: - table.add_row([ - package['prices'][0]['id'], - package.get('capacity'), - package['description'], - format(float(package['prices'][0]['recurringFee']), '.2f') - ]) - - return table diff --git a/SoftLayer/CLI/modules/messaging.py b/SoftLayer/CLI/modules/messaging.py deleted file mode 100644 index 0c39a8ad0..000000000 --- a/SoftLayer/CLI/modules/messaging.py +++ /dev/null @@ -1,522 +0,0 @@ -""" -usage: sl messaging [] [...] [options] - -Manage the SoftLayer Message Queue service. For most commands, a queue account -is required. Use 'sl messaging accounts-list' to list current accounts - -The available commands are: - accounts-list List all queue accounts - endpoints-list List all service endpoints - ping Ping the service - - queue-add Create a new queue - queue-detail Prints the details of a queue - queue-edit Modifies an existing queue - queue-list Lists out all queues on an account - queue-pop Pop a message from a queue - queue-push Pushes a message into a queue - queue-remove Delete a queue - - topic-add Creates a new topic - topic-detail Prints the details of a topic - topic-list Lists out all topics on an account - topic-push Pushes a notification to a topic - topic-remove Deletes a topic - topic-subscribe Adds a subscription on a topic - topic-unsubscribe Remove a subscription on a topic - -""" -# :license: MIT, see LICENSE for more details. -# Missing docstrings ignored due to __doc__ = __doc__ magic -# pylint: disable=C0111 -import sys - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting - - -COMMON_MESSAGING_ARGS = """Service Options: - --datacenter=NAME Datacenter, E.G.: dal05 - --network=TYPE Network type, [Options: public, private] -""" - - -class ListAccounts(environment.CLIRunnable): - """ -usage: sl messaging accounts-list [options] - -List SoftLayer Message Queue Accounts - -""" - action = 'accounts-list' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - accounts = manager.list_accounts() - - table = formatting.Table([ - 'id', 'name', 'status' - ]) - for account in accounts: - if not account['nodes']: - continue - - table.add_row([ - account['nodes'][0]['accountName'], - account['name'], - account['status']['name'], - ]) - - return table - - -class ListEndpoints(environment.CLIRunnable): - """ -usage: sl messaging endpoints-list [options] - -List SoftLayer Message Queue Endpoints - -""" - action = 'endpoints-list' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - regions = manager.get_endpoints() - - table = formatting.Table([ - 'name', 'public', 'private' - ]) - for region, endpoints in regions.items(): - table.add_row([ - region, - endpoints.get('public') or formatting.blank(), - endpoints.get('private') or formatting.blank(), - ]) - - return table - - -class Ping(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging ping [options] - -Ping the SoftLayer Message Queue service - -""" + COMMON_MESSAGING_ARGS - action = 'ping' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - okay = manager.ping( - datacenter=args['--datacenter'], network=args['--network']) - if okay: - return 'OK' - else: - exceptions.CLIAbort('Ping failed') - - -def queue_table(queue): - """ Returns a table with details about a queue """ - table = formatting.Table(['property', 'value']) - table.align['property'] = 'r' - table.align['value'] = 'l' - - table.add_row(['name', queue['name']]) - table.add_row(['message_count', queue['message_count']]) - table.add_row(['visible_message_count', queue['visible_message_count']]) - table.add_row(['tags', formatting.listing(queue['tags'] or [])]) - table.add_row(['expiration', queue['expiration']]) - table.add_row(['visibility_interval', queue['visibility_interval']]) - return table - - -def message_table(message): - """ Returns a table with details about a message """ - table = formatting.Table(['property', 'value']) - table.align['property'] = 'r' - table.align['value'] = 'l' - - table.add_row(['id', message['id']]) - table.add_row(['initial_entry_time', message['initial_entry_time']]) - table.add_row(['visibility_delay', message['visibility_delay']]) - table.add_row(['visibility_interval', message['visibility_interval']]) - table.add_row(['fields', message['fields']]) - return [table, message['body']] - - -def topic_table(topic): - """ Returns a table with details about a topic """ - table = formatting.Table(['property', 'value']) - table.align['property'] = 'r' - table.align['value'] = 'l' - - table.add_row(['name', topic['name']]) - table.add_row(['tags', formatting.listing(topic['tags'] or [])]) - return table - - -def subscription_table(sub): - """ Returns a table with details about a subscription """ - table = formatting.Table(['property', 'value']) - table.align['property'] = 'r' - table.align['value'] = 'l' - - table.add_row(['id', sub['id']]) - table.add_row(['endpoint_type', sub['endpoint_type']]) - for key, val in sub['endpoint'].items(): - table.add_row([key, val]) - return table - - -class QueueList(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-list [options] - -List all queues on an account - -""" + COMMON_MESSAGING_ARGS - action = 'queue-list' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - - queues = mq_client.get_queues()['items'] - - table = formatting.Table([ - 'name', 'message_count', 'visible_message_count' - ]) - for queue in queues: - table.add_row([ - queue['name'], - queue['message_count'], - queue['visible_message_count'], - ]) - return table - - -class QueueDetail(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-detail [options] - -Detail a queue - -""" + COMMON_MESSAGING_ARGS - action = 'queue-detail' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - queue = mq_client.get_queue(args['']) - return queue_table(queue) - - -class QueueCreate(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-add [options] - -Create a queue - -Options: - --visibility_interval=SECONDS Time in seconds that messages will re-appear - after being popped - --expiration=SECONDS Time in seconds that messages will live - --tags=TAGS Comma-separated list of tags - -""" + COMMON_MESSAGING_ARGS - action = 'queue-add' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - tags = None - if args.get('--tags'): - tags = [tag.strip() for tag in args.get('--tags').split(',')] - - queue = mq_client.create_queue( - args[''], - visibility_interval=int(args.get('--visibility_interval') or 30), - expiration=int(args.get('--expiration') or 604800), - tags=tags, - ) - return queue_table(queue) - - -class QueueModify(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-edit [options] - -Modify a queue - -Options: - --visibility_interval=SECONDS Time in seconds that messages will re-appear - after being popped - --expiration=SECONDS Time in seconds that messages will live - --tags=TAGS Comma-separated list of tags - -""" + COMMON_MESSAGING_ARGS - action = 'queue-edit' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - tags = None - if args.get('--tags'): - tags = [tag.strip() for tag in args.get('--tags').split(',')] - - queue = mq_client.create_queue( - args[''], - visibility_interval=int(args.get('--visibility_interval') or 30), - expiration=int(args.get('--expiration') or 604800), - tags=tags, - ) - return queue_table(queue) - - -class QueueDelete(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-remove [] - [options] - -Delete a queue or a queued message - -Options: - --force Flag to force the deletion of the queue even when there are messages - -""" + COMMON_MESSAGING_ARGS - action = 'queue-remove' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - - if args['']: - mq_client.delete_message(args[''], - args['']) - else: - mq_client.delete_queue(args[''], args.get('--force')) - - -class QueuePush(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-push ( | -) - [options] - -Push a message into a queue - -Options: - --force Flag to force the deletion of the queue even when there are messages - -""" + COMMON_MESSAGING_ARGS - action = 'queue-push' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - body = '' - if args[''] == '-': - body = sys.stdin.read() - else: - body = args[''] - return message_table( - mq_client.push_queue_message(args[''], body)) - - -class QueuePop(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-pop [options] - -Pops a message from a queue - -Options: - --count=NUM Count of messages to pop - --delete-after Remove popped messages from the queue - -""" + COMMON_MESSAGING_ARGS - action = 'queue-pop' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - - messages = mq_client.pop_messages( - args[''], - args.get('--count') or 1) - formatted_messages = [] - for message in messages['items']: - formatted_messages.append(message_table(message)) - - if args.get('--delete-after'): - for message in messages['items']: - mq_client.delete_message( - args[''], - message['id']) - return formatted_messages - - -class TopicList(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-list [options] - -List all topics on an account - -""" + COMMON_MESSAGING_ARGS - action = 'topic-list' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - topics = mq_client.get_topics()['items'] - - table = formatting.Table(['name']) - for topic in topics: - table.add_row([topic['name']]) - return table - - -class TopicDetail(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-detail [options] - -Detail a topic - -""" + COMMON_MESSAGING_ARGS - action = 'topic-detail' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - topic = mq_client.get_topic(args['']) - subscriptions = mq_client.get_subscriptions(args['']) - tables = [] - for sub in subscriptions['items']: - tables.append(subscription_table(sub)) - return [topic_table(topic), tables] - - -class TopicCreate(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-add [options] - -Create a new topic - -""" + COMMON_MESSAGING_ARGS - action = 'topic-add' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - tags = None - if args.get('--tags'): - tags = [tag.strip() for tag in args.get('--tags').split(',')] - - topic = mq_client.create_topic( - args[''], - visibility_interval=int( - args.get('--visibility_interval') or 30), - expiration=int(args.get('--expiration') or 604800), - tags=tags, - ) - return topic_table(topic) - - -class TopicDelete(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-remove [options] - -Delete a topic or subscription - -Options: - --force Flag to force the deletion of the topic even when there are - subscriptions -""" + COMMON_MESSAGING_ARGS - action = 'topic-remove' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - mq_client.delete_topic(args[''], args.get('--force')) - - -class TopicSubscribe(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-subscribe [options] - -Create a subscription on a topic - -Options: - --type=TYPE Type of endpoint, [Options: http, queue] - --queue-name=NAME Queue name. Required if --type is queue - --http-method=METHOD HTTP Method to use if --type is http - --http-url=URL HTTP/HTTPS URL to use. Required if --type is http - --http-body=BODY HTTP Body template to use if --type is http - -""" + COMMON_MESSAGING_ARGS - action = 'topic-subscribe' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - if args['--type'] == 'queue': - subscription = mq_client.create_subscription( - args[''], - 'queue', - queue_name=args['--queue-name'], - ) - elif args['--type'] == 'http': - subscription = mq_client.create_subscription( - args[''], - 'http', - method=args['--http-method'] or 'GET', - url=args['--http-url'], - body=args['--http-body'] - ) - else: - raise exceptions.ArgumentError( - '--type should be either queue or http.') - return subscription_table(subscription) - - -class TopicUnsubscribe(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-unsubscribe - [options] - -Remove a subscription on a topic - -""" + COMMON_MESSAGING_ARGS - action = 'topic-unsubscribe' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - - mq_client.delete_subscription( - args[''], - args['']) - - -class TopicPush(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-push ( | -) - [options] - -Push a message into a topic - -""" + COMMON_MESSAGING_ARGS - action = 'topic-push' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - - # the message body comes from the positional argument or stdin - body = '' - if args[''] == '-': - body = sys.stdin.read() - else: - body = args[''] - return message_table( - mq_client.push_topic_message(args[''], body)) diff --git a/SoftLayer/CLI/modules/metadata.py b/SoftLayer/CLI/modules/metadata.py deleted file mode 100644 index 4bf89d9b4..000000000 --- a/SoftLayer/CLI/modules/metadata.py +++ /dev/null @@ -1,243 +0,0 @@ -""" -usage: sl metadata [] [...] [options] - -Find details about this machine. These commands only work on devices on the -backend SoftLayer network. This allows for self-discovery for newly provisioned -resources. - -The available commands are: - backend_ip Primary backend ip address - backend_mac Backend mac addresses - datacenter Datacenter name - datacenter_id Datacenter id - fqdn Fully qualified domain name - frontend_mac Frontend mac addresses - hostname Hostname - id Id - ip Primary ip address - network Details about either the public or private network - provision_state Provision state - tags Tags - user_data User-defined data -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting - - -class MetaRunnable(environment.CLIRunnable): - """ A CLIRunnable that raises a nice error on connection issues because - the metadata service is only accessable on a SoftLayer device - """ - def execute(self, args): - try: - return self._execute(args) - except SoftLayer.TransportError: - raise exceptions.CLIAbort( - 'Cannot connect to the backend service address. Make sure ' - 'this command is being ran from a device on the backend ' - 'network.') - - def _execute(self, _): - """ To be overridden exactly like the execute() method """ - pass - - -class BackendMacAddresses(MetaRunnable): - """ -usage: sl metadata backend_mac [options] - -List backend mac addresses -""" - action = 'backend_mac' - - def _execute(self, _): - backend_macs = SoftLayer.MetadataManager().get('backend_mac') - return formatting.listing(backend_macs, separator=',') - - -class Datacenter(MetaRunnable): - """ -usage: sl metadata datacenter [options] - -Get datacenter name -""" - action = 'datacenter' - - def _execute(self, _): - return SoftLayer.MetadataManager().get('datacenter') - - -class DatacenterId(MetaRunnable): - """ -usage: sl metadata datacenter_id [options] - -Get datacenter id -""" - action = 'datacenter_id' - - def _execute(self, _): - return str(SoftLayer.MetadataManager().get('datacenter_id')) - - -class FrontendMacAddresses(MetaRunnable): - """ -usage: sl metadata frontend_mac [options] - -List frontend mac addresses -""" - action = 'frontend_mac' - - def _execute(self, _): - frontend_macs = SoftLayer.MetadataManager().get('frontend_mac') - return formatting.listing(frontend_macs, separator=',') - - -class FullyQualifiedDomainName(MetaRunnable): - """ -usage: sl metadata fqdn [options] - -Get fully qualified domain name -""" - action = 'fqdn' - - def _execute(self, _): - return SoftLayer.MetadataManager().get('fqdn') - - -class Hostname(MetaRunnable): - """ -usage: sl metadata hostname [options] - -Get hostname -""" - action = 'hostname' - - def _execute(self, _): - return SoftLayer.MetadataManager().get('hostname') - - -class Id(MetaRunnable): - """ -usage: sl metadata id - -Get id -""" - action = 'id' - - def _execute(self, _): - return str(SoftLayer.MetadataManager().get('id')) - - -class PrimaryBackendIpAddress(MetaRunnable): - """ -usage: sl metadata backend_ip [options] - -Get primary backend ip address -""" - action = 'backend_ip' - - def _execute(self, _): - return SoftLayer.MetadataManager().get('primary_backend_ip') - - -class PrimaryIpAddress(MetaRunnable): - """ -usage: sl metadata ip [options] - -Get primary ip address -""" - action = 'ip' - - def _execute(self, _): - return SoftLayer.MetadataManager().get('primary_ip') - - -class ProvisionState(MetaRunnable): - """ -usage: sl metadata provision_state [options] - -Get provision state -""" - action = 'provision_state' - - def _execute(self, _): - return SoftLayer.MetadataManager().get('provision_state') - - -class Tags(MetaRunnable): - """ -usage: sl metadata tags [options] - -List tags -""" - action = 'tags' - - def _execute(self, _): - return formatting.listing(SoftLayer.MetadataManager().get('tags'), - separator=',') - - -class UserMetadata(MetaRunnable): - """ -usage: sl metadata user_data [options] - -Get user-defined data -""" - action = 'user_data' - - def _execute(self, _): - """ Returns user metadata """ - userdata = SoftLayer.MetadataManager().get('user_data') - if userdata: - return userdata - else: - raise exceptions.CLIAbort("No user metadata.") - - -class Network(MetaRunnable): - """ -usage: sl metadata network ( | ) [options] - -Get details about the public or private network -""" - action = 'network' - - def _execute(self, args): - meta = SoftLayer.MetadataManager() - if args['']: - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - network = meta.public_network() - table.add_row([ - 'mac addresses', - formatting.listing(network['mac_addresses'], separator=',')]) - table.add_row([ - 'router', network['router']]) - table.add_row([ - 'vlans', formatting.listing(network['vlans'], separator=',')]) - table.add_row([ - 'vlan ids', - formatting.listing(network['vlan_ids'], separator=',')]) - return table - - if args['']: - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - network = meta.private_network() - table.add_row([ - 'mac addresses', - formatting.listing(network['mac_addresses'], separator=',')]) - table.add_row([ - 'router', network['router']]) - table.add_row([ - 'vlans', formatting.listing(network['vlans'], separator=',')]) - table.add_row([ - 'vlan ids', - formatting.listing(network['vlan_ids'], separator=',')]) - return table diff --git a/SoftLayer/CLI/modules/nas.py b/SoftLayer/CLI/modules/nas.py deleted file mode 100644 index 860e7bf09..000000000 --- a/SoftLayer/CLI/modules/nas.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -usage: sl nas [] [...] [options] - -Manage NAS accounts - -The available commands are: - list List NAS accounts -""" -# :license: MIT, see LICENSE for more details. - -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer import utils - - -class ListNAS(environment.CLIRunnable): - """ -usage: sl nas list [options] - -List NAS accounts - -Options: -""" - action = 'list' - - def execute(self, args): - account = self.client['Account'] - - nas_accounts = account.getNasNetworkStorage( - mask='eventCount,serviceResource[datacenter.name]') - - table = formatting.Table(['id', 'datacenter', 'size', 'username', - 'password', 'server']) - - for nas_account in nas_accounts: - table.add_row([ - nas_account['id'], - utils.lookup(nas_account, - 'serviceResource', - 'datacenter', - 'name') or formatting.blank(), - formatting.FormattedItem( - nas_account.get('capacityGb', formatting.blank()), - "%dGB" % nas_account.get('capacityGb', 0)), - nas_account.get('username', formatting.blank()), - nas_account.get('password', formatting.blank()), - nas_account.get('serviceResourceBackendIpAddress', - formatting.blank())]) - - return table diff --git a/SoftLayer/CLI/modules/rwhois.py b/SoftLayer/CLI/modules/rwhois.py deleted file mode 100644 index c036488a0..000000000 --- a/SoftLayer/CLI/modules/rwhois.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -usage: sl rwhois [] [...] [options] - -Manage the RWhoIs information on the account. - -The available commands are: - edit Edit the RWhois data on the account - show Show the RWhois data on the account -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting - - -class RWhoisEdit(environment.CLIRunnable): - """ -usage: sl rwhois edit [options] - -Updates the RWhois information on your account. Only the fields you -specify will be changed. To clear a value, specify an empty string like: "" - -Options: - --abuse=EMAIL Set the abuse email - --address1=ADDR Update the address 1 field - --address2=ADDR Update the address 2 field - --city=CITY Set the city information - --company=NAME Set the company name - --country=COUNTRY Set the country information. Use the two-letter - abbreviation. - --firstname=NAME Update the first name field - --lastname=NAME Update the last name field - --postal=CODE Set the postal code field - --private Flags the address as a private residence. - --public Flags the address as a public residence. - --state=STATE Set the state information. Use the two-letter - abbreviation. -""" - action = 'edit' - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - - update = { - 'abuse_email': args.get('--abuse'), - 'address1': args.get('--address1'), - 'address2': args.get('--address2'), - 'company_name': args.get('--company'), - 'city': args.get('--city'), - 'country': args.get('--country'), - 'first_name': args.get('--firstname'), - 'last_name': args.get('--lastname'), - 'postal_code': args.get('--postal'), - 'state': args.get('--state') - } - - if args.get('--private'): - update['private_residence'] = False - elif args.get('--public'): - update['private_residence'] = True - - check = [x for x in update.values() if x is not None] - if not check: - raise exceptions.CLIAbort( - "You must specify at least one field to update.") - - mgr.edit_rwhois(**update) # pylint: disable=W0142 - - -class RWhoisShow(environment.CLIRunnable): - """ -usage: sl rwhois show [options] - -Display the RWhois information for your account. -""" - action = 'show' - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - result = mgr.get_rwhois() - - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - table.add_row(['Name', result['firstName'] + ' ' + result['lastName']]) - table.add_row(['Company', result['companyName']]) - table.add_row(['Abuse Email', result['abuseEmail']]) - table.add_row(['Address 1', result['address1']]) - if result.get('address2'): - table.add_row(['Address 2', result['address2']]) - table.add_row(['City', result['city']]) - table.add_row(['State', result.get('state', '-')]) - table.add_row(['Postal Code', result.get('postalCode', '-')]) - table.add_row(['Country', result['country']]) - - return table diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py deleted file mode 100644 index e49891991..000000000 --- a/SoftLayer/CLI/modules/server.py +++ /dev/null @@ -1,1107 +0,0 @@ -""" -usage: sl server [] [...] [options] - sl server [-h | --help] - -Manage hardware servers - -The available commands are: - cancel Cancel a dedicated server. - cancel-reasons Provides the list of possible cancellation reasons - create Create a new dedicated server - create-options Display a list of creation options for a specific chassis - detail Retrieve hardware details - list List hardware devices - list-chassis Provide a list of all chassis available for ordering - nic-edit Edit NIC settings - power-cycle Issues power cycle to server - power-off Powers off a running server - power-on Boots up a server - reboot Reboots a running server - reload Perform an OS reload - -For several commands, will be asked for. This can be the id, -hostname or the ip address for a piece of hardware. -""" -# :license: MIT, see LICENSE for more details. -import os -import re - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer.CLI import template -from SoftLayer import utils - - -class ListServers(environment.CLIRunnable): - """ -usage: sl server list [options] - -List hardware servers on the account - -Examples: - sl server list --datacenter=dal05 - sl server list --network=100 --domain=example.com - sl server list --tags=production,db - -Options: - --sortby=ARG Column to sort by. options: id, datacenter, host, cores, - memory, primary_ip, backend_ip - -Filters: - -c, --cpu=CPU Number of CPU cores - -D, --domain=DOMAIN Domain portion of the FQDN. example: example.com - -d, --datacenter=DC Datacenter shortname (sng01, dal05, ...) - -H, --hostname=HOST Host portion of the FQDN. example: server - -m, --memory=MEMORY Memory in gigabytes - -n, --network=MBPS Network port speed in Mbps - --tags=ARG Only show instances that have one of these tags. - Comma-separated. (production,db) - -For more on filters see 'sl help filters' -""" - action = 'list' - - def execute(self, args): - manager = SoftLayer.HardwareManager(self.client) - - tags = None - if args.get('--tags'): - tags = [tag.strip() for tag in args.get('--tags').split(',')] - - servers = manager.list_hardware( - hostname=args.get('--hostname'), - domain=args.get('--domain'), - cpus=args.get('--cpu'), - memory=args.get('--memory'), - datacenter=args.get('--datacenter'), - nic_speed=args.get('--network'), - tags=tags) - - table = formatting.Table([ - 'id', - 'datacenter', - 'host', - 'cores', - 'memory', - 'primary_ip', - 'backend_ip', - 'active_transaction', - 'owner' - ]) - table.sortby = args.get('--sortby') or 'host' - - for server in servers: - server = utils.NestedDict(server) - - table.add_row([ - server['id'], - server['datacenter']['name'] or formatting.blank(), - server['fullyQualifiedDomainName'], - server['processorPhysicalCoreAmount'], - formatting.gb(server['memoryCapacity'] or 0), - server['primaryIpAddress'] or formatting.blank(), - server['primaryBackendIpAddress'] or formatting.blank(), - formatting.active_txn(server), - utils.lookup( - server, 'billingItem', 'orderItem', 'order', 'userRecord', - 'username') or formatting.blank(), - ]) - - return table - - -class ServerDetails(environment.CLIRunnable): - """ -usage: sl server detail [--passwords] [--price] [options] - -Get details for a hardware device - -Options: - --passwords Show passwords (check over your shoulder!) - --price Show associated prices -""" - action = 'detail' - - def execute(self, args): - hardware = SoftLayer.HardwareManager(self.client) - - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - - hardware_id = helpers.resolve_id( - hardware.resolve_ids, args.get(''), 'hardware') - result = hardware.get_hardware(hardware_id) - result = utils.NestedDict(result) - - table.add_row(['id', result['id']]) - table.add_row(['hostname', result['fullyQualifiedDomainName']]) - table.add_row(['status', result['hardwareStatus']['status']]) - table.add_row(['datacenter', - result['datacenter']['name'] or formatting.blank()]) - table.add_row(['cores', result['processorPhysicalCoreAmount']]) - table.add_row(['memory', - formatting.gb(result['memoryCapacity'])]) - table.add_row(['public_ip', - result['primaryIpAddress'] or formatting.blank()]) - table.add_row(['private_ip', - result['primaryBackendIpAddress'] - or formatting.blank()]) - table.add_row(['ipmi_ip', - result['networkManagementIpAddress'] - or formatting.blank()]) - table.add_row([ - 'os', - formatting.FormattedItem( - result['operatingSystem']['softwareLicense'] - ['softwareDescription']['referenceCode'] or formatting.blank(), - result['operatingSystem']['softwareLicense'] - ['softwareDescription']['name'] or formatting.blank() - )]) - - table.add_row( - ['created', result['provisionDate'] or formatting.blank()]) - - table.add_row(['owner', formatting.FormattedItem( - utils.lookup(result, 'billingItem', 'orderItem', - 'order', 'userRecord', - 'username') or formatting.blank() - )]) - - vlan_table = formatting.Table(['type', 'number', 'id']) - - for vlan in result['networkVlans']: - vlan_table.add_row([ - vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) - table.add_row(['vlans', vlan_table]) - - if result.get('notes'): - table.add_row(['notes', result['notes']]) - - if args.get('--price'): - table.add_row(['price rate', - result['billingItem']['recurringFee']]) - - if args.get('--passwords'): - user_strs = [] - for item in result['operatingSystem']['passwords']: - user_strs.append( - "%s %s" % (item['username'], item['password'])) - table.add_row(['users', formatting.listing(user_strs)]) - - tag_row = [] - for tag in result['tagReferences']: - tag_row.append(tag['tag']['name']) - - if tag_row: - table.add_row(['tags', formatting.listing(tag_row, separator=',')]) - - # Test to see if this actually has a primary (public) ip address - try: - if not result['privateNetworkOnlyFlag']: - ptr_domains = (self.client['Hardware_Server'] - .getReverseDomainRecords(id=hardware_id)) - - for ptr_domain in ptr_domains: - for ptr in ptr_domain['resourceRecords']: - table.add_row(['ptr', ptr['data']]) - except SoftLayer.SoftLayerAPIError: - pass - return table - - -class ServerReload(environment.CLIRunnable): - """ -usage: sl server reload [--key=KEY...] [options] - -Reload the OS on a hardware server based on its current configuration - -Optional: - -i, --postinstall=URI Post-install script to download - (Only HTTPS executes, HTTP leaves file in /root) - -k, --key=KEY SSH keys to add to the root user. Can be specified - multiple times -""" - - action = 'reload' - options = ['confirm'] - - def execute(self, args): - hardware = SoftLayer.HardwareManager(self.client) - hardware_id = helpers.resolve_id( - hardware.resolve_ids, args.get(''), 'hardware') - keys = [] - if args.get('--key'): - for key in args.get('--key'): - resolver = SoftLayer.SshKeyManager(self.client).resolve_ids - key_id = helpers.resolve_id(resolver, key, 'SshKey') - keys.append(key_id) - if args['--really'] or formatting.no_going_back(hardware_id): - hardware.reload(hardware_id, args['--postinstall'], keys) - else: - raise exceptions.CLIAbort('Aborted') - - -class CancelServer(environment.CLIRunnable): - """ -usage: sl server cancel [options] - -Cancel a dedicated server - -Options: - --immediate Cancels the server immediately (instead of on the billing - anniversary) - --comment=COMMENT An optional comment to add to the cancellation ticket - --reason=REASON An optional cancellation reason. See cancel-reasons for a - list of available options -""" - - action = 'cancel' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id( - mgr.resolve_ids, args.get(''), 'hardware') - - comment = args.get('--comment') - - if not comment and not args['--really']: - comment = self.env.input("(Optional) Add a cancellation comment:") - - reason = args.get('--reason') - immediate = args.get('--immediate') - - if args['--really'] or formatting.no_going_back(hw_id): - mgr.cancel_hardware(hw_id, reason, comment, immediate) - else: - raise exceptions.CLIAbort('Aborted') - - -class ServerCancelReasons(environment.CLIRunnable): - """ -usage: sl server cancel-reasons - -Display a list of cancellation reasons -""" - - action = 'cancel-reasons' - - def execute(self, args): - table = formatting.Table(['Code', 'Reason']) - table.align['Code'] = 'r' - table.align['Reason'] = 'l' - - mgr = SoftLayer.HardwareManager(self.client) - - for code, reason in mgr.get_cancellation_reasons().items(): - table.add_row([code, reason]) - - return table - - -class ServerPowerOff(environment.CLIRunnable): - """ -usage: sl server power-off [options] - -Power off an active server -""" - action = 'power-off' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, - args.get(''), - 'hardware') - if args['--really'] or formatting.confirm('This will power off the ' - 'server with id %s ' - 'Continue?' % hw_id): - self.client['Hardware_Server'].powerOff(id=hw_id) - else: - raise exceptions.CLIAbort('Aborted.') - - -class ServerReboot(environment.CLIRunnable): - """ -usage: sl server reboot [--hard | --soft] [options] - -Reboot an active server - -Optional: - --hard Perform an abrupt reboot - --soft Perform a graceful reboot -""" - action = 'reboot' - options = ['confirm'] - - def execute(self, args): - hardware_server = self.client['Hardware_Server'] - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, - args.get(''), - 'hardware') - if args['--really'] or formatting.confirm('This will power off the ' - 'server with id %s. ' - 'Continue?' % hw_id): - if args['--hard']: - hardware_server.rebootHard(id=hw_id) - elif args['--soft']: - hardware_server.rebootSoft(id=hw_id) - else: - hardware_server.rebootDefault(id=hw_id) - else: - raise exceptions.CLIAbort('Aborted.') - - -class ServerPowerOn(environment.CLIRunnable): - """ -usage: sl server power-on [options] - -Power on a server -""" - action = 'power-on' - - def execute(self, args): - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, - args.get(''), - 'hardware') - self.client['Hardware_Server'].powerOn(id=hw_id) - - -class ServerPowerCycle(environment.CLIRunnable): - """ -usage: sl server power-cycle [options] - -Issues power cycle to server via the power strip -""" - action = 'power-cycle' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, - args.get(''), - 'hardware') - - if args['--really'] or formatting.confirm('This will power off the ' - 'server with id %s. ' - 'Continue?' % hw_id): - self.client['Hardware_Server'].powerCycle(id=hw_id) - else: - raise exceptions.CLIAbort('Aborted.') - - -class NicEditServer(environment.CLIRunnable): - """ -usage: sl server nic-edit (public | private) --speed=SPEED - [options] - -Manage NIC settings - -Options: - --speed=SPEED Port speed. 0 disables the port. - [Options: 0, 10, 100, 1000, 10000] -""" - action = 'nic-edit' - - def execute(self, args): - public = args['public'] - - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, - args.get(''), - 'hardware') - - mgr.change_port_speed(hw_id, public, args['--speed']) - - -class ListChassisServer(environment.CLIRunnable): - """ -usage: sl server list-chassis [options] - -Display a list of chassis available for ordering dedicated servers. -""" - action = 'list-chassis' - - def execute(self, args): - table = formatting.Table(['Code', 'Chassis']) - table.align['Code'] = 'r' - table.align['Chassis'] = 'l' - - mgr = SoftLayer.HardwareManager(self.client) - chassis = mgr.get_available_dedicated_server_packages() - - for chassis in chassis: - table.add_row([chassis[0], chassis[1]]) - - return table - - -class ServerRescue(environment.CLIRunnable): - """ -usage: sl server rescue [options] - -Reboot server into a rescue image - - -""" - action = 'rescue' - options = ['confirm'] - - def execute(self, args): - server = SoftLayer.HardwareManager(self.client) - server_id = helpers.resolve_id(server.resolve_ids, - args.get(''), - 'hardware') - - if args['--really'] or formatting.confirm( - "This action will reboot this server. " - "Continue?"): - - server.rescue(server_id) - else: - raise exceptions.CLIAbort('Aborted') - - -class ServerCreateOptions(environment.CLIRunnable): - """ -usage: sl server create-options [options] - -Output available available options when creating a dedicated server with the -specified chassis. - -Options: - --all Show all options. default if no other option provided - --controller Show disk controller options - --cpu Show CPU options - --datacenter Show datacenter options - --disk Show disk options - --memory Show memory size options - --nic Show NIC speed options - --os Show operating system options -""" - - action = 'create-options' - options = ['datacenter', 'cpu', 'memory', 'os', 'disk', 'nic', - 'controller'] - - def execute(self, args): - mgr = SoftLayer.HardwareManager(self.client) - - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - - chassis_id = args.get('') - - found = False - for chassis in mgr.get_available_dedicated_server_packages(): - if chassis_id == str(chassis[0]): - found = True - break - - if not found: - raise exceptions.CLIAbort('Invalid chassis specified.') - - ds_options = mgr.get_dedicated_server_create_options(chassis_id) - - show_all = True - for opt_name in self.options: - if args.get("--" + opt_name): - show_all = False - break - - if args['--all']: - show_all = True - - # Determine if this is a "Bare Metal Instance" or regular server - bmc = False - if chassis_id == str(mgr.get_bare_metal_package_id()): - bmc = True - - if args['--datacenter'] or show_all: - results = self.get_create_options(ds_options, 'datacenter')[0] - - table.add_row([results[0], formatting.listing(sorted(results[1]))]) - - if (args['--cpu'] or show_all) and not bmc: - results = self.get_create_options(ds_options, 'cpu') - - cpu_table = formatting.Table(['ID', 'Description']) - cpu_table.align['ID'] = 'r' - cpu_table.align['Description'] = 'l' - - for result in sorted(results, key=lambda x: x[1]): - cpu_table.add_row([result[1], result[0]]) - table.add_row(['cpu', cpu_table]) - - if (args['--memory'] or show_all) and not bmc: - results = self.get_create_options(ds_options, 'memory')[0] - - table.add_row([results[0], formatting.listing( - item[0] for item in sorted(results[1]))]) - - if bmc and (show_all or args['--memory'] or args['--cpu']): - results = self.get_create_options(ds_options, 'server_core') - memory_cpu_table = formatting.Table(['memory', 'cpu']) - for result in results: - memory_cpu_table.add_row([ - result[0], - formatting.listing( - [item[0] for item in sorted( - result[1], key=lambda x: int(x[0]) - )])]) - table.add_row(['memory/cpu', memory_cpu_table]) - - if args['--os'] or show_all: - results = self.get_create_options(ds_options, 'os') - - for result in results: - table.add_row([ - result[0], - formatting.listing( - [item[0] for item in sorted(result[1])], - separator=os.linesep - )]) - - if args['--disk'] or show_all: - results = self.get_create_options(ds_options, 'disk')[0] - - table.add_row([ - results[0], - formatting.listing( - [item[0] for item in sorted(results[1])], - separator=os.linesep - )]) - - if args['--nic'] or show_all: - results = self.get_create_options(ds_options, 'nic') - - for result in results: - table.add_row([result[0], formatting.listing( - item[0] for item in sorted(result[1],))]) - - if (args['--controller'] or show_all) and not bmc: - results = self.get_create_options(ds_options, 'disk_controller')[0] - - table.add_row([results[0], formatting.listing( - item[0] for item in sorted(results[1],))]) - - return table - - def get_create_options(self, ds_options, section, pretty=True): - """ This method can be used to parse the bare metal instance creation - options into different sections. This can be useful for data validation - as well as printing the options on a help screen. - - :param dict ds_options: The instance options to parse. Must come from - the .get_bare_metal_create_options() function - in the HardwareManager. - :param string section: The section to parse out. - :param bool pretty: If true, it will return the results in a 'pretty' - format that's easier to print. - """ - return_value = None - - if 'datacenter' == section: - datacenters = [loc['keyname'] - for loc in ds_options['locations']] - return_value = [('datacenter', datacenters)] - elif 'cpu' == section and 'server' in ds_options['categories']: - results = [] - - for item in ds_options['categories']['server']['items']: - results.append(( - item['description'], - item['price_id'] - )) - - return_value = results - elif 'memory' == section and 'ram' in ds_options['categories']: - ram = [] - for option in ds_options['categories']['ram']['items']: - ram.append((int(option['capacity']), option['price_id'])) - - return_value = [('memory', ram)] - elif ('server_core' == section - and 'server_core' in ds_options['categories']): - mem_options = {} - cpu_regex = re.compile(r'(\d+) x ') - memory_regex = re.compile(r' - (\d+) GB Ram', re.I) - - for item in ds_options['categories']['server_core']['items']: - cpu = cpu_regex.search(item['description']).group(1) - memory = memory_regex.search(item['description']).group(1) - - if cpu and memory: - if memory not in mem_options: - mem_options[memory] = [] - - mem_options[memory].append((cpu, item['price_id'])) - - results = [] - for memory in sorted(mem_options.keys(), key=int): - key = memory - - if pretty: - key = memory - - results.append((key, mem_options[memory])) - - return_value = results - elif 'os' == section: - os_regex = re.compile(r'(^[A-Za-z\s\/\-]+) ([\d\.]+)') - bit_regex = re.compile(r' \((\d+)\s*bit') - extra_regex = re.compile(r' - (.+)\(') - - os_list = {} - flat_list = [] - - # Loop through the operating systems and get their OS codes - for opsys in ds_options['categories']['os']['items']: - if 'Windows Server' in opsys['description']: - os_code = self._generate_windows_code(opsys['description']) - else: - os_results = os_regex.search(opsys['description']) - - # Skip this operating system if it's not parsable - if os_results is None: - continue - - name = os_results.group(1) - version = os_results.group(2) - bits = bit_regex.search(opsys['description']) - extra_info = extra_regex.search(opsys['description']) - - if bits: - bits = bits.group(1) - if extra_info: - extra_info = extra_info.group(1) - - os_code = self._generate_os_code(name, version, bits, - extra_info) - - name = os_code.split('_')[0] - - if name not in os_list: - os_list[name] = [] - - os_list[name].append((os_code, opsys['price_id'])) - flat_list.append((os_code, opsys['price_id'])) - - if pretty: - results = [] - for opsys in sorted(os_list.keys()): - results.append(('os (%s)' % opsys, os_list[opsys])) - - return_value = results - else: - return_value = [('os', flat_list)] - - elif 'disk' == section: - disks = [] - type_regex = re.compile(r'^[\d\.]+[GT]B\s+(.+)$') - for disk in ds_options['categories']['disk0']['items']: - disk_type = 'SATA' - disk_type = type_regex.match(disk['description']).group(1) - - disk_type = disk_type.replace('RPM', '').strip() - disk_type = disk_type.replace(' ', '_').upper() - disk_type = str(int(disk['capacity'])) + '_' + disk_type - disks.append((disk_type, disk['price_id'], disk['id'])) - - return_value = [('disk', disks)] - elif 'nic' == section: - single = [] - dual = [] - - for item in ds_options['categories']['port_speed']['items']: - if 'dual' in item['description'].lower(): - dual.append((str(int(item['capacity'])) + '_DUAL', - item['price_id'])) - else: - single.append((str(int(item['capacity'])), - item['price_id'])) - - return_value = [('single nic', single), ('dual nic', dual)] - elif 'disk_controller' == section: - options = [] - for item in ds_options['categories']['disk_controller']['items']: - text = item['description'].replace(' ', '') - - if 'Non-RAID' == text: - text = 'None' - - options.append((text, item['price_id'])) - - return_value = [('disk_controllers', options)] - - return return_value - - def _generate_os_code(self, name, version, bits, extra_info): - """ Encapsulates the code for generating the operating system code. """ - name = name.replace(' Linux', '') - name = name.replace('Enterprise', '') - name = name.replace('GNU/Linux', '') - - os_code = name.strip().replace(' ', '_').upper() - - if os_code.startswith('RED_HAT'): - os_code = 'REDHAT' - - if 'UBUNTU' in os_code: - version = re.sub(r'\.\d+', '', version) - - os_code += '_' + version.replace('.0', '') - - if bits: - os_code += '_' + bits - - if extra_info: - garbage = ['Install', '(32 bit)', '(64 bit)'] - - for obj in garbage: - extra_info = extra_info.replace(obj, '') - - os_code += '_' + extra_info.strip().replace(' ', '_').upper() - - return os_code - - def _generate_windows_code(self, description): - """ Separates the code for generating the Windows OS code - since it's significantly different from the rest. - """ - version_check = re.search(r'Windows Server (\d+)', description) - version = version_check.group(1) - - os_code = 'WIN_' + version - - if 'Datacenter' in description: - os_code += '-DC' - elif 'Enterprise' in description: - os_code += '-ENT' - else: - os_code += '-STD' - - if 'ith R2' in description: - os_code += '-R2' - elif 'ith Hyper-V' in description: - os_code += '-HYPERV' - - bit_check = re.search(r'\((\d+)\s*bit', description) - if bit_check: - os_code += '_' + bit_check.group(1) - - return os_code - - -class CreateServer(environment.CLIRunnable): - """ -usage: sl server create [--disk=SIZE...] [--key=KEY...] [options] - -Order/create a dedicated server. See 'sl server list-chassis' and -'sl server create-options' for valid options. - -Required: - -H --hostname=HOST Host portion of the FQDN. example: server - -D --domain=DOMAIN Domain portion of the FQDN. example: example.com - --chassis=CHASSIS The chassis to use for the new server - -c --cpu=CPU CPU model - -o OS, --os=OS OS install code. - -m --memory=MEMORY Memory in gigabytes. example: 4 - --billing=BILLING Billing rate. Options are "monthly" (default) or - "hourly". The hourly rate is only available on the - "Bare Metal Instance" chassis. - -Optional: - -d, --datacenter=DC Datacenter name - Note: Omitting this value defaults to the first - available datacenter - -n, --network=MBPS Network port speed in Mbps - --disk=SIZE... Disks. Can be specified multiple times - --controller=RAID The RAID configuration for the server. - Defaults to None. - -i, --postinstall=URI Post-install script to download - -k KEY, --key=KEY SSH keys to assign to the root user. Can be specified - multiple times. - --test Do not create the server, just get a quote - --vlan_public=VLAN The ID of the public VLAN on which you want the - hardware placed - --vlan_private=VLAN The ID of the private VLAN on which you want the - hardware placed - -t, --template=FILE A template file that defaults the command-line - options using the long name in INI format - --export=FILE Exports options to a template file -""" - action = 'create' - options = ['confirm'] - required_params = ['--hostname', '--domain', '--chassis', '--cpu', - '--memory', '--os'] - - def execute(self, args): - template.update_with_template_args(args, list_args=['--disk', '--key']) - mgr = SoftLayer.HardwareManager(self.client) - self._validate_args(args) - - ds_options = mgr.get_dedicated_server_create_options(args['--chassis']) - - order = self._process_args(args, ds_options) - - # Do not create hardware server with --test or --export - do_create = not (args['--export'] or args['--test']) - - output = None - if args.get('--test'): - result = mgr.verify_order(**order) - - table = formatting.Table(['Item', 'cost']) - table.align['Item'] = 'r' - table.align['cost'] = 'r' - - total = 0.0 - for price in result['prices']: - total += float(price.get('recurringFee', 0.0)) - rate = "%.2f" % float(price['recurringFee']) - - table.add_row([price['item']['description'], rate]) - - table.add_row(['Total monthly cost', "%.2f" % total]) - output = [] - output.append(table) - output.append(formatting.FormattedItem( - '', - ' -- ! Prices reflected here are retail and do not ' - 'take account level discounts and are not guaranteed.')) - - if args['--export']: - export_file = args.pop('--export') - template.export_to_template(export_file, args, - exclude=['--wait', '--test']) - return 'Successfully exported options to a template file.' - - if do_create: - if args['--really'] or formatting.confirm( - "This action will incur charges on your account. " - "Continue?"): - result = mgr.place_order(**order) - - table = formatting.KeyValueTable(['name', 'value']) - table.align['name'] = 'r' - table.align['value'] = 'l' - table.add_row(['id', result['orderId']]) - table.add_row(['created', result['orderDate']]) - output = table - else: - raise exceptions.CLIAbort('Aborting dedicated server order.') - - return output - - def _process_args(self, args, ds_options): - """ - Helper method to centralize argument processing without convoluting - code flow of the main execute method. - """ - mgr = SoftLayer.HardwareManager(self.client) - - order = { - 'hostname': args['--hostname'], - 'domain': args['--domain'], - 'bare_metal': False, - 'package_id': args['--chassis'], - } - - # Determine if this is a "Bare Metal Instance" or regular server - bmc = False - if args['--chassis'] == str(mgr.get_bare_metal_package_id()): - bmc = True - - # Convert the OS code back into a price ID - os_price = self._get_price_id_from_options(ds_options, 'os', - args['--os']) - - if os_price: - order['os'] = os_price - else: - raise exceptions.CLIAbort('Invalid operating system specified.') - - order['location'] = args['--datacenter'] or 'FIRST_AVAILABLE' - - if bmc: - order['server'] = self._get_cpu_and_memory_price_ids( - ds_options, args['--cpu'], args['--memory']) - order['bare_metal'] = True - - if args['--billing'] == 'hourly': - order['hourly'] = True - else: - order['server'] = args['--cpu'] - order['ram'] = self._get_price_id_from_options( - ds_options, 'memory', int(args['--memory'])) - - # Set the disk sizes - disk_prices = [] - disk_number = 0 - for disk in args.get('--disk'): - disk_price = self._get_disk_price(ds_options, disk, disk_number) - disk_number += 1 - if disk_price: - disk_prices.append(disk_price) - - if not disk_prices: - disk_prices.append(self._get_default_value(ds_options, 'disk0')) - - order['disks'] = disk_prices - - # Set the disk controller price - if not bmc: - if args.get('--controller'): - dc_price = self._get_price_id_from_options( - ds_options, 'disk_controller', args.get('--controller')) - else: - dc_price = self._get_price_id_from_options(ds_options, - 'disk_controller', - 'None') - - order['disk_controller'] = dc_price - - # Set the port speed - port_speed = args.get('--network') or '100' - - nic_price = self._get_price_id_from_options(ds_options, 'nic', - port_speed) - - if nic_price: - order['port_speed'] = nic_price - else: - raise exceptions.CLIAbort('Invalid NIC speed specified.') - - if args.get('--postinstall'): - order['post_uri'] = args.get('--postinstall') - - # Get the SSH keys - if args.get('--key'): - keys = [] - for key in args.get('--key'): - resolver = SoftLayer.SshKeyManager(self.client).resolve_ids - key_id = helpers.resolve_id(resolver, key, 'SshKey') - keys.append(key_id) - order['ssh_keys'] = keys - - if args.get('--vlan_public'): - order['public_vlan'] = args['--vlan_public'] - - if args.get('--vlan_private'): - order['private_vlan'] = args['--vlan_private'] - - return order - - def _validate_args(self, args): - """ Raises an ArgumentError if the given arguments are not valid """ - invalid_args = [k for k in self.required_params if args.get(k) is None] - if invalid_args: - raise exceptions.ArgumentError('Missing required options: %s' - % ','.join(invalid_args)) - - def _get_default_value(self, ds_options, option): - """ Returns a 'free' price id given an option """ - if option not in ds_options['categories']: - return - - for item in ds_options['categories'][option]['items']: - if not any([ - float(item.get('setupFee', 0)), - float(item.get('recurringFee', 0)), - float(item.get('hourlyRecurringFee', 0)), - float(item.get('oneTimeFee', 0)), - float(item.get('laborFee', 0)), - ]): - return item['price_id'] - - def _get_disk_price(self, ds_options, value, number): - """ Returns a price id that matches a given disk config """ - if not number: - return self._get_price_id_from_options(ds_options, 'disk', value) - # This will get the item ID for the matching identifier string, which - # we can then use to get the price ID for our specific disk - item_id = self._get_price_id_from_options(ds_options, 'disk', - value, True) - key = 'disk' + str(number) - if key in ds_options['categories']: - for item in ds_options['categories'][key]['items']: - if item['id'] == item_id: - return item['price_id'] - - def _get_cpu_and_memory_price_ids(self, ds_options, cpu_value, - memory_value): - """ - Returns a price id for a cpu/memory pair in pre-configured servers - (formerly known as BMC). - """ - ds_obj = ServerCreateOptions() - for memory, options in ds_obj.get_create_options(ds_options, - 'server_core', - False): - if memory == memory_value: - for cpu_size, price_id in options: - if cpu_size == cpu_value: - return price_id - - def _get_price_id_from_options(self, ds_options, option, value, - item_id=False): - """ Returns a price_id for a given option and value """ - ds_obj = ServerCreateOptions() - - for _, options in ds_obj.get_create_options(ds_options, option, False): - for item_options in options: - if item_options[0] == value: - if not item_id: - return item_options[1] - return item_options[2] - - -class EditServer(environment.CLIRunnable): - """ -usage: sl server edit [options] - -Edit hardware details - -Options: - -D --domain=DOMAIN Domain portion of the FQDN example: example.com - -F --userfile=FILE Read userdata from file - -H --hostname=HOST Host portion of the FQDN. example: server - -u --userdata=DATA User defined metadata string -""" - action = 'edit' - - def execute(self, args): - data = {} - - if args['--userdata'] and args['--userfile']: - raise exceptions.ArgumentError( - '[-u | --userdata] not allowed with [-F | --userfile]') - if args['--userfile']: - if not os.path.exists(args['--userfile']): - raise exceptions.ArgumentError( - 'File does not exist [-u | --userfile] = %s' - % args['--userfile']) - - if args.get('--userdata'): - data['userdata'] = args['--userdata'] - elif args.get('--userfile'): - with open(args['--userfile'], 'r') as userfile: - data['userdata'] = userfile.read() - - data['hostname'] = args.get('--hostname') - data['domain'] = args.get('--domain') - - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, - args.get(''), - 'hardware') - if not mgr.edit(hw_id, **data): - raise exceptions.CLIAbort("Failed to update hardware") diff --git a/SoftLayer/CLI/modules/snapshot.py b/SoftLayer/CLI/modules/snapshot.py deleted file mode 100644 index a7d95b3b5..000000000 --- a/SoftLayer/CLI/modules/snapshot.py +++ /dev/null @@ -1,152 +0,0 @@ -""" -usage: sl snapshot [] [...] [options] - -Manage, order, delete iSCSI snapshots - -The available commands are: - cancel Cancel an iSCSI snapshot - create Create a snapshot of given iSCSI volume - create-space Orders space for storing snapshots - list List snpshots of given iSCSI - restore-volume Restores volume from existing snapshot - -For several commands will be asked for.This can be the id -of iSCSI volume or iSCSI snapshot. -""" -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer import utils - - -class CreateSnapshot(environment.CLIRunnable): - - """ -usage: sl snapshot create [options] - -Create a snapshot of the iSCSI volume. - -Examples: - sl snapshot create 123456 --note='Backup' - sl snapshot create 123456 - -Options: - --notes=NOTE An optional snapshot's note - -""" - action = 'create' - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') - notes = args.get('--notes') - iscsi_mgr.create_snapshot(iscsi_id, notes) - - -class CreateSnapshotSpace(environment.CLIRunnable): - - """ -usage: sl snapshot create-space [options] - -Orders snapshot space for given iSCSI. - -Examples: - sl snapshot create-space 123456 --capacity=20 - -Required : - --capacity=CAPACITY Size of snapshot space to create -""" - - action = 'create-space' - required_params = ['--capacity'] - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - invalid_args = [k for k in self.required_params if args.get(k) is None] - if invalid_args: - raise exceptions.ArgumentError('Missing required options: %s' - % ','.join(invalid_args)) - iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') - capacity = args.get('--capacity') - iscsi_mgr.create_snapshot_space(iscsi_id, capacity) - - -class CancelSnapshot(environment.CLIRunnable): - - """ -usage: sl snapshot cancel [options] - -Cancel/Delete iSCSI snapshot. - -""" - action = 'cancel' - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - snapshot_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - args.get(''), - 'Snapshot') - iscsi_mgr.delete_snapshot(snapshot_id) - - -class RestoreVolumeFromSnapshot(environment.CLIRunnable): - - """ -usage: sl snapshot restore-volume - -restores volume from existing snapshot. - -""" - action = 'restore-volume' - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - volume_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') - snapshot_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - args.get(''), - 'Snapshot') - iscsi_mgr.restore_from_snapshot(volume_id, snapshot_id) - - -class ListSnapshots(environment.CLIRunnable): - - """ -usage: sl snapshot list - -List iSCSI Snapshots -""" - action = 'list' - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') - iscsi = self.client['Network_Storage_Iscsi'] - snapshots = iscsi.getPartnerships( - mask='volumeId,partnerVolumeId,createDate,type', id=iscsi_id) - snapshots = [utils.NestedDict(n) for n in snapshots] - - table = formatting.Table([ - 'id', - 'createDate', - 'name', - 'description', - ]) - - for snapshot in snapshots: - table.add_row([ - snapshot['partnerVolumeId'], - snapshot['createDate'], - snapshot['type']['name'], - snapshot['type']['description'], - ]) - return table diff --git a/SoftLayer/CLI/modules/sshkey.py b/SoftLayer/CLI/modules/sshkey.py deleted file mode 100644 index 9649f355f..000000000 --- a/SoftLayer/CLI/modules/sshkey.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -usage: sl sshkey [] [...] [options] - -Manage SSH keys - -The available commands are: - add Add a new SSH key to your account - remove Removes an SSH key - edit Edits information about the SSH key - list Display a list of SSH keys on your account - print Prints out an SSH key -""" -# :license: MIT, see LICENSE for more details. - -from os import path - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers - - -class AddSshKey(environment.CLIRunnable): - """ -usage: sl sshkey add