@@ -12,6 +12,7 @@ import {
1212 isFeatureFlag ,
1313 isSecretReference ,
1414 GetSnapshotOptions ,
15+ ListConfigurationSettingsForSnapshotOptions ,
1516 GetSnapshotResponse ,
1617 KnownSnapshotComposition
1718} from "@azure/app-configuration" ;
@@ -81,6 +82,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
8182 #featureFlagTracing: FeatureFlagTracingOptions | undefined ;
8283 #fmVersion: string | undefined ;
8384 #aiConfigurationTracing: AIConfigurationTracingOptions | undefined ;
85+ #useSnapshotReference: boolean = false ;
8486
8587 // Refresh
8688 #refreshInProgress: boolean = false ;
@@ -212,7 +214,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
212214 isFailoverRequest : this . #isFailoverRequest,
213215 featureFlagTracing : this . #featureFlagTracing,
214216 fmVersion : this . #fmVersion,
215- aiConfigurationTracing : this . #aiConfigurationTracing
217+ aiConfigurationTracing : this . #aiConfigurationTracing,
218+ useSnapshotReference : this . #useSnapshotReference
216219 } ;
217220 }
218221
@@ -482,71 +485,59 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
482485 */
483486 async #loadConfigurationSettings( loadFeatureFlag : boolean = false ) : Promise < ConfigurationSetting [ ] > {
484487 const selectors = loadFeatureFlag ? this . #ffSelectors : this . #kvSelectors;
485- const funcToExecute = async ( client ) => {
486- // Use a Map to deduplicate configuration settings by key. When multiple selectors return settings with the same key,
487- // the configuration setting loaded by the later selector in the iteration order will override the one from the earlier selector.
488- const loadedSettings : Map < string , ConfigurationSetting > = new Map < string , ConfigurationSetting > ( ) ;
489- // deep copy selectors to avoid modification if current client fails
490- const selectorsToUpdate : PagedSettingsWatcher [ ] = JSON . parse (
491- JSON . stringify ( selectors )
492- ) ;
493488
494- for ( const selector of selectorsToUpdate ) {
495- if ( selector . snapshotName === undefined ) {
496- const listOptions : ListConfigurationSettingsOptions = {
497- keyFilter : selector . keyFilter ,
498- labelFilter : selector . labelFilter ,
499- tagsFilter : selector . tagFilters
500- } ;
501- const pageWatchers : SettingWatcher [ ] = [ ] ;
502- const pageIterator = listConfigurationSettingsWithTrace (
503- this . #requestTraceOptions,
504- client ,
505- listOptions
506- ) . byPage ( ) ;
507-
508- for await ( const page of pageIterator ) {
509- pageWatchers . push ( { etag : page . etag } ) ;
510- for ( const setting of page . items ) {
511- if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
512- loadedSettings . set ( setting . key , setting ) ;
513- }
514- }
515- }
516- selector . pageWatchers = pageWatchers ;
517- } else { // snapshot selector
518- const snapshot = await this . #getSnapshot( selector . snapshotName ) ;
519- if ( snapshot === undefined ) {
520- throw new InvalidOperationError ( `Could not find snapshot with name ${ selector . snapshotName } .` ) ;
521- }
522- if ( snapshot . compositionType != KnownSnapshotComposition . Key ) {
523- throw new InvalidOperationError ( `Composition type for the selected snapshot with name ${ selector . snapshotName } must be 'key'.` ) ;
524- }
525- const pageIterator = listConfigurationSettingsForSnapshotWithTrace (
526- this . #requestTraceOptions,
527- client ,
528- selector . snapshotName
529- ) . byPage ( ) ;
530-
531- for await ( const page of pageIterator ) {
532- for ( const setting of page . items ) {
533- if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
534- loadedSettings . set ( setting . key , setting ) ;
535- }
489+ // Use a Map to deduplicate configuration settings by key. When multiple selectors return settings with the same key,
490+ // the configuration setting loaded by the later selector in the iteration order will override the one from the earlier selector.
491+ const loadedSettings : Map < string , ConfigurationSetting > = new Map < string , ConfigurationSetting > ( ) ;
492+ // deep copy selectors to avoid modification if current client fails
493+ const selectorsToUpdate : PagedSettingsWatcher [ ] = JSON . parse (
494+ JSON . stringify ( selectors )
495+ ) ;
496+
497+ for ( const selector of selectorsToUpdate ) {
498+ let settings : ConfigurationSetting [ ] = [ ] ;
499+ if ( selector . snapshotName === undefined ) {
500+ const listOptions : ListConfigurationSettingsOptions = {
501+ keyFilter : selector . keyFilter ,
502+ labelFilter : selector . labelFilter ,
503+ tagsFilter : selector . tagFilters
504+ } ;
505+ const { items, pageWatchers } = await this . #listConfigurationSettings( listOptions ) ;
506+ selector . pageWatchers = pageWatchers ;
507+ settings = items ;
508+ } else { // snapshot selector
509+ settings = await this . #loadConfigurationSettingsFromSnapshot( selector . snapshotName ) ;
510+ }
511+
512+ for ( const setting of settings ) {
513+ if ( isSnapshotReference ( setting ) && ! loadFeatureFlag ) {
514+ this . #useSnapshotReference = true ;
515+
516+ // TODO: When SDK supports snapshot reference, use the helper method from SDK.
517+ const snapshotName = parseSnapshotReference ( setting ) . value . snapshotName ;
518+ const settingsFromSnapshot = await this . #loadConfigurationSettingsFromSnapshot( snapshotName ) ;
519+
520+ for ( const snapshotSetting of settingsFromSnapshot ) {
521+ if ( ! isFeatureFlag ( snapshotSetting ) ) {
522+ // Feature flags inside snapshot are ignored. This is consistent the behavior that key value selectors ignore feature flags.
523+ loadedSettings . set ( snapshotSetting . key , snapshotSetting ) ;
536524 }
537525 }
526+ continue ;
538527 }
539- }
540528
541- if ( loadFeatureFlag ) {
542- this . #ffSelectors = selectorsToUpdate ;
543- } else {
544- this . #kvSelectors = selectorsToUpdate ;
529+ if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
530+ loadedSettings . set ( setting . key , setting ) ;
531+ }
545532 }
546- return Array . from ( loadedSettings . values ( ) ) ;
547- } ;
533+ }
548534
549- return await this . #executeWithFailoverPolicy( funcToExecute ) as ConfigurationSetting [ ] ;
535+ if ( loadFeatureFlag ) {
536+ this . #ffSelectors = selectorsToUpdate ;
537+ } else {
538+ this . #kvSelectors = selectorsToUpdate ;
539+ }
540+ return Array . from ( loadedSettings . values ( ) ) ;
550541 }
551542
552543 /**
@@ -595,6 +586,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
595586 }
596587 }
597588
589+ async #loadConfigurationSettingsFromSnapshot( snapshotName : string ) : Promise < ConfigurationSetting [ ] > {
590+ const snapshot = await this . #getSnapshot( snapshotName ) ;
591+ if ( snapshot === undefined ) {
592+ throw new InvalidOperationError ( `Could not find snapshot with name ${ snapshotName } .` ) ;
593+ }
594+ if ( snapshot . compositionType != KnownSnapshotComposition . Key ) {
595+ throw new InvalidOperationError ( `Composition type for the selected snapshot with name ${ snapshotName } must be 'key'.` ) ;
596+ }
597+ const settings : ConfigurationSetting [ ] = await this . #listConfigurationSettingsForSnapshot( snapshotName ) ;
598+ return settings ;
599+ }
600+
598601 /**
599602 * Clears all existing key-values in the local configuration except feature flags.
600603 */
@@ -754,13 +757,13 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
754757 /**
755758 * Gets a configuration setting by key and label.If the setting is not found, return undefine instead of throwing an error.
756759 */
757- async #getConfigurationSetting( configurationSettingId : ConfigurationSettingId , customOptions ?: GetConfigurationSettingOptions ) : Promise < GetConfigurationSettingResponse | undefined > {
760+ async #getConfigurationSetting( configurationSettingId : ConfigurationSettingId , getOptions ?: GetConfigurationSettingOptions ) : Promise < GetConfigurationSettingResponse | undefined > {
758761 const funcToExecute = async ( client ) => {
759762 return getConfigurationSettingWithTrace (
760763 this . #requestTraceOptions,
761764 client ,
762765 configurationSettingId ,
763- customOptions
766+ getOptions
764767 ) ;
765768 } ;
766769
@@ -777,13 +780,33 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
777780 return response ;
778781 }
779782
780- async #getSnapshot( snapshotName : string , customOptions ?: GetSnapshotOptions ) : Promise < GetSnapshotResponse | undefined > {
783+ async #listConfigurationSettings( listOptions : ListConfigurationSettingsOptions ) : Promise < { items : ConfigurationSetting [ ] ; pageWatchers : SettingWatcher [ ] } > {
784+ const funcToExecute = async ( client ) => {
785+ const pageWatchers : SettingWatcher [ ] = [ ] ;
786+ const pageIterator = listConfigurationSettingsWithTrace (
787+ this . #requestTraceOptions,
788+ client ,
789+ listOptions
790+ ) . byPage ( ) ;
791+
792+ const items : ConfigurationSetting [ ] = [ ] ;
793+ for await ( const page of pageIterator ) {
794+ pageWatchers . push ( { etag : page . etag } ) ;
795+ items . push ( ...page . items ) ;
796+ }
797+ return { items, pageWatchers } ;
798+ } ;
799+
800+ return await this . #executeWithFailoverPolicy( funcToExecute ) ;
801+ }
802+
803+ async #getSnapshot( snapshotName : string , getOptions ?: GetSnapshotOptions ) : Promise < GetSnapshotResponse | undefined > {
781804 const funcToExecute = async ( client ) => {
782805 return getSnapshotWithTrace (
783806 this . #requestTraceOptions,
784807 client ,
785808 snapshotName ,
786- customOptions
809+ getOptions
787810 ) ;
788811 } ;
789812
@@ -800,6 +823,25 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
800823 return response ;
801824 }
802825
826+ async #listConfigurationSettingsForSnapshot( snapshotName : string , listOptions ?: ListConfigurationSettingsForSnapshotOptions ) : Promise < ConfigurationSetting [ ] > {
827+ const funcToExecute = async ( client ) => {
828+ const pageIterator = listConfigurationSettingsForSnapshotWithTrace (
829+ this . #requestTraceOptions,
830+ client ,
831+ snapshotName ,
832+ listOptions
833+ ) . byPage ( ) ;
834+
835+ const items : ConfigurationSetting [ ] = [ ] ;
836+ for await ( const page of pageIterator ) {
837+ items . push ( ...page . items ) ;
838+ }
839+ return items ;
840+ } ;
841+
842+ return await this . #executeWithFailoverPolicy( funcToExecute ) ;
843+ }
844+
803845 // Only operations related to Azure App Configuration should be executed with failover policy.
804846 async #executeWithFailoverPolicy( funcToExecute : ( client : AppConfigurationClient ) => Promise < any > ) : Promise < any > {
805847 let clientWrappers = await this . #clientManager. getClients ( ) ;
@@ -875,7 +917,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
875917 #setAIConfigurationTracing( setting : ConfigurationSetting < string > ) : void {
876918 if ( this . #requestTracingEnabled && this . #aiConfigurationTracing !== undefined ) {
877919 const contentType = parseContentType ( setting . contentType ) ;
878- // content type: "application/json; profile=\"https://azconfig.io/mime-profiles/ai\"""
920+ // content type: "application/json; profile=\"https://azconfig.io/mime-profiles/ai\""
879921 if ( isJsonContentType ( contentType ) &&
880922 ! isFeatureFlagContentType ( contentType ) &&
881923 ! isSecretReferenceContentType ( contentType ) ) {
@@ -1049,3 +1091,28 @@ function validateTagFilters(tagFilters: string[]): void {
10491091 }
10501092 }
10511093}
1094+
1095+ // TODO: Temporary workaround until SDK supports snapshot reference
1096+ const snapshotReferenceContentType = "application/json; profile=\"https://azconfig.io/mime-profiles/snapshot-ref\"; charset=utf-8" ;
1097+
1098+ interface JsonSnapshotReferenceValue {
1099+ snapshot_name : string ;
1100+ }
1101+
1102+ function isSnapshotReference ( setting : ConfigurationSetting ) :
1103+ setting is ConfigurationSetting & Required < Pick < ConfigurationSetting , "value" > > {
1104+ return ( setting && setting . contentType === snapshotReferenceContentType && typeof setting . value === "string" ) ;
1105+ }
1106+
1107+ function parseSnapshotReference ( setting : ConfigurationSetting ) {
1108+ if ( ! isSnapshotReference ( setting ) ) {
1109+ throw new Error ( `Invalid snapshot reference: ${ setting } ` ) ;
1110+ }
1111+ const jsonSnapshotReferenceValue = JSON . parse ( setting . value ) as JsonSnapshotReferenceValue ;
1112+
1113+ const snapshotReference = {
1114+ ...setting ,
1115+ value : { snapshotName : jsonSnapshotReferenceValue . snapshot_name } ,
1116+ } ;
1117+ return snapshotReference ;
1118+ }
0 commit comments