Module: TrainPlugins::Juniper::Platform

Included in:
Connection
Defined in:
lib/train-juniper/platform.rb

Overview

Note:

This module is mixed into the Connection class to provide platform detection

Platform detection mixin for Juniper network devices

Constant Summary collapse

PLATFORM_NAME =

Platform name constant for consistency

'juniper'

Instance Method Summary collapse

Instance Method Details

#detect_attribute(attribute_name, command = 'show version') {|String| ... } ⇒ String? (private)

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



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
# File 'lib/train-juniper/platform.rb', line 88

def detect_attribute(attribute_name, command = 'show version', &extraction_block)
  cache_var = "@detected_#{attribute_name}"
  return instance_variable_get(cache_var) if instance_variable_defined?(cache_var)

  unless respond_to?(:run_command_via_connection)
    logger&.debug('run_command_via_connection not available yet')
    return instance_variable_set(cache_var, nil)
  end

  logger&.debug("Mock mode: #{@options&.dig(:mock)}, Connected: #{connected?}")

  begin
    return instance_variable_set(cache_var, nil) unless connected?

    # Reuse cached command result if available
    result = @cached_show_version_result || run_command_via_connection(command)
    @cached_show_version_result ||= result if command == 'show version' && result&.exit_status&.zero?

    return instance_variable_set(cache_var, nil) unless result&.exit_status&.zero?

    value = extraction_block.call(result.stdout)

    if value
      logger&.debug("Detected #{attribute_name}: #{value}")
      instance_variable_set(cache_var, value)
    else
      logger&.debug("Could not parse #{attribute_name} from: #{result.stdout[0..100]}")
      instance_variable_set(cache_var, nil)
    end
  rescue StandardError => e
    logger&.debug("#{attribute_name} detection failed: #{e.message}")
    instance_variable_set(cache_var, nil)
  end
end

#detect_junos_architectureString? (private)

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



146
147
148
# File 'lib/train-juniper/platform.rb', line 146

def detect_junos_architecture
  detect_attribute('junos_architecture', 'show version | display xml') { |output| extract_architecture_from_xml(output) }
end

#detect_junos_serialString? (private)

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



183
184
185
# File 'lib/train-juniper/platform.rb', line 183

def detect_junos_serial
  detect_attribute('junos_serial', 'show chassis hardware | display xml') { |output| extract_serial_from_xml(output) }
end

#detect_junos_versionString? (private)

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



126
127
128
# File 'lib/train-juniper/platform.rb', line 126

def detect_junos_version
  detect_attribute('junos_version', 'show version | display xml') { |output| extract_version_from_xml(output) }
end

#extract_architecture_from_xml(output) ⇒ String? (private)

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



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/train-juniper/platform.rb', line 153

def extract_architecture_from_xml(output)
  xpath_patterns = [
    '//product-model',
    '//software-information/product-model',
    '//chassis-inventory/chassis/description'
  ]

  extract_from_xml(output, xpath_patterns, 'show version | display xml') do |element|
    model = element.text.strip

    # Map model names to architecture
    case model
    when /SRX\d+/i
      'x86_64'  # Most SRX models are x86_64
    when /MX\d+/i
      'x86_64'  # MX routers are typically x86_64
    when /EX\d+/i
      'arm64'   # Many EX switches use ARM
    when /QFX\d+/i
      'x86_64'  # QFX switches typically x86_64
    else
      # Default to x86_64 for unknown models
      'x86_64'
    end
  end
end

#extract_from_xml(output, xpath_patterns, command_desc) {|REXML::Element| ... } ⇒ String? (private)

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



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/train-juniper/platform.rb', line 62

def extract_from_xml(output, xpath_patterns, command_desc)
  return nil if output.nil? || output.empty?

  doc = REXML::Document.new(output)

  # Try each XPath pattern until we find an element
  element = nil
  xpath_patterns.each do |xpath|
    element = doc.elements[xpath]
    break if element
  end

  return nil unless element

  # If block given, let it process the element, otherwise return text
  block_given? ? yield(element) : element.text&.strip
rescue StandardError => e
  logger&.warn("Failed to parse XML output from '#{command_desc}': #{e.message}")
  nil
end

#extract_serial_from_xml(output) ⇒ String? (private)

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



190
191
192
193
194
195
196
197
198
# File 'lib/train-juniper/platform.rb', line 190

def extract_serial_from_xml(output)
  xpath_patterns = [
    '//chassis/serial-number',
    '//chassis-sub-module/serial-number',
    '//module/serial-number[1]'
  ]

  extract_from_xml(output, xpath_patterns, 'show chassis hardware | display xml')
end

#extract_version_from_xml(output) ⇒ String? (private)

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



133
134
135
136
137
138
139
140
141
# File 'lib/train-juniper/platform.rb', line 133

def extract_version_from_xml(output)
  xpath_patterns = [
    '//junos-version',
    '//package-information/name[text()="junos"]/following-sibling::comment',
    '//software-information/version'
  ]

  extract_from_xml(output, xpath_patterns, 'show version | display xml')
end

#platformTrain::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



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/train-juniper/platform.rb', line 23

def platform
  # Return cached platform if already computed
  return @platform if defined?(@platform)

  # Register the juniper platform in Train's platform registry
  # JunOS devices are FreeBSD-based, so inherit from bsd family for InSpec resource compatibility
  # This allows InSpec resources like 'command' to work with Juniper devices
  Train::Platforms.name(PLATFORM_NAME).title('Juniper JunOS').in_family('bsd')

  # Try to detect actual JunOS version and architecture from device
  device_version = detect_junos_version || TrainPlugins::Juniper::VERSION
  device_arch = detect_junos_architecture || 'unknown'
  logger&.debug("Detected device architecture: #{device_arch}")

  # Bypass Train's platform detection and declare our known platform
  # Include architecture in the platform details to ensure it's properly set
  platform_details = {
    release: device_version,
    arch: device_arch
  }

  platform_obj = force_platform!(PLATFORM_NAME, platform_details)
  logger&.debug("Set platform data: #{platform_obj.platform}")

  # Log platform detection results if logging helpers available
  log_platform_detection(PLATFORM_NAME, device_version) if respond_to?(:log_platform_detection)

  # Cache the platform object to prevent repeated calls
  @platform = platform_obj
end