Project

General

Profile

Download (2.85 KB) Statistics
| Branch: | Tag: | Revision:
require 'timeout'

class Foreman::Provision::SSH
attr_reader :template, :uuid, :results, :address, :username, :options

def initialize(address, username = "root", options = { })
@username = username
@address = address
@template = options.delete(:template) || raise("must provide a template")
@uuid = options.delete(:uuid) || "#{address}-#{username}"
@options = defaults.merge(options)

initiate_connection!
end

def deploy!
logger.debug "about to upload #{template} to remote system at #{remote_script}"
scp.upload(template, remote_script)
logger.debug "about to execute #{command}"
@results = ssh.run(command)
log_stdout
log_stderr
success?
end

private

def success?
return true if results.empty?
results.map(&:status).compact == [0]
end

def log_stdout
results.each do |r|
r.stdout.split("\n").each { |l| logger.debug l }
end
end

def log_stderr
results.each do |r|
r.stderr.split("\n").each { |l| logger.warn l }
end
end

def remote_script
"bootstrap-#{uuid}"
end

def command_prefix
(username == "root") ? "" : "sudo "
end

def command
# Use the users home to store the provision script since we can't reliably
# tell if other locations are writeable or executable by the user.
main_execution = "(chmod 0701 ./#{remote_script} && #{command_prefix} ./#{remote_script} ; echo $? >#{remote_script}.status) 2>&1"
"#{command_prefix} sh -c '#{main_execution} | tee #{remote_script}.log; exit $(cat #{remote_script}.status)'"
end

def defaults
{
:keys_only => true,
:config => false,
:auth_methods => %w(publickey),
:compression => true,
:logger => logger
}
end

def logger
Rails.logger
end

def initiate_connection!
Timeout.timeout(360) do
begin
Timeout.timeout(8) do
ssh.run('pwd')
end
rescue Errno::ECONNREFUSED
logger.debug "Connection refused for #{address}, retrying"
sleep(2)
retry
rescue Errno::EHOSTUNREACH
logger.debug "Host unreachable for #{address}, retrying"
sleep(2)
retry
rescue Net::SSH::Disconnect
logger.debug "Host dropping connections for #{address}, retrying"
sleep(2)
retry
rescue Net::SSH::ConnectionTimeout
logger.debug "Host timed out for #{address}, retrying"
sleep(2)
retry
rescue Net::SSH::AuthenticationFailed
logger.debug "Auth failed for #{username} at #{address}, retrying"
sleep(2)
retry
rescue Timeout::Error
retry
rescue => e
Foreman::Logging.exception("SSH error", e)
end
end
end

def ssh
Fog::SSH.new(address, username, options.merge(:timeout => 4))
end

def scp
Fog::SCP.new(address, username, options)
end
end
    (1-1/1)