From 0a8ca6be8c92fbad4b76005aecfef750ada4a3a2 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 31 Jul 2024 15:49:45 -0500 Subject: [PATCH 1/8] Added x509 certificate support to cli --- SoftLayer/API.py | 6 ++++-- SoftLayer/config.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 0a2788b63..00dbfe74f 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -211,9 +211,11 @@ def employee_client(username=None, access_token = settings.get('access_token') user_id = settings.get('userid') - # Assume access_token is valid for now, user has logged in before at least. - if access_token and user_id: + if settings.get('auth_cert', False): + auth = slauth.X509Authentication(settings.get('auth_cert'), verify) + return EmployeeClient(auth=auth, transport=transport, config_file=config_file) + elif access_token and user_id: auth = slauth.EmployeeAuthentication(user_id, access_token) return EmployeeClient(auth=auth, transport=transport, config_file=config_file) else: diff --git a/SoftLayer/config.py b/SoftLayer/config.py index e695d6b9a..f67552639 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -61,7 +61,8 @@ def get_client_settings_config_file(**kwargs): # pylint: disable=inconsistent-r 'proxy': '', 'userid': '', 'access_token': '', - 'verify': "True" + 'verify': "True", + 'auth_cert': '' }) config.read(config_files) @@ -74,7 +75,8 @@ def get_client_settings_config_file(**kwargs): # pylint: disable=inconsistent-r 'api_key': config.get('softlayer', 'api_key'), 'userid': config.get('softlayer', 'userid'), 'access_token': config.get('softlayer', 'access_token'), - 'verify': config.get('softlayer', 'verify') + 'verify': config.get('softlayer', 'verify'), + 'auth_cert': config.get('softlayer', 'auth_cert') } if r_config["verify"].lower() == "true": r_config["verify"] = True From 0bed0d863c8ee2a9b1fa52931d5fcee3b7d457d3 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 31 Jul 2024 16:04:03 -0500 Subject: [PATCH 2/8] v6.2.5 updates --- SoftLayer/consts.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 0b20929f6..136e2800d 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v6.2.4' +VERSION = 'v6.2.5' 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/setup.py b/setup.py index b930bb8ad..045cb6d75 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name='SoftLayer', - version='v6.2.4', + version='v6.2.5', description=DESCRIPTION, long_description=LONG_DESCRIPTION, long_description_content_type='text/x-rst', From edf3fff46765c0a4d0bee9317f158a3f3fba9814 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 21:01:31 +0000 Subject: [PATCH 3/8] pip prod(deps): bump sphinx from 8.0.0 to 8.0.2 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 8.0.0 to 8.0.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/v8.0.2/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v8.0.0...v8.0.2) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 0dffff663..2c1e4075b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ -sphinx==8.0.0 +sphinx==8.0.2 sphinx_rtd_theme==2.0.0 sphinx-click==6.0.0 click From ec89f09de58fd5755103c51d0a8c740b8873246d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 5 Aug 2024 17:51:13 -0500 Subject: [PATCH 4/8] Reverted 'globalip assign' syntax while still using the Network_Subnet::route() method. Fixed #2176 --- SoftLayer/CLI/dns/zone_delete.py | 1 + SoftLayer/CLI/formatting.py | 3 +- SoftLayer/CLI/globalip/assign.py | 46 ++++++++++++++----- SoftLayer/CLI/globalip/cancel.py | 4 +- SoftLayer/CLI/globalip/unassign.py | 20 ++++++-- ...ftLayer_Network_Subnet_IpAddress_Global.py | 2 +- SoftLayer/managers/network.py | 6 +-- tests/CLI/modules/globalip_tests.py | 37 +++++++++++---- 8 files changed, 87 insertions(+), 32 deletions(-) diff --git a/SoftLayer/CLI/dns/zone_delete.py b/SoftLayer/CLI/dns/zone_delete.py index 83eb11273..cca4c9c9e 100644 --- a/SoftLayer/CLI/dns/zone_delete.py +++ b/SoftLayer/CLI/dns/zone_delete.py @@ -17,6 +17,7 @@ def cli(env, zone): """Delete zone. Example:: + slcli dns zone-delete ibm.com This command deletes a zone that is named ibm.com """ diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index c4c284636..0e51eb308 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -254,8 +254,7 @@ def confirm(prompt_str, default=False): def no_going_back(confirmation): """Show a confirmation to a user. - :param confirmation str: the string the user has to enter in order to - confirm their action. + :param confirmation str: the string the user has to enter in order to confirm their action. """ if not confirmation: confirmation = 'yes' diff --git a/SoftLayer/CLI/globalip/assign.py b/SoftLayer/CLI/globalip/assign.py index 1e793761e..a03d83744 100644 --- a/SoftLayer/CLI/globalip/assign.py +++ b/SoftLayer/CLI/globalip/assign.py @@ -5,27 +5,51 @@ import SoftLayer from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers -target_types = {'vlan': 'SoftLayer_Network_Vlan', - 'ip': 'SoftLayer_Network_Subnet_IpAddress', - 'hardware': 'SoftLayer_Hardware_Server', - 'vsi': 'SoftLayer_Virtual_Guest'} + +# pylint: disable=unused-argument +def targetipcallback(ctx, param, value): + """This is here to allow for using --target-id in some cases. Takes the first value and returns it""" + if value: + return value[0] + return value @click.command(cls=SoftLayer.CLI.command.SLCommand, epilog="More information about types and identifiers " "on https://sldn.softlayer.com/reference/services/SoftLayer_Network_Subnet/route/") -@click.argument('identifier') +@click.argument('globalip') +@click.argument('targetip', nargs=-1, callback=targetipcallback) @click.option('--target', type=click.Choice(['vlan', 'ip', 'hardware', 'vsi']), help='choose the type. vlan, ip, hardware, vsi') -@click.option('--target-id', help='The identifier for the destination resource to route this subnet to. ') +@click.option('--target-id', help='The identifier for the destination resource to route this subnet to.') @environment.pass_env -def cli(env, identifier, target, target_id): - """Assigns the subnet to a target. +def cli(env, globalip, targetip, target, target_id): + """Assigns the GLOBALIP to TARGETIP. + GLOBALIP should be either the Global IP address, or the SoftLayer_Network_Subnet_IpAddress_Global id + See `slcli globalip list` + TARGETIP should be either the target IP address, or the SoftLayer_Network_Subnet_IpAddress id + See `slcli subnet list` Example:: + slcli globalip assign 12345678 9.111.123.456 - This command assigns IP address with ID 12345678 to a target device whose IP address is 9.111.123.456 - """ + This command assigns Global IP address with ID 12345678 to a target device whose IP address is 9.111.123.456 + slcli globalip assign 123.4.5.6 6.5.4.123 + Global IPs can be specified by their IP address + """ mgr = SoftLayer.NetworkManager(env.client) - mgr.route(identifier, target_types.get(target), target_id) + # Find SoftLayer_Network_Subnet_IpAddress_Global::id + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, globalip, name='Global IP') + + # Find Global IPs SoftLayer_Network_Subnet::id + mask = "mask[id,ipAddress[subnetId]]" + subnet = env.client.call('SoftLayer_Network_Subnet_IpAddress_Global', 'getObject', id=global_ip_id, mask=mask) + subnet_id = subnet.get('ipAddress', {}).get('subnetId') + + # For backwards compatibility + if target_id: + targetip = target_id + + mgr.route(subnet_id, 'SoftLayer_Network_Subnet_IpAddress', targetip) diff --git a/SoftLayer/CLI/globalip/cancel.py b/SoftLayer/CLI/globalip/cancel.py index 0d9394b24..920d07c71 100644 --- a/SoftLayer/CLI/globalip/cancel.py +++ b/SoftLayer/CLI/globalip/cancel.py @@ -18,12 +18,12 @@ def cli(env, identifier, force): """Cancel global IP. Example:: + slcli globalip cancel 12345 """ mgr = SoftLayer.NetworkManager(env.client) - global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, identifier, - name='global ip') + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, identifier, name='global ip') if not force: if not (env.skip_confirmations or diff --git a/SoftLayer/CLI/globalip/unassign.py b/SoftLayer/CLI/globalip/unassign.py index 563ebb106..564c74a8f 100644 --- a/SoftLayer/CLI/globalip/unassign.py +++ b/SoftLayer/CLI/globalip/unassign.py @@ -12,9 +12,21 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Unassigns a global IP from a target.""" + """Unroutes IDENTIFIER + + IDENTIFIER should be either the Global IP address, or the SoftLayer_Network_Subnet_IpAddress_Global id + Example:: + + slcli globalip unassign 123456 + + slcli globalip unassign 123.43.22.11 +""" 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) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, identifier, name='global ip') + + # Find Global IPs SoftLayer_Network_Subnet::id + mask = "mask[id,ipAddress[subnetId]]" + subnet = env.client.call('SoftLayer_Network_Subnet_IpAddress_Global', 'getObject', id=global_ip_id, mask=mask) + subnet_id = subnet.get('ipAddress', {}).get('subnetId') + mgr.clear_route(subnet_id) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress_Global.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress_Global.py index 89cd22f50..39244730b 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress_Global.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress_Global.py @@ -1,3 +1,3 @@ route = True unroute = True -getObject = {'id': 1234, 'billingItem': {'id': 1234}} +getObject = {'id': 1234, 'billingItem': {'id': 1234}, 'ipAddress': {'subnetId': 9988}} diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 201864606..49af7197f 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -841,12 +841,12 @@ def get_closed_pods(self): def route(self, subnet_id, type_serv, target): """Assigns a subnet to a specified target. - :param int subnet_id: The ID of the global IP being assigned + https://sldn.softlayer.com/reference/services/SoftLayer_Network_Subnet/route/ + :param int subnet_id: The ID of the SoftLayer_Network_Subnet_IpAddress being routed :param string type_serv: The type service to assign :param string target: The instance to assign """ - return self.client.call('SoftLayer_Network_Subnet', 'route', - type_serv, target, id=subnet_id, ) + return self.client.call('SoftLayer_Network_Subnet', 'route', type_serv, target, id=subnet_id, ) def get_datacenter(self, _filter=None, datacenter=None): """Calls SoftLayer_Location::getDatacenters() diff --git a/tests/CLI/modules/globalip_tests.py b/tests/CLI/modules/globalip_tests.py index 43c5b0f4d..a309d5636 100644 --- a/tests/CLI/modules/globalip_tests.py +++ b/tests/CLI/modules/globalip_tests.py @@ -12,15 +12,9 @@ import json -class DnsTests(testing.TestCase): +class GlobalIpTests(testing.TestCase): - def test_ip_assign(self): - result = self.run_command(['globalip', 'assign', '1']) - - self.assert_no_fail(result) - self.assertEqual(result.output, "") - - @mock.patch('SoftLayer.CLI.formatting.no_going_back') + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_ip_cancel(self, no_going_back_mock): # Test using --really flag result = self.run_command(['--really', 'globalip', 'cancel', '1']) @@ -39,7 +33,7 @@ def test_ip_cancel(self, no_going_back_mock): no_going_back_mock.return_value = False result = self.run_command(['globalip', 'cancel', '1']) - self.assertEqual(result.exit_code, 0) + self.assertEqual(result.exit_code, 2) def test_ip_list(self): result = self.run_command(['globalip', 'list', '--ip-version=v4']) @@ -84,6 +78,31 @@ def test_ip_unassign(self): result = self.run_command(['globalip', 'unassign', '1']) self.assert_no_fail(result) self.assertEqual(result.output, "") + self.assert_called_with('SoftLayer_Network_Subnet', 'clearRoute', identifier=9988) + + def test_ip_assign(self): + result = self.run_command(['globalip', 'assign', '1', '999']) + self.assert_no_fail(result) + self.assertEqual(result.output, "") + service = 'SoftLayer_Network_Subnet_IpAddress' + self.assert_called_with('SoftLayer_Network_Subnet', 'route', identifier=9988, args=(service, '999')) + + def test_ip_assign_target(self): + result = self.run_command(['globalip', 'assign', '1', '--target-id=8123']) + self.assert_no_fail(result) + self.assertEqual(result.output, "") + service = 'SoftLayer_Network_Subnet_IpAddress' + self.assert_called_with('SoftLayer_Network_Subnet', 'route', identifier=9988, args=(service, '8123')) + + def test_ip_assign_ip(self): + mock_api = self.set_mock('SoftLayer_Account', 'getGlobalIpRecords') + mock_api.return_value = [{"id": 112233}] + result = self.run_command(['globalip', 'assign', '192.168.1.1', '1.2.3.4']) + self.assert_no_fail(result) + self.assertEqual(result.output, "") + service = 'SoftLayer_Network_Subnet_IpAddress' + self.assert_called_with(f"{service}_Global", "getObject", identifier=112233) + self.assert_called_with('SoftLayer_Network_Subnet', 'route', identifier=9988, args=(service, '1.2.3.4')) def test_ip_cancel_force(self): result = self.run_command(['globalip', 'cancel', '1', '--force']) From b535762127d481a2e8e5aa9dc56c361dc69cf042 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Aug 2024 17:35:42 -0500 Subject: [PATCH 5/8] Added vpn status to user list. Fixed #2178 --- SoftLayer/CLI/user/list.py | 6 ++++-- SoftLayer/managers/user.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/user/list.py b/SoftLayer/CLI/user/list.py index 17f3c8f69..779f14011 100644 --- a/SoftLayer/CLI/user/list.py +++ b/SoftLayer/CLI/user/list.py @@ -20,7 +20,8 @@ column_helper.Column('hardwareCount', ('hardwareCount',)), column_helper.Column('virtualGuestCount', ('virtualGuestCount',)), column_helper.Column('2FA', (TWO_FACTO_AUTH,)), - column_helper.Column('classicAPIKey', (CLASSIC_API_KEYS,)) + column_helper.Column('classicAPIKey', (CLASSIC_API_KEYS,)), + column_helper.Column('vpn', ('sslVpnAllowedFlag',)) ] DEFAULT_COLUMNS = [ @@ -30,6 +31,7 @@ 'displayName', '2FA', 'classicAPIKey', + 'vpn' ] @@ -48,7 +50,7 @@ def cli(env, columns): table = formatting.Table(columns.columns) for user in users: - user = _yes_format(user, [TWO_FACTO_AUTH, CLASSIC_API_KEYS]) + user = _yes_format(user, [TWO_FACTO_AUTH, CLASSIC_API_KEYS, 'sslVpnAllowedFlag']) table.add_row([value or formatting.blank() for value in columns.row(user)]) diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 72b23dfda..87a5f0791 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -54,7 +54,7 @@ def list_users(self, objectmask=None, objectfilter=None): if objectmask is None: objectmask = """mask[id, username, displayName, userStatus[name], hardwareCount, virtualGuestCount, - email, roles, externalBindingCount,apiAuthenticationKeyCount]""" + email, roles, externalBindingCount,apiAuthenticationKeyCount, sslVpnAllowedFlag]""" return self.account_service.getUsers(mask=objectmask, filter=objectfilter) From 337514079aa53f6ee1d30d0a5a8fa50b07ce5823 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 9 Aug 2024 15:39:53 -0500 Subject: [PATCH 6/8] Fixed a bug when displaying empty tables. Fixed #2165 --- SoftLayer/CLI/formatting.py | 7 +++++-- SoftLayer/CLI/virt/detail.py | 30 +++++++---------------------- tests/CLI/formatting_table_tests.py | 20 +++++++++++++++++++ 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 0e51eb308..b9eca571e 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -70,7 +70,6 @@ def format_output(data, fmt='table', theme=None): # pylint: disable=R0911,R0912 return output # fallback, convert this odd object to a string - # print(f"Casting this to string {data}") return str(data) @@ -318,12 +317,16 @@ def __init__(self, columns, title=None, align=None): self.sortby = None self.title = title # Used to print a message if the table is empty - self.empty_message = None + self.empty_message = "-" def __bool__(self): """Useful for seeing if the table has any rows""" return len(self.rows) > 0 + def __str__(self): + """A Table should only be cast to a string if its empty""" + return self.empty_message + def set_empty_message(self, message): """Sets the empty message for this table for env.fout diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 958c865b1..041375d86 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -17,8 +17,7 @@ @click.command(cls=SoftLayer.CLI.command.SLCommand, ) @click.argument('identifier') -@click.option('--passwords', - is_flag=True, +@click.option('--passwords', is_flag=True, help='Show passwords (check over your shoulder!)') @click.option('--price', is_flag=True, help='Show associated prices') @environment.pass_env @@ -53,10 +52,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['active_transaction', formatting.active_txn(result)]) table.add_row(['datacenter', result['datacenter']['name'] or formatting.blank()]) _cli_helper_dedicated_host(env, result, table) - operating_system = utils.lookup(result, - 'operatingSystem', - 'softwareLicense', - 'softwareDescription') or {} + operating_system = utils.lookup(result, 'operatingSystem', 'softwareLicense', 'softwareDescription') or {} table.add_row(['os', operating_system.get('name', '-')]) table.add_row(['os_version', operating_system.get('version', '-')]) table.add_row(['cores', result['maxCpu']]) @@ -76,10 +72,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['last_transaction', last_transaction]) table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else 'Monthly']) - table.add_row(['preset', utils.lookup(result, 'billingItem', - 'orderItem', - 'preset', - 'keyName') or '-']) + table.add_row(['preset', utils.lookup(result, 'billingItem', 'orderItem', 'preset', 'keyName') or '-']) table.add_row(_get_owner_row(result)) table.add_row(_get_vlan_table(result)) @@ -94,9 +87,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['notes', result.get('notes', '-')]) if price: - total_price = utils.lookup(result, - 'billingItem', - 'nextInvoiceTotalRecurringAmount') or 0 + total_price = utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount') or 0 if total_price != 0: table.add_row(['Prices', _price_table(utils.lookup(result, 'billingItem'), total_price)]) table.add_row(['Price rate', total_price]) @@ -107,10 +98,7 @@ def cli(env, identifier, passwords=False, price=False): for component in result['softwareComponents']: for item in component['passwords']: pass_table.add_row([ - utils.lookup(component, - 'softwareLicense', - 'softwareDescription', - 'name'), + utils.lookup(component, 'softwareLicense', 'softwareDescription', 'name'), item['username'], item['password'], ]) @@ -122,10 +110,7 @@ def cli(env, identifier, passwords=False, price=False): # Test to see if this actually has a primary (public) ip address try: if not result['privateNetworkOnlyFlag']: - ptr_domains = env.client.call( - 'Virtual_Guest', 'getReverseDomainRecords', - id=vs_id, - ) + ptr_domains = env.client.call('Virtual_Guest', 'getReverseDomainRecords', id=vs_id) for ptr_domain in ptr_domains: for ptr in ptr_domain['resourceRecords']: @@ -196,8 +181,7 @@ def _get_vlan_table(result): vlan_table = formatting.Table(['type', 'number', 'id']) for vlan in result['networkVlans']: - vlan_table.add_row([ - vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) + vlan_table.add_row([vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) return ['vlans', vlan_table] diff --git a/tests/CLI/formatting_table_tests.py b/tests/CLI/formatting_table_tests.py index 4d62a742b..f59764231 100644 --- a/tests/CLI/formatting_table_tests.py +++ b/tests/CLI/formatting_table_tests.py @@ -48,6 +48,26 @@ def test_key_value_table(self): result = capture.get() self.assertEqual(expected, result) + def test_key_value_table_empty(self): + + expected = """┌────────┬───────┐ +│ name │ value │ +├────────┼───────┤ +│ table2 │ - │ +└────────┴───────┘ +""" + table1 = formatting.KeyValueTable(["name", "value"]) + table2 = formatting.Table(["one", "two", "three"]) + table1.add_row(["table2", table2]) + result = formatting.format_output(table1, "table") + console = Console() + + with console.capture() as capture: + to_print = formatting.format_output(table1) + console.print(to_print) + result = capture.get() + self.assertEqual(expected, result) + def test_unrenderable_recovery_table(self): expected = """│ Sub Table │ [ Date: Fri, 9 Aug 2024 15:58:14 -0500 Subject: [PATCH 7/8] Updated snapcraft build to hopefully fix build errors. Fixed #2167 --- snap/snapcraft.yaml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 42375ded7..4b78a2c9d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -3,6 +3,7 @@ adopt-info: slcli summary: A CLI tool to interact with the SoftLayer API. description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. + SLCLI documentation can be found here: https://softlayer-python.readthedocs.io/en/latest/ license: MIT @@ -10,14 +11,9 @@ base: core22 grade: stable confinement: strict -assumes: - - command-chain - apps: slcli: command: bin/slcli - command-chain: - - bin/homeishome-launch environment: LC_ALL: C.UTF-8 plugs: @@ -38,9 +34,4 @@ parts: - python3 stage-packages: - - python3 - - homeishome-launch: - plugin: nil - stage-snaps: - - homeishome-launch + - python3 From c4bded12ddc622983c3f368093a56cac97981a88 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 9 Aug 2024 16:01:46 -0500 Subject: [PATCH 8/8] Improved the formatting of hardware details page. Fixed #2153 --- SoftLayer/CLI/hardware/detail.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index fe8661153..404a3235f 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -173,10 +173,13 @@ def _bw_table(bw_data): def _system_table(system_data): - table = formatting.Table(['Type', 'name']) + table = formatting.Table(['Type', 'Name']) + table.align['Type'] = 'r' + table.align['Name'] = 'l' + table.sortby = 'Type' for system in system_data: - table.add_row([utils.lookup(system, 'hardwareComponentModel', - 'hardwareGenericComponentModel', - 'hardwareComponentType', 'keyName'), - utils.lookup(system, 'hardwareComponentModel', 'longDescription')]) + c_type = utils.lookup(system, 'hardwareComponentModel', 'hardwareGenericComponentModel', + 'hardwareComponentType', 'keyName') + c_name = utils.lookup(system, 'hardwareComponentModel', 'longDescription') + table.add_row([c_type, c_name]) return table