Configuration loader can handle upgrading.
The basic process for this is to: 1. Load the configuration using the proper loader for that version. i.e. if you're loading V1 config, then use the V1 loader. 2. If we just loaded a version that isn't current (imagine we're currently at V3), then we need to upgrade that config. So we first ask the V2 loader to upgrade the V1 config to V2, then we ask the V3 loader to upgrade the V2 config to V3. We keep track of warnings and errors throughout this process. 3. Finally, we have a current config, so we merge it into the in-process configuration that is being loaded.
This commit is contained in:
parent
9bc1ea5f04
commit
7a299ae2de
|
@ -81,7 +81,7 @@ module Vagrant
|
|||
end
|
||||
|
||||
# Get the current version config class to use
|
||||
current_version = @version_order.last
|
||||
current_version = @version_order.last
|
||||
current_config_klass = @versions.get(current_version)
|
||||
|
||||
# This will hold our result
|
||||
|
@ -93,7 +93,40 @@ module Vagrant
|
|||
@sources[key].each do |version, proc|
|
||||
if !@config_cache.has_key?(proc)
|
||||
@logger.debug("Loading from: #{key} (evaluating)")
|
||||
@config_cache[proc] = current_config_klass.load(proc)
|
||||
|
||||
# Get the proper version loader for this version and load
|
||||
version_loader = @versions.get(version)
|
||||
version_config = version_loader.load(proc)
|
||||
|
||||
# If this version is not the current version, then we need
|
||||
# to upgrade to the latest version.
|
||||
if version != current_version
|
||||
@logger.debug("Upgrading config from #{version} to #{current_version}")
|
||||
version_index = @version_order.index(version)
|
||||
current_index = @version_order.index(current_version)
|
||||
|
||||
(version_index + 1).upto(current_index) do |index|
|
||||
next_version = @version_order[index]
|
||||
@logger.debug("Upgrading config to version #{next_version}")
|
||||
|
||||
# Get the loader of this version and ask it to upgrade
|
||||
loader = @versions.get(next_version)
|
||||
upgrade_result = loader.upgrade(version_config)
|
||||
|
||||
# XXX: Do something with the warning/error messages
|
||||
warnings = upgrade_result[1]
|
||||
errors = upgrade_result[2]
|
||||
@logger.debug("Upgraded to version #{next_version} with " +
|
||||
"#{warnings.length} warnings and " +
|
||||
"#{errors.length} errors")
|
||||
|
||||
# Store the new upgraded version
|
||||
version_config = upgrade_result[0]
|
||||
end
|
||||
end
|
||||
|
||||
# Cache the results for this proc
|
||||
@config_cache[proc] = version_config
|
||||
else
|
||||
@logger.debug("Loading from: #{key} (cache)")
|
||||
end
|
||||
|
|
|
@ -57,6 +57,24 @@ module Vagrant
|
|||
def self.merge(old, new)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# This is called if a previous version of configuration needs to be
|
||||
# upgraded to this version. Each version of configuration should know
|
||||
# how to upgrade the version immediately prior to it. This should be
|
||||
# a best effort upgrade that makes many assumptions. The goal is for
|
||||
# this to work in almost every case, but perhaps with some warnings.
|
||||
# The return value for this is a 3-tuple: `[object, warnings, errors]`,
|
||||
# where `object` is the upgraded configuration object, `warnings` is
|
||||
# an array of warning messages, and `errors` is an array of error
|
||||
# messages.
|
||||
#
|
||||
# @param [Object] old The version of the configuration object just
|
||||
# prior to this one.
|
||||
# @return [Array] The 3-tuple result. Please see the above documentation
|
||||
# for more information on the exact structure of this object.
|
||||
def self.upgrade(old)
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,95 +38,125 @@ describe Vagrant::Config::Loader do
|
|||
|
||||
let(:instance) { described_class.new(versions, version_order) }
|
||||
|
||||
it "should ignore non-existent load order keys" do
|
||||
instance.load_order = [:foo]
|
||||
instance.load
|
||||
end
|
||||
|
||||
it "should load and return the configuration" do
|
||||
proc = Proc.new do |config|
|
||||
config[:foo] = "yep"
|
||||
describe "basic loading" do
|
||||
it "should ignore non-existent load order keys" do
|
||||
instance.load_order = [:foo]
|
||||
instance.load
|
||||
end
|
||||
|
||||
instance.load_order = [:proc]
|
||||
instance.set(:proc, [[current_version, proc]])
|
||||
config = instance.load
|
||||
it "should load and return the configuration" do
|
||||
proc = Proc.new do |config|
|
||||
config[:foo] = "yep"
|
||||
end
|
||||
|
||||
config[:foo].should == "yep"
|
||||
end
|
||||
instance.load_order = [:proc]
|
||||
instance.set(:proc, [[current_version, proc]])
|
||||
config = instance.load
|
||||
|
||||
it "should finalize the configuration" do
|
||||
# Create the finalize method on our loader
|
||||
def test_loader.finalize(obj)
|
||||
obj[:finalized] = true
|
||||
obj
|
||||
end
|
||||
|
||||
# Basic configuration proc
|
||||
proc = lambda do |config|
|
||||
config[:foo] = "yep"
|
||||
end
|
||||
|
||||
# Run the actual configuration and assert that we get the proper result
|
||||
instance.load_order = [:proc]
|
||||
instance.set(:proc, [[current_version, proc]])
|
||||
config = instance.load
|
||||
config[:foo].should == "yep"
|
||||
config[:finalized].should == true
|
||||
end
|
||||
|
||||
it "should only run the same proc once" do
|
||||
count = 0
|
||||
proc = Proc.new do |config|
|
||||
config[:foo] = "yep"
|
||||
count += 1
|
||||
end
|
||||
|
||||
instance.load_order = [:proc]
|
||||
instance.set(:proc, [[current_version, proc]])
|
||||
|
||||
5.times do
|
||||
result = instance.load
|
||||
|
||||
# Verify the config result
|
||||
result[:foo].should == "yep"
|
||||
|
||||
# Verify the count is only one
|
||||
count.should == 1
|
||||
config[:foo].should == "yep"
|
||||
end
|
||||
end
|
||||
|
||||
it "should only load configuration files once" do
|
||||
$_config_data = 0
|
||||
describe "finalization" do
|
||||
it "should finalize the configuration" do
|
||||
# Create the finalize method on our loader
|
||||
def test_loader.finalize(obj)
|
||||
obj[:finalized] = true
|
||||
obj
|
||||
end
|
||||
|
||||
# We test both setting a file multiple times as well as multiple
|
||||
# loads, since both should not cache the data.
|
||||
file = temporary_file("$_config_data += 1")
|
||||
instance.load_order = [:file]
|
||||
5.times { instance.set(:file, file) }
|
||||
5.times { instance.load }
|
||||
# Basic configuration proc
|
||||
proc = lambda do |config|
|
||||
config[:foo] = "yep"
|
||||
end
|
||||
|
||||
$_config_data.should == 1
|
||||
# Run the actual configuration and assert that we get the proper result
|
||||
instance.load_order = [:proc]
|
||||
instance.set(:proc, [[current_version, proc]])
|
||||
config = instance.load
|
||||
config[:foo].should == "yep"
|
||||
config[:finalized].should == true
|
||||
end
|
||||
end
|
||||
|
||||
it "should not clear the cache if setting to the same value multiple times" do
|
||||
$_config_data = 0
|
||||
describe "upgrading" do
|
||||
it "should do an upgrade to the latest version" do
|
||||
test_loader_v2 = Class.new(test_loader) do
|
||||
def self.upgrade(old)
|
||||
new = old.dup
|
||||
new[:v2] = true
|
||||
|
||||
file = temporary_file("$_config_data += 1")
|
||||
[new, [], []]
|
||||
end
|
||||
end
|
||||
|
||||
instance.load_order = [:proc]
|
||||
instance.set(:proc, file)
|
||||
5.times { instance.load }
|
||||
versions.register("2") { test_loader_v2 }
|
||||
version_order << "2"
|
||||
|
||||
instance.set(:proc, file)
|
||||
5.times { instance.load }
|
||||
|
||||
$_config_data.should == 1
|
||||
# Load a version 1 proc, and verify it is upgraded to version 2
|
||||
proc = lambda { |config| config[:foo] = "yep" }
|
||||
instance.load_order = [:proc]
|
||||
instance.set(:proc, [["1", proc]])
|
||||
config = instance.load
|
||||
config[:foo].should == "yep"
|
||||
config[:v2].should == true
|
||||
end
|
||||
end
|
||||
|
||||
it "should raise proper error if there is a syntax error in a Vagrantfile" do
|
||||
instance.load_order = [:file]
|
||||
expect { instance.set(:file, temporary_file("Vagrant:^Config")) }.
|
||||
to raise_exception(Vagrant::Errors::VagrantfileSyntaxError)
|
||||
describe "loading edge cases" do
|
||||
it "should only run the same proc once" do
|
||||
count = 0
|
||||
proc = Proc.new do |config|
|
||||
config[:foo] = "yep"
|
||||
count += 1
|
||||
end
|
||||
|
||||
instance.load_order = [:proc]
|
||||
instance.set(:proc, [[current_version, proc]])
|
||||
|
||||
5.times do
|
||||
result = instance.load
|
||||
|
||||
# Verify the config result
|
||||
result[:foo].should == "yep"
|
||||
|
||||
# Verify the count is only one
|
||||
count.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
it "should only load configuration files once" do
|
||||
$_config_data = 0
|
||||
|
||||
# We test both setting a file multiple times as well as multiple
|
||||
# loads, since both should not cache the data.
|
||||
file = temporary_file("$_config_data += 1")
|
||||
instance.load_order = [:file]
|
||||
5.times { instance.set(:file, file) }
|
||||
5.times { instance.load }
|
||||
|
||||
$_config_data.should == 1
|
||||
end
|
||||
|
||||
it "should not clear the cache if setting to the same value multiple times" do
|
||||
$_config_data = 0
|
||||
|
||||
file = temporary_file("$_config_data += 1")
|
||||
|
||||
instance.load_order = [:proc]
|
||||
instance.set(:proc, file)
|
||||
5.times { instance.load }
|
||||
|
||||
instance.set(:proc, file)
|
||||
5.times { instance.load }
|
||||
|
||||
$_config_data.should == 1
|
||||
end
|
||||
|
||||
it "should raise proper error if there is a syntax error in a Vagrantfile" do
|
||||
instance.load_order = [:file]
|
||||
expect { instance.set(:file, temporary_file("Vagrant:^Config")) }.
|
||||
to raise_exception(Vagrant::Errors::VagrantfileSyntaxError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue