module Deplomat class RemoteNode < Node def initialize(host:, port: nil, user: "deploy", raise_exceptions: true, path: nil, logfile: '~/deplomat.log', log_to: [:stdout]) @log_to = log_to @logfile = logfile # Create ControlMasters dir, user might not have it unless File.exists?("#{ENV['HOME']}/.ssh/controlmasters/") Dir.mkdir("#{ENV['HOME']}/.ssh/controlmasters/") end # Establish connection first_ssh_command = "ssh -MNfS #{ENV['HOME']}/.ssh/controlmasters/%r@%h:%p #{user}@#{host} #{port ? "-p #{port.to_s} " : ""}-o ControlPersist=10m" system first_ssh_command # get background process id by the full command name @pids = [] Sys::ProcTable.ps.each do |process| if process.cmdline.match(first_ssh_command) @pids << process.pid.to_i end end @pids.compact! if @pids.empty? raise Deplomat::ExecutionError.new("ERROR: no ssh pid found for\n\t#{first_ssh_command}.\nThis is weird.") elsif @pids.length > 1 log("Connected with ssh, host #{host}, but looks like another connection has been opened before...", color: "white") log("connection pids: #{@pids.join(", ")}. We'll be closing them all when finished.", color: "white") else log("Connected with ssh, host #{host}, pid #{@pids.first}", color: "white") end @ssh_command = "ssh -S #{ENV['HOME']}/.ssh/controlmasters/%r@%h:%p #{user}@#{host} #{port ? "-p #{port.to_s} " : ""}" @host = host @user = user @port = port super(logfile: '~/deplomat.log', path: path, raise_exceptions: raise_exceptions) end alias :local_execute :execute def execute(command, path=@current_path, message: [], env_vars: '', login_shell: false, stdout_source: :stdout, log_command: true, _raise_exceptions: @raise_exceptions) log("(#{@host}) --> " + command + "\n", color: "white") if log_command command = "#{env_vars} cd #{path} && #{command}" if path command = "bash -l -c \"#{command}\"" if login_shell super("#{@ssh_command} '#{command}'", nil, message: message, stdout_source: stdout_source, log_command: false, _raise_exceptions: _raise_exceptions) end def path_exist?(path) path = adjusted_path(path) status = execute("test -f #{path} || test -d #{path}", nil, log_command: true, _raise_exceptions: false)[:status] log((status == 1 ? "NOT FOUND: #{path}" : "EXISTS: #{path}")) status == 0 end alias :path_exists? :path_exist? alias :file_exists? :path_exist? def upload(what, where, except: nil) except = "--exclude '#{except}'" if except local_execute "rsync -arzve 'ssh #{@port ? "-p #{@port.to_s} " : ""}' #{except} --delete #{what} #{@user}@#{@host}:#{where}", nil end def download(what, where, except: nil) except = "--exclude '#{except}'" if except local_execute "rsync -arzve 'ssh #{@port ? "-p #{@port.to_s} " : ""}' #{except} --delete #{@user}@#{@host}:#{what} #{where}", nil end def close begin puts "Closing connection(s) to #{@host}, ssh pid(s): #{@pids.join(", ")}." @pids.each { |pid| Process.kill 'TERM', pid } rescue Errno::ESRCH puts "WARNING: no process with #{@pid} found, no connection to close!" end end end end