diff --git a/__tests__/__packages__/cli-test-utils/package.json b/__tests__/__packages__/cli-test-utils/package.json index 4e72cab7f0..a83dddbe8d 100644 --- a/__tests__/__packages__/cli-test-utils/package.json +++ b/__tests__/__packages__/cli-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/cli-test-utils", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Test utilities package for Zowe CLI plug-ins", "author": "Zowe", "license": "EPL-2.0", @@ -43,7 +43,7 @@ "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/uuid": "^10.0.0", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next" diff --git a/lerna.json b/lerna.json index 8849da3e0f..5c27a61411 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "command": { "publish": { "ignoreChanges": [ diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index d0a4f6123e..83710a78e3 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -52,7 +52,7 @@ }, "__tests__/__packages__/cli-test-utils": { "name": "@zowe/cli-test-utils", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { "find-up": "^5.0.0", @@ -63,7 +63,7 @@ "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/uuid": "^10.0.0", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next" @@ -16341,21 +16341,21 @@ }, "packages/cli": { "name": "@zowe/cli", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "hasInstallScript": true, "license": "EPL-2.0", "dependencies": { - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516", - "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407021516", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717", + "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717", "find-process": "1.4.7", "lodash": "4.17.21", "minimatch": "9.0.5", @@ -16368,7 +16368,7 @@ "@types/diff": "^5.0.9", "@types/lodash": "^4.17.6", "@types/tar": "^6.1.11", - "@zowe/cli-test-utils": "8.0.0-next.202407021516", + "@zowe/cli-test-utils": "8.0.0-next.202407051717", "comment-json": "^4.2.3", "strip-ansi": "^6.0.1", "which": "^4.0.0" @@ -16377,7 +16377,7 @@ "node": ">=18.12.0" }, "optionalDependencies": { - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717" } }, "packages/cli/node_modules/brace-expansion": { @@ -16424,15 +16424,15 @@ }, "packages/core": { "name": "@zowe/core-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { "comment-json": "~4.2.3", "string-width": "^4.2.3" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16443,7 +16443,7 @@ }, "packages/imperative": { "name": "@zowe/imperative", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { "@types/yargs": "^17.0.32", @@ -16497,7 +16497,7 @@ "@types/pacote": "^11.1.8", "@types/progress": "^2.0.7", "@types/stack-trace": "^0.0.33", - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407021516", + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717", "concurrently": "^8.0.0", "cowsay": "^1.6.0", "deep-diff": "^1.0.0", @@ -16646,16 +16646,16 @@ }, "packages/provisioning": { "name": "@zowe/provisioning-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { "js-yaml": "^4.1.0" }, "devDependencies": { "@types/js-yaml": "^4.0.9", - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16667,7 +16667,7 @@ }, "packages/secrets": { "name": "@zowe/secrets-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "hasInstallScript": true, "license": "EPL-2.0", "devDependencies": { @@ -16680,15 +16680,15 @@ }, "packages/workflows": { "name": "@zowe/zos-workflows-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16700,12 +16700,12 @@ }, "packages/zosconsole": { "name": "@zowe/zos-console-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16717,16 +16717,16 @@ }, "packages/zosfiles": { "name": "@zowe/zos-files-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { "minimatch": "^9.0.5" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16758,15 +16758,15 @@ }, "packages/zosjobs": { "name": "@zowe/zos-jobs-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16778,12 +16778,12 @@ }, "packages/zoslogs": { "name": "@zowe/zos-logs-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16795,12 +16795,12 @@ }, "packages/zosmf": { "name": "@zowe/zosmf-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16812,15 +16812,15 @@ }, "packages/zostso": { "name": "@zowe/zos-tso-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16832,15 +16832,15 @@ }, "packages/zosuss": { "name": "@zowe/zos-uss-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { "ssh2": "^1.15.0" }, "devDependencies": { "@types/ssh2": "^1.11.19", - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" diff --git a/packages/cli/package.json b/packages/cli/package.json index aef839e916..5762a5938d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/cli", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "zoweVersion": "V3", "description": "Zowe CLI is a command line interface (CLI) that provides a simple and streamlined way to interact with IBM z/OS.", "author": "Zowe", @@ -58,17 +58,17 @@ "preshrinkwrap": "node ../../scripts/rewriteShrinkwrap.js" }, "dependencies": { - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516", - "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407021516", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717", + "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717", "find-process": "1.4.7", "lodash": "4.17.21", "minimatch": "9.0.5", @@ -78,13 +78,13 @@ "@types/diff": "^5.0.9", "@types/lodash": "^4.17.6", "@types/tar": "^6.1.11", - "@zowe/cli-test-utils": "8.0.0-next.202407021516", + "@zowe/cli-test-utils": "8.0.0-next.202407051717", "comment-json": "^4.2.3", "strip-ansi": "^6.0.1", "which": "^4.0.0" }, "optionalDependencies": { - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" diff --git a/packages/core/package.json b/packages/core/package.json index cac9554a65..1c07861c3a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/core-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Core libraries shared by Zowe SDK packages", "author": "Zowe", "license": "EPL-2.0", @@ -49,8 +49,8 @@ "string-width": "^4.2.3" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next" diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index 2079702f24..0cf571ef7e 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -7,6 +7,10 @@ All notable changes to the Imperative package will be documented in this file. - BugFix: Resolved bug that resulted in each plug-in to have identical public registries regardless of actual installation location/reference. [#2189](https://github.com/zowe/zowe-cli/pull/2189) - BugFix: Resolved bug that resulted in every plug-in to have the same registry location field as the first if multiple plugins were installed in the same command. [#2189](https://github.com/zowe/zowe-cli/pull/2189) +## `8.0.0-next.202407051717` + +- BugFix: V3 Breaking: Modified the ConvertV1Profiles.convert API to accept a new ProfileInfo option and initialize components sufficiently to enable VSCode apps to convert V1 profiles. [#2170](https://github.com/zowe/zowe-cli/issues/2170) + ## `8.0.0-next.202407021516` - BugFix: Updated dependencies for technical currency [#2188](https://github.com/zowe/zowe-cli/pull/2188) diff --git a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/__resources__/plugins/plugins.json b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/__resources__/plugins/plugins.json new file mode 100644 index 0000000000..e17d2382f9 --- /dev/null +++ b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/__resources__/plugins/plugins.json @@ -0,0 +1,22 @@ +{ + "@zowe/secure-credential-store-for-zowe-cli": { + "package": "@zowe/secure-credential-store-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.1.12" + }, + "@zowe/cics-for-zowe-cli": { + "package": "@zowe/cics-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.0.11" + }, + "@broadcom/jclcheck-for-zowe-cli": { + "package": "@broadcom/jclcheck-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "1.1.2" + }, + "@broadcom/endevor-for-zowe-cli": { + "package": "@broadcom/endevor-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "5.7.6" + } +} diff --git a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts index 643ca94ac6..6263057a56 100644 --- a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts +++ b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts @@ -34,6 +34,7 @@ describe("imperative-test-cli config convert-profiles", () => { beforeEach(() => { fsExtra.copySync(__dirname + "/../../config/__resources__/profiles_secured_and_base", TEST_ENVIRONMENT.workingDir + "/profiles"); + fsExtra.copySync(__dirname + "/../../config/__resources__/plugins", TEST_ENVIRONMENT.workingDir + "/plugins"); }); afterEach(() => { diff --git a/packages/imperative/package.json b/packages/imperative/package.json index e91ad5c51a..0376a3b00f 100644 --- a/packages/imperative/package.json +++ b/packages/imperative/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/imperative", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "framework for building configurable CLIs", "author": "Zowe", "license": "EPL-2.0", @@ -94,7 +94,7 @@ "@types/pacote": "^11.1.8", "@types/progress": "^2.0.7", "@types/stack-trace": "^0.0.33", - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407021516", + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717", "concurrently": "^8.0.0", "cowsay": "^1.6.0", "deep-diff": "^1.0.0", diff --git a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts index 9ca07a18f2..a842f4e953 100644 --- a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts @@ -9,10 +9,11 @@ * */ +jest.mock("jsonfile"); + import * as fs from "fs"; import * as fsExtra from "fs-extra"; -import { PluginIssues } from "../../imperative/src/plugins/utilities/PluginIssues"; -import { OverridesLoader } from "../../imperative/src/OverridesLoader"; +import * as jsonfile from "jsonfile"; import { CredentialManagerFactory } from "../.."; import { ConvertV1Profiles } from "../"; import { ConvertMsgFmt } from "../src/doc/IConvertV1Profiles"; @@ -20,34 +21,47 @@ import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; import { ImperativeError } from "../../error/src/ImperativeError"; import { keyring } from "@zowe/secrets-for-zowe-sdk"; import { Logger } from "../../logger/src/Logger"; +import { LoggingConfigurer } from "../../imperative/src/LoggingConfigurer"; import { V1ProfileRead } from "../../profiles"; -import { Config } from "../../config/src/Config"; import { ConfigSchema } from "../../config/src/ConfigSchema"; import { AppSettings } from "../../settings/src/AppSettings"; import { CredentialManagerOverride } from "../../security/src/CredentialManagerOverride"; +import { ProfileInfo } from "../src/ProfileInfo"; +import { OverridesLoader } from "../../imperative/src/OverridesLoader"; jest.mock("../../imperative/src/OverridesLoader"); describe("ConvertV1Profiles tests", () => { + const oldScsPluginNm = "@zowe/secure-credential-store-for-zowe-cli"; + const profileDir = "/fake/path/to/profiles/"; + const appName = "zowe"; beforeAll(() => { - // do not attempt to actually log any errors + // do not attempt to do any logging configuration + Logger.initLogger = jest.fn(); + LoggingConfigurer.configureLogger = jest.fn(); + }); + + beforeEach(() => { + // do not actually log any errors jest.spyOn(Logger, "getImperativeLogger").mockReturnValue({ error: jest.fn() } as any); }); describe("convert", () => { - let isConversionNeededSpy: any; - let moveV1ProfilesToConfigFileSpy: any; - let removeOldOverridesSpy: any; - let deleteV1ProfilesSpy: any; + let isConversionNeededSpy: any = jest.fn(); + let replaceOldCredMgrOverrideSpy: any = jest.fn(); + let initCredMgrSpy: any = jest.fn(); + let moveV1ProfilesToConfigFileSpy: any = jest.fn(); + let deleteV1ProfilesSpy: any = jest.fn(); beforeAll(() => { // use "any" so that we can call private functions isConversionNeededSpy = jest.spyOn(ConvertV1Profiles as any, "isConversionNeeded"); + replaceOldCredMgrOverrideSpy = jest.spyOn(ConvertV1Profiles as any, "replaceOldCredMgrOverride"); + initCredMgrSpy = jest.spyOn(ConvertV1Profiles as any, "initCredMgr"); moveV1ProfilesToConfigFileSpy = jest.spyOn(ConvertV1Profiles as any, "moveV1ProfilesToConfigFile"); - removeOldOverridesSpy = jest.spyOn(ConvertV1Profiles as any, "removeOldOverrides"); deleteV1ProfilesSpy = jest.spyOn(ConvertV1Profiles as any, "deleteV1Profiles"); // cliHome is a getter property, so mock the property. @@ -59,15 +73,29 @@ describe("ConvertV1Profiles tests", () => { }); }); + beforeEach(() => { + // functions called by convert which we just want to confirm have been called. + replaceOldCredMgrOverrideSpy.mockReturnValue(void 0); + initCredMgrSpy.mockResolvedValue(Promise.resolve()); + moveV1ProfilesToConfigFileSpy.mockResolvedValue(Promise.resolve()); + deleteV1ProfilesSpy.mockResolvedValue(Promise.resolve()); + }); + afterEach(() => { jest.clearAllMocks(); // clear our spies usage counts }); + afterAll(() => { + // restore original app implementations + isConversionNeededSpy.mockRestore(); + replaceOldCredMgrOverrideSpy.mockRestore(); + initCredMgrSpy.mockRestore(); + moveV1ProfilesToConfigFileSpy.mockRestore(); + deleteV1ProfilesSpy.mockRestore(); + }); + it("should complete a conversion when all utility functions work", async () => { isConversionNeededSpy.mockReturnValueOnce(true); - moveV1ProfilesToConfigFileSpy.mockResolvedValue(Promise.resolve()); - removeOldOverridesSpy.mockImplementation(() => { }); - deleteV1ProfilesSpy.mockResolvedValue(Promise.resolve()); // call the function that we want to test await ConvertV1Profiles.convert({ @@ -75,16 +103,57 @@ describe("ConvertV1Profiles tests", () => { }); expect(isConversionNeededSpy).toHaveBeenCalled(); + expect(replaceOldCredMgrOverrideSpy).toHaveBeenCalled(); + expect(initCredMgrSpy).toHaveBeenCalled(); expect(moveV1ProfilesToConfigFileSpy).toHaveBeenCalled(); - expect(removeOldOverridesSpy).toHaveBeenCalled(); expect(deleteV1ProfilesSpy).toHaveBeenCalled(); }); + it("should report that CLI must uninstall plugin when called with ProfileInfo", async () => { + isConversionNeededSpy.mockReturnValueOnce(true); + + // Ensure that the old SCS plugin name is populated in the convert result + replaceOldCredMgrOverrideSpy.mockImplementation(() => { + ConvertV1Profiles["convertResult"].v1ScsPluginName = oldScsPluginNm; + }); + + // call the function that we want to test + const profInfo = new ProfileInfo(appName); + await ConvertV1Profiles.convert({ + deleteV1Profs: true, + profileInfo : profInfo + }); + + expect(isConversionNeededSpy).toHaveBeenCalled(); + expect(replaceOldCredMgrOverrideSpy).toHaveBeenCalled(); + expect(initCredMgrSpy).toHaveBeenCalled(); + expect(moveV1ProfilesToConfigFileSpy).toHaveBeenCalled(); + expect(deleteV1ProfilesSpy).toHaveBeenCalled(); + + /* The following line is a swell debug tool when a code block, + * which is trying to match lines of result messages (like the block below), + * does not get the results that it expects. + * + console.log("convertResult:\n " + JSON.stringify(ConvertV1Profiles["convertResult"], null, 2)); + */ + + let numMsgsFound = 0; + for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { + if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE && + nextMsg.msgText.includes( + `The obsolete plug-in ${oldScsPluginNm} should be uninstalled because the SCS is now ` + + `embedded within the Zowe clients. Zowe CLI plugins can only be uninstalled by the CLI. ` + + `Use the command 'zowe plugins uninstall ${oldScsPluginNm}'.` + ) + ) { + numMsgsFound++; + } + } + expect(numMsgsFound).toEqual(1); + }); + it("should not delete profiles when asked not to delete", async () => { isConversionNeededSpy.mockReturnValueOnce(true); - moveV1ProfilesToConfigFileSpy.mockResolvedValue(Promise.resolve()); - removeOldOverridesSpy.mockImplementation(() => { }); - deleteV1ProfilesSpy.mockResolvedValue(Promise.resolve()); // call the function that we want to test await ConvertV1Profiles.convert({ @@ -92,16 +161,14 @@ describe("ConvertV1Profiles tests", () => { }); expect(isConversionNeededSpy).toHaveBeenCalled(); + expect(replaceOldCredMgrOverrideSpy).toHaveBeenCalled(); + expect(initCredMgrSpy).toHaveBeenCalled(); expect(moveV1ProfilesToConfigFileSpy).toHaveBeenCalled(); - expect(removeOldOverridesSpy).toHaveBeenCalled(); expect(deleteV1ProfilesSpy).not.toHaveBeenCalled(); }); it("should skip conversion and not delete profiles", async () => { isConversionNeededSpy.mockReturnValueOnce(false); - moveV1ProfilesToConfigFileSpy.mockResolvedValue(Promise.resolve()); - removeOldOverridesSpy.mockImplementation(() => { }); - deleteV1ProfilesSpy.mockResolvedValue(Promise.resolve()); // call the function that we want to test await ConvertV1Profiles.convert({ @@ -109,16 +176,14 @@ describe("ConvertV1Profiles tests", () => { }); expect(isConversionNeededSpy).toHaveBeenCalled(); + expect(replaceOldCredMgrOverrideSpy).not.toHaveBeenCalled(); + expect(initCredMgrSpy).not.toHaveBeenCalled(); expect(moveV1ProfilesToConfigFileSpy).not.toHaveBeenCalled(); - expect(removeOldOverridesSpy).not.toHaveBeenCalled(); expect(deleteV1ProfilesSpy).not.toHaveBeenCalled(); }); it("should skip conversion but still delete profiles", async () => { isConversionNeededSpy.mockReturnValueOnce(false); - moveV1ProfilesToConfigFileSpy.mockResolvedValue(Promise.resolve()); - removeOldOverridesSpy.mockImplementation(() => { }); - deleteV1ProfilesSpy.mockResolvedValue(Promise.resolve()); // call the function that we want to test await ConvertV1Profiles.convert({ @@ -126,8 +191,9 @@ describe("ConvertV1Profiles tests", () => { }); expect(isConversionNeededSpy).toHaveBeenCalled(); + expect(replaceOldCredMgrOverrideSpy).not.toHaveBeenCalled(); + expect(initCredMgrSpy).not.toHaveBeenCalled(); expect(moveV1ProfilesToConfigFileSpy).not.toHaveBeenCalled(); - expect(removeOldOverridesSpy).not.toHaveBeenCalled(); expect(deleteV1ProfilesSpy).toHaveBeenCalled(); }); @@ -136,9 +202,6 @@ describe("ConvertV1Profiles tests", () => { isConversionNeededSpy.mockImplementation(() => { throw new Error(fakeErrMsg); }); - moveV1ProfilesToConfigFileSpy.mockResolvedValue(Promise.resolve()); - removeOldOverridesSpy.mockImplementation(() => { }); - deleteV1ProfilesSpy.mockResolvedValue(Promise.resolve()); // call the function that we want to test await ConvertV1Profiles.convert({ @@ -146,17 +209,11 @@ describe("ConvertV1Profiles tests", () => { }); expect(isConversionNeededSpy).toHaveBeenCalled(); + expect(replaceOldCredMgrOverrideSpy).not.toHaveBeenCalled(); + expect(initCredMgrSpy).not.toHaveBeenCalled(); expect(moveV1ProfilesToConfigFileSpy).not.toHaveBeenCalled(); - expect(removeOldOverridesSpy).not.toHaveBeenCalled(); expect(deleteV1ProfilesSpy).not.toHaveBeenCalled(); - /* The following line is a swell debug tool when a code block, - * which is trying to match lines of result messages (like the block below), - * does not get the results that it expects. - * - console.log("convertResult:\n " + JSON.stringify(ConvertV1Profiles["convertResult"], null, 2)); - */ - let numErrMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE && @@ -166,7 +223,7 @@ describe("ConvertV1Profiles tests", () => { numErrMsgsFound++; } } - expect(numErrMsgsFound).toEqual(2); + expect(numErrMsgsFound).toEqual(3); }); }); // end convert @@ -184,21 +241,12 @@ describe("ConvertV1Profiles tests", () => { } as any); } - beforeAll(() => { - jest.restoreAllMocks(); // put spies back to original app implementation - - // do not attempt to actually log any errors - jest.spyOn(Logger, "getImperativeLogger").mockReturnValue({ - error: jest.fn() - } as any); - }); - beforeEach(() => { // create the result normally created by the public function convert() ConvertV1Profiles["convertResult"] = { msgs: [], v1ScsPluginName: null, - reInitCredMgr: false, + credsWereMigrated: true, cfgFilePathNm: ConvertV1Profiles["noCfgFilePathNm"], numProfilesFound: 0, profilesConverted: {}, @@ -211,7 +259,13 @@ describe("ConvertV1Profiles tests", () => { }); describe("isConversionNeeded", () => { - it("should return false if a client config exists", () => { + let getOldProfileCountSpy: any; + + afterAll(() => { + getOldProfileCountSpy.mockRestore(); // restore original app implementation + }); + + it("should return false if a client config exists", async () => { // Pretend that we have a zowe config. Object.defineProperty(ImperativeConfig.instance, "config", { configurable: true, @@ -224,7 +278,7 @@ describe("ConvertV1Profiles tests", () => { // call the function that we want to test // using class["name"] notation because it is a private static function - const convNeeded = ConvertV1Profiles["isConversionNeeded"](); + const convNeeded = await ConvertV1Profiles["isConversionNeeded"](); expect(convNeeded).toEqual(false); let numErrMsgsFound = 0; @@ -240,7 +294,7 @@ describe("ConvertV1Profiles tests", () => { expect(numErrMsgsFound).toEqual(1); }); - it("should return false if we find no V1 profiles", () => { + it("should return false if we find no V1 profiles", async () => { // Pretend that we have no zowe config. Object.defineProperty(ImperativeConfig.instance, "config", { configurable: true, @@ -252,12 +306,12 @@ describe("ConvertV1Profiles tests", () => { }); // pretend that we have no old V1 profiles - const getOldProfileCountSpy = jest.spyOn( + getOldProfileCountSpy = jest.spyOn( ConvertV1Profiles as any, "getOldProfileCount") .mockReturnValueOnce(0); // call the function that we want to test - const convNeeded = ConvertV1Profiles["isConversionNeeded"](); + const convNeeded = await ConvertV1Profiles["isConversionNeeded"](); expect(getOldProfileCountSpy).toHaveBeenCalled(); expect(convNeeded).toEqual(false); @@ -273,7 +327,7 @@ describe("ConvertV1Profiles tests", () => { expect(numErrMsgsFound).toEqual(1); }); - it("should return false if no profiles directory exists", () => { + it("should return false if no profiles directory exists", async () => { // Pretend that we have no zowe config. Object.defineProperty(ImperativeConfig.instance, "config", { configurable: true, @@ -285,33 +339,32 @@ describe("ConvertV1Profiles tests", () => { }); // pretend that an error occurred because the profiles directory did not exist - const profileDir = "/fake/path/to/profiles/"; ConvertV1Profiles["profilesRootDir"] = profileDir; const noDirError = new ImperativeError({ additionalDetails: { code: 'ENOENT' } } as any); - const getOldProfileCountSpy = jest.spyOn(ConvertV1Profiles as any, "getOldProfileCount") + getOldProfileCountSpy = jest.spyOn(ConvertV1Profiles as any, "getOldProfileCount") .mockImplementationOnce(() => { throw noDirError; }); // call the function that we want to test - const convNeeded = ConvertV1Profiles["isConversionNeeded"](); + const convNeeded = await ConvertV1Profiles["isConversionNeeded"](); expect(getOldProfileCountSpy).toHaveBeenCalled(); expect(convNeeded).toEqual(false); - let numErrMsgsFound = 0; + let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE && nextMsg.msgText.includes(`Did not convert any V1 profiles because ` + - `no V1 profiles were found at "${profileDir}"`) + `no V1 profiles were found at ${profileDir}`) ) { - numErrMsgsFound++; + numMsgsFound++; } } - expect(numErrMsgsFound).toEqual(1); + expect(numMsgsFound).toEqual(1); }); - it("should return false if an IO error occurs while reading profiles", () => { + it("should return false if an IO error occurs while reading profiles", async () => { // Pretend that we have no zowe config. Object.defineProperty(ImperativeConfig.instance, "config", { configurable: true, @@ -323,17 +376,16 @@ describe("ConvertV1Profiles tests", () => { }); // pretend that got an I/O error - const profileDir = "/fake/path/to/profiles/"; ConvertV1Profiles["profilesRootDir"] = profileDir; const ioErrMsg = "Fake I/O error occurred"; const ioError = new ImperativeError({ msg: ioErrMsg }); - const getOldProfileCountSpy = jest.spyOn(ConvertV1Profiles as any, "getOldProfileCount") + getOldProfileCountSpy = jest.spyOn(ConvertV1Profiles as any, "getOldProfileCount") .mockImplementationOnce(() => { throw ioError; }); // call the function that we want to test - const convNeeded = ConvertV1Profiles["isConversionNeeded"](); + const convNeeded = await ConvertV1Profiles["isConversionNeeded"](); expect(getOldProfileCountSpy).toHaveBeenCalled(); expect(convNeeded).toEqual(false); @@ -352,7 +404,7 @@ describe("ConvertV1Profiles tests", () => { expect(numErrMsgsFound).toEqual(2); }); - it("should return true if we find some V1 profiles", () => { + it("should return true if we find some V1 profiles", async () => { // Pretend that we have no zowe config. Object.defineProperty(ImperativeConfig.instance, "config", { configurable: true, @@ -364,12 +416,12 @@ describe("ConvertV1Profiles tests", () => { }); // pretend that we have 6 old V1 profiles - const getOldProfileCountSpy = jest.spyOn( + getOldProfileCountSpy = jest.spyOn( ConvertV1Profiles as any, "getOldProfileCount") .mockReturnValueOnce(6); // call the function that we want to test - const convNeeded = ConvertV1Profiles["isConversionNeeded"](); + const convNeeded = await ConvertV1Profiles["isConversionNeeded"](); expect(getOldProfileCountSpy).toHaveBeenCalled(); expect(convNeeded).toEqual(true); @@ -377,6 +429,14 @@ describe("ConvertV1Profiles tests", () => { }); // end isConversionNeeded describe("moveV1ProfilesToConfigFile", () => { + let convertPropNamesSpy: any = jest.fn(); + let createNewConfigFileSpy: any = jest.fn(); + + afterAll(() => { + // restore original app implementations + convertPropNamesSpy.mockRestore(); + createNewConfigFileSpy.mockRestore(); + }); it("should successfully move multiple v1 profiles to a config file", async () => { jest.spyOn(V1ProfileRead, "getAllProfileDirectories").mockReturnValueOnce(["fruit", "nut"]); @@ -393,9 +453,9 @@ describe("ConvertV1Profiles tests", () => { .mockReturnValueOnce({ unitPrice: 1 }) .mockReturnValueOnce({ unitPrice: 5 }) .mockReturnValueOnce({ unitPrice: 2 }); - jest.spyOn(ConvertV1Profiles as any, "convertPropNames") + convertPropNamesSpy = jest.spyOn(ConvertV1Profiles as any, "convertPropNames") .mockImplementation(jest.fn()); - jest.spyOn(ConvertV1Profiles as any, "createNewConfigFile") + createNewConfigFileSpy = jest.spyOn(ConvertV1Profiles as any, "createNewConfigFile") .mockImplementation(jest.fn()); // Avoid using the real secure credMgr. Pretend it works. @@ -433,9 +493,9 @@ describe("ConvertV1Profiles tests", () => { .mockImplementationOnce(() => ({ color: "green", secret: "managed by A" })) .mockImplementationOnce(() => { throw profileError; }) .mockImplementationOnce(() => ({ color: "brown", secret: "managed by C" })); - jest.spyOn(ConvertV1Profiles as any, "convertPropNames") + convertPropNamesSpy = jest.spyOn(ConvertV1Profiles as any, "convertPropNames") .mockImplementation(jest.fn()); - jest.spyOn(ConvertV1Profiles as any, "createNewConfigFile") + createNewConfigFileSpy = jest.spyOn(ConvertV1Profiles as any, "createNewConfigFile") .mockImplementation(jest.fn()); // Avoid using the real secure credMgr. Pretend it fails. @@ -488,7 +548,6 @@ describe("ConvertV1Profiles tests", () => { }, autoStore: true }; - jest.restoreAllMocks(); // put spies back to original app implementation // call the function that we want to test ConvertV1Profiles["convertPropNames"](testConfig); @@ -521,15 +580,14 @@ describe("ConvertV1Profiles tests", () => { autoStore: true }; + let loadV1SchemasSpy: any = jest.fn(); + let updateSchemaSpy: any = jest.fn(); let activateSpy: any; let mergeSpy: any; let saveSpy: any; - let updateSchemaSpy: any; let layerActiveSpy: any; beforeAll(() => { - jest.restoreAllMocks(); // put spies back to original app implementation - // Pretend that our utility functions work. activateSpy = jest.fn(); mergeSpy = jest.fn(); @@ -542,8 +600,8 @@ describe("ConvertV1Profiles tests", () => { properties: null as any, }); - updateSchemaSpy = jest.spyOn(ConfigSchema, "updateSchema") - .mockReturnValue(0 as any); + loadV1SchemasSpy = jest.spyOn(ConvertV1Profiles as any, "loadV1Schemas").mockReturnValue(void 0); + updateSchemaSpy = jest.spyOn(ConfigSchema, "updateSchema").mockReturnValue(0 as any); jest.spyOn(ImperativeConfig.instance, "config", "get").mockReturnValue({ api: { @@ -557,6 +615,18 @@ describe("ConvertV1Profiles tests", () => { } as any); }); + beforeEach(() => { + // reset usage counts + loadV1SchemasSpy.mockClear(); + updateSchemaSpy.mockClear(); + }); + + afterAll(() => { + // restore original app implementations + loadV1SchemasSpy.mockRestore(); + updateSchemaSpy.mockRestore(); + }); + it("should create a config file and report movement of old profiles", async () => { // we report movement when we do not delete ConvertV1Profiles["convertOpts"] = { @@ -572,6 +642,7 @@ describe("ConvertV1Profiles tests", () => { expect(activateSpy).toHaveBeenCalled(); expect(mergeSpy).toHaveBeenCalled(); + expect(loadV1SchemasSpy).toHaveBeenCalled(); expect(updateSchemaSpy).toHaveBeenCalled(); expect(saveSpy).toHaveBeenCalled(); expect(renameSpy).toHaveBeenCalled(); @@ -580,7 +651,7 @@ describe("ConvertV1Profiles tests", () => { for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE) { if (nextMsg.msgText.includes("Your old V1 profiles have been moved") && - nextMsg.msgText.includes("Delete them by re-running this operation and requesting deletion") + nextMsg.msgText.includes("Delete them by re-running this operation and requesting deletion") || nextMsg.msgText.includes("Your new profiles have been saved") && nextMsg.msgText.includes("To change your configuration, update that file in your text editor") @@ -607,6 +678,7 @@ describe("ConvertV1Profiles tests", () => { expect(activateSpy).toHaveBeenCalled(); expect(mergeSpy).toHaveBeenCalled(); + expect(loadV1SchemasSpy).toHaveBeenCalled(); expect(updateSchemaSpy).toHaveBeenCalled(); expect(saveSpy).toHaveBeenCalled(); expect(renameSpy).toHaveBeenCalled(); @@ -626,6 +698,48 @@ describe("ConvertV1Profiles tests", () => { expect(numMsgsFound).toEqual(1); }); + it("should create a config vault if needed", async () => { + // make the vault non-existent + const configObj = ImperativeConfig.instance.config; + configObj["mVault"] = null as any; + + // request that we do not delete profiles + ConvertV1Profiles["convertOpts"] = { + deleteV1Profs: false + }; + + // pretend that rename worked + const renameSpy = jest.spyOn(fs, "renameSync") + .mockReturnValue(0 as any); + + // call the function that we want to test + await ConvertV1Profiles["createNewConfigFile"](testConfig); + + expect(ImperativeConfig.instance.config["mVault"]).not.toEqual(null); + expect(activateSpy).toHaveBeenCalled(); + expect(mergeSpy).toHaveBeenCalled(); + expect(loadV1SchemasSpy).toHaveBeenCalled(); + expect(updateSchemaSpy).toHaveBeenCalled(); + expect(saveSpy).toHaveBeenCalled(); + expect(renameSpy).toHaveBeenCalled(); + + // prevent calling the real underlying credentialManager factory load and save functions + Object.defineProperty(CredentialManagerFactory, "manager", { + configurable: true, + get: jest.fn(() => { + return { + configurable: true, + load: jest.fn(() => { }), + save: jest.fn(() => { }) + }; + }) + }); + + // get coverage of the load and save functions of the vault + await ImperativeConfig.instance.config.mVault.load(appName); + await ImperativeConfig.instance.config.mVault.save("name", "value"); + }); + it("should catch and report a problem when rename throws an error", async () => { // we were asked to delete ConvertV1Profiles["convertOpts"] = { @@ -649,6 +763,7 @@ describe("ConvertV1Profiles tests", () => { expect(caughtErr).not.toBeDefined(); expect(activateSpy).toHaveBeenCalled(); expect(mergeSpy).toHaveBeenCalled(); + expect(loadV1SchemasSpy).toHaveBeenCalled(); expect(updateSchemaSpy).toHaveBeenCalled(); expect(saveSpy).toHaveBeenCalled(); expect(renameSpy).toHaveBeenCalled(); @@ -663,13 +778,14 @@ describe("ConvertV1Profiles tests", () => { } } else { if (nextMsg.msgText.includes("Failed to rename profiles directory") || - nextMsg.msgText.includes(renameError) + nextMsg.msgText.includes(`Reason: ${renameError}`) || + nextMsg.msgText.includes(`Error: ${renameError}`) ) { numMsgsFound++; } } } - expect(numMsgsFound).toEqual(3); + expect(numMsgsFound).toEqual(4); }); }); // end createNewConfigFile @@ -700,28 +816,49 @@ describe("ConvertV1Profiles tests", () => { }); // end putCfgFileNmInResult describe("deleteV1Profiles", () => { + let isZoweKeyRingAvailableSpy: any = jest.fn(); + let findOldSecurePropsSpy: any = jest.fn(); + let deleteOldSecurePropsSpy: any = jest.fn(); + + afterAll(() => { + // restore original app implementations + isZoweKeyRingAvailableSpy.mockRestore(); + findOldSecurePropsSpy.mockRestore(); + deleteOldSecurePropsSpy.mockRestore(); + }); + const oldProfileDir = "/fake/path/to/profiles-old"; - let existsSyncSpy: any = jest.fn(); - let removeSyncSpy: any = jest.fn(); + let existsSyncSpy: any; + let removeSyncSpy: any; + + beforeAll(() => { + ConvertV1Profiles["oldProfilesDir"] = oldProfileDir; + existsSyncSpy = jest.spyOn(fs, "existsSync"); + removeSyncSpy = jest.spyOn(fsExtra, "removeSync"); + }); beforeEach(() => { + // pretend that remove works + removeSyncSpy.mockReturnValue(0 as any); + + // reset usage counts + existsSyncSpy.mockClear(); + removeSyncSpy.mockClear(); + }); + + afterAll(() => { // restore original app implementations existsSyncSpy.mockRestore(); removeSyncSpy.mockRestore(); - - ConvertV1Profiles["oldProfilesDir"] = oldProfileDir; }); it("should delete the old v1 profiles directory", async () => { // pretend that we found no secure property names under any old-school service - jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") + findOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") .mockResolvedValue(Promise.resolve([])); // pretend that the profiles directory exists - existsSyncSpy = jest.spyOn(fs, "existsSync").mockReturnValue(true); - - // pretend that remove worked - removeSyncSpy = jest.spyOn(fsExtra, "removeSync").mockReturnValue(0 as any); + existsSyncSpy.mockReturnValue(true); // call the function that we want to test await ConvertV1Profiles["deleteV1Profiles"](); @@ -730,7 +867,7 @@ describe("ConvertV1Profiles tests", () => { let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE) { - if (nextMsg.msgText.includes(`Deleted the old profiles directory '${oldProfileDir}'`)) { + if (nextMsg.msgText.includes(`Deleted the old profiles directory ${oldProfileDir}`)) { numMsgsFound++; } } @@ -740,14 +877,11 @@ describe("ConvertV1Profiles tests", () => { it("should report that the old v1 profiles directory does not exist", async () => { // pretend that we found no secure property names under any old-school service - jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") + findOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") .mockResolvedValue(Promise.resolve([])); // pretend that the profiles directory not exist - existsSyncSpy = jest.spyOn(fs, "existsSync").mockReturnValue(false); - - // pretend that remove works - removeSyncSpy = jest.spyOn(fsExtra, "removeSync").mockReturnValue(0 as any); + existsSyncSpy.mockReturnValue(false); // call the function that we want to test await ConvertV1Profiles["deleteV1Profiles"](); @@ -756,7 +890,7 @@ describe("ConvertV1Profiles tests", () => { let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE) { - if (nextMsg.msgText.includes(`The old profiles directory '${oldProfileDir}' did not exist.`)) { + if (nextMsg.msgText.includes(`The old profiles directory ${oldProfileDir} did not exist.`)) { numMsgsFound++; } } @@ -766,15 +900,15 @@ describe("ConvertV1Profiles tests", () => { it("should catch and report a problem when remove throws an error", async () => { // pretend that we found no secure property names under any old-school service - jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") + findOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") .mockResolvedValue(Promise.resolve([])); // pretend that the profiles directory exists - existsSyncSpy = jest.spyOn(fs, "existsSync").mockReturnValue(true); + existsSyncSpy.mockReturnValue(true); // pretend that remove crashed const removeError = "fsExtra.removeSync threw a horrible error"; - removeSyncSpy = jest.spyOn(fsExtra, "removeSync").mockImplementation(() => { + removeSyncSpy.mockImplementation(() => { throw new Error(removeError); }); @@ -803,21 +937,19 @@ describe("ConvertV1Profiles tests", () => { it("should also delete credentials stored by old SCS plugin", async () => { // pretend that the zowe keyring is available - jest.spyOn(ConvertV1Profiles as any, "checkZoweKeyRingAvailable") + isZoweKeyRingAvailableSpy = jest.spyOn(ConvertV1Profiles as any, "isZoweKeyRingAvailable") .mockResolvedValue(Promise.resolve(true)); // pretend that we found secure property names under one old-school service - jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") - .mockResolvedValueOnce(Promise.resolve(["secureUser", "securePassword"])); + findOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") + .mockResolvedValueOnce(Promise.resolve(["secureUser", "securePassword"])) + .mockResolvedValue(Promise.resolve([])); - jest.spyOn(ConvertV1Profiles as any, "deleteOldSecureProps") + deleteOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "deleteOldSecureProps") .mockResolvedValue(Promise.resolve(true)); // pretend that the profiles directory exists - existsSyncSpy = jest.spyOn(fs, "existsSync").mockReturnValue(true); - - // pretend that remove worked - removeSyncSpy = jest.spyOn(fsExtra, "removeSync").mockReturnValue(0 as any); + existsSyncSpy.mockReturnValue(true); // call the function that we want to test await ConvertV1Profiles["deleteV1Profiles"](); @@ -826,7 +958,7 @@ describe("ConvertV1Profiles tests", () => { let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE) { - if (nextMsg.msgText.includes("Deleted secure value for ") && + if (nextMsg.msgText.includes("Deleted obsolete secure value ") && ( nextMsg.msgText.includes("secureUser") || nextMsg.msgText.includes("securePassword") @@ -841,22 +973,20 @@ describe("ConvertV1Profiles tests", () => { it("should report an error when we fail to delete secure credentials", async () => { // pretend that the zowe keyring is available - jest.spyOn(ConvertV1Profiles as any, "checkZoweKeyRingAvailable") + isZoweKeyRingAvailableSpy = jest.spyOn(ConvertV1Profiles as any, "isZoweKeyRingAvailable") .mockResolvedValue(Promise.resolve(true)); // pretend that we found secure property names under one old-school service - jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") - .mockResolvedValueOnce(Promise.resolve(["secureUser", "securePassword"])); + findOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") + .mockResolvedValueOnce(Promise.resolve(["secureUser", "securePassword"])) + .mockResolvedValue(Promise.resolve([])); // pretend that secure credential deletion failed - jest.spyOn(ConvertV1Profiles as any, "deleteOldSecureProps") + deleteOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "deleteOldSecureProps") .mockResolvedValue(Promise.resolve(false)); // pretend that the profiles directory exists - existsSyncSpy = jest.spyOn(fs, "existsSync").mockReturnValue(true); - - // pretend that remove worked - removeSyncSpy = jest.spyOn(fsExtra, "removeSync").mockReturnValue(0 as any); + existsSyncSpy.mockReturnValue(true); // call the function that we want to test await ConvertV1Profiles["deleteV1Profiles"](); @@ -864,7 +994,7 @@ describe("ConvertV1Profiles tests", () => { let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { - if (nextMsg.msgText.includes("Failed to delete secure value") && + if (nextMsg.msgText.includes("Failed to delete obsolete secure value") && ( nextMsg.msgText.includes("secureUser") || nextMsg.msgText.includes("securePassword") @@ -877,49 +1007,51 @@ describe("ConvertV1Profiles tests", () => { expect(numMsgsFound).toEqual(2); }); - it("should report an error when the zowe keyring is unavailable", async () => { + it("should only report directory deletion when zowe keyring is unavailable", async () => { // pretend that the zowe keyring is unavailable - const checkKeyRingSpy = jest.spyOn(ConvertV1Profiles as any, "checkZoweKeyRingAvailable") + isZoweKeyRingAvailableSpy = jest.spyOn(ConvertV1Profiles as any, "isZoweKeyRingAvailable") .mockResolvedValue(Promise.resolve(false)); // pretend that the profiles directory exists - existsSyncSpy = jest.spyOn(fs, "existsSync").mockReturnValue(true); - - // pretend that remove worked - removeSyncSpy = jest.spyOn(fsExtra, "removeSync").mockReturnValue(0 as any); + existsSyncSpy.mockReturnValue(true); // call the function that we want to test await ConvertV1Profiles["deleteV1Profiles"](); expect(removeSyncSpy).toHaveBeenCalled(); - let numMsgsFound = 0; + let numDirDelMsgs = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { - if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { - if (nextMsg.msgText.includes( - "Zowe keyring or the credential vault are unavailable. Unable to delete old secure values")) - { - numMsgsFound++; - } + if (nextMsg.msgText.includes(`Deleted the old profiles directory ${oldProfileDir}`)) { + numDirDelMsgs++; } } - expect(numMsgsFound).toEqual(1); - checkKeyRingSpy.mockRestore(); // restore original app implementation + expect(ConvertV1Profiles["convertResult"].msgs.length).toEqual(1); + expect(numDirDelMsgs).toEqual(1); }); }); // end deleteV1Profiles - describe("removeOldOverrides", () => { + describe("replaceOldCredMgrOverride", () => { + let getOldPluginInfoSpy: any; + + beforeAll(() => { + getOldPluginInfoSpy = jest.spyOn(ConvertV1Profiles as any, "getOldPluginInfo"); + }); + + afterAll(() => { + // restore original app implementations + getOldPluginInfoSpy.mockRestore(); + }); it("should do nothing if there are no overrides", () => { // pretend that no overrides exist - jest.spyOn(ConvertV1Profiles as any, "getOldPluginInfo") - .mockReturnValueOnce({ plugins: [], overrides: [] }); + getOldPluginInfoSpy.mockReturnValueOnce({ plugins: [], overrides: [] }); const appSettingsGetSpy = jest.spyOn(AppSettings, "instance", "get"); // call the function that we want to test let caughtErr: any; try { - ConvertV1Profiles["removeOldOverrides"](); + ConvertV1Profiles["replaceOldCredMgrOverride"](); } catch (err) { caughtErr = err; } @@ -931,7 +1063,7 @@ describe("ConvertV1Profiles tests", () => { it("should replace a v1 SCS credential manager and report a v1 SCS plugin", async () => { // pretend that we found an old credential manager const fakeV1ScsPlugin = "FakeScsPlugin"; - jest.spyOn(ConvertV1Profiles as any, "getOldPluginInfo").mockReturnValueOnce( + getOldPluginInfoSpy.mockReturnValueOnce( { plugins: [fakeV1ScsPlugin], overrides: ["CredentialManager"] } ); @@ -948,29 +1080,10 @@ describe("ConvertV1Profiles tests", () => { } } as any); - // mock getter properties, so that we avoid real ImperativeConfig actions - Object.defineProperty(ImperativeConfig.instance, "cliHome", { - configurable: true, - get: jest.fn(() => { - return "/fake/cliHome"; - }) - }); - Object.defineProperty(ImperativeConfig.instance, "config", { - configurable: true, - set: jest.fn() - }); - - // avoid running the real Config.load and OverridesLoader.load - const configLoadSpy = jest.spyOn(Config, "load").mockResolvedValue(Config.empty() as any); - const overridesLoaderLoadSpy = jest.spyOn(OverridesLoader, "load").mockResolvedValue(Promise.resolve()); - - // Avoid using the real secure credMgr. Pretend it works. - setCredMgrState("works"); - // call the function that we want to test let caughtErr: any; try { - await ConvertV1Profiles["removeOldOverrides"](); + await ConvertV1Profiles["replaceOldCredMgrOverride"](); } catch (err) { caughtErr = err; } @@ -979,61 +1092,30 @@ describe("ConvertV1Profiles tests", () => { expect(appSettingsSetSpy).toHaveBeenCalledWith( "overrides", "CredentialManager", CredentialManagerOverride.DEFAULT_CRED_MGR_NAME ); - expect(configLoadSpy).toHaveBeenCalled(); - expect(overridesLoaderLoadSpy).toHaveBeenCalled(); expect(ConvertV1Profiles["convertResult"].v1ScsPluginName).toEqual(fakeV1ScsPlugin); - expect(ConvertV1Profiles["convertResult"].reInitCredMgr).toEqual(false); + expect(ConvertV1Profiles["convertResult"].credsWereMigrated).toEqual(true); }); - it("should report when credMgr must be re-initialized", async () => { + it("should catch and report an error thrown by AppSettings.instance.set", () => { // pretend that we found an old credential manager const fakeV1ScsPlugin = "FakeScsPlugin"; - jest.spyOn(ConvertV1Profiles as any, "getOldPluginInfo").mockReturnValueOnce( + getOldPluginInfoSpy.mockReturnValueOnce( { plugins: [fakeV1ScsPlugin], overrides: ["CredentialManager"] } ); - // pretend that we set the credMgr - const appSettingsSetSpy = jest.fn(); + // pretend that AppSettings.set() throws an exception + const appSettingsCrashErrMsg = "A fake exception from AppSettings.instance.set"; + const appSettingsSetSpy = jest.fn().mockImplementation(() => { + throw new Error(appSettingsCrashErrMsg); + }); jest.spyOn(AppSettings, "instance", "get").mockReturnValue({ set: appSettingsSetSpy } as any); - // pretend that our loadedConfig has a credMgr override - jest.spyOn(ImperativeConfig.instance, "loadedConfig", "get").mockReturnValue({ - overrides: { - CredentialManager: "CfgMgrOverride" - } - } as any); - - // mock getter properties, so that we avoid real ImperativeConfig actions - Object.defineProperty(ImperativeConfig.instance, "cliHome", { - configurable: true, - get: jest.fn(() => { - return "/fake/cliHome"; - }) - }); - Object.defineProperty(ImperativeConfig.instance, "config", { - configurable: true, - set: jest.fn() - }); - - // avoid running the real Config.load and OverridesLoader.load - const configLoadSpy = jest.spyOn(Config, "load").mockResolvedValue(Config.empty() as any); - const overridesLoaderLoadSpy = jest.spyOn(OverridesLoader, "load").mockResolvedValue(Promise.resolve()); - - // Avoid using the real secure credMgr. Pretend it works. - setCredMgrState("works"); - - // pretend that the CredMgr has been initialized - Object.defineProperty(CredentialManagerFactory, "initialized", { - configurable: true, - get: jest.fn(() => true) - }); - // call the function that we want to test let caughtErr: any; try { - await ConvertV1Profiles["removeOldOverrides"](); + ConvertV1Profiles["replaceOldCredMgrOverride"](); } catch (err) { caughtErr = err; } @@ -1043,64 +1125,32 @@ describe("ConvertV1Profiles tests", () => { "overrides", "CredentialManager", CredentialManagerOverride.DEFAULT_CRED_MGR_NAME ); expect(ConvertV1Profiles["convertResult"].v1ScsPluginName).toEqual(fakeV1ScsPlugin); - expect(configLoadSpy).not.toHaveBeenCalled(); - expect(overridesLoaderLoadSpy).not.toHaveBeenCalled(); - expect(ConvertV1Profiles["convertResult"].reInitCredMgr).toEqual(true); - let numMsgsFound = 0; - for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { - if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE) { - if (nextMsg.msgText.includes( - "The following plug-ins will be removed because they are now part of the core CLI and are no longer needed") || - fakeV1ScsPlugin - ) { - numMsgsFound++; - } - } - } - expect(numMsgsFound).toEqual(2); - }); - - it("should catch and report an error thrown by AppSettings.instance.set", () => { - // pretend that we found an old credential manager - jest.spyOn(ConvertV1Profiles as any, "getOldPluginInfo").mockReturnValueOnce( - { plugins: [], overrides: ["CredentialManager"] } - ); - - // pretend that AppSettings.instance.set throws an exception - const fakeErrMsg = "A fake exception from AppSettings.instance.set"; - const appSettingsSetSpy = jest.fn().mockImplementation(() => { - throw new Error(fakeErrMsg); - }); - jest.spyOn(AppSettings, "instance", "get").mockReturnValue({ - set: appSettingsSetSpy - } as any); - - // call the function that we want to test - let caughtErr: any; - try { - ConvertV1Profiles["removeOldOverrides"](); - } catch (err) { - caughtErr = err; - } - - expect(appSettingsSetSpy).toHaveBeenCalled(); - expect(caughtErr).not.toBeDefined(); - + expect(ConvertV1Profiles["convertResult"].credsWereMigrated).toEqual(false); let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { if (nextMsg.msgText.includes("Failed to replace credential manager override setting") || - nextMsg.msgText.includes(fakeErrMsg) + nextMsg.msgText.includes(`Reason: ${appSettingsCrashErrMsg}`) || + nextMsg.msgText.includes(`Error: ${appSettingsCrashErrMsg}`) ) { numMsgsFound++; } } } - expect(numMsgsFound).toEqual(2); + expect(numMsgsFound).toEqual(3); }); - }); // end removeOldOverrides + }); // end replaceOldCredMgrOverride describe("getOldPluginInfo", () => { + let isPluginInstalledSpy: any; + + beforeEach(() => { + isPluginInstalledSpy = jest.spyOn(ConvertV1Profiles as any, "isPluginInstalled"); + }); + + afterAll(() => { + isPluginInstalledSpy.mockRestore(); // restore original app implementation + }); it("should retrieve old credMgr override and old plugin", () => { // pretend that we find the old SCS CredMgr name @@ -1110,14 +1160,8 @@ describe("ConvertV1Profiles tests", () => { get: appSettingsGetSpy } as any); - // pretend that PluginIssues.instance.getInstalledPlugins returns the name of the old SCS - const getPluginsSpy = jest.fn().mockReturnValue({ - [oldScsName]: {}, - "AnIrrelevantPluginName": {} - } as any); - jest.spyOn(PluginIssues, "instance", "get").mockReturnValue({ - getInstalledPlugins: getPluginsSpy - } as any); + // pretend that the old zowe SCS plugin is installed + isPluginInstalledSpy.mockReturnValue(true); // call the function that we want to test let pluginInfo: any; @@ -1130,7 +1174,6 @@ describe("ConvertV1Profiles tests", () => { expect(caughtErr).not.toBeDefined(); expect(appSettingsGetSpy).toHaveBeenCalled(); - expect(getPluginsSpy).toHaveBeenCalled(); let numItemsFound = 0; for (const nextOverride of pluginInfo.overrides) { @@ -1149,6 +1192,31 @@ describe("ConvertV1Profiles tests", () => { expect(numItemsFound).toEqual(1); }); + it("should initialize appSettings when AppSettings.instance fails", () => { + // pretend that AppSettings.instance.get crashes + const appSettingsGetSpy = jest.spyOn(AppSettings, "instance", "get").mockImplementation(() => { + throw new Error("Error does not matter"); + }); + + // pretend that the old zowe SCS plugin is installed + isPluginInstalledSpy.mockReturnValue(true); + + // call the function that we want to test + let pluginInfo: any; + let caughtErr: any; + try { + pluginInfo = ConvertV1Profiles["getOldPluginInfo"](); + } catch (err) { + caughtErr = err; + } + + expect(caughtErr).not.toBeDefined(); + expect(isPluginInstalledSpy).toHaveBeenCalled(); + expect(appSettingsGetSpy).toHaveBeenCalled(); + expect(pluginInfo.overrides.length).toEqual(0); + expect(pluginInfo.plugins.length).toEqual(1); + }); + it("should catch exception from AppSettings.instance.get and record error", () => { // pretend that AppSettings.instance.get crashes const fakeErrMsg = "A fake exception from AppSettings.instance.get"; @@ -1159,11 +1227,8 @@ describe("ConvertV1Profiles tests", () => { get: appSettingsGetSpy } as any); - // pretend that PluginIssues.instance.getInstalledPlugins returns no plugins - const getPluginsSpy = jest.fn().mockReturnValue({} as any); - jest.spyOn(PluginIssues, "instance", "get").mockReturnValue({ - getInstalledPlugins: getPluginsSpy - } as any); + // pretend that the old zowe SCS plugin is installed + isPluginInstalledSpy.mockReturnValue(true); // call the function that we want to test let pluginInfo: any; @@ -1176,24 +1241,24 @@ describe("ConvertV1Profiles tests", () => { expect(caughtErr).not.toBeDefined(); expect(appSettingsGetSpy).toHaveBeenCalled(); - expect(getPluginsSpy).toHaveBeenCalled(); expect(pluginInfo.overrides.length).toEqual(0); - expect(pluginInfo.plugins.length).toEqual(0); + expect(pluginInfo.plugins.length).toEqual(1); let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { if (nextMsg.msgText.includes("Failed trying to read 'CredentialManager' overrides.") || - nextMsg.msgText.includes(fakeErrMsg) + nextMsg.msgText.includes(`Reason: ${fakeErrMsg}`) || + nextMsg.msgText.includes(`Error: ${fakeErrMsg}`) ) { numMsgsFound++; } } } - expect(numMsgsFound).toEqual(2); + expect(numMsgsFound).toEqual(3); }); - it("should catch exception from PluginIssues.instance.getInstalledPlugins and record error", () => { + it("should catch exception from isPluginInstalled and record error", () => { // pretend that we find the old SCS CredMgr name const oldScsName = "@zowe/secure-credential-store-for-zowe-cli"; const appSettingsGetSpy = jest.fn().mockReturnValue(oldScsName); @@ -1201,14 +1266,11 @@ describe("ConvertV1Profiles tests", () => { get: appSettingsGetSpy } as any); - // pretend that PluginIssues.instance.getInstalledPlugins crashes - const fakeErrMsg = "A fake exception from PluginIssues.instance.getInstalledPlugins"; - const getPluginsSpy = jest.fn().mockImplementation(() => { - throw new Error(fakeErrMsg); - }); - jest.spyOn(PluginIssues, "instance", "get").mockReturnValue({ - getInstalledPlugins: getPluginsSpy - } as any); + // pretend that isPluginInstalled throws an error + const caughtErrMsg = "isPluginInstalled threw a horrible exception"; + isPluginInstalledSpy.mockImplementation(jest.fn(() => { + throw new Error(caughtErrMsg); + })); // call the function that we want to test let pluginInfo: any; @@ -1220,31 +1282,173 @@ describe("ConvertV1Profiles tests", () => { } expect(caughtErr).not.toBeDefined(); - expect(appSettingsGetSpy).toHaveBeenCalled(); - expect(getPluginsSpy).toHaveBeenCalled(); - expect(pluginInfo.plugins.length).toEqual(0); - let numItemsFound = 0; - for (const nextOverride of pluginInfo.overrides) { - if (nextOverride === "CredentialManager") { - numItemsFound++; + let numMsgsFound = 0; + for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { + if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { + if (nextMsg.msgText.includes("Failed trying to get the set of installed plugins") || + nextMsg.msgText.includes(`Reason: ${caughtErrMsg}`) || + nextMsg.msgText.includes(`Error: ${caughtErrMsg}`) + ) { + numMsgsFound++; + } } } - expect(numItemsFound).toEqual(1); + expect(numMsgsFound).toEqual(3); + }); + }); // end getOldPluginInfo - numItemsFound = 0; + describe("isPluginInstalled", () => { + let readFileSyncSpy: any; + + beforeAll(() => { + readFileSyncSpy = jest.spyOn(jsonfile, "readFileSync"); + + // cliHome is a getter property, so mock the property. + Object.defineProperty(ImperativeConfig.instance, "cliHome", { + configurable: true, + get: jest.fn(() => { + return "/fake/cliHome"; + }) + }); + }); + + afterAll(() => { + readFileSyncSpy.mockRestore(); // restore original app implementation + }); + + it("should return true if plugin name is in the plugins file", () => { + // make readFileSync return some fake data + const pluginName = "FakePluginName"; + const fakePluginsJson = JSON.parse(`{ + "@zowe/secure-credential-store-for-zowe-cli": { + "package": "@zowe/secure-credential-store-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.1.12" + }, + "@zowe/cics-for-zowe-cli": { + "package": "@zowe/cics-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.0.11" + }, + "${pluginName}": { + "package": "@zowe/${pluginName}-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "1.2.3" + } + }`); + readFileSyncSpy.mockImplementation(() => { + return fakePluginsJson; + }); + + // call the function that we want to test + const pluginInstResult: boolean = ConvertV1Profiles["isPluginInstalled"](pluginName); + expect(pluginInstResult).toEqual(true); + }); + + it("should return false if plugin name is NOT in the plugins file", () => { + // make readFileSync return some fake data + const fakePluginsJson = JSON.parse(`{ + "@zowe/secure-credential-store-for-zowe-cli": { + "package": "@zowe/secure-credential-store-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.1.12" + }, + "@zowe/cics-for-zowe-cli": { + "package": "@zowe/cics-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.0.11" + }, + "@zowe/not-your-plugin": { + "package": "@zowe/these-are-not-the-droids-you-are-looking-for@zowe-v2-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.0.11" + } + }`); + readFileSyncSpy.mockImplementation(() => { + return fakePluginsJson; + }); + + // call the function that we want to test + const pluginInstResult: boolean = ConvertV1Profiles["isPluginInstalled"]("PluginNameNotInstalled"); + expect(pluginInstResult).toEqual(false); + }); + + it("should catch exception from readFileSync and record error for CLI", () => { + // pretend that readFileSync throws an error + const readFileErrMsg = "readFileSync threw some horrible exception"; + readFileSyncSpy.mockImplementation(jest.fn(() => { + throw new Error(readFileErrMsg); + })); + + // call the function that we want to test + const pluginName = "FakePluginName"; + let pluginInstResult: boolean = false; + let caughtErr: any; + try { + pluginInstResult = ConvertV1Profiles["isPluginInstalled"](pluginName); + } catch (err) { + caughtErr = err; + } + + expect(caughtErr).not.toBeDefined(); + expect(pluginInstResult).toEqual(false); + + let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { - if (nextMsg.msgText.includes("Failed trying to get the set of installed plugins") || - nextMsg.msgText.includes(fakeErrMsg) + if (nextMsg.msgText.includes("Cannot read plugins file") && nextMsg.msgText.includes("plugins.json") ) { + numMsgsFound++; + } + if ((nextMsg.msgText.includes("Reason: ") || nextMsg.msgText.includes("Error: ")) && + nextMsg.msgText.includes(readFileErrMsg) ) { - numItemsFound++; + numMsgsFound++; } } } - expect(numItemsFound).toEqual(2); + expect(numMsgsFound).toEqual(3); }); - }); // end getOldPluginInfo + + it("should catch exception from readFileSync but not record error for VSCode app", () => { + // pretend that readFileSync throws an error + const readFileErrMsg = "readFileSync threw some horrible exception"; + readFileSyncSpy.mockImplementation(jest.fn(() => { + throw new Error(readFileErrMsg); + })); + + // pretend that we were called by a VSCode app + ConvertV1Profiles["profileInfo"] = new ProfileInfo(appName); + + // call the function that we want to test + const pluginName = "FakePluginName"; + let pluginInstResult: boolean = false; + let caughtErr: any; + try { + pluginInstResult = ConvertV1Profiles["isPluginInstalled"](pluginName); + } catch (err) { + caughtErr = err; + } + + expect(caughtErr).not.toBeDefined(); + expect(pluginInstResult).toEqual(false); + + let numMsgsFound = 0; + for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { + if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { + if (nextMsg.msgText.includes("Cannot read plugins file") && nextMsg.msgText.includes("plugins.json")) { + numMsgsFound++; + } + if ((nextMsg.msgText.includes("Reason: ") || nextMsg.msgText.includes("Error: ")) && + nextMsg.msgText.includes(readFileErrMsg) + ) { + numMsgsFound++; + } + } + } + expect(numMsgsFound).toEqual(0); + }); + }); // end isPluginInstalled describe("getOldProfileCount", () => { @@ -1265,7 +1469,231 @@ describe("ConvertV1Profiles tests", () => { }); }); // end getOldProfileCount - describe("checkZoweKeyRingAvailable", () => { + describe("initCredMgr", () => { + let readProfilesFromDiskSpy: any = jest.fn(); + + beforeEach(() => { + // Reset the messages that have been reported + ConvertV1Profiles["convertResult"].msgs = []; + }); + + afterEach(() => { + jest.clearAllMocks(); // clear the mock counters + }); + + afterAll(() => { + // restore original app implementations + readProfilesFromDiskSpy.mockRestore(); + }); + + it("should detect when credMgr has already been initialized", async () => { + // change logger spy to record the message + let logMsg: string = "Nothing logged"; + jest.spyOn(Logger, "getImperativeLogger").mockImplementation(() => { + return { + error: jest.fn((errMsg) => { + logMsg = errMsg; + }) + } as any; + }); + + // pretend that credMgr has been initialized. + let initializedWasCalled = false; + Object.defineProperty(CredentialManagerFactory, "initialized", { + configurable: true, + get: jest.fn(() => { + initializedWasCalled = true; + return true; + }) + }); + + // pretend that the SCS plugin was configured as the credMgr + ConvertV1Profiles["oldScsPluginWasConfigured"] = true; + + // call the function that we want to test + await ConvertV1Profiles["initCredMgr"](); + + expect(initializedWasCalled).toEqual(true); + expect(logMsg).toContain( + `Credential manager has already been initialized with the old SCS plugin ${oldScsPluginNm}. ` + + `Old credentials cannot be migrated` + ); + }); + + it("should read profiles from disk when ProfileInfo is supplied", async () => { + // pretend that credMgr has NOT been initialized. + Object.defineProperty(CredentialManagerFactory, "initialized", { + configurable: true, + get: jest.fn(() => { + return false; + }) + }); + + // pretend that the SCS plugin was configured as the credMgr + ConvertV1Profiles["oldScsPluginWasConfigured"] = true; + + // pretend that our caller supplied ProfileInfo + ConvertV1Profiles["profileInfo"] = new ProfileInfo(appName); + + // do not actually read any ProfileInfo from disk + readProfilesFromDiskSpy = jest.spyOn(ConvertV1Profiles["profileInfo"], + "readProfilesFromDisk").mockResolvedValue(Promise.resolve()); + + // call the function that we want to test + await ConvertV1Profiles["initCredMgr"](); + expect(readProfilesFromDiskSpy).toHaveBeenCalled(); + }); + + it("should call overridesLoader when ProfileInfo is NOT supplied", async () => { + // pretend that credMgr has NOT been initialized. + Object.defineProperty(CredentialManagerFactory, "initialized", { + configurable: true, + get: jest.fn(() => { + return false; + }) + }); + + // do not actually load the overrides + ConvertV1Profiles["profileInfo"] = null as any; + const overridesLoaderSpy = jest.spyOn(OverridesLoader, "load"); + + // call the function that we want to test + await ConvertV1Profiles["initCredMgr"](); + expect(overridesLoaderSpy).toHaveBeenCalled(); + }); + + it("should catch an exception and report the error", async () => { + // pretend that credMgr has NOT been initialized. + Object.defineProperty(CredentialManagerFactory, "initialized", { + configurable: true, + get: jest.fn(() => { + return false; + }) + }); + + // do not actually read any ProfileInfo from disk + ConvertV1Profiles["profileInfo"] = new ProfileInfo(appName); + const fakeErrMsg = "A fake exception from findCredentials"; + readProfilesFromDiskSpy = jest.spyOn(ConvertV1Profiles["profileInfo"], "readProfilesFromDisk") + .mockImplementation(() => { + throw new Error(fakeErrMsg); + }); + + // call the function that we want to test + let caughtErr: any; + try { + await ConvertV1Profiles["initCredMgr"](); + } catch (err) { + caughtErr = err; + } + + expect(readProfilesFromDiskSpy).toHaveBeenCalled(); + expect(caughtErr).not.toBeDefined(); + + let numMsgsFound = 0; + for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { + if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { + if (nextMsg.msgText.includes(`Failed to initialize CredentialManager`) || + nextMsg.msgText.includes(`Reason: ${fakeErrMsg}`) || + nextMsg.msgText.includes(`Error: ${fakeErrMsg}`) + ) { + numMsgsFound++; + } + } + } + expect(numMsgsFound).toEqual(3); + }); + }); // end initCredMgr + + describe("loadV1Schemas", () => { + let existsSyncSpy: any; + + beforeAll(() => { + existsSyncSpy = jest.spyOn(fs, "existsSync"); + }); + + beforeEach(() => { + existsSyncSpy.mockClear(); // reset usage counts + + // pretend that our loadedConfig has no schemas in it + jest.spyOn(ImperativeConfig.instance, "loadedConfig", "get").mockReturnValue({} as any); + }); + + afterAll(() => { + // restore original app implementations + existsSyncSpy.mockRestore(); + }); + + it("should load schemas when none exist in ImperativeConfig loadedConfig", () => { + // pretend that the profiles root directory and schema file (xxx_meta.yaml) exist + existsSyncSpy.mockReturnValue(true); + + // pretend that we have profiles and they have schemas + const getAllProfileDirectoriesSpy = jest.spyOn(V1ProfileRead, "getAllProfileDirectories") + .mockReturnValue(["base", "cics", "zosmf"]); + + const readMetaFileSpy = jest.spyOn(V1ProfileRead, "readMetaFile") + .mockReturnValueOnce({ defaultProfile: "base", configuration: "baseSchema" as any }) + .mockReturnValueOnce({ defaultProfile: "cics", configuration: "cicsSchema" as any }) + .mockReturnValueOnce({ defaultProfile: "zosmf", configuration: "zosmfSchema" as any}); + + // call the function that we want to test + ConvertV1Profiles["loadV1Schemas"](); + + expect(existsSyncSpy).toHaveBeenCalled(); + expect(getAllProfileDirectoriesSpy).toHaveBeenCalledTimes(1); + expect(readMetaFileSpy).toHaveBeenCalledTimes(3); + expect(ImperativeConfig.instance.loadedConfig.profiles).toEqual(["baseSchema", "cicsSchema", "zosmfSchema"]); + }); + + it("should catch and report error thrown by readMetaFile", () => { + // pretend that the profiles root directory and schema file (xxx_meta.yaml) exist + existsSyncSpy.mockReturnValue(true); + + // pretend that we have profiles and they have schemas + const getAllProfileDirectoriesSpy = jest.spyOn(V1ProfileRead, "getAllProfileDirectories") + .mockReturnValue(["base", "cics", "zosmf"]); + + const fakeErrMsg = "A fake exception from readMetaFile"; + const readMetaFileSpy = jest.spyOn(V1ProfileRead, "readMetaFile").mockImplementation(() => { + throw new Error(fakeErrMsg); + }); + + // call the function that we want to test + let caughtErr: any; + try { + ConvertV1Profiles["loadV1Schemas"](); + } catch (err) { + caughtErr = err; + } + + expect(caughtErr).not.toBeDefined(); + expect(existsSyncSpy).toHaveBeenCalled(); + expect(getAllProfileDirectoriesSpy).toHaveBeenCalled(); + expect(readMetaFileSpy).toHaveBeenCalledTimes(3); + + let numMsgsFound = 0; + for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { + if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { + if (nextMsg.msgText.includes("Failed to load schema for profile type base") || + nextMsg.msgText.includes("Failed to load schema for profile type cics") || + nextMsg.msgText.includes("Failed to load schema for profile type zosmf") + ) { + numMsgsFound++; + } + if (nextMsg.msgText.includes(`Reason: ${fakeErrMsg}`) || + nextMsg.msgText.includes(`Error: ${fakeErrMsg}`) + ) { + numMsgsFound++; + } + } + } + expect(numMsgsFound).toEqual(9); + + }); + }); // end loadV1Schemas + + describe("isZoweKeyRingAvailable", () => { it("should return true if it finds credentials in the vault", async () => { // pretend that findCredentials found a bunch of accounts and passwords @@ -1281,7 +1709,7 @@ describe("ConvertV1Profiles tests", () => { } as any; // call the function that we want to test - const result = await ConvertV1Profiles["checkZoweKeyRingAvailable"](); + const result = await ConvertV1Profiles["isZoweKeyRingAvailable"](); ConvertV1Profiles["zoweKeyRing"] = origZoweKeyRing; expect(findCredentialsSpy).toHaveBeenCalledWith("@zowe/cli"); @@ -1303,7 +1731,7 @@ describe("ConvertV1Profiles tests", () => { let caughtErr: any; let checkKeyRingResult: boolean = true; try { - checkKeyRingResult = await ConvertV1Profiles["checkZoweKeyRingAvailable"](); + checkKeyRingResult = await ConvertV1Profiles["isZoweKeyRingAvailable"](); } catch (err) { caughtErr = err; } @@ -1313,14 +1741,10 @@ describe("ConvertV1Profiles tests", () => { expect(caughtErr).not.toBeDefined(); expect(checkKeyRingResult).toEqual(false); }); - }); // end checkZoweKeyRingAvailable + }); // end isZoweKeyRingAvailable describe("findOldSecureProps", () => { - beforeEach(() => { - jest.restoreAllMocks(); // put spies back to original app implementation - }); - it("should find old secure properties", async () => { // pretend that findCredentials found a bunch of accounts and passwords const origZoweKeyRing = ConvertV1Profiles["zoweKeyRing"]; @@ -1366,25 +1790,21 @@ describe("ConvertV1Profiles tests", () => { for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { if (nextMsg.msgText.includes(`Encountered an error while gathering secure properties ` + - `for service '${fakeServiceName}':`) - || - nextMsg.msgText.includes(fakeFindCredError) + `for service '${fakeServiceName}':`) || + nextMsg.msgText.includes(`Reason: ${fakeFindCredError}`) || + nextMsg.msgText.includes(`Error: ${fakeFindCredError}`) ) { numMsgsFound++; } } } - expect(numMsgsFound).toEqual(2); + expect(numMsgsFound).toEqual(3); ConvertV1Profiles["zoweKeyRing"] = origZoweKeyRing; }); }); // end findOldSecureProps describe("deleteOldSecureProps", () => { - beforeEach(() => { - jest.restoreAllMocks(); // put spies back to original app implementation - }); - it("should delete the specified secure property", async () => { // pretend that we successfully deleted the secure property ConvertV1Profiles["zoweKeyRing"] = { @@ -1421,15 +1841,15 @@ describe("ConvertV1Profiles tests", () => { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { if (nextMsg.msgText.includes( `Encountered an error while deleting secure data for ` + - `service '${fakeAcct}/${fakeProp}':`) - || - nextMsg.msgText.includes(fakeDelPassError) + `service '${fakeAcct}/${fakeProp}':`) || + nextMsg.msgText.includes(`Reason: ${fakeDelPassError}`) || + nextMsg.msgText.includes(`Error: ${fakeDelPassError}`) ) { numMsgsFound++; } } } - expect(numMsgsFound).toEqual(2); + expect(numMsgsFound).toEqual(3); ConvertV1Profiles["zoweKeyRing"] = origZoweKeyRing; }); }); // end deleteOldSecureProps diff --git a/packages/imperative/src/config/src/ConvertV1Profiles.ts b/packages/imperative/src/config/src/ConvertV1Profiles.ts index d94e92a963..4669d2f8dd 100644 --- a/packages/imperative/src/config/src/ConvertV1Profiles.ts +++ b/packages/imperative/src/config/src/ConvertV1Profiles.ts @@ -11,6 +11,7 @@ import * as fs from "fs"; import * as path from "path"; +import { readFileSync } from "jsonfile"; import { removeSync } from "fs-extra"; import stripAnsi = require("strip-ansi"); import { V1ProfileRead, ProfilesConstants, ProfileUtils } from "../../profiles"; @@ -21,11 +22,12 @@ import { IConvertV1ProfOpts, ConvertMsg, ConvertMsgFmt, IConvertV1ProfResult } f import { IImperativeOverrides } from "../../imperative/src/doc/IImperativeOverrides"; import { keyring } from "@zowe/secrets-for-zowe-sdk"; import { AppSettings } from "../../settings"; +import { ISettingsFile } from "../../settings/src/doc/ISettingsFile"; import { ImperativeConfig } from "../../utilities"; -import { PluginIssues } from "../../imperative/src/plugins/utilities/PluginIssues"; import { CredentialManagerOverride } from "../../security/src/CredentialManagerOverride"; import { OverridesLoader } from "../../imperative/src/OverridesLoader"; import { ConfigSchema } from "./ConfigSchema"; +import { ProfileInfo } from "./ProfileInfo"; import { Logger } from "../../logger"; interface IOldPluginInfo { @@ -41,7 +43,12 @@ interface IOldPluginInfo { export class ConvertV1Profiles { private static readonly noCfgFilePathNm: string = "CouldNotGetPathToConfigFile"; + private static readonly credMgrKey: string = "CredentialManager"; + private static readonly oldScsPluginNm = "@zowe/secure-credential-store-for-zowe-cli"; + private static readonly builtInCredMgrNm: string = "@zowe/cli"; + private static profileInfo: ProfileInfo = null; + private static oldScsPluginWasConfigured: boolean = false; private static convertOpts: IConvertV1ProfOpts = null; private static convertResult: IConvertV1ProfResult = null; private static profilesRootDir: string = "NotYetSet"; @@ -49,23 +56,34 @@ export class ConvertV1Profiles { private static zoweKeyRing: typeof keyring = undefined; /** - * Convert V1 profiles into a current zowe client config. - * Remove old credential manager overrides. - * Uninstall old SCS plugin. - * Delete old V1 profiles if requested. + * Convert V1 profiles into a zowe.config.json file. + * + * It will also do the following: + * Create a zowe.schema.json file. + * Migrate V1 secure properties into the current consolidated Zowe client secure properties. + * Replace old SCS-plugin credential manager override with the Zowe embedded SCS. + * Delete old V1 profiles (and old V1 secure properties) if requested. * * Calling this function after having already converted, will not attempt to * convert again. However it will still delete the old profiles if requested. * + * You should **NOT** initialize the secure credential manager before calling this function. + * The CredMgr can only be initialized once. If the old V1 SCS-plugin happens to be configured + * as the CredMgr when this function is called, the old V1 SCS-plugin CredMgr is unable + * to create the current consolidated Zowe client secure properties. Users will have to + * re-enter all of their credentials. + * * @param convertOpts Options that will control the conversion process. * @returns Result object into which messages and stats are stored. */ public static async convert(convertOpts: IConvertV1ProfOpts): Promise { + ConvertV1Profiles.profileInfo = convertOpts.profileInfo; + // initialize our result, which will be used by our utility functions, and returned by us ConvertV1Profiles.convertResult = { msgs: [], v1ScsPluginName: null, - reInitCredMgr: false, + credsWereMigrated: true, cfgFilePathNm: ConvertV1Profiles.noCfgFilePathNm, numProfilesFound: 0, profilesConverted: {}, @@ -79,23 +97,33 @@ export class ConvertV1Profiles { ConvertV1Profiles.profilesRootDir = ProfileUtils.constructProfilesRootDirectory(ImperativeConfig.instance.cliHome); ConvertV1Profiles.oldProfilesDir = `${ConvertV1Profiles.profilesRootDir.replace(/[\\/]$/, "")}-old`; - if (ConvertV1Profiles.isConversionNeeded()) { + if (await ConvertV1Profiles.isConversionNeeded()) { + ConvertV1Profiles.replaceOldCredMgrOverride(); + await ConvertV1Profiles.initCredMgr(); await ConvertV1Profiles.moveV1ProfilesToConfigFile(); - await ConvertV1Profiles.removeOldOverrides(); } - if (convertOpts.deleteV1Profs){ + if (convertOpts.deleteV1Profs) { await ConvertV1Profiles.deleteV1Profiles(); } + + // Report if the old SCS plugin should be uninstalled + if (ConvertV1Profiles.convertResult.v1ScsPluginName != null) { + let verb = "will"; + if (ConvertV1Profiles.profileInfo) { + verb = "should"; + } + let uninstallMsg: string = `The obsolete plug-in ${ConvertV1Profiles.convertResult.v1ScsPluginName} ` + + `${verb} be uninstalled because the SCS is now embedded within the Zowe clients.`; + + if (ConvertV1Profiles.profileInfo) { + uninstallMsg += ` Zowe CLI plugins can only be uninstalled by the CLI. Use the command ` + + `'zowe plugins uninstall ${ConvertV1Profiles.convertResult.v1ScsPluginName}'.`; + } + ConvertV1Profiles.addToConvertMsgs(ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, uninstallMsg); + } } catch (error) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - "Encountered the following error while trying to convert V1 profiles:" - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - stripAnsi(error.message) - ); + ConvertV1Profiles.addExceptionToConvertMsgs("Encountered the following error while trying to convert V1 profiles:", error); } return ConvertV1Profiles.convertResult; @@ -105,8 +133,19 @@ export class ConvertV1Profiles { * Detect whether we must convert any V1 profiles to a zowe client configuration. * @returns True means we must do a conversion. False otherwise. */ - private static isConversionNeeded(): boolean { + private static async isConversionNeeded(): Promise { ConvertV1Profiles.convertResult.numProfilesFound = 0; + + if (ImperativeConfig.instance.config == null) { + // Initialization for VSCode extensions does not create the config property, so create it now. + ImperativeConfig.instance.config = await Config.load( + ImperativeConfig.instance.loadedConfig.name, + { + homeDir: ImperativeConfig.instance.loadedConfig.defaultHome + } + ); + } + if (ImperativeConfig.instance.config?.exists) { // We do not convert if we already have an existing zowe client config ConvertV1Profiles.putCfgFileNmInResult(ImperativeConfig.instance.config); @@ -118,28 +157,21 @@ export class ConvertV1Profiles { } else { // with no client config, the existence of old V1 profiles dictates if we will convert const noProfilesMsg = `Did not convert any V1 profiles because no V1 profiles were found at ` + - `"${ConvertV1Profiles.profilesRootDir}".`; + `${ConvertV1Profiles.profilesRootDir}.`; try { ConvertV1Profiles.convertResult.numProfilesFound = ConvertV1Profiles.getOldProfileCount(ConvertV1Profiles.profilesRootDir); if (ConvertV1Profiles.convertResult.numProfilesFound === 0) { ConvertV1Profiles.addToConvertMsgs(ConvertMsgFmt.REPORT_LINE, noProfilesMsg); } - } catch (caughtErr) { - ConvertV1Profiles.convertResult.numProfilesFound = 0; - + } catch (error) { // did the profiles directory not exist? - if (caughtErr?.additionalDetails?.code === "ENOENT") { + if (error?.additionalDetails?.code === "ENOENT") { ConvertV1Profiles.addToConvertMsgs(ConvertMsgFmt.REPORT_LINE, noProfilesMsg); } else { // must have been some sort of I/O error - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed to get V1 profiles in "${ConvertV1Profiles.profilesRootDir}".` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - stripAnsi(caughtErr.message) + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed to get V1 profiles in ${ConvertV1Profiles.profilesRootDir}.`, error ); } } @@ -147,6 +179,80 @@ export class ConvertV1Profiles { return ConvertV1Profiles.convertResult.numProfilesFound > 0; } + /** + * Replace any detected oldCredMgr override entry in settings.json with the Zowe embedded credMgr. + * + * After the replacement of the credential manager override, we can then initialize + * credential manager later in this class. + */ + private static replaceOldCredMgrOverride(): void { + const oldPluginInfo = ConvertV1Profiles.getOldPluginInfo(); + for (const override of oldPluginInfo.overrides) { + if (override === ConvertV1Profiles.credMgrKey) { + try { + AppSettings.instance.set("overrides", ConvertV1Profiles.credMgrKey, CredentialManagerOverride.DEFAULT_CRED_MGR_NAME); + if (ImperativeConfig.instance.loadedConfig.overrides?.CredentialManager != null) { + delete ImperativeConfig.instance.loadedConfig.overrides.CredentialManager; + } + } catch (error) { + ConvertV1Profiles.convertResult.credsWereMigrated = false; + ConvertV1Profiles.addExceptionToConvertMsgs("Failed to replace credential manager override setting.", error); + } + } + } + + /* We only had one override in V1 - the old SCS plugin. + * So, despite the array for multiple plugins, we just report + * the first plugin name in the array as the plugin that our + * caller should uninstall. + */ + ConvertV1Profiles.convertResult.v1ScsPluginName = + oldPluginInfo.plugins.length > 0 ? oldPluginInfo.plugins[0] : null; + } + + /** + * Initialize credential manager so that we can migrate the secure properties that are + * stored for V1 profiles to new secure properties for the converted config that we will create. + * + * For all CLI commands other than convert-profiles, the credential manager is loaded in + * Imperative.init and frozen with Object.freeze so it cannot be modified later on. + * Because convert-profiles cannot create new secure properties for the converted config + * (if the old SCS plugin credMgr is already loaded), Imperative.init does not load the + * credential manager for the convert-profiles command. + * + * VSCode extensions must also avoid initializing the Credential Manager before calling + * ConvertV1Profiles.convert. + * + * If we encounter an error when trying to initialize the credential manager, we report (through + * ConvertV1Profiles.convertResult.credsWereMigrated) that creds were not migrated. + */ + private static async initCredMgr(): Promise { + if (CredentialManagerFactory.initialized) { + if (ConvertV1Profiles.oldScsPluginWasConfigured) { + Logger.getImperativeLogger().error( + `Credential manager has already been initialized with the old SCS plugin ` + + `${ConvertV1Profiles.oldScsPluginNm}. Old credentials cannot be migrated.` + ); + } + } else { + // we must initialize credMgr to get and store credentials + try { + if (ConvertV1Profiles.profileInfo) { + // Initialize CredMgr using the profileInfo object supplied by a VS Code extension + await ConvertV1Profiles.profileInfo.readProfilesFromDisk(); + } else { + // Initialize CredMgr using CLI techniques. + await OverridesLoader.load(ImperativeConfig.instance.loadedConfig, + ImperativeConfig.instance.callerPackageJson + ); + } + } catch (error) { + ConvertV1Profiles.convertResult.credsWereMigrated = false; + ConvertV1Profiles.addExceptionToConvertMsgs("Failed to initialize CredentialManager", error); + } + } + } + /** * Move the contents of existing v1 profiles to a zowe client config file. * @@ -155,12 +261,6 @@ export class ConvertV1Profiles { private static async moveV1ProfilesToConfigFile(): Promise { const convertedConfig: IConfig = Config.empty(); - /* Only the convert-profiles command is able to disable the credential manager - * and reload it. For all other commands, the credential manager is loaded in - * `Imperative.init` and frozen with `Object.freeze` so cannot be modified later on. - */ - await OverridesLoader.ensureCredentialManagerLoaded(); - for (const profileType of V1ProfileRead.getAllProfileDirectories(ConvertV1Profiles.profilesRootDir)) { const profileTypeDir = path.join(ConvertV1Profiles.profilesRootDir, profileType); const profileNames = V1ProfileRead.getAllProfileNames(profileTypeDir, ".yaml", `${profileType}_meta`); @@ -197,14 +297,10 @@ export class ConvertV1Profiles { ...ConvertV1Profiles.convertResult.profilesConverted[profileType] || [], profileName ]; } catch (error) { + ConvertV1Profiles.convertResult.credsWereMigrated = false; ConvertV1Profiles.convertResult.profilesFailed.push({ name: profileName, type: profileType, error }); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed to read "${profileType}" profile named "${profileName}"` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - stripAnsi(error.message) + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed to read '${profileType}' profile named '${profileName}'`, error ); } } @@ -217,14 +313,7 @@ export class ConvertV1Profiles { } } catch (error) { ConvertV1Profiles.convertResult.profilesFailed.push({ type: profileType, error }); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed to find default "${profileType}" profile.` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - stripAnsi(error.message) - ); + ConvertV1Profiles.addExceptionToConvertMsgs(`Failed to find default '${profileType}' profile.`, error); } } @@ -259,9 +348,25 @@ export class ConvertV1Profiles { * @returns string - Path name to the newly created config file. */ private static async createNewConfigFile(convertedConfig: IConfig): Promise { + if (typeof ImperativeConfig.instance.config.mVault === "undefined" || + ImperativeConfig.instance.config.mVault === null || + Object.keys(ImperativeConfig.instance.config.mVault).length == 0 + ) { + // Either the vault does not exist or it is empty. So create a vault. + ImperativeConfig.instance.config.mVault = { + load: (key: string): Promise => { + return CredentialManagerFactory.manager.load(key, true); + }, + save: (key: string, value: any): Promise => { + return CredentialManagerFactory.manager.save(key, value); + } + }; + } + const newConfig = ImperativeConfig.instance.config; newConfig.api.layers.activate(false, true); newConfig.api.layers.merge(convertedConfig); + ConvertV1Profiles.loadV1Schemas(); ConfigSchema.updateSchema(); await newConfig.save(); ConvertV1Profiles.putCfgFileNmInResult(newConfig); @@ -278,13 +383,8 @@ export class ConvertV1Profiles { ); } } catch (error) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed to rename profiles directory to ${ConvertV1Profiles.oldProfilesDir}:` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - error.message + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed to rename profiles directory to ${ConvertV1Profiles.oldProfilesDir}:`, error ); } @@ -295,6 +395,32 @@ export class ConvertV1Profiles { ); } + /** + * Load V1 profile schemas, which will not have been loaded for VSCode extensions. + */ + private static loadV1Schemas(): void { + if (!Object.hasOwn(ImperativeConfig.instance.loadedConfig, "profiles")) { + // since no schemas are loaded, we read them from the V1 profiles directory + ImperativeConfig.instance.loadedConfig.profiles = []; + const v1ProfileTypes = fs.existsSync(ConvertV1Profiles.profilesRootDir) ? + V1ProfileRead.getAllProfileDirectories(ConvertV1Profiles.profilesRootDir) : []; + + for (const profType of v1ProfileTypes) { + const schemaFileNm = path.join(ConvertV1Profiles.profilesRootDir, profType, profType + "_meta.yaml"); + if (fs.existsSync(schemaFileNm)) { + try { + const schemaContent = V1ProfileRead.readMetaFile(schemaFileNm); + ImperativeConfig.instance.loadedConfig.profiles.push(schemaContent.configuration); + } catch (error) { + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed to load schema for profile type ${profType} from file ${schemaFileNm}`, error + ); + } + } + } + } + } + /** * Put the path name to the config file, obtained from the supplied Config object, * into our result object. @@ -305,14 +431,7 @@ export class ConvertV1Profiles { try { ConvertV1Profiles.convertResult.cfgFilePathNm = configForPath?.layerActive().path; } catch (error) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE, - "Failed to retrieve the path to the config file." - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - error.message - ); + ConvertV1Profiles.addExceptionToConvertMsgs("Failed to retrieve the path to the config file.", error); } if (!ConvertV1Profiles.convertResult.cfgFilePathNm) { ConvertV1Profiles.convertResult.cfgFilePathNm = ConvertV1Profiles.noCfgFilePathNm; @@ -330,134 +449,48 @@ export class ConvertV1Profiles { removeSync(ConvertV1Profiles.oldProfilesDir); ConvertV1Profiles.addToConvertMsgs( ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, - `Deleted the old profiles directory '${ConvertV1Profiles.oldProfilesDir}'.` + `Deleted the old profiles directory ${ConvertV1Profiles.oldProfilesDir}.` ); } else { ConvertV1Profiles.addToConvertMsgs( ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, - `The old profiles directory '${ConvertV1Profiles.oldProfilesDir}' did not exist.` + `The old profiles directory ${ConvertV1Profiles.oldProfilesDir} did not exist.` ); } } catch (error) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed to delete the profiles directory '${ConvertV1Profiles.oldProfilesDir}'` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - error.message + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed to delete the profiles directory ${ConvertV1Profiles.oldProfilesDir}`, error ); } // Delete the securely stored credentials - const isZoweKeyRingAvailable = await ConvertV1Profiles.checkZoweKeyRingAvailable(); - if (isZoweKeyRingAvailable) { - const knownServices = ["@brightside/core", "@zowe/cli", "Zowe-Plugin", "Broadcom-Plugin", "Zowe"]; + if (await ConvertV1Profiles.isZoweKeyRingAvailable()) { + let deleteMsgFormat: any = ConvertMsgFmt.PARAGRAPH; + const knownServices = [ConvertV1Profiles.builtInCredMgrNm, "@brightside/core", "Zowe-Plugin", "Broadcom-Plugin", "Zowe"]; for (const service of knownServices) { const accounts = await ConvertV1Profiles.findOldSecureProps(service); for (const account of accounts) { if (!account.includes("secure_config_props")) { const success = await ConvertV1Profiles.deleteOldSecureProps(service, account); - const errMsgTrailer = `secure value for "${service}/${account}".`; + const errMsgTrailer = `obsolete secure value ${service}/${account}`; if (success) { ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, + ConvertMsgFmt.REPORT_LINE | deleteMsgFormat, `Deleted ${errMsgTrailer}.` ); } else { ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, + ConvertMsgFmt.ERROR_LINE | deleteMsgFormat, `Failed to delete ${errMsgTrailer}.` ); } - } - } - } - } else { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - "Zowe keyring or the credential vault are unavailable. Unable to delete old secure values." - ); - } - } - /** - * Remove any old credential manager overrides. - */ - private static async removeOldOverrides(): Promise { - /* Replace any detected oldCredMgr override entry in settings.json with the Zowe embedded credMgr. - * Only the convert-profiles command is able to disable the credential manager - * and reload it. For all other commands, the credential manager is loaded in - * `Imperative.init` and frozen with `Object.freeze` so cannot be modified later on. - * - * Unlike a CLI command (which gets re-initialized on the next command), long-running apps - * must re-initialize the credential manager with a call to CredentialManagerFactory.initialize. - * That initialize function can only be called once within a running process. - * ConvertV1Profiles.convertResult.reInitCredMgr will be set to true to tell our calling app - * that the app must be restarted. - */ - const oldPluginInfo = ConvertV1Profiles.getOldPluginInfo(); - for (const override of oldPluginInfo.overrides) { - if (override === "CredentialManager") { - try { - AppSettings.instance.set("overrides", "CredentialManager", CredentialManagerOverride.DEFAULT_CRED_MGR_NAME); - if (ImperativeConfig.instance.loadedConfig.overrides.CredentialManager != null) { - delete ImperativeConfig.instance.loadedConfig.overrides.CredentialManager; + // only start a new paragraph on our first delete message + deleteMsgFormat = 0; } - if (CredentialManagerFactory.initialized ) { - // We cannot re-initialize CredMgr, so let our caller know. - ConvertV1Profiles.convertResult.reInitCredMgr = true; - } else { - /* We can initialize the CredMgr. - * At this point, we have a new config file. Load that new config, so that - * ImperativeConfig.instance.config.exists is true when we call OverridesLoader. - */ - ImperativeConfig.instance.config = await Config.load(ImperativeConfig.instance.rootCommandName, - { homeDir: ImperativeConfig.instance.cliHome } - ); - - // Load the overrides that we just set. That will re-initialize the CredMgr. - await OverridesLoader.load( - ImperativeConfig.instance.loadedConfig, - ImperativeConfig.instance.callerPackageJson - ); - } - } catch (error) { - ConvertV1Profiles.convertResult.reInitCredMgr = true; - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - "Failed to replace credential manager override setting." - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - stripAnsi(error.message) - ); } } } - - // Report any plugin that we will uninstall - if (oldPluginInfo.plugins.length > 0) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, - "The following plug-ins will be removed because they are now part of the core CLI and are no longer needed:" - ); - - for (const nextPlugin of oldPluginInfo.plugins) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.INDENT, - nextPlugin - ); - } - } - - /* We only had one override in V1 - the old SCS plugin. - * So, despite the array for multiple plugins, we just report - * the first plugin name in the array as the plugin that our - * caller should uninstall. - */ - ConvertV1Profiles.convertResult.v1ScsPluginName = - oldPluginInfo.plugins.length > 0 ? oldPluginInfo.plugins[0] : null; } /** @@ -529,35 +562,53 @@ export class ConvertV1Profiles { * overrides - List of overrides to replace in app settings */ private static getOldPluginInfo(): IOldPluginInfo { - const oldScsPluginNm = "@zowe/secure-credential-store-for-zowe-cli"; const pluginInfo: IOldPluginInfo = { plugins: [], overrides: [] }; // if the old SCS plugin is our credential manager, record that it should be replaced - const credMgrKey = "CredentialManager"; let currCredMgr; try { - currCredMgr = AppSettings.instance.get("overrides", credMgrKey); + // have AppSettings been initialized? + AppSettings.instance; + } catch (error) { + let settingsFile: string = "NotSetYet"; + try { + // A VSCode extension will not have initialized AppSettings, so initialize it now + settingsFile = path.join(ImperativeConfig.instance.cliHome, "settings", "imperative.json"); + const defaultSettings: ISettingsFile = { + overrides: {} + } as any; + defaultSettings.overrides[ConvertV1Profiles.credMgrKey] = ConvertV1Profiles.builtInCredMgrNm; + AppSettings.initialize(settingsFile, defaultSettings); + } catch(error) { + currCredMgr = null; + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed to initialize AppSettings overrides from ${settingsFile}.`, error + ); + } + } + + // get the current credMgr from AppSettings + try { + currCredMgr = AppSettings.instance.get("overrides", ConvertV1Profiles.credMgrKey); } catch(error) { currCredMgr = null; - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed trying to read '${credMgrKey}' overrides.` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - stripAnsi(error.message) + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed trying to read '${ConvertV1Profiles.credMgrKey}' overrides.`, error ); } // we leave the 'false' indicator unchanged to allow for the use of no credMgr if (typeof currCredMgr === "string") { // if any of the old SCS credMgr names are found, record that we want to replace the credMgr - for (const oldOverrideName of [oldScsPluginNm, "KeytarCredentialManager", "Zowe-Plugin", "Broadcom-Plugin"]) { + for (const oldOverrideName of [ + ConvertV1Profiles.oldScsPluginNm, "KeytarCredentialManager", "Zowe-Plugin", "Broadcom-Plugin"]) + { if (currCredMgr.includes(oldOverrideName)) { - pluginInfo.overrides.push(credMgrKey); + ConvertV1Profiles.oldScsPluginWasConfigured = true; + pluginInfo.overrides.push(ConvertV1Profiles.credMgrKey); break; } } @@ -565,26 +616,44 @@ export class ConvertV1Profiles { try { // Only record the need to uninstall the SCS plug-in if it is currently installed - if (oldScsPluginNm in PluginIssues.instance.getInstalledPlugins()) { - pluginInfo.plugins.push(oldScsPluginNm); + if (ConvertV1Profiles.isPluginInstalled(ConvertV1Profiles.oldScsPluginNm)) { + pluginInfo.plugins.push(ConvertV1Profiles.oldScsPluginNm); } - } catch (caughtErr) { + } catch (error) { // report all errors except the absence of the plugins.json file - if (!caughtErr.message.includes("ENOENT")) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - "Failed trying to get the set of installed plugins." - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - caughtErr.message - ); + if (!error.message.includes("ENOENT")) { + ConvertV1Profiles.addExceptionToConvertMsgs("Failed trying to get the set of installed plugins.", error); } } return pluginInfo; } + /** + * Report whether the specified plugin is installed. + * @param pluginName name of the plugin to search for. + * @returns True if plugin is installed. False otherwise. + */ + private static isPluginInstalled(pluginName: string): boolean { + let pluginsFileNm: string; + try { + pluginsFileNm = path.join(ImperativeConfig.instance.cliHome, "plugins", "plugins.json"); + const pluginsFileJson = readFileSync(pluginsFileNm); + if (Object.hasOwn(pluginsFileJson, pluginName)) { + return true; + } + } + catch (ioErr) { + // A VSCode extension may legitimately not have any plugins directory. + // However, something is wrong if the CLI does not have the plugins + // directory and file, so display the error. + if (!ConvertV1Profiles.profileInfo) { + ConvertV1Profiles.addExceptionToConvertMsgs(`Cannot read plugins file ${pluginsFileNm}`, ioErr); + } + } + return false; + } + /** * Get the number of old profiles present in the CLI home dir. * @param profilesRootDir Root profiles directory @@ -602,16 +671,17 @@ export class ConvertV1Profiles { } /** - * Lazy load zoweKeyRing, and verify that the credential vault is able to be accessed, - * or whether there is a problem. + * Verify that the credential vault is accessible, or whether there is a problem. * @returns true if credential vault is available, false if it is not */ - private static async checkZoweKeyRingAvailable(): Promise { + private static async isZoweKeyRingAvailable(): Promise { try { - const zoweSecretsPath = require.resolve("@zowe/secrets-for-zowe-sdk"); - ConvertV1Profiles.zoweKeyRing = (await import(zoweSecretsPath)).keyring; + ConvertV1Profiles.zoweKeyRing = (await import("@zowe/secrets-for-zowe-sdk")).keyring; await ConvertV1Profiles.zoweKeyRing.findCredentials(CredentialManagerOverride.DEFAULT_CRED_MGR_NAME); - } catch (err) { + } catch (error) { + ConvertV1Profiles.addExceptionToConvertMsgs( + "Zowe keyring or the credential vault are unavailable. Unable to delete old secure values.", error + ); return false; } return true; @@ -632,13 +702,9 @@ export class ConvertV1Profiles { oldSecurePropNames.push(element.account); } } catch (error) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE, - `Encountered an error while gathering secure properties for service '${acct}':` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - error.message + ConvertV1Profiles.convertResult.credsWereMigrated = false; + ConvertV1Profiles.addExceptionToConvertMsgs( + `Encountered an error while gathering secure properties for service '${acct}':`, error ); } return oldSecurePropNames; @@ -656,19 +722,28 @@ export class ConvertV1Profiles { try { success = await ConvertV1Profiles.zoweKeyRing.deletePassword(acct, propName); } catch (error) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE, - `Encountered an error while deleting secure data for service '${acct}/${propName}':` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - error.message + ConvertV1Profiles.addExceptionToConvertMsgs( + `Encountered an error while deleting secure data for service '${acct}/${propName}':`, error ); success = false; } return success; } + /** + * Add a new message to the V1 profile conversion messages that reports a caught exception. + * + * @param introMsg An introductory message describing what action was being attempted when we failed. + * @param error The exception that we caught. + */ + private static addExceptionToConvertMsgs(introMsg: string, error: Error): void { + ConvertV1Profiles.addToConvertMsgs(ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, introMsg); + ConvertV1Profiles.addToConvertMsgs(ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, "Reason: " + stripAnsi(error.message)); + if (Object.hasOwn(error, "stack")) { + ConvertV1Profiles.addToConvertMsgs(ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, stripAnsi(error.stack)); + } + } + /** * Add a new message to the V1 profile conversion messages. * @param msgFormat Formatting clues for the message. @@ -680,6 +755,5 @@ export class ConvertV1Profiles { } const newMsg = new ConvertMsg(msgFormat, msgText); ConvertV1Profiles.convertResult.msgs.push(newMsg); - } } diff --git a/packages/imperative/src/config/src/doc/IConvertV1Profiles.ts b/packages/imperative/src/config/src/doc/IConvertV1Profiles.ts index 2d36403d4d..2c463df139 100644 --- a/packages/imperative/src/config/src/doc/IConvertV1Profiles.ts +++ b/packages/imperative/src/config/src/doc/IConvertV1Profiles.ts @@ -9,12 +9,18 @@ * */ +import { ProfileInfo } from "../ProfileInfo"; + /** * This is the structure of the input options to be supplied to ConvertV1Profiles.convert. */ export interface IConvertV1ProfOpts { // Should V1 profiles be deleted after conversion? deleteV1Profs: boolean; + + // The ProfileInfo object that the API will use to initialize the Secure Credential Manager. + // This property should be supplied by a VSCode application. + profileInfo?: ProfileInfo } /** @@ -64,12 +70,14 @@ export interface IConvertV1ProfResult { v1ScsPluginName: string | null; /** + * This property indicates whether secure credentials were migrated during conversion. + * * If the old V1 Secure Credential Store plugin was supplying the credential manager - * override, that CredMgr has been replaced with the Zowe CLI built-in credential manager. - * This property indicates whether the caller must re-initialize the credential Manager - * by restarting its app. + * override and the CredentialManager was initialized before calling this function, + * profile conversion will not be able to migrate credentials from the old SCS plugin + * to the current embedded Secure Credential Store. */ - reInitCredMgr: boolean; + credsWereMigrated: boolean; /** * The following properties contain information about the success or failure of diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/convert-profiles/convert-profiles.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/convert-profiles/convert-profiles.handler.unit.test.ts index 040e44720b..7df77d6800 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/convert-profiles/convert-profiles.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/convert-profiles/convert-profiles.handler.unit.test.ts @@ -59,7 +59,7 @@ describe("Configuration Convert Profiles command handler", () => { { msgFormat: 2, msgText: "Error Msg 2" } ], v1ScsPluginName: null as any, - reInitCredMgr: false, + credsWereMigrated: true, cfgFilePathNm: ConvertV1Profiles["noCfgFilePathNm"], numProfilesFound: 0, profilesConverted: { @@ -160,7 +160,7 @@ describe("Configuration Convert Profiles command handler", () => { expect(uninstallSpy).toHaveBeenCalled(); expect(stdout).toContain("Report Msg 1"); expect(stdout).toContain("Report Msg 2"); - expect(stdout).toContain('Uninstalled plug-in "fakeScsPluginName"'); + expect(stdout).toContain('Successfully uninstalled plug-in fakeScsPluginName'); expect(stderr).toContain("Error Msg 1"); expect(stderr).toContain("Error Msg 2"); }); @@ -186,7 +186,7 @@ describe("Configuration Convert Profiles command handler", () => { expect(stdout).toContain("Report Msg 2"); expect(stderr).toContain("Error Msg 1"); expect(stderr).toContain("Error Msg 2"); - expect(stderr).toContain('Failed to uninstall plug-in "fakeScsPluginName"'); + expect(stderr).toContain('Failed to uninstall plug-in fakeScsPluginName'); expect(stderr).toContain(fakeUninstallErr); }); }); diff --git a/packages/imperative/src/imperative/src/config/cmd/convert-profiles/convert-profiles.handler.ts b/packages/imperative/src/imperative/src/config/cmd/convert-profiles/convert-profiles.handler.ts index 21cd8c8777..943bf806df 100644 --- a/packages/imperative/src/imperative/src/config/cmd/convert-profiles/convert-profiles.handler.ts +++ b/packages/imperative/src/imperative/src/config/cmd/convert-profiles/convert-profiles.handler.ts @@ -61,12 +61,14 @@ export default class ConvertProfilesHandler implements ICommandHandler { try { uninstallPlugin(convertResult.v1ScsPluginName); const newMsg = new ConvertMsg( - ConvertMsgFmt.REPORT_LINE, `Uninstalled plug-in "${convertResult.v1ScsPluginName}"` + ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, + `Successfully uninstalled plug-in ${convertResult.v1ScsPluginName}.` ); convertResult.msgs.push(newMsg); } catch (error) { let newMsg = new ConvertMsg( - ConvertMsgFmt.ERROR_LINE, `Failed to uninstall plug-in "${convertResult.v1ScsPluginName}"` + ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, + `Failed to uninstall plug-in ${convertResult.v1ScsPluginName}.` ); convertResult.msgs.push(newMsg); diff --git a/packages/imperative/src/utilities/src/ImperativeConfig.ts b/packages/imperative/src/utilities/src/ImperativeConfig.ts index ff6c17fb83..7d49569448 100644 --- a/packages/imperative/src/utilities/src/ImperativeConfig.ts +++ b/packages/imperative/src/utilities/src/ImperativeConfig.ts @@ -82,11 +82,11 @@ export class ImperativeConfig { private mConfig: Config; /** - * Gets a single instance of the PluginIssues. On the first call of - * ImperativeConfig.instance, a new Plugin Issues object is initialized and returned. + * Gets a single instance of the ImperativeConfig. On the first call of + * ImperativeConfig.instance, a new ImperativeConfig object is initialized and returned. * Every subsequent call will use the one that was first created. * - * @returns {ImperativeConfig} The newly initialized PMF object. + * @returns {ImperativeConfig} The newly initialized ImperativeConfig object. */ public static get instance(): ImperativeConfig { if (this.mInstance == null) { diff --git a/packages/provisioning/package.json b/packages/provisioning/package.json index d5ab141ff5..86888b30d8 100644 --- a/packages/provisioning/package.json +++ b/packages/provisioning/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/provisioning-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with the z/OS provisioning APIs", "author": "Zowe", "license": "EPL-2.0", @@ -49,9 +49,9 @@ }, "devDependencies": { "@types/js-yaml": "^4.0.9", - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/secrets/package.json b/packages/secrets/package.json index 697c0ed9a3..25fdb5d971 100644 --- a/packages/secrets/package.json +++ b/packages/secrets/package.json @@ -3,7 +3,7 @@ "description": "Credential management facilities for Imperative, Zowe CLI, and extenders.", "repository": "https://github.com/zowe/zowe-cli.git", "author": "Zowe", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "homepage": "https://github.com/zowe/zowe-cli/tree/master/packages/secrets#readme", "bugs": { "url": "https://github.com/zowe/zowe-cli/issues" diff --git a/packages/workflows/package.json b/packages/workflows/package.json index 8d798d4078..6756a80d57 100644 --- a/packages/workflows/package.json +++ b/packages/workflows/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-workflows-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with the z/OS workflows APIs", "author": "Zowe", "license": "EPL-2.0", @@ -45,12 +45,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosconsole/package.json b/packages/zosconsole/package.json index 98e56a3df5..abf58e59db 100644 --- a/packages/zosconsole/package.json +++ b/packages/zosconsole/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-console-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with the z/OS console", "author": "Zowe", "license": "EPL-2.0", @@ -45,9 +45,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosfiles/package.json b/packages/zosfiles/package.json index ebcc2d0e17..2c4574d1e0 100644 --- a/packages/zosfiles/package.json +++ b/packages/zosfiles/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-files-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with files and data sets on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -49,10 +49,10 @@ "minimatch": "^9.0.5" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosjobs/package.json b/packages/zosjobs/package.json index 9c6f893a54..455536a3a7 100644 --- a/packages/zosjobs/package.json +++ b/packages/zosjobs/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-jobs-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with jobs on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -46,12 +46,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zoslogs/package.json b/packages/zoslogs/package.json index a4d9efc176..f1b7da5a79 100644 --- a/packages/zoslogs/package.json +++ b/packages/zoslogs/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-logs-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with the z/OS logs", "author": "Zowe", "license": "EPL-2.0", @@ -45,9 +45,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosmf/package.json b/packages/zosmf/package.json index 17b31fe51c..eb6dd8c5cc 100644 --- a/packages/zosmf/package.json +++ b/packages/zosmf/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zosmf-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with the z/OS Management Facility", "author": "Zowe", "license": "EPL-2.0", @@ -44,9 +44,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zostso/package.json b/packages/zostso/package.json index 2df023715f..829fcd438b 100644 --- a/packages/zostso/package.json +++ b/packages/zostso/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-tso-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with TSO on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -45,12 +45,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosuss/package.json b/packages/zosuss/package.json index 8882f5b1af..5a6790754c 100644 --- a/packages/zosuss/package.json +++ b/packages/zosuss/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-uss-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with USS on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -49,8 +49,8 @@ }, "devDependencies": { "@types/ssh2": "^1.11.19", - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next"