Repository: aws/aws-cli

Issue:
`create-virtual-mfa-device` output file QRCode.png can't be opened/viewed
I'm using the following command to create a MFA device,

```
aws iam create-virtual-mfa-device --virtual-mfa-device-name hydrajump --bootstrap-method QRCodePNG --outfile "/Users/hydrajump/Downloads/QRCode.png" 
```

The output file `QRCode.png` can't be opened with Preview.app and I get the following error,

**"It may be damaged or use a file format that Preview doesn’t recognise."**

Do I need a _special_ QR code viewer? 

remove unnecessary decoding process for mfa device creation
This fixes an issue where `aws iam create-virtual-mfa-device` tries to
base64-decode already base64-decoded data.
2nd decoding results in decoding error or malformed data.

This happened whichever `--bootstrap-method` was used.
fixes #970

API
- http://docs.aws.amazon.com/cli/latest/reference/iam/create-virtual-mfa-device.html
- http://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateVirtualMFADevice.html



Discussion:
Looks like a regression on our end.  Working on a fix.  Thanks for reporting.

PR #1005 should fix this.

> "It may be damaged or use a file format that Preview doesn’t recognise."

Decoding already-decoded data was the culprit.



Resolving patch:
diff --git a/awscli/clidriver.py b/awscli/clidriver.py
--- a/awscli/clidriver.py
+++ b/awscli/clidriver.py
@@ -43,17 +43,6 @@
 LOG_FORMAT = (
     '%(asctime)s - %(threadName)s - %(name)s - %(levelname)s - %(message)s')
 
-# NOTE: this is temporary.
-# Botocore now parsers timestamps to datetime.datetime objects.
-# The AWS CLI has historically not parsed timestamp objects and treated
-# them as strings.
-# We will eventually want to allow for users to specify how to parse
-# the timestamp formats, but for now, we set the default timestamp parser
-# to be a noop.
-# The eventual plan is to add a client option for providing a timestamp parser,
-# and once the CLI has switched over to client objects we can remove this
-# and set the timestamp parsing on a per client basis.
-parsers.DEFAULT_TIMESTAMP_PARSER = lambda x: x
 
 
 def main():
diff --git a/awscli/customizations/scalarparse.py b/awscli/customizations/scalarparse.py
new file mode 100644
--- /dev/null
+++ b/awscli/customizations/scalarparse.py
@@ -0,0 +1,48 @@
+# Copyright 2014 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.
+"""Change the scalar response parsing behavior for the AWS CLI.
+
+The underlying library used by botocore has some response parsing
+behavior that we'd like to modify in the AWS CLI.  There are two:
+
+    * Parsing binary content.
+    * Parsing timestamps (dates)
+
+For the first option we can't print binary content to the terminal,
+so this customization leaves the binary content base64 encoded.  If the
+user wants the binary content, they can then base64 decode the appropriate
+fields as needed.
+
+There's nothing currently done for timestamps, but this will change
+in the future.
+
+"""
+def register_scalar_parser(event_handlers):
+    event_handlers.register('building-command-table.main',
+                            add_scalar_parsers)
+
+
+def identity(x):
+    return x
+
+
+def add_scalar_parsers(session, **kwargs):
+    factory = session.get_component('response_parser_factory')
+    # For backwards compatibility reasons, we replace botocore's timestamp
+    # parser (which parsers to a datetime.datetime object) with the identity
+    # function which prints the date exactly the same as it comes across the
+    # wire.  We will eventually add a config option that allows for a user to
+    # have normalized datetime representation, but we can't change the default.
+    factory.set_parser_defaults(
+        blob_parser=identity,
+        timestamp_parser=identity)
diff --git a/awscli/handlers.py b/awscli/handlers.py
--- a/awscli/handlers.py
+++ b/awscli/handlers.py
@@ -58,6 +58,7 @@
 from awscli.customizations.configservice.getstatus import register_get_status
 from awscli.customizations.configservice.rename_cmd import \
     register_rename_config
+from awscli.customizations.scalarparse import register_scalar_parser
 
 
 def awscli_initialize(event_handlers):
@@ -118,3 +119,4 @@ def awscli_initialize(event_handlers):
     register_subscribe(event_handlers)
     register_get_status(event_handlers)
     register_rename_config(event_handlers)
+    register_scalar_parser(event_handlers)


Test patch:
diff --git a/tests/integration/test_smoke.py b/tests/integration/test_smoke.py
--- a/tests/integration/test_smoke.py
+++ b/tests/integration/test_smoke.py
@@ -40,6 +40,7 @@
     'emr list-clusters',
     'iam list-users',
     'kinesis list-streams',
+    'kms generate-random --number-of-bytes 128',
     'logs describe-log-groups',
     'opsworks describe-stacks',
     'rds describe-db-instances',
diff --git a/tests/unit/customizations/test_scalarparse.py b/tests/unit/customizations/test_scalarparse.py
new file mode 100644
--- /dev/null
+++ b/tests/unit/customizations/test_scalarparse.py
@@ -0,0 +1,39 @@
+# Copyright 2014 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 awscli.testutils import unittest
+import six
+import mock
+
+from awscli.customizations import scalarparse
+
+
+class TestScalarParse(unittest.TestCase):
+
+    def test_register_scalar_parser(self):
+        event_handers = mock.Mock()
+        scalarparse.register_scalar_parser(event_handers)
+        event_handers.register.assert_called_with(
+            'building-command-table.main', scalarparse.add_scalar_parsers)
+
+    def test_identity(self):
+        self.assertEqual(scalarparse.identity('foo'), 'foo')
+        self.assertEqual(scalarparse.identity(10), 10)
+
+    def test_scalar_parsers_set(self):
+        session = mock.Mock()
+        scalarparse.add_scalar_parsers(session)
+        session.get_component.assert_called_with('response_parser_factory')
+        factory = session.get_component.return_value
+        factory.set_parser_defaults.assert_called_with(
+            blob_parser=scalarparse.identity,
+            timestamp_parser=scalarparse.identity)