train 2.1.7 → 2.1.13
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
- data/lib/train.rb +20 -20
- data/lib/train/errors.rb +1 -1
- data/lib/train/extras.rb +2 -2
- data/lib/train/extras/command_wrapper.rb +24 -24
- data/lib/train/extras/stat.rb +27 -27
- data/lib/train/file.rb +30 -30
- data/lib/train/file/local.rb +8 -8
- data/lib/train/file/local/unix.rb +5 -5
- data/lib/train/file/local/windows.rb +1 -1
- data/lib/train/file/remote.rb +8 -8
- data/lib/train/file/remote/aix.rb +1 -1
- data/lib/train/file/remote/linux.rb +2 -2
- data/lib/train/file/remote/qnx.rb +8 -8
- data/lib/train/file/remote/unix.rb +10 -14
- data/lib/train/file/remote/windows.rb +5 -5
- data/lib/train/globals.rb +1 -1
- data/lib/train/options.rb +8 -8
- data/lib/train/platforms.rb +8 -8
- data/lib/train/platforms/common.rb +1 -1
- data/lib/train/platforms/detect/helpers/os_common.rb +36 -32
- data/lib/train/platforms/detect/helpers/os_linux.rb +12 -12
- data/lib/train/platforms/detect/helpers/os_windows.rb +27 -29
- data/lib/train/platforms/detect/scanner.rb +4 -4
- data/lib/train/platforms/detect/specifications/api.rb +8 -8
- data/lib/train/platforms/detect/specifications/os.rb +252 -252
- data/lib/train/platforms/detect/uuid.rb +5 -7
- data/lib/train/platforms/platform.rb +9 -5
- data/lib/train/plugin_test_helper.rb +12 -12
- data/lib/train/plugins.rb +5 -5
- data/lib/train/plugins/base_connection.rb +13 -13
- data/lib/train/plugins/transport.rb +7 -7
- data/lib/train/transports/azure.rb +23 -23
- data/lib/train/transports/cisco_ios_connection.rb +20 -20
- data/lib/train/transports/clients/azure/graph_rbac.rb +2 -2
- data/lib/train/transports/clients/azure/vault.rb +4 -4
- data/lib/train/transports/docker.rb +4 -10
- data/lib/train/transports/gcp.rb +23 -23
- data/lib/train/transports/helpers/azure/file_credentials.rb +8 -8
- data/lib/train/transports/helpers/azure/file_parser.rb +1 -1
- data/lib/train/transports/helpers/azure/subscription_number_file_parser.rb +1 -1
- data/lib/train/transports/local.rb +22 -22
- data/lib/train/transports/mock.rb +33 -35
- data/lib/train/transports/ssh.rb +47 -47
- data/lib/train/transports/ssh_connection.rb +28 -28
- data/lib/train/transports/vmware.rb +32 -34
- data/lib/train/transports/winrm.rb +37 -37
- data/lib/train/transports/winrm_connection.rb +12 -12
- data/lib/train/version.rb +1 -1
- metadata +2 -2
data/lib/train/transports/ssh.rb
CHANGED
@@ -18,9 +18,9 @@
|
|
18
18
|
# See the License for the specific language governing permissions and
|
19
19
|
# limitations under the License.
|
20
20
|
|
21
|
-
require
|
22
|
-
require
|
23
|
-
require
|
21
|
+
require "net/ssh"
|
22
|
+
require "net/scp"
|
23
|
+
require "train/errors"
|
24
24
|
|
25
25
|
module Train::Transports
|
26
26
|
# Wrapped exception for any internally raised SSH-related errors.
|
@@ -34,10 +34,10 @@ module Train::Transports
|
|
34
34
|
#
|
35
35
|
# @author Fletcher Nichol <fnichol@nichol.ca>
|
36
36
|
class SSH < Train.plugin(1) # rubocop:disable Metrics/ClassLength
|
37
|
-
name
|
37
|
+
name "ssh"
|
38
38
|
|
39
|
-
require
|
40
|
-
require
|
39
|
+
require "train/transports/ssh_connection"
|
40
|
+
require "train/transports/cisco_ios_connection"
|
41
41
|
|
42
42
|
# add options for submodules
|
43
43
|
include_options Train::Extras::CommandWrapper
|
@@ -45,7 +45,7 @@ module Train::Transports
|
|
45
45
|
# common target configuration
|
46
46
|
option :host, required: true
|
47
47
|
option :port, default: 22, required: true
|
48
|
-
option :user, default:
|
48
|
+
option :user, default: "root", required: true
|
49
49
|
option :key_files, default: nil
|
50
50
|
option :password, default: nil
|
51
51
|
|
@@ -60,7 +60,7 @@ module Train::Transports
|
|
60
60
|
option :pty, default: false
|
61
61
|
option :proxy_command, default: nil
|
62
62
|
option :bastion_host, default: nil
|
63
|
-
option :bastion_user, default:
|
63
|
+
option :bastion_user, default: "root"
|
64
64
|
option :bastion_port, default: 22
|
65
65
|
option :non_interactive, default: false
|
66
66
|
option :verify_host_key, default: false
|
@@ -89,34 +89,34 @@ module Train::Transports
|
|
89
89
|
super(options)
|
90
90
|
|
91
91
|
key_files = Array(options[:key_files])
|
92
|
-
options[:auth_methods] ||= [
|
92
|
+
options[:auth_methods] ||= ["none"]
|
93
93
|
|
94
94
|
unless key_files.empty?
|
95
|
-
options[:auth_methods].push(
|
95
|
+
options[:auth_methods].push("publickey")
|
96
96
|
options[:keys_only] = true if options[:password].nil?
|
97
97
|
options[:key_files] = key_files
|
98
98
|
end
|
99
99
|
|
100
100
|
unless options[:password].nil?
|
101
|
-
options[:auth_methods].push(
|
101
|
+
options[:auth_methods].push("password", "keyboard-interactive")
|
102
102
|
end
|
103
103
|
|
104
|
-
if options[:auth_methods] == [
|
104
|
+
if options[:auth_methods] == ["none"]
|
105
105
|
if ssh_known_identities.empty?
|
106
|
-
|
107
|
-
|
106
|
+
raise Train::ClientError,
|
107
|
+
"Your SSH Agent has no keys added, and you have not specified a password or a key file"
|
108
108
|
else
|
109
|
-
logger.debug(
|
110
|
-
options[:auth_methods].push(
|
109
|
+
logger.debug("[SSH] Using Agent keys as no password or key file have been specified")
|
110
|
+
options[:auth_methods].push("publickey")
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
114
|
if options[:pty]
|
115
|
-
logger.warn(
|
115
|
+
logger.warn("[SSH] PTY requested: stderr will be merged into stdout")
|
116
116
|
end
|
117
117
|
|
118
118
|
if [options[:proxy_command], options[:bastion_host]].all? { |type| !type.nil? }
|
119
|
-
|
119
|
+
raise Train::ClientError, "Only one of proxy_command or bastion_host needs to be specified"
|
120
120
|
end
|
121
121
|
|
122
122
|
super
|
@@ -142,30 +142,30 @@ module Train::Transports
|
|
142
142
|
# @api private
|
143
143
|
def connection_options(opts)
|
144
144
|
connection_options = {
|
145
|
-
logger:
|
146
|
-
user_known_hosts_file:
|
147
|
-
hostname:
|
148
|
-
port:
|
149
|
-
username:
|
150
|
-
compression:
|
151
|
-
compression_level:
|
152
|
-
keepalive:
|
153
|
-
keepalive_interval:
|
154
|
-
timeout:
|
155
|
-
connection_retries:
|
145
|
+
logger: logger,
|
146
|
+
user_known_hosts_file: "/dev/null",
|
147
|
+
hostname: opts[:host],
|
148
|
+
port: opts[:port],
|
149
|
+
username: opts[:user],
|
150
|
+
compression: opts[:compression],
|
151
|
+
compression_level: opts[:compression_level],
|
152
|
+
keepalive: opts[:keepalive],
|
153
|
+
keepalive_interval: opts[:keepalive_interval],
|
154
|
+
timeout: opts[:connection_timeout],
|
155
|
+
connection_retries: opts[:connection_retries],
|
156
156
|
connection_retry_sleep: opts[:connection_retry_sleep],
|
157
|
-
max_wait_until_ready:
|
158
|
-
auth_methods:
|
159
|
-
keys_only:
|
160
|
-
keys:
|
161
|
-
password:
|
162
|
-
forward_agent:
|
163
|
-
proxy_command:
|
164
|
-
bastion_host:
|
165
|
-
bastion_user:
|
166
|
-
bastion_port:
|
167
|
-
non_interactive:
|
168
|
-
transport_options:
|
157
|
+
max_wait_until_ready: opts[:max_wait_until_ready],
|
158
|
+
auth_methods: opts[:auth_methods],
|
159
|
+
keys_only: opts[:keys_only],
|
160
|
+
keys: opts[:key_files],
|
161
|
+
password: opts[:password],
|
162
|
+
forward_agent: opts[:forward_agent],
|
163
|
+
proxy_command: opts[:proxy_command],
|
164
|
+
bastion_host: opts[:bastion_host],
|
165
|
+
bastion_user: opts[:bastion_user],
|
166
|
+
bastion_port: opts[:bastion_port],
|
167
|
+
non_interactive: opts[:non_interactive],
|
168
|
+
transport_options: opts,
|
169
169
|
}
|
170
170
|
# disable host key verification. The hash key and value to use
|
171
171
|
# depends on the version of net-ssh in use.
|
@@ -203,20 +203,20 @@ module Train::Transports
|
|
203
203
|
# 5.0+ style
|
204
204
|
{
|
205
205
|
# It's not a boolean anymore.
|
206
|
-
|
207
|
-
|
206
|
+
"true" => :always,
|
207
|
+
"false" => :never,
|
208
208
|
true => :always,
|
209
209
|
false => :never,
|
210
210
|
# May be correct value, but strings from JSON config
|
211
|
-
|
212
|
-
|
211
|
+
"always" => :always,
|
212
|
+
"never" => :never,
|
213
213
|
nil => :never,
|
214
214
|
}.fetch(given, given)
|
215
215
|
else
|
216
216
|
# up to 4.2 style
|
217
217
|
{
|
218
|
-
|
219
|
-
|
218
|
+
"true" => true,
|
219
|
+
"false" => false,
|
220
220
|
nil => false,
|
221
221
|
}.fetch(given, given)
|
222
222
|
end
|
@@ -18,9 +18,9 @@
|
|
18
18
|
# See the License for the specific language governing permissions and
|
19
19
|
# limitations under the License.
|
20
20
|
|
21
|
-
require
|
22
|
-
require
|
23
|
-
require
|
21
|
+
require "net/ssh"
|
22
|
+
require "net/scp"
|
23
|
+
require "timeout"
|
24
24
|
|
25
25
|
class Train::Transports::SSH
|
26
26
|
# A Connection instance can be generated and re-generated, given new
|
@@ -63,17 +63,17 @@ class Train::Transports::SSH
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def ssh_opts
|
66
|
-
level = logger.debug? ?
|
67
|
-
fwd_agent = options[:forward_agent] ?
|
66
|
+
level = logger.debug? ? "VERBOSE" : "ERROR"
|
67
|
+
fwd_agent = options[:forward_agent] ? "yes" : "no"
|
68
68
|
|
69
69
|
args = %w{ -o UserKnownHostsFile=/dev/null }
|
70
70
|
args += %w{ -o StrictHostKeyChecking=no }
|
71
71
|
args += %w{ -o IdentitiesOnly=yes } if options[:keys]
|
72
72
|
args += %w{ -o BatchMode=yes } if options[:non_interactive]
|
73
|
-
args += %W
|
74
|
-
args += %W
|
73
|
+
args += %W{ -o LogLevel=#{level} }
|
74
|
+
args += %W{ -o ForwardAgent=#{fwd_agent} } if options.key?(:forward_agent)
|
75
75
|
Array(options[:keys]).each do |ssh_key|
|
76
|
-
args += %W
|
76
|
+
args += %W{ -i #{ssh_key} }
|
77
77
|
end
|
78
78
|
args
|
79
79
|
end
|
@@ -86,19 +86,19 @@ class Train::Transports::SSH
|
|
86
86
|
return @proxy_command unless @proxy_command.nil?
|
87
87
|
args = %w{ ssh }
|
88
88
|
args += ssh_opts
|
89
|
-
args += %W
|
90
|
-
args += %W
|
89
|
+
args += %W{ #{@bastion_user}@#{@bastion_host} }
|
90
|
+
args += %W{ -p #{@bastion_port} }
|
91
91
|
args += %w{ -W %h:%p }
|
92
|
-
args.join(
|
92
|
+
args.join(" ")
|
93
93
|
end
|
94
94
|
|
95
95
|
# (see Base::Connection#login_command)
|
96
96
|
def login_command
|
97
97
|
args = ssh_opts
|
98
|
-
args += %W
|
99
|
-
args += %W
|
100
|
-
args += %W
|
101
|
-
LoginCommand.new(
|
98
|
+
args += %W{ -o ProxyCommand='#{generate_proxy_command}' } if check_proxy
|
99
|
+
args += %W{ -p #{@port} }
|
100
|
+
args += %W{ #{@username}@#{@hostname} }
|
101
|
+
LoginCommand.new("ssh", args)
|
102
102
|
end
|
103
103
|
|
104
104
|
# (see Base::Connection#upload)
|
@@ -138,7 +138,7 @@ class Train::Transports::SSH
|
|
138
138
|
retries: @max_wait_until_ready / delay,
|
139
139
|
delay: delay,
|
140
140
|
message: "Waiting for SSH service on #{@hostname}:#{@port}, " \
|
141
|
-
"retrying in #{delay} seconds"
|
141
|
+
"retrying in #{delay} seconds"
|
142
142
|
)
|
143
143
|
run_command(PING_COMMAND.dup)
|
144
144
|
end
|
@@ -172,14 +172,14 @@ class Train::Transports::SSH
|
|
172
172
|
def establish_connection(opts)
|
173
173
|
logger.debug("[SSH] opening connection to #{self}")
|
174
174
|
if check_proxy
|
175
|
-
require
|
175
|
+
require "net/ssh/proxy/command"
|
176
176
|
@options[:proxy] = Net::SSH::Proxy::Command.new(generate_proxy_command)
|
177
177
|
end
|
178
178
|
Net::SSH.start(@hostname, @username, @options.clone.delete_if { |_key, value| value.nil? })
|
179
179
|
rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH => e
|
180
180
|
if (opts[:retries] -= 1) <= 0
|
181
181
|
logger.warn("[SSH] connection failed, terminating (#{e.inspect})")
|
182
|
-
raise Train::Transports::SSHFailed,
|
182
|
+
raise Train::Transports::SSHFailed, "SSH session could not be established"
|
183
183
|
end
|
184
184
|
|
185
185
|
if opts[:message]
|
@@ -200,7 +200,7 @@ class Train::Transports::SSH
|
|
200
200
|
Train::File::Remote::Aix.new(self, path)
|
201
201
|
elsif os.solaris?
|
202
202
|
Train::File::Remote::Unix.new(self, path)
|
203
|
-
elsif os[:name] ==
|
203
|
+
elsif os[:name] == "qnx"
|
204
204
|
Train::File::Remote::Qnx.new(self, path)
|
205
205
|
elsif os.windows?
|
206
206
|
Train::File::Remote::Windows.new(self, path)
|
@@ -210,7 +210,7 @@ class Train::Transports::SSH
|
|
210
210
|
end
|
211
211
|
|
212
212
|
def run_command_via_connection(cmd, &data_handler)
|
213
|
-
cmd.dup.force_encoding(
|
213
|
+
cmd.dup.force_encoding("binary") if cmd.respond_to?(:force_encoding)
|
214
214
|
logger.debug("[SSH] #{self} (#{cmd})")
|
215
215
|
|
216
216
|
reset_session if session.closed?
|
@@ -228,7 +228,7 @@ class Train::Transports::SSH
|
|
228
228
|
# transport. This retries the command if this is the case.
|
229
229
|
# See:
|
230
230
|
# https://github.com/inspec/train/pull/271
|
231
|
-
logger.debug(
|
231
|
+
logger.debug("[SSH] Possible Cisco IOS race condition, retrying command")
|
232
232
|
|
233
233
|
# Only attempt retry up to 5 times to avoid infinite loop
|
234
234
|
@ios_cmd_retries += 1
|
@@ -246,7 +246,7 @@ class Train::Transports::SSH
|
|
246
246
|
def session(retry_options = {})
|
247
247
|
@session ||= establish_connection({
|
248
248
|
retries: @connection_retries.to_i,
|
249
|
-
delay:
|
249
|
+
delay: @connection_retry_sleep.to_i,
|
250
250
|
}.merge(retry_options))
|
251
251
|
end
|
252
252
|
|
@@ -260,7 +260,7 @@ class Train::Transports::SSH
|
|
260
260
|
# @api private
|
261
261
|
def to_s
|
262
262
|
options_to_print = @options.clone
|
263
|
-
options_to_print[:password] =
|
263
|
+
options_to_print[:password] = "<hidden>" if options_to_print.key?(:password)
|
264
264
|
"#{@username}@#{@hostname}<#{options_to_print.inspect}>"
|
265
265
|
end
|
266
266
|
|
@@ -274,7 +274,7 @@ class Train::Transports::SSH
|
|
274
274
|
#
|
275
275
|
# @api private
|
276
276
|
def execute_on_channel(cmd, &data_handler)
|
277
|
-
stdout = stderr =
|
277
|
+
stdout = stderr = ""
|
278
278
|
exit_status = nil
|
279
279
|
session.open_channel do |channel|
|
280
280
|
# wrap commands if that is configured
|
@@ -282,11 +282,11 @@ class Train::Transports::SSH
|
|
282
282
|
|
283
283
|
if @transport_options[:pty]
|
284
284
|
channel.request_pty do |_ch, success|
|
285
|
-
|
285
|
+
raise Train::Transports::SSHPTYFailed, "Requesting PTY failed" unless success
|
286
286
|
end
|
287
287
|
end
|
288
288
|
channel.exec(cmd) do |_, success|
|
289
|
-
abort
|
289
|
+
abort "Couldn't execute command on SSH." unless success
|
290
290
|
channel.on_data do |_, data|
|
291
291
|
yield(data) unless data_handler.nil?
|
292
292
|
stdout += data
|
@@ -297,11 +297,11 @@ class Train::Transports::SSH
|
|
297
297
|
stderr += data
|
298
298
|
end
|
299
299
|
|
300
|
-
channel.on_request(
|
300
|
+
channel.on_request("exit-status") do |_, data|
|
301
301
|
exit_status = data.read_long
|
302
302
|
end
|
303
303
|
|
304
|
-
channel.on_request(
|
304
|
+
channel.on_request("exit-signal") do |_, data|
|
305
305
|
exit_status = data.read_long
|
306
306
|
end
|
307
307
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
2
|
+
require "train/plugins"
|
3
|
+
require "open3"
|
4
|
+
require "ostruct"
|
5
|
+
require "json"
|
6
|
+
require "mkmf"
|
7
7
|
|
8
8
|
module Train::Transports
|
9
9
|
class VMware < Train.plugin(1)
|
10
|
-
name
|
11
|
-
option :viserver, default: ENV[
|
12
|
-
option :username, default: ENV[
|
13
|
-
option :password, default: ENV[
|
10
|
+
name "vmware"
|
11
|
+
option :viserver, default: proc { ENV["VISERVER"] }
|
12
|
+
option :username, default: proc { ENV["VISERVER_USERNAME"] }
|
13
|
+
option :password, default: proc { ENV["VISERVER_PASSWORD"] }
|
14
14
|
option :insecure, default: false
|
15
15
|
|
16
16
|
def connection(_ = nil)
|
@@ -18,7 +18,7 @@ module Train::Transports
|
|
18
18
|
end
|
19
19
|
|
20
20
|
class Connection < BaseConnection # rubocop:disable ClassLength
|
21
|
-
POWERSHELL_PROMPT_REGEX = /PS\s.*>
|
21
|
+
POWERSHELL_PROMPT_REGEX = /PS\s.*> $/.freeze
|
22
22
|
|
23
23
|
def initialize(options)
|
24
24
|
super(options)
|
@@ -29,18 +29,18 @@ module Train::Transports
|
|
29
29
|
@username = options[:username]
|
30
30
|
@viserver = options[:viserver]
|
31
31
|
@session = nil
|
32
|
-
@stdout_buffer =
|
33
|
-
@stderr_buffer =
|
32
|
+
@stdout_buffer = ""
|
33
|
+
@stderr_buffer = ""
|
34
34
|
|
35
35
|
@powershell_binary = detect_powershell_binary
|
36
36
|
|
37
37
|
if @powershell_binary == :powershell
|
38
|
-
require
|
38
|
+
require "train/transports/local"
|
39
39
|
@powershell = Train::Transports::Local::Connection.new(options)
|
40
40
|
end
|
41
41
|
|
42
42
|
if options[:insecure] == true
|
43
|
-
run_command_via_connection(
|
43
|
+
run_command_via_connection("Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$False")
|
44
44
|
end
|
45
45
|
|
46
46
|
@platform_details = {
|
@@ -58,11 +58,11 @@ module Train::Transports
|
|
58
58
|
message = "Unable to connect to VIServer at #{options[:viserver]}. "
|
59
59
|
case result.stderr
|
60
60
|
when /Invalid server certificate/
|
61
|
-
message +=
|
61
|
+
message += "Certification verification failed. Please use `--insecure` or set `Set-PowerCLIConfiguration -InvalidCertificateAction Ignore` in PowerShell"
|
62
62
|
when /incorrect user name or password/
|
63
|
-
message +=
|
63
|
+
message += "Incorrect username or password"
|
64
64
|
else
|
65
|
-
message += result.stderr.gsub(/-Password .*\s/,
|
65
|
+
message += result.stderr.gsub(/-Password .*\s/, "-Password REDACTED")
|
66
66
|
end
|
67
67
|
|
68
68
|
raise message
|
@@ -70,7 +70,7 @@ module Train::Transports
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def platform
|
73
|
-
force_platform!(
|
73
|
+
force_platform!("vmware", @platform_details)
|
74
74
|
end
|
75
75
|
|
76
76
|
def run_command_via_connection(cmd, &_data_handler)
|
@@ -78,8 +78,8 @@ module Train::Transports
|
|
78
78
|
result = parse_pwsh_output(cmd)
|
79
79
|
|
80
80
|
# Attach exit status to result
|
81
|
-
exit_status = parse_pwsh_output(
|
82
|
-
result.exit_status = exit_status ==
|
81
|
+
exit_status = parse_pwsh_output("echo $?").stdout.chomp
|
82
|
+
result.exit_status = exit_status == "True" ? 0 : 1
|
83
83
|
|
84
84
|
result
|
85
85
|
else
|
@@ -88,7 +88,7 @@ module Train::Transports
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def unique_identifier
|
91
|
-
uuid_command =
|
91
|
+
uuid_command = "(Get-VMHost | Get-View).hardware.systeminfo.uuid"
|
92
92
|
run_command_via_connection(uuid_command).stdout.chomp
|
93
93
|
end
|
94
94
|
|
@@ -99,26 +99,24 @@ module Train::Transports
|
|
99
99
|
private
|
100
100
|
|
101
101
|
def detect_powershell_binary
|
102
|
-
if find_executable0(
|
102
|
+
if find_executable0("pwsh")
|
103
103
|
:pwsh
|
104
|
-
elsif find_executable0(
|
104
|
+
elsif find_executable0("powershell")
|
105
105
|
:powershell
|
106
106
|
else
|
107
|
-
raise
|
107
|
+
raise "Cannot find PowerShell binary, is `pwsh` installed?"
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
111
|
# Read from stdout pipe until prompt is received
|
112
112
|
def flush_stdout(pipe)
|
113
|
-
while @stdout_buffer !~ POWERSHELL_PROMPT_REGEX
|
114
|
-
@stdout_buffer += pipe.read_nonblock(1)
|
115
|
-
end
|
113
|
+
@stdout_buffer += pipe.read_nonblock(1) while @stdout_buffer !~ POWERSHELL_PROMPT_REGEX
|
116
114
|
@stdout_buffer
|
117
115
|
rescue IO::EAGAINWaitReadable
|
118
116
|
# We cannot know when the stdout pipe is finished so we keep reading
|
119
117
|
retry
|
120
118
|
ensure
|
121
|
-
@stdout_buffer =
|
119
|
+
@stdout_buffer = ""
|
122
120
|
end
|
123
121
|
|
124
122
|
# This must be called after `flush_stdout` to ensure buffer is full
|
@@ -132,7 +130,7 @@ module Train::Transports
|
|
132
130
|
# is unreadable.
|
133
131
|
@stderr_buffer
|
134
132
|
ensure
|
135
|
-
@stderr_buffer =
|
133
|
+
@stderr_buffer = ""
|
136
134
|
end
|
137
135
|
|
138
136
|
def parse_pwsh_output(cmd)
|
@@ -141,10 +139,10 @@ module Train::Transports
|
|
141
139
|
stdout = flush_stdout(session.stdout)
|
142
140
|
|
143
141
|
# Remove stdin from stdout (including trailing newline)
|
144
|
-
stdout.slice!(0, cmd.length+1)
|
142
|
+
stdout.slice!(0, cmd.length + 1)
|
145
143
|
|
146
144
|
# Remove prompt from stdout
|
147
|
-
stdout.gsub!(POWERSHELL_PROMPT_REGEX,
|
145
|
+
stdout.gsub!(POWERSHELL_PROMPT_REGEX, "")
|
148
146
|
|
149
147
|
# Grab stderr
|
150
148
|
stderr = flush_stderr(session.stderr)
|
@@ -157,10 +155,10 @@ module Train::Transports
|
|
157
155
|
end
|
158
156
|
|
159
157
|
def powercli_version
|
160
|
-
version_command =
|
158
|
+
version_command = "[string](Get-Module -Name VMware.PowerCLI -ListAvailable | Select -ExpandProperty Version)"
|
161
159
|
result = run_command_via_connection(version_command)
|
162
160
|
if result.stdout.empty? || result.exit_status != 0
|
163
|
-
raise
|
161
|
+
raise "Unable to determine PowerCLI Module version, is it installed?"
|
164
162
|
end
|
165
163
|
|
166
164
|
result.stdout.chomp
|
@@ -169,7 +167,7 @@ module Train::Transports
|
|
169
167
|
def session
|
170
168
|
return @session unless @session.nil?
|
171
169
|
|
172
|
-
stdin, stdout, stderr = Open3.popen3(
|
170
|
+
stdin, stdout, stderr = Open3.popen3("pwsh")
|
173
171
|
|
174
172
|
# Remove leading prompt and intro text
|
175
173
|
flush_stdout(stdout)
|