# Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: BUSL-1.1 require "digest/sha1" require "pathname" require "tempfile" require "tmpdir" require "webrick" require "fake_ftp" require File.expand_path("../../../../base", __FILE__) require "vagrant/util/file_checksum" describe Vagrant::Action::Builtin::BoxAdd, :skip_windows, :bsdtar do include_context "unit" let(:app) { lambda { |env| } } let(:env) { { box_collection: box_collection, hook: Proc.new { |name, env| env }, tmp_path: Pathname.new(Dir.mktmpdir("vagrant-test-builtin-box-add")), ui: Vagrant::UI::Silent.new, } } subject { described_class.new(app, env) } let(:box_collection) { double("box_collection") } let(:iso_env) { isolated_environment } let(:box) do box_dir = iso_env.box3("foo", "1.0", :virtualbox) Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) end after do FileUtils.rm_rf(env[:tmp_path]) end # Helper to quickly SHA1 checksum a path def checksum(path) FileChecksum.new(path, Digest::SHA1).checksum end def with_ftp_server(path, **opts) path = Pathname.new(path) port = nil server = nil with_random_port do |port1, port2| port = port1 server = FakeFtp::Server.new(port1, port2) end server.add_file(path.basename.to_s, path.read) server.start yield port ensure server.stop rescue nil end def with_web_server(path, **opts) tf = Tempfile.new("vagrant-web-server") tf.close opts[:json_type] ||= "application/json" mime_types = WEBrick::HTTPUtils::DefaultMimeTypes mime_types.store "json", opts[:json_type] 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 tf.unlink server.shutdown rescue nil thr.join rescue nil end before do allow(box_collection).to receive(:find).and_return(nil) end context "the download location is locked" do let(:box_path) { iso_env.box2_file(:virtualbox) } let(:mutex_path) { env[:tmp_path].join("box" + Digest::SHA1.hexdigest("file://" + box_path.to_s) + ".lock").to_s } before do # Lock file @f = File.open(mutex_path, "w+") @f.flock(File::LOCK_EX|File::LOCK_NB) end after { @f.close } it "raises a download error" do env[:box_name] = "foo" env[:box_url] = box_path.to_s expect { subject.call(env) }. to raise_error(Vagrant::Errors::DownloadAlreadyInProgress) end end context "with box file directly" do it "adds it" do box_path = iso_env.box2_file(:virtualbox) env[:box_name] = "foo" env[:box_url] = box_path.to_s expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo") expect(version).to eq("0") expect(opts[:metadata_url]).to be_nil true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end it "adds from multiple URLs" do box_path = iso_env.box2_file(:virtualbox) env[:box_name] = "foo" env[:box_url] = [ "/foo/bar/baz", box_path.to_s, ] expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo") expect(version).to eq("0") expect(opts[:metadata_url]).to be_nil true }.and_return(box) expect(app).to receive(:call).with(env) 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}" expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo") expect(version).to eq("0") expect(opts[:metadata_url]).to be_nil true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end end it "adds from FTP URL" do box_path = iso_env.box2_file(:virtualbox) with_ftp_server(box_path) do |port| env[:box_name] = "foo" env[:box_url] = "ftp://127.0.0.1:#{port}/#{box_path.basename}" expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo") expect(version).to eq("0") expect(opts[:metadata_url]).to be_nil true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end end it "raises an error if no name is given" do box_path = iso_env.box2_file(:virtualbox) env[:box_url] = box_path.to_s expect(box_collection).to receive(:add).never expect(app).to receive(:call).never expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxAddNameRequired) end it "raises an error if the box already exists" do box_path = iso_env.box2_file(:virtualbox) env[:box_name] = "foo" env[:box_url] = box_path.to_s env[:box_provider] = "virtualbox" expect(box_collection).to receive(:find).with( "foo", ["virtualbox"], "0", nil).and_return(box) expect(box_collection).to receive(:add).never expect(app).to receive(:call).never expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxAlreadyExists) end it "raises an error if checksum specified and doesn't match" do box_path = iso_env.box2_file(:virtualbox) env[:box_name] = "foo" env[:box_url] = box_path.to_s env[:box_checksum] = checksum(box_path) + "A" env[:box_checksum_type] = "sha1" expect(box_collection).to receive(:add).never expect(app).to receive(:call).never expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxChecksumMismatch) end it "strips space if checksum specified ends or begins with blank space" do box_path = iso_env.box2_file(:virtualbox) box = double( name: "foo", version: "1.2.3", provider: "virtualbox", ) env[:box_name] = "foo" env[:box_url] = box_path.to_s env[:box_checksum] = " #{checksum(box_path)} " env[:box_checksum_type] = "sha1" expect(box_collection).to receive(:add).and_return(box) expect { subject.call(env) }.to_not raise_error end it "ignores checksums if empty string" 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}" env[:box_checksum] = "" env[:box_checksum_type] = "" expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo") expect(version).to eq("0") expect(opts[:metadata_url]).to be_nil true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end end it "does not raise an error if the checksum has different case" do box_path = iso_env.box2_file(:virtualbox) box = double( name: "foo", version: "1.2.3", provider: "virtualbox", ) env[:box_name] = box.name env[:box_url] = box_path.to_s env[:box_checksum] = checksum(box_path) env[:box_checksum_type] = "sha1" # Convert to a different case env[:box_checksum].upcase! expect(box_collection).to receive(:add).and_return(box) expect { subject.call(env) }.to_not raise_error end it "raises an error if the box path doesn't exist" do box_path = iso_env.box2_file(:virtualbox) env[:box_name] = "foo" env[:box_url] = box_path.to_s + "nope" expect(box_collection).to receive(:add).never expect(app).to receive(:call).never expect { subject.call(env) }. to raise_error(Vagrant::Errors::DownloaderError) end it "raises an error if a version was specified" do box_path = iso_env.box2_file(:virtualbox) env[:box_name] = "foo" env[:box_url] = box_path.to_s env[:box_version] = "1" expect(box_collection).to receive(:add).never expect(app).to receive(:call).never expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxAddDirectVersion) end it "force adds if exists and specified" do box_path = iso_env.box2_file(:virtualbox) env[:box_force] = true env[:box_name] = "foo" env[:box_url] = box_path.to_s env[:box_provider] = "virtualbox" allow(box_collection).to receive(:find).and_return(box) expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo") expect(version).to eq("0") expect(opts[:metadata_url]).to be_nil true }.and_return(box) expect(app).to receive(:call).with(env).once subject.call(env) end context "with a box name accidentally set as a URL" do it "displays a warning to the user" do box_path = iso_env.box2_file(:virtualbox) port = 9999 box_url_name = "http://127.0.0.1:#{port}/#{box_path.basename}" env[:box_name] = box_url_name expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(name).to eq(box_url_name) expect(version).to eq("0") expect(opts[:metadata_url]).to be_nil true }.and_return(box) expect(app).to receive(:call).with(env) expect(env[:ui]).to receive(:warn).and_call_original .with(/It looks like you attempted to add a box with a URL for the name/) subject.call(env) end end context "with a box name containing invalid URI characters" do it "should not raise an error" do box_path = iso_env.box2_file(:virtualbox) box_url_name = "box name with spaces" env[:box_name] = box_url_name expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(name).to eq(box_url_name) expect(version).to eq("0") expect(opts[:metadata_url]).to be_nil true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end end context "with URL containing credentials" do let(:username){ "box-username" } let(:password){ "box-password" } it "scrubs credentials in output" do box_path = iso_env.box2_file(:virtualbox) with_web_server(box_path) do |port| env[:box_name] = "foo" env[:box_url] = "http://#{username}:#{password}@127.0.0.1:#{port}/#{box_path.basename}" expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo") expect(version).to eq("0") expect(opts[:metadata_url]).to be_nil true }.and_return(box) allow(env[:ui]).to receive(:detail).and_call_original expect(env[:ui]).to receive(:detail).with(%r{.*http://(?!#{username}).+?:(?!#{password}).+?@127\.0\.0\.1:#{port}/#{box_path.basename}.*}). and_call_original expect(app).to receive(:call).with(env) subject.call(env) end end end end context "with box metadata" do it "adds from HTTP URL" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-test-box-http-url", ".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}" expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(checksum(path)).to eq(checksum(box_path)) expect(opts[:metadata_url]).to eq(env[:box_url]) true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end end it "adds from HTTP URL with complex JSON mime type" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-test-http-json", ".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 opts = { json_type: "application/json; charset=utf-8" } md_path = Pathname.new(tf.path) with_web_server(md_path, **opts) do |port| env[:box_url] = "http://127.0.0.1:#{port}/#{md_path.basename}" expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(checksum(path)).to eq(checksum(box_path)) expect(opts[:metadata_url]).to eq(env[:box_url]) true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end end it "adds from shorthand path" do box_path = iso_env.box2_file(:virtualbox) td = Pathname.new(Dir.mktmpdir("vagrant-test-box-add-shorthand-path")) tf = td.join("mitchellh", "precise64.json") tf.dirname.mkpath tf.open("w") do |f| f.write(<<-RAW) { "name": "mitchellh/precise64", "versions": [ { "version": "0.5" }, { "version": "0.7", "providers": [ { "name": "virtualbox", "url": "#{box_path}" } ] } ] } RAW end with_web_server(tf.dirname) do |port| url = "http://127.0.0.1:#{port}" env[:box_url] = "mitchellh/precise64.json" expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(name).to eq("mitchellh/precise64") expect(version).to eq("0.7") expect(checksum(path)).to eq(checksum(box_path)) expect(opts[:metadata_url]).to eq( "#{url}/#{env[:box_url]}") true }.and_return(box) expect(app).to receive(:call).with(env) with_temp_env("VAGRANT_SERVER_URL" => url) do subject.call(env) end end FileUtils.rm_rf(td) end it "adds from API endpoint when available" do box_path = iso_env.box2_file(:virtualbox) td = Pathname.new(Dir.mktmpdir("vagrant-test-box-api-endpoint")) tf = td.join("mitchellh", "precise64.json") tf.dirname.mkpath tf.open("w") do |f| f.write(<<-RAW) { "name": "mitchellh/precise64", "versions": [ { "version": "0.5" }, { "version": "0.7", "providers": [ { "name": "virtualbox", "url": "#{box_path}" } ] } ] } RAW end apif = td.join("api", "v2", "vagrant", "mitchellh", "precise64.json") apif.dirname.mkpath apif.open("w") do |f| f.write(<<-RAW) { "name": "mitchellh/precise64", "versions": [ { "version": "0.5" }, { "version": "0.8", "providers": [ { "name": "virtualbox", "url": "#{box_path}" } ] } ] } RAW end with_web_server(tf.dirname) do |port| url = "http://127.0.0.1:#{port}" env[:box_url] = "mitchellh/precise64.json" env[:box_server_url] = url expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(name).to eq("mitchellh/precise64") expect(version).to eq("0.8") expect(checksum(path)).to eq(checksum(box_path)) expect(opts[:metadata_url]).to eq( "#{url}/api/v2/vagrant/#{env[:box_url]}") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end FileUtils.rm_rf(td) end it "add from shorthand path with configured server url" do box_path = iso_env.box2_file(:virtualbox) td = Pathname.new(Dir.mktmpdir("vagrant-test-box-add-server-url")) tf = td.join("mitchellh", "precise64.json") tf.dirname.mkpath tf.open("w") do |f| f.write(<<-RAW) { "name": "mitchellh/precise64", "versions": [ { "version": "0.5" }, { "version": "0.7", "providers": [ { "name": "virtualbox", "url": "#{box_path}" } ] } ] } RAW end with_web_server(tf.dirname) do |port| url = "http://127.0.0.1:#{port}" env[:box_url] = "mitchellh/precise64.json" env[:box_server_url] = url expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(name).to eq("mitchellh/precise64") expect(version).to eq("0.7") expect(checksum(path)).to eq(checksum(box_path)) expect(opts[:metadata_url]).to eq( "#{url}/#{env[:box_url]}") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end FileUtils.rm_rf(td) end it "authenticates HTTP URLs and adds them" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-test-http", ".json"]).tap do |f| f.write(<<-RAW) { "name": "foo/bar", "versions": [ { "version": "0.5" }, { "version": "0.7", "providers": [ { "name": "virtualbox", "url": "bar" } ] } ] } RAW f.close end md_path = Pathname.new(tf.path) with_web_server(md_path) do |port| real_url = "http://127.0.0.1:#{port}/#{md_path.basename}" # Set the box URL to something fake so we can modify it in place env[:box_url] = "foo" env[:hook] = double("hook") expect(env[:hook]).to receive(:call).with(:authenticate_box_downloader, any_args).at_least(:once) allow(env[:hook]).to receive(:call).with(:authenticate_box_url, any_args).at_least(:once) do |name, opts| if opts[:box_urls] == ["foo"] next { box_urls: [real_url] } elsif opts[:box_urls] == ["bar"] next { box_urls: [box_path.to_s] } else raise "UNKNOWN: #{opts[:box_urls].inspect}" end end expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(checksum(path)).to eq(checksum(box_path)) expect(opts[:metadata_url]).to eq("foo") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end end it "authenticates HTTP URLs and adds them directly" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-test-http", ".box"]).tap do |f| f.write() f.close end md_path = Pathname.new(tf.path) with_web_server(md_path) do |port| real_url = "http://127.0.0.1:#{port}/#{md_path.basename}" # Set the box URL to something fake so we can modify it in place env[:box_url] = "foo" env[:hook] = double("hook") env[:box_name] = "foo/bar" env[:box_provider] = "virtualbox" env[:box_checksum] = checksum(box_path) expect(env[:hook]).to receive(:call).with(:authenticate_box_downloader, any_args).at_least(:once) allow(env[:hook]).to receive(:call).with(:authenticate_box_url, any_args).at_least(:once) do |name, opts| if opts[:box_urls] == ["foo"] next { box_urls: [real_url] } else raise "UNKNOWN: #{opts[:box_urls].inspect}" end end expect(subject).to receive(:add_direct).with([real_url], anything) expect(app).to receive(:call).with(env) subject.call(env) end end it "adds from HTTP URL with a checksum" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-test-http-checksum", ".json"]).tap do |f| f.write(<<-RAW) { "name": "foo/bar", "versions": [ { "version": "0.5" }, { "version": "0.7", "providers": [ { "name": "virtualbox", "url": "#{box_path}", "checksum_type": "sha1", "checksum": "#{checksum(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}" expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(checksum(path)).to eq(checksum(box_path)) expect(opts[:metadata_url]).to eq(env[:box_url]) true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end end it "raises an exception if checksum given but not correct" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-test-bad-checksum", ".json"]).tap do |f| f.write(<<-RAW) { "name": "foo/bar", "versions": [ { "version": "0.5" }, { "version": "0.7", "providers": [ { "name": "virtualbox", "url": "#{box_path}", "checksum_type": "sha1", "checksum": "thisisnotcorrect" } ] } ] } 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}" expect(box_collection).to receive(:add).never expect(app).to receive(:call).never expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxChecksumMismatch) end end it "raises an error if no Vagrant server is set" do env[:box_url] = "mitchellh/precise64.json" expect(box_collection).to receive(:add).never expect(app).to receive(:call).never allow(Vagrant).to receive(:server_url).and_return(nil) expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxServerNotSet) end it "raises an error if shorthand is invalid" do path = Dir::Tmpname.create("vagrant-shorthand-invalid") {} with_web_server(Pathname.new(path)) do |port| env[:box_url] = "mitchellh/precise64.json" expect(box_collection).to receive(:add).never expect(app).to receive(:call).never url = "http://127.0.0.1:#{port}" with_temp_env("VAGRANT_SERVER_URL" => url) do expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxAddShortNotFound) end end end it "raises an error if downloading metadata fails" do path = Dir::Tmpname.create("vagrant-shorthand-invalid") {} with_web_server(Pathname.new(path)) do |port| env[:box_url] = "http://127.0.0.1:#{port}/bad" expect(box_collection).to receive(:add).never expect(app).to receive(:call).never expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxMetadataDownloadError) end end it "raises an error if multiple metadata URLs are given" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-box-multi-metadata", ".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 env[:box_url] = [ "/foo/bar/baz", tf.path, ] expect(box_collection).to receive(:add).never expect(app).to receive(:call).never expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxAddMetadataMultiURL) 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-box-latest-version", ".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 env[:box_url] = tf.path expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(opts[:metadata_url]).to eq("file://#{tf.path}") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end context "with box architecture configured" do before do allow(Vagrant::Util::Platform).to receive(:architecture).and_return("test-arch") end context "set to :auto" do before do env[:box_architecture] = :auto end it "adds the latest version of a box with only one provider and matching architecture" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| f.write( { name: "foo/bar", versions: [ { version: "0.5", }, { version: "0.7", providers: [ { name: "virtualbox", url: "/dev/null/invalid.path", architecture: "amd64", default_architecture: true, }, { name: "virtualbox", url: "#{box_path}", architecture: "test-arch", default_architecture: false, } ] } ] }.to_json ) f.close end env[:box_url] = tf.path expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(opts[:metadata_url]).to eq("file://#{tf.path}") expect(opts[:architecture]).to eq("test-arch") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end it "adds the latest version of a box with only one provider and unknown architecture set as default" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| f.write( { name: "foo/bar", versions: [ { version: "0.5", }, { version: "0.7", providers: [ { name: "virtualbox", url: "#{box_path}", architecture: "unknown", default_architecture: true, }, { name: "virtualbox", url: "/dev/null/invalid.path", architecture: "amd64", default_architecture: false, } ] } ] }.to_json ) f.close end env[:box_url] = tf.path expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(opts[:metadata_url]).to eq("file://#{tf.path}") expect(opts[:architecture]).to be_nil true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end it "errors adding latest version of a box with only one provider and no matching architecture" do allow(Vagrant::Util::Platform).to receive(:architecture).and_return("test-arch") box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| f.write( { name: "foo/bar", versions: [ { version: "0.5", }, { version: "0.7", providers: [ { name: "virtualbox", url: "#{box_path}", architecture: "amd64", default_architecture: true, }, { name: "virtualbox", url: "/dev/null/invalid.path", architecture: "arm64", default_architecture: false, } ] } ] }.to_json ) f.close end env[:box_url] = tf.path expect { subject.call(env) }.to raise_error(Vagrant::Errors::BoxAddNoMatchingVersion) end end context "set to nil" do before do env[:box_architecture] = nil end it "adds the latest version of a box with only one provider and default architecture" do allow(Vagrant::Util::Platform).to receive(:architecture).and_return("default-arch") box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| f.write( { name: "foo/bar", versions: [ { version: "0.5", }, { version: "0.7", providers: [ { name: "virtualbox", url: "#{box_path}", architecture: "default-arch", default_architecture: true, }, { name: "virtualbox", url: "/dev/null/invalid.path", architecture: "test-arch", default_architecture: false, } ] } ] }.to_json ) f.close end env[:box_url] = tf.path expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(opts[:metadata_url]).to eq("file://#{tf.path}") expect(opts[:architecture]).to eq("default-arch") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end end context "set to explicit value" do before do env[:box_architecture] = "arm64" end it "adds the latest version of a box with only one provider and matching architecture" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| f.write( { name: "foo/bar", versions: [ { version: "0.5", }, { version: "0.7", providers: [ { name: "virtualbox", url: "#{box_path}", architecture: "arm64", default_architecture: false, }, { name: "virtualbox", url: "/dev/null/invalid.path", architecture: "test-arch", default_architecture: true, } ] } ] }.to_json ) f.close end env[:box_url] = tf.path expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(opts[:metadata_url]).to eq("file://#{tf.path}") expect(opts[:architecture]).to eq("arm64") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end it "errors adding the latest version of a box with only one provider and no matching architecture" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| f.write( { name: "foo/bar", versions: [ { version: "0.5", }, { version: "0.7", providers: [ { name: "virtualbox", url: "#{box_path}", architecture: "amd64", default_architecture: true, }, { name: "virtualbox", url: "/dev/null/invalid.path", architecture: "386", default_architecture: false, } ] } ] }.to_json ) f.close end env[:box_url] = tf.path expect { subject.call(env) }.to raise_error(Vagrant::Errors::BoxAddNoMatchingVersion) end end end it "adds the latest version of a box with the specified provider" do box_path = iso_env.box2_file(:vmware) tf = Tempfile.new(["vagrant-box-specific-provider", ".json"]).tap do |f| f.write(<<-RAW) { "name": "foo/bar", "versions": [ { "version": "0.5" }, { "version": "0.7", "providers": [ { "name": "virtualbox", "url": "#{iso_env.box2_file(:virtualbox)}" }, { "name": "vmware", "url": "#{box_path}" } ] } ] } RAW f.close end env[:box_url] = tf.path env[:box_provider] = "vmware" expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(opts[:metadata_url]).to eq("file://#{tf.path}") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) expect(env[:box_added]).to equal(box) end it "adds the latest version of a box with the specified provider, even if not latest" do box_path = iso_env.box2_file(:vmware) tf = Tempfile.new(["vagrant-box-specified-provider", ".json"]).tap do |f| f.write(<<-RAW) { "name": "foo/bar", "versions": [ { "version": "0.5" }, { "version": "0.7", "providers": [ { "name": "virtualbox", "url": "#{iso_env.box2_file(:virtualbox)}" }, { "name": "vmware", "url": "#{box_path}" } ] }, { "version": "1.5" } ] } RAW f.close end env[:box_url] = tf.path env[:box_provider] = "vmware" expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(opts[:metadata_url]).to eq("file://#{tf.path}") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) expect(env[:box_added]).to equal(box) end it "adds the constrained version of a box with the only provider" do box_path = iso_env.box2_file(:vmware) tf = Tempfile.new(["vagrant-box-constrained", ".json"]).tap do |f| f.write(<<-RAW) { "name": "foo/bar", "versions": [ { "version": "0.5", "providers": [ { "name": "vmware", "url": "#{box_path}" } ] }, { "version": "1.1" } ] } RAW f.close end env[:box_url] = tf.path env[:box_version] = "~> 0.1" expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.5") expect(opts[:metadata_url]).to eq("file://#{tf.path}") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) expect(env[:box_added]).to equal(box) end it "adds the constrained version of a box with the specified provider" do box_path = iso_env.box2_file(:vmware) tf = Tempfile.new(["vagrant-box-constrained-provider", ".json"]).tap do |f| f.write(<<-RAW) { "name": "foo/bar", "versions": [ { "version": "0.5", "providers": [ { "name": "vmware", "url": "#{box_path}" }, { "name": "virtualbox", "url": "#{iso_env.box2_file(:virtualbox)}" } ] }, { "version": "1.1" } ] } RAW f.close end env[:box_url] = tf.path env[:box_provider] = "vmware" env[:box_version] = "~> 0.1" expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.5") expect(opts[:metadata_url]).to eq("file://#{tf.path}") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) expect(env[:box_added]).to equal(box) end it "adds the latest version of a box with any specified provider" do box_path = iso_env.box2_file(:vmware) tf = Tempfile.new(["vagrant-box-latest-version", ".json"]).tap do |f| f.write(<<-RAW) { "name": "foo/bar", "versions": [ { "version": "0.5", "providers": [ { "name": "virtualbox", "url": "#{iso_env.box2_file(:virtualbox)}" } ] }, { "version": "0.7", "providers": [ { "name": "vmware", "url": "#{box_path}" } ] } ] } RAW f.close end env[:box_url] = tf.path env[:box_provider] = ["virtualbox", "vmware"] expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(opts[:metadata_url]).to eq("file://#{tf.path}") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) expect(env[:box_added]).to equal(box) end it "asks the user what provider if multiple options" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-box-provider-asks", ".json"]).tap do |f| f.write(<<-RAW) { "name": "foo/bar", "versions": [ { "version": "0.5" }, { "version": "0.7", "providers": [ { "name": "virtualbox", "url": "#{box_path}" }, { "name": "vmware", "url": "#{iso_env.box2_file(:vmware)}" } ] } ] } RAW f.close end env[:box_url] = tf.path expect(env[:ui]).to receive(:ask).and_return("1") expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(opts[:metadata_url]).to eq("file://#{tf.path}") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) end it "raises an exception if the name doesn't match a requested name" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-box-name-mismatch", ".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 env[:box_name] = "foo" env[:box_url] = tf.path expect(box_collection).to receive(:add).never expect(app).to receive(:call).never expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxAddNameMismatch) end it "raises an exception if no matching version" do box_path = iso_env.box2_file(:vmware) tf = Tempfile.new(["vagrant-box-no-matching-version", ".json"]).tap do |f| f.write(<<-RAW) { "name": "foo/bar", "versions": [ { "version": "0.5", "providers": [ { "name": "vmware", "url": "#{box_path}" } ] }, { "version": "1.1" } ] } RAW f.close end env[:box_url] = tf.path env[:box_version] = "~> 2.0" expect(box_collection).to receive(:add).never expect(app).to receive(:call).never expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxAddNoMatchingVersion) end it "raises an error if there is no matching provider" do tf = Tempfile.new(["vagrant-box-no-matching-provider", ".json"]).tap do |f| f.write(<<-RAW) { "name": "foo/bar", "versions": [ { "version": "0.5" }, { "version": "0.7", "providers": [ { "name": "virtualbox", "url": "#{iso_env.box2_file(:virtualbox)}" } ] } ] } RAW f.close end env[:box_url] = tf.path env[:box_provider] = "vmware" expect(box_collection).to receive(:add).never expect(app).to receive(:call).never expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxAddNoMatchingProvider) end it "raises an error if a box already exists" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-box-already-exists", ".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 env[:box_url] = tf.path expect(box_collection).to receive(:find). with("foo/bar", "virtualbox", "0.7", nil).and_return(box) expect(box_collection).to receive(:add).never expect(app).to receive(:call).never expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxAlreadyExists) end it "force adds a box if specified" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new(["vagrant-box-force-add", ".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 env[:box_force] = true env[:box_url] = tf.path allow(box_collection).to receive(:find).and_return(box) expect(box_collection).to receive(:add).with(any_args) { |path, name, version, opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(opts[:force]).to be(true) expect(opts[:metadata_url]).to eq("file://#{tf.path}") true }.and_return(box) expect(app).to receive(:call).with(env) subject.call(env) expect(env[:box_added]).to equal(box) end end describe "#downloader" do let(:options) { {} } after { subject.send(:downloader, url, env, **options) } context "when box is local" do let(:url) { "/dev/null/vagrant.box" } it "should prepend file protocol to path" do expect(Vagrant::Util::Downloader).to receive(:new) do |durl, *_| expect(durl).to eq("file://#{url}") end end end context "when box is remote" do let(:url) { "http://localhost/vagrant.box" } it "should not modify the url" do expect(Vagrant::Util::Downloader).to receive(:new) do |durl, *_| expect(durl).to eq(url) end end end context "when options are set in the environment" do let(:url) { "http://localhost/vagrant.box" } context "when ca cert value is set" do let(:ca_cert) { "/dev/null/ca.cert" } before { env[:box_download_ca_cert] = ca_cert } it "should include ca cert value" do expect(Vagrant::Util::Downloader).to receive(:new) do |_, _, opts| expect(opts[:ca_cert]).to eq(ca_cert) end end end context "when ca path value is set" do let(:ca_path) { "/dev/null/ca.path" } before { env[:box_download_ca_path] = ca_path } it "should include ca path value" do expect(Vagrant::Util::Downloader).to receive(:new) do |_, _, opts| expect(opts[:ca_path]).to eq(ca_path) end end end context "when insecure value is set" do before { env[:box_download_insecure] = true } it "should include insecure value" do expect(Vagrant::Util::Downloader).to receive(:new) do |_, _, opts| expect(opts[:insecure]).to be_truthy end end end context "when client cert value is set" do let(:client_cert_path) { "/dev/null/client.cert" } before { env[:box_download_client_cert] = client_cert_path } it "should include client cert value" do expect(Vagrant::Util::Downloader).to receive(:new) do |_, _, opts| expect(opts[:client_cert]).to eq(client_cert_path) end end end context "when location trusted is set" do before { env[:box_download_location_trusted] = true } it "should include client cert value" do expect(Vagrant::Util::Downloader).to receive(:new) do |_, _, opts| expect(opts[:location_trusted]).to be_truthy end end end context "when disable ssl revoke best effort is set" do before { env[:box_download_disable_ssl_revoke_best_effort] = true } it "should include disable revoke best effort value value" do expect(Vagrant::Util::Downloader).to receive(:new) do |_, _, opts| expect(opts[:disable_ssl_revoke_best_effort]).to be_truthy end end end end end end