Skip to content

Commit 986e3f0

Browse files
Resolve key vault secret in parallel (#192)
* get secret in parallel * remove test project * add parallelSecretResolutionEnabled option * fix lint
1 parent 28cbd6d commit 986e3f0

File tree

3 files changed

+45
-4
lines changed

3 files changed

+45
-4
lines changed

src/AzureAppConfigurationImpl.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
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";
55
import { isRestError } from "@azure/core-rest-pipeline";
66
import { AzureAppConfiguration, ConfigurationObjectConstructionOptions } from "./AzureAppConfiguration.js";
77
import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions.js";
@@ -83,6 +83,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
8383
#ffRefreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS;
8484
#ffRefreshTimer: RefreshTimer;
8585

86+
// Key Vault references
87+
#resolveSecretInParallel: boolean = false;
88+
8689
/**
8790
* Selectors of key-values obtained from @see AzureAppConfigurationOptions.selectors
8891
*/
@@ -163,6 +166,10 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
163166
}
164167
}
165168

169+
if (options?.keyVaultOptions?.parallelSecretResolutionEnabled) {
170+
this.#resolveSecretInParallel = options.keyVaultOptions.parallelSecretResolutionEnabled;
171+
}
172+
166173
this.#adapters.push(new AzureKeyVaultKeyValueAdapter(options?.keyVaultOptions));
167174
this.#adapters.push(new JsonKeyValueAdapter());
168175
}
@@ -484,7 +491,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
484491
*/
485492
async #loadSelectedAndWatchedKeyValues() {
486493
const keyValues: [key: string, value: unknown][] = [];
487-
const loadedSettings = await this.#loadConfigurationSettings();
494+
const loadedSettings: ConfigurationSetting[] = await this.#loadConfigurationSettings();
488495
if (this.#refreshEnabled && !this.#watchAll) {
489496
await this.#updateWatchedKeyValuesEtag(loadedSettings);
490497
}
@@ -494,11 +501,25 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
494501
this.#aiConfigurationTracing.reset();
495502
}
496503

497-
// adapt configuration settings to key-values
504+
const secretResolutionPromises: Promise<void>[] = [];
498505
for (const setting of loadedSettings) {
506+
if (this.#resolveSecretInParallel && isSecretReference(setting)) {
507+
// secret references are resolved asynchronously to improve performance
508+
const secretResolutionPromise = this.#processKeyValue(setting)
509+
.then(([key, value]) => {
510+
keyValues.push([key, value]);
511+
});
512+
secretResolutionPromises.push(secretResolutionPromise);
513+
continue;
514+
}
515+
// adapt configuration settings to key-values
499516
const [key, value] = await this.#processKeyValue(setting);
500517
keyValues.push([key, value]);
501518
}
519+
if (secretResolutionPromises.length > 0) {
520+
// wait for all secret resolution promises to be resolved
521+
await Promise.all(secretResolutionPromises);
522+
}
502523

503524
this.#clearLoadedKeyValues(); // clear existing key-values in case of configuration setting deletion
504525
for (const [k, v] of keyValues) {
@@ -543,7 +564,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
543564
*/
544565
async #loadFeatureFlags() {
545566
const loadFeatureFlag = true;
546-
const featureFlagSettings = await this.#loadConfigurationSettings(loadFeatureFlag);
567+
const featureFlagSettings: ConfigurationSetting[] = await this.#loadConfigurationSettings(loadFeatureFlag);
547568

548569
if (this.#requestTracingEnabled && this.#featureFlagTracing !== undefined) {
549570
// Reset old feature flag tracing in order to track the information present in the current response from server.

src/keyvault/KeyVaultOptions.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,12 @@ export interface KeyVaultOptions {
3232
* @returns The secret value.
3333
*/
3434
secretResolver?: (keyVaultReference: URL) => string | Promise<string>;
35+
36+
/**
37+
* Specifies whether to resolve the secret value in parallel.
38+
*
39+
* @remarks
40+
* If not specified, the default value is false.
41+
*/
42+
parallelSecretResolutionEnabled?: boolean;
3543
}

test/keyvault.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,16 @@ describe("key vault reference", function () {
127127
expect(settings.get("TestKey")).eq("SecretValue");
128128
expect(settings.get("TestKey2")).eq("SecretValue2");
129129
});
130+
131+
it("should resolve key vault reference in parallel", async () => {
132+
const settings = await load(createMockedConnectionString(), {
133+
keyVaultOptions: {
134+
credential: createMockedTokenCredential(),
135+
parallelSecretResolutionEnabled: true
136+
}
137+
});
138+
expect(settings).not.undefined;
139+
expect(settings.get("TestKey")).eq("SecretValue");
140+
expect(settings.get("TestKeyFixedVersion")).eq("OldSecretValue");
141+
});
130142
});

0 commit comments

Comments
 (0)