lib/bolt/shell/bash.rb in bolt-3.1.0 vs lib/bolt/shell/bash.rb in bolt-3.3.0
- old
+ new
@@ -10,11 +10,10 @@
def initialize(target, conn)
super
@run_as = nil
-
@sudo_id = SecureRandom.uuid
@sudo_password = @target.options['sudo-password'] || @target.password
end
def provided_features
@@ -52,23 +51,39 @@
end
end
def download(source, destination, options = {})
running_as(options[:run_as]) do
- # Target OS may be either Unix or Windows. Without knowing the target OS before-hand
- # we can't assume whether the path separator is '/' or '\'. Assume we're connecting
- # to a target with Unix and then check if the path exists after downloading.
download = File.join(destination, Bolt::Util.unix_basename(source))
- conn.download_file(source, destination, download)
+ # If using run-as, the file is copied to a tmpdir and chowned to the
+ # connecting user. This is a workaround for limitations in net-ssh that
+ # only allow for downloading files as the connecting user, which is a
+ # problem for users who cannot connect to targets as the root user.
+ # This temporary copy should *always* be deleted.
+ if run_as
+ with_tmpdir(force_cleanup: true) do |dir|
+ tmpfile = File.join(dir.to_s, Bolt::Util.unix_basename(source))
- # If the download path doesn't exist, then the file was likely downloaded from Windows
- # using a source path with backslashes (e.g. 'C:\Users\Administrator\foo'). The file
- # should be saved to the expected location, so update the download path assuming a
- # Windows basename so the result shows the correct local path.
- unless File.exist?(download)
- download = File.join(destination, Bolt::Util.windows_basename(source))
+ result = execute(['cp', '-r', source, dir.to_s], sudoable: true)
+
+ if result.exit_code != 0
+ message = "Could not copy file '#{source}' to temporary directory '#{dir}': #{result.stderr.string}"
+ raise Bolt::Node::FileError.new(message, 'CP_ERROR')
+ end
+
+ # We need to force the chown, otherwise this will just return
+ # without doing anything since the chown user is the same as the
+ # connecting user.
+ dir.chown(conn.user, force: true)
+
+ conn.download_file(tmpfile, destination, download)
+ end
+ # If not using run-as, we can skip creating a temporary copy and just
+ # download the file directly.
+ else
+ conn.download_file(source, destination, download)
end
Bolt::Result.for_download(target, source, destination, download)
end
end
@@ -143,11 +158,14 @@
remote_task_path = write_executable(dir, wrapper, 'wrapper.sh')
end
dir.chown(run_as)
- execute_options[:stdin] = stdin
+ # Don't pass parameters on stdin if using a tty, as the parameters are
+ # already part of the wrapper script.
+ execute_options[:stdin] = stdin unless stdin && target.options['tty']
+
execute_options[:sudoable] = true if run_as
output = execute(remote_task_path, **execute_options)
end
Bolt::Result.for_task(target, output.stdout.string,
output.stderr.string,
@@ -289,16 +307,16 @@
remote_path
end
# A helper to create and delete a tmpdir on the remote system. Yields the
# directory name.
- def with_tmpdir
+ def with_tmpdir(force_cleanup: false)
dir = make_tmpdir
yield dir
ensure
if dir
- if target.options['cleanup']
+ if target.options['cleanup'] || force_cleanup
dir.delete
else
Bolt::Logger.warn("skip_cleanup", "Skipping cleanup of tmpdir #{dir}")
end
end
@@ -388,18 +406,27 @@
# True while the process is running or waiting for IO input
while t.alive?
# See if we can read from out or err, or write to in
ready_read, ready_write, = select(read_streams.keys, write_stream, nil, timeout)
- # Read from out and err
ready_read&.each do |stream|
+ stream_name = stream == out ? 'out' : 'err'
# Check for sudo prompt
- read_streams[stream] << if use_sudo
- check_sudo(stream, inp, options[:stdin])
- else
- stream.readpartial(CHUNK_SIZE)
- end
+ to_print = if use_sudo
+ check_sudo(stream, inp, options[:stdin])
+ else
+ stream.readpartial(CHUNK_SIZE)
+ end
+
+ if !to_print.chomp.empty? && @stream_logger
+ formatted = to_print.lines.map do |msg|
+ "[#{@target.safe_name}] #{stream_name}: #{msg.chomp}"
+ end.join("\n")
+ @stream_logger.warn(formatted)
+ end
+
+ read_streams[stream] << to_print
rescue EOFError
end
# select will either return an empty array if there are no
# writable streams or nil if no IO object is available before the
@@ -443,10 +470,10 @@
case result_output.exit_code
when 0
@logger.trace { "Command `#{command_str}` returned successfully" }
when 126
- msg = "\n\nThis may be caused by the default tmpdir being mounted "\
+ msg = "\n\nThis might be caused by the default tmpdir being mounted "\
"using 'noexec'. See http://pup.pt/task-failure for details and workarounds."
result_output.stderr << msg
@logger.trace { "Command #{command_str} failed with exit code #{result_output.exit_code}" }
else
@logger.trace { "Command #{command_str} failed with exit code #{result_output.exit_code}" }