train 3.2.14 → 3.2.20
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
- metadata +29 -149
- data/LICENSE +0 -201
- data/lib/train.rb +0 -193
- data/lib/train/errors.rb +0 -44
- data/lib/train/extras.rb +0 -11
- data/lib/train/extras/command_wrapper.rb +0 -201
- data/lib/train/extras/stat.rb +0 -136
- data/lib/train/file.rb +0 -212
- data/lib/train/file/local.rb +0 -82
- data/lib/train/file/local/unix.rb +0 -96
- data/lib/train/file/local/windows.rb +0 -68
- data/lib/train/file/remote.rb +0 -40
- data/lib/train/file/remote/aix.rb +0 -29
- data/lib/train/file/remote/linux.rb +0 -21
- data/lib/train/file/remote/qnx.rb +0 -41
- data/lib/train/file/remote/unix.rb +0 -110
- data/lib/train/file/remote/windows.rb +0 -110
- data/lib/train/globals.rb +0 -5
- data/lib/train/options.rb +0 -81
- data/lib/train/platforms.rb +0 -102
- data/lib/train/platforms/common.rb +0 -34
- data/lib/train/platforms/detect.rb +0 -12
- data/lib/train/platforms/detect/helpers/os_common.rb +0 -160
- data/lib/train/platforms/detect/helpers/os_linux.rb +0 -80
- data/lib/train/platforms/detect/helpers/os_windows.rb +0 -142
- data/lib/train/platforms/detect/scanner.rb +0 -85
- data/lib/train/platforms/detect/specifications/api.rb +0 -20
- data/lib/train/platforms/detect/specifications/os.rb +0 -629
- data/lib/train/platforms/detect/uuid.rb +0 -32
- data/lib/train/platforms/family.rb +0 -31
- data/lib/train/platforms/platform.rb +0 -109
- data/lib/train/plugin_test_helper.rb +0 -51
- data/lib/train/plugins.rb +0 -40
- data/lib/train/plugins/base_connection.rb +0 -198
- data/lib/train/plugins/transport.rb +0 -49
- data/lib/train/transports/cisco_ios_connection.rb +0 -133
- data/lib/train/transports/local.rb +0 -240
- data/lib/train/transports/mock.rb +0 -183
- data/lib/train/transports/ssh.rb +0 -271
- data/lib/train/transports/ssh_connection.rb +0 -342
- data/lib/train/version.rb +0 -7
@@ -1,49 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
#
|
3
|
-
# Author:: Dominik Richter (<dominik.richter@gmail.com>)
|
4
|
-
# Author:: Christoph Hartmann (<chris@lollyrock.com>)
|
5
|
-
|
6
|
-
require "logger"
|
7
|
-
require_relative "../errors"
|
8
|
-
require_relative "../extras"
|
9
|
-
require_relative "../options"
|
10
|
-
|
11
|
-
class Train::Plugins
|
12
|
-
class Transport
|
13
|
-
include Train::Extras
|
14
|
-
Train::Options.attach(self)
|
15
|
-
|
16
|
-
require_relative "base_connection"
|
17
|
-
|
18
|
-
# Initialize a new Transport object
|
19
|
-
#
|
20
|
-
# @param [Hash] config = nil the configuration for this transport
|
21
|
-
# @return [Transport] the transport object
|
22
|
-
def initialize(options = {})
|
23
|
-
@options = merge_options({}, options || {})
|
24
|
-
@logger = @options[:logger] || Logger.new($stdout, level: :fatal)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Create a connection to the target. Options may be provided
|
28
|
-
# for additional configuration.
|
29
|
-
#
|
30
|
-
# @param [Hash] _options = nil provide optional configuration params
|
31
|
-
# @return [Connection] the connection for this configuration
|
32
|
-
def connection(_options = nil)
|
33
|
-
raise Train::ClientError, "#{self.class} does not implement #connection()"
|
34
|
-
end
|
35
|
-
|
36
|
-
# Register the inheriting class with as a train plugin using the
|
37
|
-
# provided name.
|
38
|
-
#
|
39
|
-
# @param [String] name of the plugin, by which it will be found
|
40
|
-
def self.name(name)
|
41
|
-
Train::Plugins.registry[name] = self
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
# @return [Logger] logger for reporting information
|
47
|
-
attr_reader :logger
|
48
|
-
end
|
49
|
-
end
|
@@ -1,133 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
class Train::Transports::SSH
|
4
|
-
class CiscoIOSConnection < BaseConnection
|
5
|
-
class BadEnablePassword < Train::TransportError; end
|
6
|
-
|
7
|
-
def initialize(options)
|
8
|
-
super(options)
|
9
|
-
|
10
|
-
# Extract options to avoid passing them in to `Net::SSH.start` later
|
11
|
-
@host = options.delete(:host)
|
12
|
-
@user = options.delete(:user)
|
13
|
-
@port = options.delete(:port)
|
14
|
-
@enable_password = options.delete(:enable_password)
|
15
|
-
|
16
|
-
# Use all options left that are not `nil` for `Net::SSH.start` later
|
17
|
-
@ssh_options = options.reject { |_key, value| value.nil? }
|
18
|
-
|
19
|
-
@prompt = /^\S+[>#]\r\n.*$/
|
20
|
-
end
|
21
|
-
|
22
|
-
def uri
|
23
|
-
"ssh://#{@user}@#{@host}:#{@port}"
|
24
|
-
end
|
25
|
-
|
26
|
-
def unique_identifier
|
27
|
-
result = run_command_via_connection("show version | include Processor")
|
28
|
-
result.stdout.split(" ")[-1]
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def establish_connection
|
34
|
-
logger.debug("[SSH] opening connection to #{self}")
|
35
|
-
|
36
|
-
Net::SSH.start(@host, @user, @ssh_options)
|
37
|
-
end
|
38
|
-
|
39
|
-
def session
|
40
|
-
return @session unless @session.nil?
|
41
|
-
|
42
|
-
@session = open_channel(establish_connection)
|
43
|
-
|
44
|
-
# Escalate privilege to enable mode if password is given
|
45
|
-
if @enable_password
|
46
|
-
# This verifies we are not in privileged exec mode before running the
|
47
|
-
# enable command. Otherwise, the password will be in history.
|
48
|
-
if run_command_via_connection("show privilege").stdout.split[-1] != "15"
|
49
|
-
# Extra newlines to get back to prompt if incorrect password is used
|
50
|
-
run_command_via_connection("enable\n#{@enable_password}\n\n\n")
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Prevent `--MORE--` by removing terminal length limit
|
55
|
-
run_command_via_connection("terminal length 0")
|
56
|
-
|
57
|
-
@session
|
58
|
-
end
|
59
|
-
|
60
|
-
def run_command_via_connection(cmd, &_data_handler)
|
61
|
-
# Ensure buffer is empty before sending data
|
62
|
-
@buf = ""
|
63
|
-
|
64
|
-
logger.debug("[SSH] Running `#{cmd}` on #{self}")
|
65
|
-
session.send_data(cmd + "\r\n")
|
66
|
-
|
67
|
-
logger.debug("[SSH] waiting for prompt")
|
68
|
-
until @buf =~ @prompt
|
69
|
-
if @buf =~ /Bad (secrets|password)|Access denied/
|
70
|
-
raise BadEnablePassword
|
71
|
-
end
|
72
|
-
|
73
|
-
session.connection.process(0)
|
74
|
-
end
|
75
|
-
|
76
|
-
# Save the buffer and clear it for the next command
|
77
|
-
output = @buf.dup
|
78
|
-
@buf = ""
|
79
|
-
|
80
|
-
format_result(format_output(output, cmd))
|
81
|
-
end
|
82
|
-
|
83
|
-
ERROR_MATCHERS = [
|
84
|
-
"Bad IP address",
|
85
|
-
"Incomplete command",
|
86
|
-
"Invalid input detected",
|
87
|
-
"Unrecognized host",
|
88
|
-
].freeze
|
89
|
-
|
90
|
-
# IOS commands do not have an exit code so we must compare the command
|
91
|
-
# output with partial segments of known errors. Then, we return a
|
92
|
-
# `CommandResult` with arguments in the correct position based on the
|
93
|
-
# result.
|
94
|
-
def format_result(result)
|
95
|
-
if ERROR_MATCHERS.none? { |e| result.include?(e) }
|
96
|
-
CommandResult.new(result, "", 0)
|
97
|
-
else
|
98
|
-
CommandResult.new("", result, 1)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# The buffer (@buf) contains all data sent/received on the SSH channel so
|
103
|
-
# we need to format the data to match what we would expect from Train
|
104
|
-
def format_output(output, cmd)
|
105
|
-
leading_prompt = /(\r\n|^)\S+[>#]/
|
106
|
-
command_string = /#{Regexp.quote(cmd)}\r\n/
|
107
|
-
trailing_prompt = /\S+[>#](\r\n|$)/
|
108
|
-
trailing_line_endings = /(\r\n)+$/
|
109
|
-
|
110
|
-
output
|
111
|
-
.sub(leading_prompt, "")
|
112
|
-
.sub(command_string, "")
|
113
|
-
.gsub(trailing_prompt, "")
|
114
|
-
.gsub(trailing_line_endings, "")
|
115
|
-
end
|
116
|
-
|
117
|
-
# Create an SSH channel that writes to @buf when data is received
|
118
|
-
def open_channel(ssh)
|
119
|
-
logger.debug("[SSH] opening SSH channel to #{self}")
|
120
|
-
ssh.open_channel do |ch|
|
121
|
-
ch.on_data do |_, data|
|
122
|
-
@buf += data
|
123
|
-
end
|
124
|
-
|
125
|
-
ch.send_channel_request("shell") do |_, success|
|
126
|
-
raise "Failed to open SSH shell" unless success
|
127
|
-
|
128
|
-
logger.debug("[SSH] shell opened")
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
@@ -1,240 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
#
|
3
|
-
# author: Dominik Richter
|
4
|
-
# author: Christoph Hartmann
|
5
|
-
|
6
|
-
require_relative "../plugins"
|
7
|
-
require_relative "../errors"
|
8
|
-
require "mixlib/shellout"
|
9
|
-
|
10
|
-
module Train::Transports
|
11
|
-
class Local < Train.plugin(1)
|
12
|
-
name "local"
|
13
|
-
|
14
|
-
class PipeError < Train::TransportError; end
|
15
|
-
|
16
|
-
def connection(_ = nil)
|
17
|
-
@connection ||= Connection.new(@options)
|
18
|
-
end
|
19
|
-
|
20
|
-
class Connection < BaseConnection
|
21
|
-
def initialize(options)
|
22
|
-
super(options)
|
23
|
-
|
24
|
-
@runner = if options[:command_runner]
|
25
|
-
force_runner(options[:command_runner])
|
26
|
-
else
|
27
|
-
select_runner(options)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def login_command
|
32
|
-
nil # none, open your shell
|
33
|
-
end
|
34
|
-
|
35
|
-
def uri
|
36
|
-
"local://"
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
def select_runner(options)
|
42
|
-
if os.windows?
|
43
|
-
# Force a 64 bit poweshell if needed
|
44
|
-
if RUBY_PLATFORM == "i386-mingw32" && os.arch == "x86_64"
|
45
|
-
powershell_cmd = "#{ENV["SystemRoot"]}\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe"
|
46
|
-
else
|
47
|
-
powershell_cmd = "powershell"
|
48
|
-
end
|
49
|
-
|
50
|
-
# Attempt to use a named pipe but fallback to ShellOut if that fails
|
51
|
-
begin
|
52
|
-
WindowsPipeRunner.new(powershell_cmd)
|
53
|
-
rescue PipeError
|
54
|
-
WindowsShellRunner.new(powershell_cmd)
|
55
|
-
end
|
56
|
-
else
|
57
|
-
GenericRunner.new(self, options)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def force_runner(command_runner)
|
62
|
-
case command_runner
|
63
|
-
when :generic
|
64
|
-
GenericRunner.new(self, options)
|
65
|
-
when :windows_pipe
|
66
|
-
WindowsPipeRunner.new
|
67
|
-
when :windows_shell
|
68
|
-
WindowsShellRunner.new
|
69
|
-
else
|
70
|
-
raise "Runner type `#{command_runner}` not supported"
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def run_command_via_connection(cmd, &_data_handler)
|
75
|
-
# Use the runner if it is available
|
76
|
-
return @runner.run_command(cmd) if defined?(@runner)
|
77
|
-
|
78
|
-
# If we don't have a runner, such as at the beginning of setting up the
|
79
|
-
# transport and performing the first few steps of OS detection, fall
|
80
|
-
# back to shelling out.
|
81
|
-
res = Mixlib::ShellOut.new(cmd)
|
82
|
-
res.run_command
|
83
|
-
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
84
|
-
rescue Errno::ENOENT => _
|
85
|
-
CommandResult.new("", "", 1)
|
86
|
-
end
|
87
|
-
|
88
|
-
def file_via_connection(path)
|
89
|
-
if os.windows?
|
90
|
-
Train::File::Local::Windows.new(self, path)
|
91
|
-
else
|
92
|
-
Train::File::Local::Unix.new(self, path)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
class GenericRunner
|
97
|
-
include_options Train::Extras::CommandWrapper
|
98
|
-
|
99
|
-
def initialize(connection, options)
|
100
|
-
@cmd_wrapper = Local::CommandWrapper.load(connection, options)
|
101
|
-
end
|
102
|
-
|
103
|
-
def run_command(cmd)
|
104
|
-
if defined?(@cmd_wrapper) && !@cmd_wrapper.nil?
|
105
|
-
cmd = @cmd_wrapper.run(cmd)
|
106
|
-
end
|
107
|
-
|
108
|
-
res = Mixlib::ShellOut.new(cmd)
|
109
|
-
res.run_command
|
110
|
-
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
class WindowsShellRunner
|
115
|
-
require "json"
|
116
|
-
require "base64"
|
117
|
-
|
118
|
-
def initialize(powershell_cmd = "powershell")
|
119
|
-
@powershell_cmd = powershell_cmd
|
120
|
-
end
|
121
|
-
|
122
|
-
def run_command(script)
|
123
|
-
# Prevent progress stream from leaking into stderr
|
124
|
-
script = "$ProgressPreference='SilentlyContinue';" + script
|
125
|
-
|
126
|
-
# Encode script so PowerShell can use it
|
127
|
-
script = script.encode("UTF-16LE", "UTF-8")
|
128
|
-
base64_script = Base64.strict_encode64(script)
|
129
|
-
|
130
|
-
cmd = "#{@powershell_cmd} -NoProfile -EncodedCommand #{base64_script}"
|
131
|
-
|
132
|
-
res = Mixlib::ShellOut.new(cmd)
|
133
|
-
res.run_command
|
134
|
-
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
class WindowsPipeRunner
|
139
|
-
require "json"
|
140
|
-
require "base64"
|
141
|
-
require "securerandom"
|
142
|
-
|
143
|
-
def initialize(powershell_cmd = "powershell")
|
144
|
-
@powershell_cmd = powershell_cmd
|
145
|
-
@pipe = acquire_pipe
|
146
|
-
raise PipeError if @pipe.nil?
|
147
|
-
end
|
148
|
-
|
149
|
-
# @param cmd The command to execute
|
150
|
-
# @return Local::ComandResult with stdout, stderr and exitstatus
|
151
|
-
# Note that exitstatus ($?) in PowerShell is boolean, but we use a numeric exit code.
|
152
|
-
# A command that succeeds without setting an exit code will have exitstatus 0
|
153
|
-
# A command that exits with an exit code will have that value as exitstatus
|
154
|
-
# A command that fails (e.g. throws exception) before setting an exit code will have exitstatus 1
|
155
|
-
def run_command(cmd)
|
156
|
-
script = "$ProgressPreference='SilentlyContinue';" + cmd
|
157
|
-
encoded_script = Base64.strict_encode64(script)
|
158
|
-
@pipe.puts(encoded_script)
|
159
|
-
@pipe.flush
|
160
|
-
res = OpenStruct.new(JSON.parse(Base64.decode64(@pipe.readline)))
|
161
|
-
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
162
|
-
end
|
163
|
-
|
164
|
-
private
|
165
|
-
|
166
|
-
def acquire_pipe
|
167
|
-
pipe_name = "inspec_#{SecureRandom.hex}"
|
168
|
-
|
169
|
-
start_pipe_server(pipe_name)
|
170
|
-
|
171
|
-
pipe = nil
|
172
|
-
|
173
|
-
# PowerShell needs time to create pipe.
|
174
|
-
100.times do
|
175
|
-
begin
|
176
|
-
pipe = open("//./pipe/#{pipe_name}", "r+")
|
177
|
-
break
|
178
|
-
rescue
|
179
|
-
sleep 0.1
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
pipe
|
184
|
-
end
|
185
|
-
|
186
|
-
def start_pipe_server(pipe_name)
|
187
|
-
require "win32/process"
|
188
|
-
|
189
|
-
script = <<-EOF
|
190
|
-
$ErrorActionPreference = 'Stop'
|
191
|
-
|
192
|
-
$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream('#{pipe_name}')
|
193
|
-
$pipeReader = New-Object System.IO.StreamReader($pipeServer)
|
194
|
-
$pipeWriter = New-Object System.IO.StreamWriter($pipeServer)
|
195
|
-
|
196
|
-
$pipeServer.WaitForConnection()
|
197
|
-
|
198
|
-
# Create loop to receive and process user commands/scripts
|
199
|
-
$clientConnected = $true
|
200
|
-
while($clientConnected) {
|
201
|
-
$input = $pipeReader.ReadLine()
|
202
|
-
$command = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($input))
|
203
|
-
|
204
|
-
# Execute user command/script and convert result to JSON
|
205
|
-
$scriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock($command)
|
206
|
-
try {
|
207
|
-
$stdout = & $scriptBlock | Out-String
|
208
|
-
$exit_code = $LastExitCode
|
209
|
-
if ($exit_code -eq $null)
|
210
|
-
{
|
211
|
-
$exit_code = 0
|
212
|
-
}
|
213
|
-
$result = @{ 'stdout' = $stdout ; 'stderr' = ''; 'exitstatus' = $exit_code }
|
214
|
-
} catch {
|
215
|
-
$stderr = $_ | Out-String
|
216
|
-
$exit_code = $LastExitCode
|
217
|
-
$result = @{ 'stdout' = ''; 'stderr' = $stderr; 'exitstatus' = $exit_code }
|
218
|
-
}
|
219
|
-
$resultJSON = $result | ConvertTo-JSON
|
220
|
-
|
221
|
-
# Encode JSON in Base64 and write to pipe
|
222
|
-
$encodedResult = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($resultJSON))
|
223
|
-
$pipeWriter.WriteLine($encodedResult)
|
224
|
-
$pipeWriter.Flush()
|
225
|
-
}
|
226
|
-
EOF
|
227
|
-
|
228
|
-
utf8_script = script.encode("UTF-16LE", "UTF-8")
|
229
|
-
base64_script = Base64.strict_encode64(utf8_script)
|
230
|
-
cmd = "#{@powershell_cmd} -NoProfile -ExecutionPolicy bypass -NonInteractive -EncodedCommand #{base64_script}"
|
231
|
-
|
232
|
-
server_pid = Process.create(command_line: cmd).process_id
|
233
|
-
|
234
|
-
# Ensure process is killed when the Train process exits
|
235
|
-
at_exit { Process.kill("KILL", server_pid) }
|
236
|
-
end
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|
@@ -1,183 +0,0 @@
|
|
1
|
-
require_relative "../plugins"
|
2
|
-
require "digest"
|
3
|
-
|
4
|
-
module Train::Transports
|
5
|
-
class Mock < Train.plugin(1)
|
6
|
-
name "mock"
|
7
|
-
|
8
|
-
def initialize(conf = nil)
|
9
|
-
@conf = conf || {}
|
10
|
-
trace_calls if @conf[:trace]
|
11
|
-
end
|
12
|
-
|
13
|
-
def connection
|
14
|
-
@connection ||= Connection.new(@conf)
|
15
|
-
end
|
16
|
-
|
17
|
-
def to_s
|
18
|
-
"Mock Transport"
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def trace_calls
|
24
|
-
interface_methods = {
|
25
|
-
"Train::Transports::Mock" =>
|
26
|
-
Train::Transports::Mock.instance_methods(false),
|
27
|
-
"Train::Transports::Mock::Connection" =>
|
28
|
-
Connection.instance_methods(false),
|
29
|
-
"Train::Transports::Mock::Connection::File" =>
|
30
|
-
Connection::FileCommon.instance_methods(false),
|
31
|
-
"Train::Transports::Mock::Connection::OS" =>
|
32
|
-
Train::Platform.instance_methods(false),
|
33
|
-
}
|
34
|
-
|
35
|
-
# rubocop:disable Metrics/ParameterLists
|
36
|
-
# rubocop:disable Security/Eval
|
37
|
-
set_trace_func(proc { |event, _file, _line, id, binding, classname|
|
38
|
-
unless classname.to_s.start_with?("Train::Transports::Mock") &&
|
39
|
-
(event == "call") &&
|
40
|
-
interface_methods[classname.to_s].include?(id)
|
41
|
-
next
|
42
|
-
end
|
43
|
-
|
44
|
-
# kindly borrowed from the wonderful simple-tracer by matugm
|
45
|
-
arg_names = eval(
|
46
|
-
"method(__method__).parameters.map { |arg| arg[1].to_s }",
|
47
|
-
binding
|
48
|
-
)
|
49
|
-
args = eval("#{arg_names}.map { |arg| eval(arg) }", binding).join(", ")
|
50
|
-
prefix = "-" * (classname.to_s.count(":") - 2) + "> "
|
51
|
-
puts("#{prefix}#{id} #{args}")
|
52
|
-
})
|
53
|
-
# rubocop:enable all
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
class Train::Transports::Mock
|
59
|
-
class Connection < BaseConnection
|
60
|
-
attr_reader :options
|
61
|
-
|
62
|
-
def initialize(conf = nil)
|
63
|
-
super(conf)
|
64
|
-
mock_os
|
65
|
-
enable_cache(:file)
|
66
|
-
enable_cache(:command)
|
67
|
-
end
|
68
|
-
|
69
|
-
def uri
|
70
|
-
"mock://"
|
71
|
-
end
|
72
|
-
|
73
|
-
DEFAULTS = {
|
74
|
-
name: "mock",
|
75
|
-
family: "mock",
|
76
|
-
release: "unknown",
|
77
|
-
arch: "unknown",
|
78
|
-
}.freeze
|
79
|
-
|
80
|
-
def mock_os(value = {})
|
81
|
-
value = DEFAULTS
|
82
|
-
.merge(value || {})
|
83
|
-
.transform_values { |v| v || "unknown" }
|
84
|
-
|
85
|
-
platform = Train::Platforms.name(value[:name])
|
86
|
-
platform.find_family_hierarchy
|
87
|
-
platform.platform = value
|
88
|
-
platform.add_platform_methods
|
89
|
-
@platform = platform
|
90
|
-
end
|
91
|
-
|
92
|
-
def commands=(commands)
|
93
|
-
@cache[:command] = commands
|
94
|
-
end
|
95
|
-
|
96
|
-
def commands
|
97
|
-
@cache[:command]
|
98
|
-
end
|
99
|
-
|
100
|
-
def files=(files)
|
101
|
-
@cache[:file] = files
|
102
|
-
end
|
103
|
-
|
104
|
-
def files
|
105
|
-
@cache[:file]
|
106
|
-
end
|
107
|
-
|
108
|
-
def mock_command(cmd, stdout = nil, stderr = nil, exit_status = 0)
|
109
|
-
@cache[:command][cmd] = Command.new(stdout || "", stderr || "", exit_status)
|
110
|
-
end
|
111
|
-
|
112
|
-
def command_not_found(cmd)
|
113
|
-
if @options[:verbose]
|
114
|
-
$stderr.puts("Command not mocked:")
|
115
|
-
$stderr.puts(" " + cmd.to_s.split("\n").join("\n "))
|
116
|
-
$stderr.puts(" SHA: " + Digest::SHA256.hexdigest(cmd.to_s))
|
117
|
-
end
|
118
|
-
# return a non-zero exit code
|
119
|
-
mock_command(cmd, nil, nil, 1)
|
120
|
-
end
|
121
|
-
|
122
|
-
def file_not_found(path)
|
123
|
-
$stderr.puts("File not mocked: " + path.to_s) if @options[:verbose]
|
124
|
-
File.new(self, path)
|
125
|
-
end
|
126
|
-
|
127
|
-
def to_s
|
128
|
-
"Mock Connection"
|
129
|
-
end
|
130
|
-
|
131
|
-
private
|
132
|
-
|
133
|
-
def run_command_via_connection(cmd, &_data_handler)
|
134
|
-
@cache[:command][Digest::SHA256.hexdigest cmd.to_s] ||
|
135
|
-
command_not_found(cmd)
|
136
|
-
end
|
137
|
-
|
138
|
-
def file_via_connection(path)
|
139
|
-
file_not_found(path)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
class Train::Transports::Mock::Connection
|
145
|
-
Command = Struct.new(:stdout, :stderr, :exit_status)
|
146
|
-
end
|
147
|
-
|
148
|
-
class Train::Transports::Mock::Connection
|
149
|
-
class File < Train::File
|
150
|
-
def self.from_json(json)
|
151
|
-
res = new(json["backend"],
|
152
|
-
json["path"],
|
153
|
-
json["follow_symlink"])
|
154
|
-
res.type = json["type"]
|
155
|
-
Train::File::DATA_FIELDS.each do |f|
|
156
|
-
m = (f.tr("?", "") + "=").to_sym
|
157
|
-
res.method(m).call(json[f])
|
158
|
-
end
|
159
|
-
res
|
160
|
-
end
|
161
|
-
|
162
|
-
Train::File::DATA_FIELDS.each do |m|
|
163
|
-
attr_accessor m.tr("?", "").to_sym
|
164
|
-
next unless m.include?("?")
|
165
|
-
|
166
|
-
define_method m.to_sym do
|
167
|
-
method(m.tr("?", "").to_sym).call
|
168
|
-
end
|
169
|
-
end
|
170
|
-
attr_accessor :type
|
171
|
-
|
172
|
-
def initialize(backend, path, follow_symlink = true)
|
173
|
-
super(backend, path, follow_symlink)
|
174
|
-
@type = :unknown
|
175
|
-
@exist = false
|
176
|
-
end
|
177
|
-
|
178
|
-
def mounted
|
179
|
-
@mounted ||=
|
180
|
-
@backend.run_command("mount | grep -- ' on #{@path}'")
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|