From 7cde1e36562c615004d223858ec0c651663013ec Mon Sep 17 00:00:00 2001 From: Simon Stone Date: Tue, 26 May 2020 17:57:36 +0100 Subject: [PATCH] Eclipse Che changes #2328, #2344, #2336, #2337 for v2 branch (#2373) * Automatically create Fablet environment when available (resolves #2328) Signed-off-by: Simon Stone * Fix home directory references and store serialization (contributes to #2344) Signed-off-by: Simon Stone * On Eclipse Che, use projects directory instead of home directory (resolves #2344) Signed-off-by: Simon Stone * Stop duplicating Eclipse Che checks everywhere (contributes to #2336) Signed-off-by: Simon Stone * Automatically select all nodes when on Eclipse Che (resolves #2336) Signed-off-by: Simon Stone * Determine node label before node creation (resolves #2337) Signed-off-by: Simon Stone * Fix compile errors Signed-off-by: Simon Stone Co-authored-by: Caroline Fletcher --- .../src/util/FileSystemUtil.ts | 7 +- .../test/environments/FabletClient.test.ts | 4 + .../test/util/FileSystemUtil.test.ts | 16 +++ .../extension/commands/UserInputUtil.ts | 19 ++- .../dependencies/DependencyManager.ts | 2 +- .../extension/explorer/environmentExplorer.ts | 13 +- .../runtimeOps/connectedTree/NodeTreeItem.ts | 22 --- .../extension/util/ExtensionUtil.ts | 62 +++++++- .../extension/util/FileSystemSecureStore.ts | 2 +- .../extension/util/SecureStoreFactory.ts | 4 +- .../blockchain-extension/package-lock.json | 18 +++ packages/blockchain-extension/package.json | 1 + .../test/commands/UserInputUtil.test.ts | 39 +++++ .../dependencies/DependencyManager.test.ts | 6 +- .../test/explorer/environmentExplorer.test.ts | 35 +++-- .../explorer/runtimeOps/NodeTreeItem.test.ts | 13 +- .../test/util/ExtensionUtil.test.ts | 136 +++++++++++++++++- .../test/util/SecureStoreFactory.test.ts | 4 +- 18 files changed, 334 insertions(+), 69 deletions(-) diff --git a/packages/blockchain-common/src/util/FileSystemUtil.ts b/packages/blockchain-common/src/util/FileSystemUtil.ts index 428d31f604..fe0bfcff3e 100644 --- a/packages/blockchain-common/src/util/FileSystemUtil.ts +++ b/packages/blockchain-common/src/util/FileSystemUtil.ts @@ -25,7 +25,12 @@ export class FileSystemUtil { */ public static getDirPath(dir: string): string { if (dir.startsWith('~')) { - dir = homeDir(dir.replace('~', '')); + const CHE_PROJECTS_ROOT: string = process.env.CHE_PROJECTS_ROOT; + if (CHE_PROJECTS_ROOT) { + dir = dir.replace('~', CHE_PROJECTS_ROOT); + } else { + dir = homeDir(dir.replace('~', '')); + } } return dir; } diff --git a/packages/blockchain-common/test/environments/FabletClient.test.ts b/packages/blockchain-common/test/environments/FabletClient.test.ts index ac6b514922..3106668069 100644 --- a/packages/blockchain-common/test/environments/FabletClient.test.ts +++ b/packages/blockchain-common/test/environments/FabletClient.test.ts @@ -153,6 +153,10 @@ describe('FabletClient', () => { }); }); + afterEach(() => { + mockAxios.restore(); + }); + describe('#getComponents', () => { it('should get the list of components', async () => { diff --git a/packages/blockchain-common/test/util/FileSystemUtil.test.ts b/packages/blockchain-common/test/util/FileSystemUtil.test.ts index c1aa593972..cdfc02af4d 100644 --- a/packages/blockchain-common/test/util/FileSystemUtil.test.ts +++ b/packages/blockchain-common/test/util/FileSystemUtil.test.ts @@ -21,12 +21,28 @@ chai.use(sinonChai); // tslint:disable no-unused-expression describe('FileSystemUtil', () => { describe('getDirPath', () => { + + beforeEach(() => { + delete process.env.CHE_PROJECTS_ROOT; + }); + + afterEach(() => { + delete process.env.CHE_PROJECTS_ROOT; + }); + it('should replace ~ with the users home directory', () => { const packageDirOriginal: string = '~/smartContractDir'; const packageDirNew: string = FileSystemUtil.getDirPath(packageDirOriginal); packageDirNew.should.not.contain('~'); }); + it('should replace ~ with the users projects directory on Eclipse Che', () => { + process.env.CHE_PROJECTS_ROOT = '/projects'; + const packageDirOriginal: string = '~/smartContractDir'; + const packageDirNew: string = FileSystemUtil.getDirPath(packageDirOriginal); + packageDirNew.should.equal('/projects/smartContractDir'); + }); + it('should not replace if not ~', () => { const packageDirOriginal: string = '/banana/smartContractDir'; const packageDirNew: string = FileSystemUtil.getDirPath(packageDirOriginal); diff --git a/packages/blockchain-extension/extension/commands/UserInputUtil.ts b/packages/blockchain-extension/extension/commands/UserInputUtil.ts index 4c11e14e90..1290dfa6a7 100644 --- a/packages/blockchain-extension/extension/commands/UserInputUtil.ts +++ b/packages/blockchain-extension/extension/commands/UserInputUtil.ts @@ -26,6 +26,7 @@ import { EnvironmentFactory } from '../fabric/environments/EnvironmentFactory'; import { TimerUtil } from '../util/TimerUtil'; import { LocalEnvironment } from '../fabric/environments/LocalEnvironment'; import { LocalEnvironmentManager } from '../fabric/environments/LocalEnvironmentManager'; +import { ExtensionUtil } from '../util/ExtensionUtil'; export interface IBlockchainQuickPickItem extends vscode.QuickPickItem { data: T; @@ -1170,7 +1171,15 @@ export class UserInputUtil { // stop the refresh until the user has finished making changes FabricEnvironmentManager.instance().stopEnvironmentRefresh(); // Ask user if they want to edit filters - const editFilters: boolean = await UserInputUtil.showConfirmationWarningMessage('Differences have been detected between the local environment and the Ops Tools environment. Would you like to filter nodes?'); + let editFilters: boolean; + if (ExtensionUtil.isChe()) { + // Issue 2336: see below; because we never show the filter quick pick, we don't want + // to show this message either. Just pretend that they said yes, and then went on + // to select all of the nodes, + editFilters = true; + } else { + editFilters = await UserInputUtil.showConfirmationWarningMessage('Differences have been detected between the local environment and the Ops Tools environment. Would you like to filter nodes?'); + } if (!editFilters) { return; } @@ -1187,6 +1196,14 @@ export class UserInputUtil { placeHolder: prompt }; + if (ExtensionUtil.isChe()) { + // Issue 2336: Eclipse Che doesn't support canPickMany, and that makes this quick pick pointless + // as 99% of the time you want to pick many. Just return all of the nodes instead for now. + return sortedQuickPickItems.map((sortedQuickPickItem: IBlockchainQuickPickItem) => { + sortedQuickPickItem.picked = true; + return sortedQuickPickItem; + }) + } return vscode.window.showQuickPick(sortedQuickPickItems, quickPickOptions); } diff --git a/packages/blockchain-extension/extension/dependencies/DependencyManager.ts b/packages/blockchain-extension/extension/dependencies/DependencyManager.ts index 92adcbd26f..f725be3786 100644 --- a/packages/blockchain-extension/extension/dependencies/DependencyManager.ts +++ b/packages/blockchain-extension/extension/dependencies/DependencyManager.ts @@ -509,7 +509,7 @@ export class DependencyManager { let info: { modules?: string, longVersion: string, shortVersion?: string }; const remote: boolean = vscode.extensions.getExtension(EXTENSION_ID).extensionKind === vscode.ExtensionKind.Workspace; - const che: boolean = 'CHE_WORKSPACE_ID' in process.env; + const che: boolean = ExtensionUtil.isChe(); if (remote || che) { runtime = 'node'; diff --git a/packages/blockchain-extension/extension/explorer/environmentExplorer.ts b/packages/blockchain-extension/extension/explorer/environmentExplorer.ts index 2e722bf4eb..a7bf411b7b 100644 --- a/packages/blockchain-extension/extension/explorer/environmentExplorer.ts +++ b/packages/blockchain-extension/extension/explorer/environmentExplorer.ts @@ -163,13 +163,18 @@ export class BlockchainEnvironmentExplorerProvider implements BlockchainExplorer arguments: [environmentRegistryEntry, node] }; + let label: string = node.cluster_name || node.name; + if (!node.wallet || !node.identity) { + label += ' ⚠'; + } + if (node.type === FabricNodeType.PEER) { - const peerTreeItem: PeerTreeItem = new PeerTreeItem(this, node.name, node.name, environmentRegistryEntry, node, command); + const peerTreeItem: PeerTreeItem = new PeerTreeItem(this, label, node.name, environmentRegistryEntry, node, command); tree.push(peerTreeItem); } if (node.type === FabricNodeType.CERTIFICATE_AUTHORITY) { - const certificateAuthorityTreeItem: CertificateAuthorityTreeItem = new CertificateAuthorityTreeItem(this, node.name, node.name, environmentRegistryEntry, node, command); + const certificateAuthorityTreeItem: CertificateAuthorityTreeItem = new CertificateAuthorityTreeItem(this, label, node.name, environmentRegistryEntry, node, command); tree.push(certificateAuthorityTreeItem); } @@ -177,10 +182,10 @@ export class BlockchainEnvironmentExplorerProvider implements BlockchainExplorer if (node.cluster_name) { const foundTreeItem: BlockchainTreeItem = tree.find((treeItem: OrdererTreeItem) => treeItem.node && treeItem.node.type === FabricNodeType.ORDERER && node.cluster_name === treeItem.node.cluster_name); if (!foundTreeItem) { - tree.push(new OrdererTreeItem(this, node.cluster_name, node.cluster_name, environmentRegistryEntry, node, command)); + tree.push(new OrdererTreeItem(this, label, node.cluster_name, environmentRegistryEntry, node, command)); } } else { - tree.push(new OrdererTreeItem(this, node.name, node.name, environmentRegistryEntry, node, command)); + tree.push(new OrdererTreeItem(this, label, node.name, environmentRegistryEntry, node, command)); } } } diff --git a/packages/blockchain-extension/extension/explorer/runtimeOps/connectedTree/NodeTreeItem.ts b/packages/blockchain-extension/extension/explorer/runtimeOps/connectedTree/NodeTreeItem.ts index 7e305f99b4..c0a1a91955 100644 --- a/packages/blockchain-extension/extension/explorer/runtimeOps/connectedTree/NodeTreeItem.ts +++ b/packages/blockchain-extension/extension/explorer/runtimeOps/connectedTree/NodeTreeItem.ts @@ -21,29 +21,7 @@ import * as vscode from 'vscode'; export abstract class NodeTreeItem extends BlockchainTreeItem { - private orginalLabel: string; - constructor(provider: BlockchainExplorerProvider, label: string, public readonly tooltip: string, public readonly environmentRegistryEntry: FabricEnvironmentRegistryEntry, public readonly node: FabricNode, public readonly command?: vscode.Command) { super(provider, label, vscode.TreeItemCollapsibleState.None); - - this.orginalLabel = label; - - this.updateProperties(); - } - - protected updateProperties(): void { - let newLabel: string = this.orginalLabel; - - if (!this.node.wallet || !this.node.identity) { - newLabel += ' ⚠'; - } - - this.setLabel(newLabel); - this.refresh(); - } - - private setLabel(label: string): void { - // label is readonly so make it less readonly - (this as any).label = label; } } diff --git a/packages/blockchain-extension/extension/util/ExtensionUtil.ts b/packages/blockchain-extension/extension/util/ExtensionUtil.ts index e47166271b..6fa14f7e2f 100644 --- a/packages/blockchain-extension/extension/util/ExtensionUtil.ts +++ b/packages/blockchain-extension/extension/util/ExtensionUtil.ts @@ -87,7 +87,7 @@ import { GlobalState, ExtensionData } from './GlobalState'; import { TemporaryCommandRegistry } from '../dependencies/TemporaryCommandRegistry'; import { version as currentExtensionVersion, dependencies } from '../../package.json'; import { UserInputUtil } from '../commands/UserInputUtil'; -import { FabricSmartContractDefinition, FabricEnvironmentRegistry, FabricEnvironmentRegistryEntry, FabricNode, FabricRuntimeUtil, FabricWalletRegistry, FabricWalletRegistryEntry, FileRegistry, LogType, FabricGatewayRegistry, FabricGatewayRegistryEntry, EnvironmentType, EnvironmentFlags } from 'ibm-blockchain-platform-common'; +import { FabricSmartContractDefinition, FabricEnvironmentRegistry, FabricEnvironmentRegistryEntry, FabricNode, FabricRuntimeUtil, FabricWalletRegistry, FabricWalletRegistryEntry, FileRegistry, LogType, FabricGatewayRegistry, FabricGatewayRegistryEntry, EnvironmentType, EnvironmentFlags, FileSystemUtil, FileConfigurations } from 'ibm-blockchain-platform-common'; import { FabricDebugConfigurationProvider } from '../debug/FabricDebugConfigurationProvider'; import { importNodesToEnvironment } from '../commands/importNodesToEnvironmentCommand'; import { deleteNode } from '../commands/deleteNodeCommand'; @@ -107,6 +107,8 @@ import { deploySmartContract } from '../commands/deployCommand'; import { openDeployView } from '../commands/openDeployView'; import { saveTutorial } from '../commands/saveTutorialCommand'; import { manageFeatureFlags } from '../commands/manageFeatureFlags'; +import Axios from 'axios'; +import { URL } from 'url'; let blockchainGatewayExplorerProvider: BlockchainGatewayExplorerProvider; let blockchainPackageExplorerProvider: BlockchainPackageExplorerProvider; @@ -668,6 +670,9 @@ export class ExtensionUtil { extensionData.generatorVersion = generatorVersion; await GlobalState.update(extensionData); } + + // Discover any environments and ensure they are available. + await this.discoverEnvironments(); } /* @@ -717,6 +722,61 @@ export class ExtensionUtil { return vscode.workspace.getConfiguration().get(SettingConfigurations.EXTENSION_SAAS_CONFIG_UPDATES); } + public static async discoverEnvironments(): Promise { + + // First, check to see if we're running in Eclipse Che; currently + // we can only discover environments created by Eclipse Che. + if (ExtensionUtil.isChe()) { + await this.discoverCheEnvironments(); + } + + } + + public static async discoverCheEnvironments(): Promise { + + // Check for a Fablet instance running within this Eclipse Che. + const FABLET_SERVICE_HOST: string = process.env['FABLET_SERVICE_HOST']; + const FABLET_SERVICE_PORT: string = process.env['FABLET_SERVICE_PORT']; + if (!FABLET_SERVICE_HOST || !FABLET_SERVICE_PORT) { + return; + } + const url: string = `http://${FABLET_SERVICE_HOST}:${FABLET_SERVICE_PORT}`; + + // Try to connect to the Fablet instance. + try { + await Axios.get(new URL('/ak/api/v1/health', url).toString()); + } catch (error) { + // This isn't a valid Fablet instance. + return; + } + + // Determine where this environment should store any files. + const extensionDirectory: string = vscode.workspace.getConfiguration().get(SettingConfigurations.EXTENSION_DIRECTORY); + const resolvedExtensionDirectory: string = FileSystemUtil.getDirPath(extensionDirectory); + const environmentDirectory: string = path.join(resolvedExtensionDirectory, FileConfigurations.FABRIC_ENVIRONMENTS, 'Fablet'); + + // Register the Fablet instance. + const environmentRegistry: FabricEnvironmentRegistry = FabricEnvironmentRegistry.instance(); + const environmentExists: boolean = await environmentRegistry.exists('Fablet'); + const environmentRegistryEntry: FabricEnvironmentRegistryEntry = new FabricEnvironmentRegistryEntry({ + name: 'Fablet', + managedRuntime: false, + environmentType: EnvironmentType.FABLET_ENVIRONMENT, + environmentDirectory, + url + }); + if (!environmentExists) { + await environmentRegistry.add(environmentRegistryEntry); + } else { + await environmentRegistry.update(environmentRegistryEntry); + } + + } + + public static isChe(): boolean { + return 'CHE_WORKSPACE_ID' in process.env; + } + private static getExtension(): vscode.Extension { return vscode.extensions.getExtension(EXTENSION_ID); } diff --git a/packages/blockchain-extension/extension/util/FileSystemSecureStore.ts b/packages/blockchain-extension/extension/util/FileSystemSecureStore.ts index 7f1fe77bbc..09e0b34f69 100644 --- a/packages/blockchain-extension/extension/util/FileSystemSecureStore.ts +++ b/packages/blockchain-extension/extension/util/FileSystemSecureStore.ts @@ -88,7 +88,7 @@ export class FileSystemSecureStore implements SecureStore { private async save(store: Store): Promise { const data: string = Buffer.from(JSON.stringify(store), 'utf8').toString('base64'); - return fs.writeJson(this.path, data, { encoding: 'utf8', mode: 0o600 }); + return fs.writeFile(this.path, data, { encoding: 'utf8', mode: 0o600 }); } } diff --git a/packages/blockchain-extension/extension/util/SecureStoreFactory.ts b/packages/blockchain-extension/extension/util/SecureStoreFactory.ts index c3037ece1e..829f1fcc91 100644 --- a/packages/blockchain-extension/extension/util/SecureStoreFactory.ts +++ b/packages/blockchain-extension/extension/util/SecureStoreFactory.ts @@ -19,6 +19,7 @@ import { SettingConfigurations } from '../configurations'; import * as path from 'path'; import * as vscode from 'vscode'; import { FileSystemSecureStore } from './FileSystemSecureStore'; +import { FileSystemUtil } from 'ibm-blockchain-platform-common'; export class SecureStoreFactory { @@ -28,7 +29,8 @@ export class SecureStoreFactory { return new KeytarSecureStore(keytar); } const extensionDirectory: string = vscode.workspace.getConfiguration().get(SettingConfigurations.EXTENSION_DIRECTORY); - const storePath: string = path.join(extensionDirectory, 'ibm-blockchain-platform.store'); + const resolvedExtensionDirectory: string = FileSystemUtil.getDirPath(extensionDirectory); + const storePath: string = path.join(resolvedExtensionDirectory, 'ibm-blockchain-platform.store'); return new FileSystemSecureStore(storePath); } diff --git a/packages/blockchain-extension/package-lock.json b/packages/blockchain-extension/package-lock.json index f63135d7c2..45b082ecc2 100644 --- a/packages/blockchain-extension/package-lock.json +++ b/packages/blockchain-extension/package-lock.json @@ -2747,6 +2747,24 @@ "follow-redirects": "1.5.10" } }, + "axios-mock-adapter": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.18.1.tgz", + "integrity": "sha512-kFBZsG1Ma5yxjRGHq5KuuL55mPb7WzFULhypquEhzPg8SH5CXICb+qwC2CCA5u+GQVpiqGPwKSRkd3mBCs6gdw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "is-buffer": "^2.0.3" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + } + } + }, "axobject-query": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.2.tgz", diff --git a/packages/blockchain-extension/package.json b/packages/blockchain-extension/package.json index 9fe93f7dcd..6116d74071 100755 --- a/packages/blockchain-extension/package.json +++ b/packages/blockchain-extension/package.json @@ -1414,6 +1414,7 @@ "@types/tmp": "0.0.33", "@types/vscode": "1.38.0", "angular-tslint-rules": "1.5.0", + "axios-mock-adapter": "^1.18.1", "chai": "4.1.2", "chai-as-promised": "7.1.1", "cucumber": "2.2.0", diff --git a/packages/blockchain-extension/test/commands/UserInputUtil.test.ts b/packages/blockchain-extension/test/commands/UserInputUtil.test.ts index 347b849e31..153c6dc305 100644 --- a/packages/blockchain-extension/test/commands/UserInputUtil.test.ts +++ b/packages/blockchain-extension/test/commands/UserInputUtil.test.ts @@ -34,6 +34,7 @@ import { FabricEnvironmentManager } from '../../extension/fabric/environments/Fa import { LocalEnvironment } from '../../extension/fabric/environments/LocalEnvironment'; import { LocalEnvironmentManager } from '../../extension/fabric/environments/LocalEnvironmentManager'; import { FabricInstalledSmartContract } from 'ibm-blockchain-platform-common/build/src/fabricModel/FabricInstalledSmartContract'; +import { ExtensionUtil } from '../../extension/util/ExtensionUtil'; chai.use(sinonChai); const should: Chai.Should = chai.should(); @@ -2988,6 +2989,22 @@ describe('UserInputUtil', () => { stopRefreshStub.should.not.have.been.called; }); + it('should just select all nodes from Ops Tools without asking on Eclipse Che', async () => { + mySandBox.stub(ExtensionUtil, 'isChe').returns(true); + quickPickStub.rejects(new Error('should not be called')); + + const result: IBlockchainQuickPickItem[] = await UserInputUtil.showNodesQuickPickBox('choose your nodes', nodes, true) as IBlockchainQuickPickItem[]; + + for (const node of nodesPickWithoutCurrent) { + node.picked = true; + } + + result.should.deep.equal(nodesPickWithoutCurrent); + quickPickStub.should.not.have.been.called; + sortNodesForQuickpickStub.should.have.been.called; + stopRefreshStub.should.not.have.been.called; + }); + it('should show description "(new)" when a new node is present from Ops Tool', async () => { const newPeerNode: FabricNode = FabricNode.newPeer('peerNew.org1.example.com', 'peerNew.org1.example.com', 'grps://somehost:7056', 'cake_fabric_wallet', 'admin', 'Org1MSP'); const newNodes: FabricNode[] = Array.from(nodes); @@ -3061,6 +3078,28 @@ describe('UserInputUtil', () => { changeDetectedWarningMessage.should.have.been.called; stopRefreshStub.should.have.been.called; }); + + it('should just select all nodes without asking on Eclipse Che when informOfChanges is true and changes are detected when connecting to Ops Tool', async () => { + mySandBox.stub(ExtensionUtil, 'isChe').returns(true); + const newPeerNode: FabricNode = FabricNode.newPeer('peerNew.org1.example.com', 'peerNew.org1.example.com', 'grps://somehost:7056', 'cake_fabric_wallet', 'admin', 'Org1MSP'); + const newNodes: FabricNode[] = Array.from(nodes); + newNodes.push(newPeerNode); + nodesPickWithoutCurrent.push({ label: newPeerNode.name, data: newPeerNode, description: '(new)' }); + changeDetectedWarningMessage.resolves(true); + quickPickStub.rejects(new Error('should not be called')); + + const result: IBlockchainQuickPickItem[] = await UserInputUtil.showNodesQuickPickBox('choose your nodes', newNodes, true, nodes, true) as IBlockchainQuickPickItem[]; + + for (const node of nodesPickWithoutCurrent) { + node.picked = true; + } + + result.should.deep.equal(nodesPickWithoutCurrent); + quickPickStub.should.not.have.been.called; + sortNodesForQuickpickStub.should.have.been.called; + changeDetectedWarningMessage.should.not.have.been.called; + stopRefreshStub.should.have.been.called; + }); }); describe('sortNodesForQuickpick', () => { diff --git a/packages/blockchain-extension/test/dependencies/DependencyManager.test.ts b/packages/blockchain-extension/test/dependencies/DependencyManager.test.ts index 9fa6fe61b0..c45b5492a0 100644 --- a/packages/blockchain-extension/test/dependencies/DependencyManager.test.ts +++ b/packages/blockchain-extension/test/dependencies/DependencyManager.test.ts @@ -43,10 +43,6 @@ describe('DependencyManager Tests', () => { getExtensionLocalFabricSetting = mySandBox.stub(ExtensionUtil, 'getExtensionLocalFabricSetting').returns(true); }); - afterEach(async () => { - delete process.env.CHE_WORKSPACE_ID; - }); - describe('hasNativeDependenciesInstalled', () => { afterEach(() => { @@ -480,7 +476,7 @@ describe('DependencyManager Tests', () => { extensionKindStub.onThirdCall().returns({ extensionKind: 2 }); mySandBox.stub(process, 'platform').value(platform); mySandBox.stub(process, 'arch').value(arch); - process.env.CHE_WORKSPACE_ID = 'wowsuchcheworkspaceid'; + mySandBox.stub(ExtensionUtil, 'isChe').returns(true); const sendCommandStub: sinon.SinonStub = mySandBox.stub(CommandUtil, 'sendCommandWithOutput').resolves(); const dependencyManager: DependencyManager = DependencyManager.instance(); diff --git a/packages/blockchain-extension/test/explorer/environmentExplorer.test.ts b/packages/blockchain-extension/test/explorer/environmentExplorer.test.ts index f9f0d04e36..fa625fe206 100644 --- a/packages/blockchain-extension/test/explorer/environmentExplorer.test.ts +++ b/packages/blockchain-extension/test/explorer/environmentExplorer.test.ts @@ -331,14 +331,15 @@ describe('environmentExplorer', () => { getStateStub.returns(ConnectedState.SETUP); const peerNode: FabricNode = FabricNode.newPeer('peer1', 'peer1.org1.example.com', 'http://peer.sample.org', undefined, undefined, undefined); - const ordererNode: FabricNode = FabricNode.newOrderer('orderer', 'orderer.example.com', 'http://orderer.sample.org', undefined, undefined, undefined, 'Ordering Service'); - const ordererNode1: FabricNode = FabricNode.newOrderer('orderer1', 'orderer1.example.com', 'http://orderer.sample.org', undefined, undefined, undefined, 'Ordering Service'); - const ordererNode2: FabricNode = FabricNode.newOrderer('orderer2', 'orderer2.example.com', 'http://orderer.sample.org', undefined, undefined, undefined, 'Another Ordering Service'); - const ordererNode3: FabricNode = FabricNode.newOrderer('orderer3', 'orderer3.example.com', 'http://orderer.sample.org', undefined, undefined, undefined, 'Another Ordering Service'); + const peerNode2: FabricNode = FabricNode.newPeer('peer2', 'peer2.org1.example.com', 'http://peer.sample.org', 'some wallet', 'some identity', undefined); + const ordererNode: FabricNode = FabricNode.newOrderer('orderer', 'orderer.example.com', 'http://orderer.sample.org', 'some wallet', undefined, undefined, 'Ordering Service'); + const ordererNode1: FabricNode = FabricNode.newOrderer('orderer1', 'orderer1.example.com', 'http://orderer.sample.org', 'some wallet', undefined, undefined, 'Ordering Service'); + const ordererNode2: FabricNode = FabricNode.newOrderer('orderer2', 'orderer2.example.com', 'http://orderer.sample.org', undefined, 'some identity', undefined, 'Another Ordering Service'); + const ordererNode3: FabricNode = FabricNode.newOrderer('orderer3', 'orderer3.example.com', 'http://orderer.sample.org', undefined, 'some identity', undefined, 'Another Ordering Service'); const caNode: FabricNode = FabricNode.newCertificateAuthority('ca1', 'ca1.org1.example.com', 'http://ca.sample.org', undefined, undefined, undefined, undefined, undefined, undefined); - mySandBox.stub(FabricEnvironment.prototype, 'getNodes').resolves([peerNode, ordererNode, ordererNode1, ordererNode2, ordererNode3, caNode]); + mySandBox.stub(FabricEnvironment.prototype, 'getNodes').resolves([peerNode, peerNode2, ordererNode, ordererNode1, ordererNode2, ordererNode3, caNode]); const environmentRegistry: FabricEnvironmentRegistryEntry = new FabricEnvironmentRegistryEntry(); environmentRegistry.name = 'myEnvironment'; @@ -352,25 +353,29 @@ describe('environmentExplorer', () => { commandStub.should.have.been.calledWith('setContext', 'blockchain-environment-setup', true); should.not.exist(blockchainRuntimeExplorerProvider['fabricEnvironmentToSetUp']); - children.length.should.equal(6); + children.length.should.equal(7); children[0].label.should.equal(`Setting up: ${environmentRegistry.name}`); children[1].label.should.equal(`(Click each node to perform setup)`); children[2].label.should.equal('peer1.org1.example.com ⚠'); children[2].command.command.should.equal(ExtensionCommands.ASSOCIATE_IDENTITY_NODE); children[2].command.arguments.should.deep.equal([environmentRegistry, peerNode]); children[2].tooltip.should.equal(peerNode.name); - children[3].label.should.equal('Ordering Service ⚠'); + children[3].label.should.equal('peer2.org1.example.com'); children[3].command.command.should.equal(ExtensionCommands.ASSOCIATE_IDENTITY_NODE); - children[3].command.arguments.should.deep.equal([environmentRegistry, ordererNode]); - children[3].tooltip.should.equal(ordererNode.cluster_name); - children[4].label.should.equal('Another Ordering Service ⚠'); + children[3].command.arguments.should.deep.equal([environmentRegistry, peerNode2]); + children[3].tooltip.should.equal(peerNode2.name); + children[4].label.should.equal('Ordering Service ⚠'); children[4].command.command.should.equal(ExtensionCommands.ASSOCIATE_IDENTITY_NODE); - children[4].command.arguments.should.deep.equal([environmentRegistry, ordererNode2]); - children[4].tooltip.should.equal(ordererNode2.cluster_name); - children[5].label.should.equal('ca1.org1.example.com ⚠'); + children[4].command.arguments.should.deep.equal([environmentRegistry, ordererNode]); + children[4].tooltip.should.equal(ordererNode.cluster_name); + children[5].label.should.equal('Another Ordering Service ⚠'); children[5].command.command.should.equal(ExtensionCommands.ASSOCIATE_IDENTITY_NODE); - children[5].command.arguments.should.deep.equal([environmentRegistry, caNode]); - children[5].tooltip.should.equal(caNode.name); + children[5].command.arguments.should.deep.equal([environmentRegistry, ordererNode2]); + children[5].tooltip.should.equal(ordererNode2.cluster_name); + children[6].label.should.equal('ca1.org1.example.com ⚠'); + children[6].command.command.should.equal(ExtensionCommands.ASSOCIATE_IDENTITY_NODE); + children[6].command.arguments.should.deep.equal([environmentRegistry, caNode]); + children[6].tooltip.should.equal(caNode.name); }); it('should error if gRPC cant connect to Fabric', async () => { diff --git a/packages/blockchain-extension/test/explorer/runtimeOps/NodeTreeItem.test.ts b/packages/blockchain-extension/test/explorer/runtimeOps/NodeTreeItem.test.ts index 3e88a3f9e4..96cdbd9199 100644 --- a/packages/blockchain-extension/test/explorer/runtimeOps/NodeTreeItem.test.ts +++ b/packages/blockchain-extension/test/explorer/runtimeOps/NodeTreeItem.test.ts @@ -44,7 +44,7 @@ describe('NodeTreeItem', () => { beforeEach(async () => { await ExtensionUtil.activateExtension(); - node = FabricNode.newPeer('peer1', 'peer1.org1.example.com', 'http://peer.sample.org', undefined, undefined, undefined); + node = FabricNode.newPeer('peer1', 'peer1.org1.example.com', 'http://peer.sample.org', 'admin', 'myWallet', 'Org1MSP'); provider = ExtensionUtil.getBlockchainGatewayExplorerProvider(); }); @@ -54,16 +54,7 @@ describe('NodeTreeItem', () => { }); describe('#constructor', () => { - it('should have the right properties for a node without identity and wallet', async () => { - const treeItem: TestTreeItem = new TestTreeItem(provider, 'peer1.org1.example.com', 'peer1.org1.example.com', new FabricEnvironmentRegistryEntry(), node); - - treeItem.label.should.equal('peer1.org1.example.com ⚠'); - treeItem.tooltip.should.equal('peer1.org1.example.com'); - }); - - it('should have the right properties for a node with identity and wallet', async () => { - node.identity = 'admin'; - node.wallet = 'myWallet'; + it('should have the right properties for a node', async () => { const tooltip: string = `Name: ${node.name}\nMSPID: ${node.msp_id}\nAssociated Identity:\n${node.identity}`; const treeItem: TestTreeItem = new TestTreeItem(provider, 'peer1.org1.example.com', tooltip, new FabricEnvironmentRegistryEntry(), node); diff --git a/packages/blockchain-extension/test/util/ExtensionUtil.test.ts b/packages/blockchain-extension/test/util/ExtensionUtil.test.ts index 3a7a5dc5fb..02eae49872 100644 --- a/packages/blockchain-extension/test/util/ExtensionUtil.test.ts +++ b/packages/blockchain-extension/test/util/ExtensionUtil.test.ts @@ -43,7 +43,9 @@ import { FabricRuntimeUtil, FabricWalletRegistry, FabricWalletRegistryEntry, - LogType + LogType, + FileSystemUtil, + FileConfigurations } from 'ibm-blockchain-platform-common'; import {FabricDebugConfigurationProvider} from '../../extension/debug/FabricDebugConfigurationProvider'; import {TestUtil} from '../TestUtil'; @@ -54,6 +56,8 @@ import { FabricEnvironmentManager, ConnectedState } from '../../extension/fabric import { FabricEnvironmentConnection } from 'ibm-blockchain-platform-environment-v1'; import { ManagedAnsibleEnvironmentManager } from '../../extension/fabric/environments/ManagedAnsibleEnvironmentManager'; import { ManagedAnsibleEnvironment } from '../../extension/fabric/environments/ManagedAnsibleEnvironment'; +import Axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; const should: Chai.Should = chai.should(); chai.use(sinonChai); @@ -236,14 +240,14 @@ describe('ExtensionUtil Tests', () => { const allCommands: Array = await vscode.commands.getCommands(); const commands: Array = allCommands.filter((command: string) => { + if (command.endsWith('.focus') || command.endsWith('.resetViewLocation')) { + // VSCode creates commands for tree views, so ignore those. + return false; + } return command.startsWith('gatewaysExplorer') || command.startsWith('aPackagesExplorer') || command.startsWith('environmentExplorer') || command.startsWith('extensionHome') || command.startsWith('walletExplorer') || command.startsWith('preReq') || command.startsWith('releaseNotes'); }); commands.should.deep.equal([ - 'aPackagesExplorer.focus', - 'environmentExplorer.focus', - 'gatewaysExplorer.focus', - 'walletExplorer.focus', ExtensionCommands.REFRESH_GATEWAYS, ExtensionCommands.CONNECT_TO_GATEWAY, ExtensionCommands.DISCONNECT_GATEWAY, @@ -1527,6 +1531,16 @@ describe('ExtensionUtil Tests', () => { executeCommandStub.should.have.been.calledWith('setContext', 'local-fabric-enabled', false); }); + + it('should discover environments', async () => { + dependencies['generator-fabric'] = '0.0.2'; + globalStateGetStub.returns({ + generatorVersion: '0.0.1' + }); + const spy: sinon.SinonSpy = mySandBox.spy(ExtensionUtil, 'discoverEnvironments'); + await ExtensionUtil.completeActivation(false); + spy.should.have.been.calledOnce; + }); }); describe('setupLocalRuntime', () => { @@ -2136,4 +2150,116 @@ describe('ExtensionUtil Tests', () => { }); }); + describe('discoverEnvironments', () => { + + let addStub: sinon.SinonStub; + let updateStub: sinon.SinonStub; + let existsStub: sinon.SinonStub; + let mockAxios: MockAdapter; + let expectedEnvironmentDirectory: string; + + beforeEach(() => { + addStub = mySandBox.stub(FabricEnvironmentRegistry.instance(), 'add'); + updateStub = mySandBox.stub(FabricEnvironmentRegistry.instance(), 'update'); + existsStub = mySandBox.stub(FabricEnvironmentRegistry.instance(), 'exists'); + existsStub.resolves(false); + mockAxios = new MockAdapter(Axios); + mockAxios.onGet('http://console.fablet.example.org:9876/ak/api/v1/health').reply(200, {}); + const extensionDirectory: string = vscode.workspace.getConfiguration().get(SettingConfigurations.EXTENSION_DIRECTORY); + const resolvedExtensionDirectory: string = FileSystemUtil.getDirPath(extensionDirectory); + expectedEnvironmentDirectory = path.join(resolvedExtensionDirectory, FileConfigurations.FABRIC_ENVIRONMENTS, 'Fablet'); + }); + + afterEach(() => { + delete process.env.FABLET_SERVICE_HOST; + delete process.env.FABLET_SERVICE_PORT; + mockAxios.restore(); + }); + + it('should not do anything if not running in Eclipse Che', async () => { + await ExtensionUtil.discoverEnvironments(); + addStub.should.not.have.been.called; + updateStub.should.not.have.been.called; + }); + + it('should not do anything if running in Eclipse Che, but FABLET_SERVICE_HOST is not set', async () => { + mySandBox.stub(ExtensionUtil, 'isChe').returns(true); + // process.env.FABLET_SERVICE_HOST = 'console.fablet.example.org'; + process.env.FABLET_SERVICE_PORT = '9876'; + await ExtensionUtil.discoverEnvironments(); + addStub.should.not.have.been.called; + updateStub.should.not.have.been.called; + }); + + it('should not do anything if running in Eclipse Che, but FABLET_SERVICE_PORT is not set', async () => { + mySandBox.stub(ExtensionUtil, 'isChe').returns(true); + process.env.FABLET_SERVICE_HOST = 'console.fablet.example.org'; + // process.env.FABLET_SERVICE_PORT = '9876'; + await ExtensionUtil.discoverEnvironments(); + addStub.should.not.have.been.called; + updateStub.should.not.have.been.called; + }); + + it('should not do anything if running in Eclipse Che, but the Fablet instance does not work', async () => { + mySandBox.stub(ExtensionUtil, 'isChe').returns(true); + process.env.FABLET_SERVICE_HOST = 'console.fablet.example.org'; + process.env.FABLET_SERVICE_PORT = '9876'; + mockAxios.onGet('http://console.fablet.example.org:9876/ak/api/v1/health').reply(404, {}); + await ExtensionUtil.discoverEnvironments(); + addStub.should.not.have.been.called; + updateStub.should.not.have.been.called; + }); + + it('should discover and add a new environment for a Fablet instance running in Eclipse Che', async () => { + mySandBox.stub(ExtensionUtil, 'isChe').returns(true); + process.env.FABLET_SERVICE_HOST = 'console.fablet.example.org'; + process.env.FABLET_SERVICE_PORT = '9876'; + await ExtensionUtil.discoverEnvironments(); + addStub.should.have.been.calledOnceWithExactly({ + name: 'Fablet', + managedRuntime: false, + environmentType: EnvironmentType.FABLET_ENVIRONMENT, + environmentDirectory: expectedEnvironmentDirectory, + url: 'http://console.fablet.example.org:9876' + }); + }); + + it('should discover and update an existing environment for a Fablet instance running in Eclipse Che', async () => { + mySandBox.stub(ExtensionUtil, 'isChe').returns(true); + process.env.FABLET_SERVICE_HOST = 'console.fablet.example.org'; + process.env.FABLET_SERVICE_PORT = '9876'; + existsStub.resolves(true); + await ExtensionUtil.discoverEnvironments(); + updateStub.should.have.been.calledOnceWithExactly({ + name: 'Fablet', + managedRuntime: false, + environmentType: EnvironmentType.FABLET_ENVIRONMENT, + environmentDirectory: expectedEnvironmentDirectory, + url: 'http://console.fablet.example.org:9876' + }); + }); + + }); + + describe('isChe', () => { + + beforeEach(async () => { + delete process.env.CHE_WORKSPACE_ID; + }); + + afterEach(async () => { + delete process.env.CHE_WORKSPACE_ID; + }); + + it('should return true on Eclipse Che', () => { + process.env.CHE_WORKSPACE_ID = 'workspacen5jfcuq4dy2cthww'; + ExtensionUtil.isChe().should.be.true; + }); + + it('should return false when not on Eclipse Che', () => { + ExtensionUtil.isChe().should.be.false; + }); + + }); + }); diff --git a/packages/blockchain-extension/test/util/SecureStoreFactory.test.ts b/packages/blockchain-extension/test/util/SecureStoreFactory.test.ts index e60625d59f..c257cdf1b7 100644 --- a/packages/blockchain-extension/test/util/SecureStoreFactory.test.ts +++ b/packages/blockchain-extension/test/util/SecureStoreFactory.test.ts @@ -24,6 +24,7 @@ import * as path from 'path'; import * as sinon from 'sinon'; import * as vscode from 'vscode'; import { SettingConfigurations } from '../../extension/configurations'; +import { FileSystemUtil } from 'ibm-blockchain-platform-common'; chai.should(); chai.use(chaiAsPromised); @@ -54,7 +55,8 @@ describe('SecureStoreFactory', () => { const secureStore: SecureStore = await SecureStoreFactory.getSecureStore(); secureStore.should.be.an.instanceOf(FileSystemSecureStore); const extensionDirectory: string = vscode.workspace.getConfiguration().get(SettingConfigurations.EXTENSION_DIRECTORY); - const storePath: string = path.join(extensionDirectory, 'ibm-blockchain-platform.store'); + const resolvedExtensionDirectory: string = FileSystemUtil.getDirPath(extensionDirectory); + const storePath: string = path.join(resolvedExtensionDirectory, 'ibm-blockchain-platform.store'); secureStore['path'].should.equal(storePath); });