Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Zowe Explorer independent of Zowe CLI installation #1993

Merged
merged 21 commits into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

All notable changes to the "vscode-extension-for-zowe" extension will be documented in this file.

## TBD Release

### New features and enhancements

- Added independence from Zowe CLI installation by creating the `~/.zowe/settings/imperative.json` file during activation if it doesn't already exist. This file is for Zowe Explorer to know the Security Credential Manager used for secure profile information. [#1850](https://github.com/zowe/vscode-extension-for-zowe/issues/1850)

### Bug fixes
JillieBeanSim marked this conversation as resolved.
Show resolved Hide resolved

- Fixed the `Secure Credentials Enabled` setting to update the `~/.zowe/settings/imperative.json` file upon change of the setting without overwriting other data in the file.

## `2.4.0`

### New features and enhancements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ async function createGlobalMocks() {
readProfilesFromDisk: jest.fn(),
};
}),
mockSetGlobalSecurityValue: jest.fn(),
mockWriteOverridesFile: jest.fn(),
mockProfCacheProfileInfo: createInstanceOfProfileInfo(),
mockProfilesCache: new ProfilesCache(zowe.imperative.Logger.getAppLogger()),
testTreeView: null,
Expand Down Expand Up @@ -261,6 +263,10 @@ async function createGlobalMocks() {
value: globalMocks.mockOnDidChangeConfiguration,
configurable: true,
});
Object.defineProperty(globals, "setGlobalSecurityValue", {
value: globalMocks.mockSetGlobalSecurityValue,
configurable: true,
});
Object.defineProperty(fs, "readdirSync", { value: globalMocks.mockReaddirSync, configurable: true });
Object.defineProperty(fs, "createReadStream", { value: globalMocks.mockCreateReadStream, configurable: true });
Object.defineProperty(vscode, "ConfigurationTarget", { value: globalMocks.enums, configurable: true });
Expand Down Expand Up @@ -464,7 +470,7 @@ describe("Extension Unit Tests", () => {

expect(globals.ISTHEIA).toEqual(true);
// tslint:disable-next-line: no-magic-numbers
expect(globalMocks.mockMkdirSync.mock.calls.length).toBe(4);
expect(globalMocks.mockMkdirSync.mock.calls.length).toBe(6);
Comment on lines 472 to +473
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably remove tslint in favor of eslint.
and for these kind of tests, we could have the eslint configuration for tests exclude the magic-number rules 😋
Example: https://github.com/zowe/zowe-cli/blob/master/.eslintrc.js#L14-L36

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesn't look like Zowe Explorer has changed over to eslint, only ZE API & FTP

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦 you are right
the first step is to migrate to eslint 😋

expect(globalMocks.mockRegisterCommand.mock.calls.length).toBe(globals.COMMAND_COUNT);
globalMocks.mockRegisterCommand.mock.calls.forEach((call, i) => {
expect(globalMocks.mockRegisterCommand.mock.calls[i][1]).toBeInstanceOf(Function);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* This program and the accompanying materials are made available under the terms of the *
* Eclipse Public License v2.0 which accompanies this distribution, and is available at *
* https://www.eclipse.org/legal/epl-v20.html *
* *
* SPDX-License-Identifier: EPL-2.0 *
* *
* Copyright Contributors to the Zowe Project. *
* *
*/

import * as fs from "fs";
import * as path from "path";
import { writeOverridesFile } from "../../../src/utils/ProfilesUtils";

jest.mock("fs");

async function createGlobalMocks() {
const globalMocks = {
mockReadFileSync: jest.fn(),
mockWriteFileSync: jest.fn(),
mockFileRead: { overrides: { CredentialManager: "@zowe/cli" } },
zoweDir: path.normalize("__tests__/.zowe/settings/imperative.json"),
encoding: "utf8",
};
Object.defineProperty(fs, "writeFileSync", { value: globalMocks.mockWriteFileSync, configurable: true });
Object.defineProperty(fs, "existsSync", {
value: () => {
return true;
},
configurable: true,
});
return globalMocks;
}

afterEach(() => {
jest.clearAllMocks();
});

describe("ProfileUtils Unit Tests", () => {
it("should have file exist", async () => {
const globalMocks = await createGlobalMocks();
const fileJson = { overrides: { CredentialManager: "@zowe/cli", testValue: true } };
const content = JSON.stringify(fileJson, null, 2);
jest.spyOn(fs, "readFileSync").mockReturnValueOnce(
JSON.stringify({ overrides: { CredentialManager: false, testValue: true } }, null, 2)
);
const spy = jest.spyOn(fs, "writeFileSync");
writeOverridesFile();
expect(spy).toBeCalledWith(globalMocks.zoweDir, content, globalMocks.encoding);
spy.mockClear();
});
it("should have no change to global variable PROFILE_SECURITY and returns", async () => {
const fileJson = { overrides: { CredentialManager: "@zowe/cli", testValue: true } };
jest.spyOn(fs, "readFileSync").mockReturnValueOnce(JSON.stringify(fileJson, null, 2));
const spy = jest.spyOn(fs, "writeFileSync");
writeOverridesFile();
expect(spy).toBeCalledTimes(0);
spy.mockClear();
});
it("should have not exist and create default file", async () => {
const globalMocks = await createGlobalMocks();
Object.defineProperty(fs, "existsSync", {
value: () => {
return false;
},
configurable: true,
});
const content = JSON.stringify(globalMocks.mockFileRead, null, 2);
const spyRead = jest.spyOn(fs, "readFileSync");
const spy = jest.spyOn(fs, "writeFileSync");
writeOverridesFile();
expect(spy).toBeCalledWith(globalMocks.zoweDir, content, globalMocks.encoding);
expect(spyRead).toBeCalledTimes(0);
spy.mockClear();
spyRead.mockClear();
});
});
19 changes: 0 additions & 19 deletions packages/zowe-explorer/src/Profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,24 +717,6 @@ export class Profiles extends ProfilesCache {
}
}

public updateImperativeSettings() {
try {
const fileName = path.join(getZoweDir(), "settings", "imperative.json");
const updatedSettings = {
overrides: {
CredentialManager: false,
},
};
fs.writeFile(fileName, JSON.stringify(updatedSettings), "utf8", (err) => {
if (err) {
this.log.error("Could not update imperative.json settings file", err);
}
});
} catch (error) {
this.log.error(error);
}
}

public async editZoweConfigFile() {
const existingLayers = await this.getConfigLayers();
if (existingLayers.length === 1) {
Expand Down Expand Up @@ -1917,7 +1899,6 @@ export class Profiles extends ProfilesCache {
for (const profile of Object.entries(newConfig.profiles)) {
delete newConfig.profiles[profile[0]].secure;
}
this.updateImperativeSettings();
newConfig.autoStore = false;
}
}
Expand Down
45 changes: 5 additions & 40 deletions packages/zowe-explorer/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
*/

import * as fs from "fs";
import * as path from "path";
import * as globals from "./globals";
import * as vscode from "vscode";
import * as ussActions from "./uss/actions";
Expand All @@ -29,8 +28,7 @@ import {
import { ZoweExplorerApiRegister } from "./ZoweExplorerApiRegister";
import { ZoweExplorerExtender } from "./ZoweExplorerExtender";
import { Profiles } from "./Profiles";
import { promptCredentials, readConfigFromDisk } from "./utils/ProfilesUtils";
import { getImperativeConfig, imperative } from "@zowe/cli";
import { initializeZoweFolder, promptCredentials, readConfigFromDisk, writeOverridesFile } from "./utils/ProfilesUtils";
import { createDatasetTree } from "./dataset/DatasetTree";
import { createJobsTree } from "./job/ZosJobsProvider";
import { createUSSTree } from "./uss/USSTree";
Expand Down Expand Up @@ -88,32 +86,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<ZoweEx
}

try {
// Ensure that ~/.zowe folder exists
if (!imperative.ImperativeConfig.instance.config?.exists) {
const zoweDir = getZoweDir();
// Should we replace the instance.config above with (await getProfileInfo(globals.ISTHEIA)).exists
await imperative.CliProfileManager.initialize({
configuration: getImperativeConfig().profiles,
profileRootDirectory: path.join(zoweDir, "profiles"),
});

// TODO(zFernand0): Will address the commented code below once this imperative issue is resolved.
// https://github.com/zowe/imperative/issues/840

// const settingsPath = path.join(zoweDir, "settings");
// if (!fs.existsSync(settingsPath)) {
// fs.mkdirSync(settingsPath);
// }
// const imperativeSettings = path.join(settingsPath, "imperative.json");
// if (!fs.existsSync(imperativeSettings)) {
// fs.writeFileSync(imperativeSettings, JSON.stringify({
// overrides: {
// CredentialManager: "@zowe/cli"
// }
// }));
// }
}

await initializeZoweFolder();
await readConfigFromDisk();
} catch (err) {
const errorMessage = localize("initialize.profiles.error", "Error reading or initializing Zowe CLI profiles.");
Expand Down Expand Up @@ -158,14 +131,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<ZoweEx

// Update imperative.json to false only when VS Code setting is set to false
context.subscriptions.push(
vscode.commands.registerCommand("zowe.updateSecureCredentials", () => {
const isSettingEnabled: boolean = vscode.workspace
.getConfiguration()
.get(globals.SETTINGS_SECURE_CREDENTIALS_ENABLED);

if (!isSettingEnabled) {
Profiles.getInstance().updateImperativeSettings();
}
vscode.commands.registerCommand("zowe.updateSecureCredentials", async () => {
await globals.setGlobalSecurityValue();
writeOverridesFile();
})
);

Expand Down Expand Up @@ -208,9 +176,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<ZoweEx
})
);

// Update imperative.json to false if VS Code setting is set to false
await vscode.commands.executeCommand("zowe.updateSecureCredentials");

if (datasetProvider) {
initDatasetProvider(context, datasetProvider);
}
Expand Down
11 changes: 11 additions & 0 deletions packages/zowe-explorer/src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ export const SETTINGS_USS_HISTORY = "zowe.uss.history";
export const SETTINGS_JOBS_HISTORY = "zowe.jobs.history";
export const SETTINGS_SECURE_CREDENTIALS_ENABLED = "zowe.security.secureCredentialsEnabled";
export const EXTENDER_CONFIG: imperative.ICommandProfileTypeConfiguration[] = [];
export const ZOWE_CLI_SCM = "@zowe/cli";
export let ACTIVATED = false;
export let PROFILE_SECURITY: string | boolean = ZOWE_CLI_SCM;

export enum CreateDataSetTypeWithKeysEnum {
DATA_SET_BINARY = 0,
Expand Down Expand Up @@ -252,3 +254,12 @@ export function initLogger(context: vscode.ExtensionContext) {
export function setActivated(value: boolean) {
ACTIVATED = value;
}

export async function setGlobalSecurityValue() {
const settingEnabled: boolean = await vscode.workspace.getConfiguration().get(SETTINGS_SECURE_CREDENTIALS_ENABLED);
if (!settingEnabled) {
PROFILE_SECURITY = false;
} else {
PROFILE_SECURITY = ZOWE_CLI_SCM;
}
}
58 changes: 55 additions & 3 deletions packages/zowe-explorer/src/utils/ProfilesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@

import * as vscode from "vscode";
import * as globals from "../globals";
import * as path from "path";
import * as fs from "fs";
import { getSecurityModules, IZoweTreeNode, ZoweTreeNode, getZoweDir, getFullPath } from "@zowe/zowe-explorer-api";
import { Profiles } from "../Profiles";
import * as nls from "vscode-nls";
import { imperative } from "@zowe/cli";
import { imperative, getImperativeConfig } from "@zowe/cli";
import { UIViews } from "../shared/ui-views";

// Set up localization
Expand Down Expand Up @@ -281,8 +283,8 @@ export async function readConfigFromDisk() {
export async function openConfigOnError(error: Error) {
if (error.message.toString().includes("Error parsing JSON")) {
const errorArray = error.message.toString().split("'");
const path = errorArray[1];
await Profiles.getInstance().openConfigFile(path);
const errorPath = errorArray[1];
await Profiles.getInstance().openConfigFile(errorPath);
}
}

Expand Down Expand Up @@ -329,3 +331,53 @@ export async function promptCredentials(node: IZoweTreeNode) {
);
}
}

export async function initializeZoweFolder(): Promise<void> {
// ensure the Secure Credentials Enabled value is read
// set globals.PROFILE_SECURITY value accordingly
globals.setGlobalSecurityValue();
// Ensure that ~/.zowe folder exists
// Ensure that the ~/.zowe/settings/imperative.json exists
// TODO: update code below once this imperative issue is resolved.
// https://github.com/zowe/imperative/issues/840
const zoweDir = getZoweDir();
if (!fs.existsSync(zoweDir)) {
fs.mkdirSync(zoweDir);
}
const settingsPath = path.join(zoweDir, "settings");
if (!fs.existsSync(settingsPath)) {
fs.mkdirSync(settingsPath);
}
const settingsFile = path.join(settingsPath, "imperative.json");
if (!fs.existsSync(settingsFile)) {
writeOverridesFile();
}
// If not using team config, ensure that the ~/.zowe/profiles directory
// exists with appropriate types within
if (!imperative.ImperativeConfig.instance.config?.exists) {
await imperative.CliProfileManager.initialize({
configuration: getImperativeConfig().profiles,
profileRootDirectory: path.join(zoweDir, "profiles"),
});
}
}

export function writeOverridesFile() {
let content;
let fileContent: string;
const settingsFile = path.join(getZoweDir(), "settings", "imperative.json");
if (fs.existsSync(settingsFile)) {
content = JSON.parse(fs.readFileSync(settingsFile).toString());
if (content && content?.overrides) {
if (content?.overrides?.CredentialManager === globals.PROFILE_SECURITY) {
return;
}
content.overrides.CredentialManager = globals.PROFILE_SECURITY;
fileContent = JSON.stringify(content, null, 2);
fs.writeFileSync(settingsFile, fileContent, "utf8");
}
} else {
fileContent = JSON.stringify({ overrides: { CredentialManager: globals.PROFILE_SECURITY } }, null, 2);
fs.writeFileSync(settingsFile, fileContent, "utf8");
}
}