Class: Vagrant::Machine

Inherits:
Object
  • Object
show all
Extended by:
Action::Builtin::MixinSyncedFolders
Defined in:
lib/vagrant/machine.rb

Overview

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.).

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Action::Builtin::MixinSyncedFolders

default_synced_folder_type, impl_opts, plugins, save_synced_folders, synced_folders_diff

Methods included from Util::ScopedHashOverride

#scoped_hash_override

Constructor Details

#initialize(name, provider_name, provider_cls, provider_config, provider_options, config, data_dir, box, env, vagrantfile, base = false) ⇒ Machine

Initialize a new machine.

Parameters:

  • name (String)

    Name of the virtual machine.

  • provider (Class)

    The provider backing this machine. This is currently expected to be a V1 provider plugin.

  • provider_config (Object)

    The provider-specific configuration for this machine.

  • provider_options (Hash)

    The provider-specific options from the plugin definition.

  • config (Object)

    The configuration for this machine.

  • data_dir (Pathname)

    The directory where machine-specific data can be stored. This directory is ensured to exist.

  • box (Box)

    The box that is backing this virtual machine.

  • env (Environment)

    The environment that this machine is a part of.


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/vagrant/machine.rb', line 98

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

Instance Attribute Details

#boxBox

The box that is backing this machine.

Returns:


19
20
21
# File 'lib/vagrant/machine.rb', line 19

def box
  @box
end

#configObject

Configuration for the machine.

Returns:

  • (Object)

24
25
26
# File 'lib/vagrant/machine.rb', line 24

def config
  @config
end

#data_dirPathname (readonly)

Directory where machine-specific data can be stored.

Returns:

  • (Pathname)

29
30
31
# File 'lib/vagrant/machine.rb', line 29

def data_dir
  @data_dir
end

#envEnvironment (readonly)

The environment that this machine is a part of.

Returns:


34
35
36
# File 'lib/vagrant/machine.rb', line 34

def env
  @env
end

#idString

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.

Returns:

  • (String)

41
42
43
# File 'lib/vagrant/machine.rb', line 41

def id
  @id
end

#nameSymbol (readonly)

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

Returns:

  • (Symbol)

46
47
48
# File 'lib/vagrant/machine.rb', line 46

def name
  @name
end

#providerObject (readonly)

The provider backing this machine.

Returns:

  • (Object)

51
52
53
# File 'lib/vagrant/machine.rb', line 51

def provider
  @provider
end

#provider_configObject

The provider-specific configuration for this machine.

Returns:

  • (Object)

56
57
58
# File 'lib/vagrant/machine.rb', line 56

def provider_config
  @provider_config
end

#provider_nameSymbol (readonly)

The name of the provider.

Returns:

  • (Symbol)

61
62
63
# File 'lib/vagrant/machine.rb', line 61

def provider_name
  @provider_name
end

#provider_optionsHash (readonly)

The options given to the provider when registering the plugin.

Returns:

  • (Hash)

66
67
68
# File 'lib/vagrant/machine.rb', line 66

def provider_options
  @provider_options
end

#triggersVagrant::Plugin::V2::Trigger (readonly)

The triggers with machine specific configuration applied


71
72
73
# File 'lib/vagrant/machine.rb', line 71

def triggers
  @triggers
end

#uiUI (readonly)

The UI for outputting in the scope of this machine.

Returns:


76
77
78
# File 'lib/vagrant/machine.rb', line 76

def ui
  @ui
end

#vagrantfileVagrantfile (readonly)

The Vagrantfile that this machine is attached to.

Returns:


81
82
83
# File 'lib/vagrant/machine.rb', line 81

def vagrantfile
  @vagrantfile
end

Instance Method Details

#action(name, opts = nil) ⇒ Object

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

Parameters:

  • name (Symbol)

    Name of the action to run.

  • extra_env (Hash)

    This data will be passed into the action runner as extra data set on the environment hash for the middleware runner.


170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/vagrant/machine.rb', line 170

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 = {}) ⇒ Hash

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

Parameters:

  • name (Symbol)

    Name of the action

  • callable (Proc)
  • extra_env (Hash) (defaults to: {})

    Extra env for the action env.

Returns:

  • (Hash)

    The resulting env


234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/vagrant/machine.rb', line 234

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

#communicateObject

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.

Returns:

  • (Object)

263
264
265
266
267
268
269
270
271
272
273
# File 'lib/vagrant/machine.rb', line 263

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

#guestGuest

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.

Returns:

Raises:


280
281
282
283
284
# File 'lib/vagrant/machine.rb', line 280

def guest
  raise Errors::MachineGuestNotReady if !communicate.ready?
  @guest.detect! if !@guest.ready?
  @guest
end

#index_uuidString

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

Returns:

  • (String)

    UUID or nil if we don't have one yet.


387
388
389
390
391
# File 'lib/vagrant/machine.rb', line 387

def index_uuid
  return nil if !@index_uuid_file
  return @index_uuid_file.read.chomp if @index_uuid_file.file?
  return nil
end

#inspectString

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

Returns:

  • (String)

397
398
399
# File 'lib/vagrant/machine.rb', line 397

def inspect
  "#<#{self.class}: #{@name} (#{@provider.class})>"
end

#recover_machine(state) ⇒ Entry

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

Parameters:

  • state (Symbol)

    of machine

Returns:

  • (Entry)

    entry of recovered machine


570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
# File 'lib/vagrant/machine.rb', line 570

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

#reloadObject

This reloads the ID of the underlying machine.


402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/vagrant/machine.rb', line 402

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_infoHash

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.

Returns:

  • (Hash)

    SSH information.


446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
# File 'lib/vagrant/machine.rb', line 446

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
    elsif info[:keys_only]
      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

#stateMachineState

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


540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
# File 'lib/vagrant/machine.rb', line 540

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_foldersHash<Symbol, Hash<String, Hash>>

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.

Returns:

  • (Hash<Symbol, Hash<String, Hash>>)

630
631
632
# File 'lib/vagrant/machine.rb', line 630

def synced_folders
  self.class.synced_folders(self)
end

#uidString

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

Returns:

  • (String)

604
605
606
607
608
609
# File 'lib/vagrant/machine.rb', line 604

def uid
  path = uid_file
  return nil if !path
  return nil if !path.file?
  return uid_file.read.chomp
end

#with_ui(ui) ⇒ Object

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


613
614
615
616
617
618
619
620
621
622
623
# File 'lib/vagrant/machine.rb', line 613

def with_ui(ui)
  @ui_mutex.synchronize do
    begin
      old_ui = @ui
      @ui    = ui
      yield
    ensure
      @ui = old_ui
    end
  end
end