diff --git a/README.md b/README.md index 687f90e..550bab0 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,25 @@ DATABASES = { } ``` +If `export_env_variables` is set to `True`, each secret will also be exported as an environment variable, with the uppercase key as the variable name, e.g.: + +```python +from confidential import SecretsManager +import os + +secrets = SecretManager( + secrets_file=".secrets/production.json", + secrets_file_default=".secrets/defaults.json", # Overridable defaults you can use in common environments + region_name="us-east-1", + export_env_variables=True, # Optionally, export secrets as environment variables. Default is False. +) + +# If the key of a secret is `api_key`, then the following is true: +assert secrets["api_key"] == os.environ.get("API_KEY") +``` + +Trying to access an inexisting key returns `None`. On previous versions, it would throw an exception. + # Testing First, install all dependencies: diff --git a/confidential/secrets_manager.py b/confidential/secrets_manager.py index ab7c1c1..a52e7f7 100644 --- a/confidential/secrets_manager.py +++ b/confidential/secrets_manager.py @@ -4,18 +4,16 @@ import logging import os import pprint -from botocore.exceptions import ClientError from confidential.secrets_manager_decrypter import SecretsManagerDecrypter from confidential.parameter_store_decrypter import ParameterStoreDecrypter -from confidential.exceptions import PermissionError from confidential.utils import merge log = logging.getLogger(__name__) class SecretsManager: - def __init__(self, secrets_file=None, secrets_file_default=None, region_name=None, profile_name=None): + def __init__(self, secrets_file=None, secrets_file_default=None, region_name=None, profile_name=None, export_env_variables=False): session = boto3.session.Session(profile_name=profile_name) self.session = session @@ -29,13 +27,16 @@ def __init__(self, secrets_file=None, secrets_file_default=None, region_name=Non self.secrets = merge(secrets_defaults, secrets) + if export_env_variables: + self.export_env_variables(self.secrets) + def __getitem__(self, key): """ Allows us to do ["foo"] instead of .secrets.get("foo") """ value = self.secrets.get(key) if value is None: - raise Exception(f"Value for '{key}' was not found in the secrets file", self.secrets) + log.warning(f"Value for '{key}' was not found in the secrets file. Returning 'None'.") return value @staticmethod @@ -94,6 +95,11 @@ def parse_secrets_file(self, path_to_file) -> dict: return config + def export_env_variables(self, secrets): + for key in secrets: + uppercase_key = key.upper() + os.environ[uppercase_key] = str(secrets[key]) + @click.command() @click.argument("secrets_file", type=click.Path(exists=True)) diff --git a/pyproject.toml b/pyproject.toml index 9d204bd..26fadd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "confidential" -version = "2.4.0" +version = "2.5.0" description = "Manage secrets in your projects using AWS Secrets Manager" authors = [ "Daniel van Flymen ", diff --git a/tests/test_secrets_manager.py b/tests/test_secrets_manager.py index 94a7b9b..ff4de0f 100644 --- a/tests/test_secrets_manager.py +++ b/tests/test_secrets_manager.py @@ -1,4 +1,5 @@ import pytest +import os from confidential import SecretsManager from confidential.exceptions import PermissionError @@ -25,6 +26,18 @@ def test_happy_path(secrets_file): assert secrets["nested_object_parameter"] == {"ping": "pong"} assert secrets["nested_parameter_key"] == {"temp_c": 3, "snow_fall_cm": 20, "some_parameter": "cold" } +def test_secrets_exported_to_env_vars(secrets_file): + with secrets_file(foo="bar", ping="pong") as f: + secrets = SecretsManager(f, region_name="us-west-1", export_env_variables=True) + + assert os.environ.get("FOO") == "bar" + assert secrets["foo"] == os.environ.get("FOO") + + assert os.environ.get("PING") == "pong" + assert secrets["ping"] == os.environ.get("PING") + + assert os.environ.get("blah") == None + assert secrets["blah"] == os.environ.get("BLAH") @pytest.mark.parametrize("secret_value_response", [{"FakeKey": "FakeValue"}, {"SecretString": None}]) def test_missing_secret_string_raises_permission_error(secret_value_response, mocker, secrets_file):