Repository: softlayer/softlayer-python

Issue:
`slcli order preset-list` prices don't seem to match the portal
```
$  slcli order preset-list PUBLIC_CLOUD_SERVER --prices | grep BL2_56
| BL2_56X242X100  |   291   | 1.094  |   726   |   - - - -   |         |
```

![image](https://user-images.githubusercontent.com/7408017/198052219-95d4eec5-d3c6-41e1-b6f8-f7f57d9b42b3.png)


Also, update `location` to say `ALL` or something instead of `[]`

Discussion:

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2ec9136a..ecbd7ad6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -40,7 +40,7 @@ ## Documentation
 
 Docs are generated with [Sphinx](https://docs.readthedocs.io/en/latest/intro/getting-started-with-sphinx.html) and once Sphinx is setup, you can simply do
 
-`make html` in the softlayer-python/docs directory, which should generate the HTML in softlayer-python/docs/_build/html for testing. 
+`make html` in the softlayer-python/docs directory, which should generate the HTML in `softlayer-python/docs/_build/html` for testing.
 
 
 ## Unit Tests
diff --git a/SoftLayer/CLI/command.py b/SoftLayer/CLI/command.py
index 95013769..eab16e2b 100644
--- a/SoftLayer/CLI/command.py
+++ b/SoftLayer/CLI/command.py
@@ -190,7 +190,7 @@ def format_help_text(self, ctx: click.Context, formatter: click.formatting.HelpF
         if text:
             text = inspect.cleandoc(text)
 
-        self.console.print(f"\n\t{text}\n")
+        self.console.print(f"\n\t{text}\n", highlight=False)
 
     def format_epilog(self, ctx: click.Context, formatter: click.formatting.HelpFormatter) -> None:
         """Writes the epilog if it exists, then prints out any sub-commands if they exist."""
diff --git a/SoftLayer/CLI/hardware/create_credential.py b/SoftLayer/CLI/hardware/create_credential.py
deleted file mode 100644
index 40bf14f6..00000000
--- a/SoftLayer/CLI/hardware/create_credential.py
+++ /dev/null
@@ -1,57 +0,0 @@
-"""Create a password for a software component"""
-# :license: MIT, see LICENSE for more details.
-
-import click
-
-import SoftLayer
-from SoftLayer.CLI import environment
-from SoftLayer.CLI import exceptions
-from SoftLayer.CLI import formatting
-
-
-@click.command(cls=SoftLayer.CLI.command.SLCommand, )
-@click.argument('identifier')
-@click.option('--username', '-U', required=True, help="The username part of the username/password pair")
-@click.option('--password', '-P', required=True, help="The password part of the username/password pair.")
-@click.option('--notes', '-n', help="A note string stored for this username/password pair.")
-@click.option('--system', required=True, help="The name of this specific piece of software.")
-@environment.pass_env
-def cli(env, identifier, username, password, notes, system):
-    """Create a password for a software component."""
-
-    mgr = SoftLayer.HardwareManager(env.client)
-
-    software = mgr.get_software_components(identifier)
-    sw_id = ''
-    try:
-        for sw_instance in software:
-            if (sw_instance['softwareLicense']['softwareDescription']['name']).lower() == system:
-                sw_id = sw_instance['id']
-    except KeyError as ex:
-        raise exceptions.CLIAbort('System id not found') from ex
-
-    template = {
-        "notes": notes,
-        "password": password,
-        "softwareId": sw_id,
-        "username": username,
-        "software": {
-            "hardwareId": identifier,
-            "softwareLicense": {
-                "softwareDescription": {
-                    "name": system
-                }
-            }
-        }}
-
-    result = mgr.create_credential(template)
-
-    table = formatting.KeyValueTable(['name', 'value'])
-    table.align['name'] = 'r'
-    table.align['value'] = 'l'
-    table.add_row(['Software Id', result['id']])
-    table.add_row(['Created', result['createDate']])
-    table.add_row(['Username', result['username']])
-    table.add_row(['Password', result['password']])
-    table.add_row(['Notes', result['notes']])
-    env.fout(table)
diff --git a/SoftLayer/CLI/hardware/credentials.py b/SoftLayer/CLI/hardware/credentials.py
index dc6ddcde..6371f639 100644
--- a/SoftLayer/CLI/hardware/credentials.py
+++ b/SoftLayer/CLI/hardware/credentials.py
@@ -29,6 +29,6 @@ def cli(env, identifier):
         for credentials in item['passwords']:
             table.add_row([credentials.get('username', 'None'),
                            credentials.get('password', 'None'),
-                           item['softwareLicense']['softwareDescription']['name'],
+                           item['softwareLicense']['softwareDescription']['referenceCode'],
                            item['softwareLicense']['softwareDescription']['version']])
     env.fout(table)
diff --git a/SoftLayer/CLI/order/preset_list.py b/SoftLayer/CLI/order/preset_list.py
index e832cf24..92560350 100644
--- a/SoftLayer/CLI/order/preset_list.py
+++ b/SoftLayer/CLI/order/preset_list.py
@@ -8,16 +8,13 @@
 from SoftLayer.CLI import formatting
 from SoftLayer.managers import ordering
 
-COLUMNS = ['name',
-           'keyName',
-           'description', ]
+COLUMNS = ['Name', 'Key Name', 'Description']
 
 
 @click.command(cls=SLCommand)
 @click.argument('package_keyname')
-@click.option('--keyword',
-              help="A word (or string) used to filter preset names.")
-@click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, e.g. --prices')
+@click.option('--keyword', help="A word (or string) used to filter preset names.")
+@click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices.')
 @environment.pass_env
 def cli(env, package_keyname, keyword, prices):
     """List package presets.
@@ -25,6 +22,7 @@ def cli(env, package_keyname, keyword, prices):
     .. Note::
         Presets are set CPU / RAM / Disk allotments. You still need to specify required items.
         Some packages do not have presets.
+        Cost includes all items in a preset, and may include optional items.
 
     ::
 
@@ -34,7 +32,37 @@ def cli(env, package_keyname, keyword, prices):
         # List the Bare Metal server presets that include a GPU
         slcli order preset-list BARE_METAL_SERVER --keyword gpu
 
+        # Get a specific flavor for Virtual Server
+        slcli order preset-list PUBLIC_CLOUD_SERVER --prices --keyword BL2.56x242x
+
+        # All packages with active presets
+        slcli call-api SoftLayer_Product_Package getAllObjects --mask="mask[id,keyName,activePresetCount]" \
+--json-filter='{"activePresets":{"operation":"not null"}}'
+        ┌───────────────────┬──────┬──────────────────────────────────────────────────────────────────┐
+        │ activePresetCount │  id  │                             keyName                              │
+        ├───────────────────┼──────┼──────────────────────────────────────────────────────────────────┤
+        │         1         │ 144  │                              3U_GPU                              │
+        │         6         │ 200  │                        BARE_METAL_SERVER                         │
+        │         1         │ 571  │                           NETWORK_VLAN                           │
+        │        100        │ 835  │                       PUBLIC_CLOUD_SERVER                        │
+        │         6         │ 865  │                     NETWORK_VLAN_FOR_SERVICE                     │
+        │         5         │ 885  │                           8U_BI_S2_H4                            │
+        │        56         │ 991  │                      TRANSIENT_CLOUD_SERVER                      │
+        │        56         │ 1035 │                       SUSPEND_CLOUD_SERVER                       │
+        │         9         │ 1045 │                          2U_BI_S3_H2000                          │
+        │         7         │ 1075 │                   2U_IC4V_FIXED_CONFIGURATIONS                   │
+        │        32         │ 1109 │                           BI_S4_H2000                            │
+        │         8         │ 1117 │                           BI_S4_H4000                            │
+        │         7         │ 1119 │                           BI_S4_H8000                            │
+        │         5         │ 2636 │                    2U_BI_S4_H2000_AEP_ENABLED                    │
+        │         5         │ 2676 │                    4U_BI_S4_H4000_AEP_ENABLED                    │
+        │         5         │ 2700 │                    4U_BI_S4_H8000_AEP_ENABLED                    │
+        │         6         │ 2866 │ ORACLE_APPLICATION_CLUSTER_CASCADE_LAKE_SCALABLE_FAMILY_4_DRIVES │
+        │         1         │ 2874 │          ORACLE_APPLICATION_CLUSTER_COFFEE_LAKE_E2174G           │
+        └───────────────────┴──────┴──────────────────────────────────────────────────────────────────┘
+
     """
+    click.secho("*NOTICE*: Cost includes all items in a preset, and may include optional items.", fg="yellow")
     manager = ordering.OrderingManager(env.client)
 
     _filter = {}
@@ -43,19 +71,23 @@ def cli(env, package_keyname, keyword, prices):
     presets = manager.list_presets(package_keyname, filter=_filter)
 
     if prices:
-        table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction', 'Location'])
+        table = formatting.Table(['Id', 'Key Name', 'Hourly', 'Monthly', 'Location'])
+        table.align = {"Id": "center", "Key Name": "left", "Hourly": "right", "Monthly": "right", "Location": "center"}
         for price in presets:
-            locations = []
+            locations_list = []
             if price['locations'] != []:
                 for location in price['locations']:
-                    locations.append(location['name'])
-            cr_max = get_item_price_data(price['prices'][0], 'capacityRestrictionMaximum')
-            cr_min = get_item_price_data(price['prices'][0], 'capacityRestrictionMinimum')
-            cr_type = get_item_price_data(price['prices'][0], 'capacityRestrictionType')
-            table.add_row([price['keyName'], price['id'],
-                          get_item_price_data(price['prices'][0], 'hourlyRecurringFee'),
-                          get_item_price_data(price['prices'][0], 'recurringFee'),
-                          "%s - %s %s" % (cr_min, cr_max, cr_type), str(locations)])
+                    locations_list.append(location['name'])
+                locations = ', '.join(locations_list)
+            else:
+                locations = "All"
+            table.add_row([
+                price['id'],
+                price['keyName'],
+                get_item_price_data(price['prices'], 'hourlyRecurringFee'),
+                get_item_price_data(price['prices'], 'recurringFee'),
+                locations
+            ])
 
     else:
         table = formatting.Table(COLUMNS)
@@ -69,9 +101,10 @@ def cli(env, package_keyname, keyword, prices):
     env.fout(table)
 
 
-def get_item_price_data(price, item_attribute):
+def get_item_price_data(prices, item_attribute):
     """Given an SoftLayer_Product_Item_Price, returns its default price data"""
-    result = '-'
-    if item_attribute in price:
-        result = price[item_attribute]
-    return result
+    result = 0.0
+    for this_price in prices:
+        if item_attribute in this_price:
+            result = result + float(this_price[item_attribute])
+    return round(result, 3)
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index 6af03600..1e96961a 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -296,7 +296,6 @@
     ('hardware:monitoring', 'SoftLayer.CLI.hardware.monitoring:cli'),
     ('hardware:notifications', 'SoftLayer.CLI.hardware.notifications:cli'),
     ('hardware:add-notification', 'SoftLayer.CLI.hardware.add_notification:cli'),
-    ('hardware:create-credential', 'SoftLayer.CLI.hardware.create_credential:cli'),
 
     ('securitygroup', 'SoftLayer.CLI.securitygroup'),
     ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'),
diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py
index 38c6fb1d..f8164453 100644
--- a/SoftLayer/fixtures/SoftLayer_Hardware.py
+++ b/SoftLayer/fixtures/SoftLayer_Hardware.py
@@ -104,35 +104,3 @@
         "upperNonCritical": "12.915",
         "upperNonRecoverable": "13.403"
     }]
-
-getSoftwareComponents = [{
-    "hardwareId": 123456,
-    "id": 67064532,
-    "passwords": [
-        {
-            "id": 77995567,
-            "notes": "testslcli1",
-            "password": "123456",
-            "softwareId": 67064532,
-            "username": "testslcli1",
-        },
-        {
-            "id": 77944803,
-            "notes": "testslcli2",
-            "password": "test123",
-            "softwareId": 67064532,
-            "username": "testslcli2",
-        }
-    ],
-    "softwareLicense": {
-        "id": 21854,
-        "softwareDescriptionId": 2914,
-        "softwareDescription": {
-            "id": 2914,
-            "longDescription": "Ubuntu 20.04.1-64",
-            "name": "Ubuntu",
-            "referenceCode": "UBUNTU_20_64",
-            "version": "20.04.1-64"
-        }
-    }
-}]
diff --git a/SoftLayer/fixtures/SoftLayer_Software_Component_Password.py b/SoftLayer/fixtures/SoftLayer_Software_Component_Password.py
deleted file mode 100644
index e5bd1673..00000000
--- a/SoftLayer/fixtures/SoftLayer_Software_Component_Password.py
+++ /dev/null
@@ -1,13 +0,0 @@
-createObject = {
-    "createDate": "2022-10-18T09:27:48-06:00",
-    "id": 78026761,
-    "notes": "test slcli",
-    "password": "123456",
-    "softwareId": 67064532,
-    "username": "testslcli",
-    "software": {
-        "hardwareId": 1532729,
-        "id": 67064532,
-        "manufacturerLicenseInstance": ""
-    }
-}
diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py
index 88ecb7bb..92a820ca 100644
--- a/SoftLayer/managers/hardware.py
+++ b/SoftLayer/managers/hardware.py
@@ -260,7 +260,7 @@ def get_hardware(self, hardware_id, **kwargs):
                                                         name,
                                                         version,
                                                         referenceCode]],
-                    passwords[id,username,password]],'''
+                    passwords[username,password]],'''
                 'billingItem['
                 'id,nextInvoiceTotalRecurringAmount,'
                 'nextInvoiceChildren[nextInvoiceTotalRecurringAmount],'
@@ -1102,14 +1102,6 @@ def add_notification(self, hardware_id, user_id):
         template = {"hardwareId": hardware_id, "userId": user_id}
         return self.client.call('SoftLayer_User_Customer_Notification_Hardware', 'createObject', template)
 
-    def get_software_components(self, hardware_id):
-        """Returns  a piece of hardware’s installed software."""
-        return self.client.call('Hardware', 'getSoftwareComponents', id=hardware_id)
-
-    def create_credential(self, template):
-        """Create a password for a software component"""
-        return self.client.call('SoftLayer_Software_Component_Password', 'createObject', template)
-
 
 def _get_bandwidth_key(items, hourly=True, no_public=False, location=None):
     """Picks a valid Bandwidth Item, returns the KeyName"""
diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py
index bfa33731..23137507 100644
--- a/SoftLayer/managers/ordering.py
+++ b/SoftLayer/managers/ordering.py
@@ -16,7 +16,7 @@
 
 PACKAGE_MASK = '''id, name, keyName, isActive, type'''
 
-PRESET_MASK = '''id, name, keyName, description, categories, prices, locations'''
+PRESET_MASK = '''id, name, keyName, description, prices[id, hourlyRecurringFee, recurringFee], locations'''
 
 
 class OrderingManager(object):
diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst
index 5689b5e4..dfd10e82 100644
--- a/docs/cli/hardware.rst
+++ b/docs/cli/hardware.rst
@@ -139,7 +139,3 @@ This function updates the firmware of a server. If already at the latest version
 .. click:: SoftLayer.CLI.hardware.add_notification:cli
    :prog: hardware add-notification
    :show-nested:
-
-.. click:: SoftLayer.CLI.hardware.create_credential:cli
-   :prog: hardware create-credential
-   :show-nested:


Test patch:
diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py
index 246aa920..88c5d869 100644
--- a/tests/CLI/modules/order_tests.py
+++ b/tests/CLI/modules/order_tests.py
@@ -265,15 +265,11 @@ def test_verify_monthly(self):
                          json.loads(result.output))
 
     def test_preset_list(self):
-        active_preset1 = {'name': 'active1', 'keyName': 'PRESET1',
-                          'description': 'description1'}
-        active_preset2 = {'name': 'active2', 'keyName': 'PRESET2',
-                          'description': 'description2'}
-        acc_preset = {'name': 'account1', 'keyName': 'PRESET3',
-                      'description': 'description3'}
+        active_preset1 = {'name': 'active1', 'keyName': 'PRESET1', 'description': 'description1'}
+        active_preset2 = {'name': 'active2', 'keyName': 'PRESET2', 'description': 'description2'}
+        acc_preset = {'name': 'account1', 'keyName': 'PRESET3', 'description': 'description3'}
         active_mock = self.set_mock('SoftLayer_Product_Package', 'getActivePresets')
-        account_mock = self.set_mock('SoftLayer_Product_Package',
-                                     'getAccountRestrictedActivePresets')
+        account_mock = self.set_mock('SoftLayer_Product_Package', 'getAccountRestrictedActivePresets')
         active_mock.return_value = [active_preset1, active_preset2]
         account_mock.return_value = [acc_preset]
 
@@ -282,16 +278,9 @@ def test_preset_list(self):
         self.assert_no_fail(result)
         self.assert_called_with('SoftLayer_Product_Package', 'getActivePresets')
         self.assert_called_with('SoftLayer_Product_Package', 'getAccountRestrictedActivePresets')
-        self.assertEqual([{'name': 'active1',
-                           'keyName': 'PRESET1',
-                           'description': 'description1'},
-                          {'name': 'active2',
-                           'keyName': 'PRESET2',
-                           'description': 'description2'},
-                          {'name': 'account1',
-                           'keyName': 'PRESET3',
-                           'description': 'description3'}],
-                         json.loads(result.output))
+        self.assertIn('"Key Name": "PRESET1",', result.output)
+        self.assertIn('"Description": "description2"', result.output)
+        self.assertIn('"Name": "account1",', result.output)
 
     def test_preset_list_keywork(self):
         result = self.run_command(['order', 'preset-list', 'package', '--keyword', 'testKeyWord'])
@@ -300,9 +289,20 @@ def test_preset_list_keywork(self):
         self.assert_called_with('SoftLayer_Product_Package', 'getActivePresets', filter=_filter)
 
     def test_preset_list_prices(self):
+        active_preset1 = {
+            'name': 'active1', 'keyName': 'PRESET1', 'description': 'description1', 'locations': [], 'id': 118822,
+            'prices': [
+                {'hourlyRecurringFee': 100, 'recurringFee': 55},
+                {'hourlyRecurringFee': 110, 'recurringFee': 44},
+            ]
+        }
+        active_mock = self.set_mock('SoftLayer_Product_Package', 'getActivePresets')
+        active_mock.return_value = [active_preset1]
         result = self.run_command(['order', 'preset-list', 'package', '--prices'])
         self.assert_no_fail(result)
         self.assert_called_with('SoftLayer_Product_Package', 'getActivePresets')
+        self.assertIn("210", result.output)
+        self.assertIn("99", result.output)
 
     def test_location_list(self):
         result = self.run_command(['order', 'package-locations', 'package'])
diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py
index 7de8fa05..d2353f32 100644
--- a/tests/CLI/modules/server_tests.py
+++ b/tests/CLI/modules/server_tests.py
@@ -46,16 +46,14 @@ def test_server_credentials(self):
                     'softwareDescription':
                         {
                             'referenceCode': 'CENTOS_7_64',
-                            'version': '7.8 - 64',
-                            'name': 'Centos'
-                        }}}]
+                            'version': '7.8 - 64'}}}]
         }
         result = self.run_command(['hardware', 'credentials', '12345'])
 
         self.assert_no_fail(result)
         self.assertEqual(json.loads(result.output), [
             {'Password': 'abc123',
-             'Software': 'Centos',
+             'Software': 'CENTOS_7_64',
              'Username': 'root',
              'Version': '7.8 - 64'}
         ])
@@ -95,7 +93,7 @@ def test_server_credentials_exception_password_not_found(self):
                     "passwords": [{}],
                     "softwareLicense": {
                         "softwareDescription": {
-                            "name": None,
+                            "referenceCode": None,
                             "version": None
                         }
                     }
@@ -1047,9 +1045,3 @@ def test_notifications(self):
     def test_add_notification(self):
         result = self.run_command(['hardware', 'add-notification', '100', '--users', '123456'])
         self.assert_no_fail(result)
-
-    def test_create_credential(self):
-        result = self.run_command(['hw', 'create-credential', '123456',
-                                   '--username', 'testslcli', '--password', 'test-123456',
-                                   '--notes', 'test slcli', '--system', 'system'])
-        self.assert_no_fail(result)
diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py
index 2d324d9d..b4e177bd 100644
--- a/tests/managers/hardware_tests.py
+++ b/tests/managers/hardware_tests.py
@@ -557,10 +557,10 @@ def test_edit(self):
         self.assert_called_with('SoftLayer_Hardware_Server',
                                 'editObject',
                                 args=({
-                                          'hostname': 'new-host',
-                                          'domain': 'new.sftlyr.ws',
-                                          'notes': 'random notes',
-                                      },),
+                                    'hostname': 'new-host',
+                                    'domain': 'new.sftlyr.ws',
+                                    'notes': 'random notes',
+                                },),
                                 identifier=100)
 
     def test_rescue(self):
@@ -928,27 +928,6 @@ def test_add_notification(self):
         self.hardware.add_notification(100, 123456)
         self.assert_called_with('SoftLayer_User_Customer_Notification_Hardware', 'createObject')
 
-    def test_get_software_component(self):
-        self.hardware.get_software_components(123456)
-        self.assert_called_with('SoftLayer_Hardware', 'getSoftwareComponents')
-
-    def test_create_credential(self):
-        template = {
-            "notes": 'notes',
-            "password": 'password',
-            "softwareId": 'sw_id',
-            "username": 'username',
-            "software": {
-                "hardwareId": 123456,
-                "softwareLicense": {
-                    "softwareDescription": {
-                        "name": 'system'
-                    }
-                }
-            }}
-        self.hardware.create_credential(template)
-        self.assert_called_with('SoftLayer_Software_Component_Password', 'createObject')
-
 
 class HardwareHelperTests(testing.TestCase):