bolt 0.16.1 → 0.16.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/bolt.rb +0 -1
- data/lib/bolt/cli.rb +12 -6
- data/lib/bolt/error.rb +8 -0
- data/lib/bolt/executor.rb +99 -113
- data/lib/bolt/inventory.rb +47 -2
- data/lib/bolt/inventory/group.rb +37 -6
- data/lib/bolt/pal.rb +28 -9
- data/lib/bolt/target.rb +0 -8
- data/lib/bolt/transport/base.rb +159 -0
- data/lib/bolt/transport/orch.rb +158 -0
- data/lib/bolt/transport/ssh.rb +135 -0
- data/lib/bolt/transport/ssh/connection.rb +278 -0
- data/lib/bolt/transport/winrm.rb +165 -0
- data/lib/bolt/transport/winrm/connection.rb +472 -0
- data/lib/bolt/version.rb +1 -1
- data/modules/boltlib/lib/puppet/datatypes/target.rb +5 -0
- data/modules/boltlib/lib/puppet/functions/fail_plan.rb +1 -1
- data/modules/boltlib/lib/puppet/functions/file_upload.rb +1 -5
- data/modules/boltlib/lib/puppet/functions/get_targets.rb +41 -0
- data/modules/boltlib/lib/puppet/functions/run_command.rb +2 -6
- data/modules/boltlib/lib/puppet/functions/run_plan.rb +4 -1
- data/modules/boltlib/lib/puppet/functions/run_script.rb +2 -6
- data/modules/boltlib/lib/puppet/functions/run_task.rb +15 -17
- data/modules/boltlib/types/targetspec.pp +7 -0
- metadata +10 -6
- data/lib/bolt/node.rb +0 -76
- data/lib/bolt/node/orch.rb +0 -126
- data/lib/bolt/node/ssh.rb +0 -356
- data/lib/bolt/node/winrm.rb +0 -598
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'bolt/node/errors'
|
2
|
+
require 'bolt/transport/base'
|
3
|
+
require 'json'
|
4
|
+
require 'shellwords'
|
5
|
+
|
6
|
+
module Bolt
|
7
|
+
module Transport
|
8
|
+
class SSH < Base
|
9
|
+
STDIN_METHODS = %w[both stdin].freeze
|
10
|
+
ENVIRONMENT_METHODS = %w[both environment].freeze
|
11
|
+
|
12
|
+
def initialize(_config)
|
13
|
+
super
|
14
|
+
|
15
|
+
require 'net/ssh'
|
16
|
+
require 'net/scp'
|
17
|
+
begin
|
18
|
+
require 'net/ssh/krb'
|
19
|
+
rescue LoadError
|
20
|
+
logger.debug {
|
21
|
+
"Authentication method 'gssapi-with-mic' is not available"
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def with_connection(target)
|
27
|
+
conn = Connection.new(target)
|
28
|
+
conn.connect
|
29
|
+
yield conn
|
30
|
+
ensure
|
31
|
+
begin
|
32
|
+
conn.disconnect if conn
|
33
|
+
rescue StandardError => ex
|
34
|
+
logger.info("Failed to close connection to #{target.uri} : #{ex.message}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def upload(target, source, destination, options = {})
|
39
|
+
with_connection(target) do |conn|
|
40
|
+
conn.running_as(options['_run_as']) do
|
41
|
+
conn.with_remote_tempdir do |dir|
|
42
|
+
basename = File.basename(destination)
|
43
|
+
tmpfile = "#{dir}/#{basename}"
|
44
|
+
conn.write_remote_file(source, tmpfile)
|
45
|
+
# pass over file ownership if we're using run-as to be a different user
|
46
|
+
dir.chown(conn.run_as)
|
47
|
+
result = conn.execute("mv '#{tmpfile}' '#{destination}'", sudoable: true)
|
48
|
+
if result.exit_code != 0
|
49
|
+
message = "Could not move temporary file '#{tmpfile}' to #{destination}: #{result.stderr.string}"
|
50
|
+
raise Bolt::Node::FileError.new(message, 'MV_ERROR')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
Bolt::Result.for_upload(target, source, destination)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def run_command(target, command, options = {})
|
59
|
+
with_connection(target) do |conn|
|
60
|
+
conn.running_as(options['_run_as']) do
|
61
|
+
output = conn.execute(command, sudoable: true)
|
62
|
+
Bolt::Result.for_command(target, output.stdout.string, output.stderr.string, output.exit_code)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def run_script(target, script, arguments, options = {})
|
68
|
+
with_connection(target) do |conn|
|
69
|
+
conn.running_as(options['_run_as']) do
|
70
|
+
conn.with_remote_tempdir do |dir|
|
71
|
+
remote_path = conn.write_remote_executable(dir, script)
|
72
|
+
dir.chown(conn.run_as)
|
73
|
+
output = conn.execute("'#{remote_path}' #{Shellwords.join(arguments)}", sudoable: true)
|
74
|
+
Bolt::Result.for_command(target, output.stdout.string, output.stderr.string, output.exit_code)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def run_task(target, task, arguments, options = {})
|
81
|
+
input_method = task.input_method
|
82
|
+
with_connection(target) do |conn|
|
83
|
+
conn.running_as(options['_run_as']) do
|
84
|
+
export_args = {}
|
85
|
+
stdin, output = nil
|
86
|
+
|
87
|
+
if STDIN_METHODS.include?(input_method)
|
88
|
+
stdin = JSON.dump(arguments)
|
89
|
+
end
|
90
|
+
|
91
|
+
if ENVIRONMENT_METHODS.include?(input_method)
|
92
|
+
export_args = arguments.map do |env, val|
|
93
|
+
"PT_#{env}='#{val}'"
|
94
|
+
end.join(' ')
|
95
|
+
end
|
96
|
+
|
97
|
+
command = export_args.empty? ? '' : "#{export_args} "
|
98
|
+
|
99
|
+
execute_options = {}
|
100
|
+
|
101
|
+
conn.with_remote_tempdir do |dir|
|
102
|
+
remote_task_path = conn.write_remote_executable(dir, task.executable)
|
103
|
+
if conn.run_as && stdin
|
104
|
+
wrapper = make_wrapper_stringio(remote_task_path, stdin)
|
105
|
+
remote_wrapper_path = conn.write_remote_executable(dir, wrapper, 'wrapper.sh')
|
106
|
+
command += "'#{remote_wrapper_path}'"
|
107
|
+
else
|
108
|
+
command += "'#{remote_task_path}'"
|
109
|
+
execute_options[:stdin] = stdin
|
110
|
+
end
|
111
|
+
dir.chown(conn.run_as)
|
112
|
+
|
113
|
+
execute_options[:sudoable] = true if conn.run_as
|
114
|
+
output = conn.execute(command, **execute_options)
|
115
|
+
end
|
116
|
+
Bolt::Result.for_task(target, output.stdout.string,
|
117
|
+
output.stderr.string,
|
118
|
+
output.exit_code)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def make_wrapper_stringio(task_path, stdin)
|
124
|
+
StringIO.new(<<-SCRIPT)
|
125
|
+
#!/bin/sh
|
126
|
+
'#{task_path}' <<EOF
|
127
|
+
#{stdin}
|
128
|
+
EOF
|
129
|
+
SCRIPT
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
require 'bolt/transport/ssh/connection'
|
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'logging'
|
2
|
+
require 'bolt/node/errors'
|
3
|
+
require 'bolt/node/output'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
module Transport
|
7
|
+
class SSH < Base
|
8
|
+
class Connection
|
9
|
+
class RemoteTempdir
|
10
|
+
def initialize(node, path)
|
11
|
+
@node = node
|
12
|
+
@owner = node.user
|
13
|
+
@path = path
|
14
|
+
@logger = node.logger
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@path
|
19
|
+
end
|
20
|
+
|
21
|
+
def chown(owner)
|
22
|
+
return if owner.nil? || owner == @owner
|
23
|
+
|
24
|
+
@owner = owner
|
25
|
+
result = @node.execute("chown -R '#{@owner}': '#{@path}'", sudoable: true, run_as: 'root')
|
26
|
+
if result.exit_code != 0
|
27
|
+
message = "Could not change owner of '#{@path}' to #{@owner}: #{result.stderr.string}"
|
28
|
+
raise Bolt::Node::FileError.new(message, 'CHOWN_ERROR')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete
|
33
|
+
result = @node.execute("rm -rf '#{@path}'", sudoable: true, run_as: @owner)
|
34
|
+
if result.exit_code != 0
|
35
|
+
@logger.warn("Failed to clean up tempdir '#{@path}': #{result.stderr.string}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :logger, :user, :target
|
41
|
+
attr_writer :run_as
|
42
|
+
|
43
|
+
def initialize(target)
|
44
|
+
@target = target
|
45
|
+
|
46
|
+
@user = @target.user || Net::SSH::Config.for(target.host)[:user] || Etc.getlogin
|
47
|
+
@run_as = nil
|
48
|
+
|
49
|
+
@logger = Logging.logger[@target.host]
|
50
|
+
end
|
51
|
+
|
52
|
+
if !!File::ALT_SEPARATOR
|
53
|
+
require 'ffi'
|
54
|
+
module Win
|
55
|
+
extend FFI::Library
|
56
|
+
ffi_lib 'user32'
|
57
|
+
ffi_convention :stdcall
|
58
|
+
attach_function :FindWindow, :FindWindowW, %i[buffer_in buffer_in], :int
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def connect
|
63
|
+
transport_logger = Logging.logger[Net::SSH]
|
64
|
+
transport_logger.level = :warn
|
65
|
+
options = {
|
66
|
+
logger: transport_logger,
|
67
|
+
non_interactive: true
|
68
|
+
}
|
69
|
+
|
70
|
+
options[:port] = target.port if target.port
|
71
|
+
options[:password] = target.password if target.password
|
72
|
+
options[:keys] = target.options[:key] if target.options[:key]
|
73
|
+
options[:verify_host_key] = if target.options[:host_key_check]
|
74
|
+
Net::SSH::Verifiers::Secure.new
|
75
|
+
else
|
76
|
+
Net::SSH::Verifiers::Lenient.new
|
77
|
+
end
|
78
|
+
options[:timeout] = target.options[:connect_timeout] if target.options[:connect_timeout]
|
79
|
+
|
80
|
+
# Mirroring:
|
81
|
+
# https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh/authentication/agent.rb#L80
|
82
|
+
# https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh/authentication/pageant.rb#L403
|
83
|
+
if defined?(UNIXSocket) && UNIXSocket
|
84
|
+
if ENV['SSH_AUTH_SOCK'].to_s.empty?
|
85
|
+
@logger.debug { "Disabling use_agent in net-ssh: ssh-agent is not available" }
|
86
|
+
options[:use_agent] = false
|
87
|
+
end
|
88
|
+
elsif !!File::ALT_SEPARATOR
|
89
|
+
pageant_wide = 'Pageant'.encode('UTF-16LE')
|
90
|
+
if Win.FindWindow(pageant_wide, pageant_wide).to_i == 0
|
91
|
+
@logger.debug { "Disabling use_agent in net-ssh: pageant process not running" }
|
92
|
+
options[:use_agent] = false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
@session = Net::SSH.start(target.host, @user, options)
|
97
|
+
@logger.debug { "Opened session" }
|
98
|
+
rescue Net::SSH::AuthenticationFailed => e
|
99
|
+
raise Bolt::Node::ConnectError.new(
|
100
|
+
e.message,
|
101
|
+
'AUTH_ERROR'
|
102
|
+
)
|
103
|
+
rescue Net::SSH::HostKeyError => e
|
104
|
+
raise Bolt::Node::ConnectError.new(
|
105
|
+
"Host key verification failed for #{target.uri}: #{e.message}",
|
106
|
+
'HOST_KEY_ERROR'
|
107
|
+
)
|
108
|
+
rescue Net::SSH::ConnectionTimeout
|
109
|
+
raise Bolt::Node::ConnectError.new(
|
110
|
+
"Timeout after #{target.options[:connect_timeout]} seconds connecting to #{target.uri}",
|
111
|
+
'CONNECT_ERROR'
|
112
|
+
)
|
113
|
+
rescue StandardError => e
|
114
|
+
raise Bolt::Node::ConnectError.new(
|
115
|
+
"Failed to connect to #{target.uri}: #{e.message}",
|
116
|
+
'CONNECT_ERROR'
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
def disconnect
|
121
|
+
if @session && !@session.closed?
|
122
|
+
@session.close
|
123
|
+
@logger.debug { "Closed session" }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# This method allows the @run_as variable to be used as a per-operation
|
128
|
+
# override for the user to run as. When @run_as is unset, the user
|
129
|
+
# specified on the target will be used.
|
130
|
+
def run_as
|
131
|
+
@run_as || target.options[:run_as]
|
132
|
+
end
|
133
|
+
|
134
|
+
# Run as the specified user for the duration of the block.
|
135
|
+
def running_as(user)
|
136
|
+
@run_as = user
|
137
|
+
yield
|
138
|
+
ensure
|
139
|
+
@run_as = nil
|
140
|
+
end
|
141
|
+
|
142
|
+
def sudo_prompt
|
143
|
+
'[sudo] Bolt needs to run as another user, password: '
|
144
|
+
end
|
145
|
+
|
146
|
+
def handled_sudo(channel, data)
|
147
|
+
if data.lines.include?(sudo_prompt)
|
148
|
+
if target.options[:sudo_password]
|
149
|
+
channel.send_data "#{target.options[:sudo_password]}\n"
|
150
|
+
channel.wait
|
151
|
+
return true
|
152
|
+
else
|
153
|
+
raise Bolt::Node::EscalateError.new(
|
154
|
+
"Sudo password for user #{@user} was not provided for #{target.uri}",
|
155
|
+
'NO_PASSWORD'
|
156
|
+
)
|
157
|
+
end
|
158
|
+
elsif data =~ /^#{@user} is not in the sudoers file\./
|
159
|
+
@logger.debug { data }
|
160
|
+
raise Bolt::Node::EscalateError.new(
|
161
|
+
"User #{@user} does not have sudo permission on #{target.uri}",
|
162
|
+
'SUDO_DENIED'
|
163
|
+
)
|
164
|
+
elsif data =~ /^Sorry, try again\./
|
165
|
+
@logger.debug { data }
|
166
|
+
raise Bolt::Node::EscalateError.new(
|
167
|
+
"Sudo password for user #{@user} not recognized on #{target.uri}",
|
168
|
+
'BAD_PASSWORD'
|
169
|
+
)
|
170
|
+
end
|
171
|
+
false
|
172
|
+
end
|
173
|
+
|
174
|
+
def execute(command, sudoable: false, **options)
|
175
|
+
result_output = Bolt::Node::Output.new
|
176
|
+
run_as = options[:run_as] || self.run_as
|
177
|
+
use_sudo = sudoable && run_as && @user != run_as
|
178
|
+
if use_sudo
|
179
|
+
command = "sudo -S -u #{run_as} -p '#{sudo_prompt}' #{command}"
|
180
|
+
end
|
181
|
+
|
182
|
+
@logger.debug { "Executing: #{command}" }
|
183
|
+
|
184
|
+
session_channel = @session.open_channel do |channel|
|
185
|
+
# Request a pseudo tty
|
186
|
+
channel.request_pty if target.options[:tty]
|
187
|
+
|
188
|
+
channel.exec(command) do |_, success|
|
189
|
+
unless success
|
190
|
+
raise Bolt::Node::ConnectError.new(
|
191
|
+
"Could not execute command: #{command.inspect}",
|
192
|
+
'EXEC_ERROR'
|
193
|
+
)
|
194
|
+
end
|
195
|
+
|
196
|
+
channel.on_data do |_, data|
|
197
|
+
unless use_sudo && handled_sudo(channel, data)
|
198
|
+
result_output.stdout << data
|
199
|
+
end
|
200
|
+
@logger.debug { "stdout: #{data}" }
|
201
|
+
end
|
202
|
+
|
203
|
+
channel.on_extended_data do |_, _, data|
|
204
|
+
unless use_sudo && handled_sudo(channel, data)
|
205
|
+
result_output.stderr << data
|
206
|
+
end
|
207
|
+
@logger.debug { "stderr: #{data}" }
|
208
|
+
end
|
209
|
+
|
210
|
+
channel.on_request("exit-status") do |_, data|
|
211
|
+
result_output.exit_code = data.read_long
|
212
|
+
end
|
213
|
+
|
214
|
+
if options[:stdin]
|
215
|
+
channel.send_data(options[:stdin])
|
216
|
+
channel.eof!
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
session_channel.wait
|
221
|
+
|
222
|
+
if result_output.exit_code == 0
|
223
|
+
@logger.debug { "Command returned successfully" }
|
224
|
+
else
|
225
|
+
@logger.info { "Command failed with exit code #{result_output.exit_code}" }
|
226
|
+
end
|
227
|
+
result_output
|
228
|
+
end
|
229
|
+
|
230
|
+
def write_remote_file(source, destination)
|
231
|
+
@session.scp.upload!(source, destination)
|
232
|
+
rescue StandardError => e
|
233
|
+
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
234
|
+
end
|
235
|
+
|
236
|
+
def make_tempdir
|
237
|
+
if target.options[:tmpdir]
|
238
|
+
tmppath = "#{target.options[:tmpdir]}/#{SecureRandom.uuid}"
|
239
|
+
command = "mkdir -m 700 #{tmppath}"
|
240
|
+
else
|
241
|
+
command = 'mktemp -d'
|
242
|
+
end
|
243
|
+
result = execute(command)
|
244
|
+
if result.exit_code != 0
|
245
|
+
raise Bolt::Node::FileError.new("Could not make tempdir: #{result.stderr.string}", 'TEMPDIR_ERROR')
|
246
|
+
end
|
247
|
+
path = tmppath || result.stdout.string.chomp
|
248
|
+
RemoteTempdir.new(self, path)
|
249
|
+
end
|
250
|
+
|
251
|
+
# A helper to create and delete a tempdir on the remote system. Yields the
|
252
|
+
# directory name.
|
253
|
+
def with_remote_tempdir
|
254
|
+
dir = make_tempdir
|
255
|
+
yield dir
|
256
|
+
ensure
|
257
|
+
dir.delete if dir
|
258
|
+
end
|
259
|
+
|
260
|
+
def write_remote_executable(dir, file, filename = nil)
|
261
|
+
filename ||= File.basename(file)
|
262
|
+
remote_path = "#{dir}/#{filename}"
|
263
|
+
write_remote_file(file, remote_path)
|
264
|
+
make_executable(remote_path)
|
265
|
+
remote_path
|
266
|
+
end
|
267
|
+
|
268
|
+
def make_executable(path)
|
269
|
+
result = execute("chmod u+x '#{path}'")
|
270
|
+
if result.exit_code != 0
|
271
|
+
message = "Could not make file '#{path}' executable: #{result.stderr.string}"
|
272
|
+
raise Bolt::Node::FileError.new(message, 'CHMOD_ERROR')
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'bolt/transport/base'
|
2
|
+
|
3
|
+
module Bolt
|
4
|
+
module Transport
|
5
|
+
class WinRM < Base
|
6
|
+
STDIN_METHODS = %w[both stdin].freeze
|
7
|
+
ENVIRONMENT_METHODS = %w[both environment].freeze
|
8
|
+
|
9
|
+
PS_ARGS = %w[
|
10
|
+
-NoProfile -NonInteractive -NoLogo -ExecutionPolicy Bypass
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
def initialize(_config)
|
14
|
+
super
|
15
|
+
require 'winrm'
|
16
|
+
require 'winrm-fs'
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_connection(target)
|
20
|
+
conn = Connection.new(target)
|
21
|
+
conn.connect
|
22
|
+
yield conn
|
23
|
+
ensure
|
24
|
+
begin
|
25
|
+
conn.disconnect if conn
|
26
|
+
rescue StandardError => ex
|
27
|
+
logger.info("Failed to close connection to #{target.uri} : #{ex.message}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def upload(target, source, destination, _options = {})
|
32
|
+
with_connection(target) do |conn|
|
33
|
+
conn.write_remote_file(source, destination)
|
34
|
+
Bolt::Result.for_upload(target, source, destination)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def run_command(target, command, _options = {})
|
39
|
+
with_connection(target) do |conn|
|
40
|
+
output = conn.execute(command)
|
41
|
+
Bolt::Result.for_command(target, output.stdout.string, output.stderr.string, output.exit_code)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def run_script(target, script, arguments, _options = {})
|
46
|
+
with_connection(target) do |conn|
|
47
|
+
conn.with_remote_file(script) do |remote_path|
|
48
|
+
if powershell_file?(remote_path)
|
49
|
+
mapped_args = arguments.map do |a|
|
50
|
+
"$invokeArgs.ArgumentList += @'\n#{a}\n'@"
|
51
|
+
end.join("\n")
|
52
|
+
output = conn.execute(<<-PS)
|
53
|
+
$invokeArgs = @{
|
54
|
+
ScriptBlock = (Get-Command "#{remote_path}").ScriptBlock
|
55
|
+
ArgumentList = @()
|
56
|
+
}
|
57
|
+
#{mapped_args}
|
58
|
+
|
59
|
+
try
|
60
|
+
{
|
61
|
+
Invoke-Command @invokeArgs
|
62
|
+
}
|
63
|
+
catch
|
64
|
+
{
|
65
|
+
exit 1
|
66
|
+
}
|
67
|
+
PS
|
68
|
+
else
|
69
|
+
path, args = *process_from_extension(remote_path)
|
70
|
+
args += escape_arguments(arguments)
|
71
|
+
output = conn.execute_process(path, args)
|
72
|
+
end
|
73
|
+
Bolt::Result.for_command(target, output.stdout.string, output.stderr.string, output.exit_code)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def run_task(target, task, arguments, _options = {})
|
79
|
+
input_method = task.input_method
|
80
|
+
with_connection(target) do |conn|
|
81
|
+
if STDIN_METHODS.include?(input_method)
|
82
|
+
stdin = JSON.dump(arguments)
|
83
|
+
end
|
84
|
+
|
85
|
+
if ENVIRONMENT_METHODS.include?(input_method)
|
86
|
+
arguments.each do |(arg, val)|
|
87
|
+
cmd = "[Environment]::SetEnvironmentVariable('PT_#{arg}', '#{val}')"
|
88
|
+
result = conn.execute(cmd)
|
89
|
+
if result.exit_code != 0
|
90
|
+
raise EnvironmentVarError(var, value)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
conn.with_remote_file(task.executable) do |remote_path|
|
96
|
+
output =
|
97
|
+
if powershell_file?(remote_path) && stdin.nil?
|
98
|
+
# NOTE: cannot redirect STDIN to a .ps1 script inside of PowerShell
|
99
|
+
# must create new powershell.exe process like other interpreters
|
100
|
+
# fortunately, using PS with stdin input_method should never happen
|
101
|
+
if input_method == 'powershell'
|
102
|
+
conn.execute(<<-PS)
|
103
|
+
$private:taskArgs = Get-ContentAsJson (
|
104
|
+
$utf8.GetString([System.Convert]::FromBase64String('#{Base64.encode64(JSON.dump(arguments))}'))
|
105
|
+
)
|
106
|
+
try { & "#{remote_path}" @taskArgs } catch { exit 1 }
|
107
|
+
PS
|
108
|
+
else
|
109
|
+
conn.execute(%(try { & "#{remote_path}" } catch { exit 1 }))
|
110
|
+
end
|
111
|
+
else
|
112
|
+
path, args = *process_from_extension(remote_path)
|
113
|
+
conn.execute_process(path, args, stdin)
|
114
|
+
end
|
115
|
+
Bolt::Result.for_task(target, output.stdout.string,
|
116
|
+
output.stderr.string,
|
117
|
+
output.exit_code)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def powershell_file?(path)
|
123
|
+
Pathname(path).extname.casecmp('.ps1').zero?
|
124
|
+
end
|
125
|
+
|
126
|
+
def process_from_extension(path)
|
127
|
+
case Pathname(path).extname.downcase
|
128
|
+
when '.rb'
|
129
|
+
[
|
130
|
+
'ruby.exe',
|
131
|
+
['-S', "\"#{path}\""]
|
132
|
+
]
|
133
|
+
when '.ps1'
|
134
|
+
[
|
135
|
+
'powershell.exe',
|
136
|
+
[*PS_ARGS, '-File', "\"#{path}\""]
|
137
|
+
]
|
138
|
+
when '.pp'
|
139
|
+
[
|
140
|
+
'puppet.bat',
|
141
|
+
['apply', "\"#{path}\""]
|
142
|
+
]
|
143
|
+
else
|
144
|
+
# Run the script via cmd, letting Windows extension handling determine how
|
145
|
+
[
|
146
|
+
'cmd.exe',
|
147
|
+
['/c', "\"#{path}\""]
|
148
|
+
]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def escape_arguments(arguments)
|
153
|
+
arguments.map do |arg|
|
154
|
+
if arg =~ / /
|
155
|
+
"\"#{arg}\""
|
156
|
+
else
|
157
|
+
arg
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
require 'bolt/transport/winrm/connection'
|