Merge pull request #3538 from mitchellh/f-box-usage
Track box usage (local) to know if envs are using boxes
This commit is contained in:
commit
de6759f94d
|
@ -71,6 +71,49 @@ module Vagrant
|
||||||
box = env[:box_collection].find(
|
box = env[:box_collection].find(
|
||||||
box_name, box_provider, box_version)
|
box_name, box_provider, box_version)
|
||||||
|
|
||||||
|
# Verify that this box is not in use by an active machine,
|
||||||
|
# otherwise warn the user.
|
||||||
|
users = []
|
||||||
|
env[:machine_index].each do |entry|
|
||||||
|
box_data = entry.extra_data["box"]
|
||||||
|
next if !box_data
|
||||||
|
|
||||||
|
# If all the data matches AND the entry is a seemingly
|
||||||
|
# valid entry, then track it.
|
||||||
|
if box_data["name"] == box.name &&
|
||||||
|
box_data["provider"] == box.provider.to_s &&
|
||||||
|
box_data["version"] == box.version.to_s &&
|
||||||
|
entry.valid?(env[:home_path])
|
||||||
|
users << entry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !users.empty?
|
||||||
|
# Build up the output to show the user.
|
||||||
|
users = users.map do |entry|
|
||||||
|
"#{entry.name} (ID: #{entry.id})"
|
||||||
|
end.join("\n")
|
||||||
|
|
||||||
|
force_key = :force_confirm_box_remove
|
||||||
|
message = I18n.t(
|
||||||
|
"vagrant.commands.box.remove_in_use_query",
|
||||||
|
name: box.name,
|
||||||
|
provider: box.provider,
|
||||||
|
version: box.version,
|
||||||
|
users: users) + " "
|
||||||
|
|
||||||
|
# Ask the user if we should do this
|
||||||
|
stack = Builder.new.tap do |b|
|
||||||
|
b.use Confirm, message, force_key
|
||||||
|
end
|
||||||
|
|
||||||
|
result = env[:action_runner].run(stack, env)
|
||||||
|
if !result[:result]
|
||||||
|
# They said "no", so just return
|
||||||
|
return @app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
env[:ui].info(I18n.t("vagrant.commands.box.removing",
|
env[:ui].info(I18n.t("vagrant.commands.box.removing",
|
||||||
:name => box.name,
|
:name => box.name,
|
||||||
:provider => box.provider,
|
:provider => box.provider,
|
||||||
|
|
|
@ -144,7 +144,7 @@ module Vagrant
|
||||||
hook(:environment_plugins_loaded, runner: Action::Runner.new(env: self))
|
hook(:environment_plugins_loaded, runner: Action::Runner.new(env: self))
|
||||||
|
|
||||||
# Call the environment load hooks
|
# Call the environment load hooks
|
||||||
hook(:environment_load)
|
hook(:environment_load, runner: Action::Runner.new(env: self))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return a human-friendly string for pretty printed or inspected
|
# Return a human-friendly string for pretty printed or inspected
|
||||||
|
@ -165,6 +165,7 @@ module Vagrant
|
||||||
:box_collection => boxes,
|
:box_collection => boxes,
|
||||||
:hook => method(:hook),
|
:hook => method(:hook),
|
||||||
:host => host,
|
:host => host,
|
||||||
|
:machine_index => machine_index,
|
||||||
:gems_path => gems_path,
|
:gems_path => gems_path,
|
||||||
:home_path => home_path,
|
:home_path => home_path,
|
||||||
:root_path => root_path,
|
:root_path => root_path,
|
||||||
|
|
|
@ -260,6 +260,15 @@ module Vagrant
|
||||||
entry.state = "preparing"
|
entry.state = "preparing"
|
||||||
entry.vagrantfile_path = @env.root_path
|
entry.vagrantfile_path = @env.root_path
|
||||||
entry.vagrantfile_name = @env.vagrantfile_name
|
entry.vagrantfile_name = @env.vagrantfile_name
|
||||||
|
|
||||||
|
if @box
|
||||||
|
entry.extra_data["box"] = {
|
||||||
|
"name" => @box.name,
|
||||||
|
"provider" => @box.provider.to_s,
|
||||||
|
"version" => @box.version.to_s,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
entry = @env.machine_index.set(entry)
|
entry = @env.machine_index.set(entry)
|
||||||
@env.machine_index.release(entry)
|
@env.machine_index.release(entry)
|
||||||
|
|
||||||
|
|
|
@ -378,6 +378,8 @@ module Vagrant
|
||||||
# The parameter given should be nil if this is being created
|
# The parameter given should be nil if this is being created
|
||||||
# publicly.
|
# publicly.
|
||||||
def initialize(id=nil, raw=nil)
|
def initialize(id=nil, raw=nil)
|
||||||
|
@extra_data = {}
|
||||||
|
|
||||||
# Do nothing if we aren't given a raw value. Otherwise, parse it.
|
# Do nothing if we aren't given a raw value. Otherwise, parse it.
|
||||||
return if !raw
|
return if !raw
|
||||||
|
|
||||||
|
@ -401,6 +403,33 @@ module Vagrant
|
||||||
@vagrantfile_path = Pathname.new(@vagrantfile_path) if @vagrantfile_path
|
@vagrantfile_path = Pathname.new(@vagrantfile_path) if @vagrantfile_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns boolean true if this entry appears to be valid.
|
||||||
|
# The critera for being valid:
|
||||||
|
#
|
||||||
|
# * Vagrantfile directory exists
|
||||||
|
# * Vagrant environment contains a machine with this
|
||||||
|
# name and provider.
|
||||||
|
#
|
||||||
|
# This method is _slow_. It should be used with care.
|
||||||
|
#
|
||||||
|
# @param [Pathname] home_path The home path for the Vagrant
|
||||||
|
# environment.
|
||||||
|
# @return [Boolean]
|
||||||
|
def valid?(home_path)
|
||||||
|
return false if !vagrantfile_path
|
||||||
|
return false if !vagrantfile_path.directory?
|
||||||
|
|
||||||
|
# Create an environment so we can determine the active
|
||||||
|
# machines...
|
||||||
|
env = vagrant_env(home_path)
|
||||||
|
env.active_machines.each do |name, provider|
|
||||||
|
return true if name.to_s == self.name.to_s &&
|
||||||
|
provider.to_s == self.provider.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
# Creates a {Vagrant::Environment} for this entry.
|
# Creates a {Vagrant::Environment} for this entry.
|
||||||
#
|
#
|
||||||
# @return [Vagrant::Environment]
|
# @return [Vagrant::Environment]
|
||||||
|
|
|
@ -6,12 +6,18 @@ module VagrantPlugins
|
||||||
class Remove < Vagrant.plugin("2", :command)
|
class Remove < Vagrant.plugin("2", :command)
|
||||||
def execute
|
def execute
|
||||||
options = {}
|
options = {}
|
||||||
|
options[:force] = false
|
||||||
|
|
||||||
opts = OptionParser.new do |o|
|
opts = OptionParser.new do |o|
|
||||||
o.banner = "Usage: vagrant box remove <name>"
|
o.banner = "Usage: vagrant box remove <name>"
|
||||||
o.separator ""
|
o.separator ""
|
||||||
o.separator "Options:"
|
o.separator "Options:"
|
||||||
o.separator ""
|
o.separator ""
|
||||||
|
|
||||||
|
o.on("-f", "--force", "Destroy without confirmation.") do |f|
|
||||||
|
options[:force] = f
|
||||||
|
end
|
||||||
|
|
||||||
o.on("--provider PROVIDER", String,
|
o.on("--provider PROVIDER", String,
|
||||||
"The specific provider type for the box to remove") do |p|
|
"The specific provider type for the box to remove") do |p|
|
||||||
options[:provider] = p
|
options[:provider] = p
|
||||||
|
@ -43,6 +49,7 @@ module VagrantPlugins
|
||||||
:box_name => argv[0],
|
:box_name => argv[0],
|
||||||
:box_provider => options[:provider],
|
:box_provider => options[:provider],
|
||||||
:box_version => options[:version],
|
:box_version => options[:version],
|
||||||
|
:force_confirm_box_remove => options[:force],
|
||||||
})
|
})
|
||||||
|
|
||||||
# Success, exit status 0
|
# Success, exit status 0
|
||||||
|
|
|
@ -42,7 +42,7 @@ module VagrantPlugins
|
||||||
@env.machine_index.each do |entry|
|
@env.machine_index.each do |entry|
|
||||||
# If we're pruning and this entry is invalid, skip it
|
# If we're pruning and this entry is invalid, skip it
|
||||||
# and prune it later.
|
# and prune it later.
|
||||||
if options[:prune] && invalid?(entry)
|
if options[:prune] && !entry.valid?(@env.home_path)
|
||||||
prune << entry
|
prune << entry
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
@ -95,23 +95,6 @@ module VagrantPlugins
|
||||||
# Success, exit status 0
|
# Success, exit status 0
|
||||||
0
|
0
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Tests if a entry is invalid and should be pruned
|
|
||||||
def invalid?(entry)
|
|
||||||
return true if !entry.vagrantfile_path.directory?
|
|
||||||
|
|
||||||
# Create an environment so we can determine the active
|
|
||||||
# machines...
|
|
||||||
env = entry.vagrant_env(@env.home_path)
|
|
||||||
env.active_machines.each do |name, provider|
|
|
||||||
return false if name.to_s == entry.name.to_s &&
|
|
||||||
provider.to_s == entry.provider.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1251,6 +1251,15 @@ en:
|
||||||
vm_not_running: "VM is not currently running. Please, first bring it up with `vagrant up` then run this command."
|
vm_not_running: "VM is not currently running. Please, first bring it up with `vagrant up` then run this command."
|
||||||
box:
|
box:
|
||||||
no_installed_boxes: "There are no installed boxes! Use `vagrant box add` to add some."
|
no_installed_boxes: "There are no installed boxes! Use `vagrant box add` to add some."
|
||||||
|
remove_in_use_query: |-
|
||||||
|
Box '%{name}' (v%{version}) with provider '%{provider}' appears
|
||||||
|
to still be in use by at least one Vagrant environment. Removing
|
||||||
|
the box could corrupt the environment. We recommend destroying
|
||||||
|
these environments first:
|
||||||
|
|
||||||
|
%{users}
|
||||||
|
|
||||||
|
Are you sure you want to remove this box? [y/N]
|
||||||
removing: |-
|
removing: |-
|
||||||
Removing box '%{name}' (v%{version}) with provider '%{provider}'...
|
Removing box '%{name}' (v%{version}) with provider '%{provider}'...
|
||||||
destroy:
|
destroy:
|
||||||
|
|
|
@ -34,11 +34,26 @@ describe VagrantPlugins::CommandBox::Command::Remove do
|
||||||
it "invokes the action runner" do
|
it "invokes the action runner" do
|
||||||
expect(action_runner).to receive(:run).with { |action, opts|
|
expect(action_runner).to receive(:run).with { |action, opts|
|
||||||
expect(opts[:box_name]).to eq("foo")
|
expect(opts[:box_name]).to eq("foo")
|
||||||
|
expect(opts[:force_confirm_box_remove]).to be_false
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
subject.execute
|
subject.execute
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with --force" do
|
||||||
|
let(:argv) { super() + ["--force"] }
|
||||||
|
|
||||||
|
it "invokes the action runner with force option" do
|
||||||
|
expect(action_runner).to receive(:run).with { |action, opts|
|
||||||
|
expect(opts[:box_name]).to eq("foo")
|
||||||
|
expect(opts[:force_confirm_box_remove]).to be_true
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
subject.execute
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with two arguments" do
|
context "with two arguments" do
|
||||||
|
|
|
@ -6,12 +6,16 @@ describe Vagrant::Action::Builtin::BoxRemove do
|
||||||
let(:app) { lambda { |env| } }
|
let(:app) { lambda { |env| } }
|
||||||
let(:env) { {
|
let(:env) { {
|
||||||
box_collection: box_collection,
|
box_collection: box_collection,
|
||||||
|
home_path: home_path,
|
||||||
|
machine_index: machine_index,
|
||||||
ui: Vagrant::UI::Silent.new,
|
ui: Vagrant::UI::Silent.new,
|
||||||
} }
|
} }
|
||||||
|
|
||||||
subject { described_class.new(app, env) }
|
subject { described_class.new(app, env) }
|
||||||
|
|
||||||
let(:box_collection) { double("box_collection") }
|
let(:box_collection) { double("box_collection") }
|
||||||
|
let(:home_path) { "foo" }
|
||||||
|
let(:machine_index) { [] }
|
||||||
let(:iso_env) { isolated_environment }
|
let(:iso_env) { isolated_environment }
|
||||||
|
|
||||||
let(:box) do
|
let(:box) do
|
||||||
|
@ -74,6 +78,71 @@ describe Vagrant::Action::Builtin::BoxRemove do
|
||||||
expect(env[:box_removed]).to equal(box)
|
expect(env[:box_removed]).to equal(box)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "checking if a box is in use" do
|
||||||
|
def new_entry(name, provider, version, valid=true)
|
||||||
|
Vagrant::MachineIndex::Entry.new.tap do |entry|
|
||||||
|
entry.extra_data["box"] = {
|
||||||
|
"name" => "foo",
|
||||||
|
"provider" => "virtualbox",
|
||||||
|
"version" => "1.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.stub(valid?: valid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:action_runner) { double("action_runner") }
|
||||||
|
|
||||||
|
before do
|
||||||
|
env[:action_runner] = action_runner
|
||||||
|
|
||||||
|
box_collection.stub(
|
||||||
|
all: [
|
||||||
|
["foo", "1.0", :virtualbox],
|
||||||
|
["foo", "1.1", :virtualbox],
|
||||||
|
])
|
||||||
|
|
||||||
|
env[:box_name] = "foo"
|
||||||
|
env[:box_version] = "1.0"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does delete if the box is not in use" do
|
||||||
|
expect(box_collection).to receive(:find).with(
|
||||||
|
"foo", :virtualbox, "1.0").and_return(box)
|
||||||
|
expect(box).to receive(:destroy!).once
|
||||||
|
|
||||||
|
subject.call(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does delete if the box is in use and user confirms" do
|
||||||
|
machine_index << new_entry("foo", "virtualbox", "1.0")
|
||||||
|
|
||||||
|
result = { result: true }
|
||||||
|
expect(action_runner).to receive(:run).
|
||||||
|
with(anything, env).and_return(result)
|
||||||
|
|
||||||
|
expect(box_collection).to receive(:find).with(
|
||||||
|
"foo", :virtualbox, "1.0").and_return(box)
|
||||||
|
expect(box).to receive(:destroy!).once
|
||||||
|
|
||||||
|
subject.call(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't delete if the box is in use" do
|
||||||
|
machine_index << new_entry("foo", "virtualbox", "1.0")
|
||||||
|
|
||||||
|
result = { result: false }
|
||||||
|
expect(action_runner).to receive(:run).
|
||||||
|
with(anything, env).and_return(result)
|
||||||
|
|
||||||
|
expect(box_collection).to receive(:find).with(
|
||||||
|
"foo", :virtualbox, "1.0").and_return(box)
|
||||||
|
expect(box).to receive(:destroy!).never
|
||||||
|
|
||||||
|
subject.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it "errors if the box doesn't exist" do
|
it "errors if the box doesn't exist" do
|
||||||
box_collection.stub(all: [])
|
box_collection.stub(all: [])
|
||||||
|
|
||||||
|
|
|
@ -265,3 +265,74 @@ describe Vagrant::MachineIndex do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe Vagrant::MachineIndex::Entry do
|
||||||
|
include_context "unit"
|
||||||
|
|
||||||
|
let(:env) {
|
||||||
|
iso_env = isolated_environment
|
||||||
|
iso_env.vagrantfile(vagrantfile)
|
||||||
|
iso_env.create_vagrant_env
|
||||||
|
}
|
||||||
|
|
||||||
|
let(:vagrantfile) { "" }
|
||||||
|
|
||||||
|
describe "#valid?" do
|
||||||
|
let(:machine) { env.machine(:default, :dummy) }
|
||||||
|
|
||||||
|
subject do
|
||||||
|
described_class.new.tap do |e|
|
||||||
|
e.name = "default"
|
||||||
|
e.provider = "dummy"
|
||||||
|
e.vagrantfile_path = env.root_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid with a valid entry" do
|
||||||
|
machine.id = "foo"
|
||||||
|
expect(subject).to be_valid(env.home_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be invalid if no Vagrantfile path is set" do
|
||||||
|
subject.vagrantfile_path = nil
|
||||||
|
expect(subject).to_not be_valid(env.home_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be invalid if the Vagrantfile path does not exist" do
|
||||||
|
subject.vagrantfile_path = Pathname.new("/i/should/not/exist")
|
||||||
|
expect(subject).to_not be_valid(env.home_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be invalid if the machine is inactive" do
|
||||||
|
machine.id = nil
|
||||||
|
expect(subject).to_not be_valid(env.home_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with another active machine" do
|
||||||
|
let(:vagrantfile) do
|
||||||
|
<<-VF
|
||||||
|
Vagrant.configure("2") do |config|
|
||||||
|
config.vm.define "web"
|
||||||
|
config.vm.define "db"
|
||||||
|
end
|
||||||
|
VF
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be invalid if the wrong machine is active only" do
|
||||||
|
m = env.machine(:web, :dummy)
|
||||||
|
m.id = "foo"
|
||||||
|
|
||||||
|
subject.name = "db"
|
||||||
|
expect(subject).to_not be_valid(env.home_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be valid if the correct machine is active" do
|
||||||
|
env.machine(:web, :dummy).id = "foo"
|
||||||
|
env.machine(:db, :dummy).id = "foo"
|
||||||
|
|
||||||
|
subject.name = "db"
|
||||||
|
expect(subject).to be_valid(env.home_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -21,7 +21,14 @@ describe Vagrant::Machine do
|
||||||
let(:provider_name) { :test }
|
let(:provider_name) { :test }
|
||||||
let(:provider_options) { {} }
|
let(:provider_options) { {} }
|
||||||
let(:base) { false }
|
let(:base) { false }
|
||||||
let(:box) { Object.new }
|
let(:box) do
|
||||||
|
double("box").tap do |b|
|
||||||
|
b.stub(name: "foo")
|
||||||
|
b.stub(provider: :dummy)
|
||||||
|
b.stub(version: "1.0")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
let(:config) { env.vagrantfile.config }
|
let(:config) { env.vagrantfile.config }
|
||||||
let(:data_dir) { Pathname.new(Dir.mktmpdir("vagrant")) }
|
let(:data_dir) { Pathname.new(Dir.mktmpdir("vagrant")) }
|
||||||
let(:env) do
|
let(:env) do
|
||||||
|
@ -403,11 +410,32 @@ describe Vagrant::Machine do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "is set one when setting an ID" do
|
it "is set one when setting an ID" do
|
||||||
|
# Setup the box information
|
||||||
|
box = double("box")
|
||||||
|
box.stub(name: "foo")
|
||||||
|
box.stub(provider: :bar)
|
||||||
|
box.stub(version: "1.2.3")
|
||||||
|
subject.box = box
|
||||||
|
|
||||||
subject.id = "foo"
|
subject.id = "foo"
|
||||||
|
|
||||||
uuid = subject.index_uuid
|
uuid = subject.index_uuid
|
||||||
expect(uuid).to_not be_nil
|
expect(uuid).to_not be_nil
|
||||||
expect(new_instance.index_uuid).to eq(uuid)
|
expect(new_instance.index_uuid).to eq(uuid)
|
||||||
|
|
||||||
|
# Test the entry itself
|
||||||
|
entry = env.machine_index.get(uuid)
|
||||||
|
expect(entry.name).to eq(subject.name)
|
||||||
|
expect(entry.provider).to eq(subject.provider_name.to_s)
|
||||||
|
expect(entry.state).to eq("preparing")
|
||||||
|
expect(entry.vagrantfile_path).to eq(env.root_path)
|
||||||
|
expect(entry.vagrantfile_name).to eq(env.vagrantfile_name)
|
||||||
|
expect(entry.extra_data["box"]).to eq({
|
||||||
|
"name" => box.name,
|
||||||
|
"provider" => box.provider.to_s,
|
||||||
|
"version" => box.version,
|
||||||
|
})
|
||||||
|
env.machine_index.release(entry)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "deletes the UUID when setting to nil" do
|
it "deletes the UUID when setting to nil" do
|
||||||
|
|
Loading…
Reference in New Issue