Repository: aws/aws-cli

Issue:
Reading/writing EMR key_pair_file configuration options behaves oddly
Version:

```
$ aws --version
aws-cli/1.11.75 Python/2.7.10 Darwin/15.6.0 botocore/1.5.38
```

[It's suggested that one can set a default key_pair_file argument here](https://github.com/aws/aws-cli/blob/master/awscli/customizations/emr/ssh.py#L25) by running `aws configure set emr.key_pair_file <value>`

By that token, I would expect `aws configure get emr.key_pair_file` to retrieve this item and to exit with a exit code of 0.

```
$ aws configure set emr.key_pair_file /tmp/foo

$ cat config
[default]
emr =
    key_pair_file = /tmp/foo

$ aws configure get emr.key_pair_file

$ echo $?
1
```

As you can see, setting this and trying to retrieve it exits with a non-zero exit code which makes it a pain to check for this config item being set as part of shell scripts prior to do other EMR-based commands (such as create-cluster).

As an aside, trying to get the top level `emr` config item fails too;

```
$ aws configure get emr

expected a character buffer object
```

Additionally this item doesn't show up when `aws configure list` is run either;

```
$ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     REDACTED                    shared-credentials-file
secret_key      REDACTED                    shared-credentials-file
    region                <not set>             None    None

```



Discussion:
Interesting. I get exit code 0 under both python 2 and 3. Can you try upgrading the cli `pip install -U awscli`? List specifically only prints out `profile`/`access_key`/`secret_key`/`region` so it won't appear there, but that doesn't mean it isn't loaded.

When using get with dot syntax (`aws configure get emr.key_pair_file`) the first component separated by they dots is interpreted as the profile. So in this case it finds nothing since there is no `key_pair_profile` in a profile named `emr`.

This will work however: 
```
$ aws configure get default.emr.key_pair_file
tmp/foo
```


Resolving patch:
diff --git a/awscli/customizations/configure/get.py b/awscli/customizations/configure/get.py
--- a/awscli/customizations/configure/get.py
+++ b/awscli/customizations/configure/get.py
@@ -11,17 +11,21 @@
 # ANY KIND, either express or implied. See the License for the specific
 # language governing permissions and limitations under the License.
 import sys
+import logging
 
 from awscli.customizations.commands import BasicCommand
+from awscli.compat import six
 
 from . import PREDEFINED_SECTION_NAMES
 
+LOG = logging.getLogger(__name__)
+
 
 class ConfigureGetCommand(BasicCommand):
     NAME = 'get'
     DESCRIPTION = BasicCommand.FROM_FILE('configure', 'get',
                                          '_description.rst')
-    SYNOPSIS = ('aws configure get varname [--profile profile-name]')
+    SYNOPSIS = 'aws configure get varname [--profile profile-name]'
     EXAMPLES = BasicCommand.FROM_FILE('configure', 'get', '_examples.rst')
     ARG_TABLE = [
         {'name': 'varname',
@@ -30,13 +34,14 @@ class ConfigureGetCommand(BasicCommand):
          'cli_type_name': 'string', 'positional_arg': True},
     ]
 
-    def __init__(self, session, stream=sys.stdout):
+    def __init__(self, session, stream=sys.stdout, error_stream=sys.stderr):
         super(ConfigureGetCommand, self).__init__(session)
         self._stream = stream
+        self._error_stream = error_stream
 
     def _run_main(self, args, parsed_globals):
         varname = args.varname
-        value = None
+
         if '.' not in varname:
             # get_scoped_config() returns the config variables in the config
             # file (not the logical_var names), which is what we want.
@@ -44,17 +49,30 @@ def _run_main(self, args, parsed_globals):
             value = config.get(varname)
         else:
             value = self._get_dotted_config_value(varname)
-        if value is not None:
+
+        LOG.debug(u'Config value retrieved: %s' % value)
+
+        if isinstance(value, six.string_types):
             self._stream.write(value)
             self._stream.write('\n')
             return 0
+        elif isinstance(value, dict):
+            # TODO: add support for this. We would need to print it off in
+            # the same format as the config file.
+            self._error_stream.write(
+                'varname (%s) must reference a value, not a section or '
+                'sub-section.' % varname
+            )
+            return 1
         else:
             return 1
 
     def _get_dotted_config_value(self, varname):
         parts = varname.split('.')
         num_dots = varname.count('.')
-        # Logic to deal with predefined sections like [preview], [plugin] and etc.
+
+        # Logic to deal with predefined sections like [preview], [plugin] and
+        # etc.
         if num_dots == 1 and parts[0] in PREDEFINED_SECTION_NAMES:
             full_config = self._session.full_config
             section, config_name = varname.split('.')
@@ -64,18 +82,23 @@ def _get_dotted_config_value(self, varname):
                 value = full_config['profiles'].get(
                     section, {}).get(config_name)
             return value
+
         if parts[0] == 'profile':
             profile_name = parts[1]
             config_name = parts[2]
             remaining = parts[3:]
-        # Check if varname starts with 'default' profile (e.g. default.emr-dev.emr.instance_profile)
-        # If not, go further to check if varname starts with a known profile name
-        elif parts[0] == 'default' or (parts[0] in self._session.full_config['profiles']):
+        # Check if varname starts with 'default' profile (e.g.
+        # default.emr-dev.emr.instance_profile) If not, go further to check
+        # if varname starts with a known profile name
+        elif parts[0] == 'default' or (
+                parts[0] in self._session.full_config['profiles']):
             profile_name = parts[0]
             config_name = parts[1]
             remaining = parts[2:]
         else:
             profile_name = self._session.get_config_variable('profile')
+            if profile_name is None:
+                profile_name = 'default'
             config_name = parts[0]
             remaining = parts[1:]
 


Test patch:
diff --git a/tests/unit/customizations/configure/test_get.py b/tests/unit/customizations/configure/test_get.py
--- a/tests/unit/customizations/configure/test_get.py
+++ b/tests/unit/customizations/configure/test_get.py
@@ -20,11 +20,16 @@
 
 class TestConfigureGetCommand(unittest.TestCase):
 
+    def create_command(self, session):
+        stdout = six.StringIO()
+        stderr = six.StringIO()
+        command = ConfigureGetCommand(session, stdout, stderr)
+        return stdout, stderr, command
+
     def test_configure_get_command(self):
         session = FakeSession({})
         session.config['region'] = 'us-west-2'
-        stream = six.StringIO()
-        config_get = ConfigureGetCommand(session, stream)
+        stream, error_stream, config_get = self.create_command(session)
         config_get(args=['region'], parsed_globals=None)
         rendered = stream.getvalue()
         self.assertEqual(rendered.strip(), 'us-west-2')
@@ -32,8 +37,7 @@ def test_configure_get_command(self):
     def test_configure_get_command_no_exist(self):
         no_vars_defined = {}
         session = FakeSession(no_vars_defined)
-        stream = six.StringIO()
-        config_get = ConfigureGetCommand(session, stream)
+        stream, error_stream, config_get = self.create_command(session)
         rc = config_get(args=['region'], parsed_globals=None)
         rendered = stream.getvalue()
         # If a config value does not exist, we don't print any output.
@@ -44,8 +48,7 @@ def test_configure_get_command_no_exist(self):
     def test_dotted_get(self):
         session = FakeSession({})
         session.full_config = {'preview': {'emr': 'true'}}
-        stream = six.StringIO()
-        config_get = ConfigureGetCommand(session, stream)
+        stream, error_stream, config_get = self.create_command(session)
         config_get(args=['preview.emr'], parsed_globals=None)
         rendered = stream.getvalue()
         self.assertEqual(rendered.strip(), 'true')
@@ -55,16 +58,16 @@ def test_dotted_get_with_profile(self):
         session.full_config = {'profiles': {'emr-dev': {
             'emr': {'instance_profile': 'my_ip'}}}}
         session.config = {'emr': {'instance_profile': 'my_ip'}}
-        stream = six.StringIO()
-        config_get = ConfigureGetCommand(session, stream)
+        stream, error_stream, config_get = self.create_command(session)
         config_get(args=['emr-dev.emr.instance_profile'], parsed_globals=None)
         rendered = stream.getvalue()
         self.assertEqual(rendered.strip(), 'my_ip')
 
     def test_get_from_profile(self):
         session = FakeSession({})
-        session.full_config = {'profiles': {'testing': {'aws_access_key_id': 'access_key'}}}
-        stream = six.StringIO()
+        session.full_config = {
+            'profiles': {'testing': {'aws_access_key_id': 'access_key'}}}
+        stream, error_stream, config_get = self.create_command(session)
         config_get = ConfigureGetCommand(session, stream)
         config_get(args=['profile.testing.aws_access_key_id'],
                    parsed_globals=None)
@@ -75,8 +78,7 @@ def test_get_nested_attribute(self):
         session = FakeSession({})
         session.full_config = {
             'profiles': {'testing': {'s3': {'signature_version': 's3v4'}}}}
-        stream = six.StringIO()
-        config_get = ConfigureGetCommand(session, stream)
+        stream, error_stream, config_get = self.create_command(session)
         config_get(args=['profile.testing.s3.signature_version'],
                    parsed_globals=None)
         rendered = stream.getvalue()
@@ -86,8 +88,7 @@ def test_get_nested_attribute_from_default(self):
         session = FakeSession({})
         session.full_config = {
             'profiles': {'default': {'s3': {'signature_version': 's3v4'}}}}
-        stream = six.StringIO()
-        config_get = ConfigureGetCommand(session, stream)
+        stream, error_stream, config_get = self.create_command(session)
         config_get(args=['default.s3.signature_version'],
                    parsed_globals=None)
         rendered = stream.getvalue()
@@ -96,9 +97,46 @@ def test_get_nested_attribute_from_default(self):
     def test_get_nested_attribute_from_default_does_not_exist(self):
         session = FakeSession({})
         session.full_config = {'profiles': {}}
-        stream = six.StringIO()
-        config_get = ConfigureGetCommand(session, stream)
+        stream, error_stream, config_get = self.create_command(session)
         config_get(args=['default.s3.signature_version'],
                    parsed_globals=None)
         rendered = stream.getvalue()
-        self.assertEqual(rendered.strip(), '')
\ No newline at end of file
+        self.assertEqual(rendered.strip(), '')
+
+    def test_get_nested_attribute_from_implicit_default(self):
+        session = FakeSession({})
+        session.full_config = {
+            'profiles': {'default': {'s3': {'signature_version': 's3v4'}}}}
+        stream, error_stream, config_get = self.create_command(session)
+        config_get(args=['s3.signature_version'],
+                   parsed_globals=None)
+        rendered = stream.getvalue()
+        self.assertEqual(rendered.strip(), 's3v4')
+
+    def test_get_section_returns_error(self):
+        session = FakeSession({})
+        session.full_config = {
+            'profiles': {'default': {'s3': {'signature_version': 's3v4'}}}}
+        session.config = {'s3': {'signature_version': 's3v4'}}
+        stream, error_stream, config_get = self.create_command(session)
+        rc = config_get(args=['s3'], parsed_globals=None)
+        self.assertEqual(rc, 1)
+
+        error_message = error_stream.getvalue()
+        expected_message = (
+            'varname (s3) must reference a value, not a section or '
+            'sub-section.')
+        self.assertEqual(error_message, expected_message)
+        self.assertEqual(stream.getvalue(), '')
+
+    def test_get_non_string_returns_error(self):
+        # This should never happen, but we handle this case so we should
+        # test it.
+        session = FakeSession({})
+        session.full_config = {
+            'profiles': {'default': {'foo': object()}}}
+        stream, error_stream, config_get = self.create_command(session)
+        rc = config_get(args=['foo'], parsed_globals=None)
+        self.assertEqual(rc, 1)
+        self.assertEqual(stream.getvalue(), '')
+        self.assertEqual(error_stream.getvalue(), '')