Merge pull request #7978 from bbrala/feature-command-box-prune

Feature command box prune
This commit is contained in:
Chris Roberts 2016-11-09 09:40:20 -08:00 committed by GitHub
commit 2f68c1f18d
5 changed files with 348 additions and 1 deletions

View File

@ -0,0 +1,128 @@
require 'optparse'
module VagrantPlugins
module CommandBox
module Command
class Prune < Vagrant.plugin("2", :command)
def execute
options = {}
options[:force] = false
options[:dry_run] = false
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant box prune [options]"
o.separator ""
o.separator "Options:"
o.separator ""
o.on("-p PROVIDER", "--provider PROVIDER", String, "The specific provider type for the boxes to destroy.") do |p|
options[:provider] = p
end
o.on("-n", "--dry-run", "Only print the boxes that would be removed.") do |f|
options[:dry_run] = f
end
o.on("--name NAME", String, "The specific box name to check for outdated versions.") do |name|
options[:name] = name
end
o.on("-f", "--force", "Destroy without confirmation even when box is in use.") do |f|
options[:force] = f
end
end
# Parse the options
argv = parse_options(opts)
return if !argv
boxes = @env.boxes.all.sort
if boxes.empty?
return @env.ui.warn(I18n.t("vagrant.commands.box.no_installed_boxes"), prefix: false)
end
delete_oldest_boxes(boxes, options[:provider], options[:force], options[:name], options[:dry_run])
# Success, exit status 0
0
end
private
def delete_oldest_boxes(boxes, only_provider, skip_confirm, only_name, dry_run)
# Find the longest box name
longest_box = boxes.max_by { |x| x[0].length }
longest_box_length = longest_box[0].length
# Hash map to keep track of newest versions
newest_boxes = Hash.new
# First find the newest version for every installed box
boxes.each do |name, version, provider|
next if only_provider and only_provider != provider.to_s
next if only_name and only_name != name
# Nested to make sure it works for boxes with different providers
if newest_boxes.has_key?(name)
if newest_boxes[name].has_key?(provider)
saved = Gem::Version.new(newest_boxes[name][provider])
current = Gem::Version.new(version)
if current > saved
newest_boxes[name][provider] = version
end
else
newest_boxes[name][provider] = version
end
else
newest_boxes[name] = Hash.new
newest_boxes[name][provider] = version
end
end
@env.ui.info("The following boxes will be kept...");
newest_boxes.each do |name, providers|
providers.each do |provider, version|
@env.ui.info("#{name.ljust(longest_box_length)} (#{provider}, #{version})")
@env.ui.machine("box-name", name)
@env.ui.machine("box-provider", provider)
@env.ui.machine("box-version", version)
end
end
@env.ui.info("", prefix: false)
@env.ui.info("Checking for older boxes...");
# Track if we removed anything so the user can be informed
removed_any_box = false
boxes.each do |name, version, provider|
next if !newest_boxes.has_key?(name) or !newest_boxes[name].has_key?(provider)
current = Gem::Version.new(version)
saved = Gem::Version.new(newest_boxes[name][provider])
if current < saved
removed_any_box = true
# Use the remove box action
if dry_run
@env.ui.info("Would remove #{name} #{provider} #{version}")
else
@env.action_runner.run(Vagrant::Action.action_box_remove, {
box_name: name,
box_provider: provider,
box_version: version,
force_confirm_box_remove: skip_confirm,
box_remove_all_versions: false,
})
end
end
end
if !removed_any_box
@env.ui.info("No old versions of boxes to remove...");
end
end
end
end
end
end

View File

@ -34,6 +34,11 @@ module VagrantPlugins
Remove
end
@subcommands.register(:prune) do
require_relative "prune"
Prune
end
@subcommands.register(:repackage) do
require File.expand_path("../repackage", __FILE__)
Repackage

View File

@ -0,0 +1,194 @@
require File.expand_path("../../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/commands/box/command/prune")
describe VagrantPlugins::CommandBox::Command::Prune do
include_context "unit"
include_context "command plugin helpers"
let(:entry_klass) { Vagrant::MachineIndex::Entry }
let(:iso_env) do
# We have to create a Vagrantfile so there is a root path
isolated_environment.tap do |env|
env.vagrantfile("")
end
end
let(:iso_vagrant_env) { iso_env.create_vagrant_env }
let(:argv) { [] }
# Seems this way of providing a box version triggers box in use.
def new_entry(name, box_name, box_provider, version)
entry_klass.new.tap do |e|
e.name = name
e.vagrantfile_path = "/bar"
e.extra_data["box"] = {
"name" => box_name,
"provider" => box_provider,
"version" => version,
}
end
end
subject { described_class.new(argv, iso_vagrant_env) }
describe "execute" do
context "with no args" do
it "removes the old version and keeps the current one" do
# Let's put some things in the index
iso_env.box3("foobox", "1.0", :virtualbox);
iso_env.box3("foobox", "1.1", :virtualbox);
iso_env.box3("barbox", "1.0", :vmware);
iso_env.box3("barbox", "1.1", :vmware);
iso_vagrant_env.machine_index.set(new_entry("foo", "foobox", "virtualbox", 1))
output = ""
allow(iso_vagrant_env.ui).to receive(:info) do |data|
output << data
end
expect(iso_vagrant_env.boxes.all.count).to eq(4)
expect(subject.execute).to eq(0)
expect(iso_vagrant_env.boxes.all.count).to eq(2)
expect(output).to include("barbox (vmware, 1.1)")
expect(output).to include("Removing box 'barbox' (v1.0) with provider 'vmware'...")
expect(output).to include("foobox (virtualbox, 1.1)")
expect(output).to include("Removing box 'foobox' (v1.0) with provider 'virtualbox'...")
end
it "removes nothing" do
# Let's put some things in the index
iso_env.box3("foobox", "1.0", :virtualbox);
iso_env.box3("barbox", "1.0", :vmware);
iso_vagrant_env.machine_index.set(new_entry("foo", "foobox", "virtualbox", 1))
output = ""
allow(iso_vagrant_env.ui).to receive(:info) do |data|
output << data
end
expect(iso_vagrant_env.boxes.all.count).to eq(2)
expect(subject.execute).to eq(0)
expect(iso_vagrant_env.boxes.all.count).to eq(2)
expect(output).to include("No old versions of boxes to remove...")
end
end
context "with --provider" do
let(:argv) { ["--provider", "virtualbox"] }
it "removes the old versions of the specified provider" do
# Let's put some things in the index
iso_env.box3("foobox", "1.0", :virtualbox);
iso_env.box3("foobox", "1.1", :virtualbox);
iso_env.box3("barbox", "1.0", :vmware);
iso_env.box3("barbox", "1.1", :vmware);
iso_vagrant_env.machine_index.set(new_entry("foo", "foobox", "virtualbox", 1))
output = ""
allow(iso_vagrant_env.ui).to receive(:info) do |data|
output << "\n" + data
end
expect(iso_vagrant_env.boxes.all.count).to eq(4)
expect(subject.execute).to eq(0)
expect(iso_vagrant_env.boxes.all.count).to eq(3)
expect(output).to include("foobox (virtualbox, 1.1)")
expect(output).to include("Removing box 'foobox' (v1.0) with provider 'virtualbox'...")
end
end
context "with --dry-run" do
let(:argv) { ["--dry-run"] }
it "removes the old versions of the specified provider" do
# Let's put some things in the index
iso_env.box3("foobox", "1.0", :virtualbox);
iso_env.box3("foobox", "1.1", :virtualbox);
iso_vagrant_env.machine_index.set(new_entry("foo", "foobox", "virtualbox", 1))
output = ""
allow(iso_vagrant_env.ui).to receive(:info) do |data|
output << "\n" + data
end
expect(iso_vagrant_env.boxes.all.count).to eq(2)
expect(subject.execute).to eq(0)
expect(iso_vagrant_env.boxes.all.count).to eq(2)
expect(output).to include("foobox (virtualbox, 1.1)")
expect(output).to include("Would remove foobox virtualbox 1.0")
end
end
context "with --name" do
let(:argv) { ["--name", "barbox"] }
it "removes the old versions of the specified provider" do
# Let's put some things in the index
iso_env.box3("foobox", "1.0", :virtualbox);
iso_env.box3("foobox", "1.1", :virtualbox);
iso_env.box3("barbox", "1.0", :vmware);
iso_env.box3("barbox", "1.1", :vmware);
iso_vagrant_env.machine_index.set(new_entry("foo", "foobox", "virtualbox", 1))
output = ""
allow(iso_vagrant_env.ui).to receive(:info) do |data|
output << "\n" + data
end
expect(iso_vagrant_env.boxes.all.count).to eq(4)
expect(subject.execute).to eq(0)
expect(iso_vagrant_env.boxes.all.count).to eq(3)
expect(output).to include("barbox (vmware, 1.1)")
expect(output).to include("Removing box 'barbox' (v1.0) with provider 'vmware'...")
end
end
context "with --name and --provider" do
let(:argv) { ["--name", "foobox", "--provider", "virtualbox"] }
it "removed the old versions of that name and provider only" do
# Let's put some things in the index
iso_env.box3("foobox", "1.0", :virtualbox);
iso_env.box3("foobox", "1.1", :virtualbox);
iso_env.box3("foobox", "1.0", :vmware);
iso_env.box3("foobox", "1.1", :vmware);
iso_env.box3("barbox", "1.0", :vmware);
iso_env.box3("barbox", "1.1", :vmware);
iso_vagrant_env.machine_index.set(new_entry("foo", "foobox", "virtualbox", 1))
output = ""
allow(iso_vagrant_env.ui).to receive(:info) do |data|
output << "\n" + data
end
expect(iso_vagrant_env.boxes.all.count).to eq(6)
expect(subject.execute).to eq(0)
expect(iso_vagrant_env.boxes.all.count).to eq(5)
expect(output).to include("Removing box 'foobox' (v1.0) with provider 'virtualbox'...")
end
end
end
end

View File

@ -100,6 +100,8 @@ user has to manually enter a command to do it.
Vagrant does not automatically prune old versions because it does not know
if they might be in use by other Vagrant environments. Because boxes can
be large, you may want to actively prune them once in awhile using
be large, you may want to actively prune them once in a while using
`vagrant box remove`. You can see all the boxes that are installed
using `vagrant box list`.
Another option is to use `vagrant box prune` command to remove all installed boxes that are outdated and not currently in use.

View File

@ -150,6 +150,24 @@ with the `--all` flag.
name. This is only required if a box is backed by multiple providers.
If there is only a single provider, Vagrant will default to removing it.
# Box prune
**Command: `vagrant box prune`**
This command removes old versions of installed boxes. If the box in currently in use vagrant will ask you if you to confirm.
## Options
* `--provider PROVIDER` - The specific provider type for the boxes to destroy.
* `--dry-run` - Only print the boxes that would be removed.
* `--name NAME` - The specific box name to check for outdated versions.
* `--force` - Destroy without confirmation even when box is in use.
# Box Repackage
**Command: `vagrant box repackage NAME PROVIDER VERSION`**