1
1
// Copyright (c) Microsoft Corporation.
2
2
// Licensed under the MIT license.
3
3
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" ;
5
17
import { isRestError } from "@azure/core-rest-pipeline" ;
6
18
import { AzureAppConfiguration , ConfigurationObjectConstructionOptions } from "./AzureAppConfiguration.js" ;
7
19
import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions.js" ;
@@ -35,7 +47,14 @@ import {
35
47
} from "./featureManagement/constants.js" ;
36
48
import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter.js" ;
37
49
import { 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" ;
39
58
import { FeatureFlagTracingOptions } from "./requestTracing/FeatureFlagTracingOptions.js" ;
40
59
import { KeyFilter , LabelFilter , SettingSelector } from "./types.js" ;
41
60
import { ConfigurationClientManager } from "./ConfigurationClientManager.js" ;
@@ -363,26 +382,49 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
363
382
) ;
364
383
365
384
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
+ }
382
425
}
383
426
}
384
427
}
385
- selector . pageEtags = pageEtags ;
386
428
}
387
429
388
430
if ( loadFeatureFlag ) {
@@ -530,6 +572,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
530
572
async #checkConfigurationSettingsChange( selectors : PagedSettingSelector [ ] ) : Promise < boolean > {
531
573
const funcToExecute = async ( client ) => {
532
574
for ( const selector of selectors ) {
575
+ if ( selector . snapshotName ) { // skip snapshot selector
576
+ continue ;
577
+ }
533
578
const listOptions : ListConfigurationSettingsOptions = {
534
579
keyFilter : selector . keyFilter ,
535
580
labelFilter : selector . labelFilter ,
@@ -581,6 +626,29 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
581
626
return response ;
582
627
}
583
628
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
+
584
652
async #executeWithFailoverPolicy( funcToExecute : ( client : AppConfigurationClient ) => Promise < any > ) : Promise < any > {
585
653
let clientWrappers = await this . #clientManager. getClients ( ) ;
586
654
if ( this . #options?. loadBalancingEnabled && this . #lastSuccessfulEndpoint !== "" && clientWrappers . length > 1 ) {
@@ -862,11 +930,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
862
930
}
863
931
}
864
932
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
867
935
const uniqueSelectors : SettingSelector [ ] = [ ] ;
868
936
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 ) ;
870
938
if ( existingSelectorIndex >= 0 ) {
871
939
uniqueSelectors . splice ( existingSelectorIndex , 1 ) ;
872
940
}
@@ -875,14 +943,20 @@ function getValidSelectors(selectors: SettingSelector[]): SettingSelector[] {
875
943
876
944
return uniqueSelectors . map ( selectorCandidate => {
877
945
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
+ }
886
960
}
887
961
return selector ;
888
962
} ) ;
@@ -893,7 +967,7 @@ function getValidKeyValueSelectors(selectors?: SettingSelector[]): SettingSelect
893
967
// Default selector: key: *, label: \0
894
968
return [ { keyFilter : KeyFilter . Any , labelFilter : LabelFilter . Null } ] ;
895
969
}
896
- return getValidSelectors ( selectors ) ;
970
+ return getValidSettingSelectors ( selectors ) ;
897
971
}
898
972
899
973
function getValidFeatureFlagSelectors ( selectors ?: SettingSelector [ ] ) : SettingSelector [ ] {
@@ -904,7 +978,7 @@ function getValidFeatureFlagSelectors(selectors?: SettingSelector[]): SettingSel
904
978
selectors . forEach ( selector => {
905
979
selector . keyFilter = `${ featureFlagPrefix } ${ selector . keyFilter } ` ;
906
980
} ) ;
907
- return getValidSelectors ( selectors ) ;
981
+ return getValidSettingSelectors ( selectors ) ;
908
982
}
909
983
}
910
984
0 commit comments