UNPKG

n8n-nodes-netdevices

Version:

n8n node to interact with network devices (Cisco, Juniper, Palo Alto PAN-OS, Ciena SAOS, Linux, etc.)

451 lines 21.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NetDevices = void 0; const n8n_workflow_1 = require("n8n-workflow"); const index_1 = require("./utils/index"); let Logger; try { const { LoggerProxy } = require('n8n-workflow'); Logger = LoggerProxy; } catch (error) { Logger = { debug: console.log, info: console.log, warn: console.warn, error: console.error }; } class NetDevices { constructor() { this.description = { displayName: 'Net Devices', name: 'netDevices', icon: 'file:netdevices-icon.svg', group: ['transform'], version: 1, description: 'Manage network devices via SSH', defaults: { name: 'Net Devices', }, usableAsTool: true, inputs: ['main'], outputs: ['main'], credentials: [ { displayName: 'netDevicesApi', name: 'netDevicesApi', required: true, } ], properties: [ { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, required: true, options: [ { name: 'Get Running Config', value: 'getRunningConfig', description: 'Get the current running configuration', action: 'Get the current running configuration', }, { name: 'Reboot Device', value: 'rebootDevice', description: 'Reboot the network device', action: 'Reboot the network device', }, { name: 'Save Config', value: 'saveConfig', description: 'Save the current configuration', action: 'Save the current configuration', }, { name: 'Send Command', value: 'sendCommand', description: 'Send a command to the device and get the response', action: 'Send a command to the device', }, { name: 'Send Config', value: 'sendConfig', description: 'Send configuration commands to the device', action: 'Send configuration commands to the device', }, ], default: 'sendCommand', }, { displayName: 'Command', name: 'command', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['sendCommand'], }, }, description: 'The command to send to the device', placeholder: 'show version', }, { displayName: 'Configuration Commands', name: 'configCommands', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['sendConfig'], }, }, description: 'Configuration commands to send (one per line)', placeholder: 'interface GigabitEthernet1/0/1\ndescription Test Interface\nno shutdown', typeOptions: { rows: 5, }, }, { displayName: 'Advanced Options', name: 'advancedOptions', type: 'collection', placeholder: 'Add Option', default: {}, options: [ { displayName: 'Auto Disconnect', name: 'autoDisconnect', type: 'boolean', default: true, description: 'Whether to automatically disconnect after command execution', }, { displayName: 'Command Retry Count', name: 'commandRetryCount', type: 'number', default: 2, description: 'Number of retry attempts for command failures', typeOptions: { minValue: 1, maxValue: 5, }, }, { displayName: 'Command Timeout', name: 'commandTimeout', type: 'number', default: 10, description: 'Timeout for command execution in seconds', typeOptions: { minValue: 2, maxValue: 300, }, }, { displayName: 'Connection Pooling', name: 'connectionPooling', type: 'boolean', default: false, description: 'Whether to enable connection pooling for better performance', }, { displayName: 'Connection Retry Count', name: 'connectionRetryCount', type: 'number', default: 3, description: 'Number of retry attempts for connection failures', typeOptions: { minValue: 1, maxValue: 10, }, }, { displayName: 'Connection Timeout', name: 'connectionTimeout', type: 'number', default: 15, description: 'Timeout for establishing connection in seconds', typeOptions: { minValue: 3, maxValue: 300, }, }, { displayName: 'Fail on Error', name: 'failOnError', type: 'boolean', default: true, description: 'Whether to fail the workflow on command errors', }, { displayName: 'Fast Mode', name: 'fastMode', type: 'boolean', default: false, description: 'Whether to enable fast mode for simple commands (skips setup steps)', }, { displayName: 'Retry Delay', name: 'retryDelay', type: 'number', default: 2, description: 'Delay between retry attempts in seconds', typeOptions: { minValue: 1, maxValue: 60, }, }, { displayName: 'Reuse Connection', name: 'reuseConnection', type: 'boolean', default: false, description: 'Whether to reuse existing connections when possible', }, ], }, ] }; } async execute() { const items = this.getInputData(); const returnData = []; const credentials = await this.getCredentials('netDevicesApi'); const operation = this.getNodeParameter('operation', 0); Logger.debug('NetDevices node execution started', { operation, itemCount: items.length, host: credentials.host, username: credentials.username, authMethod: credentials.authMethod, deviceType: credentials.deviceType, hasPassword: !!credentials.password, hasPrivateKey: !!credentials.privateKey, hasPassphrase: !!credentials.passphrase, passphraseValue: credentials.passphrase ? `"${credentials.passphrase}"` : 'undefined', passphraseLength: credentials.passphrase ? String(credentials.passphrase).length : 0 }); for (let i = 0; i < items.length; i++) { const startTime = Date.now(); let connection = null; const advancedOptions = this.getNodeParameter('advancedOptions', i, {}); const autoDisconnect = advancedOptions.autoDisconnect !== false; const connectionRetryCount = Math.max(1, advancedOptions.connectionRetryCount || 3); const commandRetryCount = Math.max(1, advancedOptions.commandRetryCount || 2); const retryDelay = Math.max(1, advancedOptions.retryDelay || 2) * 1000; const connectionTimeout = Math.max(5, advancedOptions.connectionTimeout || 15) * 1000; const commandTimeout = Math.max(5, advancedOptions.commandTimeout || 10) * 1000; const failOnError = advancedOptions.failOnError !== false; const fastMode = advancedOptions.fastMode || false; const connectionPooling = advancedOptions.connectionPooling || false; const reuseConnection = advancedOptions.reuseConnection || false; Logger.debug('Processing item with advanced options', { itemIndex: i, operation, autoDisconnect, connectionRetryCount, commandRetryCount, connectionTimeout: connectionTimeout / 1000, commandTimeout: commandTimeout / 1000, fastMode, connectionPooling, reuseConnection }); const deviceCredentials = { host: credentials.host, port: credentials.port, username: credentials.username, authMethod: credentials.authMethod, deviceType: credentials.deviceType, timeout: connectionTimeout / 1000, keepAlive: true, fastMode: fastMode, commandTimeout: commandTimeout, connectionPooling: connectionPooling, reuseConnection: reuseConnection, }; if (deviceCredentials.authMethod === 'privateKey') { deviceCredentials.privateKey = credentials.privateKey; if (credentials.passphrase && String(credentials.passphrase).trim() !== '') { deviceCredentials.passphrase = credentials.passphrase; } } else { deviceCredentials.password = credentials.password; } if (credentials.enablePassword) { deviceCredentials.enablePassword = credentials.enablePassword; } if (credentials.useJumpHost) { deviceCredentials.useJumpHost = true; deviceCredentials.jumpHostHost = credentials.jumpHostHost; deviceCredentials.jumpHostPort = credentials.jumpHostPort; deviceCredentials.jumpHostUsername = credentials.jumpHostUsername; deviceCredentials.jumpHostAuthMethod = credentials.jumpHostAuthMethod; deviceCredentials.jumpHostPassword = credentials.jumpHostPassword; deviceCredentials.jumpHostPrivateKey = credentials.jumpHostPrivateKey; deviceCredentials.jumpHostPassphrase = credentials.jumpHostPassphrase; } Logger.debug('Configured device credentials', { host: deviceCredentials.host, port: deviceCredentials.port, username: deviceCredentials.username, authMethod: deviceCredentials.authMethod, deviceType: deviceCredentials.deviceType, hasPassword: !!deviceCredentials.password, hasPrivateKey: !!deviceCredentials.privateKey, hasPassphrase: !!deviceCredentials.passphrase, passphraseLength: deviceCredentials.passphrase ? deviceCredentials.passphrase.length : 0, timeout: deviceCredentials.timeout, fastMode: deviceCredentials.fastMode, connectionPooling: deviceCredentials.connectionPooling, reuseConnection: deviceCredentials.reuseConnection }); try { let connectionError = null; for (let attempt = 1; attempt <= connectionRetryCount; attempt++) { try { connection = (0, index_1.ConnectHandler)(deviceCredentials); const connectPromise = connection.connect(); const timeoutPromise = new Promise((_, reject) => { global.setTimeout(() => { reject(new Error(`Connection timeout after ${connectionTimeout / 1000} seconds`)); }, connectionTimeout); }); await Promise.race([connectPromise, timeoutPromise]); connectionError = null; break; } catch (error) { connectionError = error instanceof Error ? error : new Error(String(error)); if (attempt === connectionRetryCount) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to connect to device ${credentials.host} after ${connectionRetryCount} attempts. Last error: ${connectionError.message}`, { itemIndex: i, description: `Connection attempts: ${attempt}/${connectionRetryCount}` }); } const delay = retryDelay * Math.pow(2, attempt - 1); await new Promise(resolve => global.setTimeout(resolve, delay)); } } let result; let commandError = null; for (let attempt = 1; attempt <= commandRetryCount; attempt++) { try { const executeCommand = async () => { switch (operation) { case 'sendCommand': const command = this.getNodeParameter('command', i); if (!command) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Command parameter is required for sendCommand operation', { itemIndex: i }); } return await connection.sendCommand(command); case 'sendConfig': const configCommands = this.getNodeParameter('configCommands', i); if (!configCommands) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Configuration commands are required for sendConfig operation', { itemIndex: i }); } const commands = configCommands.split('\n') .map(cmd => cmd.trim()) .filter(cmd => cmd.length > 0); return await connection.sendConfig(commands); case 'getRunningConfig': return await connection.getCurrentConfig(); case 'saveConfig': return await connection.saveConfig(); case 'rebootDevice': return await connection.rebootDevice(); default: throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The operation "${operation}" is not supported!`, { itemIndex: i }); } }; const commandPromise = executeCommand(); const timeoutPromise = new Promise((_, reject) => { global.setTimeout(() => { reject(new Error(`Command timeout after ${commandTimeout / 1000} seconds`)); }, commandTimeout); }); result = await Promise.race([commandPromise, timeoutPromise]); if (!result.success) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), result.error || 'Command execution failed', { itemIndex: i }); } commandError = null; break; } catch (error) { commandError = error instanceof Error ? error : new Error(String(error)); if (attempt === commandRetryCount) { if (failOnError) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Command execution failed after ${commandRetryCount} attempts: ${commandError.message}`, { itemIndex: i, description: `Command attempts: ${attempt}/${commandRetryCount}` }); } else { result = { command: operation, output: '', success: false, error: commandError.message, }; break; } } await new Promise(resolve => global.setTimeout(resolve, retryDelay)); } } const executionTime = Date.now() - startTime; const outputData = { success: result.success, command: result.command, output: result.output, deviceType: connection.getDeviceType(), host: connection.getHost(), timestamp: new Date().toISOString(), executionTime: executionTime, connectionRetries: connectionRetryCount, commandRetries: commandRetryCount, }; if (!result.success && result.error) { outputData.error = result.error; } const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(outputData), { itemData: { item: i } }); returnData.push(...executionData); } catch (error) { if (connection && autoDisconnect) { try { await connection.disconnect(); } catch (disconnectError) { } } if (this.continueOnFail()) { const executionTime = Date.now() - startTime; const errorData = { error: error instanceof Error ? error.message : 'Unknown error', success: false, timestamp: new Date().toISOString(), executionTime: executionTime, host: credentials.host, operation: operation, }; returnData.push({ json: errorData }); continue; } throw error; } finally { if (connection && autoDisconnect && !deviceCredentials.reuseConnection) { try { await connection.disconnect(); } catch (disconnectError) { } } } } return [returnData]; } } exports.NetDevices = NetDevices; //# sourceMappingURL=NetDevices.node.js.map