126 lines
4.3 KiB
Ruby
126 lines
4.3 KiB
Ruby
require "pathname"
|
|
|
|
require "log4r"
|
|
|
|
module Vagrant
|
|
module Config
|
|
# This class is responsible for loading Vagrant configuration,
|
|
# usually in the form of Vagrantfiles.
|
|
#
|
|
# Loading works by specifying the sources for the configuration
|
|
# as well as the order the sources should be loaded. Configuration
|
|
# set later always overrides those set earlier; this is how
|
|
# configuration "scoping" is implemented.
|
|
class Loader
|
|
# This is an array of symbols specifying the order in which
|
|
# configuration is loaded. For examples, see the class documentation.
|
|
attr_accessor :load_order
|
|
|
|
def initialize
|
|
@logger = Log4r::Logger.new("vagrant::config::loader")
|
|
@sources = {}
|
|
@proc_cache = {}
|
|
@config_cache = {}
|
|
end
|
|
|
|
# Set the configuration data for the given name.
|
|
#
|
|
# The `name` should be a symbol and must uniquely identify the data
|
|
# being given.
|
|
#
|
|
# `data` can either be a path to a Ruby Vagrantfile or a `Proc` directly.
|
|
# `data` can also be an array of such values.
|
|
#
|
|
# At this point, no configuration is actually loaded. Note that calling
|
|
# `set` multiple times with the same name will override any previously
|
|
# set values. In this way, the last set data for a given name wins.
|
|
def set(name, sources)
|
|
@logger.debug("Set #{name.inspect} = #{sources.inspect}")
|
|
|
|
# Sources should be an array
|
|
sources = [sources] if !sources.kind_of?(Array)
|
|
|
|
# Gather the procs for every source, since that is what we care about.
|
|
procs = []
|
|
sources.each do |source|
|
|
if !@proc_cache.has_key?(source)
|
|
# Load the procs for this source and cache them. This caching
|
|
# avoids the issue where a file may have side effects when loading
|
|
# and loading it multiple times causes unexpected behavior.
|
|
@logger.debug("Populating proc cache for #{source.inspect}")
|
|
@proc_cache[source] = procs_for_source(source)
|
|
end
|
|
|
|
# Add on to the array of procs we're going to use
|
|
procs.concat(@proc_cache[source])
|
|
end
|
|
|
|
# Set this source by name.
|
|
@sources[name] = procs
|
|
end
|
|
|
|
# This loads the configured sources in the configured order and returns
|
|
# an actual configuration object that is ready to be used.
|
|
def load
|
|
@logger.debug("Loading configuration in order: #{@load_order.inspect}")
|
|
|
|
unknown_sources = @sources.keys - @load_order
|
|
if !unknown_sources.empty?
|
|
# TODO: Raise exception here perhaps.
|
|
@logger.error("Unknown config sources: #{unknown_sources.inspect}")
|
|
end
|
|
|
|
# Create the top-level configuration which will hold all the config.
|
|
result = Top.new
|
|
|
|
@load_order.each do |key|
|
|
next if !@sources.has_key?(key)
|
|
|
|
@sources[key].each do |proc|
|
|
if !@config_cache.has_key?(proc)
|
|
@logger.debug("Loading from: #{key} (evaluating)")
|
|
current = Top.new
|
|
proc.call(current)
|
|
@config_cache[proc] = current
|
|
end
|
|
|
|
# Merge in the results of this proc's configuration
|
|
result = result.merge(@config_cache[proc])
|
|
end
|
|
end
|
|
|
|
@logger.debug("Configuration loaded successfully")
|
|
result
|
|
end
|
|
|
|
protected
|
|
|
|
# This returns an array of `Proc` objects for the given source.
|
|
# The `Proc` objects returned will expect a single argument for
|
|
# the configuration object and are expected to mutate this
|
|
# configuration object.
|
|
def procs_for_source(source)
|
|
return [source] if source.is_a?(Proc)
|
|
|
|
# Assume all string sources are actually pathnames
|
|
source = Pathname.new(source) if source.is_a?(String)
|
|
|
|
if source.is_a?(Pathname)
|
|
@logger.debug("Load procs for pathname: #{source.inspect}")
|
|
|
|
begin
|
|
return Config.capture_configures do
|
|
Kernel.load source
|
|
end
|
|
rescue SyntaxError => e
|
|
# Report syntax errors in a nice way.
|
|
raise Errors::VagrantfileSyntaxError, :file => e.message
|
|
end
|
|
end
|
|
|
|
raise Exception, "Unknown configuration source: #{source.inspect}"
|
|
end
|
|
end
|
|
end
|
|
end
|