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 {
5+ AppConfigurationClient ,
6+ ConfigurationSetting ,
7+ ConfigurationSettingId ,
8+ GetConfigurationSettingOptions ,
9+ GetConfigurationSettingResponse ,
10+ ListConfigurationSettingsOptions ,
11+ featureFlagPrefix ,
12+ isFeatureFlag ,
13+ GetSnapshotOptions ,
14+ GetSnapshotResponse ,
15+ KnownSnapshotComposition
16+ } from "@azure/app-configuration" ;
517import { isRestError } from "@azure/core-rest-pipeline" ;
618import { AzureAppConfiguration , ConfigurationObjectConstructionOptions } from "./AzureAppConfiguration.js" ;
719import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions.js" ;
@@ -35,7 +47,14 @@ import {
3547} from "./featureManagement/constants.js" ;
3648import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter.js" ;
3749import { RefreshTimer } from "./refresh/RefreshTimer.js" ;
38- import { RequestTracingOptions , getConfigurationSettingWithTrace , listConfigurationSettingsWithTrace , requestTracingEnabled } from "./requestTracing/utils.js" ;
50+ import {
51+ RequestTracingOptions ,
52+ getConfigurationSettingWithTrace ,
53+ listConfigurationSettingsWithTrace ,
54+ getSnapshotWithTrace ,
55+ listConfigurationSettingsForSnapshotWithTrace ,
56+ requestTracingEnabled
57+ } from "./requestTracing/utils.js" ;
3958import { FeatureFlagTracingOptions } from "./requestTracing/FeatureFlagTracingOptions.js" ;
4059import { KeyFilter , LabelFilter , SettingSelector } from "./types.js" ;
4160import { ConfigurationClientManager } from "./ConfigurationClientManager.js" ;
@@ -363,26 +382,49 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
363382 ) ;
364383
365384 for ( const selector of selectorsToUpdate ) {
366- const listOptions : ListConfigurationSettingsOptions = {
367- keyFilter : selector . keyFilter ,
368- labelFilter : selector . labelFilter
369- } ;
370-
371- const pageEtags : string [ ] = [ ] ;
372- const pageIterator = listConfigurationSettingsWithTrace (
373- this . #requestTraceOptions,
374- client ,
375- listOptions
376- ) . byPage ( ) ;
377- for await ( const page of pageIterator ) {
378- pageEtags . push ( page . etag ?? "" ) ;
379- for ( const setting of page . items ) {
380- if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
381- loadedSettings . push ( setting ) ;
385+ if ( selector . snapshotName === undefined ) {
386+ const listOptions : ListConfigurationSettingsOptions = {
387+ keyFilter : selector . keyFilter ,
388+ labelFilter : selector . labelFilter
389+ } ;
390+ const pageEtags : string [ ] = [ ] ;
391+ const pageIterator = listConfigurationSettingsWithTrace (
392+ this . #requestTraceOptions,
393+ client ,
394+ listOptions
395+ ) . byPage ( ) ;
396+
397+ for await ( const page of pageIterator ) {
398+ pageEtags . push ( page . etag ?? "" ) ;
399+ for ( const setting of page . items ) {
400+ if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
401+ loadedSettings . push ( setting ) ;
402+ }
403+ }
404+ }
405+ selector . pageEtags = pageEtags ;
406+ } else { // snapshot selector
407+ const snapshot = await this . #getSnapshot( selector . snapshotName ) ;
408+ if ( snapshot === undefined ) {
409+ throw new Error ( `Could not find snapshot with name ${ selector . snapshotName } .` ) ;
410+ }
411+ if ( snapshot . compositionType != KnownSnapshotComposition . Key ) {
412+ throw new Error ( `Composition type for the selected snapshot with name ${ selector . snapshotName } must be 'key'.` ) ;
413+ }
414+ const pageIterator = listConfigurationSettingsForSnapshotWithTrace (
415+ this . #requestTraceOptions,
416+ client ,
417+ selector . snapshotName
418+ ) . byPage ( ) ;
419+
420+ for await ( const page of pageIterator ) {
421+ for ( const setting of page . items ) {
422+ if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
423+ loadedSettings . push ( setting ) ;
424+ }
382425 }
383426 }
384427 }
385- selector . pageEtags = pageEtags ;
386428 }
387429
388430 if ( loadFeatureFlag ) {
@@ -530,6 +572,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
530572 async #checkConfigurationSettingsChange( selectors : PagedSettingSelector [ ] ) : Promise < boolean > {
531573 const funcToExecute = async ( client ) => {
532574 for ( const selector of selectors ) {
575+ if ( selector . snapshotName ) { // skip snapshot selector
576+ continue ;
577+ }
533578 const listOptions : ListConfigurationSettingsOptions = {
534579 keyFilter : selector . keyFilter ,
535580 labelFilter : selector . labelFilter ,
@@ -581,6 +626,29 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
581626 return response ;
582627 }
583628
629+ async #getSnapshot( snapshotName : string , customOptions ?: GetSnapshotOptions ) : Promise < GetSnapshotResponse | undefined > {
630+ const funcToExecute = async ( client ) => {
631+ return getSnapshotWithTrace (
632+ this . #requestTraceOptions,
633+ client ,
634+ snapshotName ,
635+ customOptions
636+ ) ;
637+ } ;
638+
639+ let response : GetSnapshotResponse | undefined ;
640+ try {
641+ response = await this . #executeWithFailoverPolicy( funcToExecute ) ;
642+ } catch ( error ) {
643+ if ( isRestError ( error ) && error . statusCode === 404 ) {
644+ response = undefined ;
645+ } else {
646+ throw error ;
647+ }
648+ }
649+ return response ;
650+ }
651+
584652 async #executeWithFailoverPolicy( funcToExecute : ( client : AppConfigurationClient ) => Promise < any > ) : Promise < any > {
585653 let clientWrappers = await this . #clientManager. getClients ( ) ;
586654 if ( this . #options?. loadBalancingEnabled && this . #lastSuccessfulEndpoint !== "" && clientWrappers . length > 1 ) {
@@ -862,11 +930,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
862930 }
863931}
864932
865- function getValidSelectors ( selectors : SettingSelector [ ] ) : SettingSelector [ ] {
866- // below code deduplicates selectors by keyFilter and labelFilter , the latter selector wins
933+ function getValidSettingSelectors ( selectors : SettingSelector [ ] ) : SettingSelector [ ] {
934+ // below code deduplicates selectors, the latter selector wins
867935 const uniqueSelectors : SettingSelector [ ] = [ ] ;
868936 for ( const selector of selectors ) {
869- const existingSelectorIndex = uniqueSelectors . findIndex ( s => s . keyFilter === selector . keyFilter && s . labelFilter === selector . labelFilter ) ;
937+ const existingSelectorIndex = uniqueSelectors . findIndex ( s => s . keyFilter === selector . keyFilter && s . labelFilter === selector . labelFilter && s . snapshotName === selector . snapshotName ) ;
870938 if ( existingSelectorIndex >= 0 ) {
871939 uniqueSelectors . splice ( existingSelectorIndex , 1 ) ;
872940 }
@@ -875,14 +943,20 @@ function getValidSelectors(selectors: SettingSelector[]): SettingSelector[] {
875943
876944 return uniqueSelectors . map ( selectorCandidate => {
877945 const selector = { ...selectorCandidate } ;
878- if ( ! selector . keyFilter ) {
879- throw new Error ( "Key filter cannot be null or empty." ) ;
880- }
881- if ( ! selector . labelFilter ) {
882- selector . labelFilter = LabelFilter . Null ;
883- }
884- if ( selector . labelFilter . includes ( "*" ) || selector . labelFilter . includes ( "," ) ) {
885- throw new Error ( "The characters '*' and ',' are not supported in label filters." ) ;
946+ if ( selector . snapshotName ) {
947+ if ( selector . keyFilter || selector . labelFilter ) {
948+ throw new Error ( "Key or label filter should not be used for a snapshot." ) ;
949+ }
950+ } else {
951+ if ( ! selector . keyFilter ) {
952+ throw new Error ( "Key filter cannot be null or empty." ) ;
953+ }
954+ if ( ! selector . labelFilter ) {
955+ selector . labelFilter = LabelFilter . Null ;
956+ }
957+ if ( selector . labelFilter . includes ( "*" ) || selector . labelFilter . includes ( "," ) ) {
958+ throw new Error ( "The characters '*' and ',' are not supported in label filters." ) ;
959+ }
886960 }
887961 return selector ;
888962 } ) ;
@@ -893,7 +967,7 @@ function getValidKeyValueSelectors(selectors?: SettingSelector[]): SettingSelect
893967 // Default selector: key: *, label: \0
894968 return [ { keyFilter : KeyFilter . Any , labelFilter : LabelFilter . Null } ] ;
895969 }
896- return getValidSelectors ( selectors ) ;
970+ return getValidSettingSelectors ( selectors ) ;
897971}
898972
899973function getValidFeatureFlagSelectors ( selectors ?: SettingSelector [ ] ) : SettingSelector [ ] {
@@ -904,7 +978,7 @@ function getValidFeatureFlagSelectors(selectors?: SettingSelector[]): SettingSel
904978 selectors . forEach ( selector => {
905979 selector . keyFilter = `${ featureFlagPrefix } ${ selector . keyFilter } ` ;
906980 } ) ;
907- return getValidSelectors ( selectors ) ;
981+ return getValidSettingSelectors ( selectors ) ;
908982 }
909983}
910984
0 commit comments