Skip to content

Commit 4b76eca

Browse files
feat(parameters): migrate AppConfig to new APIs due to API deprecation (#1553)
Co-authored-by: Heitor Lessa <[email protected]>
1 parent be64e4e commit 4b76eca

File tree

13 files changed

+467
-83
lines changed

13 files changed

+467
-83
lines changed

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

+27-19
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,17 @@
55

66
import os
77
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
8-
from uuid import uuid4
98

109
import boto3
1110
from botocore.config import Config
1211

1312
if TYPE_CHECKING:
14-
from mypy_boto3_appconfig import AppConfigClient
13+
from mypy_boto3_appconfigdata import AppConfigDataClient
1514

1615
from ...shared import constants
1716
from ...shared.functions import resolve_env_var_choice
1817
from .base import DEFAULT_MAX_AGE_SECS, DEFAULT_PROVIDERS, BaseProvider
1918

20-
CLIENT_ID = str(uuid4())
21-
2219

2320
class AppConfigProvider(BaseProvider):
2421
"""
@@ -34,8 +31,8 @@ class AppConfigProvider(BaseProvider):
3431
Botocore configuration to pass during client initialization
3532
boto3_session : boto3.session.Session, optional
3633
Boto3 session to create a boto3_client from
37-
boto3_client: AppConfigClient, optional
38-
Boto3 AppConfig Client to use, boto3_session will be ignored if both are provided
34+
boto3_client: AppConfigDataClient, optional
35+
Boto3 AppConfigData Client to use, boto3_session will be ignored if both are provided
3936
4037
Example
4138
-------
@@ -73,16 +70,16 @@ def __init__(
7370
application: Optional[str] = None,
7471
config: Optional[Config] = None,
7572
boto3_session: Optional[boto3.session.Session] = None,
76-
boto3_client: Optional["AppConfigClient"] = None,
73+
boto3_client: Optional["AppConfigDataClient"] = None,
7774
):
7875
"""
7976
Initialize the App Config client
8077
"""
8178

8279
super().__init__()
8380

84-
self.client: "AppConfigClient" = self._build_boto3_client(
85-
service_name="appconfig", client=boto3_client, session=boto3_session, config=config
81+
self.client: "AppConfigDataClient" = self._build_boto3_client(
82+
service_name="appconfigdata", client=boto3_client, session=boto3_session, config=config
8683
)
8784

8885
self.application = resolve_env_var_choice(
@@ -91,6 +88,9 @@ def __init__(
9188
self.environment = environment
9289
self.current_version = ""
9390

91+
self._next_token = "" # nosec - token for get_latest_configuration executions
92+
self.last_returned_value = ""
93+
9494
def _get(self, name: str, **sdk_options) -> str:
9595
"""
9696
Retrieve a parameter value from AWS App config.
@@ -100,16 +100,26 @@ def _get(self, name: str, **sdk_options) -> str:
100100
name: str
101101
Name of the configuration
102102
sdk_options: dict, optional
103-
Dictionary of options that will be passed to the client's get_configuration API call
103+
SDK options to propagate to `start_configuration_session` API call
104104
"""
105+
if not self._next_token:
106+
sdk_options["ConfigurationProfileIdentifier"] = name
107+
sdk_options["ApplicationIdentifier"] = self.application
108+
sdk_options["EnvironmentIdentifier"] = self.environment
109+
response_configuration = self.client.start_configuration_session(**sdk_options)
110+
self._next_token = response_configuration["InitialConfigurationToken"]
105111

106-
sdk_options["Configuration"] = name
107-
sdk_options["Application"] = self.application
108-
sdk_options["Environment"] = self.environment
109-
sdk_options["ClientId"] = CLIENT_ID
112+
# The new AppConfig APIs require two API calls to return the configuration
113+
# First we start the session and after that we retrieve the configuration
114+
# We need to store the token to use in the next execution
115+
response = self.client.get_latest_configuration(ConfigurationToken=self._next_token)
116+
return_value = response["Configuration"].read()
117+
self._next_token = response["NextPollConfigurationToken"]
110118

111-
response = self.client.get_configuration(**sdk_options)
112-
return response["Content"].read() # read() of botocore.response.StreamingBody
119+
if return_value:
120+
self.last_returned_value = return_value
121+
122+
return self.last_returned_value
113123

114124
def _get_multiple(self, path: str, **sdk_options) -> Dict[str, str]:
115125
"""
@@ -145,7 +155,7 @@ def get_app_config(
145155
max_age: int
146156
Maximum age of the cached value
147157
sdk_options: dict, optional
148-
Dictionary of options that will be passed to the boto client get_configuration API call
158+
SDK options to propagate to `start_configuration_session` API call
149159
150160
Raises
151161
------
@@ -180,8 +190,6 @@ def get_app_config(
180190
if "appconfig" not in DEFAULT_PROVIDERS:
181191
DEFAULT_PROVIDERS["appconfig"] = AppConfigProvider(environment=environment, application=application)
182192

183-
sdk_options["ClientId"] = CLIENT_ID
184-
185193
return DEFAULT_PROVIDERS["appconfig"].get(
186194
name, max_age=max_age, transform=transform, force_fetch=force_fetch, **sdk_options
187195
)

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from .exceptions import GetParameterError, TransformParameterError
1616

1717
if TYPE_CHECKING:
18-
from mypy_boto3_appconfig import AppConfigClient
18+
from mypy_boto3_appconfigdata import AppConfigDataClient
1919
from mypy_boto3_dynamodb import DynamoDBServiceResource
2020
from mypy_boto3_secretsmanager import SecretsManagerClient
2121
from mypy_boto3_ssm import SSMClient
@@ -28,7 +28,7 @@
2828
TRANSFORM_METHOD_JSON = "json"
2929
TRANSFORM_METHOD_BINARY = "binary"
3030
SUPPORTED_TRANSFORM_METHODS = [TRANSFORM_METHOD_JSON, TRANSFORM_METHOD_BINARY]
31-
ParameterClients = Union["AppConfigClient", "SecretsManagerClient", "SSMClient"]
31+
ParameterClients = Union["AppConfigDataClient", "SecretsManagerClient", "SSMClient"]
3232

3333

3434
class BaseProvider(ABC):

Diff for: docs/upgrade.md

+7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Changes at a glance:
1313
* The API for **event handler's `Response`** has minor changes to support multi value headers and cookies.
1414
* The **legacy SQS batch processor** was removed.
1515
* The **Idempotency key** format changed slightly, invalidating all the existing cached results.
16+
* The **Feature Flags and AppConfig Parameter utility** API calls have changed and you must update your IAM permissions.
1617

1718
???+ important
1819
Powertools for Python v2 drops suport for Python 3.6, following the Python 3.6 End-Of-Life (EOL) reached on December 23, 2021.
@@ -154,3 +155,9 @@ Prior to this change, the Idempotency key was generated using only the caller fu
154155
After this change, the key is generated using the `module name` + `qualified function name` + `idempotency key` (e.g: `app.classExample.function#app.handler#282e83393862a613b612c00283fef4c8`).
155156
156157
Using qualified names prevents distinct functions with the same name to contend for the same Idempotency key.
158+
159+
## Feature Flags and AppConfig Parameter utility
160+
161+
AWS AppConfig deprecated the current API (GetConfiguration) - [more details here](https://github.com/awslabs/aws-lambda-powertools-python/issues/1506#issuecomment-1266645884).
162+
163+
You must update your IAM permissions to allow `appconfig:GetLatestConfiguration` and `appconfig:StartConfigurationSession`. There are no code changes required.

Diff for: docs/utilities/feature_flags.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ title: Feature flags
33
description: Utility
44
---
55

6-
???+ note
7-
This is currently in Beta, as we might change Store parameters in the next release.
8-
96
The feature flags utility provides a simple rule engine to define when one or multiple features should be enabled depending on the input.
107

8+
???+ info
9+
We currently only support AppConfig using [freeform configuration profile](https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-creating-configuration-and-profile.html#appconfig-creating-configuration-and-profile-free-form-configurations).
10+
1111
## Terminology
1212

1313
Feature flags are used to modify behaviour without changing the application's code. These flags can be **static** or **dynamic**.
@@ -28,6 +28,9 @@ If you want to learn more about feature flags, their variations and trade-offs,
2828
* [AWS Lambda Feature Toggles Made Simple - Ran Isenberg](https://isenberg-ran.medium.com/aws-lambda-feature-toggles-made-simple-580b0c444233)
2929
* [Feature Flags Getting Started - CloudBees](https://www.cloudbees.com/blog/ultimate-feature-flag-guide)
3030

31+
???+ note
32+
AWS AppConfig requires two API calls to fetch configuration for the first time. You can improve latency by consolidating your feature settings in a single [Configuration](https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-creating-configuration-and-profile.html).
33+
3134
## Key features
3235

3336
* Define simple feature flags to dynamically decide when to enable a feature
@@ -38,7 +41,7 @@ If you want to learn more about feature flags, their variations and trade-offs,
3841

3942
### IAM Permissions
4043

41-
Your Lambda function must have `appconfig:GetConfiguration` IAM permission in order to fetch configuration from AWS AppConfig.
44+
Your Lambda function IAM Role must have `appconfig:GetLatestConfiguration` and `appconfig:StartConfigurationSession` IAM permissions before using this feature.
4245

4346
### Required resources
4447

Diff for: docs/utilities/parameters.md

+9-8
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@ This utility requires additional permissions to work as expected.
2424
???+ note
2525
Different parameter providers require different permissions.
2626

27-
| Provider | Function/Method | IAM Permission |
28-
| ------------------- | ---------------------------------------------------- | ------------------------------- |
29-
| SSM Parameter Store | `get_parameter`, `SSMProvider.get` | `ssm:GetParameter` |
30-
| SSM Parameter Store | `get_parameters`, `SSMProvider.get_multiple` | `ssm:GetParametersByPath` |
31-
| Secrets Manager | `get_secret`, `SecretsManager.get` | `secretsmanager:GetSecretValue` |
32-
| DynamoDB | `DynamoDBProvider.get` | `dynamodb:GetItem` |
33-
| DynamoDB | `DynamoDBProvider.get_multiple` | `dynamodb:Query` |
34-
| App Config | `AppConfigProvider.get_app_config`, `get_app_config` | `appconfig:GetConfiguration` |
27+
| Provider | Function/Method | IAM Permission |
28+
| ------------------- | -----------------------------------------------------------------| -----------------------------------------------------------------------------|
29+
| SSM Parameter Store | `get_parameter`, `SSMProvider.get` | `ssm:GetParameter` |
30+
| SSM Parameter Store | `get_parameters`, `SSMProvider.get_multiple` | `ssm:GetParametersByPath` |
31+
| SSM Parameter Store | If using `decrypt=True` | You must add an additional permission `kms:Decrypt` |
32+
| Secrets Manager | `get_secret`, `SecretsManager.get` | `secretsmanager:GetSecretValue` |
33+
| DynamoDB | `DynamoDBProvider.get` | `dynamodb:GetItem` |
34+
| DynamoDB | `DynamoDBProvider.get_multiple` | `dynamodb:Query` |
35+
| App Config | `get_app_config`, `AppConfigProvider.get_app_config` | `appconfig:GetLatestConfiguration` and `appconfig:StartConfigurationSession` |
3536

3637
### Fetching parameters
3738

0 commit comments

Comments
 (0)