From f2b91d26fdcdfe2c107ca6c1db121ee636717c89 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 14 Nov 2012 16:07:46 -0800 Subject: [PATCH] Load the configuration per machine, so that provider boxes work Boxes are provider-specific, and we don't know the provider until Environment#machine is called, so we need to build up the machine configuration during this time. --- lib/vagrant/environment.rb | 197 ++++++++------------------ test/unit/vagrant/environment_test.rb | 150 ++++++++++---------- 2 files changed, 141 insertions(+), 206 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 8aa6bc785..a8d5f60e8 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -16,6 +16,12 @@ module Vagrant DEFAULT_HOME = "~/.vagrant.d" DEFAULT_RC = "~/.vagrantrc" + # 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. + attr_reader :config_global + # The `cwd` that this environment represents attr_reader :cwd @@ -99,6 +105,9 @@ module Vagrant # Load the plugins load_plugins + + # Initialize the configuration. This will load our global configuration. + initialize_config end # Return a human-friendly string for pretty printed or inspected @@ -119,7 +128,7 @@ module Vagrant # @return [Pathname] def dotfile_path return nil if !root_path - root_path.join(config.global.vagrant.dotfile_name) + root_path.join(config_global.vagrant.dotfile_name) end # Returns the collection of boxes for the environment. @@ -144,8 +153,8 @@ module Vagrant @machines ||= {} return @machines[cache_key] if @machines.has_key?(cache_key) - vm_config = config.for_vm(name) - if !vm_config + sub_vm = config_global.vm.defined_vms[name] + if !sub_vm raise Errors::MachineNotFound, :name => name, :provider => provider end @@ -154,11 +163,36 @@ module Vagrant raise Errors::ProviderNotFound, :machine => name, :provider => provider end - box = boxes.find(vm_config.vm.box, provider) + # 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_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 + + box_vagrantfile = find_vagrantfile(box.directory) + if box_vagrantfile + # The box has a custom Vagrantfile, so we load that into the config + # as well. + box_config_key = "box_#{box.name}_#{box.provider}".to_sym + @config_loader.set(box_config_key, box_vagrantfile) + config = @config_loader.load([:default, box_config_key, :home, :root, vm_config_key]) + 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_cls, vm_config, box, self) + @machines[cache_key] = Machine.new(name, provider_cls, config, box, self) end # This returns a list of the configured machines for this environment. @@ -167,7 +201,7 @@ module Vagrant # # @return [Array] Configured machine names. def machine_names - config.vms + config_global.vm.defined_vm_keys.dup end # Returns the primary VM associated with this environment. This @@ -180,7 +214,7 @@ module Vagrant return machine(machine_names[0], :virtualbox) end - config.global.vm.defined_vms.each do |name, subvm| + config_global.vm.defined_vms.each do |name, subvm| return machine(name, :virtualbox) if subvm.options[:primary] end @@ -206,7 +240,7 @@ module Vagrant # 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 = config_global.vagrant.host if host_klass.nil? || host_klass == :detect hosts = Vagrant.plugin("2").manager.hosts @@ -228,7 +262,7 @@ module Vagrant { :action_runner => action_runner, :box_collection => boxes, - :global_config => config.global, + :global_config => config_global, :host => host, :root_path => root_path, :tmp_path => tmp_path, @@ -314,138 +348,33 @@ module Vagrant end end - #--------------------------------------------------------------- - # Config Methods - #--------------------------------------------------------------- - - # The configuration object represented by this environment. This - # will trigger the environment to load if it hasn't loaded yet (see - # {#load!}). - # - # @return [Config::Container] - def config - load! if !loaded? - @config - end - #--------------------------------------------------------------- # Load Methods #--------------------------------------------------------------- - # Returns a boolean representing if the environment has been - # loaded or not. - # - # @return [Bool] - def loaded? - !!@loaded - end + # This initializes the config loader for this environment. The config + # loader is cached so that prior Vagrantfiles aren't loaded multiple + # times. + def initialize_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 - # Loads this entire environment, setting up the instance variables - # 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 - @logger.info("Loading configuration...") - load_config! - end + # 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 - self - end + # Make the initial call to get the "global" config. This is mostly + # only useful to get the list of machines that we are managing. + @config_global = @config_loader.load([:default, :home, :root]) - # Reloads the configuration of this environment. - def reload! - # Reload the configuration - load_config! - - # 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! - # Initialize the config loader - config_loader = Config::Loader.new(Config::VERSIONS, Config::VERSIONS_ORDER) - config_loader.load_order = [:default, :box, :home, :root, :vm] - - 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] - - # 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 = find_vagrantfile(box.directory) - config_loader.set(:box, box_vagrantfile) if box_vagrantfile - end - - if home_path - # Load the home Vagrantfile - 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 = 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.config_procs) - end - - # Execute the configuration stack and store the result as the final - # value in the config ivar. - config_loader.load - end - - # 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 - - vm_configs = defined_vm_keys.map do |vm_name| - @logger.debug("Loading configuration for VM: #{vm_name}") - - subvm = defined_vms[vm_name] - - # First pass, first run. - config = inner_load[subvm] - - # Second pass, with the box - box = nil - - begin - box = boxes.find(config.vm.box, :virtualbox) if config.vm.box - 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 - - inner_load[subvm, box] - end - - # Finally, we have our configuration. Set it and forget it. - @config = Config::Container.new(global, vm_configs) + # Old order: default, box, home, root, vm end # This sets the `@home_path` variable properly. diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index 2664a6486..a9b71b16c 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -117,6 +117,8 @@ Vagrant.configure("2") do |config| config.vm.define :bar, :primary => true end VF + + env.box2("base", :virtualbox) end env = environment.create_vagrant_env @@ -135,7 +137,7 @@ VF end env = environment.create_vagrant_env - env.config.global.vagrant.dotfile_name.should == "foo" + env.config_global.vagrant.dotfile_name.should == "foo" end it "should load from a custom Vagrantfile" do @@ -148,79 +150,9 @@ VF end env = environment.create_vagrant_env(:vagrantfile_name => "non_standard_name") - env.config.global.vagrant.dotfile_name.should == "custom" + env.config_global.vagrant.dotfile_name.should == "custom" end - it "should load VM configuration" do - environment = isolated_environment do |env| - env.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vagrant.dotfile_name = "foo" -end -VF - end - - env = environment.create_vagrant_env - env.config.for_vm(:default).vm.name.should == :default - end - - it "should load VM configuration with multiple VMs" do - environment = isolated_environment do |env| - env.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vm.define :foo do |vm| - vm.ssh.port = 100 - end - - config.vm.define :bar do |vm| - vm.ssh.port = 200 - end -end -VF - end - - env = environment.create_vagrant_env - env.config.for_vm(:foo).ssh.port.should == 100 - env.config.for_vm(:bar).ssh.port.should == 200 - end - - it "should load a V1 vagrant box" do - environment = isolated_environment do |env| - env.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vm.box = "base" -end -VF - - env.box("base", <<-VF) -Vagrant.configure("2") do |config| - config.ssh.port = 100 -end -VF - end - - env = environment.create_vagrant_env - env.config.for_vm(:default).ssh.port.should == 100 - end - - it "should load box configuration" do - environment = isolated_environment do |env| - env.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vm.box = "base" -end -VF - - env.box2("base", :virtualbox, :vagrantfile => <<-VF) -Vagrant.configure("2") do |config| - config.ssh.port = 100 -end -VF - end - - env = environment.create_vagrant_env - env.config.for_vm(:default).ssh.port.should == 100 - end end describe "ui" do @@ -261,6 +193,8 @@ Vagrant.configure("2") do |config| config.vm.define "foo" end VF + + e.box2("base", :foo) end # Verify that we can get the machine @@ -285,6 +219,9 @@ Vagrant.configure("2") do |config| config.vm.define "vm2" end VF + + e.box2("base", :foo) + e.box2("base", :bar) end env = isolated_env.create_vagrant_env @@ -298,6 +235,75 @@ VF vm2_foo.should eql(env.machine(:vm2, :foo)) end + it "should load the machine configuration" do + register_provider("foo") + + environment = isolated_environment do |env| + env.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.ssh.port = 1 + config.vagrant.dotfile_name = "foo" + config.vm.box = "base" + + config.vm.define "vm1" do |inner| + inner.ssh.port = 100 + end +end +VF + + env.box2("base", :foo) + end + + env = environment.create_vagrant_env + machine = env.machine(:vm1, :foo) + machine.config.ssh.port.should == 100 + machine.config.vagrant.dotfile_name.should == "foo" + end + + it "should load a machine configured with a V1 box" do + register_provider("foo") + + environment = isolated_environment do |env| + env.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "base" +end +VF + + env.box("base", <<-VF) +Vagrant.configure("2") do |config| + config.ssh.port = 100 +end +VF + end + + env = environment.create_vagrant_env + machine = env.machine(:default, :virtualbox) + machine.config.ssh.port.should == 100 + end + + it "should load the box configuration for a V2 box" do + register_provider("foo") + + environment = isolated_environment do |env| + env.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "base" +end +VF + + env.box2("base", :foo, :vagrantfile => <<-VF) +Vagrant.configure("2") do |config| + config.ssh.port = 100 +end +VF + end + + env = environment.create_vagrant_env + machine = env.machine(:default, :foo) + machine.config.ssh.port.should == 100 + end + it "should raise an error if the VM is not found" do expect { instance.machine("i-definitely-dont-exist", :virtualbox) }. to raise_error(Vagrant::Errors::MachineNotFound)