From 149c5e5ff931805071a0ddb09b0237910c649cb3 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 5 Apr 2023 22:23:41 +0100 Subject: [PATCH 1/4] feat(parameters) - environment default variables --- aws_lambda_powertools/shared/constants.py | 3 ++ .../utilities/parameters/ssm.py | 51 +++++++++++++++---- docs/index.md | 1 + docs/utilities/parameters.md | 3 ++ tests/functional/test_utilities_parameters.py | 38 ++++++++++++++ 5 files changed, 86 insertions(+), 10 deletions(-) diff --git a/aws_lambda_powertools/shared/constants.py b/aws_lambda_powertools/shared/constants.py index 2ec120e4d4a..7061d399515 100644 --- a/aws_lambda_powertools/shared/constants.py +++ b/aws_lambda_powertools/shared/constants.py @@ -22,6 +22,9 @@ IDEMPOTENCY_DISABLED_ENV: str = "POWERTOOLS_IDEMPOTENCY_DISABLED" +PARAMETERS_DEFAULT_DECRYPT: str = "POWERTOOLS_PARAMETERS_SSM_DECRYPT" +PARAMETERS_MAX_AGE: str = "POWERTOOLS_PARAMETERS_MAX_AGE" + LOGGER_LAMBDA_CONTEXT_KEYS = [ "function_arn", "function_memory_size", diff --git a/aws_lambda_powertools/utilities/parameters/ssm.py b/aws_lambda_powertools/utilities/parameters/ssm.py index ae4a76dac4a..ae22e9f1fee 100644 --- a/aws_lambda_powertools/utilities/parameters/ssm.py +++ b/aws_lambda_powertools/utilities/parameters/ssm.py @@ -3,13 +3,18 @@ """ from __future__ import annotations +import os from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, overload import boto3 from botocore.config import Config from typing_extensions import Literal -from aws_lambda_powertools.shared.functions import slice_dictionary +from aws_lambda_powertools.shared import constants +from aws_lambda_powertools.shared.functions import ( + resolve_truthy_env_var_choice, + slice_dictionary, +) from .base import DEFAULT_MAX_AGE_SECS, DEFAULT_PROVIDERS, BaseProvider, transform_value from .exceptions import GetParameterError @@ -112,7 +117,7 @@ def get( # type: ignore[override] name: str, max_age: int = DEFAULT_MAX_AGE_SECS, transform: TransformOptions = None, - decrypt: bool = False, + decrypt: Optional[bool] = None, force_fetch: bool = False, **sdk_options, ) -> Optional[Union[str, dict, bytes]]: @@ -145,6 +150,11 @@ def get( # type: ignore[override] When the parameter provider fails to transform a parameter value. """ + # Resolving if will use the value passed by parameter or the environment + decrypt = resolve_truthy_env_var_choice( + env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt + ) + # Add to `decrypt` sdk_options to we can have an explicit option for this sdk_options["decrypt"] = decrypt @@ -212,7 +222,7 @@ def get_parameters_by_name( self, parameters: Dict[str, Dict], transform: TransformOptions = None, - decrypt: bool = False, + decrypt: Optional[bool] = None, max_age: int = DEFAULT_MAX_AGE_SECS, raise_on_error: bool = True, ) -> Dict[str, str] | Dict[str, bytes] | Dict[str, dict]: @@ -259,6 +269,12 @@ def get_parameters_by_name( When "_errors" reserved key is in parameters to be fetched from SSM. """ + + # Resolving if will use the default value (False), the value passed by parameter or the environment variable + decrypt = resolve_truthy_env_var_choice( + env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt + ) + # Init potential batch/decrypt batch responses and errors batch_ret: Dict[str, Any] = {} decrypt_ret: Dict[str, Any] = {} @@ -487,7 +503,7 @@ def _raise_if_errors_key_is_present(parameters: Dict, reserved_parameter: str, r def get_parameter( name: str, transform: Optional[str] = None, - decrypt: bool = False, + decrypt: Optional[bool] = None, force_fetch: bool = False, max_age: int = DEFAULT_MAX_AGE_SECS, **sdk_options, @@ -543,6 +559,11 @@ def get_parameter( if "ssm" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["ssm"] = SSMProvider() + # Resolving if will use the default value (False), the value passed by parameter or the environment variable + decrypt = resolve_truthy_env_var_choice( + env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt + ) + # Add to `decrypt` sdk_options to we can have an explicit option for this sdk_options["decrypt"] = decrypt @@ -555,7 +576,7 @@ def get_parameters( path: str, transform: Optional[str] = None, recursive: bool = True, - decrypt: bool = False, + decrypt: Optional[bool] = None, force_fetch: bool = False, max_age: int = DEFAULT_MAX_AGE_SECS, raise_on_transform_error: bool = False, @@ -617,6 +638,11 @@ def get_parameters( if "ssm" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["ssm"] = SSMProvider() + # Resolving if will use the default value (False), the value passed by parameter or the environment variable + decrypt = resolve_truthy_env_var_choice( + env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt + ) + sdk_options["recursive"] = recursive sdk_options["decrypt"] = decrypt @@ -634,7 +660,7 @@ def get_parameters( def get_parameters_by_name( parameters: Dict[str, Dict], transform: None = None, - decrypt: bool = False, + decrypt: Optional[bool] = None, max_age: int = DEFAULT_MAX_AGE_SECS, raise_on_error: bool = True, ) -> Dict[str, str]: @@ -645,7 +671,7 @@ def get_parameters_by_name( def get_parameters_by_name( parameters: Dict[str, Dict], transform: Literal["binary"], - decrypt: bool = False, + decrypt: Optional[bool] = None, max_age: int = DEFAULT_MAX_AGE_SECS, raise_on_error: bool = True, ) -> Dict[str, bytes]: @@ -656,7 +682,7 @@ def get_parameters_by_name( def get_parameters_by_name( parameters: Dict[str, Dict], transform: Literal["json"], - decrypt: bool = False, + decrypt: Optional[bool] = None, max_age: int = DEFAULT_MAX_AGE_SECS, raise_on_error: bool = True, ) -> Dict[str, Dict[str, Any]]: @@ -667,7 +693,7 @@ def get_parameters_by_name( def get_parameters_by_name( parameters: Dict[str, Dict], transform: Literal["auto"], - decrypt: bool = False, + decrypt: Optional[bool] = None, max_age: int = DEFAULT_MAX_AGE_SECS, raise_on_error: bool = True, ) -> Union[Dict[str, str], Dict[str, dict]]: @@ -677,7 +703,7 @@ def get_parameters_by_name( def get_parameters_by_name( parameters: Dict[str, Any], transform: TransformOptions = None, - decrypt: bool = False, + decrypt: Optional[bool] = None, max_age: int = DEFAULT_MAX_AGE_SECS, raise_on_error: bool = True, ) -> Union[Dict[str, str], Dict[str, bytes], Dict[str, dict]]: @@ -732,6 +758,11 @@ def get_parameters_by_name( # NOTE: Decided against using multi-thread due to single-thread outperforming in 128M and 1G + timeout risk # see: https://github.com/awslabs/aws-lambda-powertools-python/issues/1040#issuecomment-1299954613 + # Resolving if will use the default value (False), the value passed by parameter or the environment variable + decrypt = resolve_truthy_env_var_choice( + env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt + ) + # Only create the provider if this function is called at least once if "ssm" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["ssm"] = SSMProvider() diff --git a/docs/index.md b/docs/index.md index 82795db6b4e..095a5b31de2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -706,6 +706,7 @@ Core utilities such as Tracing, Logging, Metrics, and Event Handler will be avai | **POWERTOOLS_LOGGER_LOG_EVENT** | Logs incoming event | [Logging](./core/logger) | `false` | | **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logging](./core/logger) | `0` | | **POWERTOOLS_LOG_DEDUPLICATION_DISABLED** | Disables log deduplication filter protection to use Pytest Live Log feature | [Logging](./core/logger) | `false` | +| **POWERTOOLS_PARAMETERS_SSM_DECRYPT** | Sets whether to decrypt or not values retrieved from AWS SSM Parameters Store | [Parameters](.//utilities/parameters/#ssmprovider) | `false` | | **POWERTOOLS_DEV** | Increases verbosity across utilities | Multiple; see [POWERTOOLS_DEV effect below](#increasing-verbosity-across-utilities) | `false` | | **LOG_LEVEL** | Sets logging level | [Logging](./core/logger) | `INFO` | diff --git a/docs/utilities/parameters.md b/docs/utilities/parameters.md index e00851fbb8b..a64c5f2b4d8 100644 --- a/docs/utilities/parameters.md +++ b/docs/utilities/parameters.md @@ -179,6 +179,9 @@ The AWS Systems Manager Parameter Store provider supports two additional argumen You can create `SecureString` parameters, which are parameters that have a plaintext parameter name and an encrypted parameter value. If you don't use the `decrypt` argument, you will get an encrypted value. Read [here](https://docs.aws.amazon.com/kms/latest/developerguide/services-parameter-store.html) about best practices using KMS to secure your parameters. +???+ tip + If you want to always decrypt parameters, you can set the `POWERTOOLS_PARAMETERS_SSM_DECRYPT=true` environment variable. **This will override the default value of `false` but can be overridden by the `decrypt` parameter**. + === "builtin_provider_ssm_with_decrypt.py" ```python hl_lines="6 10 16" --8<-- "examples/parameters/src/builtin_provider_ssm_with_decrypt.py" diff --git a/tests/functional/test_utilities_parameters.py b/tests/functional/test_utilities_parameters.py index f3d326fcd58..83b6440a50c 100644 --- a/tests/functional/test_utilities_parameters.py +++ b/tests/functional/test_utilities_parameters.py @@ -547,6 +547,44 @@ def test_ssm_provider_get_with_custom_client(mock_name, mock_value, mock_version stubber.deactivate() +def test_ssm_provider_get_with_decrypt_environment_variable(monkeypatch, mock_name, mock_value, mock_version, config): + """ + Test SSMProvider.get() with decrypt value replaced by environment variable + """ + + # Setting environment variable to override the default value + monkeypatch.setenv("POWERTOOLS_PARAMETERS_SSM_DECRYPT", "true") + + # Create a new provider + provider = parameters.SSMProvider(config=config) + + # Stub the boto3 client + stubber = stub.Stubber(provider.client) + response = { + "Parameter": { + "Name": mock_name, + "Type": "String", + "Value": mock_value, + "Version": mock_version, + "Selector": f"{mock_name}:{mock_version}", + "SourceResult": "string", + "LastModifiedDate": datetime(2015, 1, 1), + "ARN": f"arn:aws:ssm:us-east-2:111122223333:parameter/{mock_name}", + } + } + expected_params = {"Name": mock_name, "WithDecryption": True} + stubber.add_response("get_parameter", response, expected_params) + stubber.activate() + + try: + value = provider.get(mock_name) + + assert value == mock_value + stubber.assert_no_pending_responses() + finally: + stubber.deactivate() + + def test_ssm_provider_get_default_config(monkeypatch, mock_name, mock_value, mock_version): """ Test SSMProvider.get() without specifying the config From b4c2d484a83defaf1e27b4ed40ce29d40ba7f537 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 5 Apr 2023 22:50:46 +0100 Subject: [PATCH 2/4] feat(parameters) - environment default variables - maxage --- aws_lambda_powertools/utilities/parameters/base.py | 6 +++++- docs/index.md | 1 + docs/utilities/parameters.md | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/utilities/parameters/base.py b/aws_lambda_powertools/utilities/parameters/base.py index 8587d3b5f3f..a4726e6dc18 100644 --- a/aws_lambda_powertools/utilities/parameters/base.py +++ b/aws_lambda_powertools/utilities/parameters/base.py @@ -5,6 +5,7 @@ import base64 import json +import os from abc import ABC, abstractmethod from datetime import datetime, timedelta from typing import ( @@ -24,6 +25,7 @@ import boto3 from botocore.config import Config +from aws_lambda_powertools.shared import constants from aws_lambda_powertools.utilities.parameters.types import TransformOptions from .exceptions import GetParameterError, TransformParameterError @@ -35,7 +37,9 @@ from mypy_boto3_ssm import SSMClient -DEFAULT_MAX_AGE_SECS = 5 +# If the environment variable is not set, the default value is 5 +DEFAULT_MAX_AGE_SECS: int = int(os.getenv(constants.PARAMETERS_MAX_AGE, 5)) + # These providers will be dynamically initialized on first use of the helper functions DEFAULT_PROVIDERS: Dict[str, Any] = {} TRANSFORM_METHOD_JSON = "json" diff --git a/docs/index.md b/docs/index.md index 095a5b31de2..c34e94c7911 100644 --- a/docs/index.md +++ b/docs/index.md @@ -706,6 +706,7 @@ Core utilities such as Tracing, Logging, Metrics, and Event Handler will be avai | **POWERTOOLS_LOGGER_LOG_EVENT** | Logs incoming event | [Logging](./core/logger) | `false` | | **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logging](./core/logger) | `0` | | **POWERTOOLS_LOG_DEDUPLICATION_DISABLED** | Disables log deduplication filter protection to use Pytest Live Log feature | [Logging](./core/logger) | `false` | +| **POWERTOOLS_PARAMETERS_MAX_AGE** | Adjust how long values are kept in cache (in seconds) | [Parameters](.//utilities/parameters/#adjusting-cache-ttl) | `5` | | **POWERTOOLS_PARAMETERS_SSM_DECRYPT** | Sets whether to decrypt or not values retrieved from AWS SSM Parameters Store | [Parameters](.//utilities/parameters/#ssmprovider) | `false` | | **POWERTOOLS_DEV** | Increases verbosity across utilities | Multiple; see [POWERTOOLS_DEV effect below](#increasing-verbosity-across-utilities) | `false` | | **LOG_LEVEL** | Sets logging level | [Logging](./core/logger) | `INFO` | diff --git a/docs/utilities/parameters.md b/docs/utilities/parameters.md index a64c5f2b4d8..4bb1d0e60f2 100644 --- a/docs/utilities/parameters.md +++ b/docs/utilities/parameters.md @@ -103,7 +103,7 @@ The following will retrieve the latest version and store it in the cache. ???+ tip `max_age` parameter is also available in underlying provider functions like `get()`, `get_multiple()`, etc. -By default, we cache parameters retrieved in-memory for 5 seconds. +By default, we cache parameters retrieved in-memory for 5 seconds. If you want to change this default value and set the same TTL for all parameters, you can set the `POWERTOOLS_PARAMETERS_MAX_AGE` environment variable. **This will override the default TTL of 5 seconds but can be overridden by the `maxAge` parameter**. You can adjust how long we should keep values in cache by using the param `max_age`, when using `get_parameter()`, `get_parameters()` and `get_secret()` methods across all providers. From 77c41a22379f4b33371172380ae620ccbc56d727 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Tue, 11 Apr 2023 17:16:32 +0100 Subject: [PATCH 3/4] feat(parameters) - environment default variables - refactor maxage code --- aws_lambda_powertools/shared/functions.py | 5 +++ .../utilities/parameters/appconfig.py | 12 +++++-- .../utilities/parameters/base.py | 13 +++++-- .../utilities/parameters/secrets.py | 13 ++++--- .../utilities/parameters/ssm.py | 36 +++++++++++++------ tests/unit/test_shared_functions.py | 35 ++++++++++++++++++ 6 files changed, 93 insertions(+), 21 deletions(-) diff --git a/aws_lambda_powertools/shared/functions.py b/aws_lambda_powertools/shared/functions.py index 86ba74d5e78..20a4994e9be 100644 --- a/aws_lambda_powertools/shared/functions.py +++ b/aws_lambda_powertools/shared/functions.py @@ -51,6 +51,11 @@ def resolve_truthy_env_var_choice(env: str, choice: Optional[bool] = None) -> bo return choice if choice is not None else strtobool(env) +def resolve_max_age(env: str, choice: Optional[int]) -> int: + """Resolve max age value""" + return choice if choice is not None else int(env) + + @overload def resolve_env_var_choice(env: Optional[str], choice: float) -> float: ... diff --git a/aws_lambda_powertools/utilities/parameters/appconfig.py b/aws_lambda_powertools/utilities/parameters/appconfig.py index 7884728024e..242b019312f 100644 --- a/aws_lambda_powertools/utilities/parameters/appconfig.py +++ b/aws_lambda_powertools/utilities/parameters/appconfig.py @@ -14,8 +14,12 @@ if TYPE_CHECKING: from mypy_boto3_appconfigdata import AppConfigDataClient -from ...shared import constants -from ...shared.functions import resolve_env_var_choice +from aws_lambda_powertools.shared import constants +from aws_lambda_powertools.shared.functions import ( + resolve_env_var_choice, + resolve_max_age, +) + from .base import DEFAULT_MAX_AGE_SECS, DEFAULT_PROVIDERS, BaseProvider @@ -136,7 +140,7 @@ def get_app_config( application: Optional[str] = None, transform: TransformOptions = None, force_fetch: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, **sdk_options ) -> Union[str, list, dict, bytes]: """ @@ -187,6 +191,8 @@ def get_app_config( >>> print(value) My configuration's JSON value """ + # Resolving if will use the default value (5), the value passed by parameter or the environment variable + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) # Only create the provider if this function is called at least once if "appconfig" not in DEFAULT_PROVIDERS: diff --git a/aws_lambda_powertools/utilities/parameters/base.py b/aws_lambda_powertools/utilities/parameters/base.py index a4726e6dc18..21ecbd6fad4 100644 --- a/aws_lambda_powertools/utilities/parameters/base.py +++ b/aws_lambda_powertools/utilities/parameters/base.py @@ -26,6 +26,7 @@ from botocore.config import Config from aws_lambda_powertools.shared import constants +from aws_lambda_powertools.shared.functions import resolve_max_age from aws_lambda_powertools.utilities.parameters.types import TransformOptions from .exceptions import GetParameterError, TransformParameterError @@ -38,7 +39,7 @@ # If the environment variable is not set, the default value is 5 -DEFAULT_MAX_AGE_SECS: int = int(os.getenv(constants.PARAMETERS_MAX_AGE, 5)) +DEFAULT_MAX_AGE_SECS = "5" # These providers will be dynamically initialized on first use of the helper functions DEFAULT_PROVIDERS: Dict[str, Any] = {} @@ -81,7 +82,7 @@ def has_not_expired_in_cache(self, key: Tuple[str, TransformOptions]) -> bool: def get( self, name: str, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, transform: TransformOptions = None, force_fetch: bool = False, **sdk_options, @@ -125,6 +126,9 @@ def get( value: Optional[Union[str, bytes, dict]] = None key = (name, transform) + # Resolving if will use the default value (5), the value passed by parameter or the environment variable + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + if not force_fetch and self.has_not_expired_in_cache(key): return self.store[key].value @@ -153,7 +157,7 @@ def _get(self, name: str, **sdk_options) -> Union[str, bytes]: def get_multiple( self, path: str, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, transform: TransformOptions = None, raise_on_transform_error: bool = False, force_fetch: bool = False, @@ -190,6 +194,9 @@ def get_multiple( """ key = (path, transform) + # Resolving if will use the default value (5), the value passed by parameter or the environment variable + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + if not force_fetch and self.has_not_expired_in_cache(key): return self.store[key].value # type: ignore # need to revisit entire typing here diff --git a/aws_lambda_powertools/utilities/parameters/secrets.py b/aws_lambda_powertools/utilities/parameters/secrets.py index 4a616fcf9f9..996589730e4 100644 --- a/aws_lambda_powertools/utilities/parameters/secrets.py +++ b/aws_lambda_powertools/utilities/parameters/secrets.py @@ -3,6 +3,7 @@ """ +import os from typing import TYPE_CHECKING, Any, Dict, Optional, Union import boto3 @@ -11,6 +12,9 @@ if TYPE_CHECKING: from mypy_boto3_secretsmanager import SecretsManagerClient +from aws_lambda_powertools.shared import constants +from aws_lambda_powertools.shared.functions import resolve_max_age + from .base import DEFAULT_MAX_AGE_SECS, DEFAULT_PROVIDERS, BaseProvider @@ -111,11 +115,7 @@ def _get_multiple(self, path: str, **sdk_options) -> Dict[str, str]: def get_secret( - name: str, - transform: Optional[str] = None, - force_fetch: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, - **sdk_options + name: str, transform: Optional[str] = None, force_fetch: bool = False, max_age: Optional[int] = None, **sdk_options ) -> Union[str, dict, bytes]: """ Retrieve a parameter value from AWS Secrets Manager @@ -162,6 +162,9 @@ def get_secret( >>> get_secret("my-secret", VersionId="f658cac0-98a5-41d9-b993-8a76a7799194") """ + # Resolving if will use the default value (5), the value passed by parameter or the environment variable + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # Only create the provider if this function is called at least once if "secrets" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["secrets"] = SecretsProvider() diff --git a/aws_lambda_powertools/utilities/parameters/ssm.py b/aws_lambda_powertools/utilities/parameters/ssm.py index ae22e9f1fee..e086e5aaf15 100644 --- a/aws_lambda_powertools/utilities/parameters/ssm.py +++ b/aws_lambda_powertools/utilities/parameters/ssm.py @@ -12,6 +12,7 @@ from aws_lambda_powertools.shared import constants from aws_lambda_powertools.shared.functions import ( + resolve_max_age, resolve_truthy_env_var_choice, slice_dictionary, ) @@ -115,7 +116,7 @@ def __init__( def get( # type: ignore[override] self, name: str, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, transform: TransformOptions = None, decrypt: Optional[bool] = None, force_fetch: bool = False, @@ -150,7 +151,10 @@ def get( # type: ignore[override] When the parameter provider fails to transform a parameter value. """ - # Resolving if will use the value passed by parameter or the environment + # Resolving if will use the default value (5), the value passed by parameter or the environment variable + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + + # Resolving if will use the default value (False), the value passed by parameter or the environment variable decrypt = resolve_truthy_env_var_choice( env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt ) @@ -223,7 +227,7 @@ def get_parameters_by_name( parameters: Dict[str, Dict], transform: TransformOptions = None, decrypt: Optional[bool] = None, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, raise_on_error: bool = True, ) -> Dict[str, str] | Dict[str, bytes] | Dict[str, dict]: """ @@ -270,6 +274,9 @@ def get_parameters_by_name( When "_errors" reserved key is in parameters to be fetched from SSM. """ + # Resolving if will use the default value (5), the value passed by parameter or the environment variable + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # Resolving if will use the default value (False), the value passed by parameter or the environment variable decrypt = resolve_truthy_env_var_choice( env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt @@ -505,7 +512,7 @@ def get_parameter( transform: Optional[str] = None, decrypt: Optional[bool] = None, force_fetch: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, **sdk_options, ) -> Union[str, dict, bytes]: """ @@ -559,6 +566,9 @@ def get_parameter( if "ssm" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["ssm"] = SSMProvider() + # Resolving if will use the default value (5), the value passed by parameter or the environment variable + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # Resolving if will use the default value (False), the value passed by parameter or the environment variable decrypt = resolve_truthy_env_var_choice( env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt @@ -578,7 +588,7 @@ def get_parameters( recursive: bool = True, decrypt: Optional[bool] = None, force_fetch: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, raise_on_transform_error: bool = False, **sdk_options, ) -> Union[Dict[str, str], Dict[str, dict], Dict[str, bytes]]: @@ -638,6 +648,9 @@ def get_parameters( if "ssm" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["ssm"] = SSMProvider() + # Resolving if will use the default value (5), the value passed by parameter or the environment variable + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # Resolving if will use the default value (False), the value passed by parameter or the environment variable decrypt = resolve_truthy_env_var_choice( env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt @@ -661,7 +674,7 @@ def get_parameters_by_name( parameters: Dict[str, Dict], transform: None = None, decrypt: Optional[bool] = None, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, raise_on_error: bool = True, ) -> Dict[str, str]: ... @@ -672,7 +685,7 @@ def get_parameters_by_name( parameters: Dict[str, Dict], transform: Literal["binary"], decrypt: Optional[bool] = None, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, raise_on_error: bool = True, ) -> Dict[str, bytes]: ... @@ -683,7 +696,7 @@ def get_parameters_by_name( parameters: Dict[str, Dict], transform: Literal["json"], decrypt: Optional[bool] = None, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, raise_on_error: bool = True, ) -> Dict[str, Dict[str, Any]]: ... @@ -694,7 +707,7 @@ def get_parameters_by_name( parameters: Dict[str, Dict], transform: Literal["auto"], decrypt: Optional[bool] = None, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, raise_on_error: bool = True, ) -> Union[Dict[str, str], Dict[str, dict]]: ... @@ -704,7 +717,7 @@ def get_parameters_by_name( parameters: Dict[str, Any], transform: TransformOptions = None, decrypt: Optional[bool] = None, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, raise_on_error: bool = True, ) -> Union[Dict[str, str], Dict[str, bytes], Dict[str, dict]]: """ @@ -758,6 +771,9 @@ def get_parameters_by_name( # NOTE: Decided against using multi-thread due to single-thread outperforming in 128M and 1G + timeout risk # see: https://github.com/awslabs/aws-lambda-powertools-python/issues/1040#issuecomment-1299954613 + # Resolving if will use the default value (5), the value passed by parameter or the environment variable + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # Resolving if will use the default value (False), the value passed by parameter or the environment variable decrypt = resolve_truthy_env_var_choice( env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt diff --git a/tests/unit/test_shared_functions.py b/tests/unit/test_shared_functions.py index 00abe2c6e08..78fe1fd2098 100644 --- a/tests/unit/test_shared_functions.py +++ b/tests/unit/test_shared_functions.py @@ -1,3 +1,4 @@ +import os import warnings from dataclasses import dataclass @@ -10,10 +11,12 @@ powertools_debug_is_set, powertools_dev_is_set, resolve_env_var_choice, + resolve_max_age, resolve_truthy_env_var_choice, strtobool, ) from aws_lambda_powertools.utilities.data_classes.common import DictWrapper +from aws_lambda_powertools.utilities.parameters.base import DEFAULT_MAX_AGE_SECS def test_resolve_env_var_choice_explicit_wins_over_env_var(): @@ -103,3 +106,35 @@ class DummyDataclass: @pytest.mark.parametrize("data", [False, True, "", 10, [], {}, object]) def test_extract_event_any(data): assert extract_event_from_common_models(data) == data + + +def test_resolve_max_age_explicit_wins_over_env_var(monkeypatch: pytest.MonkeyPatch): + # GIVEN POWERTOOLS_PARAMETERS_MAX_AGE environment variable is set + monkeypatch.setenv(constants.PARAMETERS_MAX_AGE, "20") + + # WHEN the choice is set explicitly + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=10) + + # THEN the result must be the choice + assert max_age == 10 + + +def test_resolve_max_age_with_default_value(): + # GIVEN POWERTOOLS_PARAMETERS_MAX_AGE is not set + + # WHEN the choice is set to None + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=None) + + # THEN the result must be the default value (DEFAULT_MAX_AGE_SECS) + assert max_age == int(DEFAULT_MAX_AGE_SECS) + + +def test_resolve_max_age_env_var_wins_over_default_value(monkeypatch: pytest.MonkeyPatch): + # GIVEN POWERTOOLS_PARAMETERS_MAX_AGE environment variable is set + monkeypatch.setenv(constants.PARAMETERS_MAX_AGE, "20") + + # WHEN the choice is set to None + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=None) + + # THEN the result must be the environment variable value + assert max_age == 20 From 74f65f9725edcb34e1f0668e1e56980d928bb541 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 13 Apr 2023 10:33:27 +0100 Subject: [PATCH 4/4] feat(parameters) - addressing Ruben's feedback --- aws_lambda_powertools/shared/constants.py | 4 +- .../utilities/parameters/appconfig.py | 6 +-- .../utilities/parameters/base.py | 9 ++-- .../utilities/parameters/secrets.py | 6 +-- .../utilities/parameters/ssm.py | 50 +++++++++---------- docs/index.md | 4 +- docs/utilities/parameters.md | 4 +- tests/unit/test_shared_functions.py | 10 ++-- 8 files changed, 46 insertions(+), 47 deletions(-) diff --git a/aws_lambda_powertools/shared/constants.py b/aws_lambda_powertools/shared/constants.py index 7061d399515..0cde7582976 100644 --- a/aws_lambda_powertools/shared/constants.py +++ b/aws_lambda_powertools/shared/constants.py @@ -22,8 +22,8 @@ IDEMPOTENCY_DISABLED_ENV: str = "POWERTOOLS_IDEMPOTENCY_DISABLED" -PARAMETERS_DEFAULT_DECRYPT: str = "POWERTOOLS_PARAMETERS_SSM_DECRYPT" -PARAMETERS_MAX_AGE: str = "POWERTOOLS_PARAMETERS_MAX_AGE" +PARAMETERS_SSM_DECRYPT_ENV: str = "POWERTOOLS_PARAMETERS_SSM_DECRYPT" +PARAMETERS_MAX_AGE_ENV: str = "POWERTOOLS_PARAMETERS_MAX_AGE" LOGGER_LAMBDA_CONTEXT_KEYS = [ "function_arn", diff --git a/aws_lambda_powertools/utilities/parameters/appconfig.py b/aws_lambda_powertools/utilities/parameters/appconfig.py index 242b019312f..297762628e1 100644 --- a/aws_lambda_powertools/utilities/parameters/appconfig.py +++ b/aws_lambda_powertools/utilities/parameters/appconfig.py @@ -158,7 +158,7 @@ def get_app_config( Transforms the content from a JSON object ('json') or base64 binary string ('binary') force_fetch: bool, optional Force update even before a cached item has expired, defaults to False - max_age: int + max_age: int, optional Maximum age of the cached value sdk_options: dict, optional SDK options to propagate to `start_configuration_session` API call @@ -191,8 +191,8 @@ def get_app_config( >>> print(value) My configuration's JSON value """ - # Resolving if will use the default value (5), the value passed by parameter or the environment variable - max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) # Only create the provider if this function is called at least once if "appconfig" not in DEFAULT_PROVIDERS: diff --git a/aws_lambda_powertools/utilities/parameters/base.py b/aws_lambda_powertools/utilities/parameters/base.py index 21ecbd6fad4..8ec1052ae37 100644 --- a/aws_lambda_powertools/utilities/parameters/base.py +++ b/aws_lambda_powertools/utilities/parameters/base.py @@ -38,7 +38,6 @@ from mypy_boto3_ssm import SSMClient -# If the environment variable is not set, the default value is 5 DEFAULT_MAX_AGE_SECS = "5" # These providers will be dynamically initialized on first use of the helper functions @@ -126,8 +125,8 @@ def get( value: Optional[Union[str, bytes, dict]] = None key = (name, transform) - # Resolving if will use the default value (5), the value passed by parameter or the environment variable - max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) if not force_fetch and self.has_not_expired_in_cache(key): return self.store[key].value @@ -194,8 +193,8 @@ def get_multiple( """ key = (path, transform) - # Resolving if will use the default value (5), the value passed by parameter or the environment variable - max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) if not force_fetch and self.has_not_expired_in_cache(key): return self.store[key].value # type: ignore # need to revisit entire typing here diff --git a/aws_lambda_powertools/utilities/parameters/secrets.py b/aws_lambda_powertools/utilities/parameters/secrets.py index 996589730e4..b11cb472012 100644 --- a/aws_lambda_powertools/utilities/parameters/secrets.py +++ b/aws_lambda_powertools/utilities/parameters/secrets.py @@ -128,7 +128,7 @@ def get_secret( Transforms the content from a JSON object ('json') or base64 binary string ('binary') force_fetch: bool, optional Force update even before a cached item has expired, defaults to False - max_age: int + max_age: int, optional Maximum age of the cached value sdk_options: dict, optional Dictionary of options that will be passed to the get_secret_value call @@ -162,8 +162,8 @@ def get_secret( >>> get_secret("my-secret", VersionId="f658cac0-98a5-41d9-b993-8a76a7799194") """ - # Resolving if will use the default value (5), the value passed by parameter or the environment variable - max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) # Only create the provider if this function is called at least once if "secrets" not in DEFAULT_PROVIDERS: diff --git a/aws_lambda_powertools/utilities/parameters/ssm.py b/aws_lambda_powertools/utilities/parameters/ssm.py index e086e5aaf15..26f225cfb28 100644 --- a/aws_lambda_powertools/utilities/parameters/ssm.py +++ b/aws_lambda_powertools/utilities/parameters/ssm.py @@ -129,7 +129,7 @@ def get( # type: ignore[override] ---------- name: str Parameter name - max_age: int + max_age: int, optional Maximum age of the cached value transform: str Optional transformation of the parameter value. Supported values @@ -151,12 +151,12 @@ def get( # type: ignore[override] When the parameter provider fails to transform a parameter value. """ - # Resolving if will use the default value (5), the value passed by parameter or the environment variable - max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) - # Resolving if will use the default value (False), the value passed by parameter or the environment variable + # If decrypt is not set, resolve it from the environment variable, defaulting to False decrypt = resolve_truthy_env_var_choice( - env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt + env=os.getenv(constants.PARAMETERS_SSM_DECRYPT_ENV, "false"), choice=decrypt ) # Add to `decrypt` sdk_options to we can have an explicit option for this @@ -261,7 +261,7 @@ def get_parameters_by_name( Transforms the content from a JSON object ('json') or base64 binary string ('binary') decrypt: bool, optional If the parameter values should be decrypted - max_age: int + max_age: int, optional Maximum age of the cached value raise_on_error: bool Whether to fail-fast or fail gracefully by including "_errors" key in the response, by default True @@ -274,12 +274,12 @@ def get_parameters_by_name( When "_errors" reserved key is in parameters to be fetched from SSM. """ - # Resolving if will use the default value (5), the value passed by parameter or the environment variable - max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) - # Resolving if will use the default value (False), the value passed by parameter or the environment variable + # If decrypt is not set, resolve it from the environment variable, defaulting to False decrypt = resolve_truthy_env_var_choice( - env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt + env=os.getenv(constants.PARAMETERS_SSM_DECRYPT_ENV, "false"), choice=decrypt ) # Init potential batch/decrypt batch responses and errors @@ -528,7 +528,7 @@ def get_parameter( If the parameter values should be decrypted force_fetch: bool, optional Force update even before a cached item has expired, defaults to False - max_age: int + max_age: int, optional Maximum age of the cached value sdk_options: dict, optional Dictionary of options that will be passed to the Parameter Store get_parameter API call @@ -566,12 +566,12 @@ def get_parameter( if "ssm" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["ssm"] = SSMProvider() - # Resolving if will use the default value (5), the value passed by parameter or the environment variable - max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) - # Resolving if will use the default value (False), the value passed by parameter or the environment variable + # If decrypt is not set, resolve it from the environment variable, defaulting to False decrypt = resolve_truthy_env_var_choice( - env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt + env=os.getenv(constants.PARAMETERS_SSM_DECRYPT_ENV, "false"), choice=decrypt ) # Add to `decrypt` sdk_options to we can have an explicit option for this @@ -607,7 +607,7 @@ def get_parameters( If the parameter values should be decrypted force_fetch: bool, optional Force update even before a cached item has expired, defaults to False - max_age: int + max_age: int, optional Maximum age of the cached value raise_on_transform_error: bool, optional Raises an exception if any transform fails, otherwise this will @@ -648,12 +648,12 @@ def get_parameters( if "ssm" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["ssm"] = SSMProvider() - # Resolving if will use the default value (5), the value passed by parameter or the environment variable - max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) - # Resolving if will use the default value (False), the value passed by parameter or the environment variable + # If decrypt is not set, resolve it from the environment variable, defaulting to False decrypt = resolve_truthy_env_var_choice( - env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt + env=os.getenv(constants.PARAMETERS_SSM_DECRYPT_ENV, "false"), choice=decrypt ) sdk_options["recursive"] = recursive @@ -731,7 +731,7 @@ def get_parameters_by_name( Transforms the content from a JSON object ('json') or base64 binary string ('binary') decrypt: bool, optional If the parameter values should be decrypted - max_age: int + max_age: int, optional Maximum age of the cached value raise_on_error: bool, optional Whether to fail-fast or fail gracefully by including "_errors" key in the response, by default True @@ -771,12 +771,12 @@ def get_parameters_by_name( # NOTE: Decided against using multi-thread due to single-thread outperforming in 128M and 1G + timeout risk # see: https://github.com/awslabs/aws-lambda-powertools-python/issues/1040#issuecomment-1299954613 - # Resolving if will use the default value (5), the value passed by parameter or the environment variable - max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=max_age) + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) - # Resolving if will use the default value (False), the value passed by parameter or the environment variable + # If decrypt is not set, resolve it from the environment variable, defaulting to False decrypt = resolve_truthy_env_var_choice( - env=os.getenv(constants.PARAMETERS_DEFAULT_DECRYPT, "false"), choice=decrypt + env=os.getenv(constants.PARAMETERS_SSM_DECRYPT_ENV, "false"), choice=decrypt ) # Only create the provider if this function is called at least once diff --git a/docs/index.md b/docs/index.md index 2d89cf3cf1e..074d5e40809 100644 --- a/docs/index.md +++ b/docs/index.md @@ -706,8 +706,8 @@ Core utilities such as Tracing, Logging, Metrics, and Event Handler will be avai | **POWERTOOLS_LOGGER_LOG_EVENT** | Logs incoming event | [Logging](./core/logger) | `false` | | **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logging](./core/logger) | `0` | | **POWERTOOLS_LOG_DEDUPLICATION_DISABLED** | Disables log deduplication filter protection to use Pytest Live Log feature | [Logging](./core/logger) | `false` | -| **POWERTOOLS_PARAMETERS_MAX_AGE** | Adjust how long values are kept in cache (in seconds) | [Parameters](.//utilities/parameters/#adjusting-cache-ttl) | `5` | -| **POWERTOOLS_PARAMETERS_SSM_DECRYPT** | Sets whether to decrypt or not values retrieved from AWS SSM Parameters Store | [Parameters](.//utilities/parameters/#ssmprovider) | `false` | +| **POWERTOOLS_PARAMETERS_MAX_AGE** | Adjust how long values are kept in cache (in seconds) | [Parameters](./utilities/parameters/#adjusting-cache-ttl) | `5` | +| **POWERTOOLS_PARAMETERS_SSM_DECRYPT** | Sets whether to decrypt or not values retrieved from AWS SSM Parameters Store | [Parameters](./utilities/parameters/#ssmprovider) | `false` | | **POWERTOOLS_DEV** | Increases verbosity across utilities | Multiple; see [POWERTOOLS_DEV effect below](#increasing-verbosity-across-utilities) | `false` | | **LOG_LEVEL** | Sets logging level | [Logging](./core/logger) | `INFO` | diff --git a/docs/utilities/parameters.md b/docs/utilities/parameters.md index 4bb1d0e60f2..3e2fd37c8aa 100644 --- a/docs/utilities/parameters.md +++ b/docs/utilities/parameters.md @@ -103,7 +103,7 @@ The following will retrieve the latest version and store it in the cache. ???+ tip `max_age` parameter is also available in underlying provider functions like `get()`, `get_multiple()`, etc. -By default, we cache parameters retrieved in-memory for 5 seconds. If you want to change this default value and set the same TTL for all parameters, you can set the `POWERTOOLS_PARAMETERS_MAX_AGE` environment variable. **This will override the default TTL of 5 seconds but can be overridden by the `maxAge` parameter**. +By default, we cache parameters retrieved in-memory for 5 seconds. If you want to change this default value and set the same TTL for all parameters, you can set the `POWERTOOLS_PARAMETERS_MAX_AGE` environment variable. **You can still set `max_age` for individual parameters**. You can adjust how long we should keep values in cache by using the param `max_age`, when using `get_parameter()`, `get_parameters()` and `get_secret()` methods across all providers. @@ -180,7 +180,7 @@ The AWS Systems Manager Parameter Store provider supports two additional argumen You can create `SecureString` parameters, which are parameters that have a plaintext parameter name and an encrypted parameter value. If you don't use the `decrypt` argument, you will get an encrypted value. Read [here](https://docs.aws.amazon.com/kms/latest/developerguide/services-parameter-store.html) about best practices using KMS to secure your parameters. ???+ tip - If you want to always decrypt parameters, you can set the `POWERTOOLS_PARAMETERS_SSM_DECRYPT=true` environment variable. **This will override the default value of `false` but can be overridden by the `decrypt` parameter**. + If you want to always decrypt parameters, you can set the `POWERTOOLS_PARAMETERS_SSM_DECRYPT=true` environment variable. **This will override the default value of `false` but you can still set the `decrypt` option for individual parameters**. === "builtin_provider_ssm_with_decrypt.py" ```python hl_lines="6 10 16" diff --git a/tests/unit/test_shared_functions.py b/tests/unit/test_shared_functions.py index 78fe1fd2098..9232b72527b 100644 --- a/tests/unit/test_shared_functions.py +++ b/tests/unit/test_shared_functions.py @@ -110,10 +110,10 @@ def test_extract_event_any(data): def test_resolve_max_age_explicit_wins_over_env_var(monkeypatch: pytest.MonkeyPatch): # GIVEN POWERTOOLS_PARAMETERS_MAX_AGE environment variable is set - monkeypatch.setenv(constants.PARAMETERS_MAX_AGE, "20") + monkeypatch.setenv(constants.PARAMETERS_MAX_AGE_ENV, "20") # WHEN the choice is set explicitly - max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=10) + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=10) # THEN the result must be the choice assert max_age == 10 @@ -123,7 +123,7 @@ def test_resolve_max_age_with_default_value(): # GIVEN POWERTOOLS_PARAMETERS_MAX_AGE is not set # WHEN the choice is set to None - max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=None) + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=None) # THEN the result must be the default value (DEFAULT_MAX_AGE_SECS) assert max_age == int(DEFAULT_MAX_AGE_SECS) @@ -131,10 +131,10 @@ def test_resolve_max_age_with_default_value(): def test_resolve_max_age_env_var_wins_over_default_value(monkeypatch: pytest.MonkeyPatch): # GIVEN POWERTOOLS_PARAMETERS_MAX_AGE environment variable is set - monkeypatch.setenv(constants.PARAMETERS_MAX_AGE, "20") + monkeypatch.setenv(constants.PARAMETERS_MAX_AGE_ENV, "20") # WHEN the choice is set to None - max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE, DEFAULT_MAX_AGE_SECS), choice=None) + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=None) # THEN the result must be the environment variable value assert max_age == 20