diff --git a/lib/vagrant/action/general/package.rb b/lib/vagrant/action/general/package.rb index 996368d3a..f112e9ede 100644 --- a/lib/vagrant/action/general/package.rb +++ b/lib/vagrant/action/general/package.rb @@ -23,7 +23,8 @@ module Vagrant @app = app env["package.files"] ||= {} - env["package.output"] ||= env[:global_config].package.name + env["package.output"] ||= env[:machine].package.name + env["package.output"] ||= "package.box" end def call(env) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 38ced5377..d4adac195 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -8,11 +8,13 @@ require 'log4r' require 'vagrant/util/file_mode' require 'vagrant/util/platform' +require "vagrant/vagrantfile" 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. + # A "Vagrant environment" represents a configuration of how Vagrant + # should behave: data directories, working directory, UI output, + # etc. In day-to-day usage, every `vagrant` invocation typically + # leads to a single Vagrant environment. class Environment # This is the current version that this version of Vagrant is # compatible with in the home directory. @@ -150,9 +152,24 @@ module Vagrant "#<#{self.class}: #{@cwd}>" end - #--------------------------------------------------------------- - # Helpers - #--------------------------------------------------------------- + # Action runner for executing actions in the context of this environment. + # + # @return [Action::Runner] + def action_runner + @action_runner ||= Action::Runner.new do + { + :action_runner => action_runner, + :box_collection => boxes, + :hook => method(:hook), + :host => host, + :gems_path => gems_path, + :home_path => home_path, + :root_path => root_path, + :tmp_path => tmp_path, + :ui => @ui + } + end + end # Returns a list of machines that this environment is currently # managing that physically have been created. @@ -218,6 +235,15 @@ module Vagrant end 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.new(args.flatten, self).execute + end + # 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 @@ -235,39 +261,25 @@ module Vagrant @_boxes ||= BoxCollection.new(boxes_path, temp_dir_root: tmp_path) end - # 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. + # Returns the {Config::Loader} that can be used to load Vagrantflies + # given the settings of this environment. # - # This is lazy-loaded upon first use. - # - # @return [Object] - def config_global - return @config_global if @config_global - - @logger.info("Initializing config...") + # @return [Config::Loader] + def config_loader + return @config_loader if @config_loader home_vagrantfile = nil root_vagrantfile = nil home_vagrantfile = find_vagrantfile(home_path) if home_path - root_vagrantfile = find_vagrantfile(root_path, @vagrantfile_name) if root_path + if root_path + root_vagrantfile = find_vagrantfile(root_path, @vagrantfile_name) + 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 = Config::Loader.new( + Config::VERSIONS, Config::VERSIONS_ORDER) @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([:home, :root]) - - # Return the config - @config_global + @config_loader end # This defines a hook point where plugin action hooks that are registered @@ -285,201 +297,6 @@ module Vagrant opts.delete(:runner).run(opts.delete(:callable), opts) end - # This returns a machine with the proper provider for this environment. - # The machine named by `name` must be in this environment. - # - # @param [Symbol] name Name of the machine (as configured in the - # Vagrantfile). - # @param [Symbol] provider The provider that this machine should be - # backed by. - # @param [Boolean] refresh If true, then if there is a cached version - # it is reloaded. - # @return [Machine] - 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_plugin = Vagrant.plugin("2").manager.providers[provider] - if !provider_plugin - raise Errors::ProviderNotFound, :machine => name, :provider => provider - end - - # Extra the provider class and options from the plugin data - provider_cls = provider_plugin[0] - provider_options = provider_plugin[1] - - # 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([:home, :root, vm_config_key]) - - # Determine the possible box formats for any boxes and find the box - box_formats = provider_options[:box_format] || provider - box = nil - - # Set this variable in order to keep track of if the box changes - # too many times. - original_box = config.vm.box - box_changed = false - - load_box_and_overrides = lambda do - box = nil - if config.vm.box - box = boxes.find( - config.vm.box, box_formats, config.vm.box_version) - 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([box_config_key, :home, :root, vm_config_key]) - end - end - - # If there are provider overrides for the machine, then we run - # those as well. - provider_overrides = config.vm.get_provider_overrides(provider) - if provider_overrides.length > 0 - @logger.info("Applying #{provider_overrides.length} provider overrides. Reloading config.") - provider_override_key = "vm_#{name}_#{config.vm.box}_#{provider}".to_sym - @config_loader.set(provider_override_key, provider_overrides) - config, config_warnings, config_errors = \ - @config_loader.load([box_config_key, :home, :root, vm_config_key, provider_override_key]) - end - - if config.vm.box && original_box != config.vm.box - if box_changed - # We already changed boxes once, so report an error that a - # box is attempting to change boxes again. - raise Errors::BoxConfigChangingBox - end - - # The box changed, probably due to the provider override. Let's - # run the configuration one more time with the new box. - @logger.info("Box changed to: #{config.vm.box}. Reloading configurations.") - original_box = config.vm.box - box_changed = true - - # Recurse so that we reload all the configurations - load_box_and_overrides.call - end - end - - # Load the box and overrides configuration - load_box_and_overrides.call - - # 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", - name: name, - :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, - provider_options, config, machine_data_path, box, self) - end - - # 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. - # - # @return [Array] Configured machine names. - def machine_names - config_global.vm.defined_vm_keys.dup - end - - # 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. - # - # @return [Symbol] - 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 - - # 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 - # - def unload - hook(:environment_unload) - 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.new(args.flatten, self).execute - end - # Returns the host object associated with this environment. # # @return [Class] @@ -490,7 +307,7 @@ module Vagrant # that shouldn't be valid anymore, but we respect it here by assuming # its old behavior. No need to deprecate this because I thin it is # fairly harmless. - host_klass = config_global.vagrant.host + host_klass = vagrantfile.config.vagrant.host host_klass = nil if host_klass == :detect begin @@ -516,46 +333,6 @@ module Vagrant end end - # Action runner for executing actions in the context of this environment. - # - # @return [Action::Runner] - def action_runner - @action_runner ||= Action::Runner.new do - { - :action_runner => action_runner, - :box_collection => boxes, - :global_config => config_global, - :hook => method(:hook), - :host => host, - :gems_path => gems_path, - :home_path => home_path, - :root_path => root_path, - :tmp_path => tmp_path, - :ui => @ui - } - end - 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] - 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`. - vf = find_vagrantfile(path, @vagrantfile_name) - return path if vf - return nil if path.root? || !File.exist?(path) - root_finder.call(path.parent) - end - - @root_path = root_finder.call(cwd) - end - # This returns the path which Vagrant uses to determine the location # of the file lock. This is specific to each operating system. def lock_path @@ -589,6 +366,116 @@ module Vagrant end end + # This returns a machine with the proper provider for this environment. + # The machine named by `name` must be in this environment. + # + # @param [Symbol] name Name of the machine (as configured in the + # Vagrantfile). + # @param [Symbol] provider The provider that this machine should be + # backed by. + # @param [Boolean] refresh If true, then if there is a cached version + # it is reloaded. + # @return [Machine] + 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.") + + # 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) + + # Create the machine and cache it for future calls. This will also + # return the machine from this method. + @machines[cache_key] = vagrantfile.machine( + name, provider, boxes, machine_data_path, self) + end + + # 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. + # + # @return [Array] Configured machine names. + def machine_names + vagrantfile.machine_names + end + + # 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. + # + # @return [Symbol] + def primary_machine_name + vagrantfile.primary_machine_name + 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] + 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`. + vf = find_vagrantfile(path, @vagrantfile_name) + return path if vf + return nil if path.root? || !File.exist?(path) + root_finder.call(path.parent) + end + + @root_path = root_finder.call(cwd) + end + + # 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 + # + def unload + hook(:environment_unload) + end + + # Represents the default Vagrantfile, or the Vagrantfile that is + # in the working directory or a parent of the working directory + # of this environment. + # + # The existence of this function is primarily a convenience. There + # is nothing stopping you from instantiating your own {Vagrantfile} + # and loading machines in any way you see fit. Typical behavior of + # Vagrant, however, loads this Vagrantfile. + # + # This Vagrantfile is comprised of two major sources: the Vagrantfile + # in the user's home directory as well as the "root" Vagrantfile or + # the Vagrantfile in the working directory (or parent). + # + # @return [Vagrantfile] + def vagrantfile + @vagrantfile ||= Vagrantfile.new(config_loader, [:home, :root]) + end + #--------------------------------------------------------------- # Load Methods #--------------------------------------------------------------- diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index e5c9651fb..e0728aafb 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -34,7 +34,7 @@ module Vagrant # Name of the machine. This is assigned by the Vagrantfile. # - # @return [String] + # @return [Symbol] attr_reader :name # The provider backing this machine. @@ -62,6 +62,11 @@ module Vagrant # @return [UI] attr_reader :ui + # The Vagrantfile that this machine is attached to. + # + # @return [Vagrantfile] + attr_reader :vagrantfile + # Initialize a new machine. # # @param [String] name Name of the virtual machine. @@ -77,7 +82,7 @@ module Vagrant # @param [Box] box The box that is backing this virtual machine. # @param [Environment] env The environment that this machine is a # part of. - def initialize(name, provider_name, provider_cls, provider_config, provider_options, config, data_dir, box, env, base=false) + 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}") @@ -88,6 +93,7 @@ module Vagrant @config = config @data_dir = data_dir @env = env + @vagrantfile = vagrantfile @guest = Guest.new( self, Vagrant.plugin("2").manager.guests, diff --git a/lib/vagrant/vagrantfile.rb b/lib/vagrant/vagrantfile.rb new file mode 100644 index 000000000..fde3769cf --- /dev/null +++ b/lib/vagrant/vagrantfile.rb @@ -0,0 +1,233 @@ +require "vagrant/util/template_renderer" + +module Vagrant + # This class provides a way to load and access the contents + # of a Vagrantfile. + # + # This class doesn't actually load Vagrantfiles, parse them, + # merge them, etc. That is the job of {Config::Loader}. This + # class, on the other hand, has higher-level operations on + # a loaded Vagrantfile such as looking up the defined machines, + # loading the configuration of a specific machine/provider combo, + # etc. + class Vagrantfile + # This is the configuration loaded as-is given the loader and + # keys to #initialize. + attr_reader :config + + # Initializes by loading a Vagrantfile. + # + # @param [Config::Loader] loader Configuration loader that should + # already be configured with the proper Vagrantflie locations. + # This usually comes from {Vagrant::Environment} + # @param [Array] keys The Vagrantfiles to load and the + # order to load them in (keys within the loader). + def initialize(loader, keys) + @keys = keys + @loader = loader + @config, _ = loader.load(keys) + end + + # Returns a {Machine} for the given name and provider that + # is represented by this Vagrantfile. + # + # @param [Symbol] name Name of the machine. + # @param [Symbol] provider The provider the machine should + # be backed by (required for provider overrides). + # @param [BoxCollection] boxes BoxCollection to look up the + # box Vagrantfile. + # @param [Pathname] data_path Path where local machine data + # can be stored. + # @param [Environment] env The environment running this machine + # @return [Machine] + def machine(name, provider, boxes, data_path, env) + # Load the configuration for the machine + results = machine_config(name, provider, boxes) + box = results[:box] + config = results[:config] + config_errors = results[:config_errors] + config_warnings = results[:config_warnings] + provider_cls = results[:provider_cls] + provider_options = results[:provider_options] + + # 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 + env.ui.send(level, I18n.t("vagrant.general.config_upgrade_messages", + name: name, + output: output)) + + # If we had errors, then we bail + raise Errors::ConfigUpgradeErrors if !config_errors.empty? + end + + # Get the provider configuration from the final loaded configuration + provider_config = config.vm.get_provider_config(provider) + + # Create the machine and cache it for future calls. This will also + # return the machine from this method. + return Machine.new(name, provider, provider_cls, provider_config, + provider_options, config, data_path, box, env, self) + end + + # Returns the configuration for a single machine. + # + # When loading a box Vagrantfile, it will be prepended to the + # key order specified when initializing this class. Sub-machine + # and provider-specific overrides are appended at the end. The + # actual order is: + # + # - box + # - keys specified for #initialize + # - sub-machine + # - provider + # + # The return value is a hash with the following keys (symbols) + # and values: + # + # - box: the {Box} backing the machine + # - config: the actual configuration + # - config_errors: list of errors, if any + # - config_warnings: list of warnings, if any + # - provider_cls: class of the provider backing the machine + # - provider_options: options for the provider + # + # @param [Symbol] name Name of the machine. + # @param [Symbol] provider The provider the machine should + # be backed by (required for provider overrides). + # @param [BoxCollection] boxes BoxCollection to look up the + # box Vagrantfile. + # @return [Hash] Various configuration parameters for a + # machine. See the main documentation body for more info. + def machine_config(name, provider, boxes) + keys = @keys.dup + + sub_machine = @config.vm.defined_vms[name] + if !sub_machine + raise Errors::MachineNotFound, + :name => name, :provider => provider + end + + provider_plugin = Vagrant.plugin("2").manager.providers[provider] + if !provider_plugin + raise Errors::ProviderNotFound, + :machine => name, :provider => provider + end + + provider_cls = provider_plugin[0] + provider_options = provider_plugin[1] + box_formats = provider_options[:box_format] || provider + + # Add the sub-machine configuration to the loader and keys + vm_config_key = "#{object_id}_machine_#{name}" + @loader.set(vm_config_key, sub_machine.config_procs) + keys << vm_config_key + + # Load once so that we can get the proper box value + config, config_warnings, config_errors = @loader.load(keys) + + # Track the original box so we know if we changed + box = nil + original_box = config.vm.box + + # The proc below loads the box and provider overrides. This is + # in a proc because it may have to recurse if the provider override + # changes the box. + load_box_proc = lambda do + local_keys = keys.dup + + # Load the box Vagrantfile, if there is one + if config.vm.box + box = boxes.find(config.vm.box, box_formats, config.vm.box_version) + if box + box_vagrantfile = find_vagrantfile(box.directory) + if box_vagrantfile + box_config_key = + "#{boxes.object_id}_#{box.name}_#{box.provider}".to_sym + @loader.set(box_config_key, box_vagrantfile) + local_keys.unshift(box_config_key) + config, config_warnings, config_errors = @loader.load(local_keys) + end + end + end + + # Load provider overrides + provider_overrides = config.vm.get_provider_overrides(provider) + if !provider_overrides.empty? + config_key = + "#{object_id}_vm_#{name}_#{config.vm.box}_#{provider}".to_sym + @loader.set(config_key, provider_overrides) + local_keys << config_key + config, config_warnings, config_errors = @loader.load(local_keys) + end + + # If the box changed, then we need to reload + if original_box != config.vm.box + # TODO: infinite loop protection? + + original_box = config.vm.box + load_box_proc.call + end + end + + # Load the box and provider overrides + load_box_proc.call + + return { + box: box, + provider_cls: provider_cls, + provider_options: provider_options, + config: config, + config_warnings: config_warnings, + config_errors: config_errors, + } + end + + # Returns a list of the machines that are defined within this + # Vagrantfile. + # + # @return [Array] + def machine_names + @config.vm.defined_vm_keys.dup + end + + # Returns the name of the machine that is designated as 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 is the machine that is marked as primary, or nil if + # no primary machine was specified. + # + # @return [Symbol] + 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.vm.defined_vms.each do |name, subvm| + return name if subvm.options[:primary] + end + + # If no primary was specified, nil it is + nil + end + + protected + + def find_vagrantfile(search_path) + ["Vagrantfile", "vagrantfile"].each do |vagrantfile| + current_path = search_path.join(vagrantfile) + return current_path if current_path.file? + end + + nil + end + end +end diff --git a/plugins/commands/box/command/repackage.rb b/plugins/commands/box/command/repackage.rb index fa986119a..ffff2b0ff 100644 --- a/plugins/commands/box/command/repackage.rb +++ b/plugins/commands/box/command/repackage.rb @@ -31,7 +31,7 @@ module VagrantPlugins raise Vagrant::Errors::BoxNotFound, :name => box_name, :provider => box_provider if !box # Repackage the box - output_name = @env.config_global.package.name || "package.box" + output_name = @env.vagrantfile.config.package.name || "package.box" output_path = Pathname.new(File.expand_path(output_name, FileUtils.pwd)) box.repackage(output_path) diff --git a/plugins/commands/package/command.rb b/plugins/commands/package/command.rb index 255c6f95e..92541be60 100644 --- a/plugins/commands/package/command.rb +++ b/plugins/commands/package/command.rb @@ -59,9 +59,9 @@ module VagrantPlugins vm = Vagrant::Machine.new( options[:base], :virtualbox, provider[0], nil, provider[1], - @env.config_global, + @env.vagrantfile.config, nil, nil, - @env, true) + @env, @env.vagrantfile, true) @logger.debug("Packaging base VM: #{vm.name}") package_vm(vm, options) end diff --git a/test/unit/plugins/providers/virtualbox/action/prepare_nfs_valid_ids_test.rb b/test/unit/plugins/providers/virtualbox/action/prepare_nfs_valid_ids_test.rb index 374f07132..23a2328a6 100644 --- a/test/unit/plugins/providers/virtualbox/action/prepare_nfs_valid_ids_test.rb +++ b/test/unit/plugins/providers/virtualbox/action/prepare_nfs_valid_ids_test.rb @@ -1,30 +1,25 @@ require_relative "../base" describe VagrantPlugins::ProviderVirtualBox::Action::PrepareNFSValidIds do + include_context "unit" include_context "virtualbox" - let(:machine) { - environment = Vagrant::Environment.new - provider = :virtualbox - provider_cls, provider_options = Vagrant.plugin("2").manager.providers[provider] - provider_config = Vagrant.plugin("2").manager.provider_configs[provider] + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end - Vagrant::Machine.new( - 'test_machine', - provider, - provider_cls, - provider_config, - provider_options, - environment.config_global, - Pathname('data_dir'), - double('box'), - environment - ) - } + let(:machine) do + iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m| + m.provider.stub(driver: driver) + end + end let(:env) {{ machine: machine }} let(:app) { lambda { |*args| }} - let(:driver) { env[:machine].provider.driver } + let(:driver) { double("driver") } subject { described_class.new(app, env) } diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index 2e7758dcd..4478b5abf 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -833,7 +833,7 @@ VF end env = environment.create_vagrant_env - env.config_global.ssh.port.should == 200 + env.vagrantfile.config.ssh.port.should == 200 end it "should load from a custom Vagrantfile" do @@ -846,7 +846,7 @@ VF end env = environment.create_vagrant_env(:vagrantfile_name => "non_standard_name") - env.config_global.ssh.port.should == 200 + env.vagrantfile.config.ssh.port.should == 200 end it "should load from a custom Vagrantfile specified by env var" do @@ -862,7 +862,7 @@ VF environment.create_vagrant_env end - env.config_global.ssh.port.should == 400 + env.vagrantfile.config.ssh.port.should == 400 end end diff --git a/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index 0be5455be..8e48dd511 100644 --- a/test/unit/vagrant/machine_test.rb +++ b/test/unit/vagrant/machine_test.rb @@ -21,7 +21,7 @@ describe Vagrant::Machine do let(:provider_name) { :test } let(:provider_options) { {} } let(:box) { Object.new } - let(:config) { env.config_global } + let(:config) { env.vagrantfile.config } let(:data_dir) { Pathname.new(Dir.mktmpdir("vagrant")) } let(:env) do # We need to create a Vagrantfile so that this test environment @@ -41,7 +41,8 @@ describe Vagrant::Machine do # Returns a new instance with the test data def new_instance described_class.new(name, provider_name, provider_cls, provider_config, - provider_options, config, data_dir, box, env) + provider_options, config, data_dir, box, + env, env.vagrantfile) end describe "initialization" do @@ -77,7 +78,8 @@ describe Vagrant::Machine do # Initialize a new machine and verify that we properly receive # the machine we expect. instance = described_class.new(name, provider_name, provider_cls, provider_config, - provider_options, config, data_dir, box, env) + provider_options, config, data_dir, box, + env, env.vagrantfile) received_machine.should eql(instance) end @@ -110,6 +112,12 @@ describe Vagrant::Machine do end end + it "should have the vagrantfile" do + provider_init_test do |machine| + expect(machine.vagrantfile).to equal(env.vagrantfile) + end + end + it "should have access to the ID" do # Stub this because #id= calls it. provider.stub(:machine_id_changed) diff --git a/test/unit/vagrant/plugin/v2/plugin_test.rb b/test/unit/vagrant/plugin/v2/plugin_test.rb index 226a0f875..3341bf87d 100644 --- a/test/unit/vagrant/plugin/v2/plugin_test.rb +++ b/test/unit/vagrant/plugin/v2/plugin_test.rb @@ -1,10 +1,8 @@ require File.expand_path("../../../../base", __FILE__) describe Vagrant::Plugin::V2::Plugin do - after(:each) do - # We want to make sure that the registered plugins remains empty - # after each test. - described_class.manager.reset! + before do + described_class.stub(manager: Vagrant::Plugin::V2::Manager.new) end it "should be able to set and get the name" do diff --git a/test/unit/vagrant/vagrantfile_test.rb b/test/unit/vagrant/vagrantfile_test.rb new file mode 100644 index 000000000..7fbaa085e --- /dev/null +++ b/test/unit/vagrant/vagrantfile_test.rb @@ -0,0 +1,324 @@ +require File.expand_path("../../base", __FILE__) + +require "pathname" +require "tmpdir" + +require "vagrant/vagrantfile" + +describe Vagrant::Vagrantfile do + include_context "unit" + + let(:keys) { [] } + let(:loader) { + Vagrant::Config::Loader.new( + Vagrant::Config::VERSIONS, Vagrant::Config::VERSIONS_ORDER) + } + + subject { described_class.new(loader, keys) } + + before do + keys << :test + end + + def configure(&block) + loader.set(:test, [["2", block]]) + end + + # A helper to register a provider for use in tests. + def register_provider(name, config_class=nil, options=nil) + provider_cls = Class.new(Vagrant.plugin("2", :provider)) + + register_plugin("2") do |p| + p.provider(name, options) { provider_cls } + + if config_class + p.config(name, :provider) { config_class } + end + end + + provider_cls + end + + describe "#config" do + it "exposes the global configuration" do + configure do |config| + config.vm.box = "what" + end + + expect(subject.config.vm.box).to eq("what") + end + end + + describe "#machine" do + let(:boxes) { Vagrant::BoxCollection.new(iso_env.boxes_dir) } + let(:data_path) { Pathname.new(Dir.mktmpdir) } + let(:env) { iso_env.create_vagrant_env } + let(:iso_env) { isolated_environment } + let(:vagrantfile) { described_class.new(loader, keys) } + + subject { vagrantfile.machine(:default, :foo, boxes, data_path, env) } + + before do + @foo_config_cls = Class.new(Vagrant.plugin("2", "config")) do + attr_accessor :value + end + + @provider_cls = register_provider("foo", @foo_config_cls) + + configure do |config| + config.vm.box = "foo" + config.vm.provider "foo" do |p| + p.value = "rawr" + end + end + + iso_env.box3("foo", "1.0", :foo, vagrantfile: <<-VF) + Vagrant.configure("2") do |config| + config.ssh.port = 123 + end + VF + end + + its(:data_dir) { should eq(data_path) } + its(:env) { should equal(env) } + its(:name) { should eq(:default) } + its(:provider) { should be_kind_of(@provider_cls) } + its(:provider_name) { should eq(:foo) } + its(:vagrantfile) { should equal(vagrantfile) } + + it "has the proper box" do + expect(subject.box.name).to eq("foo") + end + + it "has the valid configuration" do + expect(subject.config.vm.box).to eq("foo") + end + + it "loads the provider-specific configuration" do + expect(subject.provider_config).to be_kind_of(@foo_config_cls) + expect(subject.provider_config.value).to eq("rawr") + end + end + + describe "#machine_config" do + let(:iso_env) { isolated_environment } + let(:boxes) { Vagrant::BoxCollection.new(iso_env.boxes_dir) } + + it "should return a basic configured machine" do + provider_cls = register_provider("foo") + + configure do |config| + config.vm.box = "foo" + end + + results = subject.machine_config(:default, :foo, boxes) + box = results[:box] + config = results[:config] + expect(config.vm.box).to eq("foo") + expect(box).to be_nil + expect(results[:provider_cls]).to equal(provider_cls) + end + + it "configures with sub-machine config" do + register_provider("foo") + + configure do |config| + config.ssh.port = "1" + config.vm.box = "base" + + config.vm.define "foo" do |f| + f.ssh.port = 100 + end + end + + results = subject.machine_config(:foo, :foo, boxes) + config = results[:config] + expect(config.vm.box).to eq("base") + expect(config.ssh.port).to eq(100) + end + + it "configures with box configuration if it exists" do + register_provider("foo") + + configure do |config| + config.vm.box = "base" + end + + iso_env.box3("base", "1.0", :foo, vagrantfile: <<-VF) + Vagrant.configure("2") do |config| + config.ssh.port = 123 + end + VF + + results = subject.machine_config(:default, :foo, boxes) + box = results[:box] + config = results[:config] + expect(config.vm.box).to eq("base") + expect(config.ssh.port).to eq(123) + expect(box).to_not be_nil + expect(box.name).to eq("base") + end + + it "configures with the proper box version" do + register_provider("foo") + + configure do |config| + config.vm.box = "base" + config.vm.box_version = "~> 1.2" + end + + iso_env.box3("base", "1.0", :foo, vagrantfile: <<-VF) + Vagrant.configure("2") do |config| + config.ssh.port = 123 + end + VF + + iso_env.box3("base", "1.3", :foo, vagrantfile: <<-VF) + Vagrant.configure("2") do |config| + config.ssh.port = 245 + end + VF + + results = subject.machine_config(:default, :foo, boxes) + box = results[:box] + config = results[:config] + expect(config.vm.box).to eq("base") + expect(config.ssh.port).to eq(245) + expect(box).to_not be_nil + expect(box.name).to eq("base") + expect(box.version).to eq("1.3") + end + + it "configures with box config of other supported formats" do + register_provider("foo", nil, box_format: "bar") + + configure do |config| + config.vm.box = "base" + end + + iso_env.box3("base", "1.0", :bar, vagrantfile: <<-VF) + Vagrant.configure("2") do |config| + config.ssh.port = 123 + end + VF + + results = subject.machine_config(:default, :foo, boxes) + config = results[:config] + expect(config.vm.box).to eq("base") + expect(config.ssh.port).to eq(123) + end + + it "loads provider overrides if set" do + register_provider("foo") + register_provider("bar") + + configure do |config| + config.ssh.port = 1 + config.vm.box = "base" + + config.vm.provider "foo" do |_, c| + c.ssh.port = 100 + end + end + + # Test with the override + results = subject.machine_config(:default, :foo, boxes) + config = results[:config] + expect(config.vm.box).to eq("base") + expect(config.ssh.port).to eq(100) + + # Test without the override + results = subject.machine_config(:default, :bar, boxes) + config = results[:config] + expect(config.vm.box).to eq("base") + expect(config.ssh.port).to eq(1) + end + + it "loads the proper box if in a provider override" do + register_provider("foo") + + configure do |config| + config.vm.box = "base" + + config.vm.provider "foo" do |_, c| + c.vm.box = "foobox" + end + end + + iso_env.box3("base", "1.0", :foo, vagrantfile: <<-VF) + Vagrant.configure("2") do |config| + config.ssh.port = 123 + end + VF + + iso_env.box3("foobox", "1.0", :foo, vagrantfile: <<-VF) + Vagrant.configure("2") do |config| + config.ssh.port = 234 + end + VF + + results = subject.machine_config(:default, :foo, boxes) + config = results[:config] + box = results[:box] + expect(config.vm.box).to eq("foobox") + expect(config.ssh.port).to eq(234) + expect(box).to_not be_nil + expect(box.name).to eq("foobox") + end + + it "raises an error if the machine is not found" do + expect { subject.machine_config(:foo, :foo, boxes) }. + to raise_error(Vagrant::Errors::MachineNotFound) + end + + it "raises an error if the provider is not found" do + expect { subject.machine_config(:default, :foo, boxes) }. + to raise_error(Vagrant::Errors::ProviderNotFound) + end + end + + describe "#machine_names" do + it "returns the default name when single-VM" do + configure { |config| } + + expect(subject.machine_names).to eq([:default]) + end + + it "returns all of the names in a multi-VM" do + configure do |config| + config.vm.define "foo" + config.vm.define "bar" + end + + expect(subject.machine_names).to eq( + [:foo, :bar]) + end + end + + describe "#primary_machine_name" do + it "returns the default name when single-VM" do + configure { |config| } + + expect(subject.primary_machine_name).to eq(:default) + end + + it "returns the designated machine in multi-VM" do + configure do |config| + config.vm.define "foo" + config.vm.define "bar", primary: true + config.vm.define "baz" + end + + expect(subject.primary_machine_name).to eq(:bar) + end + + it "returns nil if no designation in multi-VM" do + configure do |config| + config.vm.define "foo" + config.vm.define "baz" + end + + expect(subject.primary_machine_name).to be_nil + end + end +end