11// Copyright (c) Microsoft Corporation.
22// Licensed under the MIT license.
33
4- import { AppConfigurationClient , ConfigurationSetting , ConfigurationSettingId , GetConfigurationSettingOptions , GetConfigurationSettingResponse , ListConfigurationSettingsOptions , featureFlagPrefix , isFeatureFlag } from "@azure/app-configuration" ;
4+ import { AppConfigurationClient , ConfigurationSetting , ConfigurationSettingId , GetConfigurationSettingOptions , GetConfigurationSettingResponse , ListConfigurationSettingsOptions , featureFlagPrefix , isFeatureFlag , isSecretReference } from "@azure/app-configuration" ;
55import { isRestError } from "@azure/core-rest-pipeline" ;
66import { AzureAppConfiguration , ConfigurationObjectConstructionOptions } from "./AzureAppConfiguration.js" ;
77import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions.js" ;
88import { IKeyValueAdapter } from "./IKeyValueAdapter.js" ;
99import { JsonKeyValueAdapter } from "./JsonKeyValueAdapter.js" ;
1010import { DEFAULT_STARTUP_TIMEOUT_IN_MS } from "./StartupOptions.js" ;
1111import { DEFAULT_REFRESH_INTERVAL_IN_MS , MIN_REFRESH_INTERVAL_IN_MS } from "./refresh/refreshOptions.js" ;
12+ import { MIN_SECRET_REFRESH_INTERVAL_IN_MS } from "./keyvault/KeyVaultOptions.js" ;
1213import { Disposable } from "./common/disposable.js" ;
1314import { base64Helper , jsonSorter } from "./common/utils.js" ;
1415import {
@@ -87,6 +88,10 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
8788 #ffRefreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS ;
8889 #ffRefreshTimer: RefreshTimer ;
8990
91+ // Key Vault references
92+ #secretRefreshEnabled: boolean = false ;
93+ #secretReferences: ConfigurationSetting [ ] = [ ] ; // cached key vault references
94+ #secretRefreshTimer: RefreshTimer ;
9095 /**
9196 * Selectors of key-values obtained from @see AzureAppConfigurationOptions.selectors
9297 */
@@ -139,9 +144,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
139144 if ( refreshIntervalInMs !== undefined ) {
140145 if ( refreshIntervalInMs < MIN_REFRESH_INTERVAL_IN_MS ) {
141146 throw new RangeError ( `The refresh interval cannot be less than ${ MIN_REFRESH_INTERVAL_IN_MS } milliseconds.` ) ;
142- } else {
143- this . #kvRefreshInterval = refreshIntervalInMs ;
144147 }
148+ this . #kvRefreshInterval = refreshIntervalInMs ;
145149 }
146150 this . #kvRefreshTimer = new RefreshTimer ( this . #kvRefreshInterval) ;
147151 }
@@ -157,16 +161,25 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
157161 if ( refreshIntervalInMs !== undefined ) {
158162 if ( refreshIntervalInMs < MIN_REFRESH_INTERVAL_IN_MS ) {
159163 throw new RangeError ( `The feature flag refresh interval cannot be less than ${ MIN_REFRESH_INTERVAL_IN_MS } milliseconds.` ) ;
160- } else {
161- this . #ffRefreshInterval = refreshIntervalInMs ;
162164 }
165+ this . #ffRefreshInterval = refreshIntervalInMs ;
163166 }
164167
165168 this . #ffRefreshTimer = new RefreshTimer ( this . #ffRefreshInterval) ;
166169 }
167170 }
168171
169- this . #adapters. push ( new AzureKeyVaultKeyValueAdapter ( options ?. keyVaultOptions ) ) ;
172+ if ( options ?. keyVaultOptions ) {
173+ const { secretRefreshIntervalInMs } = options . keyVaultOptions ;
174+ if ( secretRefreshIntervalInMs !== undefined ) {
175+ if ( secretRefreshIntervalInMs < MIN_SECRET_REFRESH_INTERVAL_IN_MS ) {
176+ throw new RangeError ( `The key vault secret refresh interval cannot be less than ${ MIN_REFRESH_INTERVAL_IN_MS } milliseconds.` ) ;
177+ }
178+ this . #secretRefreshEnabled = true ;
179+ this . #secretRefreshTimer = new RefreshTimer ( secretRefreshIntervalInMs ) ;
180+ }
181+ }
182+ this . #adapters. push ( new AzureKeyVaultKeyValueAdapter ( options ?. keyVaultOptions , this . #secretRefreshTimer) ) ;
170183 this . #adapters. push ( new JsonKeyValueAdapter ( ) ) ;
171184 }
172185
@@ -305,8 +318,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
305318 * Refreshes the configuration.
306319 */
307320 async refresh ( ) : Promise < void > {
308- if ( ! this . #refreshEnabled && ! this . #featureFlagRefreshEnabled) {
309- throw new OperationError ( "Refresh is not enabled for key-values or feature flags." ) ;
321+ if ( ! this . #refreshEnabled && ! this . #featureFlagRefreshEnabled && ! this . #secretRefreshEnabled ) {
322+ throw new OperationError ( "Refresh is not enabled for key-values, key vault secrets or feature flags." ) ;
310323 }
311324
312325 if ( this . #refreshInProgress) {
@@ -324,8 +337,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
324337 * Registers a callback function to be called when the configuration is refreshed.
325338 */
326339 onRefresh ( listener : ( ) => any , thisArg ?: any ) : Disposable {
327- if ( ! this . #refreshEnabled && ! this . #featureFlagRefreshEnabled) {
328- throw new OperationError ( "Refresh is not enabled for key-values or feature flags." ) ;
340+ if ( ! this . #refreshEnabled && ! this . #featureFlagRefreshEnabled && ! this . #secretRefreshEnabled ) {
341+ throw new OperationError ( "Refresh is not enabled for key-values, key vault secrets or feature flags." ) ;
329342 }
330343
331344 const boundedListener = listener . bind ( thisArg ) ;
@@ -399,6 +412,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
399412 if ( this . #featureFlagRefreshEnabled) {
400413 refreshTasks . push ( this . #refreshFeatureFlags( ) ) ;
401414 }
415+ if ( this . #secretRefreshEnabled) {
416+ refreshTasks . push ( this . #refreshSecrets( ) ) ;
417+ }
402418
403419 // wait until all tasks are either resolved or rejected
404420 const results = await Promise . allSettled ( refreshTasks ) ;
@@ -481,8 +497,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
481497 await this . #updateWatchedKeyValuesEtag( loadedSettings ) ;
482498 }
483499
484- // adapt configuration settings to key-values
500+ // clear all cached key vault references
501+ this . #secretReferences = [ ] ;
485502 for ( const setting of loadedSettings ) {
503+ if ( this . #secretRefreshEnabled && isSecretReference ( setting ) ) {
504+ this . #secretReferences. push ( setting ) ;
505+ }
486506 const [ key , value ] = await this . #processKeyValues( setting ) ;
487507 keyValues . push ( [ key , value ] ) ;
488508 }
@@ -597,6 +617,21 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
597617 return Promise . resolve ( needRefresh ) ;
598618 }
599619
620+ async #refreshSecrets( ) : Promise < boolean > {
621+ // if still within refresh interval/backoff, return
622+ if ( ! this . #secretRefreshTimer. canRefresh ( ) ) {
623+ return Promise . resolve ( false ) ;
624+ }
625+
626+ for ( const setting of this . #secretReferences) {
627+ const [ key , value ] = await this . #processKeyValues( setting ) ;
628+ this . #configMap. set ( key , value ) ;
629+ }
630+
631+ this . #secretRefreshTimer. reset ( ) ;
632+ return Promise . resolve ( true ) ;
633+ }
634+
600635 /**
601636 * Checks whether the key-value collection has changed.
602637 * @param selectors - The @see PagedSettingSelector of the kev-value collection.
0 commit comments