train-juniper 0.5.4
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 +7 -0
- data/.env.example +29 -0
- data/CHANGELOG.md +60 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/CONTRIBUTING.md +155 -0
- data/LICENSE.md +9 -0
- data/NOTICE.md +9 -0
- data/README.md +399 -0
- data/Rakefile +94 -0
- data/SECURITY.md +79 -0
- data/lib/train-juniper/connection.rb +408 -0
- data/lib/train-juniper/platform.rb +191 -0
- data/lib/train-juniper/transport.rb +55 -0
- data/lib/train-juniper/version.rb +12 -0
- data/lib/train-juniper.rb +23 -0
- data/train-juniper.gemspec +72 -0
- metadata +115 -0
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Platform definition file for Juniper network devices.
|
4
|
+
# This defines the "juniper" platform within Train's platform detection system.
|
5
|
+
|
6
|
+
module TrainPlugins::Juniper
|
7
|
+
# Platform detection mixin for Juniper network devices
|
8
|
+
module Platform
|
9
|
+
# Platform name constant for consistency
|
10
|
+
PLATFORM_NAME = 'juniper'
|
11
|
+
|
12
|
+
# Platform detection for Juniper network devices
|
13
|
+
#
|
14
|
+
# For dedicated transport plugins, we use force_platform! to bypass
|
15
|
+
# Train's automatic platform detection, which might run commands before
|
16
|
+
# the connection is ready. This is the standard pattern used by official
|
17
|
+
# Train plugins like train-k8s-container.
|
18
|
+
def platform
|
19
|
+
# Return cached platform if already computed
|
20
|
+
return @platform if defined?(@platform)
|
21
|
+
|
22
|
+
# Register the juniper platform in Train's platform registry
|
23
|
+
# JunOS devices are FreeBSD-based, so inherit from bsd family for InSpec resource compatibility
|
24
|
+
# This allows InSpec resources like 'command' to work with Juniper devices
|
25
|
+
Train::Platforms.name(PLATFORM_NAME).title('Juniper JunOS').in_family('bsd')
|
26
|
+
|
27
|
+
# Try to detect actual JunOS version and architecture from device
|
28
|
+
device_version = detect_junos_version || TrainPlugins::Juniper::VERSION
|
29
|
+
device_arch = detect_junos_architecture || 'unknown'
|
30
|
+
logger&.debug("Detected device architecture: #{device_arch}")
|
31
|
+
|
32
|
+
# Bypass Train's platform detection and declare our known platform
|
33
|
+
# Include architecture in the platform details to ensure it's properly set
|
34
|
+
platform_details = {
|
35
|
+
release: device_version,
|
36
|
+
arch: device_arch
|
37
|
+
}
|
38
|
+
|
39
|
+
platform_obj = force_platform!(PLATFORM_NAME, platform_details)
|
40
|
+
logger&.debug("Set platform data: #{platform_obj.platform}")
|
41
|
+
|
42
|
+
# Cache the platform object to prevent repeated calls
|
43
|
+
@platform = platform_obj
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Detect JunOS version from device output
|
49
|
+
# This runs safely after the connection is established
|
50
|
+
def detect_junos_version
|
51
|
+
# Return cached version if already detected
|
52
|
+
return @detected_junos_version if defined?(@detected_junos_version)
|
53
|
+
|
54
|
+
# Only try version detection if we have an active connection
|
55
|
+
return @detected_junos_version = nil unless respond_to?(:run_command_via_connection)
|
56
|
+
return @detected_junos_version = nil if @options&.dig(:mock) # Skip in mock mode
|
57
|
+
|
58
|
+
begin
|
59
|
+
# Check if connection is ready before running commands
|
60
|
+
return @detected_junos_version = nil unless connected?
|
61
|
+
|
62
|
+
# Execute 'show version' command to get JunOS information
|
63
|
+
result = run_command_via_connection('show version')
|
64
|
+
return @detected_junos_version = nil unless result&.exit_status&.zero?
|
65
|
+
|
66
|
+
# Cache the result for architecture detection to avoid duplicate calls
|
67
|
+
@cached_show_version_result = result
|
68
|
+
|
69
|
+
# Parse JunOS version from output using multiple patterns
|
70
|
+
version = extract_version_from_output(result.stdout)
|
71
|
+
|
72
|
+
if version
|
73
|
+
logger&.debug("Detected JunOS version: #{version}")
|
74
|
+
@detected_junos_version = version
|
75
|
+
else
|
76
|
+
logger&.debug("Could not parse JunOS version from: #{result.stdout[0..100]}")
|
77
|
+
@detected_junos_version = nil
|
78
|
+
end
|
79
|
+
rescue StandardError => e
|
80
|
+
# If version detection fails, log and return nil
|
81
|
+
logger&.debug("JunOS version detection failed: #{e.message}")
|
82
|
+
@detected_junos_version = nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Extract version string from JunOS show version output
|
87
|
+
def extract_version_from_output(output)
|
88
|
+
return nil if output.nil? || output.empty?
|
89
|
+
|
90
|
+
# Try multiple JunOS version patterns
|
91
|
+
patterns = [
|
92
|
+
/Junos:\s+([\d\w\.-]+)/, # "Junos: 12.1X47-D15.4"
|
93
|
+
/JUNOS Software Release \[([\d\w\.-]+)\]/, # "JUNOS Software Release [12.1X47-D15.4]"
|
94
|
+
/junos version ([\d\w\.-]+)/i, # "junos version 21.4R3"
|
95
|
+
/Model: \S+, JUNOS Base OS boot \[([\d\w\.-]+)\]/, # Some hardware variants
|
96
|
+
/([\d]+\.[\d]+[\w\.-]*)/ # Generic version pattern
|
97
|
+
]
|
98
|
+
|
99
|
+
patterns.each do |pattern|
|
100
|
+
match = output.match(pattern)
|
101
|
+
return match[1] if match
|
102
|
+
end
|
103
|
+
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# Detect JunOS architecture from device output
|
108
|
+
# This runs safely after the connection is established
|
109
|
+
def detect_junos_architecture
|
110
|
+
# Return cached architecture if already detected
|
111
|
+
return @detected_junos_architecture if defined?(@detected_junos_architecture)
|
112
|
+
|
113
|
+
# Only try architecture detection if we have an active connection
|
114
|
+
return @detected_junos_architecture = nil unless respond_to?(:run_command_via_connection)
|
115
|
+
return @detected_junos_architecture = nil if @options&.dig(:mock) # Skip in mock mode
|
116
|
+
|
117
|
+
begin
|
118
|
+
# Check if connection is ready before running commands
|
119
|
+
return @detected_junos_architecture = nil unless connected?
|
120
|
+
|
121
|
+
# Reuse version detection result to avoid duplicate 'show version' calls
|
122
|
+
# Both version and architecture come from the same command output
|
123
|
+
if defined?(@detected_junos_version) && @detected_junos_version
|
124
|
+
# We already have the output from version detection, parse architecture from it
|
125
|
+
result = @cached_show_version_result
|
126
|
+
else
|
127
|
+
# Execute 'show version' command and cache the result
|
128
|
+
result = run_command_via_connection('show version')
|
129
|
+
@cached_show_version_result = result if result&.exit_status&.zero?
|
130
|
+
end
|
131
|
+
|
132
|
+
return @detected_junos_architecture = nil unless result&.exit_status&.zero?
|
133
|
+
|
134
|
+
# Parse architecture from output using multiple patterns
|
135
|
+
arch = extract_architecture_from_output(result.stdout)
|
136
|
+
|
137
|
+
if arch
|
138
|
+
logger&.debug("Detected JunOS architecture: #{arch}")
|
139
|
+
@detected_junos_architecture = arch
|
140
|
+
else
|
141
|
+
logger&.debug("Could not parse JunOS architecture from: #{result.stdout[0..100]}")
|
142
|
+
@detected_junos_architecture = nil
|
143
|
+
end
|
144
|
+
rescue StandardError => e
|
145
|
+
# If architecture detection fails, log and return nil
|
146
|
+
logger&.debug("JunOS architecture detection failed: #{e.message}")
|
147
|
+
@detected_junos_architecture = nil
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Extract architecture string from JunOS show version output
|
152
|
+
def extract_architecture_from_output(output)
|
153
|
+
return nil if output.nil? || output.empty?
|
154
|
+
|
155
|
+
# Try multiple JunOS architecture patterns
|
156
|
+
patterns = [
|
157
|
+
/Model:\s+(\S+)/, # "Model: SRX240H2" -> extract model as arch indicator
|
158
|
+
/Junos:\s+[\d\w\.-]+\s+built\s+[\d-]+\s+[\d:]+\s+by\s+builder\s+on\s+(\S+)/, # Build architecture
|
159
|
+
/JUNOS.*\[([\w-]+)\]/, # JUNOS package architecture
|
160
|
+
/Architecture:\s+(\S+)/i, # Direct architecture line
|
161
|
+
/Platform:\s+(\S+)/i, # Platform designation
|
162
|
+
/Processor.*:\s*(\S+)/i # Processor type
|
163
|
+
]
|
164
|
+
|
165
|
+
patterns.each do |pattern|
|
166
|
+
match = output.match(pattern)
|
167
|
+
next unless match
|
168
|
+
|
169
|
+
arch_value = match[1]
|
170
|
+
# Convert model names to architecture indicators
|
171
|
+
case arch_value
|
172
|
+
when /SRX\d+/i
|
173
|
+
return 'x86_64' # Most SRX models are x86_64
|
174
|
+
when /MX\d+/i
|
175
|
+
return 'x86_64' # MX routers are typically x86_64
|
176
|
+
when /EX\d+/i
|
177
|
+
return 'arm64' # Many EX switches use ARM
|
178
|
+
when /QFX\d+/i
|
179
|
+
return 'x86_64' # QFX switches typically x86_64
|
180
|
+
when /^(x86_64|amd64|i386|arm64|aarch64|sparc|mips)$/i
|
181
|
+
return arch_value.downcase
|
182
|
+
else
|
183
|
+
# Return the model as-is if we can't map it
|
184
|
+
return arch_value
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
nil
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Juniper Train Plugin Transport Definition
|
4
|
+
# Defines the main transport class for connecting to Juniper network devices.
|
5
|
+
# This transport enables SSH connectivity to JunOS devices for InSpec.
|
6
|
+
require 'train-juniper/connection'
|
7
|
+
|
8
|
+
module TrainPlugins
|
9
|
+
module Juniper
|
10
|
+
class Transport < Train.plugin(1)
|
11
|
+
name 'juniper'
|
12
|
+
|
13
|
+
# Connection options for Juniper devices
|
14
|
+
# Following Train SSH transport standard options
|
15
|
+
option :host, required: true
|
16
|
+
option :port, default: 22
|
17
|
+
option :user, required: true
|
18
|
+
option :password, default: nil
|
19
|
+
option :timeout, default: 30
|
20
|
+
|
21
|
+
# Proxy/Bastion host support (Train standard options)
|
22
|
+
option :bastion_host, default: nil
|
23
|
+
option :bastion_user, default: nil # Let connection handle env vars and defaults
|
24
|
+
option :bastion_port, default: 22
|
25
|
+
option :bastion_password, default: nil # Separate password for bastion authentication
|
26
|
+
option :proxy_command, default: nil
|
27
|
+
|
28
|
+
# SSH key authentication options
|
29
|
+
option :key_files, default: nil
|
30
|
+
option :keys_only, default: false
|
31
|
+
|
32
|
+
# Advanced SSH options
|
33
|
+
option :keepalive, default: true
|
34
|
+
option :keepalive_interval, default: 60
|
35
|
+
option :connection_timeout, default: 30
|
36
|
+
option :connection_retries, default: 5
|
37
|
+
option :connection_retry_sleep, default: 1
|
38
|
+
|
39
|
+
# Standard Train options for compatibility
|
40
|
+
option :insecure, default: false
|
41
|
+
option :self_signed, default: false
|
42
|
+
|
43
|
+
# Juniper-specific options
|
44
|
+
option :mock, default: false
|
45
|
+
option :disable_complete_on_space, default: false
|
46
|
+
|
47
|
+
# Create and return a connection to a Juniper device
|
48
|
+
def connection(_instance_opts = nil)
|
49
|
+
# Cache the connection instance for reuse
|
50
|
+
# @options contains parsed connection details from train URI
|
51
|
+
@connection ||= TrainPlugins::Juniper::Connection.new(@options)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file exists simply to record the version number of the plugin.
|
4
|
+
# It is kept in a separate file, so that your gemspec can load it and
|
5
|
+
# learn the current version without loading the whole plugin. Also,
|
6
|
+
# many CI servers can update this file when "version bumping".
|
7
|
+
|
8
|
+
module TrainPlugins
|
9
|
+
module Juniper
|
10
|
+
VERSION = '0.5.4'
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is known as the "entry point."
|
4
|
+
# This is the file Train will try to load if it
|
5
|
+
# thinks your plugin is needed.
|
6
|
+
|
7
|
+
# The *only* thing this file should do is setup the
|
8
|
+
# load path, then load plugin files.
|
9
|
+
|
10
|
+
# Next two lines simply add the path of the gem to the load path.
|
11
|
+
# This is not needed when being loaded as a gem; but when doing
|
12
|
+
# plugin development, you may need it. Either way, it's harmless.
|
13
|
+
libdir = __dir__
|
14
|
+
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
15
|
+
|
16
|
+
# It's traditional to keep your gem version in a separate file, so CI can find it easier.
|
17
|
+
require 'train-juniper/version'
|
18
|
+
|
19
|
+
# A train plugin has three components: Transport, Connection, and Platform.
|
20
|
+
# Transport acts as the glue.
|
21
|
+
require 'train-juniper/transport'
|
22
|
+
require 'train-juniper/platform'
|
23
|
+
require 'train-juniper/connection'
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# As plugins are usually packaged and distributed as a RubyGem,
|
4
|
+
# we have to provide a .gemspec file, which controls the gembuild
|
5
|
+
# and publish process. This is a fairly generic gemspec.
|
6
|
+
|
7
|
+
# It is traditional in a gemspec to dynamically load the current version
|
8
|
+
# from a file in the source tree. The next three lines make that happen.
|
9
|
+
lib = File.expand_path('lib', __dir__)
|
10
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
11
|
+
require 'train-juniper/version'
|
12
|
+
|
13
|
+
Gem::Specification.new do |spec|
|
14
|
+
# Importantly, all Train plugins must be prefixed with `train-`
|
15
|
+
spec.name = 'train-juniper'
|
16
|
+
|
17
|
+
# It is polite to namespace your plugin under TrainPlugins::YourPluginInCamelCase
|
18
|
+
spec.version = TrainPlugins::Juniper::VERSION
|
19
|
+
spec.authors = ['MITRE Corporation']
|
20
|
+
spec.email = ['saf@mitre.org']
|
21
|
+
spec.summary = 'Train transport for Juniper Networks JunOS devices'
|
22
|
+
spec.description = 'Provides SSH connectivity to Juniper Networks devices running JunOS for InSpec compliance testing and ' \
|
23
|
+
'infrastructure inspection. Supports platform detection, command execution, and configuration file access.'
|
24
|
+
spec.homepage = 'https://github.com/mitre/train-juniper'
|
25
|
+
spec.license = 'Apache-2.0'
|
26
|
+
|
27
|
+
# Metadata for better gem discovery
|
28
|
+
spec.metadata = {
|
29
|
+
'bug_tracker_uri' => 'https://github.com/mitre/train-juniper/issues',
|
30
|
+
'changelog_uri' => 'https://github.com/mitre/train-juniper/blob/main/CHANGELOG.md',
|
31
|
+
'documentation_uri' => 'https://mitre.github.io/train-juniper/',
|
32
|
+
'homepage_uri' => 'https://github.com/mitre/train-juniper',
|
33
|
+
'source_code_uri' => 'https://github.com/mitre/train-juniper',
|
34
|
+
'security_policy_uri' => 'https://github.com/mitre/train-juniper/security/policy',
|
35
|
+
'rubygems_mfa_required' => 'true'
|
36
|
+
}
|
37
|
+
|
38
|
+
# Though complicated-looking, this is pretty standard for a gemspec.
|
39
|
+
# It just filters what will actually be packaged in the gem (leaving
|
40
|
+
# out tests, etc)
|
41
|
+
# Standard pattern for Train plugins - include all lib files and key docs
|
42
|
+
spec.files = %w[
|
43
|
+
README.md train-juniper.gemspec LICENSE.md NOTICE.md CHANGELOG.md
|
44
|
+
CODE_OF_CONDUCT.md CONTRIBUTING.md SECURITY.md
|
45
|
+
.env.example Rakefile
|
46
|
+
] + Dir.glob(
|
47
|
+
'lib/**/*', File::FNM_DOTMATCH
|
48
|
+
).reject { |f| File.directory?(f) }
|
49
|
+
spec.require_paths = ['lib']
|
50
|
+
|
51
|
+
# If you rely on any other gems, list them here with any constraints.
|
52
|
+
# This is how `inspec plugin install` is able to manage your dependencies.
|
53
|
+
# For example, perhaps you are writing a thing that talks to AWS, and you
|
54
|
+
# want to ensure you have `aws-sdk` in a certain version.
|
55
|
+
|
56
|
+
# If you only need certain gems during development or testing, list
|
57
|
+
# them in Gemfile, not here.
|
58
|
+
# Do not list inspec as a dependency of the train plugin.
|
59
|
+
|
60
|
+
# All plugins should mention train, > 1.4
|
61
|
+
spec.required_ruby_version = '>= 3.1.0'
|
62
|
+
|
63
|
+
# Community plugins typically use train-core for smaller footprint
|
64
|
+
# train-core provides core functionality without cloud dependencies
|
65
|
+
spec.add_dependency 'train-core', '~> 3.12.13'
|
66
|
+
|
67
|
+
# SSH connectivity dependencies - match train-core's exact version range
|
68
|
+
spec.add_dependency 'net-ssh', '>= 2.9', '< 8.0'
|
69
|
+
|
70
|
+
# Force compatible FFI version to avoid conflicts with InSpec
|
71
|
+
spec.add_dependency 'ffi', '~> 1.16.0'
|
72
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: train-juniper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- MITRE Corporation
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-06-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: train-core
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.12.13
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.12.13
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: net-ssh
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.9'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '8.0'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.9'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '8.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: ffi
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.16.0
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 1.16.0
|
61
|
+
description: Provides SSH connectivity to Juniper Networks devices running JunOS for
|
62
|
+
InSpec compliance testing and infrastructure inspection. Supports platform detection,
|
63
|
+
command execution, and configuration file access.
|
64
|
+
email:
|
65
|
+
- saf@mitre.org
|
66
|
+
executables: []
|
67
|
+
extensions: []
|
68
|
+
extra_rdoc_files: []
|
69
|
+
files:
|
70
|
+
- ".env.example"
|
71
|
+
- CHANGELOG.md
|
72
|
+
- CODE_OF_CONDUCT.md
|
73
|
+
- CONTRIBUTING.md
|
74
|
+
- LICENSE.md
|
75
|
+
- NOTICE.md
|
76
|
+
- README.md
|
77
|
+
- Rakefile
|
78
|
+
- SECURITY.md
|
79
|
+
- lib/train-juniper.rb
|
80
|
+
- lib/train-juniper/connection.rb
|
81
|
+
- lib/train-juniper/platform.rb
|
82
|
+
- lib/train-juniper/transport.rb
|
83
|
+
- lib/train-juniper/version.rb
|
84
|
+
- train-juniper.gemspec
|
85
|
+
homepage: https://github.com/mitre/train-juniper
|
86
|
+
licenses:
|
87
|
+
- Apache-2.0
|
88
|
+
metadata:
|
89
|
+
bug_tracker_uri: https://github.com/mitre/train-juniper/issues
|
90
|
+
changelog_uri: https://github.com/mitre/train-juniper/blob/main/CHANGELOG.md
|
91
|
+
documentation_uri: https://mitre.github.io/train-juniper/
|
92
|
+
homepage_uri: https://github.com/mitre/train-juniper
|
93
|
+
source_code_uri: https://github.com/mitre/train-juniper
|
94
|
+
security_policy_uri: https://github.com/mitre/train-juniper/security/policy
|
95
|
+
rubygems_mfa_required: 'true'
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 3.1.0
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubygems_version: 3.3.27
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: Train transport for Juniper Networks JunOS devices
|
115
|
+
test_files: []
|