class Vagrant::Machine

This represents a machine that Vagrant manages. This provides a singular API for querying the state and making state changes to the machine, which is backed by any sort of provider (VirtualBox, VMware, etc.).

Attributes

box[RW]

The box that is backing this machine.

@return [Box]

config[RW]

Configuration for the machine.

@return [Object]

data_dir[R]

Directory where machine-specific data can be stored.

@return [Pathname]

env[R]

The environment that this machine is a part of.

@return [Environment]

id[R]

ID of the machine. This ID comes from the provider and is not guaranteed to be of any particular format except that it is a string.

@return [String]

name[R]

Name of the machine. This is assigned by the Vagrantfile.

@return [Symbol]

provider[R]

The provider backing this machine.

@return [Object]

provider_config[RW]

The provider-specific configuration for this machine.

@return [Object]

provider_name[R]

The name of the provider.

@return [Symbol]

provider_options[R]

The options given to the provider when registering the plugin.

@return [Hash]

triggers[R]

The triggers with machine specific configuration applied

@return [Vagrant::Plugin::V2::Trigger]

ui[R]

The UI for outputting in the scope of this machine.

@return [UI]

vagrantfile[R]

The Vagrantfile that this machine is attached to.

@return [Vagrantfile]

Public Class Methods

new(name, provider_name, provider_cls, provider_config, provider_options, config, data_dir, box, env, vagrantfile, base=false) click to toggle source

Initialize a new machine.

@param [String] name Name of the virtual machine. @param [Class] provider The provider backing this machine. This is

currently expected to be a V1 `provider` plugin.

@param [Object] provider_config The provider-specific configuration for

this machine.

@param [Hash] provider_options The provider-specific options from the

plugin definition.

@param [Object] config The configuration for this machine. @param [Pathname] data_dir The directory where machine-specific data

can be stored. This directory is ensured to exist.

@param [Box] box The box that is backing this virtual machine. @param [Environment] env The environment that this machine is a

part of.
# File lib/vagrant/machine.rb, line 100
def initialize(name, provider_name, provider_cls, provider_config, provider_options, config, data_dir, box, env, vagrantfile, base=false)
  @logger = Log4r::Logger.new("vagrant::machine")
  @logger.info("Initializing machine: #{name}")
  @logger.info("  - Provider: #{provider_cls}")
  @logger.info("  - Box: #{box}")
  @logger.info("  - Data dir: #{data_dir}")

  @box             = box
  @config          = config
  @data_dir        = data_dir
  @env             = env
  @vagrantfile     = vagrantfile
  @guest           = Guest.new(
    self,
    Vagrant.plugin("2").manager.guests,
    Vagrant.plugin("2").manager.guest_capabilities)
  @name            = name
  @provider_config = provider_config
  @provider_name   = provider_name
  @provider_options = provider_options
  @ui              = Vagrant::UI::Prefixed.new(@env.ui, @name)
  @ui_mutex        = Mutex.new
  @state_mutex     = Mutex.new
  @triggers        = Vagrant::Plugin::V2::Trigger.new(@env, @config.trigger, self, @ui)

  # Read the ID, which is usually in local storage
  @id = nil

  # XXX: This is temporary. This will be removed very soon.
  if base
    @id = name

    # For base setups, we don't want to insert the key
    @config.ssh.insert_key = false
  else
    reload
  end

  # Keep track of where our UUID should be placed
  @index_uuid_file = nil
  @index_uuid_file = @data_dir.join("index_uuid") if @data_dir

  # Initializes the provider last so that it has access to all the
  # state we setup on this machine.
  @provider = provider_cls.new(self)
  @provider._initialize(@provider_name, self)

  # If we're using WinRM, we eager load the plugin because of
  # GH-3390
  if @config.vm.communicator == :winrm
    @logger.debug("Eager loading WinRM communicator to avoid GH-3390")
    communicate
  end

  # If the ID is the special not created ID, then set our ID to
  # nil so that we destroy all our data.
  if state.id == MachineState::NOT_CREATED_ID
    self.id = nil
  end

  # Output a bunch of information about this machine in
  # machine-readable format in case someone is listening.
  @ui.machine("metadata", "provider", provider_name)
end

Public Instance Methods

action(name, opts=nil) click to toggle source

This calls an action on the provider. The provider may or may not actually implement the action.

@param [Symbol] name Name of the action to run. @param [Hash] extra_env This data will be passed into the action runner

as extra data set on the environment hash for the middleware
runner.
# File lib/vagrant/machine.rb, line 172
def action(name, opts=nil)
  @logger.info("Calling action: #{name} on provider #{@provider}")

  opts ||= {}

  # Determine whether we lock or not
  lock = true
  lock = opts.delete(:lock) if opts.key?(:lock)

  # Extra env keys are the remaining opts
  extra_env = opts.dup
  # An environment is required for triggers to function properly. This is
  # passed in specifically for the `#Action::Warden` class triggers. We call it
  # `:trigger_env` instead of `env` in case it collides with an existing environment
  extra_env[:trigger_env] = @env

  check_cwd # Warns the UI if the machine was last used on a different dir

  # Create a deterministic ID for this machine
  vf = nil
  vf = @env.vagrantfile_name[0] if @env.vagrantfile_name
  id = Digest::MD5.hexdigest(
    "#{@env.root_path}#{vf}#{@env.local_data_path}#{@name}")

  # We only lock if we're not executing an SSH action. In the future
  # we will want to do more fine-grained unlocking in actions themselves
  # but for a 1.6.2 release this will work.
  locker = Proc.new { |*args, &block| block.call }
  locker = @env.method(:lock) if lock && !name.to_s.start_with?("ssh")

  # Lock this machine for the duration of this action
  return_env = locker.call("machine-action-#{id}") do
    # Get the callable from the provider.
    callable = @provider.action(name)

    # If this action doesn't exist on the provider, then an exception
    # must be raised.
    if callable.nil?
      raise Errors::UnimplementedProviderAction,
        action: name,
        provider: @provider.to_s
    end

    # Call the action
    ui.machine("action", name.to_s, "start")
    action_result = action_raw(name, callable, extra_env)
    ui.machine("action", name.to_s, "end")
    action_result
  end
  # preserve returning environment after machine action runs
  return return_env
rescue Errors::EnvironmentLockedError
  raise Errors::MachineActionLockedError,
    action: name,
    name: @name
end
action_raw(name, callable, extra_env={}) click to toggle source

This calls a raw callable in the proper context of the machine using the middleware stack.

@param [Symbol] name Name of the action @param [Proc] callable @param [Hash] extra_env Extra env for the action env. @return [Hash] The resulting env

# File lib/vagrant/machine.rb, line 236
def action_raw(name, callable, extra_env={})
  if !extra_env.is_a?(Hash)
    extra_env = {}
  end

  # Run the action with the action runner on the environment
  env = {ui: @ui}.merge(extra_env).merge(
    raw_action_name: name,
    action_name: "machine_action_#{name}".to_sym,
    machine: self,
    machine_action: name
  )
  @env.action_runner.run(callable, env)
end
communicate() click to toggle source

Returns a communication object for executing commands on the remote machine. Note that the exact semantics of this are up to the communication provider itself. Despite this, the semantics are expected to be consistent across operating systems. For example, all linux-based systems should have similar communication (usually a shell). All Windows systems should have similar communication as well. Therefore, prior to communicating with the machine, users of this method are expected to check the guest OS to determine their behavior.

This method will always return some valid communication object. The ‘ready?` API can be used on the object to check if communication is actually ready.

@return [Object]

# File lib/vagrant/machine.rb, line 265
def communicate
  if !@communicator
    requested  = @config.vm.communicator
    requested ||= :ssh
    klass = Vagrant.plugin("2").manager.communicators[requested]
    raise Errors::CommunicatorNotFound, comm: requested.to_s if !klass
    @communicator = klass.new(self)
  end

  @communicator
end
guest() click to toggle source

Returns a guest implementation for this machine. The guest implementation knows how to do guest-OS specific tasks, such as configuring networks, mounting folders, etc.

@return [Guest]

# File lib/vagrant/machine.rb, line 282
def guest
  raise Errors::MachineGuestNotReady if !communicate.ready?
  @guest.detect! if !@guest.ready?
  @guest
end
id=(value) click to toggle source

This sets the unique ID associated with this machine. This will persist this ID so that in the future Vagrant will be able to find this machine again. The unique ID must be absolutely unique to the virtual machine, and can be used by providers for finding the actual machine associated with this instance.

WARNING: Only providers should ever use this method.

@param [String] value The ID.

# File lib/vagrant/machine.rb, line 297
def id=(value)
  @logger.info("New machine ID: #{value.inspect}")

  id_file = nil
  if @data_dir
    # The file that will store the id if we have one. This allows the
    # ID to persist across Vagrant runs. Also, store the UUID for the
    # machine index.
    id_file = @data_dir.join("id")
  end

  if value
    if id_file
      # Write the "id" file with the id given.
      id_file.open("w+") do |f|
        f.write(value)
      end
    end

    if uid_file
      # Write the user id that created this machine
      uid_file.open("w+") do |f|
        f.write(Process.uid.to_s)
      end
    end

    # If we don't have a UUID, then create one
    if index_uuid.nil?
      # Create the index entry and save it
      entry = MachineIndex::Entry.new
      entry.local_data_path = @env.local_data_path
      entry.name = @name.to_s
      entry.provider = @provider_name.to_s
      entry.state = "preparing"
      entry.vagrantfile_path = @env.root_path
      entry.vagrantfile_name = @env.vagrantfile_name

      if @box
        entry.extra_data["box"] = {
          "name"     => @box.name,
          "provider" => @box.provider.to_s,
          "version"  => @box.version.to_s,
        }
      end

      entry = @env.machine_index.set(entry)
      @env.machine_index.release(entry)

      # Store our UUID so we can access it later
      if @index_uuid_file
        @index_uuid_file.open("w+") do |f|
          f.write(entry.id)
        end
      end
    end
  else
    # Delete the file, since the machine is now destroyed
    id_file.delete if id_file && id_file.file?
    uid_file.delete if uid_file && uid_file.file?

    # If we have a UUID associated with the index, remove it
    uuid = index_uuid
    if uuid
      entry = @env.machine_index.get(uuid)
      @env.machine_index.delete(entry) if entry
    end

    if @data_dir
      # Delete the entire data directory contents since all state
      # associated with the VM is now gone.
      @data_dir.children.each do |child|
        begin
          child.rmtree
        rescue Errno::EACCES
          @logger.info("EACCESS deleting file: #{child}")
        end
      end
    end
  end

  # Store the ID locally
  @id = value.nil? ? nil : value.to_s

  # Notify the provider that the ID changed in case it needs to do
  # any accounting from it.
  @provider.machine_id_changed
end
index_uuid() click to toggle source

Returns the UUID associated with this machine in the machine index. We only have a UUID if an ID has been set.

@return [String] UUID or nil if we don’t have one yet.

# File lib/vagrant/machine.rb, line 389
def index_uuid
  return nil if !@index_uuid_file
  return @index_uuid_file.read.chomp if @index_uuid_file.file?
  return nil
end
inspect() click to toggle source

This returns a clean inspect value so that printing the value via a pretty print (‘p`) results in a readable value.

@return [String]

# File lib/vagrant/machine.rb, line 399
def inspect
  "#<#{self.class}: #{@name} (#{@provider.class})>"
end
recover_machine(state) click to toggle source

Returns the state of this machine. The state is queried from the backing provider, so it can be any arbitrary symbol.

@param [Symbol] state of machine @return [Entry] entry of recovered machine

# File lib/vagrant/machine.rb, line 572
def recover_machine(state)
  entry = @env.machine_index.get(index_uuid)
  if entry
    @env.machine_index.release(entry)
    return entry
  end

  entry = MachineIndex::Entry.new(id=index_uuid, {})
  entry.local_data_path = @env.local_data_path
  entry.name = @name.to_s
  entry.provider = @provider_name.to_s
  entry.state = state
  entry.vagrantfile_path = @env.root_path
  entry.vagrantfile_name = @env.vagrantfile_name

  if @box
    entry.extra_data["box"] = {
      "name"     => @box.name,
      "provider" => @box.provider.to_s,
      "version"  => @box.version.to_s,
    }
  end

  @state_mutex.synchronize do
    entry = @env.machine_index.recover(entry)
    @env.machine_index.release(entry)
  end
  return entry
end
reload() click to toggle source

This reloads the ID of the underlying machine.

# File lib/vagrant/machine.rb, line 404
def reload
  old_id = @id
  @id = nil

  if @data_dir
    # Read the id file from the data directory if it exists as the
    # ID for the pre-existing physical representation of this machine.
    id_file = @data_dir.join("id")
    id_content = id_file.read.strip if id_file.file?
    if !id_content.to_s.empty?
      @id = id_content
    end
  end

  if @id != old_id && @provider
    # It changed, notify the provider
    @provider.machine_id_changed
  end

  @id
end
ssh_info() click to toggle source

This returns the SSH info for accessing this machine. This SSH info is queried from the underlying provider. This method returns ‘nil` if the machine is not ready for SSH communication.

The structure of the resulting hash is guaranteed to contain the following structure, although it may return other keys as well not documented here:

{
  host: "1.2.3.4",
  port: "22",
  username: "mitchellh",
  private_key_path: "/path/to/my/key"
}

Note that Vagrant makes no guarantee that this info works or is correct. This is simply the data that the provider gives us or that is configured via a Vagrantfile. It is still possible after this point when attempting to connect via SSH to get authentication errors.

@return [Hash] SSH information.

# File lib/vagrant/machine.rb, line 448
def ssh_info
  # First, ask the provider for their information. If the provider
  # returns nil, then the machine is simply not ready for SSH, and
  # we return nil as well.
  info = @provider.ssh_info
  return nil if info.nil?

  # Delete out the nil entries.
  info.dup.each do |key, value|
    info.delete(key) if value.nil?
  end

  # We set the defaults
  info[:host] ||= @config.ssh.default.host
  info[:port] ||= @config.ssh.default.port
  info[:private_key_path] ||= @config.ssh.default.private_key_path
  info[:keys_only] ||= @config.ssh.default.keys_only
  info[:verify_host_key] ||= @config.ssh.default.verify_host_key
  info[:username] ||= @config.ssh.default.username
  info[:remote_user] ||= @config.ssh.default.remote_user
  info[:compression] ||= @config.ssh.default.compression
  info[:dsa_authentication] ||= @config.ssh.default.dsa_authentication
  info[:extra_args] ||= @config.ssh.default.extra_args
  info[:config] ||= @config.ssh.default.config

  # We set overrides if they are set. These take precedence over
  # provider-returned data.
  info[:host] = @config.ssh.host if @config.ssh.host
  info[:port] = @config.ssh.port if @config.ssh.port
  info[:keys_only] = @config.ssh.keys_only
  info[:verify_host_key] = @config.ssh.verify_host_key
  info[:compression] = @config.ssh.compression
  info[:dsa_authentication] = @config.ssh.dsa_authentication
  info[:username] = @config.ssh.username if @config.ssh.username
  info[:password] = @config.ssh.password if @config.ssh.password
  info[:remote_user] = @config.ssh.remote_user if @config.ssh.remote_user
  info[:extra_args] = @config.ssh.extra_args if @config.ssh.extra_args
  info[:config] = @config.ssh.config if @config.ssh.config

  # We also set some fields that are purely controlled by Vagrant
  info[:forward_agent] = @config.ssh.forward_agent
  info[:forward_x11] = @config.ssh.forward_x11
  info[:forward_env] = @config.ssh.forward_env
  info[:connect_timeout] = @config.ssh.connect_timeout

  info[:ssh_command] = @config.ssh.ssh_command if @config.ssh.ssh_command

  # Add in provided proxy command config
  info[:proxy_command] = @config.ssh.proxy_command if @config.ssh.proxy_command

  # Set the private key path. If a specific private key is given in
  # the Vagrantfile we set that. Otherwise, we use the default (insecure)
  # private key, but only if the provider didn't give us one.
  if !info[:private_key_path] && !info[:password]
    if @config.ssh.private_key_path
      info[:private_key_path] = @config.ssh.private_key_path
    else
      info[:private_key_path] = @env.default_private_key_path
    end
  end

  # If we have a private key in our data dir, then use that
  if @data_dir && !@config.ssh.private_key_path
    data_private_key = @data_dir.join("private_key")
    if data_private_key.file?
      info[:private_key_path] = [data_private_key.to_s]
    end
  end

  # Setup the keys
  info[:private_key_path] ||= []
  info[:private_key_path] = Array(info[:private_key_path])

  # Expand the private key path relative to the root path
  info[:private_key_path].map! do |path|
    File.expand_path(path, @env.root_path)
  end

  # Check that the private key permissions are valid
  info[:private_key_path].each do |path|
    key_path = Pathname.new(path)
    if key_path.exist?
      Vagrant::Util::SSH.check_key_permissions(key_path)
    end
  end

  # Return the final compiled SSH info data
  info
end
state() click to toggle source

Returns the state of this machine. The state is queried from the backing provider, so it can be any arbitrary symbol.

@return [MachineState]

# File lib/vagrant/machine.rb, line 542
def state
  result = @provider.state
  raise Errors::MachineStateInvalid if !result.is_a?(MachineState)

  # Update our state cache if we have a UUID and an entry in the
  # master index.
  uuid = index_uuid
  if uuid
    # active_machines provides access to query this info on each machine
    # from a different thread, ensure multiple machines do not access
    # the locked entry simultaneously as this triggers a locked machine
    # exception.
    @state_mutex.synchronize do
      entry = @env.machine_index.get(uuid)
      if entry
        entry.state = result.short_description
        @env.machine_index.set(entry)
        @env.machine_index.release(entry)
      end
    end
  end

  result
end
synced_folders() click to toggle source

This returns the set of shared folders that should be done for this machine. It returns the folders in a hash keyed by the implementation class for the synced folders.

@return [Hash<Symbol, Hash<String, Hash>>]

# File lib/vagrant/machine.rb, line 632
def synced_folders
  self.class.synced_folders(self)
end
uid() click to toggle source

Returns the user ID that created this machine. This is specific to the host machine that this was created on.

@return [String]

# File lib/vagrant/machine.rb, line 606
def uid
  path = uid_file
  return nil if !path
  return nil if !path.file?
  return uid_file.read.chomp
end
with_ui(ui) { || ... } click to toggle source

Temporarily changes the machine UI. This is useful if you want to execute an {#action} with a different UI.

# File lib/vagrant/machine.rb, line 615
def with_ui(ui)
  @ui_mutex.synchronize do
    begin
      old_ui = @ui
      @ui    = ui
      yield
    ensure
      @ui = old_ui
    end
  end
end

Protected Instance Methods

check_cwd() click to toggle source

Checks the current directory for a given machine and displays a warning if that machine has moved from its previous location on disk. If the machine has moved, it prints a warning to the user.

# File lib/vagrant/machine.rb, line 648
def check_cwd
  desired_encoding = @env.root_path.to_s.encoding
  vagrant_cwd_filepath = @data_dir.join('vagrant_cwd')
  vagrant_cwd = if File.exist?(vagrant_cwd_filepath)
                  File.read(vagrant_cwd_filepath,
                    external_encoding: desired_encoding
                  ).chomp
                end

  if !File.identical?(vagrant_cwd.to_s, @env.root_path.to_s)
    if vagrant_cwd
      ui.warn(I18n.t(
        'vagrant.moved_cwd',
        old_wd:     "#{vagrant_cwd}",
        current_wd: "#{@env.root_path.to_s}"))
    end
    File.write(vagrant_cwd_filepath, @env.root_path.to_s,
      external_encoding: desired_encoding
    )
  end
end
uid_file() click to toggle source

Returns the path to the file that stores the UID.

# File lib/vagrant/machine.rb, line 639
def uid_file
  return nil if !@data_dir
  @data_dir.join("creator_uid")
end