diff options
author | Bryan McLellan <btm@opscode.com> | 2013-02-27 07:53:37 -0800 |
---|---|---|
committer | Bryan McLellan <btm@opscode.com> | 2013-02-27 07:53:37 -0800 |
commit | a82935b4a8e1e8174d02e83259f07ea9926dd007 (patch) | |
tree | daefb2c6df08276e822378e1fd5c9d6820f1d294 | |
parent | 464087c7a6da4ed51237cbb6ff39e45feeae0ecd (diff) | |
parent | 6f6fff817866e1fb5ab3c90862bfaa627b5378fd (diff) | |
download | chef-a82935b4a8e1e8174d02e83259f07ea9926dd007.tar.gz |
Merge branch 'CHEF-1031'
-rw-r--r-- | lib/chef/provider/remote_file.rb | 93 | ||||
-rw-r--r-- | lib/chef/provider/remote_file/ftp.rb | 95 | ||||
-rw-r--r-- | lib/chef/providers.rb | 2 | ||||
-rw-r--r-- | lib/chef/resource/remote_file.rb | 11 | ||||
-rw-r--r-- | spec/data/remote_file/nyan_cat.png.gz | bin | 0 -> 14944 bytes | |||
-rw-r--r-- | spec/functional/resource/remote_file_spec.rb | 43 | ||||
-rw-r--r-- | spec/tiny_server.rb | 21 | ||||
-rw-r--r-- | spec/unit/provider/remote_file/ftp_spec.rb | 117 | ||||
-rw-r--r-- | spec/unit/provider/remote_file_spec.rb | 65 | ||||
-rw-r--r-- | spec/unit/resource/remote_file_spec.rb | 18 |
10 files changed, 375 insertions, 90 deletions
diff --git a/lib/chef/provider/remote_file.rb b/lib/chef/provider/remote_file.rb index f985682441..4d1e696d0e 100644 --- a/lib/chef/provider/remote_file.rb +++ b/lib/chef/provider/remote_file.rb @@ -1,4 +1,5 @@ # +# Author:: Jesse Campbell (<hikeit@gmail.com>) # Author:: Adam Jacob (<adam@opscode.com>) # Copyright:: Copyright (c) 2008 Opscode, Inc. # License:: Apache License, Version 2.0 @@ -17,10 +18,9 @@ # require 'chef/provider/file' -require 'chef/rest' +require 'rest_client' require 'uri' require 'tempfile' -require 'net/https' class Chef class Provider @@ -40,24 +40,12 @@ class Chef Chef::Log.debug("#{@new_resource} checksum matches target checksum (#{@new_resource.checksum}) - not updating") else sources = @new_resource.source - source = sources.shift - begin - rest = Chef::REST.new(source, nil, nil, http_client_opts(source)) - raw_file = rest.streaming_request(rest.create_url(source), {}) - rescue SocketError, Errno::ECONNREFUSED, Timeout::Error, Net::HTTPFatalError => e - Chef::Log.debug("#{@new_resource} cannot be downloaded from #{source}") - if source = sources.shift - Chef::Log.debug("#{@new_resource} trying to download from another mirror") - retry - else - raise e - end - end + raw_file, raw_file_source = try_multiple_sources(sources) if matches_current_checksum?(raw_file) Chef::Log.debug "#{@new_resource} target and source checksums are the same - not updating" else description = [] - description << "copy file downloaded from #{@new_resource.source} into #{@new_resource.path}" + description << "copy file downloaded from #{raw_file_source} into #{@new_resource.path}" description << diff_current(raw_file.path) converge_by(description) do backup_new_resource @@ -102,38 +90,55 @@ class Chef end end - def source_file(source, current_checksum, &block) - if absolute_uri?(source) - fetch_from_uri(source, &block) - elsif !Chef::Config[:solo] - fetch_from_chef_server(source, current_checksum, &block) - else - fetch_from_local_cookbook(source, &block) - end - end + private - def http_client_opts(source) - opts={} - # CHEF-3140 - # 1. If it's already compressed, trying to compress it more will - # probably be counter-productive. - # 2. Some servers are misconfigured so that you GET $URL/file.tgz but - # they respond with content type of tar and content encoding of gzip, - # which tricks Chef::REST into decompressing the response body. In this - # case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz, - # which is not what you wanted. - if @new_resource.path =~ /gz$/ or source =~ /gz$/ - opts[:disable_gzip] = true + # Given an array of source uris, iterate through them until one does not fail + def try_multiple_sources(sources) + sources = sources.dup + source = sources.shift + begin + uri = URI.parse(source) + raw_file = grab_file_from_uri(uri) + rescue ArgumentError => e + raise e + rescue => e + if e.is_a?(RestClient::Exception) + error = "Request returned #{e.message}" + else + error = e.to_s + end + Chef::Log.debug("#{@new_resource} cannot be downloaded from #{source}: #{error}") + if source = sources.shift + Chef::Log.debug("#{@new_resource} trying to download from another mirror") + retry + else + raise e + end end - opts + if uri.userinfo + uri.password = "********" + end + return raw_file, uri.to_s end - private - - def absolute_uri?(source) - URI.parse(source).absolute? - rescue URI::InvalidURIError - false + # Given a source uri, return a Tempfile, or a File that acts like a Tempfile (close! method) + def grab_file_from_uri(uri) + if URI::HTTP === uri + #HTTP or HTTPS + raw_file = RestClient::Request.execute(:method => :get, :url => uri.to_s, :raw_response => true).file + elsif URI::FTP === uri + #FTP + raw_file = FTP::fetch(uri, @new_resource.ftp_active_mode) + elsif uri.scheme == "file" + #local/network file + raw_file = ::File.new(uri.path, "r") + def raw_file.close! + self.close + end + else + raise ArgumentError, "Invalid uri. Only http(s), ftp, and file are currently supported" + end + raw_file end end diff --git a/lib/chef/provider/remote_file/ftp.rb b/lib/chef/provider/remote_file/ftp.rb new file mode 100644 index 0000000000..3c5d3e0a91 --- /dev/null +++ b/lib/chef/provider/remote_file/ftp.rb @@ -0,0 +1,95 @@ +# +# Author:: Jesse Campbell (<hikeit@gmail.com>) +# Copyright:: Copyright (c) 2013 Jesse Campbell +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'uri' +require 'tempfile' +require 'net/ftp' +require 'chef/provider/remote_file' + +class Chef + class Provider + class RemoteFile + class FTP + + # Fetches the file at uri using Net::FTP, returning a Tempfile + def self.fetch(uri, ftp_active_mode) + self.new(uri, ftp_active_mode).fetch() + end + + # Parse the uri into instance variables + def initialize(uri, ftp_active_mode) + @directories, @filename = parse_path(uri.path) + @typecode = uri.typecode + # Only support ascii and binary types + if @typecode && /\A[ai]\z/ !~ @typecode + raise ArgumentError, "invalid typecode: #{@typecode.inspect}" + end + @ftp_active_mode = ftp_active_mode + @hostname = uri.hostname + @port = uri.port + if uri.userinfo + @user = URI.unescape(uri.user) + @pass = URI.unescape(uri.password) + else + @user = 'anonymous' + @pass = nil + end + end + + # Fetches using Net::FTP, returns a Tempfile with the content + def fetch() + tempfile = Tempfile.new(@filename) + + # The access sequence is defined by RFC 1738 + ftp = Net::FTP.new + ftp.connect(@hostname, @port) + ftp.passive = !@ftp_active_mode + ftp.login(@user, @pass) + @directories.each do |cwd| + ftp.voidcmd("CWD #{cwd}") + end + if @typecode + ftp.voidcmd("TYPE #{@typecode.upcase}") + end + ftp.getbinaryfile(@filename, tempfile.path) + ftp.close + + tempfile + end + + private + + def parse_path(path) + path = path.sub(%r{\A/}, '%2F') # re-encode the beginning slash because uri library decodes it. + directories = path.split(%r{/}, -1) + directories.each {|d| + d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") } + } + unless filename = directories.pop + raise ArgumentError, "no filename: #{path.inspect}" + end + if filename.length == 0 || filename.end_with?( "/" ) + raise ArgumentError, "no filename: #{path.inspect}" + end + return directories, filename + end + + end + end + end +end diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index ae95632eaa..3a40c542ec 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -101,5 +101,7 @@ require 'chef/provider/mount/windows' require 'chef/provider/deploy/revision' require 'chef/provider/deploy/timestamped' +require 'chef/provider/remote_file/ftp' + require "chef/provider/lwrp_base" require 'chef/provider/registry_key' diff --git a/lib/chef/resource/remote_file.rb b/lib/chef/resource/remote_file.rb index 2798cba3f2..524e00186b 100644 --- a/lib/chef/resource/remote_file.rb +++ b/lib/chef/resource/remote_file.rb @@ -32,7 +32,8 @@ class Chef super @resource_name = :remote_file @action = "create" - @source = nil + @source = [] + @ftp_active_mode = false @provider = Chef::Provider::RemoteFile end @@ -54,6 +55,14 @@ class Chef ) end + def ftp_active_mode(args=nil) + set_or_return( + :ftp_active_mode, + args, + :kind_of => [ TrueClass, FalseClass ] + ) + end + def after_created validate_source(@source) end diff --git a/spec/data/remote_file/nyan_cat.png.gz b/spec/data/remote_file/nyan_cat.png.gz Binary files differnew file mode 100644 index 0000000000..efa9d4427a --- /dev/null +++ b/spec/data/remote_file/nyan_cat.png.gz diff --git a/spec/functional/resource/remote_file_spec.rb b/spec/functional/resource/remote_file_spec.rb index 57a5321ea2..fbb921d48c 100644 --- a/spec/functional/resource/remote_file_spec.rb +++ b/spec/functional/resource/remote_file_spec.rb @@ -23,14 +23,6 @@ describe Chef::Resource::RemoteFile do include_context Chef::Resource::File let(:file_base) { "remote_file_spec" } - let(:source) { 'http://localhost:9000/nyan_cat.png' } - let(:expected_content) do - content = File.open(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png'), "rb") do |f| - f.read - end - content.force_encoding(Encoding::BINARY) if content.respond_to?(:force_encoding) - content - end def create_resource node = Chef::Node.new @@ -71,13 +63,44 @@ describe Chef::Resource::RemoteFile do f.read end } + @api.get("/nyan_cat.png.gz", 200, nil, { 'Content-Type' => 'application/gzip', 'Content-Encoding' => 'gzip' } ) { + File.open(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png.gz'), "rb") do |f| + f.read + end + } end after(:all) do @server.stop end - it_behaves_like "a file resource" + context "when using normal encoding" do + let(:source) { 'http://localhost:9000/nyan_cat.png' } + let(:expected_content) do + content = File.open(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png'), "rb") do |f| + f.read + end + content.force_encoding(Encoding::BINARY) if content.respond_to?(:force_encoding) + content + end + + it_behaves_like "a file resource" - it_behaves_like "a securable resource with reporting" + it_behaves_like "a securable resource with reporting" + end + + context "when using gzip encoding" do + let(:source) { 'http://localhost:9000/nyan_cat.png.gz' } + let(:expected_content) do + content = File.open(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png.gz'), "rb") do |f| + f.read + end + content.force_encoding(Encoding::BINARY) if content.respond_to?(:force_encoding) + content + end + + it_behaves_like "a file resource" + + it_behaves_like "a securable resource with reporting" + end end diff --git a/spec/tiny_server.rb b/spec/tiny_server.rb index 9eecf13cec..eb5f5c0fc0 100644 --- a/spec/tiny_server.rb +++ b/spec/tiny_server.rb @@ -127,20 +127,20 @@ module TinyServer @routes = {GET => [], PUT => [], POST => [], DELETE => []} end - def get(path, response_code, data=nil, &block) - @routes[GET] << Route.new(path, Response.new(response_code,data, &block)) + def get(path, response_code, data=nil, headers=nil, &block) + @routes[GET] << Route.new(path, Response.new(response_code, data, headers, &block)) end - def put(path, response_code, data=nil, &block) - @routes[PUT] << Route.new(path, Response.new(response_code,data, &block)) + def put(path, response_code, data=nil, headers=nil, &block) + @routes[PUT] << Route.new(path, Response.new(response_code, data, headers, &block)) end - def post(path, response_code, data=nil, &block) - @routes[POST] << Route.new(path, Response.new(response_code,data, &block)) + def post(path, response_code, data=nil, headers=nil, &block) + @routes[POST] << Route.new(path, Response.new(response_code, data, headers, &block)) end - def delete(path, response_code, data=nil, &block) - @routes[DELETE] << Route.new(path, Response.new(response_code,data, &block)) + def delete(path, response_code, data=nil, headers=nil, &block) + @routes[DELETE] << Route.new(path, Response.new(response_code, data, headers, &block)) end def call(env) @@ -183,14 +183,15 @@ module TinyServer class Response HEADERS = {'Content-Type' => 'application/json'} - def initialize(response_code=200,data=nil, &block) + def initialize(response_code=200, data=nil, headers=nil, &block) @response_code, @data = response_code, data + @response_headers = headers ? HEADERS.merge(headers) : HEADERS @block = block_given? ? block : nil end def call data = @data || @block.call - [@response_code, HEADERS, Array(data)] + [@response_code, @response_headers, Array(data)] end def to_s diff --git a/spec/unit/provider/remote_file/ftp_spec.rb b/spec/unit/provider/remote_file/ftp_spec.rb new file mode 100644 index 0000000000..03ef9424dd --- /dev/null +++ b/spec/unit/provider/remote_file/ftp_spec.rb @@ -0,0 +1,117 @@ +# +# Author:: Jesse Campbell (<hikeit@gmail.com>) +# Copyright:: Copyright (c) 2013 Jesse Campbell +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +describe Chef::Provider::RemoteFile::FTP, "fetch" do + before(:each) do + @ftp = mock(Net::FTP, { }) + Net::FTP.stub!(:new).and_return(@ftp) + @ftp.stub!(:connect) + @ftp.stub!(:login) + @ftp.stub!(:voidcmd) + @ftp.stub!(:getbinaryfile) + @ftp.stub!(:close) + @ftp.stub!(:passive=) + @tempfile = Tempfile.new("chef-rspec-ftp_spec-line#{__LINE__}--") + Tempfile.stub!(:new).and_return(@tempfile) + @uri = URI.parse("ftp://opscode.com/seattle.txt") + end + + describe "when parsing the uri" do + it "throws an argument exception when no path is given" do + @uri.path = "" + lambda { Chef::Provider::RemoteFile::FTP.new(@uri, false) }.should raise_error(ArgumentError) + end + + it "throws an argument exception when only a / is given" do + @uri.path = "/" + lambda { Chef::Provider::RemoteFile::FTP.new(@uri, false) }.should raise_error(ArgumentError) + end + + it "throws an argument exception when no filename is given" do + @uri.path = "/the/whole/path/" + lambda { Chef::Provider::RemoteFile::FTP.new(@uri, false) }.should raise_error(ArgumentError) + end + + it "throws an argument exception when the typecode is invalid" do + @uri.typecode = "d" + lambda { Chef::Provider::RemoteFile::FTP.new(@uri, false) }.should raise_error(ArgumentError) + end + end + + describe "when connecting to the remote" do + it "should connect to the host from the uri on the default port 21" do + @ftp.should_receive(:connect).with("opscode.com", 21) + Chef::Provider::RemoteFile::FTP.fetch(@uri, false).close! + end + + it "should connect on an alternate port when one is provided" do + @ftp.should_receive(:connect).with("opscode.com", 8021) + Chef::Provider::RemoteFile::FTP.fetch(URI.parse("ftp://opscode.com:8021/seattle.txt"), false).close! + end + + it "should set passive true when ftp_active_mode is false" do + @ftp.should_receive(:passive=).with(true) + Chef::Provider::RemoteFile::FTP.fetch(@uri, false).close! + end + + it "should set passive false when ftp_active_mode is false" do + @ftp.should_receive(:passive=).with(false) + Chef::Provider::RemoteFile::FTP.fetch(@uri, true).close! + end + + it "should use anonymous ftp when no userinfo is provided" do + @ftp.should_receive(:login).with("anonymous", nil) + Chef::Provider::RemoteFile::FTP.fetch(@uri, false).close! + end + + it "should use authenticated ftp when userinfo is provided" do + @ftp.should_receive(:login).with("the_user", "the_password") + Chef::Provider::RemoteFile::FTP.fetch(URI.parse("ftp://the_user:the_password@opscode.com/seattle.txt"), false).close! + end + + it "should accept ascii for the typecode" do + @uri.typecode = "a" + @ftp.should_receive(:voidcmd).with("TYPE A").once + Chef::Provider::RemoteFile::FTP.fetch(@uri, false).close! + end + + it "should accept image for the typecode" do + @uri.typecode = "i" + @ftp.should_receive(:voidcmd).with("TYPE I").once + Chef::Provider::RemoteFile::FTP.fetch(@uri, false).close! + end + + it "should fetch the file from the correct path" do + @ftp.should_receive(:voidcmd).with("CWD the").once + @ftp.should_receive(:voidcmd).with("CWD whole").once + @ftp.should_receive(:voidcmd).with("CWD path").once + @ftp.should_receive(:getbinaryfile).with("seattle.txt", @tempfile.path) + Chef::Provider::RemoteFile::FTP.fetch(URI.parse("ftp://opscode.com/the/whole/path/seattle.txt"), false).close! + end + end + + describe "when it finishes downloading" do + it "should return a tempfile" do + ftpfile = Chef::Provider::RemoteFile::FTP.fetch(@uri, false) + ftpfile.should equal @tempfile + ftpfile.close! + end + end +end diff --git a/spec/unit/provider/remote_file_spec.rb b/spec/unit/provider/remote_file_spec.rb index 78d7e77121..149f0462c3 100644 --- a/spec/unit/provider/remote_file_spec.rb +++ b/spec/unit/provider/remote_file_spec.rb @@ -46,11 +46,9 @@ describe Chef::Provider::RemoteFile, "action_create" do describe "when fetching the file from the remote" do before(:each) do @tempfile = Tempfile.new("chef-rspec-remote_file_spec-line#{__LINE__}--") + @rawresp = RestClient::RawResponse.new(@tempfile, nil, nil) - @rest = mock(Chef::REST, { }) - Chef::REST.stub!(:new).and_return(@rest) - @rest.stub!(:streaming_request).and_return(@tempfile) - @rest.stub!(:create_url) { |url| url } + RestClient::Request.stub!(:execute).and_return(@rawresp) @resource.cookbook_name = "monkey" @provider.stub!(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") @@ -81,19 +79,19 @@ describe Chef::Provider::RemoteFile, "action_create" do shared_examples_for "source specified with multiple URIs" do it "should try to download the next URI when the first one fails" do - @rest.should_receive(:streaming_request).with("http://foo", {}).once.and_raise(SocketError) - @rest.should_receive(:streaming_request).with("http://bar", {}).once.and_return(@tempfile) + RestClient::Request.should_receive(:execute).with(:method => :get, :url => "http://foo", :raw_response => true).once.and_raise(SocketError) + RestClient::Request.should_receive(:execute).with(:method => :get, :url => "http://bar", :raw_response => true).once.and_return(@rawresp) @provider.run_action(:create) end it "should raise an exception when all the URIs fail" do - @rest.should_receive(:streaming_request).with("http://foo", {}).once.and_raise(SocketError) - @rest.should_receive(:streaming_request).with("http://bar", {}).once.and_raise(SocketError) + RestClient::Request.should_receive(:execute).with(:method => :get, :url => "http://foo", :raw_response => true).once.and_raise(SocketError) + RestClient::Request.should_receive(:execute).with(:method => :get, :url => "http://bar", :raw_response => true).once.and_raise(SocketError) lambda { @provider.run_action(:create) }.should raise_error(SocketError) end it "should download from only one URI when the first one works" do - @rest.should_receive(:streaming_request).once.and_return(@tempfile) + RestClient::Request.should_receive(:execute).once.and_return(@rawresp) @provider.run_action(:create) end @@ -123,7 +121,7 @@ describe Chef::Provider::RemoteFile, "action_create" do end it "does not download the file" do - @rest.should_not_receive(:fetch).with("http://opscode.com/seattle.txt").and_return(@tempfile) + RestClient::Request.should_not_receive(:execute).with("http://opscode.com/seattle.txt").and_return(@tempfile) @provider.run_action(:create) end @@ -140,7 +138,7 @@ describe Chef::Provider::RemoteFile, "action_create" do end it "should not download the file if the checksum is a partial match from the beginning" do - @rest.should_not_receive(:fetch).with("http://opscode.com/seattle.txt").and_return(@tempfile) + @rawresp.should_not_receive(:fetch).with("http://opscode.com/seattle.txt").and_return(@tempfile) @provider.run_action(:create) end @@ -154,7 +152,7 @@ describe Chef::Provider::RemoteFile, "action_create" do describe "and the existing file doesn't match the given checksum" do it "downloads the file" do @resource.checksum("this hash doesn't match") - @rest.should_receive(:streaming_request).with("http://opscode.com/seattle.txt", {}).and_return(@tempfile) + RestClient::Request.should_receive(:execute).with(:method => :get, :url => "http://opscode.com/seattle.txt", :raw_response => true).and_return(@rawresp) @provider.stub!(:update_new_file_state) @provider.run_action(:create) end @@ -162,7 +160,7 @@ describe Chef::Provider::RemoteFile, "action_create" do it "does not consider the checksum a match if the matching string is offset" do # i.e., the existing file is "0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa" @resource.checksum("fd012fd") - @rest.should_receive(:streaming_request).with("http://opscode.com/seattle.txt", {}).and_return(@tempfile) + RestClient::Request.should_receive(:execute).with(:method => :get, :url => "http://opscode.com/seattle.txt", :raw_response => true).and_return(@rawresp) @provider.stub!(:update_new_file_state) @provider.run_action(:create) end @@ -173,7 +171,7 @@ describe Chef::Provider::RemoteFile, "action_create" do describe "and the resource doesn't specify a checksum" do it "should download the file from the remote URL" do @resource.checksum(nil) - @rest.should_receive(:streaming_request).with("http://opscode.com/seattle.txt", {}).and_return(@tempfile) + RestClient::Request.should_receive(:execute).with(:method => :get, :url => "http://opscode.com/seattle.txt", :raw_response => true).and_return(@rawresp) @provider.run_action(:create) end end @@ -190,7 +188,7 @@ describe Chef::Provider::RemoteFile, "action_create" do context "and the target file is a tarball" do before do @resource.path(File.expand_path(File.join(CHEF_SPEC_DATA, "seattle.tar.gz"))) - Chef::REST.should_receive(:new).with("http://opscode.com/seattle.txt", nil, nil, :disable_gzip => true).and_return(@rest) + RestClient::Request.should_receive(:execute).with(:method => :get, :url => "http://opscode.com/seattle.txt", :raw_response => true).and_return(@rawresp) end it "disables gzip in the http client" do @@ -202,7 +200,7 @@ describe Chef::Provider::RemoteFile, "action_create" do context "and the source appears to be a tarball" do before do @resource.source("http://example.com/tarball.tgz") - Chef::REST.should_receive(:new).with("http://example.com/tarball.tgz", nil, nil, :disable_gzip => true).and_return(@rest) + RestClient::Request.should_receive(:execute).with(:method => :get, :url => "http://example.com/tarball.tgz", :raw_response => true).and_return(@rawresp) end it "disables gzip in the http client" do @@ -210,17 +208,45 @@ describe Chef::Provider::RemoteFile, "action_create" do end end + context "and the uri scheme is ftp" do + before do + @resource.source("ftp://opscode.com/seattle.txt") + end + + it "should fetch with ftp in passive mode" do + Chef::Provider::RemoteFile::FTP.should_receive(:fetch).with(URI.parse("ftp://opscode.com/seattle.txt"), false).and_return(@tempfile) + @provider.run_action(:create) + end + + it "should fetch with ftp in active mode" do + @resource.ftp_active_mode true + Chef::Provider::RemoteFile::FTP.should_receive(:fetch).with(URI.parse("ftp://opscode.com/seattle.txt"), true).and_return(@tempfile) + @provider.run_action(:create) + end + end + + context "and the uri scheme is file" do + before do + @resource.source("file:///nyan_cat.png") + end + + it "should load the local file" do + File.should_receive(:new).with("/nyan_cat.png", "r").and_return(File.open(File.join(CHEF_SPEC_DATA, "remote_file", "nyan_cat.png"), "r")) + @provider.run_action(:create) + end + end + it "should raise an exception if it's any other kind of retriable response than 304" do r = Net::HTTPMovedPermanently.new("one", "two", "three") e = Net::HTTPRetriableError.new("301", r) - @rest.stub!(:streaming_request).and_raise(e) + RestClient::Request.stub!(:execute).and_raise(e) lambda { @provider.run_action(:create) }.should raise_error(Net::HTTPRetriableError) end it "should raise an exception if anything else happens" do r = Net::HTTPBadRequest.new("one", "two", "three") e = Net::HTTPServerException.new("fake exception", r) - @rest.stub!(:streaming_request).and_raise(e) + RestClient::Request.stub!(:execute).and_raise(e) lambda { @provider.run_action(:create) }.should raise_error(Net::HTTPServerException) end @@ -316,9 +342,6 @@ describe Chef::Provider::RemoteFile, "action_create" do @provider.should_receive(:set_all_access_controls).and_return(true) @provider.run_action(:create) end - - end - end end diff --git a/spec/unit/resource/remote_file_spec.rb b/spec/unit/resource/remote_file_spec.rb index d91f80d1a7..064c97fc27 100644 --- a/spec/unit/resource/remote_file_spec.rb +++ b/spec/unit/resource/remote_file_spec.rb @@ -42,7 +42,7 @@ describe Chef::Resource::RemoteFile do describe "source" do it "does not have a default value for 'source'" do - @resource.source.should be_nil + @resource.source.should eql([]) end it "should accept a URI for the remote file source" do @@ -64,7 +64,7 @@ describe Chef::Resource::RemoteFile do lambda { @resource.source("not-a-uri") }.should raise_error(Chef::Exceptions::InvalidRemoteFileURI) end - it "should raise and exception when source is an empty array" do + it "should raise an exception when source is an empty array" do lambda { @resource.source([]) }.should raise_error(ArgumentError) end @@ -80,7 +80,18 @@ describe Chef::Resource::RemoteFile do @resource.checksum.should == nil end end - + + describe "ftp_active_mode" do + it "should accept a boolean for the ftp_active_mode object" do + @resource.ftp_active_mode true + @resource.ftp_active_mode.should be_true + end + + it "should default to false" do + @resource.ftp_active_mode.should be_false + end + end + describe "when it has group, mode, owner, source, and checksum" do before do if Chef::Platform.windows? @@ -119,5 +130,4 @@ describe Chef::Resource::RemoteFile do end end end - end |