class Vagrant::Vagrantfile

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.

Attributes

config[R]

This is the configuration loaded as-is given the loader and keys to initialize.

Public Class Methods

new(loader, keys) click to toggle source

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).
# File lib/vagrant/vagrantfile.rb, line 28
def initialize(loader, keys)
  @keys   = keys
  @loader = loader
  @config, _ = loader.load(keys)
  @logger = Log4r::Logger.new("vagrant::vagrantfile")
end

Public Instance Methods

machine(name, provider, boxes, data_path, env) click to toggle source

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]

# File lib/vagrant/vagrantfile.rb, line 47
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
machine_config(name, provider, boxes, data_path=nil, validate_provider=true) click to toggle source

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.
# File lib/vagrant/vagrantfile.rb, line 116
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 initial 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
machine_names() click to toggle source

Returns a list of the machines that are defined within this Vagrantfile.

@return [Array<Symbol>]

# File lib/vagrant/vagrantfile.rb, line 270
def machine_names
  @config.vm.defined_vm_keys.dup
end
machine_names_and_options() click to toggle source

Returns a list of the machine names as well as the options that were specified for that machine.

@return [Hash<Symbol, Hash>]

# File lib/vagrant/vagrantfile.rb, line 278
def machine_names_and_options
  {}.tap do |r|
    @config.vm.defined_vms.each do |name, subvm|
      r[name] = subvm.options || {}
    end
  end
end
primary_machine_name() click to toggle source

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]

# File lib/vagrant/vagrantfile.rb, line 295
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 Instance Methods

find_vagrantfile(search_path) click to toggle source
# File lib/vagrant/vagrantfile.rb, line 310
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