core: BoxCheckOutdated can check if a box is outdated

This commit is contained in:
Mitchell Hashimoto 2014-01-24 14:50:55 -08:00
parent f2509f5c65
commit e537e02d9d
6 changed files with 281 additions and 4 deletions

View File

@ -9,6 +9,7 @@ module Vagrant
# and are thus available to all plugins as a "standard library" of sorts.
module Builtin
autoload :BoxAdd, "vagrant/action/builtin/box_add"
autoload :BoxCheckOutdated, "vagrant/action/builtin/box_check_outdated"
autoload :BoxRemove, "vagrant/action/builtin/box_remove"
autoload :Call, "vagrant/action/builtin/call"
autoload :Confirm, "vagrant/action/builtin/confirm"
@ -45,6 +46,14 @@ module Vagrant
end
end
# This actions checks if a box is outdated in a given Vagrant
# environment for a single machine.
def self.action_box_outdated
Builder.new.tap do |b|
b.use Builtin::BoxCheckOutdated
end
end
# This is the action that will remove a box given a name (and optionally
# a provider). This middleware sequence is built-in to Vagrant. Plugins
# can hook into this like any other middleware sequence.

View File

@ -0,0 +1,65 @@
require "digest/sha1"
require "log4r"
require "pathname"
require "uri"
require "vagrant/box_metadata"
require "vagrant/util/downloader"
require "vagrant/util/file_checksum"
require "vagrant/util/platform"
module Vagrant
module Action
module Builtin
# This middleware checks if there are outdated boxes. By default,
# it only checks locally, but if `box_outdated_refresh` is set, it
# will refresh the metadata associated with a box.
class BoxCheckOutdated
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new(
"vagrant::action::builtin::box_check_outdated")
end
def call(env)
machine = env[:machine]
if !machine.box
# The box doesn't exist. I suppose technically that means
# that it is "outdated" but we show a specialized error
# message anyways.
raise Errors::BoxOutdatedNoBox, name: machine.config.vm.box
end
if !machine.box.metadata_url
# This box doesn't have a metadata URL, so we can't
# possibly check the version information.
raise Errors::BoxOutdatedNoMetadata, name: machine.box.name
end
md = machine.box.load_metadata
newer = md.version(
"> #{machine.box.version}", provider: machine.box.provider)
if !newer
env[:ui].success(I18n.t(
"vagrant.box_up_to_date_single",
name: machine.box.name,
version: machine.box.version))
env[:box_outdated] = false
return @app.call(env)
end
env[:ui].warn(I18n.t(
"vagrant.box_outdated_single",
name: machine.box.name,
current: machine.box.version,
latest: newer.version))
env[:box_outdated] = true
@app.call(env)
end
end
end
end
end

View File

@ -172,6 +172,14 @@ module Vagrant
error_key(:box_metadata_malformed)
end
class BoxOutdatedNoBox < VagrantError
error_key(:box_outdated_no_box)
end
class BoxOutdatedNoMetadata < VagrantError
error_key(:box_outdated_no_metadata)
end
class BoxProviderDoesntMatch < VagrantError
error_key(:box_provider_doesnt_match)
end

View File

@ -5,10 +5,34 @@ module VagrantPlugins
module Command
class Outdated < Vagrant.plugin("2", :command)
def execute
OptionParser.new do |o|
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant box outdated"
o.separator ""
o.on("--global", "Check all boxes installed.") do |g|
options[:global] = g
end
end
argv = parse_options(opts)
# If we're checking the boxes globally, then do that.
if options[:global]
outdated_global
return 0
end
with_target_vms(argv) do |machine|
@env.action_runner.run(Vagrant::Action.action_box_outdated, {
box_outdated_refresh: true,
machine: machine,
})
end
end
def outdated_global
boxes = {}
@env.boxes.all.reverse.each do |name, version, provider|
next if boxes[name]
@ -49,9 +73,6 @@ module VagrantPlugins
latest: latest.to_s,))
end
end
# Success, exit status 0
0
end
end
end

View File

@ -30,12 +30,18 @@ en:
Loading metadata for box '%{name}'
box_outdated: |-
* '%{name}' is outdated! Current: %{current}. Latest: %{latest}
box_outdated_single: |-
A newer version of the box '%{name}' is available! You currently
have version '%{current}'. The latest is version '%{latest}'. Run
`vagrant box update` to update.
box_outdated_metadata_error: |-
* '%{name}': Error loading metadata: %{message}
box_outdated_no_metadata: |-
* '%{name}' wasn't added from a catalog, no version information
box_up_to_date: |-
* '%{name}' (v%{version}) is up to date
box_up_to_date_single: |-
Your box '%{name}' (v%{version}) is running the latest version.
cfengine_bootstrapping: |-
Bootstrapping CFEngine with policy server: %{policy_server}...
cfengine_bootstrapping_policy_hub: |-
@ -357,6 +363,15 @@ en:
that this issue can be fixed.
%{error}
box_outdated_no_metadata: |-
The box '%{name}' is not a versioned box. The box was added
directly instead of from a box catalog. Vagrant can only
check the versions of boxes that were added from a catalog
such as from the public Vagrant Server.
box_outdated_no_box: |-
The box '%{name}' isn't downloaded or added yet, so we can't
check if it is outdated. Run a `vagrant up` or add the box
with `vagrant box add` to download an appropriate version.
box_provider_doesnt_match: |-
The box you attempted to add doesn't match the provider you specified.

View File

@ -0,0 +1,159 @@
require File.expand_path("../../../../base", __FILE__)
describe Vagrant::Action::Builtin::BoxCheckOutdated do
include_context "unit"
let(:app) { lambda { |env| } }
let(:env) { {
machine: machine,
} }
subject { described_class.new(app, env) }
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(:box) do
box_dir = iso_env.box3("foo", "1.0", :virtualbox)
Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir)
end
let(:machine) { iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy) }
context "no box" do
it "raises an exception if the machine doesn't have a box yet" do
machine.stub(box: nil)
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxOutdatedNoBox)
end
end
context "box with no metadata_url" do
let(:box) do
box_dir = iso_env.box3("foo", "1.0", :virtualbox)
Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir)
end
before do
machine.stub(box: box)
end
it "raises an exception" do
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxOutdatedNoMetadata)
end
end
context "with a metadata URL" do
let(:metadata_url) do
Tempfile.new("vagrant").tap do |f|
f.close
end
end
let(:box_dir) { iso_env.box3("foo", "1.0", :virtualbox) }
it "isn't outdated if it isn't" do
File.open(metadata_url.path, "w") do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "1.0",
"providers": [
{
"name": "virtualbox",
"url": "#{iso_env.box2_file(:virtualbox)}"
}
]
}
]
}
RAW
end
box = Vagrant::Box.new(
"foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path)
machine.stub(box: box)
subject.call(env)
expect(env[:box_outdated]).to be_false
end
it "is outdated if it is" do
File.open(metadata_url.path, "w") do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "1.0"
},
{
"version": "1.5",
"providers": [
{
"name": "virtualbox",
"url": "#{iso_env.box2_file(:virtualbox)}"
}
]
}
]
}
RAW
end
box = Vagrant::Box.new(
"foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path)
machine.stub(box: box)
subject.call(env)
expect(env[:box_outdated]).to be_true
end
it "isn't outdated if the newer box is for another provider" do
File.open(metadata_url.path, "w") do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "1.0"
},
{
"version": "1.5",
"providers": [
{
"name": "vmware",
"url": "#{iso_env.box2_file(:vmware)}"
}
]
}
]
}
RAW
end
box = Vagrant::Box.new(
"foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path)
machine.stub(box: box)
subject.call(env)
expect(env[:box_outdated]).to be_false
end
end
end