Skip to content

Commit 233af51

Browse files
handle keyvault error
1 parent 5d23399 commit 233af51

File tree

8 files changed

+51
-39
lines changed

8 files changed

+51
-39
lines changed

src/AzureAppConfiguration.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
import { Disposable } from "./common/disposable.js";
55

6+
/**
7+
* Azure App Configuration provider.
8+
*/
69
export type AzureAppConfiguration = {
710
/**
811
* API to trigger refresh operation.

src/AzureAppConfigurationImpl.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
482482
await this.#updateWatchedKeyValuesEtag(loadedSettings);
483483
}
484484

485-
// process key-values, watched settings have higher priority
485+
// adapt configuration settings to key-values
486486
for (const setting of loadedSettings) {
487487
const [key, value] = await this.#processKeyValues(setting);
488488
keyValues.push([key, value]);
@@ -657,6 +657,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
657657
return response;
658658
}
659659

660+
// Only operations related to Azure App Configuration service should be executed with failover policy.
660661
async #executeWithFailoverPolicy(funcToExecute: (client: AppConfigurationClient) => Promise<any>): Promise<any> {
661662
let clientWrappers = await this.#clientManager.getClients();
662663
if (this.#options?.loadBalancingEnabled && this.#lastSuccessfulEndpoint !== "" && clientWrappers.length > 1) {

src/ConfigurationClientManager.ts

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import { TokenCredential } from "@azure/identity";
77
import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions.js";
88
import { isBrowser, isWebWorker } from "./requestTracing/utils.js";
99
import * as RequestTracing from "./requestTracing/constants.js";
10-
import { shuffleList } from "./common/utils.js";
11-
import { OperationError } from "./error.js";
10+
import { instanceOfTokenCredential, shuffleList, getValidUrl } from "./common/utils.js";
1211

1312
// Configuration client retry options
1413
const CLIENT_MAX_RETRIES = 2;
@@ -82,7 +81,7 @@ export class ConfigurationClientManager {
8281
this.#credential = credential;
8382
staticClient = new AppConfigurationClient(this.endpoint.origin, this.#credential, this.#clientOptions);
8483
} else {
85-
throw new OperationError("A connection string or an endpoint with credential must be specified to create a client.");
84+
throw new RangeError("A connection string or an endpoint with credential must be specified to create a client.");
8685
}
8786

8887
this.#staticClients = [new ConfigurationClientWrapper(this.endpoint.origin, staticClient)];
@@ -207,12 +206,12 @@ export class ConfigurationClientManager {
207206
});
208207
index++;
209208
}
210-
} catch (err) {
211-
if (err.code === "ENOTFOUND") {
209+
} catch (error) {
210+
if (error.code === "ENOTFOUND") {
212211
// No more SRV records found, return results.
213212
return results;
214213
} else {
215-
throw new Error(`Failed to lookup SRV records: ${err.message}`);
214+
throw new Error(`Failed to lookup SRV records: ${error.message}`);
216215
}
217216
}
218217

@@ -279,20 +278,3 @@ function getClientOptions(options?: AzureAppConfigurationOptions): AppConfigurat
279278
}
280279
});
281280
}
282-
283-
function getValidUrl(endpoint: string): URL {
284-
try {
285-
return new URL(endpoint);
286-
} catch (error) {
287-
if (error.code === "ERR_INVALID_URL") {
288-
throw new RangeError("Invalid endpoint URL.", { cause: error });
289-
} else {
290-
throw error;
291-
}
292-
}
293-
}
294-
295-
export function instanceOfTokenCredential(obj: unknown) {
296-
return obj && typeof obj === "object" && "getToken" in obj && typeof obj.getToken === "function";
297-
}
298-

src/common/utils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,23 @@ export function shuffleList<T>(array: T[]): T[] {
3030
}
3131
return array;
3232
}
33+
34+
export function getValidUrl(endpoint: string): URL {
35+
try {
36+
return new URL(endpoint);
37+
} catch (error) {
38+
if (error.code === "ERR_INVALID_URL") {
39+
throw new RangeError("Invalid endpoint URL.", { cause: error });
40+
} else {
41+
throw error;
42+
}
43+
}
44+
}
45+
46+
export function getUrlHost(url: string) {
47+
return new URL(url).host;
48+
}
49+
50+
export function instanceOfTokenCredential(obj: unknown) {
51+
return obj && typeof obj === "object" && "getToken" in obj && typeof obj.getToken === "function";
52+
}

src/error.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
// Licensed under the MIT license.
33

44
import { isRestError } from "@azure/core-rest-pipeline";
5+
import { AuthenticationError } from "@azure/identity";
56

67
/**
7-
* Error thrown when an operation is not allowed to be performed.
8+
* Error thrown when an operation cannot be performed by the Azure App Configuration provider.
89
*/
910
export class OperationError extends Error {
1011
constructor(message: string) {
@@ -31,8 +32,9 @@ export function isFailoverableError(error: any): boolean {
3132
}
3233

3334
export function isRetriableError(error: any): boolean {
34-
if (error instanceof OperationError ||
35-
error instanceof RangeError) {
35+
if (error instanceof AuthenticationError || // this error occurs when using wrong credential to access the key vault
36+
error instanceof RangeError || // this error is caused by misconfiguration of the Azure App Configuration provider
37+
error instanceof OperationError) {
3638
return false;
3739
}
3840
return true;

src/keyvault/AzureKeyVaultKeyValueAdapter.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import { ConfigurationSetting, isSecretReference, parseSecretReference } from "@azure/app-configuration";
55
import { IKeyValueAdapter } from "../IKeyValueAdapter.js";
66
import { KeyVaultOptions } from "./KeyVaultOptions.js";
7+
import { getUrlHost } from "../common/utils.js";
78
import { SecretClient, parseKeyVaultSecretIdentifier } from "@azure/keyvault-secrets";
8-
import { OperationError } from "../error.js";
99

1010
export class AzureKeyVaultKeyValueAdapter implements IKeyValueAdapter {
1111
/**
@@ -25,7 +25,7 @@ export class AzureKeyVaultKeyValueAdapter implements IKeyValueAdapter {
2525
async processKeyValue(setting: ConfigurationSetting): Promise<[string, unknown]> {
2626
// TODO: cache results to save requests.
2727
if (!this.#keyVaultOptions) {
28-
throw new OperationError("Failed to process the key vault reference. The keyVaultOptions is not configured.");
28+
throw new RangeError("Failed to process the key vault reference. The keyVaultOptions is not configured.");
2929
}
3030

3131
// precedence: secret clients > credential > secret resolver
@@ -35,7 +35,7 @@ export class AzureKeyVaultKeyValueAdapter implements IKeyValueAdapter {
3535

3636
const client = this.#getSecretClient(new URL(vaultUrl));
3737
if (client) {
38-
// TODO: what if error occurs when reading a key vault value? Now it breaks the whole load.
38+
// If the credential of the secret client is wrong, AuthenticationError will be thrown.
3939
const secret = await client.getSecret(secretName, { version });
4040
return [setting.key, secret.value];
4141
}
@@ -44,14 +44,21 @@ export class AzureKeyVaultKeyValueAdapter implements IKeyValueAdapter {
4444
return [setting.key, await this.#keyVaultOptions.secretResolver(new URL(sourceId))];
4545
}
4646

47-
throw new OperationError("Failed to process the key vault reference. No key vault credential or secret resolver callback configured, and no matching secret client could be found.");
47+
// When code reaches here, it means the key vault secret reference is not resolved.
48+
49+
throw new RangeError("Failed to process the key vault reference. No key vault credential or secret resolver callback is configured.");
4850
}
4951

52+
/**
53+
*
54+
* @param vaultUrl - The url of the key vault.
55+
* @returns
56+
*/
5057
#getSecretClient(vaultUrl: URL): SecretClient | undefined {
5158
if (this.#secretClients === undefined) {
5259
this.#secretClients = new Map();
53-
for (const c of this.#keyVaultOptions?.secretClients ?? []) {
54-
this.#secretClients.set(getHost(c.vaultUrl), c);
60+
for (const client of this.#keyVaultOptions?.secretClients ?? []) {
61+
this.#secretClients.set(getUrlHost(client.vaultUrl), client);
5562
}
5663
}
5764

@@ -70,7 +77,3 @@ export class AzureKeyVaultKeyValueAdapter implements IKeyValueAdapter {
7077
return undefined;
7178
}
7279
}
73-
74-
function getHost(url: string) {
75-
return new URL(url).host;
76-
}

src/load.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { TokenCredential } from "@azure/identity";
55
import { AzureAppConfiguration } from "./AzureAppConfiguration.js";
66
import { AzureAppConfigurationImpl } from "./AzureAppConfigurationImpl.js";
77
import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions.js";
8-
import { ConfigurationClientManager, instanceOfTokenCredential } from "./ConfigurationClientManager.js";
8+
import { ConfigurationClientManager } from "./ConfigurationClientManager.js";
9+
import { instanceOfTokenCredential } from "./common/utils.js";
910

1011
const MIN_DELAY_FOR_UNHANDLED_ERROR: number = 5_000; // 5 seconds
1112

test/keyvault.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ describe("key vault reference", function () {
9696
]
9797
}
9898
});
99-
return expect(loadKeyVaultPromise).eventually.rejectedWith("Failed to process the key vault reference. No key vault credential or secret resolver callback configured, and no matching secret client could be found.");
99+
return expect(loadKeyVaultPromise).eventually.rejectedWith("Failed to process the key vault reference. No key vault credential or secret resolver callback is configured.");
100100
});
101101

102102
it("should fallback to use default credential when corresponding secret client not provided", async () => {

0 commit comments

Comments
 (0)