Skip to content

Commit 1df31c1

Browse files
author
Ran Isenberg
committed
feat: Add AppConfig parameter provider
1 parent 06d58d4 commit 1df31c1

File tree

5 files changed

+325
-8
lines changed

5 files changed

+325
-8
lines changed

Diff for: aws_lambda_powertools/utilities/parameters/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,22 @@
44
Parameter retrieval and caching utility
55
"""
66

7+
from .appconfig import AppConfigProvider, get_app_config
78
from .base import BaseProvider
89
from .dynamodb import DynamoDBProvider
910
from .exceptions import GetParameterError, TransformParameterError
1011
from .secrets import SecretsProvider, get_secret
1112
from .ssm import SSMProvider, get_parameter, get_parameters
1213

1314
__all__ = [
15+
"AppConfigProvider",
1416
"BaseProvider",
1517
"GetParameterError",
1618
"DynamoDBProvider",
1719
"SecretsProvider",
1820
"SSMProvider",
1921
"TransformParameterError",
22+
"get_app_config",
2023
"get_parameter",
2124
"get_parameters",
2225
"get_secret",
+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
"""
2+
AWS App Config configuration retrieval and caching utility
3+
"""
4+
5+
6+
import os
7+
from typing import Dict, Optional, Union
8+
from uuid import uuid4
9+
10+
import boto3
11+
from botocore.config import Config
12+
13+
from .base import DEFAULT_PROVIDERS, BaseProvider
14+
15+
CLIENT_ID = str(uuid4())
16+
17+
18+
class AppConfigProvider(BaseProvider):
19+
"""
20+
AWS App Config Provider
21+
22+
Parameters
23+
----------
24+
environment: str
25+
Environment of the configuration to pass during client initialization
26+
application: str, optional
27+
Application of the configuration to pass during client initialization
28+
config: botocore.config.Config, optional
29+
Botocore configuration to pass during client initialization
30+
31+
Example
32+
-------
33+
**Retrieves a configuration value from App Config**
34+
35+
>>> from aws_lambda_powertools.utilities.parameters import AppConfigProvider
36+
>>> appconf_provider = parameters.AppConfigProvider(environment="my_env", application="my_app")
37+
>>>
38+
>>> value : bytes = appconf_provider.get("my_conf")
39+
>>>
40+
>>> print(value)
41+
My configuration value
42+
43+
**Retrieves a configuration value from App Config in another AWS region**
44+
45+
>>> from botocore.config import Config
46+
>>> from aws_lambda_powertools.utilities.parameters import AppConfigProvider
47+
>>>
48+
>>> config = Config(region_name="us-west-1")
49+
>>> appconf_provider = parameters.AppConfigProvider(environment="my_env", application="my_app", config=config)
50+
>>>
51+
>>> value : bytes = appconf_provider.get("my_conf")
52+
>>>
53+
>>> print(value)
54+
My configuration value
55+
56+
**Retrieves a specific version number of configuration value from App Config**
57+
58+
>>> from aws_lambda_powertools.utilities.parameters import AppConfigProvider
59+
>>> appconf_provider = parameters.AppConfigProvider(environment="my_env", application="my_app")
60+
>>>
61+
>>> value = appconf_provider.get("my_config", environment="my_env", application="env", client_conf_version="1")
62+
>>>
63+
>>> print(value)
64+
My configuration value
65+
"""
66+
67+
client = None
68+
69+
def __init__(
70+
self, environment: str, application: Optional[str] = None, config: Optional[Config] = None,
71+
):
72+
"""
73+
Initialize the App Config client
74+
"""
75+
76+
config = config or Config()
77+
self.client = boto3.client("appconfig", config=config)
78+
self.application = application or os.getenv("POWERTOOLS_SERVICE_NAME") or "application_undefined"
79+
self.environment = environment
80+
81+
super().__init__()
82+
83+
def _get(self, name: str, client_conf_version: Optional[str] = None, **sdk_options) -> str:
84+
"""
85+
Retrieve a parameter value from AWS App config.
86+
87+
Parameters
88+
----------
89+
name: str
90+
Name of the configuration
91+
environment: str
92+
Environment of the configuration
93+
client_conf_version: str, optional
94+
Client configuration vrsion to get, otherise gets the latest version
95+
sdk_options: dict, optional
96+
Dictionary of options that will be passed to the Parameter Store get_parameter API call
97+
"""
98+
99+
sdk_options["Configuration"] = name
100+
sdk_options["Application"] = self.application
101+
sdk_options["Environment"] = self.environment
102+
sdk_options["ClientId"] = CLIENT_ID
103+
if client_conf_version is not None:
104+
sdk_options["ClientConfigurationVersion"] = client_conf_version
105+
106+
response = self.client.get_configuration(**sdk_options)
107+
return response["Content"].read() # read() of botocore.response.StreamingBody
108+
109+
def _get_multiple(self, path: str, **sdk_options) -> Dict[str, str]:
110+
"""
111+
Retrieving multiple parameter values is not supported with AWS App Config Provider
112+
"""
113+
raise NotImplementedError()
114+
115+
116+
def get_app_config(
117+
name: str,
118+
environment: str,
119+
application: Optional[str] = None,
120+
transform: Optional[str] = None,
121+
client_conf_version: Optional[str] = None,
122+
**sdk_options
123+
) -> Union[str, list, dict, bytes]:
124+
"""
125+
Retrieve a configuration value from AWS App Config.
126+
127+
Parameters
128+
----------
129+
name: str
130+
Name of the configuration
131+
environment: str
132+
Environment of the configuration
133+
application: str
134+
Application of the configuration
135+
transform: str, optional
136+
Transforms the content from a JSON object ('json') or base64 binary string ('binary')
137+
client_conf_version: str, optional
138+
Client configuration vrsion to get, otherise gets the latest version
139+
sdk_options: dict, optional
140+
Dictionary of options that will be passed to the Parameter Store get_parameter API call
141+
142+
Raises
143+
------
144+
GetParameterError
145+
When the parameter provider fails to retrieve a parameter value for
146+
a given name.
147+
TransformParameterError
148+
When the parameter provider fails to transform a parameter value.
149+
150+
Example
151+
-------
152+
**Retrieves the latest version of configuration value from App Config**
153+
154+
>>> from aws_lambda_powertools.utilities.parameters import get_app_config
155+
>>>
156+
>>> value = get_app_config("my_config", environment="my_env", application="my_env")
157+
>>>
158+
>>> print(value)
159+
My configuration value
160+
161+
**Retrieves a specific version number of configuration value from App Config**
162+
163+
>>> from aws_lambda_powertools.utilities.parameters import get_app_config
164+
>>>
165+
>>> value = get_app_config("my_config", environment="my_env", application="my_env", client_conf_version="1")
166+
>>>
167+
>>> print(value)
168+
My configuration value
169+
170+
**Retrieves a confiugration value and decodes it using a JSON decoder**
171+
172+
>>> from aws_lambda_powertools.utilities.parameters import get_parameter
173+
>>>
174+
>>> value = get_app_config("my_config", environment="my_env", application="my_env", transform='json')
175+
>>>
176+
>>> print(value)
177+
My configuration's JSON value
178+
"""
179+
180+
# Only create the provider if this function is called at least once
181+
if "appconfig" not in DEFAULT_PROVIDERS:
182+
DEFAULT_PROVIDERS["appconfig"] = AppConfigProvider(environment=environment, application=application)
183+
184+
sdk_options["ClientId"] = CLIENT_ID
185+
if client_conf_version is not None:
186+
sdk_options["ClientConfigurationVersion"] = client_conf_version
187+
188+
return DEFAULT_PROVIDERS["appconfig"].get(name, transform=transform, **sdk_options)

Diff for: aws_lambda_powertools/utilities/parameters/base.py

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
DEFAULT_PROVIDERS = {}
1818
TRANSFORM_METHOD_JSON = "json"
1919
TRANSFORM_METHOD_BINARY = "binary"
20+
TRANSFORM_METHOD_YAML = "yaml"
2021
SUPPORTED_TRANSFORM_METHODS = [TRANSFORM_METHOD_JSON, TRANSFORM_METHOD_BINARY]
2122

2223

@@ -230,6 +231,8 @@ def transform_value(value: str, transform: str, raise_on_transform_error: bool =
230231
return json.loads(value)
231232
elif transform == TRANSFORM_METHOD_BINARY:
232233
return base64.b64decode(value)
234+
elif transform == TRANSFORM_METHOD_YAML:
235+
raise NotImplementedError
233236
else:
234237
raise ValueError(f"Invalid transform type '{transform}'")
235238

Diff for: docs/content/utilities/parameters.mdx

+48-8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ SSM Parameter Store | `get_parameters`, `SSMProvider.get_multiple` | `ssm:GetPar
2424
Secrets Manager | `get_secret`, `SecretsManager.get` | `secretsmanager:GetSecretValue`
2525
DynamoDB | `DynamoDBProvider.get` | `dynamodb:GetItem`
2626
DynamoDB | `DynamoDBProvider.get_multiple` | `dynamodb:Query`
27+
App Config | `AppConfigProvider.get_app_config`, `get_app_config` | `appconfig:GetConfiguration`
2728

2829
## SSM Parameter Store
2930

@@ -204,6 +205,44 @@ def handler(event, context):
204205
value = dynamodb_provider.get("my-parameter")
205206
```
206207

208+
## App Config
209+
210+
For configurations stored in App Config, use `get_app_config`.
211+
The following will retrieve the latest version and store it in the cache.
212+
213+
```python:title=appconfig.py
214+
from aws_lambda_powertools.utilities import parameters
215+
216+
def handler(event, context):
217+
# Retrieve a single configuration, latest version
218+
value: bytes = parameters.get_app_config(name="my_configuration", environment="my_env", application="my_app")
219+
```
220+
221+
The following will retrieve the configuration version number 1 and store it in the cache.
222+
```python:title=appconfig.py specific version
223+
def handler(event, context):
224+
# Retrieve a single configuration, latest version
225+
value: bytes = parameters.get_app_config(name="my_configuration", environment="my_env", application="my_app", client_conf_version="1")
226+
```
227+
228+
### AppConfigProvider class
229+
230+
Alternatively, you can use the `AppConfigProvider` class, which give more flexibility, such as the ability to configure the underlying SDK client.
231+
232+
This can be used to retrieve values from other regions, change the retry behavior, etc.
233+
234+
```python:title=appconfig.py
235+
from aws_lambda_powertools.utilities import parameters
236+
from botocore.config import Config
237+
238+
config = Config(region_name="us-west-1")
239+
appconf_provider = parameters.AppConfigProvider(environment="my_env", application="my_app", config=config)
240+
241+
def handler(event, context):
242+
# Retrieve a single secret
243+
value : bytes = appconf_provider.get("my_conf")
244+
```
245+
207246
## Create your own provider
208247

209248
You can create your own custom parameter store provider by inheriting the `BaseProvider` class, and implementing both `_get()` and `_get_multiple()` methods to retrieve a single, or multiple parameters from your custom store.
@@ -334,13 +373,14 @@ def handler(event, context):
334373

335374
Here is the mapping between this utility's functions and methods and the underlying SDK:
336375

337-
| Provider | Function/Method | Client name | Function name |
338-
|---------------------|---------------------------------|-------------|---------------|
339-
| SSM Parameter Store | `get_parameter` | `ssm` | [get_parameter](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.get_parameter) |
340-
| SSM Parameter Store | `get_parameters` | `ssm` | [get_parameters_by_path](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.get_parameters_by_path) |
341-
| SSM Parameter Store | `SSMProvider.get` | `ssm` | [get_parameter](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.get_parameter) |
342-
| SSM Parameter Store | `SSMProvider.get_multiple` | `ssm` | [get_parameters_by_path](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.get_parameters_by_path) |
376+
| Provider | Function/Method | Client name | Function name |
377+
|---------------------|---------------------------------|------------------|----------------|
378+
| SSM Parameter Store | `get_parameter` | `ssm` | [get_parameter](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.get_parameter) |
379+
| SSM Parameter Store | `get_parameters` | `ssm` | [get_parameters_by_path](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.get_parameters_by_path) |
380+
| SSM Parameter Store | `SSMProvider.get` | `ssm` | [get_parameter](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.get_parameter) |
381+
| SSM Parameter Store | `SSMProvider.get_multiple` | `ssm` | [get_parameters_by_path](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.get_parameters_by_path) |
343382
| Secrets Manager | `get_secret` | `secretsmanager` | [get_secret_value](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.get_secret_value) |
344383
| Secrets Manager | `SecretsManager.get` | `secretsmanager` | [get_secret_value](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.get_secret_value) |
345-
| DynamoDB | `DynamoDBProvider.get` | `dynamodb` ([Table resource](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#table)) | [get_item](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Table.get_item)
346-
| DynamoDB | `DynamoDBProvider.get_multiple` | `dynamodb` ([Table resource](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#table)) | [query](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Table.query)
384+
| DynamoDB | `DynamoDBProvider.get` | `dynamodb` | ([Table resource](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#table)) | [get_item](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Table.get_item)
385+
| DynamoDB | `DynamoDBProvider.get_multiple` | `dynamodb` | ([Table resource](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#table)) | [query](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Table.query)
386+
| App Config | `get_app_config` | `appconfig` | [get_configuration](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/appconfig.html#AppConfig.Client.get_configuration) |

0 commit comments

Comments
 (0)