Store box metadata of active guest

When a guest is created, the box metadata information is stored in the
machine data directory. This allows modifications to happen to the
Vagrantfile definition of the box in use (box name change, box version
change, etc) while still allowing the Machine instance of an active
guest successfully load the box currently backing it.
This commit is contained in:
Chris Roberts 2018-08-02 11:01:36 -07:00
parent 9daea21c4f
commit 6c1a9dc58e
6 changed files with 158 additions and 18 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]
@ -109,7 +109,7 @@ module Vagrant
# box Vagrantfile.
# @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 +170,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["box_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 +227,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

@ -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

@ -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)