Class: TrainPlugins::Juniper::Connection

Inherits:
Train::Plugins::Transport::BaseConnection
  • Object
show all
Includes:
BastionProxy, CommandExecutor, Environment, ErrorHandling, Logging, Platform, SSHSession, Validation
Defined in:
lib/train-juniper/connection.rb

Overview

Main connection class for Juniper devices

Constant Summary collapse

CommandResult =

Alias for Train CommandResult for backward compatibility

Train::Extras::CommandResult
ENV_CONFIG =

Configuration mapping for environment variables

{
  host: { env: 'JUNIPER_HOST' },
  user: { env: 'JUNIPER_USER' },
  password: { env: 'JUNIPER_PASSWORD' },
  port: { env: 'JUNIPER_PORT', type: :int, default: Constants::DEFAULT_SSH_PORT },
  timeout: { env: 'JUNIPER_TIMEOUT', type: :int, default: 30 },
  bastion_host: { env: 'JUNIPER_BASTION_HOST' },
  bastion_user: { env: 'JUNIPER_BASTION_USER' },
  bastion_port: { env: 'JUNIPER_BASTION_PORT', type: :int, default: Constants::DEFAULT_SSH_PORT },
  bastion_password: { env: 'JUNIPER_BASTION_PASSWORD' },
  proxy_command: { env: 'JUNIPER_PROXY_COMMAND' }
}.freeze
SENSITIVE_OPTIONS =

List of sensitive option keys to redact in logs

%i[password bastion_password key_files proxy_command].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Connection

Initialize a new Juniper connection

Parameters:

  • options (Hash)

    Connection options

Options Hash (options):

  • :host (String)

    The hostname or IP address of the Juniper device

  • :user (String)

    The username for authentication

  • :password (String)

    The password for authentication (optional if using key_files)

  • :port (Integer)

    The SSH port (default: 22)

  • :timeout (Integer)

    Connection timeout in seconds (default: 30)

  • :bastion_host (String)

    Jump/bastion host for connection

  • :proxy_command (String)

    SSH proxy command

  • :logger (Logger)

    Custom logger instance

  • :mock (Boolean)

    Enable mock mode for testing



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/train-juniper/connection.rb', line 83

def initialize(options)
  # Configure SSH connection options for Juniper devices
  # Support environment variables for authentication (following train-vsphere pattern)
  @options = options.dup

  # Apply environment variable configuration using DRY approach
  ENV_CONFIG.each do |key, config|
    # Skip if option already has a value from command line
    next if @options[key]

    # Get value from environment
    env_val = config[:type] == :int ? env_int(config[:env]) : env_value(config[:env])

    # Only apply env value if it exists, otherwise use default (but not for nil CLI values)
    if env_val
      @options[key] = env_val
    elsif !@options.key?(key) && config[:default]
      @options[key] = config[:default]
    end
  end

  @options[:keepalive] = true
  @options[:keepalive_interval] = Constants::SSH_KEEPALIVE_INTERVAL

  # Setup logger
  @logger = @options[:logger] || Logger.new(STDOUT, level: Constants::DEFAULT_LOG_LEVEL)

  # JunOS CLI prompt patterns
  @cli_prompt = /[%>$#]\s*$/
  @config_prompt = /[%#]\s*$/

  # Log connection info safely
  log_connection_info

  # Validate all connection options
  validate_connection_options!

  super(@options)

  # Establish SSH connection to Juniper device (unless in mock mode or skip_connect)
  if @options[:mock]
    log_mock_mode
  elsif !@options[:skip_connect]
    @logger.debug('Attempting to connect to Juniper device...')
    connect
  end
end

Instance Attribute Details

#ssh_sessionObject (readonly)

Returns the value of attribute ssh_session.



56
57
58
# File 'lib/train-juniper/connection.rb', line 56

def ssh_session
  @ssh_session
end

Instance Method Details

#bastion_auth_error?(error) ⇒ Boolean Originally defined in module ErrorHandling

Check if error is bastion authentication related

Parameters:

  • error (StandardError)

    The error to check

Returns:

  • (Boolean)

    true if error is bastion-related

#bastion_error_message(error) ⇒ String Originally defined in module ErrorHandling

Build helpful bastion error message

Parameters:

  • error (StandardError)

    The original error

Returns:

  • (String)

    Detailed error message with troubleshooting steps

Build plink.exe proxy command for Windows bastion authentication

Parameters:

  • bastion_host (String)

    Bastion hostname

  • user (String)

    Username for bastion

  • port (Integer)

    Port for bastion

  • password (String)

    Password for bastion

Returns:

  • (String)

    Complete plink command string

#build_proxy_jump_string(bastion_user, bastion_port) ⇒ String (private) Originally defined in module BastionProxy

Build proxy jump string from bastion options

Parameters:

  • bastion_user (String)

    Username for bastion

  • bastion_port (Integer)

    Port for bastion

Returns:

  • (String)

    Proxy jump string

#build_ssh_optionsObject (private) Originally defined in module SSHSession

Build SSH connection options from @options

#clean_output(output, cmd) ⇒ Object (private) Originally defined in module CommandExecutor

Clean command output

#configure_bastion_proxy(ssh_options) ⇒ Object Originally defined in module BastionProxy

Configure bastion proxy for SSH connection

Parameters:

  • ssh_options (Hash)

    SSH options to modify

Configure plink.exe proxy for Windows password authentication

Parameters:

  • ssh_options (Hash)

    SSH options to modify

  • bastion_user (String)

    Username for bastion

  • bastion_port (Integer)

    Port for bastion

  • bastion_password (String)

    Password for bastion

#configure_standard_proxy(ssh_options, bastion_user, bastion_port) ⇒ Object (private) Originally defined in module BastionProxy

Configure standard SSH proxy using Net::SSH::Proxy::Jump

Parameters:

  • ssh_options (Hash)

    SSH options to modify

  • bastion_user (String)

    Username for bastion

  • bastion_port (Integer)

    Port for bastion

#connectObject Originally defined in module SSHSession

Establish SSH connection to Juniper device

#connected?Boolean Originally defined in module SSHSession

Check if SSH connection is active

Returns:

  • (Boolean)

    true if connected, false otherwise

#create_ssh_askpass_script(password) ⇒ String Originally defined in module SshAskpass

Create temporary SSH_ASKPASS script for automated password authentication

Parameters:

  • password (String)

    The password to use

Returns:

  • (String)

    Path to the created script

#create_unix_askpass_script(password) ⇒ String (private) Originally defined in module SshAskpass

Create Unix shell script for SSH_ASKPASS

Parameters:

  • password (String)

    The password to use

Returns:

  • (String)

    Path to the created script

#create_windows_askpass_script(password) ⇒ String (private) Originally defined in module SshAskpass

Create Windows PowerShell script for SSH_ASKPASS

Parameters:

  • password (String)

    The password to use

Returns:

  • (String)

    Path to the wrapper batch file

#detect_attribute(attribute_name, command = 'show version') {|String| ... } ⇒ String? (private) Originally defined in module Platform

Generic detection helper for version and architecture

Parameters:

  • attribute_name (String)

    Name of the attribute to detect

  • command (String) (defaults to: 'show version')

    Command to run (default: 'show version')

Yields:

  • (String)

    Block that extracts the attribute from command output

Returns:

  • (String, nil)

    Detected attribute value or nil

#detect_junos_architectureString? (private) Originally defined in module Platform

Note:

This runs safely after the connection is established

Detect JunOS architecture from device output

Returns:

  • (String, nil)

    Architecture string or nil if not detected

#detect_junos_serialString? (private) Originally defined in module Platform

Note:

This runs safely after the connection is established

Detect JunOS serial number from device output

Returns:

  • (String, nil)

    Serial number string or nil if not detected

#detect_junos_versionString? (private) Originally defined in module Platform

Note:

This runs safely after the connection is established

Detect JunOS version from device output

Returns:

  • (String, nil)

    JunOS version string or nil if not detected

#download(remotes, local) ⇒ Object

Note:

Use run_command() to retrieve configuration data instead

Download files from Juniper device (not supported)

Parameters:

  • remotes (String, Array<String>)

    Remote file path(s)

  • local (String)

    Local destination path

Raises:

  • (NotImplementedError)

    Always raises as downloads are not supported



171
172
173
# File 'lib/train-juniper/connection.rb', line 171

def download(remotes, local)
  raise NotImplementedError, Constants::DOWNLOAD_NOT_SUPPORTED
end

#env_int(key) ⇒ Integer? Originally defined in module Environment

Helper method to get environment variable as integer Returns nil if env var is not set, empty, or not a valid integer

Parameters:

  • key (String)

    The environment variable name

Returns:

  • (Integer, nil)

    The integer value or nil if not valid

#env_value(key) ⇒ String? Originally defined in module Environment

Helper method to safely get environment variable value Returns nil if env var is not set or is empty string

Parameters:

  • key (String)

    The environment variable name

Returns:

  • (String, nil)

    The value or nil if not set/empty

#error_result(message) ⇒ Train::Extras::CommandResult (private) Originally defined in module CommandExecutor

Factory method for error command results

Parameters:

  • message (String)

    Error message

Returns:

  • (Train::Extras::CommandResult)

    Error result object

#extract_architecture_from_xml(output) ⇒ String? (private) Originally defined in module Platform

Extract architecture string from JunOS show version XML output

Parameters:

  • output (String)

    XML output from 'show version | display xml' command

Returns:

  • (String, nil)

    Architecture string (x86_64, arm64, etc.) or nil

#extract_from_xml(output, xpath_patterns, command_desc) {|REXML::Element| ... } ⇒ String? (private) Originally defined in module Platform

Generic XML extraction helper

Parameters:

  • output (String)

    XML output from command

  • xpath_patterns (Array<String>)

    XPath patterns to try in order

  • command_desc (String)

    Description of command for error messages

Yields:

  • (REXML::Element)

    Optional block to process the found element

Returns:

  • (String, nil)

    Extracted text or result of block processing

#extract_serial_from_xml(output) ⇒ String? (private) Originally defined in module Platform

Extract serial number from JunOS chassis hardware XML output

Parameters:

  • output (String)

    XML output from 'show chassis hardware | display xml' command

Returns:

  • (String, nil)

    Serial number string or nil

#extract_version_from_xml(output) ⇒ String? (private) Originally defined in module Platform

Extract version string from JunOS show version XML output

Parameters:

  • output (String)

    XML output from 'show version | display xml' command

Returns:

  • (String, nil)

    Extracted version string or nil

#file_via_connection(path) ⇒ JuniperFile

Access Juniper configuration and operational data as pseudo-files

Examples:

Access interface configuration

file = connection.file('/config/interfaces')
puts file.content

Access operational data

file = connection.file('/operational/interfaces')
puts file.content

Parameters:

  • path (String)

    The pseudo-file path to access

Returns:

  • (JuniperFile)

    A file-like object for accessing Juniper data



151
152
153
154
155
# File 'lib/train-juniper/connection.rb', line 151

def file_via_connection(path)
  # For Juniper devices, "files" are typically configuration sections
  # or operational command outputs rather than traditional filesystem paths
  JuniperFile.new(self, path)
end

#format_junos_result(output, cmd) ⇒ Object (private) Originally defined in module CommandExecutor

Format JunOS command results

#generate_bastion_proxy_command(bastion_user, bastion_port) ⇒ String (private) Originally defined in module BastionProxy

Generate SSH proxy command for bastion host using ProxyJump (-J)

Parameters:

  • bastion_user (String)

    Username for bastion

  • bastion_port (Integer)

    Port for bastion

Returns:

  • (String)

    SSH command string

#handle_connection_error(error) ⇒ Object Originally defined in module ErrorHandling

Handle connection errors with helpful messages

Parameters:

  • error (StandardError)

    The error that occurred

Raises:

  • (Train::TransportError)

    Always raises with formatted message

#healthy?Boolean

Check connection health

Examples:

if connection.healthy?
  puts "Connection is healthy"
end

Returns:

  • (Boolean)

    true if connection is healthy, false otherwise



181
182
183
184
185
186
187
188
# File 'lib/train-juniper/connection.rb', line 181

def healthy?
  return false unless connected?

  result = run_command_via_connection('show version')
  result.exit_status.zero?
rescue StandardError
  false
end

#inspectString

Secure inspect method that uses to_s

Returns:

  • (String)

    Secure string representation



138
139
140
# File 'lib/train-juniper/connection.rb', line 138

def inspect
  to_s
end

#junos_error?(output) ⇒ Boolean Originally defined in module ErrorHandling

Check for JunOS error patterns

Parameters:

  • output (String)

    Command output to check

Returns:

  • (Boolean)

    true if output contains error patterns

#log_bastion_connection(bastion_host) ⇒ Object Originally defined in module Logging

Log bastion connection attempt

Parameters:

  • bastion_host (String)

    The bastion host

#log_command(cmd) ⇒ Object Originally defined in module Logging

Log a command execution attempt

Parameters:

  • cmd (String)

    The command being executed

#log_connection_attempt(target, port = nil) ⇒ Object Originally defined in module Logging

Log a connection attempt

Parameters:

  • target (String)

    The host/target being connected to

  • port (Integer) (defaults to: nil)

    The port number

#log_connection_infoObject (private)

Log connection info without exposing sensitive data



229
230
231
232
233
# File 'lib/train-juniper/connection.rb', line 229

def log_connection_info
  safe_options = @options.except(*SENSITIVE_OPTIONS)
  @logger.debug("Juniper connection initialized with options: #{safe_options.inspect}")
  @logger.debug("Environment: JUNIPER_BASTION_USER=#{env_value('JUNIPER_BASTION_USER')} -> bastion_user=#{@options[:bastion_user]}")
end

#log_connection_success(target) ⇒ Object Originally defined in module Logging

Log successful connection

Parameters:

  • target (String)

    The host that was connected to

#log_error(error, context = nil) ⇒ Object Originally defined in module Logging

Log an error with consistent formatting

Parameters:

  • error (Exception, String)

    The error to log

  • context (String) (defaults to: nil)

    Additional context for the error

#log_mock_modeObject Originally defined in module Logging

Log mock mode activation

#log_platform_detection(platform_name, version) ⇒ Object Originally defined in module Logging

Log platform detection results

Parameters:

  • platform_name (String)

    Detected platform name

  • version (String)

    Detected version

#log_ssh_options(options) ⇒ Object Originally defined in module Logging

Log SSH session details (redacting sensitive info)

Parameters:

  • options (Hash)

    SSH options hash

#mock?Boolean Originally defined in module SSHSession

Check if running in mock mode

Returns:

  • (Boolean)

    true if in mock mode

#mock_command_result(cmd) ⇒ Object (private) Originally defined in module CommandExecutor

Mock command execution for testing

#platformTrain::Platform Originally defined in module Platform

Note:

Uses force_platform! to bypass Train's automatic detection

Platform detection for Juniper network devices

Examples:

platform = connection.platform
platform.name     #=> "juniper"
platform.release  #=> "12.1X47-D15.4"
platform.arch     #=> "x86_64"

Returns:

  • (Train::Platform)

    Platform object with JunOS details

Check if plink.exe is available on Windows

Returns:

  • (Boolean)

    true if plink.exe is found in PATH

#run_command_via_connection(cmd) ⇒ CommandResult Originally defined in module CommandExecutor

Execute commands on Juniper device via SSH

Examples:

result = connection.run_command('show version')
puts result.stdout

Parameters:

  • cmd (String)

    The JunOS command to execute

Returns:

  • (CommandResult)

    Result object with stdout, stderr, and exit status

Raises:

  • (Train::ClientError)

    If command contains dangerous characters

#sanitize_command(cmd) ⇒ Object (private) Originally defined in module CommandExecutor

Sanitize command to prevent injection attacks

#setup_bastion_password_authObject Originally defined in module SshAskpass

Set up SSH_ASKPASS for bastion password authentication

#success_result(output, cmd = nil) ⇒ Train::Extras::CommandResult (private) Originally defined in module CommandExecutor

Factory method for successful command results

Parameters:

  • output (String)

    Command output

  • cmd (String, nil) (defaults to: nil)

    Original command for cleaning output

Returns:

  • (Train::Extras::CommandResult)

    Successful result object

#test_and_configure_sessionObject Originally defined in module SSHSession

Test connection and configure JunOS session

#to_sObject

Secure string representation (never expose credentials)



132
133
134
# File 'lib/train-juniper/connection.rb', line 132

def to_s
  "#<#{self.class.name}:0x#{object_id.to_s(16)} @host=#{@options[:host]} @user=#{@options[:user]}>"
end

#unique_identifierString

Note:

Tries to get Juniper device serial number, falls back to hostname

Optional method for better UUID generation using device-specific identifiers

Returns:

  • (String)

    Unique identifier for this device/connection



213
214
215
216
217
218
219
220
# File 'lib/train-juniper/connection.rb', line 213

def unique_identifier
  # Don't attempt device detection in mock mode
  return @options[:host] if @options[:mock]

  # Use the platform module's serial detection which follows DRY principle
  serial = detect_junos_serial
  serial || @options[:host]
end

#upload(locals, remote) ⇒ Object

Note:

Network devices use command-based configuration instead of file uploads

Upload files to Juniper device (not supported)

Parameters:

  • locals (String, Array<String>)

    Local file path(s)

  • remote (String)

    Remote destination path

Raises:

  • (NotImplementedError)

    Always raises as uploads are not supported



162
163
164
# File 'lib/train-juniper/connection.rb', line 162

def upload(locals, remote)
  raise NotImplementedError, Constants::UPLOAD_NOT_SUPPORTED
end

#uriString

Required by Train framework for node identification

Examples:

Direct connection

"juniper://admin@device.example.com:22"

Bastion connection

"juniper://admin@device.example.com:22?via=jumpuser@bastion.example.com:2222"

Returns:

  • (String)

    URI that uniquely identifies this connection



196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/train-juniper/connection.rb', line 196

def uri
  base_uri = "juniper://#{@options[:user]}@#{@options[:host]}:#{@options[:port]}"

  # Include bastion information if connecting through a jump host
  if @options[:bastion_host]
    bastion_user = @options[:bastion_user] || @options[:user]
    bastion_port = @options[:bastion_port] || 22
    bastion_info = "via=#{bastion_user}@#{@options[:bastion_host]}:#{bastion_port}"
    "#{base_uri}?#{bastion_info}"
  else
    base_uri
  end
end

#validate_bastion_port!Object Originally defined in module Validation

Validate bastion port is in valid range

#validate_connection_options!Object Originally defined in module Validation

Validate all connection options

#validate_option_types!Object Originally defined in module Validation

Validate option types and ranges

#validate_port!Object Originally defined in module Validation

Validate port is in valid range

#validate_port_value!(port_key, port_name = nil) ⇒ Object (private) Originally defined in module Validation

DRY method for validating port values

Parameters:

  • port_key (Symbol)

    The options key containing the port value

  • port_name (String) (defaults to: nil)

    The name to use in error messages (defaults to port_key)

Raises:

  • (Train::ClientError)

#validate_proxy_options!Object (private) Originally defined in module Validation

Validate proxy configuration options (Train standard)

#validate_required_options!Object Originally defined in module Validation

Validate required options are present

Raises:

  • (Train::ClientError)

#validate_timeout!Object Originally defined in module Validation

Validate timeout is positive number

Raises:

  • (Train::ClientError)