n8n-nodes-customssh
Version:
n8n community node for advanced SSH connections with configurable ciphers and network device support
986 lines • 57.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomSsh = void 0;
// CustomSsh.node.ts
const n8n_workflow_1 = require("n8n-workflow");
const CipherUtils_1 = require("./utils/CipherUtils");
const SshCommandExecutor_1 = require("./services/SshCommandExecutor");
const ConfigManager_1 = require("./services/ConfigManager");
/**
* Custom SSH Node for n8n that handles network device connections with varied SSH requirements
*/
class CustomSsh {
constructor() {
this.description = {
displayName: 'Custom SSH',
name: 'customSsh',
icon: 'fa:terminal',
group: ['input'],
version: 2,
subtitle: '={{$parameter["operation"]}}',
description: 'Execute commands on network devices via SSH with configurable ciphers',
defaults: {
name: 'Custom SSH',
color: '#000000',
},
usableAsTool: true,
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'sshPassword',
required: true,
},
],
properties: [
// Connection Properties
{
displayName: 'Operation',
name: 'operation',
type: 'options',
options: [
{
name: 'Execute Command',
value: 'executeCommand',
description: 'Execute a command on a remote device',
action: 'Execute a command on a remote device',
},
{
name: 'Execute Multiple Commands',
value: 'executeMultipleCommands',
description: 'Execute multiple commands on a remote device',
action: 'Execute multiple commands on a remote device',
},
{
name: 'Execute Command List',
value: 'executeCommandList',
description: 'Execute multiple commands from a text list (AI-friendly)',
action: 'Execute commands from list',
},
{
name: 'Backup Configuration',
value: 'backupConfig',
description: 'Export device configuration',
action: 'Backup device configuration',
},
{
name: 'Restore Configuration',
value: 'restoreConfig',
description: 'Restore configuration to device',
action: 'Restore device configuration',
},
],
default: 'executeCommand',
noDataExpression: true,
},
{
displayName: 'Host',
name: 'host',
type: 'string',
default: '',
placeholder: 'e.g. 172.30.0.6',
description: 'IPv4/IPv6 address or hostname of the device',
required: true,
},
{
displayName: 'Port',
name: 'port',
type: 'number',
default: 22,
description: 'SSH port of the device',
},
// SSH Configuration
{
displayName: 'Cipher Selection',
name: 'cipherSelection',
type: 'options',
options: [
{ name: 'All Available Ciphers (Including Legacy)', value: 'all' },
{ name: 'Secure Ciphers Only', value: 'secure-only' },
{ name: 'Legacy Ciphers Only', value: 'legacy-only' },
{ name: 'AES 128-bit CTR', value: 'aes128-ctr' },
{ name: 'AES 192-bit CTR', value: 'aes192-ctr' },
{ name: 'AES 256-bit CTR', value: 'aes256-ctr' },
{ name: 'AES 128-bit GCM', value: 'aes128-gcm@openssh.com' },
{ name: 'AES 256-bit GCM', value: 'aes256-gcm@openssh.com' },
{ name: 'ChaCha20-Poly1305', value: 'chacha20-poly1305@openssh.com' },
// Legacy ciphers
{ name: 'AES 128-bit CBC (Legacy)', value: 'aes128-cbc' },
{ name: 'AES 192-bit CBC (Legacy)', value: 'aes192-cbc' },
{ name: 'AES 256-bit CBC (Legacy)', value: 'aes256-cbc' },
{ name: '3DES CBC (Legacy)', value: '3des-cbc' },
],
default: 'all',
description: 'Select specific cipher or cipher group for the SSH connection',
},
// Command Execution
{
displayName: 'Command',
name: 'command',
type: 'string',
displayOptions: {
show: {
operation: ['executeCommand'],
},
},
default: '',
placeholder: 'e.g. show version',
description: 'Command to execute on the remote device',
required: true,
},
{
displayName: 'Commands',
name: 'commands',
type: 'fixedCollection',
placeholder: 'Add Command',
displayOptions: {
show: {
operation: ['executeMultipleCommands'],
},
},
typeOptions: {
multipleValues: true,
},
description: 'Commands to execute',
default: {},
options: [
{
name: 'commandsArray',
displayName: 'Commands',
values: [
{
displayName: 'Command',
name: 'command',
type: 'string',
default: '',
description: 'The command to execute',
required: true,
},
{
displayName: 'Wait Time (ms)',
name: 'waitTime',
type: 'number',
default: 1000,
description: 'Time to wait after executing this command before sending the next one',
},
],
},
],
},
// Command List Parameters (AI-friendly)
{
displayName: 'Commands Format',
name: 'commandsFormat',
type: 'options',
displayOptions: {
show: {
operation: ['executeCommandList'],
},
},
options: [
{
name: 'Text List (One Command Per Line)',
value: 'textList',
description: 'Commands separated by new lines',
},
{
name: 'JSON Array',
value: 'jsonArray',
description: 'Array of command objects in JSON format',
},
{
name: 'Simple Array',
value: 'simpleArray',
description: 'Array of command strings',
},
],
default: 'textList',
},
{
displayName: 'Commands',
name: 'commandsList',
type: 'string',
typeOptions: {
rows: 10,
},
displayOptions: {
show: {
operation: ['executeCommandList'],
commandsFormat: ['textList'],
},
},
default: '',
placeholder: 'show version\nshow interfaces\nshow ip route',
description: 'Enter commands separated by new lines',
required: true,
},
{
displayName: 'Commands JSON',
name: 'commandsJson',
type: 'json',
displayOptions: {
show: {
operation: ['executeCommandList'],
commandsFormat: ['jsonArray', 'simpleArray'],
},
},
default: '["show version", "show interfaces"]',
description: 'JSON array of commands or command objects',
required: true,
},
{
displayName: 'Default Wait Time (ms)',
name: 'defaultWaitTime',
type: 'number',
displayOptions: {
show: {
operation: ['executeCommandList'],
},
},
default: 1000,
description: 'Default time to wait between commands (milliseconds)',
},
// Config Backup Parameters
{
displayName: 'Configuration Type',
name: 'configType',
type: 'options',
displayOptions: {
show: {
operation: ['backupConfig'],
},
},
options: [
{
name: 'Running Configuration',
value: 'running',
description: 'Current active configuration',
},
{
name: 'Startup Configuration',
value: 'startup',
description: 'Configuration used at boot',
},
],
default: 'running',
description: 'Type of configuration to retrieve',
},
{
displayName: 'Return Type',
name: 'returnType',
type: 'options',
displayOptions: {
show: {
operation: ['backupConfig'],
},
},
options: [
{
name: 'Return Config as Text',
value: 'text',
description: 'Return configuration as text in the output',
},
{
name: 'Binary File',
value: 'binary',
description: 'Return configuration as a binary file',
},
],
default: 'text',
},
// Config Restore Parameters
{
displayName: 'Configuration Source',
name: 'configSource',
type: 'options',
displayOptions: {
show: {
operation: ['restoreConfig'],
},
},
options: [
{
name: 'Text Input',
value: 'text',
description: 'Directly enter configuration commands',
},
{
name: 'Binary Data',
value: 'binary',
description: 'Use binary data (from previous node)',
},
],
default: 'text',
},
{
displayName: 'Configuration Commands',
name: 'configCommands',
type: 'string',
typeOptions: {
rows: 10,
},
displayOptions: {
show: {
operation: ['restoreConfig'],
configSource: ['text'],
},
},
default: '',
placeholder: 'hostname router1\ninterface GigabitEthernet0/1\n description WAN\n ip address dhcp',
description: 'Configuration commands to apply',
required: true,
},
{
displayName: 'Binary Property',
name: 'binaryProperty',
type: 'string',
displayOptions: {
show: {
operation: ['restoreConfig'],
configSource: ['binary'],
},
},
default: 'data',
description: 'Name of the binary property containing the configuration file',
required: true,
},
{
displayName: 'Apply Method',
name: 'applyMethod',
type: 'options',
displayOptions: {
show: {
operation: ['restoreConfig'],
},
},
options: [
{
name: 'Merge (Add to existing)',
value: 'merge',
description: 'Add/update to current configuration',
},
{
name: 'Replace (Dangerous)',
value: 'replace',
description: 'Replace entire configuration',
},
],
default: 'merge',
description: 'How to apply the configuration',
},
{
displayName: 'Save After Apply',
name: 'saveAfterApply',
type: 'boolean',
displayOptions: {
show: {
operation: ['restoreConfig'],
},
},
default: true,
description: 'Save configuration after applying',
},
{
displayName: 'Advanced Security Options',
name: 'advancedSecurity',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Compatibility Level',
name: 'compatibilityLevel',
type: 'options',
options: [
{ name: 'High (Most Compatible, Less Secure)', value: 'high' },
{ name: 'Medium (Balance of Compatibility and Security)', value: 'medium' },
{ name: 'Legacy Only (For Very Old Devices)', value: 'legacy-only' },
{ name: 'Modern Only (Most Secure, Least Compatible)', value: 'modern-only' },
],
default: 'medium',
description: 'Compatibility level for key exchange and encryption algorithms',
},
{
displayName: 'Security Level',
name: 'securityLevel',
type: 'options',
options: [
{ name: 'High (Most Secure)', value: 'high' },
{ name: 'Medium (Balance)', value: 'medium' },
{ name: 'Low (Most Compatible)', value: 'low' },
],
default: 'medium',
description: 'Security level for MAC and host key algorithms',
},
{
displayName: 'Allow Legacy Algorithms',
name: 'allowLegacyAlgorithms',
type: 'boolean',
default: true,
description: 'Allow older, less secure algorithms for maximum compatibility',
},
],
},
// Connection Settings
{
displayName: 'Connection Options',
name: 'connectionOptions',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
// Basic connection options
{
displayName: 'Connection Timeout',
name: 'connTimeout',
type: 'number',
default: 10000,
description: 'Connection timeout in milliseconds',
},
{
displayName: 'Login Prompt Timeout',
name: 'promptTimeout',
type: 'number',
default: 8000,
description: 'Timeout for login prompt detection in milliseconds',
},
{
displayName: 'Username Prompt',
name: 'usernamePrompt',
type: 'string',
default: 'login:|username:|user:',
description: 'Regular expression to match username prompt',
},
{
displayName: 'Password Prompt',
name: 'passwordPrompt',
type: 'string',
default: 'password:|Password:',
description: 'Regular expression to match password prompt',
},
{
displayName: 'Command Prompt',
name: 'commandPrompt',
type: 'string',
default: '[#>$]\\s*$',
description: 'Regular expression to match command prompt',
},
{
displayName: 'Try Fallback Ciphers',
name: 'fallbackCiphers',
type: 'boolean',
default: true,
description: 'Automatically try other ciphers if the selected one fails',
},
{
displayName: 'Retry Count',
name: 'retryCount',
type: 'number',
default: 3,
description: 'Number of retries on connection failure',
},
{
displayName: 'Retry Delay',
name: 'retryDelay',
type: 'number',
default: 3000,
description: 'Delay between retries in milliseconds',
},
{
displayName: 'Verbose Logging',
name: 'verboseLogging',
type: 'boolean',
default: false,
description: 'Enable detailed logging (not recommended for production)',
},
// Advanced options
{
displayName: 'Send Initial CR',
name: 'sendInitialCR',
type: 'boolean',
default: true,
description: 'Send carriage return on connect to stimulate prompt',
},
{
displayName: 'Terminal Type',
name: 'terminalType',
type: 'options',
options: [
{ name: 'VT100', value: 'vt100' },
{ name: 'VT102', value: 'vt102' },
{ name: 'VT220', value: 'vt220' },
{ name: 'Ansi', value: 'ansi' },
{ name: 'Xterm', value: 'xterm' },
],
default: 'vt100',
description: 'Terminal type to use for the SSH connection',
},
{
displayName: 'Allow Empty Prompt',
name: 'allowEmptyPrompt',
type: 'boolean',
default: false,
description: "Allow empty prompt response (use for devices that don't send initial prompt)",
},
{
displayName: 'Debug Buffer Content',
name: 'debugBuffer',
type: 'boolean',
default: false,
description: 'Enable detailed buffer content debugging (shows received bytes in hex)',
},
{
displayName: 'Initial Command',
name: 'initialCommand',
type: 'string',
default: '',
description: 'Command to send immediately after connection (helpful for non-standard SSH devices)',
},
{
displayName: 'Enable Privilege Mode',
name: 'enablePrivilegeMode',
type: 'boolean',
default: false,
description: 'Send enable command after connecting (for Cisco and similar devices)',
},
{
displayName: 'Privilege Mode Password',
name: 'privilegeModePassword',
type: 'string',
typeOptions: {
password: true,
},
default: '',
displayOptions: {
show: {
enablePrivilegeMode: [true],
},
},
description: 'Password for the enable command (if different from login password)',
},
{
displayName: 'Line Ending Style',
name: 'lineEnding',
type: 'options',
options: [
{ name: 'CR+LF (\\r\\n)', value: '\r\n' },
{ name: 'LF only (\\n)', value: '\n' },
{ name: 'CR only (\\r)', value: '\r' },
],
default: '\r\n',
description: 'Line ending characters to use when sending commands',
},
// Device-specific options
{
displayName: 'Device Type',
name: 'deviceType',
type: 'options',
options: [
{ name: 'Generic', value: 'generic' },
{ name: 'Aruba CX Switch', value: 'aruba' },
{ name: 'Aruba OS Switch', value: 'aruba-os' },
{ name: 'Aruba Access Point', value: 'aruba-ap' },
{ name: 'Aruba Gateway', value: 'aruba-gw' },
{ name: 'Cisco IOS', value: 'cisco' },
{ name: 'Juniper', value: 'juniper' },
{ name: 'Legacy Switch (CBC Ciphers)', value: 'legacy-switch' },
],
default: 'generic',
description: 'Select device type for vendor-specific optimizations',
},
],
},
],
};
}
async execute() {
var _a, _b;
const items = this.getInputData();
const returnData = [];
// Process each item from the input
for (let i = 0; i < items.length; i++) {
try {
// Get node parameters
const operation = this.getNodeParameter('operation', i);
const host = this.getNodeParameter('host', i);
const port = this.getNodeParameter('port', i);
const cipherSelection = this.getNodeParameter('cipherSelection', i);
const connectionOptions = this.getNodeParameter('connectionOptions', i, {});
// Get credentials
const credentials = await this.getCredentials('sshPassword');
if (!credentials) {
throw new Error('No credentials provided for SSH connection');
}
const username = credentials.username;
const password = credentials.password;
// Configure ciphers based on selection
const ciphers = CipherUtils_1.CipherUtils.configureCiphers(cipherSelection);
const advancedSecurity = this.getNodeParameter('advancedSecurity', i, {});
// Set connection options
const options = {
connTimeout: connectionOptions.connTimeout || 10000,
promptTimeout: connectionOptions.promptTimeout || 8000,
usernamePromptRegex: new RegExp(connectionOptions.usernamePrompt || 'login:|username:|user:', 'i'),
passwordPromptRegex: new RegExp(connectionOptions.passwordPrompt || 'password:|Password:', 'i'),
commandPromptRegex: new RegExp(connectionOptions.commandPrompt || '\\S+[#>$]\\s*$|#\\s*$'),
fallbackCiphers: connectionOptions.fallbackCiphers !== false,
retryCount: connectionOptions.retryCount || 3,
retryDelay: connectionOptions.retryDelay || 3000,
verboseLogging: connectionOptions.verboseLogging || false,
// Add default values for new properties
sendInitialCR: (_a = connectionOptions.sendInitialCR) !== null && _a !== void 0 ? _a : true,
terminalType: connectionOptions.terminalType || 'vt100',
allowEmptyPrompt: connectionOptions.allowEmptyPrompt || false,
debugBuffer: connectionOptions.debugBuffer || false,
initialCommand: connectionOptions.initialCommand || '',
enablePrivilegeMode: connectionOptions.enablePrivilegeMode || false,
privilegeModePassword: connectionOptions.privilegeModePassword || '',
lineEnding: connectionOptions.lineEnding || '\r\n',
deviceType: connectionOptions.deviceType || 'generic',
deviceSpecific: {},
// Add stable output detection
stableOutputDetection: true,
lastDataTime: Date.now(),
// Add advanced security options
advancedSecurity: {
compatibilityLevel: advancedSecurity.compatibilityLevel || 'medium',
securityLevel: advancedSecurity.securityLevel || 'medium',
allowLegacyAlgorithms: advancedSecurity.allowLegacyAlgorithms !== false,
},
};
// For older switches, update device-specific settings
if (connectionOptions.deviceType === 'legacy-switch') {
options.commandPromptRegex = new RegExp('\\S+[#>]\\s*$');
options.terminalType = 'vt100';
options.lineEnding = '\r\n';
options.advancedSecurity = {
compatibilityLevel: 'high',
securityLevel: 'low',
allowLegacyAlgorithms: true,
};
}
// Apply device-specific settings
if (connectionOptions.deviceType === 'aruba') {
// Use a regex that handles the Aruba CX prompt pattern with or without trailing \r\n
options.commandPromptRegex = new RegExp('\\S+#\\s*(?:\\r\\n)?$');
options.terminalType = 'vt100';
options.lineEnding = '\r\n';
options.sendInitialCR = true;
options.promptTimeout = Math.max(options.promptTimeout, 15000); // At least 15 seconds
options.stableOutputDetection = true;
options.deviceSpecific = {
arubaMode: true,
// Add specific known prompts for exact matching
knownPrompts: ['ox60-mdf-c2-sw1#', 'homelab-6300#'],
};
}
else if (connectionOptions.deviceType === 'aruba-os') {
// Aruba OS Switch specific configuration
options.commandPromptRegex = new RegExp('(?:\\\\S+|)[#>]\\\\s*$');
options.terminalType = 'vt100';
options.lineEnding = '\r\n';
options.sendInitialCR = true;
options.promptTimeout = Math.max(options.promptTimeout, 20000); // Longer timeout for Aruba OS
options.stableOutputDetection = true;
options.debugBuffer = true; // Enable buffer debugging for Aruba OS by default
options.advancedSecurity = {
compatibilityLevel: 'high', // More compatible
securityLevel: 'low', // For better compatibility
allowLegacyAlgorithms: true, // Allow legacy algorithms
};
options.deviceSpecific = {
arubaOsMode: true,
// Add your specific hostname prompt pattern
knownPrompts: ['ofer-3-sw1#', '>', '#', '(config)#', '(config-if)#', '(config-vlan)#'],
// Specific handling flags
allowMultiplePromptPatterns: true,
handlePagination: true,
paginationPrompt: '--MORE--',
paginationContinue: ' ',
};
// Set the initial command to 'no pag' to disable pagination if possible
options.initialCommand = 'no pag';
}
else if (connectionOptions.deviceType === 'aruba-ap') {
// Aruba Access Point specific configuration
options.commandPromptRegex = new RegExp('\\S+#\\s*$|\\S+>\\s*$');
options.terminalType = 'vt100';
options.lineEnding = '\r\n';
options.sendInitialCR = true;
options.promptTimeout = Math.max(options.promptTimeout, 15000); // APs may be slower
options.stableOutputDetection = true;
options.debugBuffer = true; // Enable debugging for AP connections
options.advancedSecurity = {
compatibilityLevel: 'high', // Maximum compatibility for APs
securityLevel: 'low', // Lower security for better AP compatibility
allowLegacyAlgorithms: true, // APs often need legacy algorithms
};
options.deviceSpecific = {
arubaApMode: true,
// Common AP prompt patterns
knownPrompts: ['#', '>', 'ap#', 'instant#', 'User:', 'Password:', 'login:'],
// AP-specific handling
allowMultiplePromptPatterns: true,
handlePagination: true,
paginationPrompt: '--More--',
paginationContinue: ' ',
// AP connection characteristics
slowConnectionMode: true,
requiresLegacyCiphers: true,
// Authentication characteristics
authRetryDelay: 2000,
supportKeyboardInteractive: true,
};
// Update authentication prompt patterns for APs
options.usernamePromptRegex = new RegExp('login:|username:|user:|User:', 'i');
options.passwordPromptRegex = new RegExp('password:|Password:', 'i');
// Aruba APs don't support pagination disable commands - no initial command needed
}
else if (connectionOptions.deviceType === 'aruba-gw') {
// Aruba Gateway/Mobility Controller specific configuration
options.commandPromptRegex = new RegExp('\\([^)]*\\)\\s*[#>]\\s*$|\\S+[#>]\\s*$');
options.terminalType = 'vt100';
options.lineEnding = '\r\n';
options.sendInitialCR = true;
options.promptTimeout = Math.max(options.promptTimeout, 18000); // Gateway may need more time
options.stableOutputDetection = true;
options.debugBuffer = true; // Enable debugging for gateway connections too
options.advancedSecurity = {
compatibilityLevel: 'high', // Higher compatibility for gateways
securityLevel: 'low', // Lower security for better compatibility
allowLegacyAlgorithms: true, // Some legacy algorithm support
};
options.deviceSpecific = {
arubaGwMode: true,
// Common gateway prompt patterns
knownPrompts: [
'(host) #',
'(config) #',
'#',
'>',
'(host) >',
'(config) >',
'User:',
'Password:',
'login:',
],
// Gateway-specific handling
allowMultiplePromptPatterns: true,
handlePagination: true,
paginationPrompt: '--More--',
paginationContinue: ' ',
// Gateway connection characteristics
supportFullCli: true,
configModeSupport: true,
// Authentication characteristics
authRetryDelay: 1500,
supportKeyboardInteractive: true,
};
// Update authentication prompt patterns for Gateways
options.usernamePromptRegex = new RegExp('login:|username:|user:|User:', 'i');
options.passwordPromptRegex = new RegExp('password:|Password:', 'i');
// Disable pagination for gateways
options.initialCommand = 'no pag';
}
else if (connectionOptions.deviceType === 'cisco') {
options.commandPromptRegex = new RegExp('\\S+[#>]\\s*$');
options.promptTimeout = Math.max(options.promptTimeout, 10000);
options.deviceSpecific = { ciscoMode: true };
}
else if (connectionOptions.deviceType === 'juniper') {
options.commandPromptRegex = new RegExp('\\S+@\\S+[>%]\\s*$');
options.deviceSpecific = { juniperMode: true };
}
// Execute commands based on the operation
if (operation === 'executeCommand') {
const command = this.getNodeParameter('command', i);
const result = await SshCommandExecutor_1.SshCommandExecutor.executeSshCommand(host, port, username, password, command, ciphers, options);
returnData.push({
json: {
success: true,
host,
...result,
},
});
}
else if (operation === 'executeMultipleCommands') {
const commandsCollection = this.getNodeParameter('commands.commandsArray', i, []);
// Extract commands and wait times
const commands = commandsCollection.map((item) => ({
command: item.command,
waitTime: item.waitTime || 1000,
}));
const result = await SshCommandExecutor_1.SshCommandExecutor.executeMultipleSshCommands(host, port, username, password, commands, ciphers, options);
returnData.push({
json: {
success: true,
host,
...result,
},
});
}
else if (operation === 'executeCommandList') {
const commandsFormat = this.getNodeParameter('commandsFormat', i);
const defaultWaitTime = this.getNodeParameter('defaultWaitTime', i, 1000);
let commandList = [];
// Parse commands based on format - improved parsing logic
if (commandsFormat === 'textList') {
const textCommands = this.getNodeParameter('commandsList', i);
// Split by newlines and also by semicolons for better compatibility
const rawCommands = textCommands
.split('\n')
.flatMap((line) => line.split(';'))
.map((cmd) => cmd.trim())
.filter((cmd) => cmd.length > 0);
commandList = rawCommands.map((cmd) => ({
command: cmd,
waitTime: defaultWaitTime,
}));
// Log parsed commands for debugging
if (options.verboseLogging) {
console.log(`[SSH Node] Parsed ${commandList.length} commands from text input`);
commandList.forEach((cmd, index) => {
console.log(`[SSH Node] Command ${index + 1}: "${cmd.command}"`);
});
}
}
else {
// Handle JSON formats with better error checking
const jsonInput = this.getNodeParameter('commandsJson', i);
let parsedJson;
try {
parsedJson = JSON.parse(jsonInput);
}
catch (error) {
throw new Error(`Invalid JSON format: ${error.message}`);
}
if (commandsFormat === 'simpleArray') {
// Input is a simple array of strings
if (!Array.isArray(parsedJson)) {
throw new Error('Expected an array of command strings');
}
commandList = parsedJson.map((cmd) => {
const cmdStr = String(cmd).trim();
return {
command: cmdStr,
waitTime: defaultWaitTime,
};
});
}
else {
// Input is array of objects
if (!Array.isArray(parsedJson)) {
throw new Error('Expected an array of command objects');
}
commandList = parsedJson.map((item) => {
if (typeof item === 'string') {
return { command: item.trim(), waitTime: defaultWaitTime };
}
else if (typeof item === 'object' && item !== null) {
if (!item.command) {
throw new Error('Each command object must contain a "command" property');
}
return {
command: String(item.command).trim(),
waitTime: item.waitTime !== undefined
? parseInt(String(item.waitTime), 10)
: defaultWaitTime,
};
}
else {
throw new Error('Invalid command format in JSON array');
}
});
}
// Log parsed commands for debugging
if (options.verboseLogging) {
console.log(`[SSH Node] Parsed ${commandList.length} commands from JSON input`);
commandList.forEach((cmd, index) => {
console.log(`[SSH Node] Command ${index + 1}: "${cmd.command}" (wait: ${cmd.waitTime}ms)`);
});
}
}
// Validate commands
if (commandList.length === 0) {
throw new Error('No valid commands provided');
}
// Adjust wait time based on device type
if (options.deviceType === 'aruba') {
commandList = commandList.map((cmd) => ({
...cmd,
waitTime: Math.max(cmd.waitTime, 1500), // At least 1.5 second wait for Aruba CX
}));
}
else if (options.deviceType === 'aruba-os') {
commandList = commandList.map((cmd) => ({
...cmd,
waitTime: Math.max(cmd.waitTime, 2000), // At least 2 second wait for Aruba OS
}));
// For Aruba OS, add pagination handling command if not already included
if (!commandList.some((cmd) => cmd.command.includes('no pag'))) {
commandList.unshift({
command: 'no pag',
waitTime: 1000,
});
}
// For Aruba OS, add pagination handling command if not already included
if (!commandList.some((cmd) => cmd.command.includes('no pag'))) {
commandList.unshift({
command: 'no pag',
waitTime: 1000,
});
}
}
else if (options.deviceType === 'aruba-ap') {
commandList = commandList.map((cmd) => ({
...cmd,
waitTime: Math.max(cmd.waitTime, 1000), // At least 1 second wait for Aruba APs
}));
// Aruba APs don't support pagination disable commands like 'no pag'
// Remove any pagination commands that might have been added
commandList = commandList.filter((cmd) => !cmd.command.includes('no pag'));
}
else if (options.deviceType === 'aruba-gw') {
commandList = commandList.map((cmd) => ({
...cmd,
waitTime: Math.max(cmd.waitTime, 2000), // At least 2 second wait for Aruba Gateways
}));
// For Aruba Gateways, add pagination handling command if not already included
if (!commandList.some((cmd) => cmd.command.includes('no pag'))) {
commandList.unshift({
command: 'no pag',
waitTime: 1000,
});
}
}
// Execute commands with enhanced timeout
const execOptions = {
...options,
promptTimeout: Math.max(options.promptTimeout, 15000), // Ensure sufficient timeout
};
if (options.verboseLogging) {
console.log(`[SSH Node] Executing ${commandList.length} commands on ${host}`);
}
const result = await SshCommandExecutor_1.SshCommandExecutor.executeMultipleSshCommands(host, port, username, password, commandList, ciphers, execOptions);
if (options.verboseLogging && result.results) {
console.log(`[SSH Node] Command execution complete. Got ${result.results.length} results`);
result.results.forEach((cmdResult, index) => {
console.log(`[SSH Node] Result ${index + 1}: Command "${cmdResult.command}" produced ${cmdResult.output.length} bytes of output`);
});
}
returnData.push({
json: {
success: true,
host,
commandCount: commandList.length,
results: result.results,
cipher: result.cipher,
},
});
}
else if (operation === 'backupConfig') {
const configType = this.getNodeParameter('configType', i);
const returnType = this.getNodeParameter('returnType', i);
// Adjust commands for Aruba OS switches if needed
if (options.deviceType === 'aruba-os') {
// Aruba OS uses different commands for c