Author: assaf
Date: Mon May 19 20:31:38 2008
New Revision: 658093
URL: http://svn.apache.org/viewvc?rev=658093&view=rev
Log:
Added HTTP upload for remote repositories that support it (proxy, basic auth,
SSL, all other good things).
Modified:
incubator/buildr/trunk/CHANGELOG
incubator/buildr/trunk/lib/buildr/core/transports.rb
incubator/buildr/trunk/spec/transport_spec.rb
Modified: incubator/buildr/trunk/CHANGELOG
URL:
http://svn.apache.org/viewvc/incubator/buildr/trunk/CHANGELOG?rev=658093&r1=658092&r2=658093&view=diff
==============================================================================
--- incubator/buildr/trunk/CHANGELOG (original)
+++ incubator/buildr/trunk/CHANGELOG Mon May 19 20:31:38 2008
@@ -1,6 +1,6 @@
-1.3.1 (Pending)
-* Added: Reading files from SFTP server.
-* Added: jibx_bind method to use JiBX or Java<=>XML binding (by David
+1.3.1 (2008-05-15)
+* Added: Downloading files from SFTP server, uploading to HTTP.
+* Added: jibx_bind method to use JiBX for Java<=>XML binding (by David
Peterson).
* Changed: Upgraded to Net::SSH 2.0 and Net::SFTP 2.0.
* Fixed: BUILDR-67 HTTP GET now works with query parameters (Tommy Knowlton).
Modified: incubator/buildr/trunk/lib/buildr/core/transports.rb
URL:
http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/buildr/core/transports.rb?rev=658093&r1=658092&r2=658093&view=diff
==============================================================================
--- incubator/buildr/trunk/lib/buildr/core/transports.rb (original)
+++ incubator/buildr/trunk/lib/buildr/core/transports.rb Mon May 19 20:31:38
2008
@@ -22,6 +22,7 @@
require 'uri'
require 'digest/md5'
require 'digest/sha1'
+require 'stringio'
require 'tempfile'
require 'buildr/core/progressbar'
@@ -264,11 +265,11 @@
end
# :call-seq:
- # proxy_uri() => URI?
+ # proxy_uri => URI?
#
# Returns the proxy server to use. Obtains the proxy from the relevant
environment variable (e.g. HTTP_PROXY).
# Supports exclusions based on host name and port number from environment
variable NO_PROXY.
- def proxy_uri()
+ def proxy_uri
proxy = ENV["#{scheme.upcase}_PROXY"]
proxy = URI.parse(proxy) if String === proxy
excludes = ENV['NO_PROXY'].to_s.split(/\s*,\s*/).compact
@@ -288,56 +289,99 @@
# See URI::Generic#read
def read(options = nil, &block)
options ||= {}
- headers = { 'If-Modified-Since' =>
CGI.rfc1123_date(options[:modified].utc) } if options[:modified]
-
- if proxy = proxy_uri
- proxy = URI.parse(proxy) if String === proxy
- http = Net::HTTP.new(host, port, proxy.host, proxy.port, proxy.user,
proxy.password)
- else
- http = Net::HTTP.new(host, port)
+ connect do |http|
+ puts "Requesting #{self}" if Buildr.application.options.trace
+ headers = { 'If-Modified-Since' =>
CGI.rfc1123_date(options[:modified].utc) } if options[:modified]
+ request = Net::HTTP::Get.new(request_uri.empty? ? '/' : request_uri,
headers)
+ request.basic_auth self.user, self.password if self.user
+ http.request request do |response|
+ case response
+ when Net::HTTPNotModified
+ # No modification, nothing to do.
+ puts 'Not modified since last download' if
Buildr.application.options.trace
+ return nil
+ when Net::HTTPRedirection
+ # Try to download from the new URI, handle relative redirects.
+ puts "Redirected to #{response['Location']}" if
Buildr.application.options.trace
+ return (self + URI.parse(response['location'])).read(options,
&block)
+ when Net::HTTPOK
+ puts "Downloading #{self}" if verbose
+ result = nil
+ with_progress_bar options[:progress], path.split('/').last,
response.content_length do |progress|
+ if block
+ response.read_body do |chunk|
+ block.call chunk
+ progress << chunk
+ end
+ else
+ result = ''
+ response.read_body do |chunk|
+ result << chunk
+ progress << chunk
+ end
+ end
+ end
+ return result
+ when Net::HTTPNotFound
+ raise NotFoundError, "Looking for #{self} and all I got was a 404!"
+ else
+ raise RuntimeError, "Failed to download #{self}:
#{response.message}"
+ end
+ end
end
- http.use_ssl = true if self.instance_of? URI::HTTPS
+ end
+
+ private
+
+ def write_internal(options, &block) #:nodoc:
+ options ||= {}
+ connect do |http|
+ puts "Uploading to #{path}" if Buildr.application.options.trace
+ content = StringIO.new
+ while chunk = yield(32 * 4096)
+ content << chunk
+ end
+ headers = { 'Content-MD5'=>Digest::MD5.hexdigest(content.string) }
+ request = Net::HTTP::Put.new(request_uri.empty? ? '/' : request_uri,
headers)
+ request.basic_auth self.user, self.password if self.user
+ response = nil
+ with_progress_bar options[:progress], path.split('/').last,
content.size do |progress|
+ request.content_length = content.size
+ content.rewind
+ stream = Object.new
+ class << stream ; self ;end.send :define_method, :read do |count|
+ bytes = content.read(count)
+ progress << bytes if bytes
+ bytes
+ end
+ request.body_stream = stream
+ response = http.request(request)
+ end
- puts "Requesting #{self}" if Buildr.application.options.trace
- request = Net::HTTP::Get.new(request_uri.empty? ? '/' : request_uri,
headers)
- request.basic_auth self.user, self.password if self.user
- http.request request do |response|
case response
- #case response = http.request(request)
- when Net::HTTPNotModified
- # No modification, nothing to do.
- puts 'Not modified since last download' if
Buildr.application.options.trace
- return nil
when Net::HTTPRedirection
# Try to download from the new URI, handle relative redirects.
puts "Redirected to #{response['Location']}" if
Buildr.application.options.trace
- return (self + URI.parse(response['location'])).read(options, &block)
- when Net::HTTPOK
- puts "Downloading #{self}" if verbose
- result = nil
- with_progress_bar options[:progress], path.split('/').last,
response.content_length do |progress|
- if block
- response.read_body do |chunk|
- block.call chunk
- progress << chunk
- end
- else
- result = ''
- response.read_body do |chunk|
- result << chunk
- progress << chunk
- end
- end
- end
- return result
- when Net::HTTPNotFound
- raise NotFoundError, "Looking for #{self} and all I got was a 404!"
+ content.rewind
+ return (self +
URI.parse(response['location'])).write_internal(options) { |bytes|
content.read(bytes) }
+ when Net::HTTPSuccess
else
- raise RuntimeError, "Failed to download #{self}: #{response.message}"
+ raise RuntimeError, "Failed to upload #{self}: #{response.message}"
end
end
end
+ def connect
+ if proxy = proxy_uri
+ proxy = URI.parse(proxy) if String === proxy
+ http = Net::HTTP.new(host, port, proxy.host, proxy.port, proxy.user,
proxy.password)
+ else
+ http = Net::HTTP.new(host, port)
+ end
+ http.use_ssl = true if self.instance_of? URI::HTTPS
+ yield http
+ end
+
end
@@ -490,14 +534,14 @@
end
end
- def to_s()
+ def to_s
"file://#{host}#{path}"
end
# The URL path always starts with a backslash. On most operating systems
(Linux, Darwin, BSD) it points
# to the absolute path on the file system. But on Windows, it comes before
the drive letter, creating an
# unusable path, so real_path fixes that. Ugly but necessary hack.
- def real_path() #:nodoc:
+ def real_path #:nodoc:
RUBY_PLATFORM =~ /win32/ && path =~ /^\/[a-zA-Z]:\// ? path[1..-1] : path
end
Modified: incubator/buildr/trunk/spec/transport_spec.rb
URL:
http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/transport_spec.rb?rev=658093&r1=658092&r2=658093&view=diff
==============================================================================
--- incubator/buildr/trunk/spec/transport_spec.rb (original)
+++ incubator/buildr/trunk/spec/transport_spec.rb Mon May 19 20:31:38 2008
@@ -311,57 +311,81 @@
describe URI::HTTP, '#write' do
-end
-
-
-describe URI::SFTP, '#write' do
before do
- @uri = URI('sftp://john:[EMAIL PROTECTED]/path/readme')
@content = 'Readme. Please!'
+ @uri = URI('http://john:[EMAIL PROTECTED]/foo/bar/baz.jar')
+ @http = mock('Net::HTTP')
+ @http.stub!(:request).and_return(Net::HTTPOK.new(nil, nil, nil))
+ Net::HTTP.stub!(:new).and_return(@http)
+ end
- @ssh_session = mock('Net::SSH::Session')
- @sftp_session = mock('Net::SFTP::Session')
- @file_factory = mock('Net::SFTP::Operations::FileFactory')
- Net::SSH.stub!(:start).with('localhost', 'john', :password=>'secret',
:port=>22) do
-
Net::SFTP::Session.should_receive(:new).with(@ssh_session).and_yield(@sftp_session).and_return(@sftp_session)
- @sftp_session.should_receive(:connect!).and_return(@sftp_session)
- @sftp_session.should_receive(:loop)
- @sftp_session.stub!(:mkdir)
- @sftp_session.should_receive(:file).with.and_return(@file_factory)
- @file_factory.stub!(:open)
- @ssh_session.should_receive(:close)
- @ssh_session
- end
+ it 'should open connection to HTTP server' do
+ Net::HTTP.should_receive(:new).with('host.domain', 80).and_return(@http)
+ @uri.write @content
end
- it 'should open connection to SFTP server' do
+ it 'should use HTTP basic authentication' do
+ @http.should_receive(:request) do |request|
+ request['authorization'].should == ('Basic ' +
['john:secret'].pack('m').delete("\r\n"))
+ Net::HTTPOK.new(nil, nil, nil)
+ end
@uri.write @content
end
- it 'should create path recursively on SFTP server' do
- @sftp_session.should_receive(:mkdir).ordered.with('', {})
- @sftp_session.should_receive(:mkdir).ordered.with('/path', {})
+ it 'should use HTTPS if applicable' do
+ Net::HTTP.should_receive(:new).with('host.domain', 443).and_return(@http)
+ @http.should_receive(:use_ssl=).with(true)
+ URI(@uri.to_s.sub(/http/, 'https')).write @content
+ end
+
+ it 'should upload file with PUT request' do
+ @http.should_receive(:request) do |request|
+ request.should be_kind_of(Net::HTTP::Put)
+ Net::HTTPOK.new(nil, nil, nil)
+ end
@uri.write @content
end
- it 'should only create paths that don\'t exist' do
- @sftp_session.should_receive(:realpath).any_number_of_times
- @sftp_session.should_not_receive(:mkdir)
+ it 'should set Content-Length header' do
+ @http.should_receive(:request) do |request|
+ request.content_length.should == @content.size
+ Net::HTTPOK.new(nil, nil, nil)
+ end
@uri.write @content
end
- it 'should open file for writing' do
- @file_factory.should_receive(:open).with('/path/readme', 'w')
+ it 'should set Content-MD5 header' do
+ @http.should_receive(:request) do |request|
+ request['Content-MD5'].should == Digest::MD5.hexdigest(@content)
+ Net::HTTPOK.new(nil, nil, nil)
+ end
@uri.write @content
end
- it 'should write contents to file' do
- file = mock('Net::SFTP::Operations::File')
- file.should_receive(:write).with(@content)
- @file_factory.should_receive(:open).with('/path/readme',
'w').and_yield(file)
+ it 'should send entire content' do
+ @http.should_receive(:request) do |request|
+ body_stream = request.body_stream
+ body_stream.read(1024).should == @content
+ body_stream.read(1024).should be_nil
+ Net::HTTPOK.new(nil, nil, nil)
+ end
@uri.write @content
end
+ it 'should fail on 4xx response' do
+ @http.should_receive(:request) do |request|
+ Net::HTTPBadRequest.new(nil, nil, nil)
+ end
+ lambda { @uri.write @content }.should raise_error(RuntimeError, /failed to
upload/i)
+ end
+
+ it 'should fail on 5xx response' do
+ @http.should_receive(:request) do |request|
+ Net::HTTPServiceUnavailable.new(nil, nil, nil)
+ end
+ lambda { @uri.write @content }.should raise_error(RuntimeError, /failed to
upload/i)
+ end
+
end
@@ -411,3 +435,54 @@
content.should eql(@content)
end
end
+
+
+describe URI::SFTP, '#write' do
+ before do
+ @uri = URI('sftp://john:[EMAIL PROTECTED]/path/readme')
+ @content = 'Readme. Please!'
+
+ @ssh_session = mock('Net::SSH::Session')
+ @sftp_session = mock('Net::SFTP::Session')
+ @file_factory = mock('Net::SFTP::Operations::FileFactory')
+ Net::SSH.stub!(:start).with('localhost', 'john', :password=>'secret',
:port=>22) do
+
Net::SFTP::Session.should_receive(:new).with(@ssh_session).and_yield(@sftp_session).and_return(@sftp_session)
+ @sftp_session.should_receive(:connect!).and_return(@sftp_session)
+ @sftp_session.should_receive(:loop)
+ @sftp_session.stub!(:mkdir)
+ @sftp_session.should_receive(:file).with.and_return(@file_factory)
+ @file_factory.stub!(:open)
+ @ssh_session.should_receive(:close)
+ @ssh_session
+ end
+ end
+
+ it 'should open connection to SFTP server' do
+ @uri.write @content
+ end
+
+ it 'should create path recursively on SFTP server' do
+ @sftp_session.should_receive(:mkdir).ordered.with('', {})
+ @sftp_session.should_receive(:mkdir).ordered.with('/path', {})
+ @uri.write @content
+ end
+
+ it 'should only create paths that don\'t exist' do
+ @sftp_session.should_receive(:realpath).any_number_of_times
+ @sftp_session.should_not_receive(:mkdir)
+ @uri.write @content
+ end
+
+ it 'should open file for writing' do
+ @file_factory.should_receive(:open).with('/path/readme', 'w')
+ @uri.write @content
+ end
+
+ it 'should write contents to file' do
+ file = mock('Net::SFTP::Operations::File')
+ file.should_receive(:write).with(@content)
+ @file_factory.should_receive(:open).with('/path/readme',
'w').and_yield(file)
+ @uri.write @content
+ end
+
+end