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