lib/vagrant/environment.rb in vagrant-0.8.10 vs lib/vagrant/environment.rb in vagrant-0.9.0
- old
+ new
@@ -1,70 +1,56 @@
require 'pathname'
require 'fileutils'
-require 'logger'
+require 'log4r'
+
+require 'vagrant/util/file_mode'
+require 'vagrant/util/platform'
+
module Vagrant
# 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.
class Environment
- HOME_SUBDIRS = ["tmp", "boxes", "logs"]
+ HOME_SUBDIRS = ["tmp", "boxes"]
DEFAULT_VM = :default
DEFAULT_HOME = "~/.vagrant.d"
- # Parent environment (in the case of multi-VMs)
- attr_reader :parent
-
# The `cwd` that this environment represents
attr_reader :cwd
# The valid name for a Vagrantfile for this environment.
attr_reader :vagrantfile_name
- # The single VM that this environment represents, in the case of
- # multi-VM.
- attr_accessor :vm
-
# The {UI} object to communicate with the outside world.
- attr_writer :ui
+ attr_reader :ui
- # The {Config} object representing the Vagrantfile loader
- attr_reader :config_loader
+ # The directory to the "home" folder that Vagrant will use to store
+ # global state.
+ attr_reader :home_path
- #---------------------------------------------------------------
- # Class Methods
- #---------------------------------------------------------------
- class << self
- # Verifies that VirtualBox is installed and that the version of
- # VirtualBox installed is high enough.
- def check_virtualbox!
- version = VirtualBox.version
- raise Errors::VirtualBoxNotDetected if version.nil?
- raise Errors::VirtualBoxInvalidVersion, :version => version.to_s if version.to_f < 4.1 || version.to_f >= 4.2
- rescue Errors::VirtualBoxNotDetected
- # On 64-bit Windows, show a special error. This error is a subclass
- # of VirtualBoxNotDetected, so libraries which use Vagrant can just
- # rescue VirtualBoxNotDetected.
- raise Errors::VirtualBoxNotDetected_Win64 if Util::Platform.windows? && Util::Platform.bit64?
+ # The directory where temporary files for Vagrant go.
+ attr_reader :tmp_path
- # Otherwise, reraise the old error
- raise
- end
- end
+ # The directory where boxes are stored.
+ attr_reader :boxes_path
+ # The path to the default private key
+ attr_reader :default_private_key_path
+
# 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).
def initialize(opts=nil)
opts = {
- :parent => nil,
- :vm => nil,
:cwd => nil,
:vagrantfile_name => nil,
- :lock_path => nil
+ :lock_path => nil,
+ :ui_class => nil,
+ :home_path => nil
}.merge(opts || {})
# Set the default working directory to look for the vagrantfile
opts[:cwd] ||= Dir.pwd
opts[:cwd] = Pathname.new(opts[:cwd])
@@ -72,21 +58,34 @@
# Set the default vagrantfile name, which can be either Vagrantfile
# or vagrantfile (capital for backwards compatibility)
opts[:vagrantfile_name] ||= ["Vagrantfile", "vagrantfile"]
opts[:vagrantfile_name] = [opts[:vagrantfile_name]] if !opts[:vagrantfile_name].is_a?(Array)
- opts.each do |key, value|
- instance_variable_set("@#{key}".to_sym, opts[key])
- end
+ # Set instance variables for all the configuration parameters.
+ @cwd = opts[:cwd]
+ @vagrantfile_name = opts[:vagrantfile_name]
+ @lock_path = opts[:lock_path]
+ @home_path = opts[:home_path]
+ ui_class = opts[:ui_class] || UI::Silent
+ @ui = ui_class.new("vagrant")
+
@loaded = false
@lock_acquired = false
- logger.info("environment") { "Environment initialized (#{self})" }
- logger.info("environment") { " - cwd: #{cwd}" }
- logger.info("environment") { " - parent: #{parent}" }
- logger.info("environment") { " - vm: #{vm}" }
+ @logger = Log4r::Logger.new("vagrant::environment")
+ @logger.info("Environment initialized (#{self})")
+ @logger.info(" - cwd: #{cwd}")
+
+ # Setup the home directory
+ setup_home_path
+ @tmp_path = @home_path.join("tmp")
+ @boxes_path = @home_path.join("boxes")
+
+ # Setup the default private key
+ @default_private_key_path = @home_path.join("insecure_private_key")
+ copy_insecure_private_key
end
#---------------------------------------------------------------
# Helpers
#---------------------------------------------------------------
@@ -94,117 +93,45 @@
# The path to the `dotfile`, which contains the persisted UUID of
# the VM if it exists.
#
# @return [Pathname]
def dotfile_path
- root_path.join(config.vagrant.dotfile_name) rescue nil
+ return nil if !root_path
+ root_path.join(config.global.vagrant.dotfile_name)
end
- # The path to the home directory and converted into a Pathname object.
- #
- # @return [Pathname]
- def home_path
- return parent.home_path if parent
- return @_home_path if defined?(@_home_path)
-
- @_home_path ||= Pathname.new(File.expand_path(ENV["VAGRANT_HOME"] || DEFAULT_HOME))
- logger.info("environment") { "Home path: #{@_home_path}" }
-
- # This is the old default that Vagrant used to be put things into
- # up until Vagrant 0.8.0. We keep around an automatic migration
- # script here in case any old users upgrade.
- old_home = File.expand_path("~/.vagrant")
- if File.exists?(old_home) && File.directory?(old_home)
- logger.info("environment") { "Found both an old and new Vagrantfile. Migration initiated." }
-
- # We can't migrate if the home directory already exists
- if File.exists?(@_home_path)
- ui.warn I18n.t("vagrant.general.home_dir_migration_failed",
- :old => old_home,
- :new => @_home_path.to_s)
- else
- # If the new home path doesn't exist, simply transition to it
- ui.info I18n.t("vagrant.general.moving_home_dir", :directory => @_home_path)
- FileUtils.mv(old_home, @_home_path)
- end
- end
-
- # Return the home path
- @_home_path
- end
-
- # The path to the Vagrant tmp directory
- #
- # @return [Pathname]
- def tmp_path
- home_path.join("tmp")
- end
-
- # The path to the Vagrant boxes directory
- #
- # @return [Pathname]
- def boxes_path
- home_path.join("boxes")
- end
-
- # Path to the Vagrant logs directory
- #
- # @return [Pathname]
- def log_path
- home_path.join("logs")
- end
-
- # Returns the name of the resource which this environment represents.
- # The resource is the VM name if there is a VM it represents, otherwise
- # it defaults to "vagrant"
- #
- # @return [String]
- def resource
- result = vm.name rescue nil
- result || "vagrant"
- end
-
# Returns the collection of boxes for the environment.
#
# @return [BoxCollection]
def boxes
- return parent.boxes if parent
- @_boxes ||= BoxCollection.new(self)
+ @_boxes ||= BoxCollection.new(boxes_path, action_runner)
end
- # Returns the box that this environment represents.
- #
- # @return [Box]
- def box
- boxes.find(config.vm.box)
- end
-
# Returns the VMs associated with this environment.
#
# @return [Hash<Symbol,VM>]
def vms
- return parent.vms if parent
load! if !loaded?
@vms ||= load_vms!
end
# Returns the VMs associated with this environment, in the order
# that they were defined.
#
# @return [Array<VM>]
def vms_ordered
- @vms_enum ||= config.vm.defined_vm_keys.map { |name| @vms[name] }
+ return @vms.values if !multivm?
+ @vms_enum ||= config.global.vm.defined_vm_keys.map { |name| @vms[name] }
end
# Returns the primary VM associated with this environment. This
# method is only applicable for multi-VM environments. This can
# potentially be nil if no primary VM is specified.
#
# @return [VM]
def primary_vm
return vms.values.first if !multivm?
- return parent.primary_vm if parent
config.vm.defined_vms.each do |name, subvm|
return vms[name] if subvm.options[:primary]
end
@@ -215,108 +142,93 @@
# environment or not. This will work even when called on child
# environments.
#
# @return [Bool]
def multivm?
- if parent
- parent.multivm?
- else
- vms.length > 1 || vms.keys.first != DEFAULT_VM
- end
+ vms.length > 1 || vms.keys.first != DEFAULT_VM
end
# 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")
#
def cli(*args)
- CLI.start(args.flatten, :env => self)
+ CLI.new(args.flatten, self).execute
end
- # Returns the {UI} for the environment, which is responsible
- # for talking with the outside world.
- #
- # @return [UI]
- def ui
- @ui ||= if parent
- result = parent.ui.clone
- result.env = self
- result
- else
- UI.new(self)
- end
- end
-
# Returns the host object associated with this environment.
#
# @return [Hosts::Base]
def host
- @host ||= Hosts::Base.load(self, config.vagrant.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
+ host_klass = Hosts.detect(Vagrant.hosts) if host_klass.nil? || host_klass == :detect
+ host_klass = Vagrant.hosts.get(host_klass) if host_klass.is_a?(Symbol)
+
+ # If no host class is detected, we use the base class.
+ host_klass ||= Hosts::Base
+
+ @host ||= host_klass.new(@ui)
end
- # Returns the {Action} class for this environment which allows actions
- # to be executed (middleware chains) in the context of this environment.
+ # Action runner for executing actions in the context of this environment.
#
- # @return [Action]
- def actions
- @actions ||= Action.new(self)
+ # @return [Action::Runner]
+ def action_runner
+ @action_runner ||= Action::Runner.new(action_registry) do
+ {
+ :action_runner => action_runner,
+ :box_collection => boxes,
+ :global_config => config.global,
+ :host => host,
+ :root_path => root_path,
+ :tmp_path => tmp_path,
+ :ui => @ui
+ }
+ end
end
+ # Action registry for registering new actions with this environment.
+ #
+ # @return [Registry]
+ def action_registry
+ return @action_registry if defined?(@action_registry)
+
+ # The action registry hasn't been loaded yet, so load it
+ # and setup the built-in actions with it.
+ @action_registry = Registry.new
+ Vagrant::Action.builtin!(@action_registry)
+ @action_registry
+ end
+
# Loads on initial access and reads data from the global data store.
# The global data store is global to Vagrant everywhere (in every environment),
# so it can be used to store system-wide information. Note that "system-wide"
# typically means "for this user" since the location of the global data
# store is in the home directory.
#
# @return [DataStore]
def global_data
- return parent.global_data if parent
@global_data ||= DataStore.new(File.expand_path("global_data.json", home_path))
end
# Loads (on initial access) and reads data from the local data
# store. This file is always at the root path as the file "~/.vagrant"
# and contains a JSON dump of a hash. See {DataStore} for more
# information.
#
# @return [DataStore]
def local_data
- return parent.local_data if parent
@local_data ||= DataStore.new(dotfile_path)
end
- # Accesses the logger for Vagrant. This logger is a _detailed_
- # logger which should be used to log internals only. For outward
- # facing information, use {#ui}.
- #
- # @return [Logger]
- def logger
- return parent.logger if parent
- return @logger if @logger
-
- # Figure out where the output should go to.
- output = nil
- if ENV["VAGRANT_LOG"] == "STDOUT"
- output = STDOUT
- elsif ENV["VAGRANT_LOG"] == "NULL"
- output = nil
- elsif ENV["VAGRANT_LOG"]
- output = ENV["VAGRANT_LOG"]
- else
- output = nil #log_path.join("#{Time.now.to_i}.log")
- end
-
- # Create the logger and custom formatter
- @logger = Logger.new(output)
- @logger.formatter = Proc.new do |severity, datetime, progname, msg|
- "#{datetime} - #{progname} - [#{resource}] #{msg}\n"
- end
-
- @logger
- end
-
# The root path is the path where the top-most (loaded last)
# Vagrantfile resides. It can be considered the project root for
# this environment.
#
# @return [String]
@@ -377,11 +289,11 @@
# The configuration object represented by this environment. This
# will trigger the environment to load if it hasn't loaded yet (see
# {#load!}).
#
- # @return [Config::Top]
+ # @return [Config::Container]
def config
load! if !loaded?
@config
end
@@ -401,116 +313,185 @@
# such as `vm`, `config`, etc. on this environment. The order this
# method calls its other methods is very particular.
def load!
if !loaded?
@loaded = true
-
- if !parent
- # We only need to check the virtualbox version once, so do it on
- # the parent most environment and then forget about it
- logger.info("environment") { "Environment not loaded. Checking virtual box version..." }
- self.class.check_virtualbox!
- end
-
- logger.info("environment") { "Loading configuration..." }
+ @logger.info("Loading configuration...")
load_config!
end
self
end
# Reloads the configuration of this environment.
- def reload_config!
- @config = nil
- @config_loader = nil
+ def reload!
+ # Reload the configuration
load_config!
- self
+
+ # Clear the VMs because this can now be diferent due to configuration
+ @vms = nil
end
# Loads this environment's configuration and stores it in the {#config}
# variable. The configuration loaded by this method is specified to
# this environment, meaning that it will use the given root directory
# to load the Vagrantfile into that context.
def load_config!
- first_run = @config.nil?
+ # Initialize the config loader
+ config_loader = Config::Loader.new
+ config_loader.load_order = [:default, :box, :home, :root, :vm]
- # First load the initial, non config-dependent Vagrantfiles
- @config_loader ||= Config.new(parent ? parent.config_loader : nil)
- @config_loader.load_order = [:default, :box, :home, :root, :sub_vm]
- @config_loader.set(:default, File.expand_path("config/default.rb", Vagrant.source_root))
+ inner_load = lambda do |*args|
+ # This is for Ruby 1.8.7 compatibility. Ruby 1.8.7 doesn't allow
+ # default arguments for lambdas, so we get around by doing a *args
+ # and setting the args here.
+ subvm = args[0]
+ box = args[1]
- vagrantfile_name.each do |rootfile|
- if !first_run && vm && box
+ # Default Vagrantfile first. This is the Vagrantfile that ships
+ # with Vagrant.
+ config_loader.set(:default, File.expand_path("config/default.rb", Vagrant.source_root))
+
+ if box
# We load the box Vagrantfile
- box_vagrantfile = box.directory.join(rootfile)
- @config_loader.set(:box, box_vagrantfile) if box_vagrantfile.exist?
+ box_vagrantfile = find_vagrantfile(box.directory)
+ config_loader.set(:box, box_vagrantfile) if box_vagrantfile
end
- if !first_run && home_path
+ if home_path
# Load the home Vagrantfile
- home_vagrantfile = home_path.join(rootfile)
- @config_loader.set(:home, home_vagrantfile) if home_vagrantfile.exist?
+ home_vagrantfile = find_vagrantfile(home_path)
+ config_loader.set(:home, home_vagrantfile) if home_vagrantfile
end
if root_path
# Load the Vagrantfile in this directory
- root_vagrantfile = root_path.join(rootfile)
- @config_loader.set(:root, root_vagrantfile) if root_vagrantfile.exist?
+ root_vagrantfile = find_vagrantfile(root_path)
+ config_loader.set(:root, root_vagrantfile) if root_vagrantfile
end
+
+ if subvm
+ # We have subvm configuration, so set that up as well.
+ config_loader.set(:vm, subvm.proc_stack)
+ end
+
+ # Execute the configuration stack and store the result as the final
+ # value in the config ivar.
+ config_loader.load
end
- # If this environment is representing a sub-VM, then we push that
- # proc on as the last configuration.
- if vm
- subvm = parent.config.vm.defined_vms[vm.name]
- @config_loader.set(:sub_vm, subvm.proc_stack) if subvm
+ # For the global configuration, we only need to load the configuration
+ # in a single pass, since nothing is conditional on the configuration.
+ global = inner_load.call
+
+ # For each virtual machine represented by this environment, we have
+ # to load the configuration in two-passes. We do this because the
+ # first pass is used to determine the box for the VM. The second pass
+ # is used to also load the box Vagrantfile.
+ defined_vm_keys = global.vm.defined_vm_keys.dup
+ defined_vms = global.vm.defined_vms.dup
+
+ # If this isn't a multi-VM environment, then setup the default VM
+ # to simply be our configuration.
+ if defined_vm_keys.empty?
+ defined_vm_keys << DEFAULT_VM
+ defined_vms[DEFAULT_VM] = Config::VMConfig::SubVM.new
end
- # Execute the configuration stack and store the result as the final
- # value in the config ivar.
- @config = @config_loader.load(self)
+ vm_configs = defined_vm_keys.map do |vm_name|
+ @logger.debug("Loading configuration for VM: #{vm_name}")
- if first_run
- # After the first run we want to load the configuration again since
- # it can change due to box Vagrantfiles and home directory Vagrantfiles
- load_home_directory!
- load_config!
+ subvm = defined_vms[vm_name]
+
+ # First pass, first run.
+ config = inner_load[subvm]
+
+ # Second pass, with the box
+ config = inner_load[subvm, boxes.find(config.vm.box)]
+ config.vm.name = vm_name
+
+ # Return the final configuration for this VM
+ config
end
+
+ # Finally, we have our configuration. Set it and forget it.
+ @config = Config::Container.new(global, vm_configs)
end
- # Loads the home directory path and creates the necessary subdirectories
- # within the home directory if they're not already created.
- def load_home_directory!
+ # Loads the persisted VM (if it exists) for this environment.
+ def load_vms!
+ result = {}
+
+ # Load all the virtual machine instances.
+ config.vms.each do |name|
+ result[name] = Vagrant::VM.new(name, self, config.for_vm(name))
+ end
+
+ result
+ end
+
+ # This sets the `@home_path` variable properly.
+ #
+ # @return [Pathname]
+ 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 array of necessary home directories
- dirs = [home_path]
- dirs += HOME_SUBDIRS.collect { |subdir| home_path.join(subdir) }
+ dirs = [@home_path]
+ dirs += HOME_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)
- ui.info I18n.t("vagrant.general.creating_home_dir", :directory => dir)
- FileUtils.mkdir_p(dir)
+ begin
+ @logger.info("Creating: #{dir}")
+ FileUtils.mkdir_p(dir)
+ rescue Errno::EACCES
+ raise Errors::HomeDirectoryNotAccessible, :home_path => @home_path.to_s
+ end
end
end
- # Loads the persisted VM (if it exists) for this environment.
- def load_vms!
- result = {}
+ protected
- # Load the VM UUIDs from the local data store
- (local_data[:active] || {}).each do |name, uuid|
- result[name.to_sym] = Vagrant::VM.find(uuid, self, name.to_sym)
+ # This method copies the private key into the home directory if it
+ # doesn't already exist.
+ #
+ # This must be done because `ssh` requires that the key is chmod
+ # 0600, but if Vagrant is installed as a separate user, then the
+ # effective uid won't be able to read the key. So the key is copied
+ # to the home directory and chmod 0600.
+ def copy_insecure_private_key
+ if !@default_private_key_path.exist?
+ @logger.info("Copying private key to home directory")
+ FileUtils.cp(File.expand_path("keys/vagrant", Vagrant.source_root),
+ @default_private_key_path)
end
- # For any VMs which aren't created, create a blank VM instance for
- # them
- all_keys = config.vm.defined_vm_keys
- all_keys = [DEFAULT_VM] if all_keys.empty?
- all_keys.each do |name|
- result[name] = Vagrant::VM.new(:name => name, :env => self) if !result.has_key?(name)
+ if !Util::Platform.windows?
+ # On Windows, permissions don't matter as much, so don't worry
+ # about doing chmod.
+ if Util::FileMode.from_octal(@default_private_key_path.stat.mode) != "600"
+ @logger.info("Changing permissions on private key to 0600")
+ @default_private_key_path.chmod(0600)
+ end
end
+ end
- result
+ # Finds the Vagrantfile in the given directory.
+ #
+ # @param [Pathname] path Path to search in.
+ # @return [Pathname]
+ def find_vagrantfile(search_path)
+ @vagrantfile_name.each do |vagrantfile|
+ current_path = search_path.join(vagrantfile)
+ return current_path if current_path.exist?
+ end
+
+ nil
end
end
end