Skip to content
This repository was archived by the owner on Apr 13, 2020. It is now read-only.

Commit 836c622

Browse files
authored
[FEATURE] Error codes for key vault lib (#535)
* [FEATURE] Error codes for key vault lib * Update i18n.json
1 parent b8df195 commit 836c622

File tree

6 files changed

+149
-84
lines changed

6 files changed

+149
-84
lines changed

src/commands/project/init.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ describe("Test execute function", () => {
119119

120120
it("negative test", async () => {
121121
jest.spyOn(init, "initialize").mockImplementation(() => {
122-
throw new Error();
122+
throw Error();
123123
});
124124
const exitFn = jest.fn();
125125
const randomDir = createTempDir();

src/lib/azure/keyvault.test.ts

+90-59
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
1+
import { PipelineOptions } from "@azure/core-http";
12
import { KeyVaultSecret } from "@azure/keyvault-secrets";
23
import uuid from "uuid/v4";
34
import { disableVerboseLogging, enableVerboseLogging } from "../../logger";
4-
import { getSecret, setSecret } from "./keyvault";
5+
import { getErrorMessage } from "../errorBuilder";
6+
import * as azurecredentials from "./azurecredentials";
7+
import { getClient, getSecret, setSecret } from "./keyvault";
58
import * as keyvault from "./keyvault";
9+
import { TokenCredential } from "azure-storage";
610

711
const keyVaultName = uuid();
812
const mockedName = uuid();
913
const secretValue = uuid();
1014

11-
jest.spyOn(keyvault, "getClient").mockReturnValue(
12-
Promise.resolve({
15+
jest.mock("@azure/keyvault-secrets", () => {
16+
class MockClient {
17+
constructor() {
18+
return {};
19+
}
20+
}
21+
return {
22+
SecretClient: MockClient,
23+
};
24+
});
25+
26+
const mockGetClient = (): void => {
27+
jest.spyOn(keyvault, "getClient").mockResolvedValueOnce({
1328
getSecret: async (): Promise<KeyVaultSecret> => {
1429
return {
1530
name: "test",
@@ -30,8 +45,8 @@ jest.spyOn(keyvault, "getClient").mockReturnValue(
3045
};
3146
},
3247
// eslint-disable-next-line @typescript-eslint/no-explicit-any
33-
} as any)
34-
);
48+
} as any);
49+
};
3550

3651
beforeAll(() => {
3752
enableVerboseLogging();
@@ -42,64 +57,64 @@ afterAll(() => {
4257
});
4358

4459
describe("set secret", () => {
45-
test("should fail when all arguments are not specified", async () => {
46-
await expect(setSecret("", "", "")).rejects.toThrow();
60+
test("negative test: missing values for name, key name and value.", async () => {
61+
await expect(setSecret("", "", "")).rejects.toThrow(
62+
getErrorMessage("azure-key-vault-set-secret-err")
63+
);
4764
});
48-
test("should create storage account", async () => {
49-
try {
50-
await setSecret(keyVaultName, mockedName, secretValue);
51-
} catch (_) {
52-
expect(true).toBe(false);
53-
}
65+
test("negative test: missing values for key name and value.", async () => {
66+
await expect(setSecret("vault-name", "", "")).rejects.toThrow(
67+
getErrorMessage("azure-key-vault-set-secret-err")
68+
);
5469
});
55-
test("negative test", async () => {
56-
jest.spyOn(keyvault, "getClient").mockReturnValueOnce(
57-
Promise.resolve({
58-
setSecret: (): Promise<KeyVaultSecret> => {
59-
throw new Error("fake error");
60-
},
61-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
62-
} as any)
70+
test("negative test: missing key value.", async () => {
71+
await expect(setSecret("vault-name", "key-name", "")).rejects.toThrow(
72+
getErrorMessage("azure-key-vault-set-secret-err")
6373
);
64-
try {
65-
await setSecret(keyVaultName, mockedName, secretValue);
66-
expect(true).toBe(false);
67-
} catch (e) {
68-
expect(e).toBeDefined();
69-
}
74+
});
75+
76+
test("positive test: should create storage account", async () => {
77+
mockGetClient();
78+
await setSecret(keyVaultName, mockedName, secretValue);
79+
});
80+
test("negative test: getclient failed", async () => {
81+
jest.spyOn(keyvault, "getClient").mockResolvedValueOnce({
82+
setSecret: (): Promise<KeyVaultSecret> => {
83+
throw Error("fake error");
84+
},
85+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
86+
} as any);
87+
88+
await expect(
89+
setSecret(keyVaultName, mockedName, secretValue)
90+
).rejects.toThrow(getErrorMessage("azure-key-vault-set-secret-err"));
7091
});
7192
});
7293

7394
describe("get secret", () => {
74-
test("should fail getting storage account key when arguments are not specified", async () => {
75-
await expect(getSecret("", "")).rejects.toThrow();
95+
test("negative test: missing values for name and key name.", async () => {
96+
await expect(getSecret("", "")).rejects.toThrow(
97+
getErrorMessage("azure-key-vault-get-secret-err")
98+
);
7699
});
77-
it("should get storage account key", async () => {
78-
try {
79-
const val = await getSecret(keyVaultName, mockedName);
80-
expect(val).toBe("secretValue");
81-
} catch (err) {
82-
expect(true).toBe(false);
83-
}
100+
it("positive test: should get storage account key", async () => {
101+
mockGetClient();
102+
const val = await getSecret(keyVaultName, mockedName);
103+
expect(val).toBe("secretValue");
84104
});
85105
it("negative test: secret not found", async () => {
86-
jest.spyOn(keyvault, "getClient").mockReturnValueOnce(
87-
Promise.resolve({
88-
getSecret: (): Promise<KeyVaultSecret> => {
89-
throw {
90-
code: "SecretNotFound",
91-
statusCode: 404,
92-
};
93-
},
94-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
95-
} as any)
96-
);
97-
try {
98-
const val = await getSecret(keyVaultName, mockedName);
99-
expect(val).toBe(undefined);
100-
} catch (err) {
101-
expect(true).toBe(false);
102-
}
106+
jest.spyOn(keyvault, "getClient").mockResolvedValueOnce({
107+
getSecret: (): Promise<KeyVaultSecret> => {
108+
throw {
109+
code: "SecretNotFound",
110+
statusCode: 404,
111+
};
112+
},
113+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
114+
} as any);
115+
116+
const val = await getSecret(keyVaultName, mockedName);
117+
expect(val).toBe(undefined);
103118
});
104119
it("negative test: other errors", async () => {
105120
jest.spyOn(keyvault, "getClient").mockReturnValueOnce(
@@ -113,11 +128,27 @@ describe("get secret", () => {
113128
// eslint-disable-next-line @typescript-eslint/no-explicit-any
114129
} as any)
115130
);
116-
try {
117-
await getSecret(keyVaultName, mockedName);
118-
expect(true).toBe(false);
119-
} catch (err) {
120-
expect(err).toBeDefined();
121-
}
131+
132+
await expect(getSecret(keyVaultName, mockedName)).rejects.toThrow(
133+
getErrorMessage("azure-key-vault-get-secret-err")
134+
);
135+
});
136+
});
137+
138+
describe("test getClient function", () => {
139+
it("negative test: missing credential", async () => {
140+
jest
141+
.spyOn(azurecredentials, "getCredentials")
142+
.mockRejectedValueOnce(new Error());
143+
await expect(getClient(keyVaultName, {})).rejects.toThrow(
144+
getErrorMessage("azure-key-vault-client-err")
145+
);
146+
});
147+
it("positive test", async () => {
148+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
149+
jest
150+
.spyOn(azurecredentials, "getCredentials")
151+
.mockResolvedValueOnce({} as any);
152+
await getClient(keyVaultName, {});
122153
});
123154
});

src/lib/azure/keyvault.ts

+42-21
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,51 @@
11
import { SecretClient } from "@azure/keyvault-secrets";
22
import { logger } from "../../logger";
33
import { AzureAccessOpts } from "../../types";
4+
import { build as buildError } from "../errorBuilder";
5+
import { errorStatusCode } from "../errorStatusCode";
46
import { getCredentials } from "./azurecredentials";
57

68
export const validateValues = (
79
keyVaultName: string,
810
secretName: string,
911
secretValue?: string
1012
): void => {
11-
const errors: string[] = [];
1213
if (!keyVaultName) {
13-
errors.push(`Invalid keyVaultName`);
14+
throw buildError(
15+
errorStatusCode.VALIDATION_ERR,
16+
"azure-key-vault-missing-name"
17+
);
1418
}
1519
if (!secretName) {
16-
errors.push(`Invalid secretName`);
20+
throw buildError(
21+
errorStatusCode.VALIDATION_ERR,
22+
"azure-key-vault-missing-secret-name"
23+
);
1724
}
1825
if (secretValue !== undefined && !secretValue) {
19-
errors.push(`Invalid secretValue`);
20-
}
21-
if (errors.length !== 0) {
22-
throw Error(`\n${errors.join("\n")}`);
26+
throw buildError(
27+
errorStatusCode.VALIDATION_ERR,
28+
"azure-key-vault-missing-secret-value"
29+
);
2330
}
2431
};
2532

2633
export const getClient = async (
2734
keyVaultName: string,
2835
opts: AzureAccessOpts
2936
): Promise<SecretClient> => {
30-
const url = `https://${keyVaultName}.vault.azure.net`;
31-
const credentials = await getCredentials(opts);
32-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
33-
return new SecretClient(url, credentials!);
37+
try {
38+
const url = `https://${keyVaultName}.vault.azure.net`;
39+
const credentials = await getCredentials(opts);
40+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
41+
return new SecretClient(url, credentials!);
42+
} catch (err) {
43+
throw buildError(
44+
errorStatusCode.AZURE_KEY_VAULT_ERR,
45+
"azure-key-vault-client-err",
46+
err
47+
);
48+
}
3449
};
3550

3651
/**
@@ -48,17 +63,20 @@ export const setSecret = async (
4863
secretValue: string,
4964
opts: AzureAccessOpts = {}
5065
): Promise<void> => {
51-
validateValues(keyVaultName, secretName, secretValue);
52-
const messageWithNoValue = `secret ${secretName} in key vault ${keyVaultName}`;
53-
5466
try {
67+
validateValues(keyVaultName, secretName, secretValue);
68+
const messageWithNoValue = `secret ${secretName} in key vault ${keyVaultName}`;
69+
5570
const client = await getClient(keyVaultName, opts);
5671
logger.debug(`Setting ${messageWithNoValue}`);
5772
await client.setSecret(secretName, secretValue);
5873
logger.debug(`Setting ${messageWithNoValue} is complete`);
5974
} catch (err) {
60-
logger.error(`Unable to set ${messageWithNoValue}. \n ${err}`);
61-
throw err;
75+
throw buildError(
76+
errorStatusCode.AZURE_KEY_VAULT_ERR,
77+
"azure-key-vault-set-secret-err",
78+
err
79+
);
6280
}
6381
};
6482

@@ -75,10 +93,10 @@ export const getSecret = async (
7593
secretName: string,
7694
opts: AzureAccessOpts = {}
7795
): Promise<string | undefined> => {
78-
validateValues(keyVaultName, secretName);
79-
80-
const message = `secret ${secretName} from key vault ${keyVaultName}`;
8196
try {
97+
validateValues(keyVaultName, secretName);
98+
const message = `secret ${secretName} from key vault ${keyVaultName}`;
99+
82100
const client = await getClient(keyVaultName, opts);
83101
logger.debug(`Getting ${message}`);
84102
const latestSecret = await client.getSecret(secretName);
@@ -88,7 +106,10 @@ export const getSecret = async (
88106
if (err.code === "SecretNotFound" && err.statusCode === 404) {
89107
return undefined;
90108
}
91-
logger.error(`Unable to read ${message}. \n ${err}`);
92-
throw err;
109+
throw buildError(
110+
errorStatusCode.AZURE_KEY_VAULT_ERR,
111+
"azure-key-vault-get-secret-err",
112+
err
113+
);
93114
}
94115
};

src/lib/errorStatusCode.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export enum errorStatusCode {
1515
PIPELINE_ERR = 1500,
1616
COMMANDER_ERR = 1600,
1717
AZURE_SUBSCRIPTION_ERR = 1700,
18+
AZURE_KEY_VAULT_ERR = 1800,
1819
AZURE_CLI_ERR = 1900,
1920
AZURE_STORAGE_OP_ERR = 2000,
2021
AZURE_RESOURCE_GROUP_ERR = 2500,

src/lib/i18n.json

+8
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@
301301
"validation-err-storage-key-vault-hyphen": "Storage Key Vault was invalid as it cannot contain consecutive hyphens.",
302302
"validation-err-storage-key-vault-length": "Storage Key Vault was invalid as it has to be between 3 and 24 characters long.",
303303
"validation-err-storage-access-key-missing": "Storage Access Key was missing. Provide it.",
304+
"validation-err-rfc1123-compliant": "Invalid {0} '{1}' was provided. Must be RFC1123 compliant and match regex: {2}",
304305

305306
"service-endpoint-err-validation": "Missing mandatory values ({0}) for service endpoint.",
306307
"service-endpoint-err-create-params": "Could not create parameters for service endpoint.",
@@ -317,6 +318,13 @@
317318
"azure-client-get-build-client-err": "Could not get build client. Check the Azure DevOps credential",
318319
"azure-client-get-task-agent-client-err": "Could not get task agent client. Check the Azure DevOps credential.",
319320

321+
"azure-key-vault-missing-name": "Missing key vault name. Provide it",
322+
"azure-key-vault-missing-secret-name": "Missing key vault secret name. Provide it",
323+
"azure-key-vault-missing-secret-value": "Missing key vault secret value. Provide it",
324+
"azure-key-vault-client-err": "Could not get Azure key vault client. Check your Azure credentials.",
325+
"azure-key-vault-set-secret-err": "Could not set secret in Azure key vault client.",
326+
"azure-key-vault-get-secret-err": "Could not get secret from Azure key vault client.",
327+
320328
"bedrock-yaml-ring-set-default-not-found": "Could not set default ring because ring {0} is not defined in {1}.",
321329
"bedrock-yaml-ring-remove-not-found": "Could not remove ring because ring {0} is not defined in {1}.",
322330
"bedrock-yaml-ring-remove-default": "Could not remove ring because ring {0} was set to isDefault. Set another default ring with 'spk ring set-default' first before attempting to delete",

src/lib/net/dns.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
// segments.
66
////////////////////////////////////////////////////////////////////////////////
77

8+
import { build as buildError } from "../errorBuilder";
9+
import { errorStatusCode } from "../errorStatusCode";
10+
811
export const validDnsRegex = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/;
912

1013
/**
@@ -54,8 +57,9 @@ export function assertIsValid(
5457
dns: string
5558
): asserts dns is string {
5659
if (!isValid(dns)) {
57-
throw Error(
58-
`Invalid ${fieldName} '${dns}' provided. Must be RFC1123 compliant and match regex: ${validDnsRegex}`
59-
);
60+
throw buildError(errorStatusCode.VALIDATION_ERR, {
61+
errorKey: "validation-err-rfc1123-compliant",
62+
values: [fieldName, dns, validDnsRegex.toString()],
63+
});
6064
}
6165
}

0 commit comments

Comments
 (0)