train 3.2.14 → 3.2.20
Sign up to get free protection for your applications and to get access to all the features.
- 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
|