Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions decouple_aws/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging

from decouple import AutoConfig, Config
from botocore.exceptions import BotoCoreError
from botocore.exceptions import ClientError, BotoCoreError

from .repository import RepositoryAwsSecretManager

Expand All @@ -11,7 +11,7 @@
logger = logging.getLogger(__name__)


def get_config(source, region):
def get_config(source, region, fail=False):
""" Get config object but fallback to AutoConfig if AWS connection fails"""
try:
logger.debug(
Expand All @@ -20,7 +20,10 @@ def get_config(source, region):
logger.debug('Successfully queried for %s in region %s',
source, region)
return Config(repo)
except BotoCoreError as e:

except (ClientError, BotoCoreError) as e:
if fail:
raise e
logger.error(
'Failed retrieving secrets from AWS Secrets Manager: %s', e)
return AutoConfig()
121 changes: 116 additions & 5 deletions tests/test_basic_usage.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import json
import os
import mock
from decouple_aws import get_config
import pytest
from botocore.exceptions import NoCredentialsError
from decouple import Config, AutoConfig, UndefinedValueError
from decouple_aws import get_config, RepositoryAwsSecretManager


def test_should_return_secrets_from_aws_secrets_manager():
""" Test if get_config returns Config (from AWS python decouple) """

with mock.patch('decouple_aws.repository.boto3') as boto3:

# Given secrets registered on AWS
Expand All @@ -20,19 +25,125 @@ def test_should_return_secrets_from_aws_secrets_manager():
MY_EMAIL_USER = config('EMAIL_USER')

# Then the secrets should be retrieved
is_aws_decouple = Config

assert isinstance(config, is_aws_decouple)
assert MY_EMAIL_USER == "user1"
assert config('EMAIL_PASS') == '123456'


def test_should_use_environment_as_fall_back():
def test_should_not_fail_when_the_aws_resource_is_not_found():
""" Test if get_config returns AutoConfig (from python decouple) as fall back """

# Given an invalid resource name or
# no aws credentials

# When
config = get_config('invalid/resource', 'us-west-2')
secret = config('EMAIL_USER', 'default-value')

# Then
is_env_decouple = AutoConfig

assert isinstance(config, is_env_decouple)
assert secret == 'default-value'


def test_should_return_secrets_from_aws_secrets_manager_repository():
"""
Test if RepositoryAwsSecretManager can be instantiate direclty
"""

with mock.patch('decouple_aws.repository.boto3') as boto3:

# Given secrets registered on AWS
boto3.client().get_secret_value.return_value = {
'SecretString': json.dumps({
'EMAIL_USER': 'user1',
'EMAIL_PASS': '123456',
})
}

# When
repo = RepositoryAwsSecretManager('myproject/secrets1', 'ap-southeast-2')
config = Config(repo)

# Then the secrets should be retrieved
assert config('EMAIL_USER') == "user1"
assert config('EMAIL_PASS') == '123456'


def test_should_raise_NoCredentialsError_with_no_credentials():
""" Test if users can handle errors direclty when necessary """
# Given no Credentials

with pytest.raises(NoCredentialsError):
repo = RepositoryAwsSecretManager('myproject/secrets1', 'ap-southeast-2')
config = Config(repo)

assert repo is not None
assert config is None


def test_should_raise_NoCredentialsError_using_the_default_api():
"""
Test if users can handle errors direclty when necessary
using a default api rather than be forced to instantiate RepositoryAwsSecretManager
"""
# Given no Credentials

with pytest.raises(NoCredentialsError):
config = get_config('myproject/secrets', 'ap-southeast-2', fail=True)

assert config is None


def test_should_use_environment_as_first_option():
""" Test if ENVIRONMENT variable is the first option """
with mock.patch('decouple_aws.repository.boto3') as boto3:

# Given an empty secrets manager and some OS environment
boto3.client().get_secret_value.return_value = {
'SecretString': json.dumps({})
'SecretString': json.dumps({
'MY_SECRET': 'secret-from-aws',
})
}
os.environ["EMAIL_USER"] = "user-test"
os.environ["MY_SECRET"] = "secret-from-environment"

# When
config = get_config('myproject/secrets', 'ap-southeast-2')
assert config('MY_SECRET') == "secret-from-environment"


def test_should_use_environment_with_no_aws_credentials():
""" Test if ENVIRONMENT variable is the first option """

# Given an empty secrets manager and some OS environment
os.environ["MY_SECRET"] = "secret-from-environment"

# When
config = get_config('myproject/secrets', 'ap-southeast-2')
assert config('MY_SECRET') == "secret-from-environment"


def test_should_raise_UndefinedValueError_for_invalid_key():

# Given no secrets and no environment variables

# When get_config used with no fail. Then an Exception is Raised
with pytest.raises(UndefinedValueError):
config = get_config('myproject/secrets1', 'ap-southeast-2')
assert config('EMAIL_USER') == "user-test"
invalid_secret = config('MY_SECRET_NO_SET')

assert invalid_secret is None


def test_should_use_default_value():

# Given no secrets and no environment variables

# When get_config used with no fail. Then an Exception is Raised
config = get_config('myproject/secrets1', 'ap-southeast-2')
secret = config('MY_SECRET_NO_SET', 'my-default-value')

assert secret == 'my-default-value'
3 changes: 3 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ deps =
boto3==1.9.16
python-decouple==3.1
commands=py.test

[flake8]
max-line-length = 120