315 lines
11 KiB
Ruby
315 lines
11 KiB
Ruby
require "vagrant/util/template_renderer"
|
|
require "log4r"
|
|
|
|
module Vagrant
|
|
# This class provides a way to load and access the contents
|
|
# of a Vagrantfile.
|
|
#
|
|
# This class doesn't actually load Vagrantfiles, parse them,
|
|
# merge them, etc. That is the job of {Config::Loader}. This
|
|
# class, on the other hand, has higher-level operations on
|
|
# a loaded Vagrantfile such as looking up the defined machines,
|
|
# loading the configuration of a specific machine/provider combo,
|
|
# etc.
|
|
class Vagrantfile
|
|
# This is the configuration loaded as-is given the loader and
|
|
# keys to #initialize.
|
|
attr_reader :config
|
|
|
|
# Initializes by loading a Vagrantfile.
|
|
#
|
|
# @param [Config::Loader] loader Configuration loader that should
|
|
# already be configured with the proper Vagrantfile locations.
|
|
# This usually comes from {Vagrant::Environment}
|
|
# @param [Array<Symbol>] keys The Vagrantfiles to load and the
|
|
# order to load them in (keys within the loader).
|
|
def initialize(loader, keys)
|
|
@keys = keys
|
|
@loader = loader
|
|
@config, _ = loader.load(keys)
|
|
@logger = Log4r::Logger.new("vagrant::vagrantfile")
|
|
end
|
|
|
|
# Returns a {Machine} for the given name and provider that
|
|
# is represented by this Vagrantfile.
|
|
#
|
|
# @param [Symbol] name Name of the machine.
|
|
# @param [Symbol] provider The provider the machine should
|
|
# be backed by (required for provider overrides).
|
|
# @param [BoxCollection] boxes BoxCollection to look up the
|
|
# box Vagrantfile.
|
|
# @param [Pathname] data_path Path where local machine data
|
|
# can be stored.
|
|
# @param [Environment] env The environment running this machine
|
|
# @return [Machine]
|
|
def machine(name, provider, boxes, data_path, env)
|
|
# Load the configuration for the machine
|
|
results = machine_config(name, provider, boxes, data_path)
|
|
box = results[:box]
|
|
config = results[:config]
|
|
config_errors = results[:config_errors]
|
|
config_warnings = results[:config_warnings]
|
|
provider_cls = results[:provider_cls]
|
|
provider_options = results[:provider_options]
|
|
|
|
# If there were warnings or errors we want to output them
|
|
if !config_warnings.empty? || !config_errors.empty?
|
|
# The color of the output depends on whether we have warnings
|
|
# or errors...
|
|
level = config_errors.empty? ? :warn : :error
|
|
output = Util::TemplateRenderer.render(
|
|
"config/messages",
|
|
warnings: config_warnings,
|
|
errors: config_errors).chomp
|
|
env.ui.send(level, I18n.t("vagrant.general.config_upgrade_messages",
|
|
name: name,
|
|
output: output))
|
|
|
|
# If we had errors, then we bail
|
|
raise Errors::ConfigUpgradeErrors if !config_errors.empty?
|
|
end
|
|
|
|
# Get the provider configuration from the final loaded configuration
|
|
provider_config = config.vm.get_provider_config(provider)
|
|
|
|
# Create machine data directory if it doesn't exist
|
|
# XXX: Permissions error here.
|
|
FileUtils.mkdir_p(data_path)
|
|
|
|
# Create the machine and cache it for future calls. This will also
|
|
# return the machine from this method.
|
|
return Machine.new(name, provider, provider_cls, provider_config,
|
|
provider_options, config, data_path, box, env, self)
|
|
end
|
|
|
|
# Returns the configuration for a single machine.
|
|
#
|
|
# When loading a box Vagrantfile, it will be prepended to the
|
|
# key order specified when initializing this class. Sub-machine
|
|
# and provider-specific overrides are appended at the end. The
|
|
# actual order is:
|
|
#
|
|
# - box
|
|
# - keys specified for #initialize
|
|
# - sub-machine
|
|
# - provider
|
|
#
|
|
# The return value is a hash with the following keys (symbols)
|
|
# and values:
|
|
#
|
|
# - box: the {Box} backing the machine
|
|
# - config: the actual configuration
|
|
# - config_errors: list of errors, if any
|
|
# - config_warnings: list of warnings, if any
|
|
# - provider_cls: class of the provider backing the machine
|
|
# - provider_options: options for the provider
|
|
#
|
|
# @param [Symbol] name Name of the machine.
|
|
# @param [Symbol] provider The provider the machine should
|
|
# 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, data_path=nil)
|
|
keys = @keys.dup
|
|
|
|
sub_machine = @config.vm.defined_vms[name]
|
|
if !sub_machine
|
|
raise Errors::MachineNotFound,
|
|
name: name, provider: provider
|
|
end
|
|
|
|
provider_plugin = nil
|
|
provider_cls = nil
|
|
provider_options = {}
|
|
box_formats = nil
|
|
if provider != nil
|
|
provider_plugin = Vagrant.plugin("2").manager.providers[provider]
|
|
if !provider_plugin
|
|
providers = Vagrant.plugin("2").manager.providers.to_hash.keys
|
|
if providers
|
|
providers_str = providers.join(', ')
|
|
else
|
|
providers_str = "N/A"
|
|
end
|
|
|
|
if providers.include? provider.downcase
|
|
raise Errors::ProviderNotFoundSuggestion,
|
|
machine: name, provider: provider,
|
|
suggestion: provider.downcase, providers: providers_str
|
|
end
|
|
|
|
raise Errors::ProviderNotFound,
|
|
machine: name, provider: provider, providers: providers_str
|
|
end
|
|
|
|
provider_cls = provider_plugin[0]
|
|
provider_options = provider_plugin[1]
|
|
box_formats = provider_options[:box_format] || provider
|
|
|
|
# Test if the provider is usable or not
|
|
begin
|
|
provider_cls.usable?(true)
|
|
rescue Errors::VagrantError => e
|
|
raise Errors::ProviderNotUsable,
|
|
machine: name.to_s,
|
|
provider: provider.to_s,
|
|
message: e.to_s
|
|
end
|
|
end
|
|
|
|
# Add the sub-machine configuration to the loader and keys
|
|
vm_config_key = "#{object_id}_machine_#{name}"
|
|
@loader.set(vm_config_key, sub_machine.config_procs)
|
|
keys << vm_config_key
|
|
|
|
# Load once so that we can get the proper box value
|
|
config, config_warnings, config_errors = @loader.load(keys)
|
|
|
|
# Track the original box so we know if we changed
|
|
box = nil
|
|
initial_box = original_box = config.vm.box
|
|
initial_version = 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.
|
|
load_box_proc = lambda do
|
|
local_keys = keys.dup
|
|
|
|
# Load the box Vagrantfile, if there is one
|
|
if config.vm.box && boxes
|
|
box = boxes.find(config.vm.box, box_formats, config.vm.box_version)
|
|
if box
|
|
box_vagrantfile = find_vagrantfile(box.directory)
|
|
if box_vagrantfile && !config.vm.ignore_box_vagrantfile
|
|
box_config_key =
|
|
"#{boxes.object_id}_#{box.name}_#{box.provider}".to_sym
|
|
@loader.set(box_config_key, box_vagrantfile)
|
|
local_keys.unshift(box_config_key)
|
|
config, config_warnings, config_errors = @loader.load(local_keys)
|
|
elsif box_vagrantfile && config.vm.ignore_box_vagrantfile
|
|
@logger.warn("Ignoring #{box.name} provided Vagrantfile inside box")
|
|
end
|
|
end
|
|
end
|
|
|
|
# Load provider overrides
|
|
provider_overrides = config.vm.get_provider_overrides(provider)
|
|
if !provider_overrides.empty?
|
|
config_key =
|
|
"#{object_id}_vm_#{name}_#{config.vm.box}_#{provider}".to_sym
|
|
@loader.set(config_key, provider_overrides)
|
|
local_keys << config_key
|
|
config, config_warnings, config_errors = @loader.load(local_keys)
|
|
end
|
|
|
|
# If the box changed, then we need to reload
|
|
if original_box != config.vm.box || original_version != config.vm.box_version
|
|
# TODO: infinite loop protection?
|
|
|
|
original_box = config.vm.box
|
|
original_version = config.vm.box_version
|
|
load_box_proc.call
|
|
end
|
|
end
|
|
|
|
# Load the box and provider overrides
|
|
load_box_proc.call
|
|
|
|
# NOTE: In cases where the box_meta file contains stale information
|
|
# and the reference box no longer exists, fall back to initial
|
|
# configuration and attempt to load that
|
|
if box.nil?
|
|
@logger.warn("Failed to locate #{config.vm.box} with version #{config.vm.box_version}")
|
|
@logger.warn("Performing lookup with inital values #{initial_box} with version #{initial_version}")
|
|
config.vm.box = original_box = initial_box
|
|
config.vm.box_version = original_box = initial_version
|
|
load_box_proc.call
|
|
end
|
|
|
|
# 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,
|
|
provider_options: provider_options.dup,
|
|
config: config,
|
|
config_warnings: config_warnings,
|
|
config_errors: config_errors,
|
|
}
|
|
end
|
|
|
|
# Returns a list of the machines that are defined within this
|
|
# Vagrantfile.
|
|
#
|
|
# @return [Array<Symbol>]
|
|
def machine_names
|
|
@config.vm.defined_vm_keys.dup
|
|
end
|
|
|
|
# Returns a list of the machine names as well as the options that
|
|
# were specified for that machine.
|
|
#
|
|
# @return [Hash<Symbol, Hash>]
|
|
def machine_names_and_options
|
|
{}.tap do |r|
|
|
@config.vm.defined_vms.each do |name, subvm|
|
|
r[name] = subvm.options || {}
|
|
end
|
|
end
|
|
end
|
|
|
|
# Returns the name of the machine that is designated as the
|
|
# "primary."
|
|
#
|
|
# In the case of a single-machine environment, this is just the
|
|
# single machine name. In the case of a multi-machine environment,
|
|
# then this is the machine that is marked as primary, or nil if
|
|
# no primary machine was specified.
|
|
#
|
|
# @return [Symbol]
|
|
def primary_machine_name
|
|
# If it is a single machine environment, then return the name
|
|
return machine_names.first if machine_names.length == 1
|
|
|
|
# If it is a multi-machine environment, then return the primary
|
|
@config.vm.defined_vms.each do |name, subvm|
|
|
return name if subvm.options[:primary]
|
|
end
|
|
|
|
# If no primary was specified, nil it is
|
|
nil
|
|
end
|
|
|
|
protected
|
|
|
|
def find_vagrantfile(search_path)
|
|
["Vagrantfile", "vagrantfile"].each do |vagrantfile|
|
|
current_path = search_path.join(vagrantfile)
|
|
return current_path if current_path.file?
|
|
end
|
|
|
|
nil
|
|
end
|
|
end
|
|
end
|