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.
@@ -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
@@ -8,6 +8,6 @@
8
8
  module TrainPlugins
9
9
  module Juniper
10
10
  # Version number of the train-juniper plugin
11
- VERSION = '0.7.0'
11
+ VERSION = '0.7.3'
12
12
  end
13
13
  end
@@ -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
- # Match InSpec 7's FFI version range for compatibility
72
- spec.add_dependency 'ffi', '>= 1.15.5', '< 1.17.0'
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.0
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.17.0
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.17.0
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