Repository: aws/aws-cli

Issue:
AWS SNS 'StringParameter' object has no attribute 'enum' 
Hi there, 

I tried both Shorthand Syntax and JSON syntax following 

http://docs.aws.amazon.com/cli/latest/reference/sns/create-platform-application.html

with no luck.

The error is 'StringParameter' object has no attribute 'enum' 

my command is 

aws sns create-platform-application --name testAndroid --platform GCM --attributes key="PlatformCredential",value="the key from google console"

or 

aws sns create-platform-application --name testAndroid --platform GCM --attributes  thejsonfile

my json file is 
[
 {
  "key":"PlatformCredential",
  "value":"the key from google console "
 }
]

Please kindly help ~

thanks
Keith



Discussion:
I also tried 

aws sns create-platform-application --name testAndroid --platform GCM --attributes PlatformCredential="the key from google console"

P.S. the AWS console on this part also give error saying the key is wrong but surely not ...  

from AWS conole:
Warning
The credentials you entered are invalid. Please enter valid credentials and try again. 

There is something odd going on and at least part of the problem appears to be in AWS CLI.  Investigating.


Resolving patch:
diff --git a/awscli/argprocess.py b/awscli/argprocess.py
--- a/awscli/argprocess.py
+++ b/awscli/argprocess.py
@@ -12,10 +12,11 @@
 # language governing permissions and limitations under the License.
 """Module for processing CLI args."""
 import os
-import json
 import logging
 import six
 
+from botocore.compat import OrderedDict, json
+
 from awscli import utils
 from awscli import SCALAR_TYPES, COMPLEX_TYPES
 
@@ -243,7 +244,7 @@ def _key_value_parse(self, param, value):
         # that is, csv key value pairs, where the key and values
         # are separated by '='.  All of this should be whitespace
         # insensitive.
-        parsed = {}
+        parsed = OrderedDict()
         parts = self._split_on_commas(value)
         valid_names = self._create_name_to_params(param)
         for part in parts:
@@ -253,18 +254,19 @@ def _key_value_parse(self, param, value):
                 raise ParamSyntaxError(part)
             key = key.strip()
             value = value.strip()
-            if key not in valid_names:
+            if valid_names and key not in valid_names:
                 raise ParamUnknownKeyError(param, key, valid_names)
-            sub_param = valid_names[key]
-            if sub_param is not None:
-                value = unpack_scalar_cli_arg(sub_param, value)
+            if valid_names:
+                sub_param = valid_names[key]
+                if sub_param is not None:
+                    value = unpack_scalar_cli_arg(sub_param, value)
             parsed[key] = value
         return parsed
 
     def _create_name_to_params(self, param):
         if param.type == 'structure':
             return dict([(p.name, p) for p in param.members])
-        elif param.type == 'map':
+        elif param.type == 'map' and hasattr(param.keys, 'enum'):
             return dict([(v, None) for v in param.keys.enum])
 
     def _docs_list_scalar_list_parse(self, param):
@@ -351,7 +353,7 @@ def unpack_cli_arg(parameter, value):
 def unpack_complex_cli_arg(parameter, value):
     if parameter.type == 'structure' or parameter.type == 'map':
         if value.lstrip()[0] == '{':
-            d = json.loads(value)
+            d = json.loads(value, object_pairs_hook=OrderedDict)
         else:
             msg = 'The value for parameter "%s" must be JSON or path to file.' % (
                 parameter.cli_name)
@@ -360,11 +362,11 @@ def unpack_complex_cli_arg(parameter, value):
     elif parameter.type == 'list':
         if isinstance(value, six.string_types):
             if value.lstrip()[0] == '[':
-                return json.loads(value)
+                return json.loads(value, object_pairs_hook=OrderedDict)
         elif isinstance(value, list) and len(value) == 1:
             single_value = value[0].strip()
             if single_value and single_value[0] == '[':
-                return json.loads(value[0])
+                return json.loads(value[0], object_pairs_hook=OrderedDict)
         return [unpack_cli_arg(parameter.members, v) for v in value]
 
 


Test patch:
diff --git a/tests/unit/sns/__init__.py b/tests/unit/sns/__init__.py
new file mode 100644
--- /dev/null
+++ b/tests/unit/sns/__init__.py
@@ -0,0 +1,12 @@
+# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You
+# may not use this file except in compliance with the License. A copy of
+# the License is located at
+#
+#     http://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is
+# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+# ANY KIND, either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
diff --git a/tests/unit/sns/test_create_platform_application.py b/tests/unit/sns/test_create_platform_application.py
new file mode 100644
--- /dev/null
+++ b/tests/unit/sns/test_create_platform_application.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You
+# may not use this file except in compliance with the License. A copy of
+# the License is located at
+#
+#     http://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is
+# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+# ANY KIND, either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+from tests.unit import BaseAWSCommandParamsTest
+
+
+class TestCreatePlatformApplication(BaseAWSCommandParamsTest):
+
+    prefix = 'sns create-platform-application'
+
+    def test_gcm_shorthand(self):
+        cmdline = self.prefix
+        cmdline += ' --name gcmpushapp'
+        cmdline += ' --platform GCM'
+        cmdline += ' --attributes '
+        cmdline += 'PlatformCredential=foo,'
+        cmdline += 'PlatformPrincipal=bar'
+        result = {'Name': 'gcmpushapp',
+                  'Platform': 'GCM',
+                  'Attributes.entry.1.key': 'PlatformCredential',
+                  'Attributes.entry.1.value': 'foo',
+                  'Attributes.entry.2.key': 'PlatformPrincipal',
+                  'Attributes.entry.2.value': 'bar'}
+        self.assert_params_for_cmd(cmdline, result)
+
+    def test_gcm_json(self):
+        cmdline = self.prefix
+        cmdline += ' --name gcmpushapp'
+        cmdline += ' --platform GCM'
+        cmdline += ' --attributes '
+        cmdline += ('{"PlatformCredential":"AIzaSyClE2lcV2zEKTLYYo645zfk2jhQPFeyxDo",'
+                    '"PlatformPrincipal":"There+is+no+principal+for+GCM"}')
+        result = {'Name': 'gcmpushapp',
+                  'Platform': 'GCM',
+                  'Attributes.entry.1.key': 'PlatformCredential',
+                  'Attributes.entry.1.value': 'AIzaSyClE2lcV2zEKTLYYo645zfk2jhQPFeyxDo',
+                  'Attributes.entry.2.key': 'PlatformPrincipal',
+                  'Attributes.entry.2.value': 'There+is+no+principal+for+GCM'}
+        self.assert_params_for_cmd(cmdline, result)
+
+
+if __name__ == "__main__":
+    unittest.main()