diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 580b059082aa..6aec03eb61cf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -87,7 +87,7 @@ # ServiceLabel: %App Configuration Provider # PRLabel: %App Configuration Provider -/sdk/appconfiguration/azure-appconfiguration-provider/ @mametcal @albertofori @avanigupta @mrm9084 +/sdk/appconfiguration/azure-appconfiguration-provider/ @albertofori @avanigupta @mrm9084 @rossgrambo # ServiceLabel: %Attestation # PRLabel: %Attestation diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/CHANGELOG.md b/sdk/appconfiguration/azure-appconfiguration-provider/CHANGELOG.md index 1126761c4a42..2d48c7dd6cd6 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/CHANGELOG.md +++ b/sdk/appconfiguration/azure-appconfiguration-provider/CHANGELOG.md @@ -1,10 +1,33 @@ # Release History -## 2.0.0b2 (2024-09-12) +## 2.0.0b4 (Unreleased) + +### Features Added + +### Breaking Changes + +### Bugs Fixed + +### Other Changes + +## 2.0.0b3 (2024-11-13) + +### Breaking Changes + +* Allocation Id value changed so other providers can match the value. + +## 2.0.0b2 (2024-10-11) + +### Feature Added + +* Added AllocationId to the feature flag telemetry metadata when the feature flag has telemetry enabled. ### Bugs Fixed -* Fixing ETag to be "ETag" instead of "etag" in feature flag telemetry. +* Fixed a number of cases where snake case was used instead of pascal case for feature flag telemetry. + * etag -> ETag + * feature_flag_reference -> FeatureFlagReference + * feature_flag_id -> FeatureFlagId ## 2.0.0b1 (2024-09-11) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py index 9e028d695e2c..e9ae3b3b65f7 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py @@ -163,6 +163,7 @@ def load_feature_flags( loaded_feature_flags = [] # Needs to be removed unknown keyword argument for list_configuration_settings kwargs.pop("sentinel_keys", None) + endpoint = self._client._impl._config.endpoint # pylint: disable=protected-access filters_used: Dict[str, bool] = {} for select in feature_flag_selectors: feature_flags = self._client.list_configuration_settings( @@ -176,6 +177,7 @@ def load_feature_flags( feature_flag_value = json.loads(feature_flag.value) + self._feature_flag_telemetry(endpoint, feature_flag, feature_flag_value) self._feature_flag_appconfig_telemetry(feature_flag, filters_used) loaded_feature_flags.append(feature_flag_value) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager_base.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager_base.py index 835a5ad02bf5..159edcecf95b 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager_base.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager_base.py @@ -8,7 +8,7 @@ import hashlib import base64 from dataclasses import dataclass -from typing import Dict, List +from typing import Dict, List, Mapping, Any from azure.appconfiguration import ( # type:ignore # pylint:disable=no-name-in-module FeatureFlagConfigurationSetting, ) @@ -20,11 +20,18 @@ PERCENTAGE_FILTER_KEY, TIME_WINDOW_FILTER_KEY, TARGETING_FILTER_KEY, + TELEMETRY_KEY, + METADATA_KEY, + ETAG_KEY, + FEATURE_FLAG_REFERENCE_KEY, + FEATURE_FLAG_ID_KEY, ) FALLBACK_CLIENT_REFRESH_EXPIRED_INTERVAL = 3600 # 1 hour in seconds MINIMAL_CLIENT_REFRESH_INTERVAL = 30 # 30 seconds +JSON = Mapping[str, Any] + @dataclass class _ConfigurationClientWrapperBase: @@ -36,10 +43,28 @@ def _calculate_feature_id(key, label): if label and not label.isspace(): basic_value += f"{label}" feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() - encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) - encoded_flag = encoded_flag.replace(b"+", b"-").replace(b"/", b"_") + encoded_flag = base64.urlsafe_b64encode(feature_flag_id_hash_bytes) return encoded_flag[: encoded_flag.find(b"=")] + def _feature_flag_telemetry( + self, endpoint: str, feature_flag: FeatureFlagConfigurationSetting, feature_flag_value: Dict + ): + if TELEMETRY_KEY in feature_flag_value: + if METADATA_KEY not in feature_flag_value[TELEMETRY_KEY]: + feature_flag_value[TELEMETRY_KEY][METADATA_KEY] = {} + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][ETAG_KEY] = feature_flag.etag + + if not endpoint.endswith("/"): + endpoint += "/" + feature_flag_reference = f"{endpoint}kv/{feature_flag.key}" + if feature_flag.label and not feature_flag.label.isspace(): + feature_flag_reference += f"?label={feature_flag.label}" + if feature_flag_value[TELEMETRY_KEY].get("enabled"): + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_ID_KEY] = self._calculate_feature_id( + feature_flag.key, feature_flag.label + ) + def _feature_flag_appconfig_telemetry( self, feature_flag: FeatureFlagConfigurationSetting, filters_used: Dict[str, bool] ): diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py index 4e2c0db60cf4..9f439907c9a5 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py @@ -19,10 +19,11 @@ TELEMETRY_KEY = "telemetry" METADATA_KEY = "metadata" -ETAG_KEY = "ETag" +ETAG_KEY = "ETag" FEATURE_FLAG_REFERENCE_KEY = "FeatureFlagReference" FEATURE_FLAG_ID_KEY = "FeatureFlagId" + PERCENTAGE_FILTER_NAMES = ["Percentage", "PercentageFilter", "Microsoft.Percentage", "Microsoft.PercentageFilter"] TIME_WINDOW_FILTER_NAMES = ["TimeWindow", "TimeWindowFilter", "Microsoft.TimeWindow", "Microsoft.TimeWindowFilter"] TARGETING_FILTER_NAMES = ["Targeting", "TargetingFilter", "Microsoft.Targeting", "Microsoft.TargetingFilter"] diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_version.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_version.py index 99e8b1d451ca..783d6dcfe961 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_version.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_version.py @@ -4,4 +4,4 @@ # license information. # ------------------------------------------------------------------------- -VERSION = "2.0.0b2" +VERSION = "2.0.0b4" diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_async_client_manager.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_async_client_manager.py index 8f236695536a..6ba0fa43d1e9 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_async_client_manager.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_async_client_manager.py @@ -165,6 +165,7 @@ async def load_feature_flags( loaded_feature_flags = [] # Needs to be removed unknown keyword argument for list_configuration_settings kwargs.pop("sentinel_keys", None) + endpoint = self._client._impl._config.endpoint # pylint: disable=protected-access filters_used: Dict[str, bool] = {} for select in feature_flag_selectors: feature_flags = self._client.list_configuration_settings( @@ -178,6 +179,7 @@ async def load_feature_flags( feature_flag_value = json.loads(feature_flag.value) + self._feature_flag_telemetry(endpoint, feature_flag, feature_flag_value) self._feature_flag_appconfig_telemetry(feature_flag, filters_used) loaded_feature_flags.append(feature_flag_value)