Skip to content

Commit

Permalink
Merge branch 'creds-file-default' into develop
Browse files Browse the repository at this point in the history
* creds-file-default:
  Add issue #847 to the changelog
  Incorporate review feedback.
  The configure command writes out cred vars to shared credentials file
  Add test coverage to credentials file
  • Loading branch information
jamesls committed Sep 23, 2014
2 parents 0f0000a + 889c45a commit 864e9eb
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 40 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
CHANGELOG
=========

Next Release (TBD)
==================

* feature:Shared Credentials File: The ``aws configure`` and
``aws configure set`` command now write out all credential
variables to the shared credentials file ``~/.aws/credentials``
(`issue 847 <https://github.com/aws/aws-cli/issues/847>`__)


1.4.4
=====

Expand Down
4 changes: 4 additions & 0 deletions awscli/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import locale
import urllib.parse as urlparse

raw_input = input

def get_stdout_text_writer():
return sys.stdout

Expand All @@ -41,6 +43,8 @@ def compat_open(filename, mode='r', encoding=None):
import io
import urlparse

raw_input = raw_input

def get_stdout_text_writer():
# In python3, all the sys.stdout/sys.stderr streams are in text
# mode. This means they expect unicode, and will encode the
Expand Down
91 changes: 73 additions & 18 deletions awscli/customizations/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,7 @@
from botocore.exceptions import ProfileNotFound

from awscli.customizations.commands import BasicCommand


try:
raw_input = raw_input
except NameError:
raw_input = input
from awscli.compat import raw_input


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -55,7 +50,7 @@ def _mask_value(current_value):
if current_value is None:
return 'None'
else:
return ('*' * 16) + current_value[-4:]
return ('*' * 16) + current_value[-4:]


class InteractivePrompter(object):
Expand All @@ -80,6 +75,32 @@ class ConfigFileWriter(object):
)

def update_config(self, new_values, config_filename):
"""Update config file with new values.
This method will update a section in a config file with
new key value pairs.
This method provides a few conveniences:
* If the ``config_filename`` does not exist, it will
be created. Any parent directories will also be created
if necessary.
* If the section to update does not exist, it will be created.
* Any existing lines that are specified by ``new_values``
**will not be touched**. This ensures that commented out
values are left unaltered.
:type new_values: dict
:param new_values: The values to update. There is a special
key ``__section__``, that specifies what section in the INI
file to update. If this key is not present, then the
``default`` section will be updated with the new values.
:type config_filename: str
:param config_filename: The config filename where values will be
written.
"""
section_name = new_values.pop('__section__', 'default')
if not os.path.isfile(config_filename):
self._create_file(config_filename)
Expand All @@ -98,11 +119,11 @@ def update_config(self, new_values, config_filename):

def _create_file(self, config_filename):
# Create the file as well as the parent dir if needed.
dirname, basename = os.path.split(config_filename)
dirname = os.path.split(config_filename)[0]
if not os.path.isdir(dirname):
os.makedirs(dirname)
with os.fdopen(os.open(config_filename,
os.O_WRONLY|os.O_CREAT, 0o600), 'w'):
os.O_WRONLY | os.O_CREAT, 0o600), 'w'):
pass

def _write_new_section(self, section_name, new_values, config_filename):
Expand All @@ -124,15 +145,15 @@ def _find_section_start(self, contents, section_name):
if match is not None and self._matches_section(match,
section_name):
return i
else:
raise SectionNotFoundError(section_name)
raise SectionNotFoundError(section_name)

def _update_section_contents(self, contents, section_name, new_values):
# First, find the line where the section_name is defined.
# This will be the value of i.
new_values = new_values.copy()
# ``contents`` is a list of file line contents.
section_start_line_num = self._find_section_start(contents, section_name)
section_start_line_num = self._find_section_start(contents,
section_name)
# If we get here, then we've found the section. We now need
# to figure out if we're updating a value or adding a new value.
# There's 2 cases. Either we're setting a normal scalar value
Expand Down Expand Up @@ -182,7 +203,8 @@ def _update_subattributes(self, index, contents, values, starting_indent):
line = contents[i]
match = self.OPTION_REGEX.search(line)
if match is not None:
current_indent = len(match.group(1)) - len(match.group(1).lstrip())
current_indent = len(
match.group(1)) - len(match.group(1).lstrip())
key_name = match.group(1).strip()
if key_name in values:
option_value = values[key_name]
Expand All @@ -205,7 +227,8 @@ def _insert_new_values(self, line_number, contents, new_values, indent=''):
subindent = indent + ' '
new_contents.append('%s%s =\n' % (indent, key))
for subkey, subval in list(value.items()):
new_contents.append('%s%s = %s\n' % (subindent, subkey, subval))
new_contents.append('%s%s = %s\n' % (subindent, subkey,
subval))
else:
new_contents.append('%s%s = %s\n' % (indent, key, value))
del new_values[key]
Expand Down Expand Up @@ -302,9 +325,9 @@ def _lookup_credentials(self):
# the credentials.method is sufficient to show
# where the credentials are coming from.
access_key = ConfigValue(credentials.access_key,
credentials.method, '')
credentials.method, '')
secret_key = ConfigValue(credentials.secret_key,
credentials.method, '')
credentials.method, '')
access_key.mask_value()
secret_key.mask_value()
return access_key, secret_key
Expand All @@ -322,6 +345,7 @@ def _lookup_config(self, name):
else:
return ConfigValue(NOT_SET, None, None)


class ConfigureSetCommand(BasicCommand):
NAME = 'set'
DESCRIPTION = BasicCommand.FROM_FILE('configure', 'set',
Expand All @@ -338,6 +362,10 @@ class ConfigureSetCommand(BasicCommand):
'action': 'store',
'cli_type_name': 'string', 'positional_arg': True},
]
# Any variables specified in this list will be written to
# the ~/.aws/credentials file instead of ~/.aws/config.
_WRITE_TO_CREDS_FILE = ['aws_access_key_id', 'aws_secret_access_key',
'aws_session_token']

def __init__(self, session, config_writer=None):
super(ConfigureSetCommand, self).__init__(session)
Expand All @@ -362,7 +390,6 @@ def _run_main(self, args, parsed_globals):
section = 'profile %s' % self._session.profile
else:
# First figure out if it's been scoped to a profile.
# This will happen if
parts = varname.split('.')
if parts[0] in ('default', 'profile'):
# Then we know we're scoped to a profile.
Expand All @@ -383,6 +410,12 @@ def _run_main(self, args, parsed_globals):
config_filename = os.path.expanduser(
self._session.get_config_variable('config_file'))
updated_config = {'__section__': section, varname: value}
if varname in self._WRITE_TO_CREDS_FILE:
config_filename = os.path.expanduser(
self._session.get_config_variable('credentials_file'))
section_name = updated_config['__section__']
if section_name.startswith('profile '):
updated_config['__section__'] = section_name[8:]
self._config_writer.update_config(updated_config, config_filename)


Expand Down Expand Up @@ -456,7 +489,6 @@ def _get_dotted_config_value(self, varname):
return value



class ConfigureCommand(BasicCommand):
NAME = 'configure'
DESCRIPTION = BasicCommand.FROM_FILE()
Expand Down Expand Up @@ -522,7 +554,30 @@ def _run_main(self, parsed_args, parsed_globals):
config_filename = os.path.expanduser(
self._session.get_config_variable('config_file'))
if new_values:
self._write_out_creds_file_values(new_values,
parsed_globals.profile)
if parsed_globals.profile is not None:
new_values['__section__'] = (
'profile %s' % parsed_globals.profile)
self._config_writer.update_config(new_values, config_filename)

def _write_out_creds_file_values(self, new_values, profile_name):
# The access_key/secret_key are now *always* written to the shared
# credentials file (~/.aws/credentials), see aws/aws-cli#847.
# post-conditions: ~/.aws/credentials will have the updated credential
# file values and new_values will have the cred vars removed.
credential_file_values = {}
if 'aws_access_key_id' in new_values:
credential_file_values['aws_access_key_id'] = new_values.pop(
'aws_access_key_id')
if 'aws_secret_access_key' in new_values:
credential_file_values['aws_secret_access_key'] = new_values.pop(
'aws_secret_access_key')
if credential_file_values:
if profile_name is not None:
credential_file_values['__section__'] = profile_name
shared_credentials_filename = self._session.get_config_variable(
'credentials_file')
self._config_writer.update_config(
credential_file_values,
shared_credentials_filename)
5 changes: 5 additions & 0 deletions awscli/examples/configure/_description.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ When you are prompted for information, the current value will be displayed in
config file. It does not use any configuration values from environment
variables or the IAM role.

Note: the values you provide for the AWS Access Key ID and the AWS Secret
Access Key will be written to the shared credentials file
(``~/.aws/credentials``).


=======================
Configuration Variables
=======================
Expand Down
5 changes: 5 additions & 0 deletions awscli/examples/configure/set/_description.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ configuration value.
If the config file does not exist, one will automatically be created. If the
configuration value already exists in the config file, it will updated with the
new configuration value.

Setting a value for the ``aws_access_key_id``, ``aws_secret_access_key``, or
the ``aws_session_token`` will result in the value being writen to the
shared credentials file (``~/.aws/credentials``). All other values will
be written to the config file (default location is ``~/.aws/config``).
Loading

0 comments on commit 864e9eb

Please sign in to comment.