class Proxy::RemoteExecution::Ssh::Session
Service that handles running external commands for Actions::Command Dynflow action. It runs just one (actor) thread for all the commands running in the system and updates the Dynflow actions periodically.
Constants
- EXPECTED_POWER_ACTION_MESSAGES
Public Class Methods
new(options = {})
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 10 def initialize(options = {}) @clock = options[:clock] || Dynflow::Clock.spawn('proxy-dispatcher-clock') @logger = options[:logger] || Logger.new($stderr) @connector_class = options[:connector_class] || Connector @local_working_dir = options[:local_working_dir] || Settings.instance.local_working_dir @remote_working_dir = options[:remote_working_dir] || Settings.instance.remote_working_dir @refresh_interval = options[:refresh_interval] || 1 @client_private_key_file = Settings.instance.ssh_identity_key_file @command = options[:command] @command_buffer = [] @refresh_planned = false reference.tell(:initialize_command) end
Public Instance Methods
dispatcher()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 105 def dispatcher self.parent end
finish_command()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 100 def finish_command close dispatcher.tell([:finish_command, @command]) end
initialize_command()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 26 def initialize_command @logger.debug("initalizing command [#{@command}]") open_connector remote_script = cp_script_to_remote output_path = File.join(File.dirname(remote_script), 'output') # pipe the output to tee while capturing the exit code script = <<-SCRIPT exec 4>&1 exit_code=`((#{su_prefix}#{remote_script}; echo $?>&3 ) | /usr/bin/tee #{output_path} ) 3>&1 >&4` exec 4>&- exit $exit_code SCRIPT @logger.debug("executing script:\n#{script.lines.map { |line| " | #{line}" }.join}") @connector.async_run(script) do |data| @command_buffer << data end rescue => e @logger.error("error while initalizing command #{e.class} #{e.message}:\n #{e.backtrace.join("\n")}") @command_buffer.concat(CommandUpdate.encode_exception("Error initializing command #{@command}", e)) refresh ensure plan_next_refresh end
kill()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 88 def kill @logger.debug("killing command [#{@command}]") if @connector @connector.run("pkill -f #{remote_command_file('script')}") else @logger.debug("connection closed") end rescue => e @command_buffer.concat(CommandUpdate.encode_exception("Failed to kill the command", e, false)) plan_next_refresh end
refresh()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 51 def refresh @connector.refresh if @connector unless @command_buffer.empty? status = refresh_command_buffer if status finish_command end end rescue Net::SSH::Disconnect => e check_expecting_disconnect if @expecting_disconnect @command_buffer << CommandUpdate::StatusData.new(0) else @command_buffer.concat(CommandUpdate.encode_exception("Failed to refresh the connector", e, true)) end refresh_command_buffer finish_command rescue => e @command_buffer.concat(CommandUpdate.encode_exception("Failed to refresh the connector", e, false)) ensure @refresh_planned = false plan_next_refresh end
refresh_command_buffer()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 76 def refresh_command_buffer @logger.debug("command #{@command} got new output: #{@command_buffer.inspect}") command_update = CommandUpdate.new(@command_buffer) check_expecting_disconnect @command.suspended_action << command_update @command_buffer = [] if command_update.exit_status @logger.debug("command [#{@command}] finished with status #{command_update.exit_status}") return command_update.exit_status end end
start_termination(*args)
click to toggle source
Calls superclass method
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 109 def start_termination(*args) super close finish_termination end
Private Instance Methods
check_expecting_disconnect()
click to toggle source
when a remote server disconnects, it's hard to tell if it was on purpose (when calling reboot) or it's an error. When it's expected, we expect the script to produce 'restart host' as its last command output
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 206 def check_expecting_disconnect last_output = @command_buffer.reverse.find { |d| d.is_a? CommandUpdate::StdoutData } return unless last_output if EXPECTED_POWER_ACTION_MESSAGES.any? { |message| last_output.data =~ /^#{message}/ } @expecting_disconnect = true end end
close()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 190 def close @connector.close if @connector @connector = nil end
cp_script_to_remote()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 163 def cp_script_to_remote local_script_file = write_command_file_locally('script', sanitize_script(@command.script)) File.chmod(0777, local_script_file) remote_script_file = remote_command_file('script') @connector.upload_file(local_script_file, remote_script_file) return remote_script_file end
ensure_local_directory(path)
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 154 def ensure_local_directory(path) if File.exist?(path) raise "#{path} expected to be a directory" unless File.directory?(path) else FileUtils.mkdir_p(path) end return path end
local_command_dir()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 138 def local_command_dir File.join(@local_working_dir, 'foreman-proxy', "foreman-ssh-cmd-#{@command.id}") end
local_command_file(filename)
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 142 def local_command_file(filename) File.join(local_command_dir, filename) end
open_connector()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 129 def open_connector raise 'Connector already opened' if @connector options = { :logger => @logger } options[:known_hosts_file] = prepare_known_hosts options[:client_private_key_file] = @client_private_key_file options[:port] = @command.ssh_port @connector = @connector_class.new(@command.host, @command.ssh_user, options) end
plan_next_refresh()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 195 def plan_next_refresh if @connector && !@refresh_planned @logger.debug("planning to refresh") @clock.ping(reference, Time.now + @refresh_interval, :refresh) @refresh_planned = true end end
prepare_known_hosts()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 182 def prepare_known_hosts path = local_command_file('known_hosts') if @command.host_public_key write_command_file_locally('known_hosts', "#{@command.host} #{@command.host_public_key}") end return path end
remote_command_dir()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 146 def remote_command_dir File.join(@remote_working_dir, "foreman-ssh-cmd-#{@command.id}") end
remote_command_file(filename)
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 150 def remote_command_file(filename) File.join(remote_command_dir, filename) end
sanitize_script(script)
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 171 def sanitize_script(script) script.tr("\r", '') end
su_prefix()
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 117 def su_prefix return if @command.effective_user.nil? || @command.effective_user == @command.ssh_user case @command.effective_user_method when 'sudo' "sudo -n -u #{@command.effective_user} " when 'su' "su - #{@command.effective_user} -c " else raise "effective_user_method ''#{@command.effective_user_method}'' not supported" end end
write_command_file_locally(filename, content)
click to toggle source
# File lib/smart_proxy_remote_execution_ssh_core/session.rb, line 175 def write_command_file_locally(filename, content) path = local_command_file(filename) ensure_local_directory(File.dirname(path)) File.write(path, content) return path end