Repository: StackStorm/st2

Issue:
CLI: `--attr` couldn't retrieve result attributes
Reference: https://docs.stackstorm.com/cli.html
Steps with Screenshots:
- Execute local action
  ![image](https://cloud.githubusercontent.com/assets/10110869/17518893/b5646370-5e17-11e6-837f-e7648a9bdf6b.png)
- Use `-k` to retrieve result attribute, which works without any issues:
  ![image](https://cloud.githubusercontent.com/assets/10110869/17518932/e0804542-5e17-11e6-8407-731bec68bec4.png)
- As per documentation, we can use `-attr` to retrieve execution attributes:

![image](https://cloud.githubusercontent.com/assets/10110869/17519113/8b6845f4-5e18-11e6-9ade-a28b73f26b5d.png)
- In my case it prints whole result set with `--attr` but fails when I try to retrieve result attributes individually. It prints `None`:

![image](https://cloud.githubusercontent.com/assets/10110869/17519061/5b54fba0-5e18-11e6-89ec-fe166cc547c2.png)



Discussion:
Thanks for the report.

Right now we don't support dotted notation for `execution get` because it uses a special formatter, but we do support for for `execution list`.

If you want to retrieve a value of a specific attribute in the result object you can use the `-k` option.

For example:

``` python
python ./st2client/st2client/shell.py execution get 57bb3f0f0640fd0ab60d896a -k stderr 
```

Will update documentation to reflect this.


Resolving patch:
diff --git a/st2client/st2client/commands/action.py b/st2client/st2client/commands/action.py
--- a/st2client/st2client/commands/action.py
+++ b/st2client/st2client/commands/action.py
@@ -892,15 +892,28 @@ class ActionExecutionReadCommand(resource.ResourceCommand):
     Base class for read / view commands (list and get).
     """
 
-    def _get_exclude_attributes(self, args):
+    @classmethod
+    def _get_exclude_attributes(cls, args):
         """
         Retrieve a list of exclude attributes for particular command line arguments.
         """
         exclude_attributes = []
 
-        if 'result' not in args.attr:
+        result_included = False
+        trigger_instance_included = False
+
+        for attr in args.attr:
+            # Note: We perform startswith check so we correctly detected child attribute properties
+            # (e.g. result, result.stdout, result.stderr, etc.)
+            if attr.startswith('result'):
+                result_included = True
+
+            if attr.startswith('trigger_instance'):
+                trigger_instance_included = True
+
+        if not result_included:
             exclude_attributes.append('result')
-        if 'trigger_instance' not in args.attr:
+        if not trigger_instance_included:
             exclude_attributes.append('trigger_instance')
 
         return exclude_attributes


Test patch:
diff --git a/st2client/tests/unit/test_commands.py b/st2client/tests/unit/test_commands.py
--- a/st2client/tests/unit/test_commands.py
+++ b/st2client/tests/unit/test_commands.py
@@ -20,12 +20,19 @@
 import argparse
 import tempfile
 import unittest2
+from collections import namedtuple
 
 from tests import base
 
 from st2client import models
 from st2client.utils import httpclient
 from st2client.commands import resource
+from st2client.commands.action import ActionExecutionReadCommand
+
+__all__ = [
+    'TestResourceCommand',
+    'ActionExecutionReadCommandTestCase'
+]
 
 
 LOG = logging.getLogger(__name__)
@@ -232,3 +239,33 @@ def test_command_delete_404(self):
     def test_command_delete_failed(self):
         args = self.parser.parse_args(['fakeresource', 'delete', 'cba'])
         self.assertRaises(Exception, self.branch.commands['delete'].run, args)
+
+
+class ActionExecutionReadCommandTestCase(unittest2.TestCase):
+
+    def test_get_exclude_attributes(self):
+        cls = namedtuple('Args', 'attr')
+
+        args = cls(attr=[])
+        result = ActionExecutionReadCommand._get_exclude_attributes(args=args)
+        self.assertEqual(result, ['result', 'trigger_instance'])
+
+        args = cls(attr=['result'])
+        result = ActionExecutionReadCommand._get_exclude_attributes(args=args)
+        self.assertEqual(result, ['trigger_instance'])
+
+        args = cls(attr=['result', 'trigger_instance'])
+        result = ActionExecutionReadCommand._get_exclude_attributes(args=args)
+        self.assertEqual(result, [])
+
+        args = cls(attr=['result.stdout'])
+        result = ActionExecutionReadCommand._get_exclude_attributes(args=args)
+        self.assertEqual(result, ['trigger_instance'])
+
+        args = cls(attr=['result.stdout', 'result.stderr'])
+        result = ActionExecutionReadCommand._get_exclude_attributes(args=args)
+        self.assertEqual(result, ['trigger_instance'])
+
+        args = cls(attr=['result.stdout', 'trigger_instance.id'])
+        result = ActionExecutionReadCommand._get_exclude_attributes(args=args)
+        self.assertEqual(result, [])