lib/core/transports.rb in buildr-0.18.0 vs lib/core/transports.rb in buildr-0.19.0

- old
+ new

@@ -10,50 +10,85 @@ # Monkeypatching: SFTP never defines the mkdir method on its session or the underlying # driver, it just redirect calls through method_missing. Rake, on the other hand, decides # to define mkdir on Object, and so routes our calls to FileUtils. -class Net::SFTP::Session - def mkdir(path, attrs = {}) - method_missing :mkdir, path, attrs +module Net #:nodoc:all + class Session + def mkdir(path, attrs = {}) + method_missing :mkdir, path, attrs + end end -end -class Net::SFTP::Protocol::Driver - def mkdir(first, path, attrs = {}) - method_missing :mkdir, first, path, attrs + class SFTP::Protocol::Driver + def mkdir(first, path, attrs = {}) + method_missing :mkdir, first, path, attrs + end end end module Buildr + + # Transports are used for downloading artifacts from remote repositories, uploading + # artifacts to deployment repositories, and anything else you need to move around. + # + # The HTTP transport is used for all URLs with the scheme http or https. You can only + # use the HTTP transport to download artifacts. + # + # The SFTP transport is used for all URLs with the schema sftp. You can only use the + # SFTP transport to upload artifacts. + # + # The SFTP transport supports the following options: + # * :username -- The username. + # * :password -- A password. If unspecified, you will be prompted to enter a password. + # * :permissions -- Permissions to set on the uploaded file. + # You can also pass the username/password in the URL. + # + # The SFTP transport will automatically create MD5 and SHA1 digest files for each file + # it uploads. module Transports + # Indicates the requested resource was not found. class NotFound < Exception end - # Perform one or more operations using an open connection to the - # specified URL. For examples, see Transport#download and Transport#upload. - def self.perform(url, options = nil, &block) - uri = URI.parse(url.to_s) - const_get(uri.scheme.upcase).perform(uri, options, &block) - end + class << self - # Convenience method for downloading a single file from the specified - # URL to the target file. - def self.download(url, target, options = nil) - uri = URI.parse(url.to_s) - path, uri.path = uri.path, "" - const_get(uri.scheme.upcase).perform(uri, options) do |transport| - transport.download(path, target) + # :call-seq: + # perform(url, options?) { |transport| ... } + # + # Perform one or more operations using an open connection to the + # specified URL. For examples, see Transport#download and Transport#upload. + def perform(url, options = nil, &block) + uri = URI.parse(url.to_s) + const_get(uri.scheme.upcase).perform(uri, options, &block) end + + # :call-seq: + # download(url, target, options?) + # + # Convenience method for downloading a single file from the specified + # URL to the target file. + def download(url, target, options = nil) + uri = URI.parse(url.to_s) + path, uri.path = uri.path, "" + const_get(uri.scheme.upcase).perform(uri, options) do |transport| + transport.download(path, target) + end + end + end + # Extend this class if you are implementing a new transport. class Transport class << self + # :call-seq: + # perform(url, options?) { |transport| ... } + # # Perform one or more operations using an open connection to the # specified URL. For examples, see #download and #upload. def perform(url, options = nil) instance = new(url, options) begin @@ -69,10 +104,11 @@ # The base path on the server, always ending with a slash. attr_reader :base_path # Options passed during construction. attr_reader :options + # Initialize the transport with the specified URL and options. def initialize(url, options) @uri = URI.parse(url.to_s) @base_path = @uri.path || "/" @base_path += "/" unless @base_path[-1] == ?/ @options = options || {} @@ -136,14 +172,14 @@ else truncated = file_name end progress_bar.format = "#{truncated}: %3d%% %s %s/%s %s" progress_bar.format = "%3d%% %s %s/%s %s" - progress_bar.format_arguments = [:percentage, :bar, :bytes, :total, :stat] + progress_bar.format_arguments = [:percentage, :bar, :bytes, :total, :stat] progress_bar.bar_mark = "." - + begin class << progress_bar def <<(bytes) inc bytes.respond_to?(:size) ? bytes.size : bytes end @@ -192,11 +228,11 @@ digester = Digester.new(types) yield digester digester.to_hash end - class Digester + class Digester #:nodoc: def initialize(types) types ||= [ "md5", "sha1" ] @digests = types.inject({}) do |hash, type| hash[type.to_s.downcase] = Digest.const_get(type.to_s.upcase).new @@ -208,11 +244,11 @@ def <<(bytes) @digests.each { |type, digest| digest << bytes } end # Iterate over all the digests calling the block with two arguments: - # the digest type (e.g. "md5") and the hexadecimal digest value. + # the digest type (e.g. "md5") and the hexadecimal digest value. def each() @digests.each { |type, digest| yield type, digest.hexdigest } end # Returns a hash that maps each digest type to its hexadecimal digest value. @@ -226,11 +262,11 @@ end end - class HTTP < Transport + class HTTP < Transport #:nodoc: def initialize(url, options) super @http = Net::HTTP.start(@uri.host, @uri.port) end @@ -252,11 +288,11 @@ download = proc do |write| # Read the body of the page and write it out. response.read_body do |chunk| write[chunk] digester << chunk - progress << chunk + progress << chunk end # Check server digests before approving the download. digester.each do |type, hexdigest| @http.request_get("#{@base_path}#{path}.#{type.to_s.downcase}") do |response| if Net::HTTPOK === response @@ -265,11 +301,11 @@ response.read_body.split.first == hexdigest end end end end - + if target # If download breaks we end up with a partial file which is # worse than not having a file at all, so download to temporary # file and then move over. temp = Tempfile.new(File.basename(target)) @@ -295,23 +331,27 @@ @http = nil end end + # Use the HTTP transport for HTTPS connections. + HTTPS = HTTP #:nodoc: - class SFTP < Transport + class SFTP < Transport #:nodoc: + class << self def passwords() @passwords ||= {} end end attr_reader :sftp def initialize(url, options) super + @permissions = options.delete :permissions # SSH options are based on the username/password from the URI. ssh_options = { :port=>@uri.port, :username=>@uri.user }.merge(options || {}) ssh_options[:password] ||= SFTP.passwords[@uri.host] begin puts "Connecting to #{@uri.host}" if Rake.application.options.trace @@ -332,44 +372,49 @@ def upload(source, path) File.open(source) do |file| with_progress_bar path.split("/").last, File.size(source) do |progress| with_digests(@options[:digests]) do |digester| - puts "Uploading to #{@base_path}#{path}" if Rake.application.options.trace - @sftp.open_handle(@base_path + path, "w") do |handle| + target_path = "#{@base_path}#{path}" + puts "Uploading to #{target_path}" if Rake.application.options.trace + @sftp.open_handle(target_path, "w") do |handle| # Writing in chunks gives us the benefit of a progress bar, # but also require that we maintain a position in the file, # since write() with two arguments always writes at position 0. pos = 0 - while chunk = file.read(32 * 4096) + while chunk = file.read(32 * 4096) @sftp.write(handle, chunk, pos) pos += chunk.size digester << chunk progress << chunk end end + @sftp.setstat(target_path, :permissions => @permissions) if @permissions # Upload all the digests. digester.each do |type, hexdigest| - puts "Uploading signature to #{@base_path}#{path}.#{type}" if Rake.application.options.trace - @sftp.open_handle("#{@base_path}#{path}.#{type}", "w") do |handle| + digest_file = "#{@base_path}#{path}.#{type}" + puts "Uploading signature to #{digest_file}" if Rake.application.options.trace + @sftp.open_handle(digest_file, "w") do |handle| @sftp.write(handle, "#{hexdigest} #{path}") end + @sftp.setstat(digest_file, :permissions => @permissions) if @permissions end end - end + end end end def mkpath(path) # To create a path, we need to create all its parent. # We use realpath to determine if the path already exists, # otherwise mkdir fails. puts "Creating path #{@base_path}" if Rake.application.options.trace path.split("/").inject(@base_path) do |base, part| - @sftp.realpath(base+part) rescue @sftp.mkdir base + part - "#{base}#{part}/" + combined = base + part + @sftp.realpath combined rescue @sftp.mkdir combined, {} + "#{combined}/" end end def close() @sftp.close