Skip to content

Commit

Permalink
Merge pull request #486 from danielgtaylor/explicit-profile
Browse files Browse the repository at this point in the history
Explicit profile overrides environment variables.
  • Loading branch information
danielgtaylor committed Mar 18, 2015
2 parents bab4238 + d0e7add commit 418ee50
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 8 deletions.
16 changes: 14 additions & 2 deletions botocore/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ def create_credential_resolver(session):
config_file = session.get_config_variable('config_file')
metadata_timeout = session.get_config_variable('metadata_service_timeout')
num_attempts = session.get_config_variable('metadata_service_num_attempts')

providers = [
EnvProvider(),
SharedCredentialProvider(
creds_filename=credential_file,
profile_name=profile_name
Expand All @@ -63,6 +63,18 @@ def create_credential_resolver(session):
num_attempts=num_attempts)
)
]

# We use ``session.profile`` for EnvProvider rather than
# ``profile_name`` because it is ``None`` when unset.
if session.profile is None:
# No profile has been explicitly set, so we prepend the environment
# variable provider. That provider, in turn, may set a profile
# or credentials.
providers.insert(0, EnvProvider())
else:
logger.debug('Skipping environment variable credential check'
' because profile name was explicitly set.')

resolver = CredentialResolver(providers=providers)
return resolver

Expand Down Expand Up @@ -580,7 +592,7 @@ def load_credentials(self):
# If we got here, no credentials could be found.
# This feels like it should be an exception, but historically, ``None``
# is returned.
#
#
# +1
# -js
return None
7 changes: 7 additions & 0 deletions tests/integration/test-credentials
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[default]
aws_access_key_id = default
aws_secret_access_key = default-secret

[test]
aws_access_key_id = test
aws_secret_access_key = test-secret
111 changes: 111 additions & 0 deletions tests/integration/test_credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Copyright 2015 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.

import os
import mock

from botocore.session import Session
from tests import BaseEnvVar


class TestCredentialPrecedence(BaseEnvVar):
def setUp(self):
super(TestCredentialPrecedence, self).setUp()

# Set the config file to something that doesn't exist so
# that we don't accidentally load a config.
os.environ['AWS_CONFIG_FILE'] = '~/.aws/config-missing'

def create_session(self, *args, **kwargs):
"""
Create a new session with the given arguments. Additionally,
this method will set the credentials file to the test credentials
used by the following test cases.
"""
kwargs['session_vars'] = {
'credentials_file': (
None, None,
os.path.join(os.path.dirname(__file__), 'test-credentials'))
}

return Session(*args, **kwargs)

def test_access_secret_vs_profile_env(self):
# If all three are given, then the access/secret keys should
# take precedence.
os.environ['AWS_ACCESS_KEY_ID'] = 'env'
os.environ['AWS_SECRET_ACCESS_KEY'] = 'env-secret'
os.environ['BOTO_DEFAULT_PROFILE'] = 'test'

s = self.create_session()
credentials = s.get_credentials()

self.assertEqual(credentials.access_key, 'env')
self.assertEqual(credentials.secret_key, 'env-secret')

@mock.patch('botocore.credentials.Credentials')
def test_access_secret_vs_profile_code(self, credentials_cls):
# If all three are given, then the access/secret keys should
# take precedence.
s = self.create_session()
s.profile = 'test'

client = s.create_client('s3', aws_access_key_id='code',
aws_secret_access_key='code-secret')

credentials_cls.assert_called_with(
access_key='code', secret_key='code-secret', token=mock.ANY)

def test_profile_env_vs_code(self):
# If the profile is set both by the env var and by code,
# then the one set by code should take precedence.
os.environ['BOTO_DEFAULT_PROFILE'] = 'test'
s = self.create_session()
s.profile = 'default'

credentials = s.get_credentials()

self.assertEqual(credentials.access_key, 'default')
self.assertEqual(credentials.secret_key, 'default-secret')

@mock.patch('botocore.credentials.Credentials')
def test_access_secret_env_vs_code(self, credentials_cls):
# If the access/secret keys are set both as env vars and via
# code, then those set by code should take precedence.
os.environ['AWS_ACCESS_KEY_ID'] = 'env'
os.environ['AWS_SECRET_ACCESS_KEY'] = 'secret'
s = self.create_session()

client = s.create_client('s3', aws_access_key_id='code',
aws_secret_access_key='code-secret')

credentials_cls.assert_called_with(
access_key='code', secret_key='code-secret', token=mock.ANY)

def test_access_secret_env_vs_profile_code(self):
# If access/secret keys are set in the environment, but then a
# specific profile is passed via code, then the access/secret
# keys defined in that profile should take precedence over
# the environment variables. Example:
#
# ``aws --profile dev s3 ls``
#
os.environ['AWS_ACCESS_KEY_ID'] = 'env'
os.environ['AWS_SECRET_ACCESS_KEY'] = 'env-secret'
s = self.create_session()
s.profile = 'test'

credentials = s.get_credentials()

self.assertEqual(credentials.access_key, 'test')
self.assertEqual(credentials.secret_key, 'test-secret')
38 changes: 32 additions & 6 deletions tests/unit/test_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from dateutil.tz import tzlocal

from botocore import credentials
from botocore.credentials import EnvProvider
import botocore.exceptions
import botocore.session
from tests import unittest, BaseEnvVar
Expand Down Expand Up @@ -195,7 +196,6 @@ def test_partial_creds_is_an_error(self):
with self.assertRaises(botocore.exceptions.PartialCredentialsError):
provider.load()


class TestSharedCredentialsProvider(BaseEnvVar):
def setUp(self):
super(TestSharedCredentialsProvider, self).setUp()
Expand Down Expand Up @@ -577,20 +577,46 @@ def test_provider_unknown(self):


class TestCreateCredentialResolver(BaseEnvVar):
def test_create_credential_resolver(self):
fake_session = mock.Mock()
config = {
def setUp(self):
super(TestCreateCredentialResolver, self).setUp()

self.session = mock.Mock()
self.config = {
'credentials_file': 'a',
'legacy_config_file': 'b',
'config_file': 'c',
'metadata_service_timeout': 'd',
'metadata_service_num_attempts': 'e',
'profile': 'profilename',
}
fake_session.get_config_variable = lambda x: config[x]
resolver = credentials.create_credential_resolver(fake_session)
self.session.get_config_variable = lambda x: self.config[x]

def test_create_credential_resolver(self):
resolver = credentials.create_credential_resolver(self.session)
self.assertIsInstance(resolver, credentials.CredentialResolver)

def test_explicit_profile_ignores_env_provider(self):
self.config['profile'] = 'dev'
resolver = credentials.create_credential_resolver(self.session)

self.assertTrue(
all(not isinstance(p, EnvProvider) for p in resolver.providers))

def test_no_profile_checks_env_provider(self):
self.config['profile'] = None
self.session.profile = None
resolver = credentials.create_credential_resolver(self.session)

self.assertTrue(
any(isinstance(p, EnvProvider) for p in resolver.providers))

def test_no_profile_env_provider_is_first(self):
self.config['profile'] = None
self.session.profile = None
resolver = credentials.create_credential_resolver(self.session)

self.assertIsInstance(resolver.providers[0], credentials.EnvProvider)


if __name__ == "__main__":
unittest.main()

0 comments on commit 418ee50

Please sign in to comment.