diff --git a/lib/vagrant/machine_index.rb b/lib/vagrant/machine_index.rb index c7ad6f31a..128827685 100644 --- a/lib/vagrant/machine_index.rb +++ b/lib/vagrant/machine_index.rb @@ -108,9 +108,11 @@ module Vagrant @lock.synchronize do with_index_lock do - return nil if !@machines[uuid] + data = find_by_prefix(uuid) + return nil if !data + uuid = data["id"] - entry = Entry.new(uuid, @machines[uuid].merge("id" => uuid)) + entry = Entry.new(uuid, data) # Lock this machine lock_file = lock_machine(uuid) @@ -127,6 +129,14 @@ module Vagrant entry end + # Tests if the index has the given UUID. + # + # @param [String] uuid + # @return [Boolean] + def include?(uuid) + !!find_by_prefix(uuid) + end + # Releases an entry, unlocking it. # # This is an idempotent operation. It is safe to call this even if you're @@ -192,6 +202,17 @@ module Vagrant protected + # Finds a machine where the UUID is prefixed by the given string. + # + # @return [Hash] + def find_by_prefix(prefix) + @machines.each do |uuid, data| + return data.merge("id" => uuid) if uuid.start_with?(prefix) + end + + nil + end + # Locks a machine exclusively to us, returning the file handle # that holds the lock. # diff --git a/lib/vagrant/plugin/v2/command.rb b/lib/vagrant/plugin/v2/command.rb index 2b0248b62..3356d0320 100644 --- a/lib/vagrant/plugin/v2/command.rb +++ b/lib/vagrant/plugin/v2/command.rb @@ -82,9 +82,6 @@ module Vagrant @logger.debug(" -- names: #{names.inspect}") @logger.debug(" -- options: #{options.inspect}") - # Using VMs requires a Vagrant environment to be properly setup - raise Errors::NoEnvironmentError if !@env.root_path - # Setup the options hash options ||= {} @@ -92,6 +89,21 @@ module Vagrant names ||= [] names = [names] if !names.is_a?(Array) + # Determine if we require a local Vagrant environment. There are + # two cases that we require a local environment: + # + # * We're asking for ANY/EVERY VM (no names given). + # + # * We're asking for specific VMs, at least once of which + # is NOT in the local machine index. + # + requires_local_env = false + requires_local_env = true if names.empty? + requires_local_env ||= names.any? { |n| + !@env.machine_index.include?(n) + } + raise Errors::NoEnvironmentError if requires_local_env && !@env.root_path + # Cache the active machines outside the loop active_machines = @env.active_machines @@ -112,6 +124,20 @@ module Vagrant provider_to_use = options[:provider] provider_to_use = provider_to_use.to_sym if provider_to_use + # If we have this machine in our index, load that. + entry = @env.machine_index.get(name.to_s) + if entry + @env.machine_index.release(entry) + + # Create an environment for this location and yield the + # machine in that environment. + env = Vagrant::Environment.new( + cwd: entry.vagrantfile_path, + home_path: @env.home_path, + ) + next env.machine(entry.name.to_sym, entry.provider.to_sym) + end + active_machines.each do |active_name, active_provider| if name == active_name # We found an active machine with the same name diff --git a/templates/locales/en.yml b/templates/locales/en.yml index d28cb05a7..3dbbdb438 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -800,9 +800,11 @@ en: %{types} no_env: |- - A Vagrant environment is required to run this command. Run `vagrant init` - to set one up in this directory, or change to a directory with a - Vagrantfile and try again. + A Vagrant environment or target machine is required to run this + command. Run `vagrant init` to create a new Vagrant environment. Or, + get an ID of a target machine from `vagrant global-status` to run + this command on. A final option is to change to a directory with a + Vagrantfile and to try again. plugin_gem_not_found: |- The plugin '%{name}' could not be installed because it could not be found. Please double check the name and try again. diff --git a/test/unit/vagrant/machine_directory_test.rb b/test/unit/vagrant/machine_directory_test.rb index 896c75575..bf69f1523 100644 --- a/test/unit/vagrant/machine_directory_test.rb +++ b/test/unit/vagrant/machine_directory_test.rb @@ -12,6 +12,13 @@ describe Vagrant::MachineIndex do let(:data_dir) { temporary_dir } let(:entry_klass) { Vagrant::MachineIndex::Entry } + let(:new_entry) do + entry_klass.new.tap do |e| + e.name = "foo" + e.vagrantfile_path = "/bar" + end + end + subject { described_class.new(data_dir) } it "raises an exception if the data file is corrupt" do @@ -96,6 +103,17 @@ describe Vagrant::MachineIndex do expect(result.updated_at).to eq("foo") end + it "returns a valid entry by unique prefix" do + result = subject.get("b") + + expect(result).to_not be_nil + expect(result.id).to eq("bar") + end + + it "should include? by prefix" do + expect(subject.include?("b")).to be_true + end + it "locks the entry so subsequent gets fail" do result = subject.get("bar") expect(result).to_not be_nil @@ -114,14 +132,22 @@ describe Vagrant::MachineIndex do end end - describe "#set and #get and #delete" do - let(:new_entry) do - entry_klass.new.tap do |e| - e.name = "foo" - e.vagrantfile_path = "/bar" - end + describe "#include" do + it "should not include non-existent things" do + expect(subject.include?("foo")).to be_false end + it "should include created entries" do + result = subject.set(new_entry) + expect(result.id).to_not be_empty + subject.release(result) + + subject = described_class.new(data_dir) + expect(subject.include?(result.id)).to be_true + end + end + + describe "#set and #get and #delete" do it "adds a new entry" do result = subject.set(new_entry) expect(result.id).to_not be_empty diff --git a/test/unit/vagrant/plugin/v2/command_test.rb b/test/unit/vagrant/plugin/v2/command_test.rb index 58a28a007..c55de305f 100644 --- a/test/unit/vagrant/plugin/v2/command_test.rb +++ b/test/unit/vagrant/plugin/v2/command_test.rb @@ -2,6 +2,8 @@ require File.expand_path("../../../../base", __FILE__) require 'optparse' describe Vagrant::Plugin::V2::Command do + include_context "unit" + describe "parsing options" do let(:klass) do Class.new(described_class) do @@ -53,18 +55,17 @@ describe Vagrant::Plugin::V2::Command do end end - let(:default_provider) { :virtualbox } - let(:environment) do - env = double("environment") - env.stub(:active_machines => []) - env.stub(:default_provider => default_provider) - env.stub(:root_path => "foo") - env + # We have to create a Vagrantfile so there is a root path + test_iso_env.vagrantfile("") + test_iso_env.create_vagrant_env end + let(:test_iso_env) { isolated_environment } let(:instance) { klass.new([], environment) } + subject { instance } + it "should raise an exception if a root_path is not available" do environment.stub(:root_path => nil) @@ -82,8 +83,8 @@ describe Vagrant::Plugin::V2::Command do bar_vm.stub(ui: Vagrant::UI::Silent.new) environment.stub(:machine_names => [:foo, :bar]) - allow(environment).to receive(:machine).with(:foo, default_provider).and_return(foo_vm) - allow(environment).to receive(:machine).with(:bar, default_provider).and_return(bar_vm) + allow(environment).to receive(:machine).with(:foo, environment.default_provider).and_return(foo_vm) + allow(environment).to receive(:machine).with(:bar, environment.default_provider).and_return(bar_vm) vms = [] instance.with_target_vms do |vm| @@ -106,7 +107,7 @@ describe Vagrant::Plugin::V2::Command do foo_vm.stub(:name => "foo", :provider => :foobarbaz) foo_vm.stub(ui: Vagrant::UI::Silent.new) - allow(environment).to receive(:machine).with(:foo, default_provider).and_return(foo_vm) + allow(environment).to receive(:machine).with(:foo, environment.default_provider).and_return(foo_vm) vms = [] instance.with_target_vms("foo") { |vm| vms << vm } @@ -167,8 +168,8 @@ describe Vagrant::Plugin::V2::Command do name = :foo machine = double("machine") - allow(environment).to receive(:machine).with(name, default_provider).and_return(machine) - machine.stub(:name => name, :provider => default_provider) + allow(environment).to receive(:machine).with(name, environment.default_provider).and_return(machine) + machine.stub(:name => name, :provider => environment.default_provider) machine.stub(ui: Vagrant::UI::Silent.new) results = [] @@ -198,16 +199,37 @@ describe Vagrant::Plugin::V2::Command do machine = double("machine") environment.stub(:active_machines => []) - allow(environment).to receive(:machine).with(name, default_provider).and_return(machine) + allow(environment).to receive(:machine).with(name, environment.default_provider).and_return(machine) environment.stub(:machine_names => []) environment.stub(:primary_machine_name => name) - machine.stub(:name => name, :provider => default_provider) + machine.stub(:name => name, :provider => environment.default_provider) machine.stub(ui: Vagrant::UI::Silent.new) vms = [] instance.with_target_vms(nil, :single_target => true) { |vm| vms << machine } expect(vms).to eq([machine]) end + + it "should yield machines from another environment" do + iso_env = isolated_environment + iso_env.vagrantfile("") + other_env = iso_env.create_vagrant_env( + home_path: environment.home_path) + other_machine = other_env.machine( + other_env.machine_names[0], other_env.default_provider) + + # Set an ID on it so that it is "created" in the index + other_machine.id = "foo" + + # Make sure we don't have a root path, to test + environment.stub(root_path: nil) + + results = [] + subject.with_target_vms(other_machine.index_uuid) { |m| results << m } + + expect(results.length).to eq(1) + expect(results[0].id).to eq(other_machine.id) + end end describe "splitting the main and subcommand args" do