File Manager
Viewing File: ssh.rb
# -*- coding: utf-8 -*-
require 'specinfra/backend/exec'
require 'net/ssh'
require 'net/scp'
module Specinfra
module Backend
class Ssh < Exec
def run_command(cmd, opt={})
cmd = build_command(cmd)
cmd = add_pre_command(cmd)
if get_config(:ssh_without_env)
ret = ssh_exec!(cmd)
else
ret = with_env do
ssh_exec!(cmd)
end
end
ret[:stdout].gsub!(/\r\n/, "\n")
ret[:stdout].gsub!(/\A\n/, "") if sudo?
if @example
@example.metadata[:command] = cmd
@example.metadata[:stdout] = ret[:stdout]
end
CommandResult.new ret
end
def send_file(from, to)
scp_upload!(from, to)
end
def send_directory(from, to)
scp_upload!(from, to, :recursive => true)
end
def build_command(cmd)
cmd = super(cmd)
if sudo?
cmd = "#{sudo} -p '#{prompt}' #{cmd}"
end
cmd
end
private
def prompt
'Password: '
end
def with_env
env = get_config(:env) || {}
env[:LANG] ||= 'C'
ssh_options = get_config(:ssh_options) || {}
ssh_options[:send_env] ||= []
env.each do |key, value|
key = key.to_s
ENV["_SPECINFRA_#{key}"] = ENV[key];
ENV[key] = value
ssh_options[:send_env] << key
end
yield
ensure
env.each do |key, value|
key = key.to_s
ENV[key] = ENV.delete("_SPECINFRA_#{key}");
end
end
def create_ssh
Net::SSH.start(
get_config(:host),
get_config(:ssh_options)[:user],
get_config(:ssh_options)
)
end
def create_scp
ssh = get_config(:ssh)
if ssh.nil?
ssh = create_ssh
end
Net::SCP.new(ssh)
end
def scp_upload!(from, to, opt={})
if get_config(:scp).nil?
set_config(:scp, create_scp)
end
tmp = File.join('/tmp', File.basename(to))
scp = get_config(:scp)
scp.upload!(from, tmp, opt)
run_command(command.get(:move_file, tmp, to))
end
def ssh_exec!(command)
stdout_data = ''
stderr_data = ''
exit_status = nil
exit_signal = nil
retry_prompt = /^Sorry, try again/
if get_config(:ssh).nil?
set_config(:ssh, create_ssh)
end
ssh = get_config(:ssh)
ssh.open_channel do |channel|
if get_config(:sudo_password) or get_config(:request_pty)
channel.request_pty do |ch, success|
abort "Could not obtain pty " if !success
end
end
channel.exec("#{command}") do |ch, success|
abort "FAILED: couldn't execute command (ssh.channel.exec)" if !success
channel.on_data do |ch, data|
if data.match retry_prompt
abort "Wrong sudo password! Please confirm your password on #{get_config(:host)}."
elsif data.match /^#{prompt}/
channel.send_data "#{get_config(:sudo_password)}\n"
# When pty is allocated and the name of the target host
# cannot be resolved, this error is injected into stdout.
# So exclude this error message.
elsif ! data.match /^sudo: unable to resolve host/
stdout_data += data
@stdout_handler.call(data) if @stdout_handler
end
end
channel.on_extended_data do |ch, type, data|
if data.match /you must have a tty to run sudo/
abort 'Please write "set :request_pty, true" in your spec_helper.rb or other appropriate file.'
end
if data.match /^sudo: no tty present and no askpass program specified/
abort 'Please set sudo password to Specinfra.configuration.sudo_password.'
else
stderr_data += data
@stderr_handler.call(data) if @stderr_handler
end
end
channel.on_request("exit-status") do |ch, data|
exit_status = data.read_long
end
channel.on_request("exit-signal") do |ch, data|
exit_signal = data.read_long
end
end
end
ssh.loop
{ :stdout => stdout_data, :stderr => stderr_data, :exit_status => exit_status, :exit_signal => exit_signal }
end
def sudo
if sudo_path = get_config(:sudo_path)
sudo_path += '/sudo'
else
sudo_path = 'sudo'
end
sudo_options = get_config(:sudo_options)
if sudo_options
sudo_options = sudo_options.shelljoin if sudo_options.is_a?(Array)
sudo_options = ' ' + sudo_options
end
"#{sudo_path.shellescape}#{sudo_options}"
end
def sudo?
user = get_config(:ssh_options)[:user]
disable_sudo = get_config(:disable_sudo)
user != 'root' && !disable_sudo
end
end
end
end