From 09e86662962d854f22de5205111d0ad4b724713e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 16:43:03 -0800 Subject: [PATCH] core: BoxAdd now works with HTTP URLs --- lib/vagrant/action/builtin/box_add.rb | 15 ++- lib/vagrant/util/downloader.rb | 111 +++++++++++------- .../vagrant/action/builtin/box_add_test.rb | 84 +++++++++++++ 3 files changed, 164 insertions(+), 46 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 4d591e7a9..97ed3d26d 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -57,12 +57,14 @@ module Vagrant version = env[:box_version] metadata = nil - if File.file?(url) - # TODO: What if file isn't valid JSON - # TODO: What if URL is in the "file:" format - File.open(url) do |f| + begin + metadata_path = download(url, env) + + File.open(metadata_path) do |f| metadata = BoxMetadata.new(f) end + ensure + metadata_path.delete if metadata_path.file? end metadata_version = metadata.version( @@ -316,7 +318,10 @@ module Vagrant end # TODO: do the HEAD request - true + output = d.head + match = output.scan(/^Content-Type: (.+?)$/).last + return false if !match + match.last.chomp == "application/json" end end end diff --git a/lib/vagrant/util/downloader.rb b/lib/vagrant/util/downloader.rb index 88a2e7d98..64028b18e 100644 --- a/lib/vagrant/util/downloader.rb +++ b/lib/vagrant/util/downloader.rb @@ -38,30 +38,8 @@ module Vagrant # If this method returns without an exception, the download # succeeded. An exception will be raised if the download failed. def download! - # Build the list of parameters to execute with cURL - options = [ - "--fail", - "--location", - "--max-redirs", "10", - "--user-agent", USER_AGENT, - "--output", @destination, - ] - - options += ["--cacert", @ca_cert] if @ca_cert - options += ["--continue-at", "-"] if @continue - options << "--insecure" if @insecure - options << "--cert" << @client_cert if @client_cert - options << @source - - # Specify some options for the subprocess - subprocess_options = {} - - # If we're in Vagrant, then we use the packaged CA bundle - if Vagrant.in_installer? - subprocess_options[:env] ||= {} - subprocess_options[:env]["CURL_CA_BUNDLE"] = - File.expand_path("cacert.pem", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]) - end + options, subprocess_options = self.options + options += ["--output", @destination] # This variable can contain the proc that'll be sent to # the subprocess execute. @@ -118,7 +96,42 @@ module Vagrant end end - # Add the subprocess options onto the options we'll execute with + @logger.info("Downloader starting download: ") + @logger.info(" -- Source: #{@source}") + @logger.info(" -- Destination: #{@destination}") + + begin + execute_curl(options, subprocess_options, &data_proc) + ensure + # If we're outputting to the UI, clear the output to + # avoid lingering progress meters. + if @ui + @ui.clear_line + + # Windows doesn't clear properly for some reason, so we just + # output one more newline. + @ui.info("") if Platform.windows? + end + end + + # Everything succeeded + true + end + + # Does a HEAD request of the URL and returns the output. + def head + options, subprocess_options = self.options + options.unshift("-I") + + @logger.info("HEAD: #{@source}") + result = execute_curl(options, subprocess_options) + result.stdout + end + + protected + + def execute_curl(options, subprocess_options, &data_proc) + options = options.dup options << subprocess_options # Create the callback that is called if we are interrupted @@ -128,10 +141,6 @@ module Vagrant interrupted = true end - @logger.info("Downloader starting download: ") - @logger.info(" -- Source: #{@source}") - @logger.info(" -- Destination: #{@destination}") - # Execute! result = Busy.busy(int_callback) do Subprocess.execute("curl", *options, &data_proc) @@ -140,16 +149,6 @@ module Vagrant # If the download was interrupted, then raise a specific error raise Errors::DownloaderInterrupted if interrupted - # If we're outputting to the UI, clear the output to - # avoid lingering progress meters. - if @ui - @ui.clear_line - - # Windows doesn't clear properly for some reason, so we just - # output one more newline. - @ui.info("") if Platform.windows? - end - # If it didn't exit successfully, we need to parse the data and # show an error message. if result.exit_code != 0 @@ -159,8 +158,38 @@ module Vagrant raise Errors::DownloaderError, :message => parts[1].chomp end - # Everything succeeded - true + result + end + + # Returns the varoius cURL and subprocess options. + # + # @return [Array] + def options + # Build the list of parameters to execute with cURL + options = [ + "--fail", + "--location", + "--max-redirs", "10", + "--user-agent", USER_AGENT, + ] + + options += ["--cacert", @ca_cert] if @ca_cert + options += ["--continue-at", "-"] if @continue + options << "--insecure" if @insecure + options << "--cert" << @client_cert if @client_cert + options << @source + + # Specify some options for the subprocess + subprocess_options = {} + + # If we're in Vagrant, then we use the packaged CA bundle + if Vagrant.in_installer? + subprocess_options[:env] ||= {} + subprocess_options[:env]["CURL_CA_BUNDLE"] = + File.expand_path("cacert.pem", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]) + end + + return [options, subprocess_options] end end end diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index cfe3ecedb..e900dff87 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -2,6 +2,7 @@ require "digest/sha1" require "pathname" require "tempfile" require "tmpdir" +require "webrick" require File.expand_path("../../../../base", __FILE__) @@ -32,6 +33,27 @@ describe Vagrant::Action::Builtin::BoxAdd do FileChecksum.new(path, Digest::SHA1).checksum end + def with_web_server(path) + tf = Tempfile.new("vagrant") + tf.close + + mime_types = WEBrick::HTTPUtils::DefaultMimeTypes + mime_types.store "json", "application/json" + + port = 3838 + server = WEBrick::HTTPServer.new( + AccessLog: [], + Logger: WEBrick::Log.new(tf.path, 7), + Port: port, + DocumentRoot: path.dirname.to_s, + MimeTypes: mime_types) + thr = Thread.new { server.start } + yield port + ensure + server.shutdown rescue nil + thr.join rescue nil + end + before do box_collection.stub(find: nil) end @@ -55,6 +77,25 @@ describe Vagrant::Action::Builtin::BoxAdd do subject.call(env) end + it "adds from HTTP URL" do + box_path = iso_env.box2_file(:virtualbox) + with_web_server(box_path) do |port| + env[:box_name] = "foo" + env[:box_url] = "http://127.0.0.1:#{port}/#{box_path.basename}" + + box_collection.should_receive(:add).with do |path, name, version| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo") + expect(version).to eq("0") + true + end.and_return(box) + + app.should_receive(:call).with(env) + + subject.call(env) + end + end + it "raises an error if the box already exists" do box_path = iso_env.box2_file(:virtualbox) @@ -93,6 +134,49 @@ describe Vagrant::Action::Builtin::BoxAdd do end context "with box metadata" do + it "adds from HTTP URL" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new(["vagrant", ".json"]).tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + f.close + end + + + md_path = Pathname.new(tf.path) + with_web_server(md_path) do |port| + env[:box_url] = "http://127.0.0.1:#{port}/#{md_path.basename}" + + box_collection.should_receive(:add).with do |path, name, version| + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + expect(checksum(path)).to eq(checksum(box_path)) + true + end.and_return(box) + + app.should_receive(:call).with(env) + + subject.call(env) + end + end + it "adds the latest version of a box with only one provider" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new("vagrant").tap do |f|