Repository: softlayer/softlayer-python

Issue:
slcli server detail example01 (KeyError: 'tag')

Hello 

I am running 

slcli server detail hostname

against our entire SLR inventory (200 servers) for most of them I get expected output but for 4 out of the 100 I get this:

```
➜  ~  slcli server detail example01
An unexpected error has occured:
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/SoftLayer-4.0.2-py2.7.egg/SoftLayer/CLI/core.py", line 181, in main
    cli.main()
  File "/usr/local/lib/python2.7/site-packages/click-4.0-py2.7.egg/click/core.py", line 644, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python2.7/site-packages/click-4.0-py2.7.egg/click/core.py", line 991, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python2.7/site-packages/click-4.0-py2.7.egg/click/core.py", line 991, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python2.7/site-packages/click-4.0-py2.7.egg/click/core.py", line 837, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python2.7/site-packages/click-4.0-py2.7.egg/click/core.py", line 464, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/click-4.0-py2.7.egg/click/decorators.py", line 64, in new_func
    return ctx.invoke(f, obj, *args[1:], **kwargs)
  File "/usr/local/lib/python2.7/site-packages/click-4.0-py2.7.egg/click/core.py", line 464, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/SoftLayer-4.0.2-py2.7.egg/SoftLayer/CLI/server/detail.py", line 100, in cli
    tag_row.append(tag['tag']['name'])
KeyError: 'tag'

Feel free to report this error as it is likely a bug:
    https://github.com/softlayer/softlayer-python/issues
```

I tried it under

```
➜  ~  python -V
Python 2.7.10
```

and 

```
(softlayer)➜  ~  python -V
Python 3.4.3
```

Discussion:

Patch:
diff --git a/CHANGELOG b/CHANGELOG
index e5e1b8ad..3397760f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,9 +1,3 @@
-4.0.4
-
-    * Fixes bug with pulling the userData property for the virtual server detail
-
-    * Fixes a class of bugs invloving unicode from the API
-
 4.0.3
 
     * Fixes bug with `slcli vs ready` command
diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py
index 6f35a985..4f10980d 100644
--- a/SoftLayer/CLI/formatting.py
+++ b/SoftLayer/CLI/formatting.py
@@ -50,7 +50,7 @@ def format_output(data, fmt='table'):  # pylint: disable=R0911,R0912
     # responds to .formatted
     if hasattr(data, 'formatted'):
         if fmt == 'table':
-            return data.formatted
+            return str(data.formatted)
 
     # responds to .separator
     if hasattr(data, 'separator'):
diff --git a/SoftLayer/CLI/server/detail.py b/SoftLayer/CLI/server/detail.py
index 943b694b..9fc76c5d 100644
--- a/SoftLayer/CLI/server/detail.py
+++ b/SoftLayer/CLI/server/detail.py
@@ -96,8 +96,10 @@ def cli(env, identifier, passwords, price):
         table.add_row(['remote users', pass_table])
 
     tag_row = []
-    for tag in result['tagReferences']:
-        tag_row.append(tag['tag']['name'])
+    for tag_detail in result['tagReferences']:
+        tag = utils.lookup(tag_detail, 'tag', 'name')
+        if tag is not None:
+            tag_row.append(tag)
 
     if tag_row:
         table.add_row(['tags', formatting.listing(tag_row, separator=',')])
diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py
index 3afe151e..4fc115ce 100644
--- a/SoftLayer/CLI/virt/detail.py
+++ b/SoftLayer/CLI/virt/detail.py
@@ -93,8 +93,10 @@ def cli(self, identifier, passwords=False, price=False):
         table.add_row(['users', pass_table])
 
     tag_row = []
-    for tag in result['tagReferences']:
-        tag_row.append(tag['tag']['name'])
+    for tag_detail in result['tagReferences']:
+        tag = utils.lookup(tag_detail, 'tag', 'name')
+        if tag is not None:
+            tag_row.append(tag)
 
     if tag_row:
         table.add_row(['tags', formatting.listing(tag_row, separator=', ')])
diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py
index 0279225c..674f0460 100644
--- a/SoftLayer/consts.py
+++ b/SoftLayer/consts.py
@@ -5,7 +5,7 @@
 
     :license: MIT, see LICENSE for more details.
 """
-VERSION = 'v4.0.4'
+VERSION = 'v4.0.3'
 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/vs.py b/SoftLayer/managers/vs.py
index 30de90b3..ebd523f4 100644
--- a/SoftLayer/managers/vs.py
+++ b/SoftLayer/managers/vs.py
@@ -191,6 +191,7 @@ def get_instance(self, instance_id, **kwargs):
                 'blockDevices',
                 'blockDeviceTemplateGroup[id, name, globalIdentifier]',
                 'postInstallScriptUri',
+                'userData',
                 '''operatingSystem[passwords[username,password],
                                    softwareLicense.softwareDescription[
                                        manufacturer,name,version,
diff --git a/docs/conf.py b/docs/conf.py
index 8f480496..0472001b 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -55,9 +55,9 @@
 # built documents.
 #
 # The short X.Y version.
-version = '4.0.4'
+version = '4.0.3'
 # The full version, including alpha/beta/rc tags.
-release = '4.0.4'
+release = '4.0.3'
 
 # 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 3f329b77..88132dbe 100644
--- a/setup.py
+++ b/setup.py
@@ -36,7 +36,7 @@
 
 setup(
     name='SoftLayer',
-    version='4.0.4',
+    version='4.0.3',
     description=DESCRIPTION,
     long_description=LONG_DESCRIPTION,
     author='SoftLayer Technologies, Inc.',


Test patch:
diff --git a/SoftLayer/tests/CLI/helper_tests.py b/SoftLayer/tests/CLI/helper_tests.py
index 873d58a8..7862ef00 100644
--- a/SoftLayer/tests/CLI/helper_tests.py
+++ b/SoftLayer/tests/CLI/helper_tests.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 """
     SoftLayer.tests.CLI.helper_tests
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -358,17 +357,6 @@ def test_format_output_python_keyvaluetable(self):
         ret = formatting.format_output(t, 'python')
         self.assertEqual({'nothing': None}, ret)
 
-    def test_format_output_unicode(self):
-        t = formatting.format_output('☃', 'raw')
-        self.assertEqual('☃', t)
-
-        item = formatting.FormattedItem('raw ☃', '☃')
-        t = formatting.format_output(item)
-        self.assertEqual('☃', t)
-
-        t = formatting.format_output(item, 'raw')
-        self.assertEqual('raw ☃', t)
-
 
 class TestTemplateArgs(testing.TestCase):
 
diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py
index 85df2f42..e41e49cd 100644
--- a/SoftLayer/tests/CLI/modules/server_tests.py
+++ b/SoftLayer/tests/CLI/modules/server_tests.py
@@ -57,6 +57,25 @@ def test_server_details(self):
         self.assertEqual(result.exit_code, 0)
         self.assertEqual(json.loads(result.output), expected)
 
+    def test_detail_vs_empty_tag(self):
+        mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
+        mock.return_value = {
+            'id': 100,
+            'processorPhysicalCoreAmount': 2,
+            'memoryCapacity': 2,
+            'tagReferences': [
+                {'tag': {'name': 'example-tag'}},
+                {},
+            ],
+        }
+        result = self.run_command(['server', 'detail', '100'])
+
+        self.assertEqual(result.exit_code, 0)
+        self.assertEqual(
+            json.loads(result.output)['tags'],
+            ['example-tag'],
+        )
+
     def test_list_servers(self):
         result = self.run_command(['server', 'list', '--tag=openstack'])
 
diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py
index 9c1064bd..76dd75e5 100644
--- a/SoftLayer/tests/CLI/modules/vs_tests.py
+++ b/SoftLayer/tests/CLI/modules/vs_tests.py
@@ -66,6 +66,25 @@ def test_detail_vs(self):
                                      'id': 1}],
                           'owner': 'chechu'})
 
+    def test_detail_vs_empty_tag(self):
+        mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject')
+        mock.return_value = {
+            'id': 100,
+            'maxCpu': 2,
+            'maxMemory': 1024,
+            'tagReferences': [
+                {'tag': {'name': 'example-tag'}},
+                {},
+            ],
+        }
+        result = self.run_command(['vs', 'detail', '100'])
+
+        self.assertEqual(result.exit_code, 0)
+        self.assertEqual(
+            json.loads(result.output)['tags'],
+            ['example-tag'],
+        )
+
     def test_create_options(self):
         result = self.run_command(['vs', 'create-options'])