2222 SecretReferenceConfigurationSetting ,
2323)
2424from azure .core .exceptions import AzureError , HttpResponseError
25- from azure .keyvault .secrets import SecretClient , KeyVaultSecretIdentifier
2625from ._models import AzureAppConfigurationKeyVaultOptions , SettingSelector
26+ from ._key_vault ._secret_provider import SecretProvider
2727from ._constants import (
2828 FEATURE_MANAGEMENT_KEY ,
2929 FEATURE_FLAG_KEY ,
3030)
3131from ._azureappconfigurationproviderbase import (
3232 AzureAppConfigurationProviderBase ,
33+ update_correlation_context_header ,
3334 delay_failure ,
3435 sdk_allowed_kwargs ,
35- update_correlation_context_header ,
3636)
3737from ._client_manager import ConfigurationClientManager , _ConfigurationClientWrapper as ConfigurationClient
3838from ._user_agent import USER_AGENT
@@ -246,46 +246,6 @@ def _buildprovider(
246246 return AzureAppConfigurationProvider (** kwargs )
247247
248248
249- def _resolve_keyvault_reference (
250- config : "SecretReferenceConfigurationSetting" , provider : "AzureAppConfigurationProvider"
251- ) -> str :
252- # pylint:disable=protected-access
253- if not (provider ._keyvault_credential or provider ._keyvault_client_configs or provider ._secret_resolver ):
254- raise ValueError (
255- """
256- Either a credential to Key Vault, custom Key Vault client, or a secret resolver must be set to resolve Key
257- Vault references.
258- """
259- )
260-
261- if config .secret_id is None :
262- raise ValueError ("Key Vault reference must have a uri value." )
263-
264- keyvault_identifier = KeyVaultSecretIdentifier (config .secret_id )
265-
266- vault_url = keyvault_identifier .vault_url + "/"
267-
268- # pylint:disable=protected-access
269- referenced_client = provider ._secret_clients .get (vault_url , None )
270-
271- vault_config = provider ._keyvault_client_configs .get (vault_url , {})
272- credential = vault_config .pop ("credential" , provider ._keyvault_credential )
273-
274- if referenced_client is None and credential is not None :
275- referenced_client = SecretClient (vault_url = vault_url , credential = credential , ** vault_config )
276- provider ._secret_clients [vault_url ] = referenced_client
277-
278- if referenced_client :
279- secret_value = referenced_client .get_secret (keyvault_identifier .name , version = keyvault_identifier .version ).value
280- if secret_value is not None :
281- return secret_value
282-
283- if provider ._secret_resolver :
284- return provider ._secret_resolver (config .secret_id )
285-
286- raise ValueError ("No Secret Client found for Key Vault reference %s" % (vault_url ))
287-
288-
289249class AzureAppConfigurationProvider (AzureAppConfigurationProviderBase ): # pylint: disable=too-many-instance-attributes
290250 """
291251 Provides a dictionary-like interface to Azure App Configuration settings. Enables loading of sets of configuration
@@ -309,8 +269,8 @@ def __init__(self, **kwargs: Any) -> None:
309269 max_backoff : int = min (kwargs .pop ("max_backoff" , 600 ), interval )
310270
311271 self ._replica_client_manager = ConfigurationClientManager (
312- connection_string = kwargs .pop ("connection_string" , None ),
313- endpoint = kwargs .pop ("endpoint" , None ),
272+ connection_string = kwargs .pop ("connection_string" ),
273+ endpoint = kwargs .pop ("endpoint" ),
314274 credential = kwargs .pop ("credential" , None ),
315275 user_agent = user_agent ,
316276 retry_total = kwargs .pop ("retry_total" , 2 ),
@@ -321,7 +281,7 @@ def __init__(self, **kwargs: Any) -> None:
321281 load_balancing_enabled = kwargs .pop ("load_balancing_enabled" , False ),
322282 ** kwargs ,
323283 )
324- self ._secret_clients : Dict [ str , SecretClient ] = {}
284+ self ._secret_provider = SecretProvider ( ** kwargs )
325285 self ._on_refresh_success : Optional [Callable ] = kwargs .pop ("on_refresh_success" , None )
326286 self ._on_refresh_error : Optional [Callable [[Exception ], None ]] = kwargs .pop ("on_refresh_error" , None )
327287 self ._configuration_mapper : Optional [Callable ] = kwargs .pop ("configuration_mapper" , None )
@@ -334,7 +294,7 @@ def _attempt_refresh(self, client: ConfigurationClient, replica_count: int, is_f
334294 replica_count ,
335295 self ._feature_flag_enabled ,
336296 self ._feature_filter_usage ,
337- self ._uses_key_vault ,
297+ self ._secret_provider . uses_key_vault ,
338298 self ._uses_load_balancing ,
339299 is_failover_request ,
340300 self ._uses_ai_configuration ,
@@ -424,6 +384,11 @@ def refresh(self, **kwargs) -> None:
424384 exception : Optional [Exception ] = None
425385 is_failover_request = False
426386 try :
387+ if (
388+ self ._secret_provider .secret_refresh_timer
389+ and self ._secret_provider .secret_refresh_timer .needs_refresh ()
390+ ):
391+ self ._dict .update (self ._secret_provider .refresh_secrets ())
427392 self ._replica_client_manager .refresh_clients ()
428393 self ._replica_client_manager .find_active_clients ()
429394 replica_count = self ._replica_client_manager .get_client_count () - 1
@@ -465,7 +430,7 @@ def _load_all(self, **kwargs):
465430 replica_count ,
466431 self ._feature_flag_enabled ,
467432 self ._feature_filter_usage ,
468- self ._uses_key_vault ,
433+ self ._secret_provider . uses_key_vault ,
469434 self ._uses_load_balancing ,
470435 is_failover_request ,
471436 self ._uses_ai_configuration ,
@@ -520,9 +485,13 @@ def _load_all(self, **kwargs):
520485 raise exception
521486
522487 def _process_configurations (self , configuration_settings : List [ConfigurationSetting ]) -> Dict [str , Any ]:
488+ # configuration_settings can contain duplicate keys, but they are in priority order, i.e. later settings take
489+ # precedence. Only process the settings with the highest priority (i.e. the last one in the list).
490+ unique_settings = self ._deduplicate_settings (configuration_settings )
491+
523492 configuration_settings_processed = {}
524493 feature_flags_processed = []
525- for settings in configuration_settings :
494+ for settings in unique_settings . values () :
526495 if self ._configuration_mapper :
527496 # If a map function is provided, use it to process the configuration setting
528497 self ._configuration_mapper (settings )
@@ -541,7 +510,7 @@ def _process_configurations(self, configuration_settings: List[ConfigurationSett
541510
542511 def _process_key_value (self , config : ConfigurationSetting ) -> Any :
543512 if isinstance (config , SecretReferenceConfigurationSetting ):
544- return _resolve_keyvault_reference (config , self )
513+ return self . _secret_provider . resolve_keyvault_reference (config )
545514 # Use the base class helper method for non-KeyVault processing
546515 return self ._process_key_value_base (config )
547516
@@ -558,17 +527,14 @@ def close(self) -> None:
558527 """
559528 Closes the connection to Azure App Configuration.
560529 """
561- for client in self ._secret_clients .values ():
562- client .close ()
530+ self ._secret_provider .close ()
563531 self ._replica_client_manager .close ()
564532
565533 def __enter__ (self ) -> "AzureAppConfigurationProvider" :
566534 self ._replica_client_manager .__enter__ ()
567- for client in self ._secret_clients .values ():
568- client .__enter__ ()
535+ self ._secret_provider .__enter__ ()
569536 return self
570537
571538 def __exit__ (self , * args ) -> None :
572539 self ._replica_client_manager .__exit__ ()
573- for client in self ._secret_clients .values ():
574- client .__exit__ ()
540+ self ._secret_provider .__exit__ ()
0 commit comments