This commit adds a unique error message for an empty box value. It requires modifications to vagrantfile.rb because some Vagrantfile config is used before validation occurs.
319 lines
12 KiB
Ruby
319 lines
12 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, validate_provider=true)
|
|
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 && validate_provider
|
|
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
|
|
|
|
if validate_provider
|
|
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
|
|
else
|
|
box_formats = provider
|
|
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.to_s.empty? && 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
|