diff --git a/aws_lambda_powertools/utilities/feature_toggles/__init__.py b/aws_lambda_powertools/utilities/feature_toggles/__init__.py index be5b0245397..378f7e23f48 100644 --- a/aws_lambda_powertools/utilities/feature_toggles/__init__.py +++ b/aws_lambda_powertools/utilities/feature_toggles/__init__.py @@ -1,12 +1,16 @@ """Advanced feature toggles utility """ +from .appconfig_fetcher import AppConfigFetcher from .configuration_store import ConfigurationStore from .exceptions import ConfigurationException from .schema import ACTION, SchemaValidator +from .schema_fetcher import SchemaFetcher __all__ = [ "ConfigurationException", "ConfigurationStore", "ACTION", "SchemaValidator", + "AppConfigFetcher", + "SchemaFetcher", ] diff --git a/aws_lambda_powertools/utilities/feature_toggles/appconfig_fetcher.py b/aws_lambda_powertools/utilities/feature_toggles/appconfig_fetcher.py new file mode 100644 index 00000000000..177d4ed0ae9 --- /dev/null +++ b/aws_lambda_powertools/utilities/feature_toggles/appconfig_fetcher.py @@ -0,0 +1,57 @@ +import logging +from typing import Any, Dict, Optional + +from botocore.config import Config + +from aws_lambda_powertools.utilities.parameters import AppConfigProvider, GetParameterError, TransformParameterError + +from .exceptions import ConfigurationException +from .schema_fetcher import SchemaFetcher + +logger = logging.getLogger(__name__) + + +TRANSFORM_TYPE = "json" + + +class AppConfigFetcher(SchemaFetcher): + def __init__( + self, + environment: str, + service: str, + configuration_name: str, + cache_seconds: int, + config: Optional[Config] = None, + ): + """This class fetches JSON schemas from AWS AppConfig + + Args: + environment (str): what appconfig environment to use 'dev/test' etc. + service (str): what service name to use from the supplied environment + configuration_name (str): what configuration to take from the environment & service combination + cache_seconds (int): cache expiration time, how often to call AppConfig to fetch latest configuration + config (Optional[Config]): boto3 client configuration + """ + super().__init__(configuration_name, cache_seconds) + self._logger = logger + self._conf_store = AppConfigProvider(environment=environment, application=service, config=config) + + def get_json_configuration(self) -> Dict[str, Any]: + """Get configuration string from AWs AppConfig and return the parsed JSON dictionary + + Raises: + ConfigurationException: Any validation error or appconfig error that can occur + + Returns: + Dict[str, Any]: parsed JSON dictionary + """ + try: + return self._conf_store.get( + name=self.configuration_name, + transform=TRANSFORM_TYPE, + max_age=self._cache_seconds, + ) # parse result conf as JSON, keep in cache for self.max_age seconds + except (GetParameterError, TransformParameterError) as exc: + error_str = f"unable to get AWS AppConfig configuration file, exception={str(exc)}" + self._logger.error(error_str) + raise ConfigurationException(error_str) diff --git a/aws_lambda_powertools/utilities/feature_toggles/configuration_store.py b/aws_lambda_powertools/utilities/feature_toggles/configuration_store.py index b285f435355..e5404477375 100644 --- a/aws_lambda_powertools/utilities/feature_toggles/configuration_store.py +++ b/aws_lambda_powertools/utilities/feature_toggles/configuration_store.py @@ -1,36 +1,23 @@ -# pylint: disable=no-name-in-module,line-too-long import logging from typing import Any, Dict, List, Optional -from botocore.config import Config - -from aws_lambda_powertools.utilities.parameters import AppConfigProvider, GetParameterError, TransformParameterError - from . import schema from .exceptions import ConfigurationException - -TRANSFORM_TYPE = "json" +from .schema_fetcher import SchemaFetcher logger = logging.getLogger(__name__) class ConfigurationStore: - def __init__( - self, environment: str, service: str, conf_name: str, cache_seconds: int, config: Optional[Config] = None - ): + def __init__(self, schema_fetcher: SchemaFetcher): """constructor Args: - environment (str): what appconfig environment to use 'dev/test' etc. - service (str): what service name to use from the supplied environment - conf_name (str): what configuration to take from the environment & service combination - cache_seconds (int): cache expiration time, how often to call AppConfig to fetch latest configuration + schema_fetcher (SchemaFetcher): A schema JSON fetcher, can be AWS AppConfig, Hashicorp Consul etc. """ - self._cache_seconds = cache_seconds self._logger = logger - self._conf_name = conf_name + self._schema_fetcher = schema_fetcher self._schema_validator = schema.SchemaValidator(self._logger) - self._conf_store = AppConfigProvider(environment=environment, application=service, config=config) def _match_by_action(self, action: str, condition_value: Any, context_value: Any) -> bool: if not context_value: @@ -99,17 +86,11 @@ def get_configuration(self) -> Dict[str, Any]: Returns: Dict[str, Any]: parsed JSON dictionary """ - try: - schema = self._conf_store.get( - name=self._conf_name, - transform=TRANSFORM_TYPE, - max_age=self._cache_seconds, - ) # parse result conf as JSON, keep in cache for self.max_age seconds - except (GetParameterError, TransformParameterError) as exc: - error_str = f"unable to get AWS AppConfig configuration file, exception={str(exc)}" - self._logger.error(error_str) - raise ConfigurationException(error_str) - + schema: Dict[ + str, Any + ] = ( + self._schema_fetcher.get_json_configuration() + ) # parse result conf as JSON, keep in cache for self.max_age seconds # validate schema self._schema_validator.validate_json_schema(schema) return schema diff --git a/aws_lambda_powertools/utilities/feature_toggles/exceptions.py b/aws_lambda_powertools/utilities/feature_toggles/exceptions.py index 6fed03d805f..9bbb5f200ba 100644 --- a/aws_lambda_powertools/utilities/feature_toggles/exceptions.py +++ b/aws_lambda_powertools/utilities/feature_toggles/exceptions.py @@ -1,2 +1,2 @@ class ConfigurationException(Exception): - """When a a configuration store raises an exception on schema retrieval or parsing""" + """When a a configuration store raises an exception on config retrieval or parsing""" diff --git a/aws_lambda_powertools/utilities/feature_toggles/schema_fetcher.py b/aws_lambda_powertools/utilities/feature_toggles/schema_fetcher.py new file mode 100644 index 00000000000..37dee63f7f2 --- /dev/null +++ b/aws_lambda_powertools/utilities/feature_toggles/schema_fetcher.py @@ -0,0 +1,20 @@ +from abc import ABC, abstractclassmethod +from typing import Any, Dict + + +class SchemaFetcher(ABC): + def __init__(self, configuration_name: str, cache_seconds: int): + self.configuration_name = configuration_name + self._cache_seconds = cache_seconds + + @abstractclassmethod + def get_json_configuration(self) -> Dict[str, Any]: + """Get configuration string from any configuration storing service and return the parsed JSON dictionary + + Raises: + ConfigurationException: Any error that can occur during schema fetch or JSON parse + + Returns: + Dict[str, Any]: parsed JSON dictionary + """ + return None diff --git a/tests/functional/feature_toggles/test_feature_toggles.py b/tests/functional/feature_toggles/test_feature_toggles.py index 4c79c42abfc..27f89eb1513 100644 --- a/tests/functional/feature_toggles/test_feature_toggles.py +++ b/tests/functional/feature_toggles/test_feature_toggles.py @@ -3,6 +3,7 @@ import pytest # noqa: F401 from botocore.config import Config +from aws_lambda_powertools.utilities.feature_toggles.appconfig_fetcher import AppConfigFetcher from aws_lambda_powertools.utilities.feature_toggles.configuration_store import ConfigurationStore from aws_lambda_powertools.utilities.feature_toggles.schema import ACTION @@ -15,13 +16,15 @@ def config(): def init_configuration_store(mocker, mock_schema: Dict, config: Config) -> ConfigurationStore: mocked_get_conf = mocker.patch("aws_lambda_powertools.utilities.parameters.AppConfigProvider.get") mocked_get_conf.return_value = mock_schema - conf_store: ConfigurationStore = ConfigurationStore( + + app_conf_fetcher = AppConfigFetcher( environment="test_env", service="test_app", - conf_name="test_conf_name", + configuration_name="test_conf_name", cache_seconds=600, config=config, ) + conf_store: ConfigurationStore = ConfigurationStore(schema_fetcher=app_conf_fetcher) return conf_store diff --git a/tests/functional/feature_toggles/test_schema_validation.py b/tests/functional/feature_toggles/test_schema_validation.py index 9febae65bad..3b024c854b9 100644 --- a/tests/functional/feature_toggles/test_schema_validation.py +++ b/tests/functional/feature_toggles/test_schema_validation.py @@ -5,12 +5,12 @@ from aws_lambda_powertools.utilities.feature_toggles.exceptions import ConfigurationException from aws_lambda_powertools.utilities.feature_toggles.schema import ( ACTION, - FEATURE_DEFAULT_VAL_KEY, - FEATURES_KEY, CONDITION_ACTION, CONDITION_KEY, CONDITION_VALUE, CONDITIONS_KEY, + FEATURE_DEFAULT_VAL_KEY, + FEATURES_KEY, RULE_DEFAULT_VALUE, RULE_NAME_KEY, RULES_KEY,