diff --git a/CHANGELOG b/CHANGELOG index 80f1b8e41..9e49ae9cf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,12 @@ +4.0.2 + + * Fixes a bug that breaks user confirmation prompts + + * Fixes general issue with sorting on certain row types in the CLI + + * Fixes image capture for Windows guests + + 4.0.1 * Fixes bug in `sl setup` command not properly defaulting to current values. diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 138e5318d..4f10980de 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -73,6 +73,7 @@ def format_prettytable(table): 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 = prettytable.FRAME ptable.horizontal_char = '.' @@ -83,12 +84,15 @@ def format_prettytable(table): def format_no_tty(table): """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 = prettytable.NONE ptable.border = False ptable.header = False @@ -192,8 +196,8 @@ def no_going_back(confirmation): prompt = ('This action cannot be undone! Type "%s" or press Enter ' 'to abort' % confirmation) - ans = click.confirm(prompt, default='', show_default=False).lower() - if ans == str(confirmation): + ans = click.prompt(prompt, default='', show_default=False) + if ans.lower() == str(confirmation): return True return False @@ -304,6 +308,30 @@ def __str__(self): __repr__ = __str__ + # Implement sorting methods. + # NOTE(kmcdonald): functools.total_ordering could be used once support for + # Python 2.6 is dropped + def __eq__(self, other): + return self.original == getattr(other, 'original', other) + + def __lt__(self, other): + if self.original is None: + return True + + other_val = getattr(other, 'original', other) + if other_val is None: + return False + return self.original < other_val + + def __gt__(self, other): + return not (self < other or self == other) + + def __le__(self, other): + return self < other or self == other + + def __ge__(self, other): + return not self < other + def _format_python_value(value): """If the value has to_python() defined then return that.""" diff --git a/SoftLayer/CLI/server/list.py b/SoftLayer/CLI/server/list.py index 03ba456e1..d6f456221 100644 --- a/SoftLayer/CLI/server/list.py +++ b/SoftLayer/CLI/server/list.py @@ -14,7 +14,7 @@ @click.command() @click.option('--sortby', help='Column to sort by', - type=click.Choice(['guid', + type=click.Choice(['id', 'hostname', 'primary_ip', 'backend_ip', diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 100cb6371..e0b285363 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v4.0.1' +VERSION = 'v4.0.2' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/SoftLayer/managers/messaging.py b/SoftLayer/managers/messaging.py index 6c7e0237c..8c273b6b5 100644 --- a/SoftLayer/managers/messaging.py +++ b/SoftLayer/managers/messaging.py @@ -168,7 +168,12 @@ def _make_request(self, method, path, **kwargs): url = '/'.join((self.endpoint, 'v1', self.account_id, path)) resp = requests.request(method, url, **kwargs) - resp.raise_for_status() + try: + resp.raise_for_status() + except requests.HTTPError as ex: + content = json.loads(ex.response.content) + raise exceptions.SoftLayerAPIError(ex.response.status_code, + content['message']) return resp def authenticate(self, username, api_key, auth_token=None): diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 5739ebc5f..c7f9fbcfb 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -581,9 +581,10 @@ def capture(self, instance_id, name, additional_disks=False, notes=None): vsi = self.get_instance(instance_id) disk_filter = lambda x: x['device'] == '0' - # Disk 1 is swap partition. Need to skip its capture. + # Skip disk 1 (swap partition) and CD mounts if additional_disks: - disk_filter = lambda x: x['device'] != '1' + disk_filter = lambda x: (str(x['device']) != '1' and + x['mountType'] != 'CD') disks = [block_device for block_device in vsi['blockDevices'] if disk_filter(block_device)] diff --git a/SoftLayer/testing/fixtures/SoftLayer_Account.py b/SoftLayer/testing/fixtures/SoftLayer_Account.py index 994d5fb68..9e1a43da7 100644 --- a/SoftLayer/testing/fixtures/SoftLayer_Account.py +++ b/SoftLayer/testing/fixtures/SoftLayer_Account.py @@ -136,7 +136,7 @@ 'friendlyName': 'Friendly Transaction Name', 'id': 6660 } - } + }, }, { 'id': 1001, 'globalIdentifier': '1a2b3c-1702', @@ -224,6 +224,8 @@ 'id': 19082 }, ] +}, { + 'id': 1003, }] getDomains = [{'name': 'example.com', 'id': 12345, diff --git a/SoftLayer/testing/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/testing/fixtures/SoftLayer_Virtual_Guest.py index 1235a3b64..438009620 100644 --- a/SoftLayer/testing/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/testing/fixtures/SoftLayer_Virtual_Guest.py @@ -26,9 +26,10 @@ "primaryNetworkComponent": {"speed": 10, "maxSpeed": 100}, 'hourlyBillingFlag': False, 'createDate': '2013-08-01 15:23:45', - 'blockDevices': [{"device": 0, "uuid": 1}, - {"device": 1}, - {"device": 2, "uuid": 2}], + 'blockDevices': [{"device": 0, 'mountType': 'Disk', "uuid": 1}, + {"device": 1, 'mountType': 'Disk'}, + {"device": 2, 'mountType': 'CD'}, + {"device": 3, 'mountType': 'Disk', "uuid": 3}], 'notes': 'notes', 'networkVlans': [{'networkSpace': 'PUBLIC', 'vlanNumber': 23, diff --git a/SoftLayer/tests/CLI/helper_tests.py b/SoftLayer/tests/CLI/helper_tests.py index 1dea9ab44..7862ef008 100644 --- a/SoftLayer/tests/CLI/helper_tests.py +++ b/SoftLayer/tests/CLI/helper_tests.py @@ -41,21 +41,21 @@ def test_fail(self): class PromptTests(testing.TestCase): - @mock.patch('click.confirm') - def test_do_or_die(self, confirm_mock): + @mock.patch('click.prompt') + def test_do_or_die(self, prompt_mock): confirmed = '37347373737' - confirm_mock.return_value = confirmed + prompt_mock.return_value = confirmed result = formatting.no_going_back(confirmed) self.assertTrue(result) # no_going_back should cast int's to str() confirmed = '4712309182309' - confirm_mock.return_value = confirmed + prompt_mock.return_value = confirmed result = formatting.no_going_back(int(confirmed)) self.assertTrue(result) confirmed = None - confirm_mock.return_value = '' + prompt_mock.return_value = '' result = formatting.no_going_back(confirmed) self.assertFalse(result) @@ -131,6 +131,27 @@ def test_blank(self): self.assertEqual('-', item.formatted) self.assertEqual('NULL', str(item)) + def test_sort_mixed(self): + blank = formatting.blank() + items = [10, blank] + sorted_items = sorted(items) + self.assertEqual(sorted_items, [blank, 10]) + + items = [blank, 10] + sorted_items = sorted(items) + self.assertEqual(sorted_items, [blank, 10]) + + items = [blank, "10"] + sorted_items = sorted(items) + self.assertEqual(sorted_items, [blank, "10"]) + + def test_sort(self): + items = [10, formatting.FormattedItem(20), formatting.FormattedItem(5)] + sorted_items = sorted(items) + self.assertEqual(sorted_items, [formatting.FormattedItem(5), + 10, + formatting.FormattedItem(20)]) + class FormattedListTests(testing.TestCase): def test_init(self): diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index a65538cf5..85df2f425 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -84,11 +84,19 @@ def test_list_servers(self): 'id': 1002, 'backend_ip': '10.1.0.4', 'action': None, - } + }, + { + 'action': None, + 'backend_ip': None, + 'datacenter': None, + 'hostname': None, + 'id': 1003, + 'primary_ip': None, + }, ] self.assertEqual(result.exit_code, 0) - self.assertEqual(json.loads(result.output), expected) + self.assertEqual(expected, json.loads(result.output)) @mock.patch('SoftLayer.CLI.formatting.no_going_back') @mock.patch('SoftLayer.HardwareManager.reload') diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py index 79aa50256..efec2591a 100644 --- a/SoftLayer/tests/managers/hardware_tests.py +++ b/SoftLayer/tests/managers/hardware_tests.py @@ -78,7 +78,7 @@ def test_list_hardware_with_filters(self): def test_resolve_ids_ip(self): _id = self.hardware._get_ids_from_ip('172.16.1.100') - self.assertEqual(_id, [1000, 1001, 1002]) + self.assertEqual(_id, [1000, 1001, 1002, 1003]) _id = self.hardware._get_ids_from_ip('nope') self.assertEqual(_id, []) @@ -93,7 +93,7 @@ def test_resolve_ids_ip(self): def test_resolve_ids_hostname(self): _id = self.hardware._get_ids_from_hostname('hardware-test1') - self.assertEqual(_id, [1000, 1001, 1002]) + self.assertEqual(_id, [1000, 1001, 1002, 1003]) def test_get_hardware(self): result = self.hardware.get_hardware(1000) diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 71b71c022..3a179b46b 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -610,9 +610,8 @@ def test_capture_additional_disks(self): expected = fixtures.SoftLayer_Virtual_Guest.createArchiveTransaction self.assertEqual(result, expected) - args = ('a', [{'device': 0, 'uuid': 1}, - {'device': 1}, - {'device': 2, 'uuid': 2}], None) + args = ('a', [{'device': 0, 'mountType': 'Disk', 'uuid': 1}, + {'device': 3, 'mountType': 'Disk', 'uuid': 3}], None) self.assert_called_with('SoftLayer_Virtual_Guest', 'createArchiveTransaction', args=args, diff --git a/docs/conf.py b/docs/conf.py index 8ed973c2f..55abd0f3f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,9 +55,9 @@ # built documents. # # The short X.Y version. -version = '4.0.1' +version = '4.0.2' # The full version, including alpha/beta/rc tags. -release = '4.0.1' +release = '4.0.2' # 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 585e70d74..087610c3a 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ setup( name='SoftLayer', - version='4.0.1', + version='4.0.2', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.',