'use strict' ; import * as vscode from 'vscode' ; import { UserInputUtil , IBlockchainQuickPickItem } from './UserInputUtil' ; import { VSCodeBlockchainOutputAdapter } from '../logging/VSCodeBlockchainOutputAdapter' ; import * as fs from 'fs-extra' ; import * as https from 'https' ; import * as path from 'path' ; import { FabricEnvironmentRegistryEntry , FabricNode , LogType , FabricEnvironment , FabricNodeType , FabricEnvironmentRegistry , EnvironmentType , EnvironmentFlags } from 'ibm-blockchain-platform-common' ; import { ExtensionCommands } from '../../ExtensionCommands' ; import { FabricEnvironmentManager } from '../fabric/environments/FabricEnvironmentManager' ; import { EnvironmentFactory } from '../fabric/environments/EnvironmentFactory' ; import Axios from 'axios' ; import { ExtensionsInteractionUtil } from '../util/ExtensionsInteractionUtil' ; import { ExtensionUtil } from '../util/ExtensionUtil' ; import { SecureStore } from '../util/SecureStore' ; import { SecureStoreFactory } from '../util/SecureStoreFactory' ; export async function importNodesToEnvironment ( environmentRegistryEntry , fromAddEnvironment = false , createMethod ? , informOfChanges = false , showSuccess = true , fromConnectEnvironment = false ) { const outputAdapter = VSCodeBlockchainOutputAdapter . instance ( ) ; const methodMessageString = createMethod !== UserInputUtil . ADD_ENVIRONMENT_FROM_OPS_TOOLS ? 'import' : 'filter' ; if ( showSuccess ) { if ( createMethod === UserInputUtil . ADD_ENVIRONMENT_FROM_NODES ) { outputAdapter . log ( LogType . INFO , undefined , 'Import nodes to environment' ) ; } else { outputAdapter . log ( LogType . INFO , undefined , 'Edit node filters' ) ; } } try { let justUpdate = false ; let chosenEnvironment ; if ( ! environmentRegistryEntry ) { if ( createMethod === UserInputUtil . ADD_ENVIRONMENT_FROM_OPS_TOOLS ) { chosenEnvironment = await UserInputUtil . showFabricEnvironmentQuickPickBox ( 'Choose an OpsTool environment to filter nodes' , false , true , [ EnvironmentFlags . OPS_TOOLS ] ) as IBlockchainQuickPickItem < FabricEnvironmentRegistryEntry > ; } else { chosenEnvironment = await UserInputUtil . showFabricEnvironmentQuickPickBox ( 'Choose an environment to import nodes to' , false , true , [ ] , [ EnvironmentFlags . OPS_TOOLS , EnvironmentFlags . LOCAL ] ) as IBlockchainQuickPickItem < FabricEnvironmentRegistryEntry > ; } if ( ! chosenEnvironment ) { return ; } environmentRegistryEntry = chosenEnvironment . data ; } const connectedRegistryEntry = FabricEnvironmentManager . instance ( ) . getEnvironmentRegistryEntry ( ) ; if ( connectedRegistryEntry && connectedRegistryEntry . name === environmentRegistryEntry . name ) { FabricEnvironmentManager . instance ( ) . stopEnvironmentRefresh ( ) ; } if ( ! fromAddEnvironment ) { if ( environmentRegistryEntry . environmentType && ( environmentRegistryEntry . environmentType === EnvironmentType . OPS_TOOLS_ENVIRONMENT || environmentRegistryEntry . environmentType === EnvironmentType . SAAS_OPS_TOOLS_ENVIRONMENT ) ) { createMethod = UserInputUtil . ADD_ENVIRONMENT_FROM_OPS_TOOLS ; } else { createMethod = UserInputUtil . ADD_ENVIRONMENT_FROM_NODES ; } } const environment = EnvironmentFactory . getEnvironment ( environmentRegistryEntry ) ; const environmentBaseDir = path . resolve ( environment . getPath ( ) ) ; const allNodes = await environment . getNodes ( false , true ) ; const oldNodes = allNodes . filter ( ( _node ) => ! _node . hidden ) ; let nodesToUpdate = [ ] ; let nodesToDelete = [ ] ; let addedAllNodes = true ; if ( createMethod === UserInputUtil . ADD_ENVIRONMENT_FROM_NODES ) { const openDialogOptions = { canSelectFiles : true , canSelectFolders : false , canSelectMany : true , openLabel : 'Select' , filters : { 'Node Files' : [ 'json' ] } } ; const nodeUris = [ ] ; let addMore = true ; do { const selectedNodeUris = await UserInputUtil . browse ( 'Select all the Fabric node (JSON) files you want to import' , UserInputUtil . BROWSE_LABEL , openDialogOptions , true ) as vscode . Uri [ ] ; if ( selectedNodeUris ) { nodeUris . push ( ... selectedNodeUris ) ; } if ( ! nodeUris || nodeUris . length === 0 ) { return ; } const addMoreString = await UserInputUtil . addMoreNodes ( `${ nodeUris . length } JSON file(s) added successfully` ) ; if ( addMoreString === UserInputUtil . ADD_MORE_NODES ) { addMore = true ; } else if ( addMoreString === UserInputUtil . DONE_ADDING_NODES ) { addMore = false ; } else { return ; } } while ( addMore ) ; for ( const nodeUri of nodeUris ) { try { let nodes = await fs . readJson ( nodeUri . fsPath ) ; if ( ! Array . isArray ( nodes ) ) { nodes = [ nodes ] ; } for ( const node of nodes ) { if ( node . display_name ) { node . name = node . display_name ; delete node . display_name ; } if ( node . type === 'fabric-ca' ) { if ( node . tls_cert ) { node . pem = node . tls_cert ; delete node . tls_cert ; } } else { if ( node . tls_ca_root_cert ) { node . pem = node . tls_ca_root_cert ; delete node . tls_ca_root_cert ; } else if ( node . tls_ca_root_certs ) { node . pem = node . tls_ca_root_certs . join ( '\n' ) ; delete node . tls_ca_root_certs ; } } } nodesToUpdate . push ( ... nodes ) ; } catch ( error ) { addedAllNodes = false ; outputAdapter . log ( LogType . ERROR , `Error importing node file ${ nodeUri . fsPath }: ${ error . message }` , `Error importing node file ${ nodeUri . fsPath }: ${ error . toString ( ) }` ) ; } } } else { const GET_ALL_COMPONENTS = '/ak/api/v2/components' ; const api = environmentRegistryEntry . url . replace ( /\/$/ , '' ) + GET_ALL_COMPONENTS ; let response ; let requestOptions ; try { if ( environmentRegistryEntry . environmentType === EnvironmentType . SAAS_OPS_TOOLS_ENVIRONMENT ) { let askUserInput ; if ( fromConnectEnvironment ) { askUserInput = ExtensionUtil . getExtensionSaasConfigUpdatesSetting ( ) ; } else { askUserInput = true ; } const accessToken = await ExtensionsInteractionUtil . cloudAccountGetAccessToken ( askUserInput ) ; if ( ! accessToken ) { if ( fromConnectEnvironment ) { throw new Error ( 'User must be logged in to an IBM Cloud account' ) ; } return ; } requestOptions = { headers : { Authorization : `Bearer ${ accessToken }` } } ; } else { const secureStore = await SecureStoreFactory . getSecureStore ( ) ; const credentials = await secureStore . getPassword ( 'blockchain-vscode-ext' , environmentRegistryEntry . url ) ; const credentialsArray = credentials . split ( ':' ) ; if ( credentialsArray . length !== 3 ) { throw new Error ( `Unable to retrieve the stored credentials` ) ; } const userAuth1 = credentialsArray [ 0 ] ; const userAuth2 = credentialsArray [ 1 ] ; const rejectUnauthorized = credentialsArray [ 2 ] === 'true' ; requestOptions = { headers : { 'Content-Type' : 'application/json' } , auth : { username : userAuth1 , password : userAuth2 } } ; requestOptions . httpsAgent = new https . Agent ( { rejectUnauthorized : rejectUnauthorized } ) ; } response = await Axios . get ( api , requestOptions ) ; const data = response . data ; const components = data . components ; let filteredData = [ ] ; if ( components && components . length > 0 ) { filteredData = components . filter ( ( _component ) => _component . api_url ) . map ( ( _component ) => { _component . name = _component . display_name ; delete _component . display_name ; if ( _component . tls_cert ) { _component . pem = _component . tls_cert ; delete _component . tls_cert ; } if ( _component . type !== 'fabric-ca' ) { if ( _component . tls_ca_root_cert ) { _component . pem = _component . tls_ca_root_cert ; delete _component . tls_ca_root_cert ; } else if ( _component . tls_ca_root_certs ) { _component . pem = _component . tls_ca_root_certs . join ( '\n' ) ; delete _component . tls_ca_root_certs ; } } return FabricNode . pruneNode ( _component ) ; } ) ; let chosenNodes ; chosenNodes = await UserInputUtil . showNodesQuickPickBox ( 'Edit filters: Which nodes would you like to include?' , filteredData , true , allNodes , informOfChanges ) as IBlockchainQuickPickItem < FabricNode > [ ] ; let chosenNodesNames = [ ] ; let chosenNodesUrls = [ ] ; if ( chosenNodes === undefined ) { if ( fromAddEnvironment ) { return ; } justUpdate = true ; chosenNodesNames = oldNodes . map ( ( _node ) => _node . type === FabricNodeType . ORDERER && _node . cluster_name ? _node . cluster_name : _node . name ) ; chosenNodesUrls = oldNodes . map ( ( _node ) => _node . api_url ) ; } else { if ( ! Array . isArray ( chosenNodes ) ) { chosenNodes = [ chosenNodes ] ; } if ( chosenNodes . length > 0 ) { chosenNodesNames = chosenNodes . map ( ( _chosenNode ) => _chosenNode . label ) ; chosenNodesUrls = chosenNodes . map ( ( _chosenNode ) => _chosenNode . data . api_url ) ; } } filteredData . forEach ( ( node ) => { if ( node . type === FabricNodeType . ORDERER && node . cluster_name ) { node . hidden = chosenNodesNames . indexOf ( node . cluster_name ) === - 1 ; } else if ( chosenNodesUrls . indexOf ( node . api_url ) === - 1 ) { node . hidden = true ; } } ) ; } if ( allNodes . length > 0 ) { const filteredNodesUrls = filteredData . map ( ( _node ) => _node . api_url ) ; nodesToDelete = allNodes . filter ( ( _node ) => filteredNodesUrls . indexOf ( _node . api_url ) === - 1 ) ; } nodesToUpdate . push ( ... filteredData ) ; } catch ( error ) { if ( fromConnectEnvironment ) { const newError = new Error ( `Nodes in ${ environmentRegistryEntry . name } might be out of date. Unable to connect to the IBM Blockchain Platform Console with error: ${ error . message }` ) ; outputAdapter . log ( LogType . ERROR , undefined , error . toString ( ) ) ; outputAdapter . log ( LogType . WARNING , newError . message , newError . toString ( ) ) ; throw newError ; } else { outputAdapter . log ( LogType . ERROR , `Failed to acquire nodes from ${ environmentRegistryEntry . url }, with error ${ error . message }` , `Failed to acquire nodes from ${ environmentRegistryEntry . url }, with error ${ error . toString ( ) }` ) ; throw error ; } } } nodesToUpdate = nodesToUpdate . filter ( ( node ) => { const validType = ( node . type === 'fabric-orderer' || node . type === 'fabric-peer' || node . type === 'fabric-ca' ) ; if ( ! validType ) { outputAdapter . log ( LogType . INFO , `Ignoring Node of type ${ node . type }` ) ; return false ; } return true ; } ) . map ( ( node ) => { if ( node . hidden === undefined ) { node . hidden = false ; } return node ; } ) ; const environmentNodesPath = path . join ( environmentBaseDir , 'nodes' ) ; await fs . ensureDir ( environmentNodesPath ) ; for ( const node of nodesToUpdate ) { try { const alreadyExists = oldNodes . some ( ( _node ) => _node . name === node . name ) ; if ( alreadyExists && createMethod === UserInputUtil . ADD_ENVIRONMENT_FROM_NODES ) { throw new Error ( `Node with name ${ node . name } already exists` ) ; } FabricNode . validateNode ( node ) ; await environment . updateNode ( node , createMethod === UserInputUtil . ADD_ENVIRONMENT_FROM_OPS_TOOLS ) ; } catch ( error ) { addedAllNodes = false ; if ( ! node . name ) { outputAdapter . log ( LogType . ERROR , `Error ${ methodMessageString }ing node: ${ error . message }` , `Error ${ methodMessageString }ing node: ${ error . toString ( ) }` ) ; } else { outputAdapter . log ( LogType . ERROR , `Error ${ methodMessageString }ing node ${ node . name }: ${ error . message }` , `Error ${ methodMessageString }ing node ${ node . name }: ${ error . toString ( ) }` ) ; } } } if ( createMethod === UserInputUtil . ADD_ENVIRONMENT_FROM_OPS_TOOLS && ( nodesToDelete . length > 0 || nodesToUpdate . length === 0 ) ) { for ( const node of nodesToDelete ) { try { await vscode . commands . executeCommand ( ExtensionCommands . DELETE_NODE , node , false ) ; } catch ( error ) { if ( ! node . name ) { outputAdapter . log ( LogType . ERROR , `Error deleting node: ${ error . message }` , `Error deleting node: ${ error . toString ( ) }` ) ; } else { outputAdapter . log ( LogType . ERROR , `Error deletinging node ${ node . name }: ${ error . message }` , `Error deleting node ${ node . name }: ${ error . toString ( ) }` ) ; } } } if ( nodesToUpdate . length === 0 && ! fromAddEnvironment ) { const deleteEnvironment = await UserInputUtil . showConfirmationWarningMessage ( `There are no nodes in ${ environment . getName ( ) }. Do you want to delete this environment?` ) ; if ( deleteEnvironment ) { await vscode . commands . executeCommand ( ExtensionCommands . DELETE_ENVIRONMENT , environmentRegistryEntry , true ) ; } } else if ( nodesToUpdate . length === 0 ) { const addEnvironment = await UserInputUtil . showQuickPickYesNo ( `There are no nodes in ${ environment . getName ( ) }. Do you still want to add this environment?` ) ; if ( addEnvironment !== UserInputUtil . YES ) { return ; } } const currentEnvironents = await FabricEnvironmentRegistry . instance ( ) . getAll ( [ ] , [ EnvironmentFlags . LOCAL ] ) ; const stillExists = currentEnvironents . some ( ( _env ) => _env . name === environment . getName ( ) ) ; if ( ! stillExists ) { return ; } } const newEnvironment = EnvironmentFactory . getEnvironment ( environmentRegistryEntry ) ; const newNodes = await newEnvironment . getNodes ( ) ; if ( newNodes . length === oldNodes . length && createMethod === UserInputUtil . ADD_ENVIRONMENT_FROM_NODES ) { throw new Error ( 'no nodes were added' ) ; } if ( justUpdate ) { return ; } else if ( addedAllNodes ) { outputAdapter . log ( LogType . SUCCESS , `Successfully ${ methodMessageString }ed nodes` ) ; } else { outputAdapter . log ( LogType . WARNING , `Finished ${ methodMessageString }ing nodes but some nodes could not be ${ methodMessageString }ed` ) ; } if ( connectedRegistryEntry && connectedRegistryEntry . name === environmentRegistryEntry . name && ! informOfChanges ) { if ( newNodes . length > 0 ) { await vscode . commands . executeCommand ( ExtensionCommands . CONNECT_TO_ENVIRONMENT , environmentRegistryEntry ) ; } else { await vscode . commands . executeCommand ( ExtensionCommands . DISCONNECT_ENVIRONMENT ) ; } } return addedAllNodes ; } catch ( error ) { if ( fromConnectEnvironment ) { return error ; } outputAdapter . log ( LogType . ERROR , `Error ${ methodMessageString }ing nodes: ${ error . message }` ) ; if ( fromAddEnvironment ) { return error ; } } }