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

Commit 6c2685d

Browse files
authored
[FEATURE] Supporting introspection configuration setup in spk init -interactive command (#415)
* stage * code clean up * added storage account and storage table prompt * Delete dennis.yaml * stage * Update init.ts * added more prompts * fix lint * fix lint * wrapping up * fix lint and update doc * fix lint * Revert "fix lint" This reverts commit 8ac94b4. * fix lint * Update init.md * incorporated review feedback * incorporate review feedback * Update validate.test.ts
1 parent 51648b2 commit 6c2685d

19 files changed

+630
-232
lines changed

src/commands/deployment/onboard.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const testPopulatedVal = (
7878
const configYaml: ConfigYaml = {
7979
introspection: {
8080
azure: {
81-
key: Promise.resolve("key")
81+
key: "key"
8282
}
8383
}
8484
};

src/commands/deployment/onboard.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export interface CommandOptions {
4040
*/
4141
export const populateValues = (opts: CommandOptions): CommandOptions => {
4242
const config = Config();
43-
const { azure } = config.introspection!;
43+
const azure = config.introspection ? config.introspection.azure : undefined;
4444

4545
opts.storageAccountName =
4646
opts.storageAccountName || azure?.account_name || undefined;

src/commands/deployment/validate.test.ts

+11-19
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ describe("Validate deployment configuration", () => {
142142
introspection: {
143143
azure: {
144144
account_name: uuid(),
145-
key: Promise.resolve(uuid()),
145+
key: uuid(),
146146
partition_key: uuid(),
147147
table_name: uuid()
148148
}
@@ -210,7 +210,7 @@ describe("test runSelfTest function", () => {
210210
const config: ConfigYaml = {
211211
introspection: {
212212
azure: {
213-
key: Promise.resolve(uuid()),
213+
key: uuid(),
214214
table_name: undefined
215215
}
216216
}
@@ -229,7 +229,7 @@ describe("test runSelfTest function", () => {
229229
const config: ConfigYaml = {
230230
introspection: {
231231
azure: {
232-
key: Promise.resolve(uuid()),
232+
key: uuid(),
233233
table_name: undefined
234234
}
235235
}
@@ -244,7 +244,7 @@ describe("test runSelfTest function", () => {
244244
const config: ConfigYaml = {
245245
introspection: {
246246
azure: {
247-
key: Promise.resolve(uuid()),
247+
key: uuid(),
248248
table_name: undefined
249249
}
250250
}
@@ -279,7 +279,7 @@ describe("Validate missing deployment.storage configuration", () => {
279279
introspection: {
280280
azure: {
281281
account_name: undefined,
282-
key: Promise.resolve(uuid())
282+
key: uuid()
283283
}
284284
}
285285
};
@@ -292,7 +292,7 @@ describe("Validate missing deployment.storage configuration", () => {
292292
const config: ConfigYaml = {
293293
introspection: {
294294
azure: {
295-
key: Promise.resolve(uuid()),
295+
key: uuid(),
296296
table_name: undefined
297297
}
298298
}
@@ -306,7 +306,7 @@ describe("Validate missing deployment.storage configuration", () => {
306306
const config: ConfigYaml = {
307307
introspection: {
308308
azure: {
309-
key: Promise.resolve(uuid()),
309+
key: uuid(),
310310
partition_key: undefined
311311
}
312312
}
@@ -319,9 +319,7 @@ describe("Validate missing deployment.storage configuration", () => {
319319
test("missing deployment.storage.key configuration", async () => {
320320
const config: ConfigYaml = {
321321
introspection: {
322-
azure: {
323-
key: Promise.resolve(undefined)
324-
}
322+
azure: {}
325323
}
326324
};
327325
await expect(isValidConfig(config)).rejects.toThrow();
@@ -332,9 +330,7 @@ describe("Validate missing deployment.pipeline configuration", () => {
332330
test("missing deployment.pipeline configuration", async () => {
333331
const config: ConfigYaml = {
334332
introspection: {
335-
azure: {
336-
key: Promise.resolve(undefined)
337-
}
333+
azure: {}
338334
}
339335
};
340336
await expect(isValidConfig(config)).rejects.toThrow();
@@ -348,9 +344,7 @@ describe("Validate missing deployment.pipeline configuration", () => {
348344
org: undefined
349345
},
350346
introspection: {
351-
azure: {
352-
key: Promise.resolve(undefined)
353-
}
347+
azure: {}
354348
}
355349
};
356350
await expect(isValidConfig(config)).rejects.toThrow();
@@ -365,9 +359,7 @@ describe("Validate missing deployment.pipeline configuration", () => {
365359
project: undefined
366360
},
367361
introspection: {
368-
azure: {
369-
key: Promise.resolve(undefined)
370-
}
362+
azure: {}
371363
}
372364
};
373365
await expect(isValidConfig(config)).rejects.toThrow();

src/commands/init.md

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ shall be no default values. These are the questions
1515
1. Organization Name of Azure dev-op account
1616
2. Project Name of Azure dev-op account
1717
3. Personal Access Token (guides)
18+
4. Would like to have introspection configuration setup? If yes
19+
1. Storage Account Name
20+
1. Storage Table Name
21+
1. Storage Partition Key
22+
1. Storage Access Key
23+
1. Key Vault Name (optional)
1824

1925
This tool shall verify these values by making an API call to Azure dev-op. They
2026
shall be written to `config.yaml` regardless the verification is successful or

src/commands/init.test.ts

+51-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
execute,
1515
getConfig,
1616
handleInteractiveMode,
17+
handleIntrospectionInteractive,
1718
prompt,
1819
validatePersonalAccessToken
1920
} from "./init";
@@ -161,6 +162,9 @@ describe("test validatePersonalAccessToken function", () => {
161162
expect(result).toBe(false);
162163
done();
163164
});
165+
it("negative test, no values in parameter", async () => {
166+
await expect(validatePersonalAccessToken({})).rejects.toThrow();
167+
});
164168
});
165169

166170
const testHandleInteractiveModeFunc = async (
@@ -171,19 +175,24 @@ const testHandleInteractiveModeFunc = async (
171175
access_token: "",
172176
org: "",
173177
project: ""
178+
},
179+
introspection: {
180+
azure: {}
174181
}
175182
});
176183
jest.spyOn(init, "prompt").mockResolvedValueOnce({
177184
azdo_org_name: "org_name",
178185
azdo_pat: "pat",
179-
azdo_project_name: "project"
186+
azdo_project_name: "project",
187+
toSetupIntrospectionConfig: true
180188
});
181189
jest
182190
.spyOn(init, "validatePersonalAccessToken")
183191
.mockReturnValueOnce(Promise.resolve(verified));
184192
const tmpFile = path.join(createTempDir(), "config.yaml");
185193

186194
jest.spyOn(config, "defaultConfigFile").mockReturnValueOnce(tmpFile);
195+
jest.spyOn(init, "handleIntrospectionInteractive").mockResolvedValueOnce();
187196

188197
await handleInteractiveMode();
189198
const content = fs.readFileSync(tmpFile, "utf8");
@@ -209,11 +218,51 @@ describe("test prompt function", () => {
209218
const answers = {
210219
azdo_org_name: "org",
211220
azdo_pat: "pat",
212-
azdo_project_name: "project"
221+
azdo_project_name: "project",
222+
toSetupIntrospectionConfig: true
213223
};
214224
jest.spyOn(inquirer, "prompt").mockResolvedValueOnce(answers);
215225
const ans = await prompt({});
216226
expect(ans).toStrictEqual(answers);
217227
done();
218228
});
219229
});
230+
231+
const testHandleIntrospectionInteractive = async (
232+
withIntrospection = false,
233+
withKeyVault = false
234+
): Promise<void> => {
235+
const config: ConfigYaml = {};
236+
if (!withIntrospection) {
237+
config["introspection"] = {
238+
azure: {}
239+
};
240+
}
241+
jest.spyOn(inquirer, "prompt").mockResolvedValueOnce({
242+
azdo_storage_account_name: "storagetest",
243+
azdo_storage_table_name: "storagetabletest",
244+
azdo_storage_partition_key: "test1234key",
245+
azdo_storage_access_key: "accessKey",
246+
azdo_storage_key_vault_name: withKeyVault ? "keyvault" : ""
247+
});
248+
await handleIntrospectionInteractive(config);
249+
expect(config.introspection?.azure?.account_name).toBe("storagetest");
250+
expect(config.introspection?.azure?.table_name).toBe("storagetabletest");
251+
expect(config.introspection?.azure?.partition_key).toBe("test1234key");
252+
expect(config.introspection?.azure?.key).toBe("accessKey");
253+
254+
if (withKeyVault) {
255+
expect(config.key_vault_name).toBe("keyvault");
256+
} else {
257+
expect(config.key_vault_name).toBeUndefined();
258+
}
259+
};
260+
261+
describe("test handleIntrospectionInteractive function", () => {
262+
it("positive test", async () => {
263+
await testHandleIntrospectionInteractive(false);
264+
await testHandleIntrospectionInteractive(true);
265+
await testHandleIntrospectionInteractive(false, true);
266+
await testHandleIntrospectionInteractive(true, true);
267+
});
268+
});

src/commands/init.ts

+60-32
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,9 @@ import {
1111
saveConfiguration
1212
} from "../config";
1313
import { build as buildCmd, exit as exitCmd } from "../lib/commandBuilder";
14+
import * as promptBuilder from "../lib/promptBuilder";
1415
import { deepClone } from "../lib/util";
15-
import {
16-
hasValue,
17-
validateAccessToken,
18-
validateOrgName,
19-
validateProjectName
20-
} from "../lib/validator";
16+
import { hasValue } from "../lib/validator";
2117
import { logger } from "../logger";
2218
import { ConfigYaml } from "../types";
2319
import decorator from "./init.decorator.json";
@@ -31,6 +27,7 @@ interface Answer {
3127
azdo_org_name: string;
3228
azdo_project_name: string;
3329
azdo_pat: string;
30+
toSetupIntrospectionConfig: boolean;
3431
}
3532

3633
/**
@@ -52,34 +49,17 @@ export const handleFileConfig = (file: string): void => {
5249
*/
5350
export const prompt = async (curConfig: ConfigYaml): Promise<Answer> => {
5451
const questions = [
55-
{
56-
default: curConfig.azure_devops?.org || undefined,
57-
message: "Enter organization name\n",
58-
name: "azdo_org_name",
59-
type: "input",
60-
validate: validateOrgName
61-
},
62-
{
63-
default: curConfig.azure_devops?.project || undefined,
64-
message: "Enter project name\n",
65-
name: "azdo_project_name",
66-
type: "input",
67-
validate: validateProjectName
68-
},
69-
{
70-
default: curConfig.azure_devops?.access_token || undefined,
71-
mask: "*",
72-
message: "Enter your AzDO personal access token\n",
73-
name: "azdo_pat",
74-
type: "password",
75-
validate: validateAccessToken
76-
}
52+
promptBuilder.azureOrgName(curConfig.azure_devops?.org),
53+
promptBuilder.azureProjectName(curConfig.azure_devops?.project),
54+
promptBuilder.azureAccessToken(curConfig.azure_devops?.access_token),
55+
promptBuilder.askToSetupIntrospectionConfig(false)
7756
];
7857
const answers = await inquirer.prompt(questions);
7958
return {
8059
azdo_org_name: answers.azdo_org_name as string,
8160
azdo_pat: answers.azdo_pat as string,
82-
azdo_project_name: answers.azdo_project_name as string
61+
azdo_project_name: answers.azdo_project_name as string,
62+
toSetupIntrospectionConfig: answers.toSetupIntrospectionConfig
8363
};
8464
};
8565

@@ -92,7 +72,7 @@ export const getConfig = (): ConfigYaml => {
9272
loadConfiguration();
9373
return Config();
9474
} catch (_) {
95-
// current config is not found.
75+
logger.info("current config is not found.");
9676
return {
9777
azure_devops: {
9878
access_token: "",
@@ -134,20 +114,68 @@ export const validatePersonalAccessToken = async (
134114
}
135115
};
136116

117+
export const isIntrospectionAzureDefined = (curConfig: ConfigYaml): boolean => {
118+
if (!curConfig.introspection) {
119+
return false;
120+
}
121+
const intro = curConfig.introspection;
122+
return intro.azure !== undefined;
123+
};
124+
125+
export const handleIntrospectionInteractive = async (
126+
curConfig: ConfigYaml
127+
): Promise<void> => {
128+
if (!isIntrospectionAzureDefined(curConfig)) {
129+
curConfig.introspection = {
130+
azure: {}
131+
};
132+
}
133+
134+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
135+
const azure = curConfig.introspection!.azure!;
136+
137+
const ans = await inquirer.prompt([
138+
promptBuilder.azureStorageAccountName(azure.account_name),
139+
promptBuilder.azureStorageTableName(azure.table_name),
140+
promptBuilder.azureStoragePartitionKey(azure.partition_key),
141+
promptBuilder.azureStorageAccessKey(azure.key),
142+
promptBuilder.azureKeyVaultName(curConfig.key_vault_name)
143+
]);
144+
azure["account_name"] = ans.azdo_storage_account_name;
145+
azure["table_name"] = ans.azdo_storage_table_name;
146+
azure["partition_key"] = ans.azdo_storage_partition_key;
147+
azure.key = ans.azdo_storage_access_key;
148+
149+
const keyVaultName = ans.azdo_storage_key_vault_name.trim();
150+
if (keyVaultName) {
151+
curConfig["key_vault_name"] = keyVaultName;
152+
} else {
153+
delete curConfig["key_vault_name"];
154+
}
155+
};
156+
137157
/**
138158
* Handles the interactive mode of the command.
139159
*/
140160
export const handleInteractiveMode = async (): Promise<void> => {
141-
const curConfig = deepClone(getConfig());
161+
const conf = getConfig();
162+
if (conf.introspection && conf.introspection.azure) {
163+
delete conf.introspection.azure.key;
164+
}
165+
const curConfig = deepClone(conf);
142166
const answer = await prompt(curConfig);
143-
144167
curConfig["azure_devops"] = curConfig.azure_devops || {};
145168

146169
curConfig.azure_devops.org = answer.azdo_org_name;
147170
curConfig.azure_devops.project = answer.azdo_project_name;
148171
curConfig.azure_devops["access_token"] = answer.azdo_pat;
149172

173+
if (answer.toSetupIntrospectionConfig) {
174+
await handleIntrospectionInteractive(curConfig);
175+
}
176+
150177
const data = yaml.safeDump(curConfig);
178+
151179
fs.writeFileSync(defaultConfigFile(), data);
152180
logger.info("Successfully constructed SPK configuration file.");
153181
const ok = await validatePersonalAccessToken(curConfig.azure_devops);

0 commit comments

Comments
 (0)