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 , isSecretReference } from "@azure/app-configuration" ;
4
+ import {
5
+ AppConfigurationClient ,
6
+ ConfigurationSetting ,
7
+ ConfigurationSettingId ,
8
+ GetConfigurationSettingOptions ,
9
+ GetConfigurationSettingResponse ,
10
+ ListConfigurationSettingsOptions ,
11
+ featureFlagPrefix ,
12
+ isFeatureFlag ,
13
+ isSecretReference ,
14
+ GetSnapshotOptions ,
15
+ GetSnapshotResponse ,
16
+ KnownSnapshotComposition
17
+ } from "@azure/app-configuration" ;
5
18
import { isRestError } from "@azure/core-rest-pipeline" ;
6
19
import { AzureAppConfiguration , ConfigurationObjectConstructionOptions } from "./AzureAppConfiguration.js" ;
7
20
import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions.js" ;
@@ -29,7 +42,14 @@ import { FM_PACKAGE_NAME, AI_MIME_PROFILE, AI_CHAT_COMPLETION_MIME_PROFILE } fro
29
42
import { parseContentType , isJsonContentType , isFeatureFlagContentType , isSecretReferenceContentType } from "./common/contentType.js" ;
30
43
import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter.js" ;
31
44
import { RefreshTimer } from "./refresh/RefreshTimer.js" ;
32
- import { RequestTracingOptions , getConfigurationSettingWithTrace , listConfigurationSettingsWithTrace , requestTracingEnabled } from "./requestTracing/utils.js" ;
45
+ import {
46
+ RequestTracingOptions ,
47
+ getConfigurationSettingWithTrace ,
48
+ listConfigurationSettingsWithTrace ,
49
+ getSnapshotWithTrace ,
50
+ listConfigurationSettingsForSnapshotWithTrace ,
51
+ requestTracingEnabled
52
+ } from "./requestTracing/utils.js" ;
33
53
import { FeatureFlagTracingOptions } from "./requestTracing/FeatureFlagTracingOptions.js" ;
34
54
import { AIConfigurationTracingOptions } from "./requestTracing/AIConfigurationTracingOptions.js" ;
35
55
import { KeyFilter , LabelFilter , SettingSelector } from "./types.js" ;
@@ -453,26 +473,49 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
453
473
) ;
454
474
455
475
for ( const selector of selectorsToUpdate ) {
456
- const listOptions : ListConfigurationSettingsOptions = {
457
- keyFilter : selector . keyFilter ,
458
- labelFilter : selector . labelFilter
459
- } ;
460
-
461
- const pageEtags : string [ ] = [ ] ;
462
- const pageIterator = listConfigurationSettingsWithTrace (
463
- this . #requestTraceOptions,
464
- client ,
465
- listOptions
466
- ) . byPage ( ) ;
467
- for await ( const page of pageIterator ) {
468
- pageEtags . push ( page . etag ?? "" ) ;
469
- for ( const setting of page . items ) {
470
- if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
471
- loadedSettings . push ( setting ) ;
476
+ if ( selector . snapshotName === undefined ) {
477
+ const listOptions : ListConfigurationSettingsOptions = {
478
+ keyFilter : selector . keyFilter ,
479
+ labelFilter : selector . labelFilter
480
+ } ;
481
+ const pageEtags : string [ ] = [ ] ;
482
+ const pageIterator = listConfigurationSettingsWithTrace (
483
+ this . #requestTraceOptions,
484
+ client ,
485
+ listOptions
486
+ ) . byPage ( ) ;
487
+
488
+ for await ( const page of pageIterator ) {
489
+ pageEtags . push ( page . etag ?? "" ) ;
490
+ for ( const setting of page . items ) {
491
+ if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
492
+ loadedSettings . push ( setting ) ;
493
+ }
494
+ }
495
+ }
496
+ selector . pageEtags = pageEtags ;
497
+ } else { // snapshot selector
498
+ const snapshot = await this . #getSnapshot( selector . snapshotName ) ;
499
+ if ( snapshot === undefined ) {
500
+ throw new InvalidOperationError ( `Could not find snapshot with name ${ selector . snapshotName } .` ) ;
501
+ }
502
+ if ( snapshot . compositionType != KnownSnapshotComposition . Key ) {
503
+ throw new InvalidOperationError ( `Composition type for the selected snapshot with name ${ selector . snapshotName } must be 'key'.` ) ;
504
+ }
505
+ const pageIterator = listConfigurationSettingsForSnapshotWithTrace (
506
+ this . #requestTraceOptions,
507
+ client ,
508
+ selector . snapshotName
509
+ ) . byPage ( ) ;
510
+
511
+ for await ( const page of pageIterator ) {
512
+ for ( const setting of page . items ) {
513
+ if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
514
+ loadedSettings . push ( setting ) ;
515
+ }
472
516
}
473
517
}
474
518
}
475
- selector . pageEtags = pageEtags ;
476
519
}
477
520
478
521
if ( loadFeatureFlag ) {
@@ -644,6 +687,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
644
687
async #checkConfigurationSettingsChange( selectors : PagedSettingSelector [ ] ) : Promise < boolean > {
645
688
const funcToExecute = async ( client ) => {
646
689
for ( const selector of selectors ) {
690
+ if ( selector . snapshotName ) { // skip snapshot selector
691
+ continue ;
692
+ }
647
693
const listOptions : ListConfigurationSettingsOptions = {
648
694
keyFilter : selector . keyFilter ,
649
695
labelFilter : selector . labelFilter ,
@@ -695,6 +741,29 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
695
741
return response ;
696
742
}
697
743
744
+ async #getSnapshot( snapshotName : string , customOptions ?: GetSnapshotOptions ) : Promise < GetSnapshotResponse | undefined > {
745
+ const funcToExecute = async ( client ) => {
746
+ return getSnapshotWithTrace (
747
+ this . #requestTraceOptions,
748
+ client ,
749
+ snapshotName ,
750
+ customOptions
751
+ ) ;
752
+ } ;
753
+
754
+ let response : GetSnapshotResponse | undefined ;
755
+ try {
756
+ response = await this . #executeWithFailoverPolicy( funcToExecute ) ;
757
+ } catch ( error ) {
758
+ if ( isRestError ( error ) && error . statusCode === 404 ) {
759
+ response = undefined ;
760
+ } else {
761
+ throw error ;
762
+ }
763
+ }
764
+ return response ;
765
+ }
766
+
698
767
// Only operations related to Azure App Configuration should be executed with failover policy.
699
768
async #executeWithFailoverPolicy( funcToExecute : ( client : AppConfigurationClient ) => Promise < any > ) : Promise < any > {
700
769
let clientWrappers = await this . #clientManager. getClients ( ) ;
@@ -838,11 +907,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
838
907
}
839
908
}
840
909
841
- function getValidSelectors ( selectors : SettingSelector [ ] ) : SettingSelector [ ] {
842
- // below code deduplicates selectors by keyFilter and labelFilter , the latter selector wins
910
+ function getValidSettingSelectors ( selectors : SettingSelector [ ] ) : SettingSelector [ ] {
911
+ // below code deduplicates selectors, the latter selector wins
843
912
const uniqueSelectors : SettingSelector [ ] = [ ] ;
844
913
for ( const selector of selectors ) {
845
- const existingSelectorIndex = uniqueSelectors . findIndex ( s => s . keyFilter === selector . keyFilter && s . labelFilter === selector . labelFilter ) ;
914
+ const existingSelectorIndex = uniqueSelectors . findIndex ( s => s . keyFilter === selector . keyFilter && s . labelFilter === selector . labelFilter && s . snapshotName === selector . snapshotName ) ;
846
915
if ( existingSelectorIndex >= 0 ) {
847
916
uniqueSelectors . splice ( existingSelectorIndex , 1 ) ;
848
917
}
@@ -851,14 +920,20 @@ function getValidSelectors(selectors: SettingSelector[]): SettingSelector[] {
851
920
852
921
return uniqueSelectors . map ( selectorCandidate => {
853
922
const selector = { ...selectorCandidate } ;
854
- if ( ! selector . keyFilter ) {
855
- throw new ArgumentError ( "Key filter cannot be null or empty." ) ;
856
- }
857
- if ( ! selector . labelFilter ) {
858
- selector . labelFilter = LabelFilter . Null ;
859
- }
860
- if ( selector . labelFilter . includes ( "*" ) || selector . labelFilter . includes ( "," ) ) {
861
- throw new ArgumentError ( "The characters '*' and ',' are not supported in label filters." ) ;
923
+ if ( selector . snapshotName ) {
924
+ if ( selector . keyFilter || selector . labelFilter ) {
925
+ throw new ArgumentError ( "Key or label filter should not be used for a snapshot." ) ;
926
+ }
927
+ } else {
928
+ if ( ! selector . keyFilter ) {
929
+ throw new ArgumentError ( "Key filter cannot be null or empty." ) ;
930
+ }
931
+ if ( ! selector . labelFilter ) {
932
+ selector . labelFilter = LabelFilter . Null ;
933
+ }
934
+ if ( selector . labelFilter . includes ( "*" ) || selector . labelFilter . includes ( "," ) ) {
935
+ throw new ArgumentError ( "The characters '*' and ',' are not supported in label filters." ) ;
936
+ }
862
937
}
863
938
return selector ;
864
939
} ) ;
@@ -869,7 +944,7 @@ function getValidKeyValueSelectors(selectors?: SettingSelector[]): SettingSelect
869
944
// Default selector: key: *, label: \0
870
945
return [ { keyFilter : KeyFilter . Any , labelFilter : LabelFilter . Null } ] ;
871
946
}
872
- return getValidSelectors ( selectors ) ;
947
+ return getValidSettingSelectors ( selectors ) ;
873
948
}
874
949
875
950
function getValidFeatureFlagSelectors ( selectors ?: SettingSelector [ ] ) : SettingSelector [ ] {
@@ -878,7 +953,9 @@ function getValidFeatureFlagSelectors(selectors?: SettingSelector[]): SettingSel
878
953
return [ { keyFilter : `${ featureFlagPrefix } ${ KeyFilter . Any } ` , labelFilter : LabelFilter . Null } ] ;
879
954
}
880
955
selectors . forEach ( selector => {
881
- selector . keyFilter = `${ featureFlagPrefix } ${ selector . keyFilter } ` ;
956
+ if ( selector . keyFilter ) {
957
+ selector . keyFilter = `${ featureFlagPrefix } ${ selector . keyFilter } ` ;
958
+ }
882
959
} ) ;
883
- return getValidSelectors ( selectors ) ;
960
+ return getValidSettingSelectors ( selectors ) ;
884
961
}
0 commit comments