train-juniper 0.7.0 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -0
- data/CONTRIBUTING.md +1 -1
- data/README.md +49 -16
- data/Rakefile +2 -2
- data/lib/train-juniper/connection/bastion_proxy.rb +112 -0
- data/lib/train-juniper/connection/command_executor.rb +112 -0
- data/lib/train-juniper/connection/error_handling.rb +71 -0
- data/lib/train-juniper/connection/ssh_session.rb +106 -0
- data/lib/train-juniper/connection/validation.rb +63 -0
- data/lib/train-juniper/connection.rb +40 -429
- data/lib/train-juniper/constants.rb +40 -0
- data/lib/train-juniper/file_abstraction/juniper_file.rb +69 -0
- data/lib/train-juniper/helpers/environment.rb +30 -0
- data/lib/train-juniper/helpers/logging.rb +77 -0
- data/lib/train-juniper/helpers/mock_responses.rb +57 -0
- data/lib/train-juniper/platform.rb +3 -0
- data/lib/train-juniper/version.rb +1 -1
- data/train-juniper.gemspec +2 -2
- metadata +13 -3
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'train-juniper/constants'
|
4
|
+
|
5
|
+
module TrainPlugins
|
6
|
+
module Juniper
|
7
|
+
# File abstraction for Juniper configuration and operational data
|
8
|
+
class JuniperFile
|
9
|
+
# Initialize a new JuniperFile
|
10
|
+
# @param connection [Connection] The Juniper connection instance
|
11
|
+
# @param path [String] The virtual file path
|
12
|
+
def initialize(connection, path)
|
13
|
+
@connection = connection
|
14
|
+
@path = path
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get the content of the virtual file
|
18
|
+
# @return [String] The command output based on the path
|
19
|
+
# @example
|
20
|
+
# file = connection.file('/config/interfaces')
|
21
|
+
# file.content # Returns output of 'show configuration interfaces'
|
22
|
+
def content
|
23
|
+
# For Juniper devices, translate file paths to appropriate commands
|
24
|
+
case @path
|
25
|
+
when Constants::CONFIG_PATH_PATTERN
|
26
|
+
# Configuration sections: /config/interfaces -> show configuration interfaces
|
27
|
+
section = ::Regexp.last_match(1)
|
28
|
+
result = @connection.run_command("show configuration #{section}")
|
29
|
+
result.stdout
|
30
|
+
when Constants::OPERATIONAL_PATH_PATTERN
|
31
|
+
# Operational data: /operational/interfaces -> show interfaces
|
32
|
+
section = ::Regexp.last_match(1)
|
33
|
+
result = @connection.run_command("show #{section}")
|
34
|
+
result.stdout
|
35
|
+
else
|
36
|
+
# Default to treating path as a show command
|
37
|
+
result = @connection.run_command("show #{@path}")
|
38
|
+
result.stdout
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Check if the file exists (has content)
|
43
|
+
# @return [Boolean] true if the file has content, false otherwise
|
44
|
+
def exist?
|
45
|
+
!content.empty?
|
46
|
+
rescue StandardError
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return string representation of file path
|
51
|
+
# @return [String] the file path
|
52
|
+
def to_s
|
53
|
+
@path
|
54
|
+
end
|
55
|
+
|
56
|
+
# File upload not supported for network devices
|
57
|
+
# @raise [NotImplementedError] always raises as upload is not supported
|
58
|
+
def upload(_content)
|
59
|
+
raise NotImplementedError, Constants::UPLOAD_NOT_SUPPORTED
|
60
|
+
end
|
61
|
+
|
62
|
+
# File download not supported for network devices
|
63
|
+
# @raise [NotImplementedError] always raises as download is not supported
|
64
|
+
def download(_local_path)
|
65
|
+
raise NotImplementedError, Constants::DOWNLOAD_NOT_SUPPORTED
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TrainPlugins
|
4
|
+
module Juniper
|
5
|
+
# Helper methods for safely handling environment variables
|
6
|
+
module Environment
|
7
|
+
# Helper method to safely get environment variable value
|
8
|
+
# Returns nil if env var is not set or is empty string
|
9
|
+
# @param key [String] The environment variable name
|
10
|
+
# @return [String, nil] The value or nil if not set/empty
|
11
|
+
def env_value(key)
|
12
|
+
value = ENV.fetch(key, nil)
|
13
|
+
return nil if value.nil? || value.empty?
|
14
|
+
|
15
|
+
value
|
16
|
+
end
|
17
|
+
|
18
|
+
# Helper method to get environment variable as integer
|
19
|
+
# Returns nil if env var is not set, empty, or not a valid integer
|
20
|
+
# @param key [String] The environment variable name
|
21
|
+
# @return [Integer, nil] The integer value or nil if not valid
|
22
|
+
def env_int(key)
|
23
|
+
value = env_value(key)
|
24
|
+
return nil unless value
|
25
|
+
|
26
|
+
value.to_i
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TrainPlugins
|
4
|
+
module Juniper
|
5
|
+
# Provides consistent logging patterns across the plugin
|
6
|
+
module Logging
|
7
|
+
# Log a command execution attempt
|
8
|
+
# @param cmd [String] The command being executed
|
9
|
+
def log_command(cmd)
|
10
|
+
@logger.debug("Executing command: #{cmd}")
|
11
|
+
end
|
12
|
+
|
13
|
+
# Log a connection attempt
|
14
|
+
# @param target [String] The host/target being connected to
|
15
|
+
# @param port [Integer] The port number
|
16
|
+
def log_connection_attempt(target, port = nil)
|
17
|
+
if port
|
18
|
+
@logger.debug("Attempting connection to #{target}:#{port}")
|
19
|
+
else
|
20
|
+
@logger.debug("Attempting connection to #{target}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Log an error with consistent formatting
|
25
|
+
# @param error [Exception, String] The error to log
|
26
|
+
# @param context [String] Additional context for the error
|
27
|
+
def log_error(error, context = nil)
|
28
|
+
message = if error.is_a?(Exception)
|
29
|
+
"#{error.class}: #{error.message}"
|
30
|
+
else
|
31
|
+
error.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
if context
|
35
|
+
@logger.error("#{context}: #{message}")
|
36
|
+
else
|
37
|
+
@logger.error(message)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Log successful connection
|
42
|
+
# @param target [String] The host that was connected to
|
43
|
+
def log_connection_success(target)
|
44
|
+
@logger.info("Successfully connected to #{target}")
|
45
|
+
end
|
46
|
+
|
47
|
+
# Log SSH session details (redacting sensitive info)
|
48
|
+
# @param options [Hash] SSH options hash
|
49
|
+
def log_ssh_options(options)
|
50
|
+
safe_options = options.dup
|
51
|
+
safe_options[:password] = '[REDACTED]' if safe_options[:password]
|
52
|
+
safe_options[:passphrase] = '[REDACTED]' if safe_options[:passphrase]
|
53
|
+
safe_options[:keys] = safe_options[:keys]&.map { |k| File.basename(k) } if safe_options[:keys]
|
54
|
+
|
55
|
+
@logger.debug("SSH options: #{safe_options.inspect}")
|
56
|
+
end
|
57
|
+
|
58
|
+
# Log platform detection results
|
59
|
+
# @param platform_name [String] Detected platform name
|
60
|
+
# @param version [String] Detected version
|
61
|
+
def log_platform_detection(platform_name, version)
|
62
|
+
@logger.info("Platform detected: #{platform_name} #{version}")
|
63
|
+
end
|
64
|
+
|
65
|
+
# Log bastion connection attempt
|
66
|
+
# @param bastion_host [String] The bastion host
|
67
|
+
def log_bastion_connection(bastion_host)
|
68
|
+
@logger.debug("Connecting through bastion host: #{bastion_host}")
|
69
|
+
end
|
70
|
+
|
71
|
+
# Log mock mode activation
|
72
|
+
def log_mock_mode
|
73
|
+
@logger.info('Running in mock mode - no real device connection')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Mock responses for Juniper device commands
|
4
|
+
# This module contains all mock data used in testing and mock mode
|
5
|
+
module TrainPlugins
|
6
|
+
module Juniper
|
7
|
+
# Mock responses for common JunOS commands
|
8
|
+
# Used when running in mock mode for testing without real devices
|
9
|
+
module MockResponses
|
10
|
+
# Configuration of mock responses
|
11
|
+
# Maps command patterns to response methods or strings
|
12
|
+
RESPONSES = {
|
13
|
+
'show version' => :mock_show_version_output,
|
14
|
+
'show chassis hardware' => :mock_chassis_output,
|
15
|
+
'show configuration' => "interfaces {\n ge-0/0/0 {\n unit 0;\n }\n}",
|
16
|
+
'show route' => "inet.0: 5 destinations, 5 routes\n0.0.0.0/0 *[Static/5] 00:00:01\n",
|
17
|
+
'show system information' => "Hardware: SRX240H2\nOS: JUNOS 12.1X47-D15.4\n",
|
18
|
+
'show interfaces' => "Physical interface: ge-0/0/0, Enabled, Physical link is Up\n"
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
# Mock JunOS version output for testing
|
22
|
+
# @return [String] mock output for 'show version' command
|
23
|
+
def self.mock_show_version_output
|
24
|
+
<<~OUTPUT
|
25
|
+
Hostname: lab-srx
|
26
|
+
Model: SRX240H2
|
27
|
+
Junos: 12.1X47-D15.4
|
28
|
+
JUNOS Software Release [12.1X47-D15.4]
|
29
|
+
OUTPUT
|
30
|
+
end
|
31
|
+
|
32
|
+
# Mock chassis hardware output
|
33
|
+
# @return [String] mock output for 'show chassis hardware' command
|
34
|
+
def self.mock_chassis_output
|
35
|
+
<<~OUTPUT
|
36
|
+
Hardware inventory:
|
37
|
+
Item Version Part number Serial number Description
|
38
|
+
Chassis JN123456 SRX240H2
|
39
|
+
OUTPUT
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get mock response for a command
|
43
|
+
# @param cmd [String] the command to get response for
|
44
|
+
# @return [Array<String, Integer>] tuple of [output, exit_status]
|
45
|
+
def self.response_for(cmd)
|
46
|
+
response = RESPONSES.find { |pattern, _| cmd.match?(/#{pattern}/) }
|
47
|
+
|
48
|
+
if response
|
49
|
+
output = response[1].is_a?(Symbol) ? send(response[1]) : response[1]
|
50
|
+
[output, 0]
|
51
|
+
else
|
52
|
+
["% Unknown command: #{cmd}", 1]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -42,6 +42,9 @@ module TrainPlugins::Juniper
|
|
42
42
|
platform_obj = force_platform!(PLATFORM_NAME, platform_details)
|
43
43
|
logger&.debug("Set platform data: #{platform_obj.platform}")
|
44
44
|
|
45
|
+
# Log platform detection results if logging helpers available
|
46
|
+
log_platform_detection(PLATFORM_NAME, device_version) if respond_to?(:log_platform_detection)
|
47
|
+
|
45
48
|
# Cache the platform object to prevent repeated calls
|
46
49
|
@platform = platform_obj
|
47
50
|
end
|
data/train-juniper.gemspec
CHANGED
@@ -68,8 +68,8 @@ Gem::Specification.new do |spec|
|
|
68
68
|
spec.add_dependency 'net-ssh', '>= 2.9', '< 8.0'
|
69
69
|
|
70
70
|
# FFI dependency - required by train-core
|
71
|
-
#
|
72
|
-
spec.add_dependency 'ffi', '>= 1.15.5', '< 1.
|
71
|
+
# Updated to support Ruby 3.3 on Windows (ffi 1.17.x required)
|
72
|
+
spec.add_dependency 'ffi', '>= 1.15.5', '< 1.18.0'
|
73
73
|
|
74
74
|
# Development dependencies
|
75
75
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: train-juniper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- MITRE Corporation
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
version: 1.15.5
|
54
54
|
- - "<"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: 1.
|
56
|
+
version: 1.18.0
|
57
57
|
type: :runtime
|
58
58
|
prerelease: false
|
59
59
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -63,7 +63,7 @@ dependencies:
|
|
63
63
|
version: 1.15.5
|
64
64
|
- - "<"
|
65
65
|
- !ruby/object:Gem::Version
|
66
|
-
version: 1.
|
66
|
+
version: 1.18.0
|
67
67
|
- !ruby/object:Gem::Dependency
|
68
68
|
name: bundler
|
69
69
|
requirement: !ruby/object:Gem::Requirement
|
@@ -224,6 +224,16 @@ files:
|
|
224
224
|
- SECURITY.md
|
225
225
|
- lib/train-juniper.rb
|
226
226
|
- lib/train-juniper/connection.rb
|
227
|
+
- lib/train-juniper/connection/bastion_proxy.rb
|
228
|
+
- lib/train-juniper/connection/command_executor.rb
|
229
|
+
- lib/train-juniper/connection/error_handling.rb
|
230
|
+
- lib/train-juniper/connection/ssh_session.rb
|
231
|
+
- lib/train-juniper/connection/validation.rb
|
232
|
+
- lib/train-juniper/constants.rb
|
233
|
+
- lib/train-juniper/file_abstraction/juniper_file.rb
|
234
|
+
- lib/train-juniper/helpers/environment.rb
|
235
|
+
- lib/train-juniper/helpers/logging.rb
|
236
|
+
- lib/train-juniper/helpers/mock_responses.rb
|
227
237
|
- lib/train-juniper/platform.rb
|
228
238
|
- lib/train-juniper/transport.rb
|
229
239
|
- lib/train-juniper/version.rb
|