Merge pull request #10083 from chrisroberts/f-box-ver-up

Store box metadata of active guest
This commit is contained in:
Chris Roberts 2018-08-08 08:25:47 -07:00 committed by GitHub
commit 8ec7963968
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 196 additions and 22 deletions

View File

@ -334,7 +334,7 @@ module Vagrant
# then look there.
root_config = vagrantfile.config
if opts[:machine]
machine_info = vagrantfile.machine_config(opts[:machine], nil, nil)
machine_info = vagrantfile.machine_config(opts[:machine], nil, nil, nil)
root_config = machine_info[:config]
end

View File

@ -42,7 +42,7 @@ module Vagrant
# @return [Machine]
def machine(name, provider, boxes, data_path, env)
# Load the configuration for the machine
results = machine_config(name, provider, boxes)
results = machine_config(name, provider, boxes, data_path)
box = results[:box]
config = results[:config]
config_errors = results[:config_errors]
@ -107,9 +107,10 @@ module Vagrant
# be backed by (required for provider overrides).
# @param [BoxCollection] boxes BoxCollection to look up the
# box Vagrantfile.
# @param [Pathname] data_path Machine data path
# @return [Hash<Symbol, Object>] Various configuration parameters for a
# machine. See the main documentation body for more info.
def machine_config(name, provider, boxes)
def machine_config(name, provider, boxes, data_path=nil)
keys = @keys.dup
sub_machine = @config.vm.defined_vms[name]
@ -170,6 +171,19 @@ module Vagrant
original_box = config.vm.box
original_version = config.vm.box_version
# Check if this machine has a local box metadata file
# describing the existing guest. If so, load it and
# set the box name and version to allow the actual
# box in use to be discovered.
if data_path
meta_file = data_path.join("box_meta")
if meta_file.file?
box_meta = JSON.parse(meta_file.read)
config.vm.box = box_meta["name"]
config.vm.box_version = box_meta["version"]
end
end
# The proc below loads the box and provider overrides. This is
# in a proc because it may have to recurse if the provider override
# changes the box.
@ -214,6 +228,11 @@ module Vagrant
# Load the box and provider overrides
load_box_proc.call
# Ensure box attributes are set to original values in
# case they were modified by the local box metadata
config.vm.box = original_box
config.vm.box_version = original_version
return {
box: box,
provider_cls: provider_cls,

View File

@ -100,10 +100,14 @@ module VagrantPlugins
end
if !machine.box
machine.ui.output(I18n.t(
"vagrant.errors.box_update_no_box",
name: machine.config.vm.box))
next
collection = Vagrant::BoxCollection.new(@env.boxes_path)
machine.box = collection.find(machine.config.vm.box, provider || machine.provider_name || @env.default_provider, "> 0")
if !machine.box
machine.ui.output(I18n.t(
"vagrant.errors.box_update_no_box",
name: machine.config.vm.box))
next
end
end
name = machine.box.name

View File

@ -0,0 +1,31 @@
require "json"
module VagrantPlugins
module CommandUp
# Stores metadata information about the box used
# for the current guest. This allows Vagrant to
# determine the box currently in use when the
# Vagrantfile is modified with a new box name or
# version while the guest still exists.
class StoreBoxMetadata
def initialize(app, env)
@app = app
end
def call(env)
box = env[:machine].box
box_meta = {
name: box.name,
version: box.version,
provider: box.provider,
directory: box.directory.sub(Vagrant.user_data_path.to_s + "/", "")
}
meta_file = env[:machine].data_dir.join("box_meta")
File.open(meta_file.to_s, "w+") do |file|
file.write(JSON.dump(box_meta))
end
@app.call(env)
end
end
end
end

View File

@ -12,6 +12,11 @@ module VagrantPlugins
require File.expand_path("../command", __FILE__)
Command
end
action_hook(:store_box_metadata, :machine_action_up) do |hook|
require_relative "middleware/store_box_metadata"
hook.append(StoreBoxMetadata)
end
end
end
end

View File

@ -307,6 +307,35 @@ describe VagrantPlugins::CommandBox::Command::Update do
subject.execute
end
context "when box version is updated but previous box exists" do
let(:collection) { double("collection") }
it "updates the box" do
# First call gets nil result to for lookup
expect(machine).to receive(:box).and_return(nil)
expect(Vagrant::BoxCollection).to receive(:new).and_return(collection)
expect(collection).to receive(:find).and_return(box)
expect(box).to receive(:has_update?).
with(machine.config.vm.box_version,
{download_options:
{ca_cert: nil, ca_path: nil, client_cert: nil,
insecure: false}}).
and_return([md, md.version("1.1"), md.version("1.1").provider("virtualbox")])
expect(action_runner).to receive(:run).with(any_args) { |action, opts|
expect(opts[:box_url]).to eq(box.metadata_url)
expect(opts[:box_provider]).to eq("virtualbox")
expect(opts[:box_version]).to eq("1.1")
expect(opts[:ui]).to equal(machine.ui)
true
}
subject.execute
end
end
context "machine has download options" do
before do
machine.config.vm.box_download_ca_cert = "oof"

View File

@ -0,0 +1,86 @@
require File.expand_path("../../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/commands/up/middleware/store_box_metadata")
describe VagrantPlugins::CommandUp::StoreBoxMetadata do
include_context "unit"
let(:app) { double("app") }
let(:machine) { double("machine", box: box) }
let(:box) {
double("box",
name: box_name,
version: box_version,
provider: box_provider,
directory: box_directory
)
}
let(:box_name) { "BOX_NAME" }
let(:box_version) { "1.0.0" }
let(:box_provider) { "dummy" }
let(:box_directory) { File.join(vagrant_user_data_path, box_directory_relative) }
let(:box_directory_relative) { File.join("boxes", "BOX_NAME") }
let(:vagrant_user_data_path) { "/vagrant/user/data" }
let(:meta_path) { "META_PATH" }
let(:env) { {machine: machine} }
let(:subject) { described_class.new(app, env) }
describe "#call" do
let(:meta_file) { double("meta_file") }
before do
allow(Vagrant).to receive(:user_data_path).and_return(vagrant_user_data_path)
allow(machine).to receive(:data_dir).and_return(meta_path)
allow(meta_path).to receive(:join).with("box_meta").and_return(meta_path)
allow(File).to receive(:open)
expect(app).to receive(:call).with(env)
end
after { subject.call(env) }
it "should open a metadata file" do
expect(File).to receive(:open).with(meta_path, anything)
end
context "contents of metadata file" do
before { expect(File).to receive(:open).with(meta_path, anything).and_yield(meta_file) }
it "should be JSON data" do
expect(meta_file).to receive(:write) do |data|
val = JSON.parse(data)
expect(val).to be_a(Hash)
end
end
it "should include box name" do
expect(meta_file).to receive(:write) do |data|
val = JSON.parse(data)
expect(val["name"]).to eq(box_name)
end
end
it "should include box version" do
expect(meta_file).to receive(:write) do |data|
val = JSON.parse(data)
expect(val["version"]).to eq(box_version)
end
end
it "should include box provider" do
expect(meta_file).to receive(:write) do |data|
val = JSON.parse(data)
expect(val["provider"]).to eq(box_provider)
end
end
it "should include relative box directory" do
expect(meta_file).to receive(:write) do |data|
val = JSON.parse(data)
expect(val["directory"]).to eq(box_directory_relative)
end
end
end
end
end

View File

@ -270,50 +270,50 @@ describe Vagrant::Machine do
end
it "should be able to run an action that exists" do
action_name = :up
action_name = :destroy
called = false
callable = lambda { |_env| called = true }
expect(provider).to receive(:action).with(action_name).and_return(callable)
instance.action(:up)
instance.action(action_name)
expect(called).to be
end
it "should provide the machine in the environment" do
action_name = :up
action_name = :destroy
machine = nil
callable = lambda { |env| machine = env[:machine] }
allow(provider).to receive(:action).with(action_name).and_return(callable)
instance.action(:up)
instance.action(action_name)
expect(machine).to eql(instance)
end
it "should pass any extra options to the environment" do
action_name = :up
action_name = :destroy
foo = nil
callable = lambda { |env| foo = env[:foo] }
allow(provider).to receive(:action).with(action_name).and_return(callable)
instance.action(:up, foo: :bar)
instance.action(action_name, foo: :bar)
expect(foo).to eq(:bar)
end
it "should pass any extra options to the environment as strings" do
action_name = :up
action_name = :destroy
foo = nil
callable = lambda { |env| foo = env["foo"] }
allow(provider).to receive(:action).with(action_name).and_return(callable)
instance.action(:up, "foo" => :bar)
instance.action(action_name, "foo" => :bar)
expect(foo).to eq(:bar)
end
it "should return the environment as a result" do
action_name = :up
action_name = :destroy
callable = lambda { |env| env[:result] = "FOO" }
allow(provider).to receive(:action).with(action_name).and_return(callable)
@ -323,7 +323,7 @@ describe Vagrant::Machine do
end
it "should raise an exception if the action is not implemented" do
action_name = :up
action_name = :destroy
allow(provider).to receive(:action).with(action_name).and_return(nil)
@ -332,7 +332,7 @@ describe Vagrant::Machine do
end
it 'should not warn if the machines cwd has not changed' do
initial_action_name = :up
initial_action_name = :destroy
second_action_name = :reload
callable = lambda { |_env| }
original_cwd = env.cwd.to_s
@ -349,7 +349,7 @@ describe Vagrant::Machine do
end
it 'should warn if the machine was last run under a different directory' do
action_name = :up
action_name = :destroy
callable = lambda { |_env| }
original_cwd = env.cwd.to_s
@ -374,7 +374,7 @@ describe Vagrant::Machine do
let (:data_dir) { env.cwd }
it 'should not warn if vagrant is run in subdirectory' do
action_name = :up
action_name = :destroy
callable = lambda { |_env| }
original_cwd = env.cwd.to_s
@ -394,7 +394,7 @@ describe Vagrant::Machine do
context "with the vagrant-triggers community plugin" do
it "should not call the internal trigger functions if installed" do
action_name = :up
action_name = :destroy
callable = lambda { |_env| }
allow(provider).to receive(:action).with(action_name).and_return(callable)
@ -412,7 +412,7 @@ describe Vagrant::Machine do
end
it "should call the internal trigger functions if not installed" do
action_name = :up
action_name = :destroy
callable = lambda { |_env| }
allow(provider).to receive(:action).with(action_name).and_return(callable)