Repository: softlayer/softlayer-python

Issue:
slcli vs list --search "query"
Add ability to list VSI based on a query


Discussion:

Patch:
diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py
index a16a1330..ee525ede 100644
--- a/SoftLayer/CLI/virt/list.py
+++ b/SoftLayer/CLI/virt/list.py
@@ -9,7 +9,6 @@
 from SoftLayer.CLI import environment
 from SoftLayer.CLI import formatting
 from SoftLayer.CLI import helpers
-from SoftLayer import utils
 
 # pylint: disable=unnecessary-lambda
 
@@ -56,67 +55,41 @@
 @click.option('--hourly', is_flag=True, help='Show only hourly instances')
 @click.option('--monthly', is_flag=True, help='Show only monthly instances')
 @click.option('--transient', help='Filter by transient instances', type=click.BOOL)
-@click.option('--hardware', is_flag=True, default=False, help='Show the all VSI related to hardware')
-@click.option('--all-guests', is_flag=True, default=False, help='Show the all VSI and hardware VSIs')
+@click.option('--search', is_flag=False, flag_value="", default=None,
+              help="Use the more flexible Search API to list instances. See `slcli search --types` for list " +
+              "of searchable fields.")
 @helpers.multi_option('--tag', help='Filter by tags')
-@click.option('--sortby',
-              help='Column to sort by',
-              default='hostname',
-              show_default=True)
+@click.option('--sortby', default='hostname', show_default=True, help='Column to sort by')
 @click.option('--columns',
               callback=column_helper.get_formatter(COLUMNS),
               help='Columns to display. [options: %s]'
               % ', '.join(column.name for column in COLUMNS),
               default=','.join(DEFAULT_COLUMNS),
               show_default=True)
-@click.option('--limit', '-l',
-              help='How many results to get in one api call, default is 100',
-              default=100,
-              show_default=True)
+@click.option('--limit', '-l', default=100, show_default=True,
+              help='How many results to get in one api call, default is 100')
 @environment.pass_env
 def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network,
-        hourly, monthly, tag, columns, limit, transient, hardware, all_guests):
+        hourly, monthly, tag, columns, limit, transient, search):
     """List virtual servers."""
 
-    vsi = SoftLayer.VSManager(env.client)
-    guests = vsi.list_instances(hourly=hourly,
-                                monthly=monthly,
-                                hostname=hostname,
-                                domain=domain,
-                                cpus=cpu,
-                                memory=memory,
-                                datacenter=datacenter,
-                                nic_speed=network,
-                                transient=transient,
-                                tags=tag,
-                                mask=columns.mask(),
-                                limit=limit)
+    guests = []
+    if search is not None:
+        object_mask = f"mask[resource(SoftLayer_Virtual_Guest)[{columns.mask()}]]"
+        search_manager = SoftLayer.SearchManager(env.client)
+        guests = search_manager.search_instances(hostname=hostname, domain=domain, datacenter=datacenter,
+                                                 tags=tag, search_string=search, mask=object_mask)
+    else:
+        vsi = SoftLayer.VSManager(env.client)
+        guests = vsi.list_instances(hourly=hourly, monthly=monthly, hostname=hostname, domain=domain,
+                                    cpus=cpu, memory=memory, datacenter=datacenter, nic_speed=network,
+                                    transient=transient, tags=tag, mask=columns.mask(), limit=limit)
 
     table = formatting.Table(columns.columns)
     table.sortby = sortby
-    if not hardware or all_guests:
-        for guest in guests:
-            table.add_row([value or formatting.blank()
-                           for value in columns.row(guest)])
 
-        env.fout(table)
+    for guest in guests:
+        table.add_row([value or formatting.blank()
+                       for value in columns.row(guest)])
 
-    if hardware or all_guests:
-        hardware_guests = vsi.get_hardware_guests()
-        for hd_guest in hardware_guests:
-            if hd_guest['virtualHost']['guests']:
-                title = "Hardware(id = {hardwareId}) guests associated".format(hardwareId=hd_guest['id'])
-                table_hardware_guest = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status',
-                                                         'powerState'], title=title)
-                table_hardware_guest.sortby = 'hostname'
-                for guest in hd_guest['virtualHost']['guests']:
-                    table_hardware_guest.add_row([
-                        guest['id'],
-                        guest['hostname'],
-                        '%i %s' % (guest['maxCpu'], guest['maxCpuUnits']),
-                        guest['maxMemory'],
-                        utils.clean_time(guest['createDate']),
-                        guest['status']['keyName'],
-                        guest['powerState']['keyName']
-                    ])
-                env.fout(table_hardware_guest)
+    env.fout(table)
diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py
index 2a895540..7fadd12e 100644
--- a/SoftLayer/managers/__init__.py
+++ b/SoftLayer/managers/__init__.py
@@ -24,6 +24,7 @@
 from SoftLayer.managers.network import NetworkManager
 from SoftLayer.managers.object_storage import ObjectStorageManager
 from SoftLayer.managers.ordering import OrderingManager
+from SoftLayer.managers.search import SearchManager
 from SoftLayer.managers.sshkey import SshKeyManager
 from SoftLayer.managers.ssl import SSLManager
 from SoftLayer.managers.tags import TagManager
@@ -53,6 +54,7 @@
     'ObjectStorageManager',
     'OrderingManager',
     'PlacementManager',
+    'SearchManager',
     'SshKeyManager',
     'SSLManager',
     'TagManager',
diff --git a/SoftLayer/managers/search.py b/SoftLayer/managers/search.py
index 5f632df2..a0ae8c33 100644
--- a/SoftLayer/managers/search.py
+++ b/SoftLayer/managers/search.py
@@ -34,3 +34,30 @@ def advanced(self, search_string):
 
         """
         return self.search_manager.advancedSearch(search_string)
+
+    def search_instances(self, search_string, mask=None, **kwargs):
+        """Lists VSIs based in the search_string.
+
+        Also takes in a few search terms as **kwargs. such as hostname, datacenter, domain and tags
+        """
+
+        # This forces the Search API to do a fuzzy search on our term, kinda. Not sure why the ** are
+        # Required but it will do an exact search without them.
+        if search_string:
+            search_string = f"*{search_string}*"
+        search_string = f"_objectType:SoftLayer_Virtual_Guest {search_string}"
+        if kwargs.get('hostname'):
+            search_string = f"{search_string} hostname: *{kwargs.get('hostname')}*"
+        if kwargs.get('domain'):
+            search_string = f"{search_string} domain: *{kwargs.get('domain')}*"
+        if kwargs.get('datacenter'):
+            search_string = f"{search_string} datacenter.longName: *{kwargs.get('datacenter')}*"
+        if kwargs.get('tags'):
+            tags = " ".join(kwargs.get("tags", []))
+            search_string = f"{search_string} internalTagReferences.tag.name: {tags}"
+        result = self.search_manager.advancedSearch(search_string, mask=mask)
+        guests = []
+        for resource in result:
+            guests.append(resource.get('resource'))
+
+        return guests
diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py
index f6520849..ef51be94 100644
--- a/SoftLayer/managers/vs.py
+++ b/SoftLayer/managers/vs.py
@@ -1441,15 +1441,6 @@ def migrate_dedicated(self, instance_id, host_id):
         """
         return self.guest.migrateDedicatedHost(host_id, id=instance_id)
 
-    def get_hardware_guests(self):
-        """Returns all virtualHost capable hardware objects and their guests.
-
-        :return SoftLayer_Hardware[].
-        """
-        object_filter = {"hardware": {"virtualHost": {"id": {"operation": "not null"}}}}
-        mask = "mask[virtualHost[guests[powerState]]]"
-        return self.client.call('SoftLayer_Account', 'getHardware', mask=mask, filter=object_filter)
-
     def authorize_storage(self, vs_id, username_storage):
         """Authorize File or Block Storage to a Virtual Server.
 


Test patch:
diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py
index 8562f446..fc0807fd 100644
--- a/tests/CLI/modules/vs/vs_tests.py
+++ b/tests/CLI/modules/vs/vs_tests.py
@@ -145,6 +145,23 @@ def test_list_vs(self):
 
         self.assert_no_fail(result)
 
+    def test_list_vs_search_noargs(self):
+        result = self.run_command(['vs', 'list', '--search'])
+        self.assert_no_fail(result)
+        self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=('_objectType:SoftLayer_Virtual_Guest ',))
+
+    def test_list_vs_search_noargs_domain(self):
+        result = self.run_command(['vs', 'list', '--search', '-Dtest'])
+        self.assert_no_fail(result)
+        self.assert_called_with('SoftLayer_Search', 'advancedSearch',
+                                args=('_objectType:SoftLayer_Virtual_Guest  domain: *test*',))
+
+    def test_list_vs_search_args(self):
+        result = self.run_command(['vs', 'list', '--search=thisTerm'])
+        self.assert_no_fail(result)
+        self.assert_called_with('SoftLayer_Search', 'advancedSearch',
+                                args=('_objectType:SoftLayer_Virtual_Guest *thisTerm*',))
+
     @mock.patch('SoftLayer.utils.lookup')
     def test_detail_vs_empty_billing(self, mock_lookup):
         def mock_lookup_func(dic, key, *keys):
@@ -919,10 +936,6 @@ def test_vs_migrate_exception(self):
         self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100)
         self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(999), identifier=100)
 
-    def test_list_vsi(self):
-        result = self.run_command(['vs', 'list', '--hardware'])
-        self.assert_no_fail(result)
-
     def test_credentail(self):
         result = self.run_command(['vs', 'credentials', '100'])
         self.assert_no_fail(result)
diff --git a/tests/managers/search_tests.py b/tests/managers/search_tests.py
index b82fdb65..54dfe67b 100644
--- a/tests/managers/search_tests.py
+++ b/tests/managers/search_tests.py
@@ -25,3 +25,22 @@ def test_search(self):
     def test_search_advanced(self):
         self.search.advanced('SoftLayer_Hardware')
         self.assert_called_with('SoftLayer_Search', 'advancedSearch')
+
+    def test_search_instances_basic(self):
+        search_string = "TEST_STRING"
+        expected = f"_objectType:SoftLayer_Virtual_Guest *{search_string}*"
+        self.search.search_instances(search_string)
+        self.assert_called_with('SoftLayer_Search', 'advancedSearch',
+                                args=(expected,))
+        self.search.search_instances(search_string, hostname="thisHostname")
+        self.assert_called_with('SoftLayer_Search', 'advancedSearch',
+                                args=(f"{expected} hostname: *thisHostname*",))
+        self.search.search_instances(search_string, domain="thisDomain")
+        self.assert_called_with('SoftLayer_Search', 'advancedSearch',
+                                args=(f"{expected} domain: *thisDomain*",))
+        self.search.search_instances(search_string, datacenter="dal13")
+        self.assert_called_with('SoftLayer_Search', 'advancedSearch',
+                                args=(f"{expected} datacenter.longName: *dal13*",))
+        self.search.search_instances(search_string, tags=["thisTag"])
+        self.assert_called_with('SoftLayer_Search', 'advancedSearch',
+                                args=(f"{expected} internalTagReferences.tag.name: thisTag",))
diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py
index 4c6d18b1..a0ac6dae 100644
--- a/tests/managers/vs/vs_tests.py
+++ b/tests/managers/vs/vs_tests.py
@@ -1278,38 +1278,6 @@ def test_migrate_dedicated(self):
         self.assertTrue(result)
         self.assert_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(5555,), identifier=1234)
 
-    def test_get_hardware_guests(self):
-        mock = self.set_mock('SoftLayer_Account', 'getHardware')
-        mock.return_value = [{
-            "accountId": 11111,
-            "domain": "vmware.chechu.com",
-            "hostname": "host14",
-            "id": 22222,
-            "virtualHost": {
-                "accountId": 11111,
-                "id": 33333,
-                "name": "host14.vmware.chechu.com",
-                "guests": [
-                    {
-                        "accountId": 11111,
-                        "hostname": "NSX-T Manager",
-                        "id": 44444,
-                        "maxCpu": 16,
-                        "maxCpuUnits": "CORE",
-                        "maxMemory": 49152,
-                        "powerState": {
-                            "keyName": "RUNNING",
-                            "name": "Running"
-                        },
-                        "status": {
-                            "keyName": "ACTIVE",
-                            "name": "Active"
-                        }
-                    }]}}]
-
-        result = self.vs.get_hardware_guests()
-        self.assertEqual("NSX-T Manager", result[0]['virtualHost']['guests'][0]['hostname'])
-
     def test_authorize_storage(self):
         options = self.vs.authorize_storage(1234, "SL01SEL301234-11")