diff --git a/src/AzureAppConfigurationImpl.ts b/src/AzureAppConfigurationImpl.ts index 1491b806..f3c84061 100644 --- a/src/AzureAppConfigurationImpl.ts +++ b/src/AzureAppConfigurationImpl.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { AppConfigurationClient, ConfigurationSetting, ConfigurationSettingId, GetConfigurationSettingOptions, GetConfigurationSettingResponse, ListConfigurationSettingsOptions, featureFlagPrefix, isFeatureFlag } from "@azure/app-configuration"; +import { AppConfigurationClient, ConfigurationSetting, ConfigurationSettingId, GetConfigurationSettingOptions, GetConfigurationSettingResponse, ListConfigurationSettingsOptions, featureFlagPrefix, isFeatureFlag, isSecretReference } from "@azure/app-configuration"; import { isRestError } from "@azure/core-rest-pipeline"; import { AzureAppConfiguration, ConfigurationObjectConstructionOptions } from "./AzureAppConfiguration.js"; import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions.js"; @@ -83,6 +83,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration { #ffRefreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS; #ffRefreshTimer: RefreshTimer; + // Key Vault references + #resolveSecretInParallel: boolean = false; + /** * Selectors of key-values obtained from @see AzureAppConfigurationOptions.selectors */ @@ -163,6 +166,10 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration { } } + if (options?.keyVaultOptions?.parallelSecretResolutionEnabled) { + this.#resolveSecretInParallel = options.keyVaultOptions.parallelSecretResolutionEnabled; + } + this.#adapters.push(new AzureKeyVaultKeyValueAdapter(options?.keyVaultOptions)); this.#adapters.push(new JsonKeyValueAdapter()); } @@ -484,7 +491,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration { */ async #loadSelectedAndWatchedKeyValues() { const keyValues: [key: string, value: unknown][] = []; - const loadedSettings = await this.#loadConfigurationSettings(); + const loadedSettings: ConfigurationSetting[] = await this.#loadConfigurationSettings(); if (this.#refreshEnabled && !this.#watchAll) { await this.#updateWatchedKeyValuesEtag(loadedSettings); } @@ -494,11 +501,25 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration { this.#aiConfigurationTracing.reset(); } - // adapt configuration settings to key-values + const secretResolutionPromises: Promise[] = []; for (const setting of loadedSettings) { + if (this.#resolveSecretInParallel && isSecretReference(setting)) { + // secret references are resolved asynchronously to improve performance + const secretResolutionPromise = this.#processKeyValue(setting) + .then(([key, value]) => { + keyValues.push([key, value]); + }); + secretResolutionPromises.push(secretResolutionPromise); + continue; + } + // adapt configuration settings to key-values const [key, value] = await this.#processKeyValue(setting); keyValues.push([key, value]); } + if (secretResolutionPromises.length > 0) { + // wait for all secret resolution promises to be resolved + await Promise.all(secretResolutionPromises); + } this.#clearLoadedKeyValues(); // clear existing key-values in case of configuration setting deletion for (const [k, v] of keyValues) { @@ -543,7 +564,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration { */ async #loadFeatureFlags() { const loadFeatureFlag = true; - const featureFlagSettings = await this.#loadConfigurationSettings(loadFeatureFlag); + const featureFlagSettings: ConfigurationSetting[] = await this.#loadConfigurationSettings(loadFeatureFlag); if (this.#requestTracingEnabled && this.#featureFlagTracing !== undefined) { // Reset old feature flag tracing in order to track the information present in the current response from server. diff --git a/src/keyvault/KeyVaultOptions.ts b/src/keyvault/KeyVaultOptions.ts index 132c9cf5..3cf4bad0 100644 --- a/src/keyvault/KeyVaultOptions.ts +++ b/src/keyvault/KeyVaultOptions.ts @@ -32,4 +32,12 @@ export interface KeyVaultOptions { * @returns The secret value. */ secretResolver?: (keyVaultReference: URL) => string | Promise; + + /** + * Specifies whether to resolve the secret value in parallel. + * + * @remarks + * If not specified, the default value is false. + */ + parallelSecretResolutionEnabled?: boolean; } diff --git a/test/keyvault.test.ts b/test/keyvault.test.ts index 81dc429d..8fd15a19 100644 --- a/test/keyvault.test.ts +++ b/test/keyvault.test.ts @@ -127,4 +127,16 @@ describe("key vault reference", function () { expect(settings.get("TestKey")).eq("SecretValue"); expect(settings.get("TestKey2")).eq("SecretValue2"); }); + + it("should resolve key vault reference in parallel", async () => { + const settings = await load(createMockedConnectionString(), { + keyVaultOptions: { + credential: createMockedTokenCredential(), + parallelSecretResolutionEnabled: true + } + }); + expect(settings).not.undefined; + expect(settings.get("TestKey")).eq("SecretValue"); + expect(settings.get("TestKeyFixedVersion")).eq("OldSecretValue"); + }); });