Class: Vagrant::Environment

Inherits:
Object
  • Object
show all
Defined in:
lib/vagrant/environment.rb

Overview

Represents a single Vagrant environment. A “Vagrant environment” is defined as basically a folder with a “Vagrantfile.” This class allows access to the VMs, CLI, etc. all in the scope of this environment.

Constant Summary collapse

DEFAULT_HOME =
"~/.vagrant.d"
DEFAULT_LOCAL_DATA =
".vagrant"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = nil) ⇒ Environment

Initializes a new environment with the given options. The options is a hash where the main available key is ‘cwd`, which defines where the environment represents. There are other options available but they shouldn’t be used in general. If ‘cwd` is nil, then it defaults to the `Dir.pwd` (which is the cwd of the executing process).



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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
# File 'lib/vagrant/environment.rb', line 63

def initialize(opts=nil)
  opts = {
    :cwd => nil,
    :home_path => nil,
    :local_data_path => nil,
    :lock_path => nil,
    :ui_class => nil,
    :vagrantfile_name => nil
  }.merge(opts || {})

  # Set the default working directory to look for the vagrantfile
  opts[:cwd] ||= ENV["VAGRANT_CWD"] if ENV.has_key?("VAGRANT_CWD")
  opts[:cwd] ||= Dir.pwd
  opts[:cwd] = Pathname.new(opts[:cwd])
  raise Errors::EnvironmentNonExistentCWD if !opts[:cwd].directory?

  # Set the default ui class
  opts[:ui_class] ||= UI::Silent

  # Set the Vagrantfile name up. We append "Vagrantfile" and "vagrantfile" so that
  # those continue to work as well, but anything custom will take precedence.
  opts[:vagrantfile_name] ||= ENV["VAGRANT_VAGRANTFILE"] if \
    ENV.has_key?("VAGRANT_VAGRANTFILE")
  opts[:vagrantfile_name] ||= ["Vagrantfile", "vagrantfile"]
  opts[:vagrantfile_name] = [opts[:vagrantfile_name]] if \
    !opts[:vagrantfile_name].is_a?(Array)

  # Set instance variables for all the configuration parameters.
  @cwd              = opts[:cwd]
  @home_path        = opts[:home_path]
  @lock_path        = opts[:lock_path]
  @vagrantfile_name = opts[:vagrantfile_name]
  @ui               = opts[:ui_class].new
  @ui_class         = opts[:ui_class]

  @lock_acquired = false

  @logger = Log4r::Logger.new("vagrant::environment")
  @logger.info("Environment initialized (#{self})")
  @logger.info("  - cwd: #{cwd}")

  # Setup the home directory
  setup_home_path
  @boxes_path = @home_path.join("boxes")
  @data_dir   = @home_path.join("data")
  @gems_path  = @home_path.join("gems")
  @tmp_path   = @home_path.join("tmp")

  # Setup the local data directory. If a configuration path is given,
  # then it is expanded relative to the working directory. Otherwise,
  # we use the default which is expanded relative to the root path.
  @local_data_path = nil
  if opts[:local_data_path]
    @local_data_path = Pathname.new(File.expand_path(opts[:local_data_path], @cwd))
  elsif !root_path.nil?
    @local_data_path = root_path.join(DEFAULT_LOCAL_DATA)
  end

  setup_local_data_path

  # Setup the default private key
  @default_private_key_path = @home_path.join("insecure_private_key")
  copy_insecure_private_key

  # Load the plugins
  load_plugins

  # Call the environment load hooks
  hook(:environment_load)
end

Instance Attribute Details

#boxes_pathObject (readonly)

The directory where boxes are stored.



50
51
52
# File 'lib/vagrant/environment.rb', line 50

def boxes_path
  @boxes_path
end

#cwdObject (readonly)

The ‘cwd` that this environment represents



20
21
22
# File 'lib/vagrant/environment.rb', line 20

def cwd
  @cwd
end

#data_dirPathname (readonly)

The persistent data directory where global data can be stored. It is up to the creator of the data in this directory to properly remove it when it is no longer needed.

Returns:

  • (Pathname)


27
28
29
# File 'lib/vagrant/environment.rb', line 27

def data_dir
  @data_dir
end

#default_private_key_pathObject (readonly)

The path to the default private key



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

def default_private_key_path
  @default_private_key_path
end

#gems_pathObject (readonly)

The path where the plugins are stored (gems)



53
54
55
# File 'lib/vagrant/environment.rb', line 53

def gems_path
  @gems_path
end

#home_pathObject (readonly)

The directory to the “home” folder that Vagrant will use to store global state.



40
41
42
# File 'lib/vagrant/environment.rb', line 40

def home_path
  @home_path
end

#local_data_pathObject (readonly)

The directory to the directory where local, environment-specific data is stored.



44
45
46
# File 'lib/vagrant/environment.rb', line 44

def local_data_path
  @local_data_path
end

#tmp_pathObject (readonly)

The directory where temporary files for Vagrant go.



47
48
49
# File 'lib/vagrant/environment.rb', line 47

def tmp_path
  @tmp_path
end

#uiObject (readonly)

The UI object to communicate with the outside world.



33
34
35
# File 'lib/vagrant/environment.rb', line 33

def ui
  @ui
end

#ui_classObject (readonly)

This is the UI class to use when creating new UIs.



36
37
38
# File 'lib/vagrant/environment.rb', line 36

def ui_class
  @ui_class
end

#vagrantfile_nameObject (readonly)

The valid name for a Vagrantfile for this environment.



30
31
32
# File 'lib/vagrant/environment.rb', line 30

def vagrantfile_name
  @vagrantfile_name
end

Instance Method Details

#action_runnerAction::Runner

Action runner for executing actions in the context of this environment.

Returns:



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/vagrant/environment.rb', line 436

def action_runner
  @action_runner ||= Action::Runner.new do
    {
      :action_runner  => action_runner,
      :box_collection => boxes,
      :global_config  => config_global,
      :host           => host,
      :gems_path      => gems_path,
      :home_path      => home_path,
      :root_path      => root_path,
      :tmp_path       => tmp_path,
      :ui             => @ui
    }
  end
end

#active_machinesArray<String, Symbol>

Returns a list of machines that this environment is currently managing that physically have been created.

An “active” machine is a machine that Vagrant manages that has been created. The machine itself may be in any state such as running, suspended, etc. but if a machine is “active” then it exists.

Note that the machines in this array may no longer be present in the Vagrantfile of this environment. In this case the machine can be considered an “orphan.” Determining which machines are orphan and which aren’t is not currently a supported feature, but will be in a future version.

Returns:

  • (Array<String, Symbol>)


160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/vagrant/environment.rb', line 160

def active_machines
  machine_folder = @local_data_path.join("machines")

  # If the machine folder is not a directory then we just return
  # an empty array since no active machines exist.
  return [] if !machine_folder.directory?

  # Traverse the machines folder accumulate a result
  result = []

  machine_folder.children(true).each do |name_folder|
    # If this isn't a directory then it isn't a machine
    next if !name_folder.directory?

    name = name_folder.basename.to_s.to_sym
    name_folder.children(true).each do |provider_folder|
      # If this isn't a directory then it isn't a provider
      next if !provider_folder.directory?

      # If this machine doesn't have an ID, then ignore
      next if !provider_folder.join("id").file?

      provider = provider_folder.basename.to_s.to_sym
      result << [name, provider]
    end
  end

  # Return the results
  result
end

#boxesBoxCollection

Returns the collection of boxes for the environment.

Returns:



204
205
206
# File 'lib/vagrant/environment.rb', line 204

def boxes
  @_boxes ||= BoxCollection.new(boxes_path)
end

#cli(*args) ⇒ Object

Makes a call to the CLI with the given arguments as if they came from the real command line (sometimes they do!). An example:

env.cli("package", "--vagrantfile", "Vagrantfile")


405
406
407
# File 'lib/vagrant/environment.rb', line 405

def cli(*args)
  CLI.new(args.flatten, self).execute
end

#config_globalObject

This is the global config, comprised of loading configuration from the default, home, and root Vagrantfiles. This configuration is only really useful for reading the list of virtual machines, since each individual VM can override most settings.

This is lazy-loaded upon first use.

Returns:

  • (Object)


216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/vagrant/environment.rb', line 216

def config_global
  return @config_global if @config_global

  @logger.info("Initializing config...")

  home_vagrantfile = nil
  root_vagrantfile = nil
  home_vagrantfile = find_vagrantfile(home_path) if home_path
  root_vagrantfile = find_vagrantfile(root_path) if root_path

  # Create the configuration loader and set the sources that are global.
  # We use this to load the configuration, and the list of machines we are
  # managing. Then, the actual individual configuration is loaded for
  # each {#machine} call.
  @config_loader = Config::Loader.new(Config::VERSIONS, Config::VERSIONS_ORDER)
  @config_loader.set(:default, File.expand_path("config/default.rb", Vagrant.source_root))
  @config_loader.set(:home, home_vagrantfile) if home_vagrantfile
  @config_loader.set(:root, root_vagrantfile) if root_vagrantfile

  # Make the initial call to get the "global" config. This is mostly
  # only useful to get the list of machines that we are managing.
  # Because of this, we ignore any warnings or errors.
  @config_global, _ = @config_loader.load([:default, :home, :root])

  # Return the config
  @config_global
end

#default_providerSymbol

This returns the provider name for the default provider for this environment. The provider returned is currently hardcoded to “virtualbox” but one day should be a detected valid, best-case provider for this environment.

Returns:

  • (Symbol)

    Name of the default provider.



197
198
199
# File 'lib/vagrant/environment.rb', line 197

def default_provider
  :virtualbox
end

#hook(name) ⇒ Object

This defines a hook point where plugin action hooks that are registered against the given name will be run in the context of this environment.

Parameters:

  • name (Symbol)

    Name of the hook.



248
249
250
251
252
253
254
255
# File 'lib/vagrant/environment.rb', line 248

def hook(name)
  @logger.info("Running hook: #{name}")
  callable = Action::Builder.new
  action_runner.run(
    callable,
    :action_name => name,
    :env => self)
end

#hostClass

Returns the host object associated with this environment.

Returns:

  • (Class)


412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/vagrant/environment.rb', line 412

def host
  return @host if defined?(@host)

  # Attempt to figure out the host class. Note that the order
  # matters here, so please don't touch. Specifically: The symbol
  # check is done after the detect check because the symbol check
  # will return nil, and we don't want to trigger a detect load.
  host_klass = config_global.vagrant.host
  if host_klass.nil? || host_klass == :detect
    hosts = Vagrant.plugin("2").manager.hosts.to_hash

    # Get the flattened list of available hosts
    host_klass = Hosts.detect(hosts)
  end

  # If no host class is detected, we use the base class.
  host_klass ||= Vagrant.plugin("2", :host)

  @host ||= host_klass.new(@ui)
end

#inspectString

Return a human-friendly string for pretty printed or inspected instances.

Returns:

  • (String)


138
139
140
# File 'lib/vagrant/environment.rb', line 138

def inspect
  "#<#{self.class}: #{@cwd}>"
end

#lockObject

This locks Vagrant for the duration of the block passed to this method. During this time, any other environment which attempts to lock which points to the same lock file will fail.



484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/vagrant/environment.rb', line 484

def lock
  # This allows multiple locks in the same process to be nested
  return yield if @lock_acquired

  File.open(lock_path, "w+") do |f|
    # The file locking fails only if it returns "false." If it
    # succeeds it returns a 0, so we must explicitly check for
    # the proper error case.
    raise Errors::EnvironmentLockedError if f.flock(File::LOCK_EX | File::LOCK_NB) === false

    begin
      # Mark that we have a lock
      @lock_acquired = true

      yield
    ensure
      # We need to make sure that no matter what this is always
      # reset to false so we don't think we have a lock when we
      # actually don't.
      @lock_acquired = false
    end
  end
end

#lock_pathObject

This returns the path which Vagrant uses to determine the location of the file lock. This is specific to each operating system.



477
478
479
# File 'lib/vagrant/environment.rb', line 477

def lock_path
  @lock_path || tmp_path.join("vagrant.lock")
end

#machine(name, provider, refresh = false) ⇒ Machine

This returns a machine with the proper provider for this environment. The machine named by ‘name` must be in this environment.

Parameters:

  • name (Symbol)

    Name of the machine (as configured in the Vagrantfile).

  • provider (Symbol)

    The provider that this machine should be backed by.

  • refresh (Boolean) (defaults to: false)

    If true, then if there is a cached version it is reloaded.

Returns:



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/vagrant/environment.rb', line 267

def machine(name, provider, refresh=false)
  @logger.info("Getting machine: #{name} (#{provider})")

  # Compose the cache key of the name and provider, and return from
  # the cache if we have that.
  cache_key = [name, provider]
  @machines ||= {}
  if refresh
    @logger.info("Refreshing machine (busting cache): #{name} (#{provider})")
    @machines.delete(cache_key)
  end

  if @machines.has_key?(cache_key)
    @logger.info("Returning cached machine: #{name} (#{provider})")
    return @machines[cache_key]
  end

  @logger.info("Uncached load of machine.")
  sub_vm = config_global.vm.defined_vms[name]
  if !sub_vm
    raise Errors::MachineNotFound, :name => name, :provider => provider
  end

  provider_cls = Vagrant.plugin("2").manager.providers[provider]
  if !provider_cls
    raise Errors::ProviderNotFound, :machine => name, :provider => provider
  end

  # Build the machine configuration. This requires two passes: The first pass
  # loads in the machine sub-configuration. Since this can potentially
  # define a new box to base the machine from, we then make a second pass
  # with the box Vagrantfile (if it has one).
  vm_config_key = "vm_#{name}".to_sym
  @config_loader.set(vm_config_key, sub_vm.config_procs)
  config, config_warnings, config_errors = \
    @config_loader.load([:default, :home, :root, vm_config_key])

  box = nil
  begin
    box = boxes.find(config.vm.box, provider)
  rescue Errors::BoxUpgradeRequired
    # Upgrade the box if we must
    @logger.info("Upgrading box during config load: #{config.vm.box}")
    boxes.upgrade(config.vm.box)
    retry
  end

  # If a box was found, then we attempt to load the Vagrantfile for
  # that box. We don't require a box since we allow providers to download
  # boxes and so on.
  if box
    box_vagrantfile = find_vagrantfile(box.directory)
    if box_vagrantfile
      # The box has a custom Vagrantfile, so we load that into the config
      # as well.
      @logger.info("Box exists with Vagrantfile. Reloading machine config.")
      box_config_key = "box_#{box.name}_#{box.provider}".to_sym
      @config_loader.set(box_config_key, box_vagrantfile)
      config, config_warnings, config_errors = \
        @config_loader.load([:default, box_config_key, :home, :root, vm_config_key])
    end
  end

  # Get the provider configuration from the final loaded configuration
  provider_config = config.vm.get_provider_config(provider)

  # Determine the machine data directory and pass it to the machine.
  # XXX: Permissions error here.
  machine_data_path = @local_data_path.join("machines/#{name}/#{provider}")
  FileUtils.mkdir_p(machine_data_path)

  # 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
    @ui.send(level, I18n.t("vagrant.general.config_upgrade_messages",
                          :output => output))

    # If we had errors, then we bail
    raise Errors::ConfigUpgradeErrors if !config_errors.empty?
  end

  # Create the machine and cache it for future calls. This will also
  # return the machine from this method.
  @machines[cache_key] = Machine.new(name, provider, provider_cls, provider_config,
                                     config, machine_data_path, box, self)
end

#machine_namesArray<Symbol>

This returns a list of the configured machines for this environment. Each of the names returned by this method is valid to be used with the #machine method.

Returns:

  • (Array<Symbol>)

    Configured machine names.



365
366
367
# File 'lib/vagrant/environment.rb', line 365

def machine_names
  config_global.vm.defined_vm_keys.dup
end

#primary_machine_nameSymbol

This returns the name of the machine that is 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 can potentially be nil if no primary machine is specified.

Returns:

  • (Symbol)


375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/vagrant/environment.rb', line 375

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_global.vm.defined_vms.each do |name, subvm|
    return name if subvm.options[:primary]
  end

  # If no primary was specified, nil it is
  nil
end

#root_pathString

The root path is the path where the top-most (loaded last) Vagrantfile resides. It can be considered the project root for this environment.

Returns:

  • (String)


457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'lib/vagrant/environment.rb', line 457

def root_path
  return @root_path if defined?(@root_path)

  root_finder = lambda do |path|
    # Note: To remain compatible with Ruby 1.8, we have to use
    # a `find` here instead of an `each`.
    found = vagrantfile_name.find do |rootfile|
      File.exist?(File.join(path.to_s, rootfile))
    end

    return path if found
    return nil if path.root? || !File.exist?(path)
    root_finder.call(path.parent)
  end

  @root_path = root_finder.call(cwd)
end

#setup_home_pathPathname

This sets the ‘@home_path` variable properly.

Returns:

  • (Pathname)


515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
# File 'lib/vagrant/environment.rb', line 515

def setup_home_path
  @home_path = Pathname.new(File.expand_path(@home_path ||
                                             ENV["VAGRANT_HOME"] ||
                                             DEFAULT_HOME))
  @logger.info("Home path: #{@home_path}")

  # Setup the list of child directories that need to be created if they
  # don't already exist.
  dirs    = [@home_path]
  subdirs = ["boxes", "data", "gems", "rgloader", "tmp"]
  dirs    += subdirs.collect { |subdir| @home_path.join(subdir) }

  # Go through each required directory, creating it if it doesn't exist
  dirs.each do |dir|
    next if File.directory?(dir)

    begin
      @logger.info("Creating: #{dir}")
      FileUtils.mkdir_p(dir)
    rescue Errno::EACCES
      raise Errors::HomeDirectoryNotAccessible, :home_path => @home_path.to_s
    end
  end

  # Create the version file to mark the version of the home directory
  # we're using.
  version_file = @home_path.join("setup_version")
  if !version_file.file?
    @logger.debug("Setting up the version file.")
    version_file.open("w") do |f|
      f.write("1.1")
    end
  end

  # Create the rgloader/loader file so we can use encoded files.
  loader_file = @home_path.join("rgloader", "loader.rb")
  if !loader_file.file?
    source_loader = Vagrant.source_root.join("templates/rgloader.rb")
    FileUtils.cp(source_loader.to_s, loader_file.to_s)
  end
end

#setup_local_data_pathObject

This creates the local data directory and show an error if it couldn’t properly be created.



559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
# File 'lib/vagrant/environment.rb', line 559

def setup_local_data_path
  if @local_data_path.nil?
    @logger.warn("No local data path is set. Local data cannot be stored.")
    return
  end

  @logger.info("Local data path: #{@local_data_path}")

  # If the local data path is a file, then we are probably seeing an
  # old (V1) "dotfile." In this case, we upgrade it. The upgrade process
  # will remove the old data file if it is successful.
  if @local_data_path.file?
    upgrade_v1_dotfile(@local_data_path)
  end

  begin
    @logger.debug("Creating: #{@local_data_path}")
    FileUtils.mkdir_p(@local_data_path)
  rescue Errno::EACCES
    raise Errors::LocalDataDirectoryNotAccessible,
      :local_data_path => @local_data_path.to_s
  end
end

#unloadObject

Unload the environment, running completion hooks. The environment should not be used after this (but CAN be, technically). It is recommended to always immediately set the variable to ‘nil` after running this so you can’t accidentally run any more methods. Example:

env.unload
env = nil


396
397
398
# File 'lib/vagrant/environment.rb', line 396

def unload
  hook(:environment_unload)
end