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:
Mitchell Hashimoto 2012-06-23 19:56:31 -07:00
parent 9bc1ea5f04
commit 7a299ae2de
3 changed files with 157 additions and 76 deletions

View File

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

View File

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

View File

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