class Hocho::Drivers::SshBase
Public Instance Methods
deploy(deploy_dir: nil, shm_prefix: []) { || ... }
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 17 def deploy(deploy_dir: nil, shm_prefix: []) @host_basedir = deploy_dir if deploy_dir shm_prefix = [*shm_prefix] ssh_cmd = ['ssh', *host.openssh_config.flat_map { |l| ['-o', "\"#{l}\""] }].join(' ') shm_exclude = shm_prefix.map{ |_| "--exclude=#{_}" } compress = host.compress? ? ['-z'] : [] rsync_cmd = [*%w(rsync -a --copy-links --copy-unsafe-links --delete --exclude=.git), *compress, *shm_exclude, '--rsh', ssh_cmd, '.', "#{host.hostname}:#{host_basedir}"] puts "=> $ #{rsync_cmd.shelljoin}" system(*rsync_cmd, chdir: base_dir) or raise 'failed to rsync' unless shm_prefix.empty? shm_include = shm_prefix.map{ |_| "--include=#{_.sub(%r{/\z},'')}/***" } rsync_cmd = [*%w(rsync -a --copy-links --copy-unsafe-links --delete), *compress, *shm_include, '--exclude=*', '--rsh', ssh_cmd, '.', "#{host.hostname}:#{host_shm_basedir}"] puts "=> $ #{rsync_cmd.shelljoin}" system(*rsync_cmd, chdir: base_dir) or raise 'failed to rsync' shm_prefix.each do |x| mkdir = if %r{\A[^/].*\/.+\z} === x %Q(mkdir -vp "$(basename #{x.shellescape})" &&) else nil end ssh_run(%Q(cd #{host_basedir} && #{mkdir} ln -sfv #{host_shm_basedir}/#{x.shellescape} ./#{x.sub(%r{/\z},'').shellescape})) end end yield ensure if !deploy_dir || !keep_synced_files cmd = "rm -rf #{host_basedir.shellescape}" puts "=> #{host.name} $ #{cmd}" ssh_run(cmd, error: false) end end
finalize()
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 12 def finalize remove_host_tmpdir! remove_host_shmdir! end
ssh()
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 8 def ssh host.ssh_connection end
Private Instance Methods
host_basedir()
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 145 def host_basedir @host_basedir || "#{host_tmpdir}/itamae" end
host_node_json_path()
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 209 def host_node_json_path "#{host_tmpdir}/node.json" end
host_shm_basedir()
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 149 def host_shm_basedir host_shmdir && "#{host_shmdir}/itamae" end
host_shmdir()
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 153 def host_shmdir return @host_shmdir if @host_shmdir return nil if @host_shmdir == false shmdir = host.shmdir unless shmdir if ssh.exec!('uname').chomp == 'Linux' shmdir = '/dev/shm' mount = ssh.exec!("grep -F #{shmdir.shellescape} /proc/mounts").each_line.map{ |_| _.chomp.split(' ') } unless mount.find { |_| _[1] == shmdir }&.first == 'tmpfs' @host_shmdir = false return nil end else @host_shmdir = false return nil end end mktemp_cmd = "TMPDIR=#{shmdir.shellescape} #{%w(mktemp -d -t hocho-run-XXXXXXXXX).shelljoin}" res = ssh.exec!(mktemp_cmd) unless res.start_with?(shmdir) raise "Failed to shm mktemp #{mktemp_cmd.inspect} -> #{res.inspect}" end @host_shmdir = res.chomp end
host_tmpdir()
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 181 def host_tmpdir @host_tmpdir ||= begin mktemp_cmd = %w(mktemp -d -t hocho-run-XXXXXXXXX).shelljoin mktemp_cmd.prepend("TMPDIR=#{host.tmpdir.shellescape} ") if host.tmpdir res = ssh.exec!(mktemp_cmd) unless res.start_with?(host.tmpdir || '/') raise "Failed to mktemp #{mktemp_cmd.inspect} -> #{res.inspect}" end res.chomp end end
prepare_sudo(password = host.sudo_password) { |nil, nil, ""| ... }
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 56 def prepare_sudo(password = host.sudo_password) raise "sudo password not present" if host.sudo_required? && !host.nopasswd_sudo? && password.nil? unless host.sudo_required? yield nil, nil, "" return end if host.nopasswd_sudo? yield nil, nil, "sudo " return end passphrase_env_name = "HOCHO_PA_#{SecureRandom.hex(8).upcase}" # password_env_name = "HOCHO_PB_#{SecureRandom.hex(8).upcase}" temporary_passphrase = SecureRandom.base64(129).chomp local_supports_pbkdf2 = system(*%w(openssl enc -pbkdf2), in: File::NULL, out: File::NULL, err: [:child, :out]) remote_supports_pbkdf2 = begin exitstatus, * = ssh_run("openssl enc -pbkdf2", error: false, &:eof!) exitstatus == 0 end derive = local_supports_pbkdf2 && remote_supports_pbkdf2 ? %w(-pbkdf2) : [] encrypted_password = IO.pipe do |r,w| w.write temporary_passphrase w.close IO.popen([*%w(openssl enc -aes-128-cbc -pass fd:5 -a -md sha256), *derive, 5 => r], "r+") do |io| io.puts password io.close_write io.read.chomp end end begin tmpdir = host_shmdir ? "TMPDIR=#{host_shmdir.shellescape} " : nil temp_executable = ssh.exec!("#{tmpdir}mktemp").chomp raise unless temp_executable.start_with?('/') ssh_run("chmod 0700 #{temp_executable.shellescape}; cat > #{temp_executable.shellescape}; chmod +x #{temp_executable.shellescape}") do |ch| ch.send_data("#!/bin/bash\nexec openssl enc -aes-128-cbc -d -a -md sha256 #{derive.shelljoin} -pass env:#{passphrase_env_name} <<< #{encrypted_password.shellescape}\n") ch.eof! end sh = "#{passphrase_env_name}=#{temporary_passphrase.shellescape} SUDO_ASKPASS=#{temp_executable.shellescape} sudo -A " exp = "export #{passphrase_env_name}=#{temporary_passphrase.shellescape}\nexport SUDO_ASKPASS=#{temp_executable.shellescape}\n" cmd = "sudo -A " yield sh, exp, cmd ensure ssh_run("shred --remove #{temp_executable.shellescape}") end end
remove_host_shmdir!()
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 201 def remove_host_shmdir! if @host_shmdir host_shmdir, @host_shmdir = @host_shmdir, nil ssh.exec!("rm -rf #{host_shmdir.shellescape}") end end
remove_host_tmpdir!()
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 194 def remove_host_tmpdir! if @host_tmpdir host_tmpdir, @host_tmpdir = @host_tmpdir, nil ssh.exec!("rm -rf #{host_tmpdir.shellescape}") end end
set_ssh_output_hook(c)
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 111 def set_ssh_output_hook(c) check = ->(prefix, data, buf) do data = buf + data unless buf.empty? return if data.empty? lines = data.lines # If data is not NL-terminated, its last line is carried over to next check.call if lines.last.end_with?("\n") buf.clear else buf.replace(lines.pop) end lines.each do |line| puts "#{prefix}#{line}" end end outbuf, errbuf = +"", +"" outpre, errpre = "[#{host.name}] ", "[#{host.name}/ERR] " c.on_data do |c, data| check.call outpre, data, outbuf end c.on_extended_data do |c, _, data| check.call errpre, data, errbuf end c.on_close do puts "#{outpre}#{outbuf}" unless outbuf.empty? puts "#{errpre}#{errbuf}" unless errbuf.empty? end end
ssh_run(cmd, error: true) { |c| ... }
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 224 def ssh_run(cmd, error: true) exitstatus, exitsignal = nil puts "$ #{cmd}" cha = ssh.open_channel do |ch| ch.exec(cmd) do |c, success| raise "execution failed on #{host.name}: #{cmd.inspect}" if !success && error c.on_request("exit-status") { |c, data| exitstatus = data.read_long } c.on_request("exit-signal") { |c, data| exitsignal = data.read_long } yield c if block_given? end end cha.wait raise "execution failed on #{host.name} (status=#{exitstatus.inspect}, signal=#{exitsignal.inspect}): #{cmd.inspect}" if (exitstatus != 0 || exitsignal) && error [exitstatus, exitsignal] end
with_host_node_json_file() { |host_node_json_path| ... }
click to toggle source
# File lib/hocho/drivers/ssh_base.rb, line 213 def with_host_node_json_file ssh_run("umask 0077 && cat > #{host_node_json_path.shellescape}") do |c| c.send_data "#{node_json}\n" c.eof! end yield host_node_json_path ensure ssh.exec!("rm #{host_node_json_path.shellescape}") end