Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/tools/capistrano/lib/capistrano/upload.rb

Revision 8760, 4.9 kB (checked in by minam, 10 months ago)

Fix documentation typo (closes #10680)

Line 
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   # This class encapsulates a single file upload to be performed in parallel
23   # across multiple machines, using the SFTP protocol. Although it is intended
24   # to be used primarily from within Capistrano, it may also be used standalone
25   # if you need to simply upload a file to multiple servers.
26   #
27   # Basic Usage:
28   #
29   #   begin
30   #     uploader = Capistrano::Upload.new(sessions, "remote-file.txt",
31   #         :data => "the contents of the file to upload")
32   #     uploader.process!
33   #   rescue Capistrano::UploadError => e
34   #     warn "Could not upload the file: #{e.message}"
35   #   end
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     # Creates and prepares a new Upload instance. The +sessions+ parameter
45     # must be an array of open Net::SSH sessions. The +filename+ is the name
46     # (including path) of the destination file on the remote server. The
47     # +options+ hash accepts the following keys (as symbols):
48     #
49     # * data: required. Should refer to a String containing the contents of
50     #   the file to upload.
51     # * mode: optional. The "mode" of the destination file. Defaults to 0664.
52     # * logger: optional. Should point to a Capistrano::Logger instance, if
53     #   given.
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     # Uploads to all specified servers in parallel. If any one of the servers
66     # fails, an exception will be raised (UploadError).
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 # a brief respite, to keep the CPU from going crazy
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
Note: See TracBrowser for help on using the browser.