| 1 |
begin |
|---|
| 2 |
require 'rubygems' |
|---|
| 3 |
gem 'net-sftp', "< 1.99.0" |
|---|
| 4 |
rescue LoadError, NameError |
|---|
| 5 |
end |
|---|
| 6 |
|
|---|
| 7 |
require 'net/sftp' |
|---|
| 8 |
require 'net/sftp/operations/errors' |
|---|
| 9 |
require 'capistrano/errors' |
|---|
| 10 |
|
|---|
| 11 |
module Capistrano |
|---|
| 12 |
unless ENV['SKIP_VERSION_CHECK'] |
|---|
| 13 |
require 'capistrano/version' |
|---|
| 14 |
require 'net/sftp/version' |
|---|
| 15 |
sftp_version = [Net::SFTP::Version::MAJOR, Net::SFTP::Version::MINOR, Net::SFTP::Version::TINY] |
|---|
| 16 |
required_version = [1,1,0] |
|---|
| 17 |
if !Capistrano::Version.check(required_version, sftp_version) |
|---|
| 18 |
raise "You have Net::SFTP #{sftp_version.join(".")}, but you need at least #{required_version.join(".")}. Net::SFTP will not be used." |
|---|
| 19 |
end |
|---|
| 20 |
end |
|---|
| 21 |
|
|---|
| 22 |
|
|---|
| 23 |
|
|---|
| 24 |
|
|---|
| 25 |
|
|---|
| 26 |
|
|---|
| 27 |
|
|---|
| 28 |
|
|---|
| 29 |
|
|---|
| 30 |
|
|---|
| 31 |
|
|---|
| 32 |
|
|---|
| 33 |
|
|---|
| 34 |
|
|---|
| 35 |
|
|---|
| 36 |
class Upload |
|---|
| 37 |
def self.process(sessions, filename, options) |
|---|
| 38 |
new(sessions, filename, options).process! |
|---|
| 39 |
end |
|---|
| 40 |
|
|---|
| 41 |
attr_reader :sessions, :filename, :options |
|---|
| 42 |
attr_reader :failed, :completed |
|---|
| 43 |
|
|---|
| 44 |
|
|---|
| 45 |
|
|---|
| 46 |
|
|---|
| 47 |
|
|---|
| 48 |
|
|---|
| 49 |
|
|---|
| 50 |
|
|---|
| 51 |
|
|---|
| 52 |
|
|---|
| 53 |
|
|---|
| 54 |
def initialize(sessions, filename, options) |
|---|
| 55 |
raise ArgumentError, "you must specify the data to upload via the :data option" unless options[:data] |
|---|
| 56 |
|
|---|
| 57 |
@sessions = sessions |
|---|
| 58 |
@filename = filename |
|---|
| 59 |
@options = options |
|---|
| 60 |
|
|---|
| 61 |
@completed = @failed = 0 |
|---|
| 62 |
@sftps = setup_sftp |
|---|
| 63 |
end |
|---|
| 64 |
|
|---|
| 65 |
|
|---|
| 66 |
|
|---|
| 67 |
def process! |
|---|
| 68 |
logger.debug "uploading #{filename}" if logger |
|---|
| 69 |
while running? |
|---|
| 70 |
@sftps.each do |sftp| |
|---|
| 71 |
next if sftp.channel[:done] |
|---|
| 72 |
begin |
|---|
| 73 |
sftp.channel.connection.process(true) |
|---|
| 74 |
rescue Net::SFTP::Operations::StatusException => error |
|---|
| 75 |
logger.important "uploading failed: #{error.description}", sftp.channel[:server] if logger |
|---|
| 76 |
failed!(sftp) |
|---|
| 77 |
end |
|---|
| 78 |
end |
|---|
| 79 |
sleep 0.01 |
|---|
| 80 |
end |
|---|
| 81 |
logger.trace "upload finished" if logger |
|---|
| 82 |
|
|---|
| 83 |
if (failed = @sftps.select { |sftp| sftp.channel[:failed] }).any? |
|---|
| 84 |
hosts = failed.map { |sftp| sftp.channel[:server] } |
|---|
| 85 |
error = UploadError.new("upload of #{filename} failed on #{hosts.join(',')}") |
|---|
| 86 |
error.hosts = hosts |
|---|
| 87 |
raise error |
|---|
| 88 |
end |
|---|
| 89 |
|
|---|
| 90 |
self |
|---|
| 91 |
end |
|---|
| 92 |
|
|---|
| 93 |
private |
|---|
| 94 |
|
|---|
| 95 |
def logger |
|---|
| 96 |
options[:logger] |
|---|
| 97 |
end |
|---|
| 98 |
|
|---|
| 99 |
def setup_sftp |
|---|
| 100 |
sessions.map do |session| |
|---|
| 101 |
server = session.xserver |
|---|
| 102 |
sftp = session.sftp |
|---|
| 103 |
sftp.connect unless sftp.state == :open |
|---|
| 104 |
|
|---|
| 105 |
sftp.channel[:server] = server |
|---|
| 106 |
sftp.channel[:done] = false |
|---|
| 107 |
sftp.channel[:failed] = false |
|---|
| 108 |
|
|---|
| 109 |
real_filename = filename.gsub(/\$CAPISTRANO:HOST\$/, server.host) |
|---|
| 110 |
sftp.open(real_filename, IO::WRONLY | IO::CREAT | IO::TRUNC, options[:mode] || 0664) do |status, handle| |
|---|
| 111 |
break unless check_status(sftp, "open #{real_filename}", server, status) |
|---|
| 112 |
|
|---|
| 113 |
logger.info "uploading data to #{server}:#{real_filename}" if logger |
|---|
| 114 |
sftp.write(handle, options[:data] || "") do |status| |
|---|
| 115 |
break unless check_status(sftp, "write to #{server}:#{real_filename}", server, status) |
|---|
| 116 |
sftp.close_handle(handle) do |
|---|
| 117 |
logger.debug "done uploading data to #{server}:#{real_filename}" if logger |
|---|
| 118 |
completed!(sftp) |
|---|
| 119 |
end |
|---|
| 120 |
end |
|---|
| 121 |
end |
|---|
| 122 |
|
|---|
| 123 |
sftp |
|---|
| 124 |
end |
|---|
| 125 |
end |
|---|
| 126 |
|
|---|
| 127 |
def check_status(sftp, action, server, status) |
|---|
| 128 |
return true if status.code == Net::SFTP::Session::FX_OK |
|---|
| 129 |
|
|---|
| 130 |
logger.error "could not #{action} on #{server} (#{status.message})" if logger |
|---|
| 131 |
failed!(sftp) |
|---|
| 132 |
|
|---|
| 133 |
return false |
|---|
| 134 |
end |
|---|
| 135 |
|
|---|
| 136 |
def running? |
|---|
| 137 |
completed < @sftps.length |
|---|
| 138 |
end |
|---|
| 139 |
|
|---|
| 140 |
def failed!(sftp) |
|---|
| 141 |
completed!(sftp) |
|---|
| 142 |
@failed += 1 |
|---|
| 143 |
sftp.channel[:failed] = true |
|---|
| 144 |
end |
|---|
| 145 |
|
|---|
| 146 |
def completed!(sftp) |
|---|
| 147 |
@completed += 1 |
|---|
| 148 |
sftp.channel[:done] = true |
|---|
| 149 |
end |
|---|
| 150 |
end |
|---|
| 151 |
|
|---|
| 152 |
end |
|---|